/* GENERATED — DO NOT EDIT.
 * Theme custom properties (:root, [data-theme]) come from
 *   resources/bao/tokens/tokens.bao
 * Rule bodies come from
 *   resources/bao/styles/rules/*.css.bao
 * Concatenation order, layering, and manifest-derived utilities come from
 *   packages/bao-compiler/src/tokens/*.ts
 * Edit whichever source owns the rule, then run `bun run compile:bao:all`.
 */

@layer base {
  :root {
    --font-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', system-ui, 'Segoe UI', sans-serif;
    --font-brand: -apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, 'Segoe UI', sans-serif;
    --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    --font-weight-normal: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --font-weight-bold: 700;
    --text-2xs: 0.6875rem;
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --text-2xl: 1.5rem;
    --text-3xl: 1.875rem;
    --text-4xl: 2.25rem;
    --leading-tight: 1.25;
    --leading-snug: 1.375;
    --leading-normal: 1.5;
    --leading-relaxed: 1.625;
    --tracking-tighter: -0.05em;
    --tracking-tight: -0.025em;
    --tracking-normal: 0em;
    --tracking-wide: 0.025em;
    --tracking-wider: 0.05em;
    --tracking-otp: 0.35em;
    --tracking-control: -0.01em;
    --tracking-display: -0.02em;
    --tracking-eyebrow: 0.04em;
    --accent-hover: #105A94;
    --accent-primary: #1766A8;
    --accent-primary-content: #ffffff;
    --accent-secondary: #F56E28;
    --accent-secondary-content: #18181b;
    --accent-secondary-hover: #E05A16;
    --annotation-accent: #0369a1;
    --annotation-canvas-bg: #f0f1f3;
    --annotation-grid: #71717a;
    --annotation-grid-major: #71717a;
    --annotation-grid-minor: #dfe1e5;
    --annotation-on-surface: #18181b;
    --annotation-paper-white: #ffffff;
    --annotation-pin: #0369a1;
    --annotation-redact-fill: #000000;
    --annotation-slide-edge: #ffffff;
    --annotation-stroke: #b91c1c;
    --annotation-stroke-ai-pending: #b45309;
    --annotation-stroke-measurement: #0369a1;
    --annotation-stroke-rejected: #7f1d1d;
    --annotation-stroke-selected: #1E73BE;
    --annotation-surface: #ffffff;
    --bg-app: #f5f5f7;
    --bg-panel: #ffffff;
    --bg-surface: #eeeef0;
    --bg-surface-hover: #e5e5ea;
    --black: #000000;
    --border-base: #d9d9de;
    --border-control: #73737a;
    --focus-ring-color: #1E73BE;
    --glass-button-highlight: rgba(255, 255, 255, 0.08);
    --glass-button-tint-opacity: 0.98;
    --glass-control-border: rgba(15, 23, 42, 0.08);
    --glass-control-fill: rgba(255, 255, 255, 0.10);
    --glass-edge-fade: rgba(15, 23, 42, 0.06);
    --glass-fill-bg: rgba(255, 255, 255, 0.68);
    --glass-fill-border: rgba(24, 24, 27, 0.10);
    --glass-highlight-clear: rgba(255, 255, 255, 0);
    --glass-highlight-soft: rgba(255, 255, 255, 0.18);
    --glass-highlight-strong: rgba(255, 255, 255, 0.65);
    --glass-illumination-soft: rgba(255, 255, 255, 0.05);
    --glass-illumination-strong: rgba(255, 255, 255, 0.15);
    --glass-mask-source: #ffffff;
    --glass-noise-blend: overlay;
    --glass-noise-size: 128px;
    --glass-noise-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
    --glass-scrim: rgba(0, 0, 0, 0.35);
    --glass-scrollbar-thumb: color-mix(in oklab, var(--color-base-content) 40%, transparent);
    --glass-scrollbar-track: transparent;
    --glass-shadow-soft: rgba(0, 0, 0, 0.06);
    --glass-shadow-strong: rgba(0, 0, 0, 0.12);
    --lg-pointer-angle: 135deg;
    --liquid-blur-clear: 8px;
    --liquid-blur-mobile: 8px;
    --liquid-blur-nav: 32px;
    --liquid-blur-none: 0px;
    --liquid-blur-regular: 20px;
    --liquid-blur-small: 12px;
    --liquid-blur-tablet: 16px;
    --liquid-border-clear: rgba(255, 255, 255, 0.10);
    --liquid-border-contrast: rgba(24, 24, 27, 0.65);
    --liquid-border-regular: rgba(24, 24, 27, 0.16);
    --liquid-fill-blocking: 0.88;
    --liquid-fill-clear: 0.22;
    --liquid-fill-control: 0.10;
    --liquid-fill-persistent: 0.58;
    --liquid-fill-persistent-coarse: 0.76;
    --liquid-fill-tinted: 0.74;
    --liquid-fill-transient: 0.84;
    --liquid-lens-blur: 1;
    --liquid-lens-frequency: 0.012 0.012;
    --liquid-lens-octaves: 2;
    --liquid-lens-scale: -28;
    --liquid-lens-seed: 7;
    --liquid-light-angle: 135deg;
    --liquid-saturate-clear: 1.35;
    --liquid-saturate-mobile: 1.3;
    --liquid-saturate-regular: 1.5;
    --liquid-saturate-strong: 1.8;
    --liquid-saturate-tablet: 1.4;
    --overlay-backdrop: var(--glass-scrim);
    --overlay-btn-secondary-bg: rgba(255, 255, 255, 0.12);
    --overlay-btn-secondary-border: rgba(255, 255, 255, 0.25);
    --overlay-grid-line: rgba(255, 255, 255, 0.3);
    --overlay-label-bg: rgba(0, 0, 0, 0.75);
    --overlay-shade: rgba(0, 0, 0, 0.5);
    --overlay-text-dim: rgba(255, 255, 255, 0.7);
    --overlay-text-muted: rgba(255, 255, 255, 0.85);
    --overlay-text-primary: #ffffff;
    --overlay-toolbar-bg: rgba(0, 0, 0, 0.82);
    --overlay-toolbar-border: rgba(255, 255, 255, 0.1);
    --status-error: #b91c1c;
    --status-error-content: #ffffff;
    --status-info: #0369a1;
    --status-info-content: #ffffff;
    --status-success: #065f46;
    --status-success-content: #ffffff;
    --status-warning: #b05109;
    --status-warning-content: #ffffff;
    --text-primary: #18181b;
    --text-secondary: #52525b;
    --text-tertiary: #67676f;
    --white: #ffffff;
    --space-2xs: 0.125rem;
    --space-xs: 0.1875rem;
    --space-page: 1rem;
    --space-section: 0.75rem;
    --space-inline: 0.5rem;
    --space-micro: 0.25rem;
    --space-lg: 1.25rem;
    --space-xl: 1.5rem;
    --space-2xl: 2rem;
    --space-3xl: 3rem;
    --space-empty: 3rem;
    --radius-control: 0.75rem;
    --radius-panel: 1.25rem;
    --radius-xs: 0.25rem;
    --radius-sm: 0.5rem;
    --radius-md: 0.75rem;
    --radius-lg: 1rem;
    --radius-xl: 1rem;
    --radius-2xl: 1.5rem;
    --radius-full: 9999px;
    --radius-none: 0;
    --touch-target-min: max(2.75rem, 44px);
    --touch-target-compact: 2.25rem;
    --checkbox-mark-size: 1.5rem;
    --control-mini: 1.75rem;
    --control-small: 2rem;
    --control-regular: 2.75rem;
    --control-large: 3.25rem;
    --control-xlarge: 4rem;
    --sidebar-row-small: 1.75rem;
    --sidebar-row-medium: 2.25rem;
    --sidebar-row-large: 2.75rem;
    --pane-divider: 1px;
    --inspector-pane-min: 14rem;
    --inspector-pane-max: 32rem;
    --breakpoint-sm: 640px;
    --breakpoint-md: 768px;
    --breakpoint-lg: 1024px;
    --breakpoint-xl: 1280px;
    --breakpoint-2xl: 1536px;
    --duration-instant: 0ms;
    --duration-fast: 120ms;
    --duration-normal: 200ms;
    --duration-slow: 320ms;
    --duration-glass: 280ms;
    --duration-press: 80ms;
    --duration-focus-pulse: 1.5s;
    --duration-skeleton: 1.8s;
    --duration-progress: 2s;
    --duration-reduced-motion-calm: 15s;
    --easing-standard: cubic-bezier(0.2, 0, 0, 1);
    --easing-entrance: cubic-bezier(0.05, 0.7, 0.1, 1);
    --easing-exit: cubic-bezier(0.3, 0, 0.8, 0.15);
    --easing-glass: cubic-bezier(0.32, 0.72, 0, 1);
    --reduced-motion-min-duration: 0.001ms;
    --entrance-offset: 0.375rem;
    --stagger-step: 30ms;
    --view-transition-new-delay: 80ms;
    --duration-pulse: 800ms;
    --duration-pulse-alpha: 1000ms;
    --pulse-alpha-floor: 0.55;
    --pulse-scale-peak: 1.12;
    --opacity-disabled: 0.5;
    --opacity-muted: 0.55;
    --opacity-soft: 0.65;
    --opacity-medium: 0.75;
    --opacity-strong: 0.85;
    --opacity-near: 0.9;
    --opacity-chrome: 0.92;
    --hover-scale: 1.02;
    --focus-ring-width: 2px;
    --focus-ring-offset: 2px;
    --focus-ring-offset-snug: 1px;
    --focus-ring-offset-tight: -2px;
    --focus-panel-ring-alpha: 40%;
    --density-compact-row-height: 2rem;
    --density-compact-font-size: 0.8125rem;
    --density-default-row-height: 2.25rem;
    --density-default-font-size: 0.875rem;
    --density-comfortable-row-height: 2.5rem;
    --density-comfortable-font-size: 0.9375rem;
    --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.04);
    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.08);
    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.08);
    --shadow-glass-elevated: 0 8px 24px -4px var(--glass-shadow-strong), 0 2px 6px -2px var(--glass-shadow-soft);
    --shadow-glass-floating: 0 12px 32px -8px var(--glass-shadow-strong), 0 4px 12px -4px var(--glass-shadow-soft);
    --shadow-glass-clear: 0 1px 0 0 var(--liquid-border-clear) inset, 0 4px 16px -6px var(--glass-shadow-soft);
    --shadow-glass-inner: 0 1px 0 0 color-mix(in oklab, var(--glass-highlight-strong) 60%, transparent) inset, 0 -1px 0 0 color-mix(in oklab, var(--glass-shadow-soft) 30%, transparent) inset;
    --shadow-overlay-toolbar: 0 -4px 12px -2px rgb(0 0 0 / 0.20);
    --z-base: 0;
    --z-local: 1;
    --z-raised: 2;
    --z-raised-high: 3;
    --z-tabs-sticky: 5;
    --z-canvas-base: 1;
    --z-canvas-empty: 20;
    --z-canvas-overlay: 30;
    --z-canvas-interaction: 31;
    --z-content-overlay: 10;
    --z-table-header: 10;
    --z-table-action: 20;
    --z-shell-drawer: 1125;
    --z-shell-drawer-overlay: 1300;
    --z-shell-topbar: 1250;
    --z-dropdown: 1250;
    --z-sticky: 1100;
    --z-overlay: 1200;
    --z-modal: 1300;
    --z-toast: 1400;
    --z-tooltip: 1400;
    --page-standard: 80rem;
    --page-wide: 96rem;
    --page-full: 100%;
    --page-conversation: 48rem;
    --description: 65ch;
    --overlay-panel: 20rem;
    --auth: 28rem;
    --form: 44rem;
    --grid-track-min: 18rem;
    --grid-panel-quarter-min: 12rem;
    --grid-panel-third-min: 16rem;
    --grid-panel-half-min: 24rem;
    --grid-panel-span-min: 38rem;
    --grid-form-min: 16rem;
    --grid-choice-min: 14rem;
    --grid-context-min: 10rem;
    --dock-primary-basis: 32rem;
    --dock-side-basis: 15rem;
    --dock-side-wide-basis: 18rem;
    --selection-column-width: 2.5rem;
    --editor-canvas-basis: 32rem;
    --editor-inspector-basis: 24rem;
    --editor-tool-rail-basis: 12rem;
    --editor-palette-basis: 14rem;
    --media-primary-basis: 24rem;
    --media-inspector-basis: 20rem;
    --skeleton-panel-min-height: 7rem;
    --skeleton-media-min-height: 12rem;
    --sheet-panel-width: 20rem;
    --sheet-panel-max-width: 85vw;
    --command-palette-max-width: 48rem;
    --keyboard-shortcuts-max-width: 36rem;
    --settings-dialog-max-width: 56rem;
    --palette-max-height: 16rem;
    --chrome-min-height: 2.75rem;
    --scroll-edge-fade: 1.5rem;
    --scroll-edge-mask: linear-gradient(to bottom, transparent, black 1.5rem);
    --modal-max-height: min(92dvh, 92vh);
    --modal-default-max-width: 32rem;
    --modal-tablet-max-width: 40rem;
    --modal-scroll-max-height: min(70dvh, 70vh);
    --modal-body-max-height: min(58dvh, 58vh);
    --editor-root-min-height: calc(100dvh - 8rem);
    --editor-canvas-height: clamp(26rem, 58vh, 44rem);
    --canvas-region-min-height: clamp(18rem, 42vh, 30rem);
    --panel-scroll-max-height: min(70dvh, 70vh);
    --panel-body-min-height: 15rem;
    --rail-composer-min-height: 15rem;
    --composer-min-height: 5rem;
    --composer-max-height: 20rem;
    --attachment-preview-size: 5rem;
    --scroll-pt-shell: 3.5rem;
    --scroll-pt-main: 3rem;
    --scroll-mt-section: 6rem;
    --shell-drawer-width: 20rem;
    --shell-drawer-max-viewport: 88vw;
    --brand-mark-size: 2.5rem;
    --icon-gutter: 2rem;
    --page-gutter-inline: min(clamp(1rem, 2vw, 1.5rem), 5vw);
    --page-gutter-block: clamp(0.75rem, 1.5vw, 1.25rem);
    --page-gutter-inline-compact: min(clamp(0.75rem, 3vw, 1rem), 5vw);
    --page-section-gap: clamp(0.75rem, 1.5vw, 1.5rem);
    --scroll-edge-width: 2.5rem;
    --scroll-edge-cue-width: 0.875rem;
    --scroll-edge-cue-opacity: 14%;
    --scroll-edge-cue-opacity-contrast: 55%;
    --toolbar-search-max-width: 7rem;
    --toolbar-spacer-min-width: 0.5rem;
    --toolbar-label-max-width: 8rem;
    --table-row-actions-max-inline: 16rem;
    --toolbar-label-reveal-max-width: 12rem;
    --toolbar-action-pad-inline: 0.625rem;
    --toolbar-action-min-width: 2.5rem;
    --toast-max-inline-size: 20rem;
    --dock-label-max-width: 4.5rem;
    --space-compact: 0.375rem;
    --tracking-label: 0.01em;
    --annotation-stroke-width-base: 2;
    --annotation-stroke-width-hairline: 1;
    --annotation-stroke-width-volume: 6;
    --annotation-sphere-radius-px: 12;
    --annotation-volume-proxy-size-px: 32;
    --annotation-pen-scale-min: 0.55;
    --annotation-pen-scale-range: 1.45;
    --annotation-grid-spacing-px: 50;
    --annotation-point-radius-px: 4;
    --annotation-label-font-size-px: 18;
    --tooltip-max-width: 20rem;
    --tooltip-viewport-inset: 0.75rem;
    --sidebar-expanded: 16rem;
    --sidebar-collapsed: 4rem;
    --session-rail-expanded: 18rem;
    --session-rail-collapsed: 3rem;
    --session-rail-min: 12rem;
    --session-rail-max: 32rem;
    --session-rail-main-min: 36rem;
    --session-rail-resize-step: 1rem;
    --session-rail-resize-step-large: 4rem;
  }

  [data-theme="capture-dx-dark"] {
    --accent-hover: #1E73BE;
    --accent-primary: #41B6E6;
    --accent-primary-content: #0c1017;
    --accent-secondary: #F56E28;
    --accent-secondary-content: #18181b;
    --accent-secondary-hover: #FF8040;
    --annotation-accent: #41B6E6;
    --annotation-canvas-bg: #1e2633;
    --annotation-grid: #71717a;
    --annotation-grid-major: #71717a;
    --annotation-grid-minor: #1e2633;
    --annotation-on-surface: #f4f4f5;
    --annotation-paper-white: #ffffff;
    --annotation-pin: #41B6E6;
    --annotation-redact-fill: #000000;
    --annotation-slide-edge: #151b24;
    --annotation-stroke: #dc2626;
    --annotation-stroke-ai-pending: #d97706;
    --annotation-stroke-measurement: #41B6E6;
    --annotation-stroke-rejected: #f87171;
    --annotation-stroke-selected: #41B6E6;
    --annotation-surface: #151b24;
    --bg-app: #101012;
    --bg-panel: #1c1c1e;
    --bg-surface: #2c2c2e;
    --bg-surface-hover: #3a3a3c;
    --black: #000000;
    --border-base: #3a3a3c;
    --border-control: #9b9da1;
    --focus-ring-color: #41B6E6;
    --glass-button-highlight: rgba(255, 255, 255, 0.24);
    --glass-button-tint-opacity: 0.92;
    --glass-control-border: rgba(255, 255, 255, 0.10);
    --glass-control-fill: rgba(255, 255, 255, 0.14);
    --glass-edge-fade: rgba(0, 0, 0, 0.22);
    --glass-fill-bg: rgba(28, 28, 30, 0.76);
    --glass-fill-border: rgba(255, 255, 255, 0.12);
    --glass-highlight-clear: rgba(255, 255, 255, 0);
    --glass-highlight-soft: rgba(255, 255, 255, 0.09);
    --glass-highlight-strong: rgba(255, 255, 255, 0.28);
    --glass-illumination-soft: rgba(255, 255, 255, 0.03);
    --glass-illumination-strong: rgba(255, 255, 255, 0.08);
    --glass-mask-source: #ffffff;
    --glass-noise-blend: overlay;
    --glass-noise-size: 128px;
    --glass-noise-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.02'/%3E%3C/svg%3E");
    --glass-scrim: rgba(0, 0, 0, 0.35);
    --glass-scrollbar-thumb: color-mix(in oklab, var(--color-base-content) 40%, transparent);
    --glass-scrollbar-track: transparent;
    --glass-shadow-soft: rgba(0, 0, 0, 0.20);
    --glass-shadow-strong: rgba(0, 0, 0, 0.34);
    --lg-pointer-angle: 135deg;
    --liquid-blur-clear: 8px;
    --liquid-blur-mobile: 10px;
    --liquid-blur-nav: 36px;
    --liquid-blur-none: 0px;
    --liquid-blur-regular: 24px;
    --liquid-blur-small: 14px;
    --liquid-blur-tablet: 20px;
    --liquid-border-clear: rgba(255, 255, 255, 0.08);
    --liquid-border-contrast: rgba(244, 244, 245, 0.30);
    --liquid-border-regular: rgba(255, 255, 255, 0.18);
    --liquid-fill-blocking: 0.92;
    --liquid-fill-clear: 0.30;
    --liquid-fill-control: 0.14;
    --liquid-fill-persistent: 0.68;
    --liquid-fill-persistent-coarse: 0.82;
    --liquid-fill-tinted: 0.78;
    --liquid-fill-transient: 0.88;
    --liquid-lens-blur: 1;
    --liquid-lens-frequency: 0.012 0.012;
    --liquid-lens-octaves: 2;
    --liquid-lens-scale: -28;
    --liquid-lens-seed: 7;
    --liquid-light-angle: 135deg;
    --liquid-saturate-clear: 1.35;
    --liquid-saturate-mobile: 1.3;
    --liquid-saturate-regular: 1.5;
    --liquid-saturate-strong: 1.8;
    --liquid-saturate-tablet: 1.4;
    --overlay-backdrop: var(--glass-scrim);
    --overlay-btn-secondary-bg: rgba(255, 255, 255, 0.12);
    --overlay-btn-secondary-border: rgba(255, 255, 255, 0.25);
    --overlay-grid-line: rgba(255, 255, 255, 0.3);
    --overlay-label-bg: rgba(0, 0, 0, 0.75);
    --overlay-shade: rgba(0, 0, 0, 0.5);
    --overlay-text-dim: rgba(255, 255, 255, 0.7);
    --overlay-text-muted: rgba(255, 255, 255, 0.85);
    --overlay-text-primary: #ffffff;
    --overlay-toolbar-bg: rgba(0, 0, 0, 0.82);
    --overlay-toolbar-border: rgba(255, 255, 255, 0.1);
    --status-error: #f87171;
    --status-error-content: #0c1017;
    --status-info: #41B6E6;
    --status-info-content: #0c1017;
    --status-success: #25ac83;
    --status-success-content: #0c1017;
    --status-warning: #e58212;
    --status-warning-content: #18181b;
    --text-primary: #f4f4f5;
    --text-secondary: #b6b6bd;
    --text-tertiary: #8b8b96;
    --white: #ffffff;
  }
}


@layer base {
  /*
   * Scrollbar presentation has exactly one owner (design.pdf: clipped content
   * must be discoverable).
   *
   * Seventeen declarations across fifteen sheets answered this question
   * before, eight of them by hiding the scrollbar outright on an inline
   * scrollport. Nothing hides a scrollbar here.
   *
   * `scrollbar-color` inherits, so `:root` reaches every scrollport including
   * the viewport's own. `scrollbar-width` does not inherit (CSS Scrollbars
   * Styling Module Level 1), so it is declared universally; it applies only to
   * elements that are scroll containers, and the base layer keeps it a default
   * that a later layer can still answer differently.
   *
   * The two custom properties declare no var() default: they are emitted from
   * `tokens.bao` (`color.glass.glass-scrollbar-thumb` / `-track`), and a
   * default here would let the token go missing without anything failing.
   */
  :root {
    scrollbar-color: var(--glass-scrollbar-thumb) var(--glass-scrollbar-track);
    /*
     * The root repeats the gutter the universal rule below already asks for,
     * because daisyUI 5 answers this property on `:root` with a space toggle:
     *   --page-has-scroll: initial;
     *   scrollbar-gutter: var(--page-has-scroll) var(--page-scroll-lock) stable;
     * With the toggle unset that declaration is invalid at computed-value
     * time, and an invalid non-inherited property computes to its INITIAL
     * value — `auto` — rather than to the value the cascade already had. So
     * daisyUI does not merely decline to reserve the gutter; it resets the
     * root's gutter, and `:root` (0,1,0) outranks `*` (0,0,0) in the same
     * layer. Measured on the served build: `html` computed `auto` while
     * `body` computed `stable`, from the one universal rule below.
     *
     * The viewport scrollport is the one whose scrollbar arriving and leaving
     * moves the whole page, so leaving the root on `auto` left the jitter this
     * file exists to remove. This file loads after daisyUI, so an equally
     * specific `:root` here wins on source order.
     */
    scrollbar-gutter: stable;
  }

  /*
   * `scrollbar-gutter` rides with `scrollbar-width` because it answers the
   * cost of showing one. Nothing here hides a scrollbar, so on any platform
   * with classic (space-taking) scrollbars every scrollport that crosses its
   * own overflow threshold takes the gutter out of its content box: the box
   * narrows, its contents reflow, and reverting the state widens it again.
   * That is the layout jitter operators report on click — a disclosure opens,
   * a tab swaps, a fragment lands, and the panel underneath shifts sideways
   * because a scrollbar arrived, then shifts back when it leaves.
   *
   * `stable` reserves the gutter whether or not the scrollbar is currently
   * shown, so the appearance of a scrollbar is no longer a layout event. It
   * hides nothing and suppresses nothing, which is what this file's rule
   * requires; it only stops the scrollbar from being paid for twice.
   *
   * Like `scrollbar-width` this applies only to scroll containers, so the
   * universal selector costs nothing on the elements that never scroll, and
   * it is inert on overlay-scrollbar platforms (macOS default), where the
   * scrollbar already takes no layout space — which is exactly why the jitter
   * does not reproduce there and does reproduce for operators who scroll on
   * classic scrollbars.
   */
  * {
    scrollbar-width: thin;
    scrollbar-gutter: stable;
  }
}

@layer utilities {
  /*
   * Horizontal scroll-edge affordance (design.pdf: clipped content must be
   * discoverable). The background-attachment pair is the no-runtime
   * affordance: local-attached covers ride the scroll position and hide each
   * shadow once its edge is fully revealed. Host sets --scroll-edge-bg to its
   * own surface color; symmetric left/right so RTL needs no mirroring. The
   * observer-stamped edge masks below refine the same affordance when the
   * shell runtime runs.
   */
  .bao-scroll-edge-x {
    background-image:
      linear-gradient(to right, var(--scroll-edge-bg, var(--color-base-100, var(--bg-panel))) 30%, transparent),
      linear-gradient(to left, var(--scroll-edge-bg, var(--color-base-100, var(--bg-panel))) 30%, transparent),
      linear-gradient(to right, color-mix(in oklab, var(--color-base-content) var(--scroll-edge-cue-opacity, 14%), transparent), transparent),
      linear-gradient(to left, color-mix(in oklab, var(--color-base-content) var(--scroll-edge-cue-opacity, 14%), transparent), transparent);
    background-position: left center, right center, left center, right center;
    background-size: var(--scroll-edge-width, 2.5rem) 100%, var(--scroll-edge-width, 2.5rem) 100%, var(--scroll-edge-cue-width, 0.875rem) 100%, var(--scroll-edge-cue-width, 0.875rem) 100%;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
  /* Contrast modes replace the soft cue — never delete it (design.pdf §9.3:
     an affordance the user cannot see is a missing affordance). Forced-colors
     drops the gradients (UA palette) and relies on the native scrollbar. */
  @media (prefers-contrast: more) {
    .bao-scroll-edge-x {
      background-image:
        linear-gradient(to right, var(--scroll-edge-bg, var(--color-base-100, var(--bg-panel))) 30%, transparent),
        linear-gradient(to left, var(--scroll-edge-bg, var(--color-base-100, var(--bg-panel))) 30%, transparent),
        linear-gradient(to right, color-mix(in oklab, var(--color-base-content) var(--scroll-edge-cue-opacity-contrast, 55%), transparent), transparent),
        linear-gradient(to left, color-mix(in oklab, var(--color-base-content) var(--scroll-edge-cue-opacity-contrast, 55%), transparent), transparent);
    }
  }
  @media (forced-colors: active) {
    .bao-scroll-edge-x {
      background-image: none;
    }
  }
  /* Native scrollbars remain available in every mode. Only normal contrast
   * adds edge masks, measured by the shared overflow observer. */
  @media (forced-colors: none) and (prefers-contrast: no-preference) {
    .bao-scroll-edge-x:dir(ltr)[data-overflow-start="true"]:not([data-overflow-end="true"]),
    .bao-scroll-edge-x:dir(rtl)[data-overflow-end="true"]:not([data-overflow-start="true"]) {
      mask-image: linear-gradient(to right, transparent, black var(--scroll-edge-cue-width));
    }
    .bao-scroll-edge-x:dir(ltr)[data-overflow-end="true"]:not([data-overflow-start="true"]),
    .bao-scroll-edge-x:dir(rtl)[data-overflow-start="true"]:not([data-overflow-end="true"]) {
      mask-image: linear-gradient(to left, transparent, black var(--scroll-edge-cue-width));
    }
    .bao-scroll-edge-x[data-overflow-start="true"][data-overflow-end="true"] {
      mask-image: linear-gradient(to right, transparent, black var(--scroll-edge-cue-width), black calc(100% - var(--scroll-edge-cue-width)), transparent);
    }
    /*
     * Vertical twin for block scrollports (menu sheets, popover panels). The
     * same observer stamps data-overflow-start/end from scrollTop against
     * scrollHeight, and the mask fades only the edge that still hides rows.
     * Mask-only on purpose: these hosts paint Liquid Glass through the
     * background shorthand, so the inline utility's background-image cue
     * would be reset by the material rule and never show.
     */
    .bao-scroll-edge-y[data-overflow-start="true"]:not([data-overflow-end="true"]) {
      mask-image: linear-gradient(to bottom, transparent, black var(--scroll-edge-cue-width));
    }
    .bao-scroll-edge-y[data-overflow-end="true"]:not([data-overflow-start="true"]) {
      mask-image: linear-gradient(to top, transparent, black var(--scroll-edge-cue-width));
    }
    .bao-scroll-edge-y[data-overflow-start="true"][data-overflow-end="true"] {
      mask-image: linear-gradient(to bottom, transparent, black var(--scroll-edge-cue-width), black calc(100% - var(--scroll-edge-cue-width)), transparent);
    }
  }
  /* daisyUI 5 .carousel sets scrollbar-width: none. Hide is banned; utilities restore the shared thin scrollbar. */
  .carousel {
    scrollbar-width: thin;
    scrollbar-gutter: stable;
  }
  .carousel::-webkit-scrollbar {
    display: block;
    width: 6px;
    height: 6px;
  }
}


@layer utilities {
  /* Stacking context for sticky filter chrome — named SSOT, not raw Tailwind-only. */
  .isolation-isolate { isolation: isolate; }

  /*
   * Sticky filter host: creates a stacking context so dropdowns/menus from the
   * bar paint above scrolling table bodies without z-index wars.
   */
  .bao-filter-bar-sticky {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky, 10);
    isolation: isolate;
  }

  /*
   * Dense queue row action cluster (annotation-review / clinical queues).
   *
   * The cluster wraps, but a wrapping box still measures its max-content until
   * something caps it — and this one sits in a `white-space: nowrap` pinned
   * cell, so its natural width became the column's. Measured 2026-08-22 at
   * 1440x900: /medical-ai-review carries three actions and its Actions column
   * came to 371px, taking the table to 1233px inside a 1042px scrollport. The
   * fitter had already given up both expendable columns, so its only move left
   * was to release the shelf, and the third action then sat off the right edge
   * with no scrollbar under it.
   *
   * Two labelled actions on a line is the cap: `--table-row-actions-max-inline`
   * is the width of two soft buttons and their gap, so a two-action queue keeps
   * its single row and a third action takes a second line inside the cell
   * instead of taking width from the columns beside it. Every action stays
   * painted, which is the contract this cluster exists to keep.
   */
  .bao-queue-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-micro);
    justify-content: flex-end;
    min-width: 0;
    max-inline-size: var(--table-row-actions-max-inline, 16rem);
  }

  /* Typography / text colors */
  .text-base-content { color: var(--text-primary); }
  .text-base-content\/70 { color: color-mix(in oklab, var(--text-primary) 70%, var(--text-secondary)); }
  .text-base-content\/60 { color: var(--text-secondary); }
  .text-base-content\/40 { color: var(--text-tertiary); }
  .text-accent-primary { color: var(--accent-primary); }
  .text-accent-hover { color: var(--accent-hover); }
  .text-accent-secondary { color: var(--accent-secondary); }
  .text-accent-secondary-hover { color: var(--accent-secondary-hover); }
  .text-status-success { color: var(--status-success); }
  .text-status-success-content { color: var(--status-success-content); }
  .text-status-error { color: var(--status-error); }
  .text-status-error-content { color: var(--status-error-content); }
  .text-status-warning { color: var(--status-warning); }
  .text-status-warning-content { color: var(--status-warning-content); }
  .text-status-info { color: var(--status-info); }
  .text-status-info-content { color: var(--status-info-content); }
  .text-accent-primary-content { color: var(--accent-primary-content); }
  .text-accent-secondary-content { color: var(--accent-secondary-content); }

  /* Background colors */
  .bg-base-100 { background-color: var(--bg-panel); }
  .bg-base-200 { background-color: var(--bg-surface); }
  .bg-base-300 { background-color: var(--bg-surface-hover); }
  .bg-accent-primary { background-color: var(--accent-primary); }
  .bg-accent-hover { background-color: var(--accent-hover); }
  .bg-accent-secondary { background-color: var(--accent-secondary); }
  .bg-accent-secondary-hover { background-color: var(--accent-secondary-hover); }
  .bg-status-success { background-color: var(--status-success); }
  .bg-status-error { background-color: var(--status-error); }
  .bg-status-warning { background-color: var(--status-warning); }
  .bg-status-info { background-color: var(--status-info); }
  .bg-border-base { background-color: var(--border-base); }
  .bg-accent-primary\/5 { background-color: color-mix(in oklab, var(--accent-primary) 5%, transparent); }
  .bg-status-error\/10 { background-color: color-mix(in oklab, var(--status-error) 10%, transparent); }
  .bg-status-info\/10 { background-color: color-mix(in oklab, var(--status-info) 10%, transparent); }
  .bg-status-warning\/10 { background-color: color-mix(in oklab, var(--status-warning) 10%, transparent); }
  .bg-base-100\/95 { background-color: color-mix(in oklab, var(--bg-panel) 95%, transparent); }
  .bg-base-100\/90 { background-color: color-mix(in oklab, var(--bg-panel) 90%, transparent); }
  .bg-base-100\/80 { background-color: color-mix(in oklab, var(--bg-panel) 80%, transparent); }
  .border-status-error { border-color: var(--status-error); }
  .border-status-success { border-color: var(--status-success); }
  .border-status-warning { border-color: var(--status-warning); }
  .border-status-info { border-color: var(--status-info); }
  .border-status-error\/30 { border-color: color-mix(in oklab, var(--status-error) 30%, transparent); }
  .border-status-error\/40 { border-color: color-mix(in oklab, var(--status-error) 40%, transparent); }
  .border-status-warning\/40 { border-color: color-mix(in oklab, var(--status-warning) 40%, transparent); }
  .border-status-info\/40 { border-color: color-mix(in oklab, var(--status-info) 40%, transparent); }

  /* Overlay surfaces (canvas chrome, sheets, dialogs) */
  .bg-overlay-backdrop { background-color: var(--overlay-backdrop); }
  .bg-overlay-shade { background-color: var(--overlay-shade); }
  .bg-overlay-toolbar-bg { background-color: var(--overlay-toolbar-bg); }
  .border-overlay-toolbar-border { border-color: var(--overlay-toolbar-border); }
  .bg-overlay-toolbar-border { background-color: var(--overlay-toolbar-border); }
  .text-overlay-text-primary { color: var(--overlay-text-primary); }
  .text-overlay-text-muted { color: var(--overlay-text-muted); }
  .text-overlay-text-dim { color: var(--overlay-text-dim); }
  .bg-overlay-label-bg { background-color: var(--overlay-label-bg); }

  /* Border colors */
  .border-base-300 { border-color: var(--border-base); }
  .border-accent-primary { border-color: var(--accent-primary); }

  /* Radius */
  .rounded-radius-control { border-radius: var(--radius-control); }
  .rounded-radius-panel { border-radius: var(--radius-panel); }
  .rounded-md { border-radius: var(--radius-control); }
  .rounded-xl { border-radius: var(--radius-panel); }
  .rounded-full { border-radius: var(--radius-full); }

  /* Shadows */
  .shadow-sm { box-shadow: var(--shadow-sm); }
  .shadow-md { box-shadow: var(--shadow-md); }
  .shadow-lg { box-shadow: var(--shadow-lg); }
  .shadow-shadow-overlay-toolbar { box-shadow: var(--shadow-overlay-toolbar); }


/* Spacing */
  .p-space-page { padding: var(--space-page); }
  .p-space-section { padding: var(--space-section); }
  .p-space-inline { padding: var(--space-inline); }
  .p-space-micro { padding: var(--space-micro); }
  .p-space-empty { padding: var(--space-empty); }
  /* Skip-link focus chrome — tokenized padding, not raw focus:p-3. */
  .focus\:p-space-section:focus { padding: var(--space-section); }
  .pt-0 { padding-top: 0; }
  .gap-space-page { gap: var(--space-page); }
  .gap-space-section { gap: var(--space-section); }
  .gap-space-inline { gap: var(--space-inline); }
  .gap-space-micro { gap: var(--space-micro); }

  /* 2xs hairline scale — mirrors the micro set so *-space-2xs utilities
     always resolve (class-resolution gate; tokens.bao space-2xs). */
  .gap-space-2xs { gap: var(--space-2xs); }
  .gap-x-space-2xs { column-gap: var(--space-2xs); }
  .gap-y-space-2xs { row-gap: var(--space-2xs); }
  .p-space-2xs { padding: var(--space-2xs); }
  .px-space-2xs { padding-inline: var(--space-2xs); }
  .py-space-2xs { padding-block: var(--space-2xs); }
  .pt-space-2xs { padding-top: var(--space-2xs); }
  .pb-space-2xs { padding-bottom: var(--space-2xs); }
  .ps-space-2xs { padding-inline-start: var(--space-2xs); }
  .pe-space-2xs { padding-inline-end: var(--space-2xs); }
  .mt-space-2xs { margin-top: var(--space-2xs); }
  .mb-space-2xs { margin-bottom: var(--space-2xs); }
  .space-y-space-2xs > * + * { margin-top: var(--space-2xs); }
  .gap-x-space-micro { column-gap: var(--space-micro); }
  .gap-x-space-inline { column-gap: var(--space-inline); }
  .gap-x-space-section { column-gap: var(--space-section); }
  .gap-y-space-micro { row-gap: var(--space-micro); }
  .gap-y-space-inline { row-gap: var(--space-inline); }
  .gap-y-space-section { row-gap: var(--space-section); }
  .px-space-page { padding-inline: var(--space-page); }
  .px-space-section { padding-inline: var(--space-section); }
  .px-space-inline { padding-inline: var(--space-inline); }
  .px-space-micro { padding-inline: var(--space-micro); }
  .px-page-gutter-inline { padding-inline: var(--page-gutter-inline); }
  .px-page-gutter-inline-compact { padding-inline: var(--page-gutter-inline-compact); }
  .bao-content-inset-section { padding-inline: min(var(--space-section), 5%); }
  .bao-content-inset-inline { padding-inline: min(var(--space-inline), 5%); }
  .bao-content-inset-micro { padding-inline: min(var(--space-micro), 5%); }
  .py-space-page { padding-block: var(--space-page); }
  .py-space-section { padding-block: var(--space-section); }
  .py-space-inline { padding-block: var(--space-inline); }
  .py-space-micro { padding-block: var(--space-micro); }
  .py-space-empty { padding-block: var(--space-empty); }
  .pe-space-inline { padding-inline-end: var(--space-inline); }
  .pe-space-section { padding-inline-end: var(--space-section); }
  .pe-space-micro { padding-inline-end: var(--space-micro); }
  .me-space-inline { margin-inline-end: var(--space-inline); }
  .me-space-micro { margin-inline-end: var(--space-micro); }
  .ms-space-page { margin-inline-start: var(--space-page); }
  .ms-space-section { margin-inline-start: var(--space-section); }
  .ms-space-inline { margin-inline-start: var(--space-inline); }
  .start-space-inline { inset-inline-start: var(--space-inline); }
  .start-space-section { inset-inline-start: var(--space-section); }
  .start-space-micro { inset-inline-start: var(--space-micro); }
  .end-space-inline { inset-inline-end: var(--space-inline); }
  .end-space-section { inset-inline-end: var(--space-section); }
  .end-space-micro { inset-inline-end: var(--space-micro); }
  .ps-icon-gutter { padding-inline-start: var(--icon-gutter); }
  .pe-icon-gutter { padding-inline-end: var(--icon-gutter); }
  .scroll-pt-shell { scroll-padding-top: var(--scroll-pt-shell); }
  .scroll-pt-main { scroll-padding-top: var(--scroll-pt-main); }
  .scroll-mt-section { scroll-margin-top: var(--scroll-mt-section); }
  .m-space-inline { margin: var(--space-inline); }
  .mx-space-micro { margin-inline: var(--space-micro); }
  .mx-space-inline { margin-inline: var(--space-inline); }
  .mx-space-section { margin-inline: var(--space-section); }
  .my-space-micro { margin-block: var(--space-micro); }
  .my-space-inline { margin-block: var(--space-inline); }
  .mt-space-page { margin-top: var(--space-page); }
  .mt-space-section { margin-top: var(--space-section); }
  .mt-space-inline { margin-top: var(--space-inline); }
  .mt-space-micro { margin-top: var(--space-micro); }
  .mb-space-page { margin-bottom: var(--space-page); }
  .mb-space-section { margin-bottom: var(--space-section); }
  .mb-space-inline { margin-bottom: var(--space-inline); }
  .mb-space-micro { margin-bottom: var(--space-micro); }
  .pt-space-page { padding-top: var(--space-page); }
  .pt-space-section { padding-top: var(--space-section); }
  .pt-space-inline { padding-top: var(--space-inline); }
  .pt-space-micro { padding-top: var(--space-micro); }
  .pb-space-page { padding-bottom: var(--space-page); }
  .pb-space-section { padding-bottom: var(--space-section); }
  .pb-space-inline { padding-bottom: var(--space-inline); }
  .pb-space-micro { padding-bottom: var(--space-micro); }
  .inset-x-space-section { inset-inline: var(--space-section); }
  .inset-x-space-inline { inset-inline: var(--space-inline); }
  .top-space-section { top: var(--space-section); }
  .top-space-inline { top: var(--space-inline); }
  .top-space-micro { top: var(--space-micro); }
  .bottom-space-section { bottom: var(--space-section); }
  .bottom-space-inline { bottom: var(--space-inline); }
  .bottom-space-micro { bottom: var(--space-micro); }
  .inset-x-space-micro { inset-inline: var(--space-micro); }
  .max-h-panel-body { max-height: var(--panel-body-min-height); }
  .space-y-space-section > * + * { margin-top: var(--space-section); }
  .space-y-space-page > * + * { margin-top: var(--space-page); }
  .space-y-space-inline > * + * { margin-top: var(--space-inline); }
  .space-y-space-micro > * + * { margin-top: var(--space-micro); }
  /*
   * Negative inline margin — lets a sticky dock bleed to the edges of a
   * gutter-padded parent (login submit dock) so its border and background span
   * the full surface instead of floating inset. Tailwind cannot generate this
   * from an arbitrary custom property, so the token owns it.
   */
  .-mx-space-inline { margin-inline: calc(var(--space-inline) * -1); }


  /* Layout */
  /* Parent marker for descendant state variants (toolbar/tooltips). */

  /* Structural utilities (.flex, .hidden, .grid, .items-…, .justify-…, .w-full)
     are intentionally NOT re-declared here. Tailwind is their single owner:
     a flat copy in this later-loaded sheet lands after Tailwind's
     breakpoint-gated variants in the same utilities layer and silently beats
     them (e.g. a re-declared .flex-wrap killed max-sm:flex-nowrap; .hidden
     killed hidden sm:flex). Only token-valued classes belong in this file. */
  .w-12 { width: 3rem; }
  .w-sidebar-expanded { width: var(--sidebar-expanded); }
  .size-brand-mark {
    width: var(--brand-mark-size);
    height: var(--brand-mark-size);
  }
  .min-w-control { min-width: var(--density-compact-row-height); }
  /* Fluid shell tracks — CSS vars from tokens.bao (no invented rem).
     max-width clamps to the viewport so end-anchored dropdowns never
     paint past the right edge on phone-width shells. */
  .bao-width-menu-lg {
    /* Prefer % over 100vw so padded #app-main does not grow scrollWidth on mobile. */
    width: min(100%, var(--dock-side-wide-basis));
    max-width: min(100%, var(--dock-side-wide-basis));
  }
  .bao-width-menu-md {
    width: min(100%, var(--grid-choice-min));
    max-width: min(100%, var(--grid-choice-min));
  }
   .bao-width-avatar { width: var(--icon-gutter); }
   .bao-width-sheet {
     width: min(100%, var(--sheet-panel-width));
     max-width: min(100%, var(--sheet-panel-max-width));
   }
   /* Phone bottom sheet panel — pathologist inspector / filter overflow. */
   .bao-sheet-panel-bottom {
     max-height: min(72dvh, 32rem);
     padding-block-end: env(safe-area-inset-bottom, 0px);
   }
   .bao-width-rail-collapsed { width: var(--session-rail-collapsed); }
   .bao-width-selection-col { width: var(--selection-column-width); }
   .bao-max-w-search { max-width: min(100%, var(--dock-primary-basis)); }
   .bao-max-w-command { --modal-box-track: var(--command-palette-max-width); max-width: min(100%, var(--command-palette-max-width)); }
   .bao-max-w-shortcuts { --modal-box-track: var(--keyboard-shortcuts-max-width); max-width: min(100%, var(--keyboard-shortcuts-max-width)); }
   .bao-max-w-settings { --modal-box-track: var(--settings-dialog-max-width); max-width: min(100%, var(--settings-dialog-max-width)); }
   /* Topbar chips size via flex tracks in shell responsive CSS — no hard 10rem cap. */
   .bao-max-w-context-chip { max-width: 100%; min-width: 0; }
   /* Topbar actions: cap width on narrow so brand/title keep room; scroll overflow. */
   .bao-max-w-topbar-actions { max-width: 50%; }
   .bao-max-w-voice-status { max-width: min(10rem, 60vw); }
   @media (min-width: 40rem) {
     .bao-max-w-voice-status { max-width: min(16rem, 60vw); }
   }
   /* Toolbar search + flex spacer — tokens.bao layoutSize (no max-w-28 / min-w-2). */
   .bao-max-w-toolbar-search { max-width: min(100%, var(--toolbar-search-max-width)); }
   .bao-min-w-toolbar-spacer { min-width: var(--toolbar-spacer-min-width); }
   @media (min-width: 40rem) {
     .bao-max-w-topbar-actions { max-width: none; }
   }
   /* Sticky topbar clears notch / status bar on phones and tablets.
      Keep padding-top explicit (never padding-block) so safe-area wins. */
   .navbar.z-shell-topbar {
      padding-top: max(var(--safe-area-inset-top, var(--space-micro, 0.25rem)), env(safe-area-inset-top, 0px));
      padding-bottom: var(--space-micro, 0.25rem);
      padding-inline-start: max(var(--safe-area-inset-left, var(--space-inline)), env(safe-area-inset-left, 0px));
      padding-inline-end: max(var(--safe-area-inset-right, var(--space-inline)), env(safe-area-inset-right, 0px));
   }
   @media (min-width: 40rem) {
     .navbar.z-shell-topbar {
       padding-inline-start: max(var(--space-section), env(safe-area-inset-left, 0px));
       padding-inline-end: max(var(--space-section), env(safe-area-inset-right, 0px));
     }
   }
   .bao-max-h-palette { max-height: var(--palette-max-height); }
   .bao-field-basis { flex-basis: min(100%, var(--grid-context-min)); }
   .bao-control-h { height: var(--density-compact-row-height); }
   .bao-min-h-chrome { min-height: var(--chrome-min-height); }
   .min-w-56 { min-width: min(100%, var(--grid-choice-min)); }
   .h-space-page { height: var(--space-page); }
   .h-space-section { height: var(--space-section); }
   .h-space-inline { height: var(--space-inline); }
   .h-space-micro { height: var(--space-micro); }
    .min-h-12 { min-height: var(--chrome-min-height); }
   .min-h-60 { min-height: var(--panel-body-min-height); }
   .max-h-64 { max-height: var(--palette-max-height); }
   .bao-min-h-panel-body { min-height: var(--panel-body-min-height); }
   .bao-min-h-rail-composer { min-height: var(--rail-composer-min-height); }
   .bao-max-h-modal { max-height: var(--modal-max-height); }
   .bao-max-h-modal-scroll { max-height: var(--modal-scroll-max-height); }
   .bao-max-h-modal-body { max-height: var(--modal-body-max-height); }
   .bao-max-h-composer { max-height: var(--composer-max-height); }
   .bao-min-h-panel-scroll { min-height: var(--panel-scroll-max-height); }
   .max-w-3xl { max-width: var(--command-palette-max-width); }
   .max-w-4xl { max-width: var(--settings-dialog-max-width); }
   .max-w-6xl { max-width: var(--page-wide); }
   .max-w-56 { max-width: var(--grid-choice-min); }
   .max-w-chip { max-width: var(--max-w-chip, 14rem); }
   .max-w-dropdown { max-width: min(100vw - 2rem, var(--max-w-dropdown, 20rem)); }
   .max-w-[var(--page-standard)] { max-width: var(--page-standard); }
  .z-canvas-base { z-index: var(--z-canvas-base); }
  .z-canvas-empty { z-index: var(--z-canvas-empty, 20); }
  .z-canvas-overlay { z-index: var(--z-canvas-overlay); }
  .z-content-overlay { z-index: var(--z-content-overlay); }
  .z-table-header { z-index: var(--z-table-header); }
  .z-table-action { z-index: var(--z-table-action); }
  .z-shell-drawer { z-index: var(--z-shell-drawer); }
  .z-shell-topbar { z-index: var(--z-shell-topbar); }
  .z-dropdown { z-index: var(--z-dropdown); }
  .z-sticky { z-index: var(--z-sticky); }
  .z-overlay { z-index: var(--z-overlay); }
  .z-modal { z-index: var(--z-modal); }
  .z-toast { z-index: var(--z-toast); }

  /* Skip link: above every chrome tier when revealed (focus-variant utilities
     cannot reach token classes, so the focus state lives here). */
  .bao-skip-link:focus { z-index: var(--z-tooltip); }

  /* Grid base utilities (.grid/.grid-cols-*) live in Tailwind only — see the
     structural-utilities note above; re-declaring them here beats Tailwind's
     responsive grid-cols variants. */
  /* Responsive variants (sm:/md:/lg:/xl:) are intentionally NOT defined here —
     hand-written flat copies have no @media gate and apply at every width,
     overriding Tailwind's correctly breakpoint-gated utilities. Tailwind
     generates these from the scanned templates; leave them to it. */


.bao-capture-device-grid {
  display: grid;
  width: 100%;
  min-width: 0;
  align-items: stretch;
  gap: var(--space-inline);
  /* Phone: single column so empty-hint fieldsets never sit beside short peers. */
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 40rem) {
  .bao-capture-device-grid {
    /* auto-fill (not fit) avoids 0px phantom tracks beside 3 short fieldsets. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
  }
}
  /*
   * Reactive panel grid shell (display/gap/container). Track template +
   * span sizes live in tokens-layout-panel-grid.ts (12-track SSOT) — never
   * redeclare grid-template-columns here or spans fight auto-fit cells.
   */
  .bao-grid-reactive-panels {
    display: grid;
    min-width: 0;
    width: 100%;
    align-items: var(--bao-panel-grid-align, stretch);
    gap: var(--space-section, 0.75rem);
    grid-auto-rows: auto;
    container-type: inline-size;
  }
  /*
   * Fluid KPI stat strip — auto-fit tracks, never hardcoded counts: a phone
   * width yields 2-up, wide hosts 4-up, purely from available inline size
   * (§8.6 canonical transformation; replaces stats-vertical stacking that
   * spent ~140px of viewport per number on compact shells).
   */
  .bao-grid-stats {
    display: grid;
    min-width: 0;
    width: 100%;
    align-items: stretch;
    gap: var(--space-inline, 0.5rem);
    grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
  }
  /* Solid metric host — content plane; never glass (pairs with .bao-stat-card). */
  .bao-stat-card {
    --radius-box: var(--radius-field);
    background-color: var(--color-base-100, var(--bg-panel));
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Page content track — never let max-w-full win over enterprise max-w-page-*. */
  .bao-page-track {
    width: 100%;
    min-width: 0;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: var(--page-section-gap, var(--space-section));
    /*
     * §8.6 canonical transformation — page track is the container query host
     * (enterprise-page @container class). Nested grids reflow off rendered
     * track width, not viewport, so split panels and embedded widgets collapse
     * correctly without per-page media forks.
     */
    container-type: inline-size;
  }
  /*
   * Page-track container column transforms — one owner for collection/form/
   * detail grids inside the content column (never duplicate in page partials).
   */
  @container (max-width: 36rem) {.bao-page-track .bao-form-grid-two,
.bao-page-track .bao-choice-grid,
.bao-page-track .bao-context-grid-reactive,
.bao-page-track .bao-grid-reactive-collection {
      grid-template-columns: minmax(0, 1fr);
    }
    /*
     * KPI stat strips keep auto-fit (Cached|Loaded 2-up on phone). Forcing
     * single-column here burned a full viewport row per number (models pipeline).
     * .bao-grid-stats base owns minmax(min(9rem, 100%), 1fr) fluid tracks.
     */
  }
  @container (min-width: 36rem) and (max-width: 64rem) {.bao-page-track .bao-form-grid-two,
.bao-page-track .bao-choice-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
  /*
   * Staggered panel entrance — transform only (no opacity:0). Disabled for
   * HTMX main swaps: desktop uses View Transitions; every settle re-fired
   * stagger and looked like a full refresh. Tokens remain for future use.
   */
  .bao-stagger-children > * {
    animation: none;
  }
  @keyframes bao-stagger-in {
    from { transform: translateY(var(--entrance-offset)); }
    to { transform: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-stagger-children > * {
      animation: none;
      transition-property: none;
      transition-duration: var(--duration-instant, 0ms);
    }
  }
  .bao-page-track.max-w-page-standard {
    max-width: var(--page-standard, 80rem);
  }
  .bao-page-track.max-w-page-wide {
    max-width: var(--page-wide, 96rem);
  }
  .bao-page-track.max-w-page-full {
    max-width: none;
  }

/* The stat group already owns its inset; cells must not add a second gutter. */
.bao-grid-stats > .stat {
  padding-inline: 0;
}

/* Content-sized groups share one sizing policy across grid and panel projections. */
.bao-grid-content {
  --bao-panel-grid-align: start;
  --bao-panel-height: auto;
  --bao-panel-min-height: 0;
}

  .bao-state-glyph-sm { width: 1.5rem; height: 1.5rem; }
  .bao-state-glyph-md { width: 3rem; height: 3rem; }
  .bao-state-glyph-lg { width: 4rem; height: 4rem; }


  /*
   * Page-header primary CTAs (New Case, etc.): HIG 44px min height.
   * daisyUI .btn-sm sets a fixed height (~32px); override height+max-height
   * so min-height actually expands the control for touch.
   */
  [data-page-header-actions] > .btn.btn-primary,
  [data-page-header-actions] > a.btn.btn-primary,
  [data-page-header-actions] > .btn.btn-primary.btn-sm,
  [data-page-header-actions] > a.btn.btn-primary.btn-sm,
  [data-primitive="PageHeader"] [data-page-header-actions] > .btn.btn-primary,
  [data-primitive="PageHeader"] [data-page-header-actions] > a.btn.btn-primary {
    /* daisyUI 5 sizes buttons via --size; raise it so btn-sm is still 44px. */
    --size: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    height: var(--touch-target-min, 2.75rem);
    max-height: none;
    min-width: 0;
  }

  /*
   * Dual-title ban — SOLE owner of shell-title visibility.
   * Demote the shell title only when a content H1 is the visible name. The
   * verdict is NOT re-derived here: pages-renderers-header.ts stamps
   * data-page-header-mode on the PageHeader, app-shell JS mirrors it onto
   * <body> for WebView 101 (no :has()), and the :has() branch reads the same
   * stamp so first paint matches post-hydration. The old structural probe
   * (PageHeader > div:not(.sr-only) h1) matched the crumbs wrapper that the
   * demoted layout emits ahead of the sr-only title block, so every page
   * resolved to content mode and BOTH titles ended up clipped.
   *
   * display:none, not the sr-only clip: a demoted shell title is a duplicate
   * of the visible H1, so it must leave the accessibility tree with the
   * pixels. #shell-page-title carries no aria-hidden, so clipping it here
   * would double-announce the page name on content-mode surfaces.
   */
  body[data-page-header-mode="content"] [data-shell-page-title],
  body:has([data-primitive="PageHeader"][data-page-header-mode="content"]) [data-shell-page-title] {
    display: none;
  }


  /*
   * Happydumpling shell law: #app-main is a flex host. Standard pages scroll
   * on main; immersive pages (media/conversation) do not — the thread/canvas does.
   * Content column uses max-width + centered track for non-immersive pages.
   */
  #app-main {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    flex: 1 1 0%;
    width: 100%;
    max-width: 100%;
    /* Inline-size containment keeps nested min-content tables from inflating scrollWidth. */
    contain: inline-size;
    overflow-y: auto;
    overscroll-behavior-x: none;
    /* Sticky topbar must not obscure focused controls (WCAG 2.4.11). */
    scroll-padding-top: var(--scroll-pt-main, var(--chrome-min-height, 3.5rem));
    /* Dock + one HIG row + section rhythm so scrollIntoView(center) clears fixed dock. */
    scroll-padding-bottom: calc(var(--dock-height, 4rem) + env(safe-area-inset-bottom, 0px) + var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
  }
  /* Direct children only — a universal descendant min-width reset lets any
     flex child collapse and paint over siblings (phone toolbar pile-ups). */
  #app-main > * {
    box-sizing: border-box;
    min-width: 0;
  }
  #app-main table {
    max-width: 100%;
  }
  /* Scroll wrap owns wide tables; #app-main must not shrink them. */
  #app-main .bao-table-scroll-x table.bao-registry-table,
  #app-main [data-primitive="DataTable"] table.bao-registry-table {
    max-width: none;
    min-width: 100%;
    width: max-content;
  }
  /* Scroll hosts own wide content; do not inflate #app-main.scrollWidth. */
  #app-main .bao-data-panel,
  #app-main .overflow-x-auto,
  #app-main .bao-panel-scroll {
    max-width: 100%;
  }
  #app-main pre,
  #app-main code {
    max-width: 100%;
    overflow-x: auto;
  }
  #app-main > [data-page]:not([data-page-type="media"]):not([data-page-type="conversation"]) {
    width: 100%;
    min-width: 0;
    max-width: 100%;
  }
  #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
    overflow-x: clip;
    overflow-y: auto;
    scroll-behavior: smooth;
    /* Happydumpling fluid gutters: clamp tracks, not flat 12px.
     * padding-block-end is owned by dock clearance SSOT (tokens-layout-dock)
     * so this higher-specificity rule cannot wipe mobile dock room. */
    padding-block-start: var(--page-gutter-block);
    /* Notched phones / home indicator — keep content out of unsafe insets. */
    padding-inline-start: max(var(--page-gutter-inline), env(safe-area-inset-left, 0px));
    padding-inline-end: max(var(--page-gutter-inline), env(safe-area-inset-right, 0px));
  }
  /* Mobile: compact inline gutter under tight viewports. */
  @media (max-width: 48rem) {
    #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
      padding-inline-start: max(var(--page-gutter-inline-compact), env(safe-area-inset-left, 0px));
      padding-inline-end: max(var(--page-gutter-inline-compact), env(safe-area-inset-right, 0px));
    }
  }
  /* Reduced motion: the ID+:has() selector above (1,x,x) out-specificities the
     universal a11y clamp, so main-scroll snapping gets its own override —
     mirrors the .bao-shell-sidebar pattern in tokens-glass-shell-webview-surfaces. */
  @media (prefers-reduced-motion: reduce) {
    #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
      scroll-behavior: auto;
    }
  }

  /*
   * Page chrome density (Apple HIG / Hick's law): phone keeps ≤1 labeled primary
   * CTA; secondary chrome is icon+tooltip only. Prefer progressive disclosure
   * over packing 6+ labeled controls in one row.
   */
  @media (max-width: 48rem) {
    /*
     * Scroll-edge fade: clipped actions stay discoverable (design.pdf §9.5),
     * but ONLY over genuinely clipped content — the shell runtime
     * (app-shell-glass.ts bindActionStripOverflowState) stamps
     * data-overflow-start/end from live scroll metrics. An unconditional
     * mask fades the primary CTA even when everything fits, which reads
     * as a half-disabled control.
     */
    [data-page-header-actions][data-overflow-end="true"] {
      mask-image: linear-gradient(to right, var(--black) calc(100% - 1.25rem), transparent);
      -webkit-mask-image: linear-gradient(to right, var(--black) calc(100% - 1.25rem), transparent);
    }
    [data-page-header-actions][data-overflow-start="true"] {
      mask-image: linear-gradient(to left, var(--black) calc(100% - 1.25rem), transparent);
      -webkit-mask-image: linear-gradient(to left, var(--black) calc(100% - 1.25rem), transparent);
    }
    [data-page-header-actions][data-overflow-start="true"][data-overflow-end="true"] {
      mask-image: linear-gradient(to right, transparent, var(--black) 1.25rem, var(--black) calc(100% - 1.25rem), transparent);
      -webkit-mask-image: linear-gradient(to right, transparent, var(--black) 1.25rem, var(--black) calc(100% - 1.25rem), transparent);
    }
    /* Contrast modes: replace the fade with the native scrollbar (design.pdf §9.3).
       :is() with the attribute variants keeps this override above the
       overflow-scoped masks in specificity. */
    @media (prefers-contrast: more), (forced-colors: active) {
      :is([data-page-header-actions], [data-page-header-actions][data-overflow-start="true"], [data-page-header-actions][data-overflow-end="true"]) {
        mask-image: none;
        -webkit-mask-image: none;
      }
    }
    /*
     * Phone density: icon-only densify for ghost/outline square chrome only.
     * Primary + soft clinical secondaries (finish / generate report) keep labels
     * (design.pdf: essentials must not depend on hover).
     */
    [data-page-header-actions] .bao-toolbar-action:not(.btn-primary):not(.btn-soft) .bao-toolbar-label,
    [data-page-header-actions] .btn:not(.btn-primary):not(.btn-soft) > span.bao-toolbar-label,
    [data-page-header-actions] .btn:not(.btn-primary):not(.btn-soft) > span:not(.sr-only):not([class*="loading"]):not(.badge) {
      display: none;
    }
    [data-page-header-actions] .btn {
      flex-shrink: 0;
      flex-direction: row;
      align-items: center;
    }
    /* Dashboard customize trays: long panel titles → icon density / truncate. */
    [data-dashboard-visibility-tray] .btn:not(.btn-primary),
    [data-dashboard-uniform-size-tray] .btn:not(.btn-primary) {
      max-width: 10rem;
    }
    [data-dashboard-visibility-tray] .btn:not(.btn-primary) {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    [data-primitive="PageHeader"] {
      gap: var(--space-inline, 0.5rem);
    }
    /* Shell topbar: single dense row — no multi-line brand/actions. */
    #app-header.navbar {
      flex-wrap: nowrap;
      min-height: calc(var(--chrome-min-height, 2.25rem) + env(safe-area-inset-top, 0px));
    }
    #app-header .navbar-start {
      flex: 1 1 auto;
      min-width: 0;
      max-width: 70%;
    }
    /* Phone: center search/status stays off; webview SSOT owns tablet+ show. */
    #app-header .navbar-end {
      flex: 0 0 auto;
      max-width: none;
      overflow: visible;
    }
    /* Page header actions: never overshoot viewport (models Customize etc.). */
    [data-page-header-actions],
    [data-primitive="PageHeader"] [data-page-header-actions] {
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      gap: var(--space-micro, 0.25rem);
      max-width: 100%;
      min-width: 0;
      justify-content: flex-start;
      flex-wrap: wrap;
    }
    [data-page-header-actions] > .btn,
    [data-page-header-actions] > a.btn {
      max-width: 100%;
      min-width: 0;
      flex: 0 1 auto;
      /* HIG 44px primary CTAs (New Case, etc.) — btn-sm alone is 32px. */
      min-height: var(--touch-target-min, 2.75rem);
      height: auto;
    }
    /* Icon-only densify ghost/outline only — soft/primary keep verb labels. */
    [data-page-header-actions] > .btn:not(.btn-primary):not(.btn-soft) .bao-toolbar-label,
    [data-page-header-actions] > a.btn:not(.btn-primary):not(.btn-soft) .bao-toolbar-label {
      display: none;
    }
    [data-page-header-actions] > .btn:not(.btn-primary):not(.btn-soft),
    [data-page-header-actions] > a.btn:not(.btn-primary):not(.btn-soft) {
      width: var(--touch-target-min, 2.75rem);
      min-width: var(--touch-target-min, 2.75rem);
      max-width: var(--touch-target-min, 2.75rem);
      padding-inline: 0;
      justify-content: center;
    }
    [data-page-header-actions] > .btn.btn-soft,
    [data-page-header-actions] > a.btn.btn-soft {
      width: auto;
      max-width: none;
      min-width: var(--touch-target-min, 2.75rem);
      padding-inline: 0.5rem;
      justify-content: center;
      gap: var(--space-micro, 0.25rem);
    }
    [data-page-header-actions] > .btn.btn-soft .bao-toolbar-label,
    [data-page-header-actions] > a.btn.btn-soft .bao-toolbar-label {
      display: inline-block;
      max-width: 8rem;
      opacity: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    /* Icon-first square chrome only — never strip verb+noun primary CTAs. */
    #app-header .btn-square .bao-toolbar-label,
    [data-page-header-actions] .btn-square .bao-toolbar-label {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border-width: 0;
    }
    /*
     * Keyboard cheatsheet: keep HTML hidden sm:inline-flex — do not force
     * display:none here (that wiped the control on every phone width and
     * could win over sm: for residual viewports). Phone uses user menu.
     */
    /* Tooltips as block captions under icons — kill on touch. */
    #app-header .tooltip::before,
    #app-header .tooltip::after,
    [data-page-header-actions] .tooltip::before,
    [data-page-header-actions] .tooltip::after {
      display: none;
    }
  }


  /* Immersive pages: :has for modern engines + data-main-page-type for WebView 101. */
  /* Overflow and dock clearance for this list are owned by
   * tokens-glass-shell-webview-surfaces, which introduced the
   * data-main-page-type key for engines without :has(). */
  #app-main[data-main-page-type="media"],
  #app-main[data-main-page-type="conversation"],
  #app-main:has([data-page-type="media"]),
  #app-main:has([data-page-type="conversation"]) {
    height: 100%;
    padding-inline: 0;
    padding-block-start: 0;
  }
  @media (min-width: 80rem) {
    #app-main[data-main-page-type="media"],
    #app-main[data-main-page-type="conversation"],
    #app-main:has([data-page-type="media"]),
    #app-main:has([data-page-type="conversation"]) {
      padding-block-end: 0;
    }
  }
  /* Standard pages: consistent content column padding via main; avoid double chrome.
   * block-end is dock SSOT — never use padding-block shorthand here. */
  #app-main:has([data-page-type="home"]),
  #app-main:has([data-page-type="collection"]) {
    padding-block-start: var(--page-gutter-block);
  }
  [data-page-type="media"],
  [data-page-type="conversation"] {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
  }
  #app-main > [data-page][data-page-type="media"],
  #app-main > [data-page][data-page-type="conversation"] {
    min-height: 0;
    flex: 1 1 auto;
  }
  [data-page-type="media"] > [data-state-content],
  .bao-media-page-root {
    display: flex;
    min-height: 0;
    flex: 1 1 0%;
    flex-direction: column;
    gap: var(--space-inline);
    overflow: hidden;
  }
  /* Capture chrome: command strip shrinks, workbench owns remaining height. */
  [data-page-type="media"] [data-capture-chrome],
  [data-page-type="media"] [data-session-mode-panel],
  .bao-media-page-root [data-capture-chrome] {
    display: flex;
    min-height: 0;
    flex: 1 1 0%;
    flex-direction: column;
    gap: var(--space-inline);
  }
  [data-page-type="media"] [data-capture-context-strip],
  [data-page-type="media"] [data-capture-command-chrome],
  [data-page-type="media"] [data-session-mode-tabs] {
    flex: 0 0 auto;
  }
  .bao-media-workbench,
  [data-page-type="media"] [data-capture-workbench] {
    display: flex;
    flex-wrap: wrap;
    min-width: 0;
    flex: 1 1 auto;
    align-items: stretch;
    gap: var(--space-section);
  }

  /*
   * Model readiness / missing-context gates — dense sticky Alert under session
   * chrome (daisyUI alert SSOT; data-model-readiness-gate owns density).
   */
  [data-page="capture"] [data-model-readiness-gate]:not([hidden]),
  [data-page="capture"] [data-capture-missing-context]:not([hidden]) {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky, 1100);
    margin-block: 0;
  }
  [data-page="capture"] [data-model-readiness-gate][data-state="success"] {
    display: none;
  }
  [data-page="capture"] [data-model-readiness-gate] {
    max-width: 100%;
    overflow: hidden;
  }
  [data-page="capture"] [data-model-readiness-gate] [data-model-readiness-message],
  [data-page="capture"] [data-model-readiness-gate] [data-model-readiness-hint] {
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  /* Narrow gate: full-width action row (container query, not viewport). */
  @container (max-width: 28rem) {
    [data-model-readiness-gate] [data-model-readiness-actions] {
      flex: 1 1 100%;
      justify-content: stretch;
      margin-inline-start: 0;
    }
    [data-model-readiness-gate] [data-model-readiness-actions] .btn {
      flex: 1 1 auto;
      justify-content: center;
    }
  }
  /*
   * Registry status readout is non-actionable chrome — hide at all widths.
   * Live device state belongs on the preview overlay / shell status, not a
   * second fieldset that steals command-strip height (Apple camera density).
   */
  [data-page="capture"] [data-capture-device-status]:not([data-capture-device-sheet] *),
  [data-page="capture"] [data-capture-command-chrome] [data-capture-control][role="status"]:not([data-capture-device-sheet] *) {
    display: none;
  }

  /* Annotate keeps a bounded flex chain for its canvas. Acquisition overrides
   * these wrappers below so its modes, controls and stage share one scroll
   * owner without consuming all available height ahead of the toolbar. */
  /*
   * Only the ACTIVE session panel participates in flex geometry.
   * Forcing display:flex on [hidden] panels overrode the HTML hidden attribute
   * and stacked acquire + annotate workbenches in one scroll (DOM / UX failure).
   */
  [data-page="capture"] [data-session-panel][hidden],
  [data-page="capture"] [data-tab-panel][hidden] {
    display: none;
  }
  [data-page="capture"] [data-session-panel]:not([hidden]),
  [data-page="capture"] [data-tab-panel]:not([hidden]) {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    flex: 1 1 0%;
    overflow: hidden;
  }
  [data-page="capture"] [data-session-panel]:not([hidden]) > [data-dashboard-layout-root],
  [data-page="capture"] [data-tab-panel]:not([hidden]) > [data-dashboard-layout-root] {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    flex: 1 1 0%;
    overflow: hidden;
  }
  [data-page="capture"] [data-capture-chrome] {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    flex: 1 0 auto;
    max-block-size: none;
    overflow: visible;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
  }
  [data-page="capture"] [data-capture-workbench] {
    display: flex;
    min-width: 0;
    flex: 1 1 auto;
    container-type: inline-size;
    container-name: capture-workbench;
  }

  /* Fluid workbench: stack when the workbench container is narrow (not only viewport). */
  @container capture-workbench (max-width: 40rem) {
    [data-capture-workbench] {
      flex-direction: column;
      flex-wrap: nowrap;
    }
    [data-capture-workbench] > * {
      flex: 0 0 auto;
      max-width: 100%;
      min-width: 0;
      width: 100%;
    }
    [data-capture-workbench-primary] {
      order: 0;
      flex: 1 1 auto;
      min-height: min(48dvh, 22rem);
    }
    [data-capture-workbench-secondary] {
      order: 1;
      flex: 0 1 auto;
      max-height: none;
      overflow-y: visible;
      overscroll-behavior: contain;
    }
  }

/*
 * Permanent-rail mode/recent/AI hide SSOT lives in tokens-glass-shell-capture.ts
 * AFTER the unlayered [data-session-mode-tabs] { display: flex } rule so cascade
 * cannot re-show main mode tabs on desktop (dual Acquire/Annotate violation).
 */


/* Readiness state paint has one owner; runtime writes only the page-state contract. */
[data-model-readiness-gate][data-state="loading"] {
  border-color: color-mix(in oklab, var(--status-info) 40%, transparent);
  background-color: color-mix(in oklab, var(--status-info) 10%, transparent);
}
[data-model-readiness-gate][data-state="denied"],
[data-model-readiness-gate][data-state="error"] {
  border-color: color-mix(in oklab, var(--status-error) 40%, transparent);
  background-color: color-mix(in oklab, var(--status-error) 10%, transparent);
}
[data-model-readiness-gate][data-state="success"] {
  border-color: color-mix(in oklab, var(--status-warning) 40%, transparent);
  background-color: color-mix(in oklab, var(--status-warning) 10%, transparent);
}
[data-model-readiness-gate][data-state="loading"] [data-model-readiness-loaded-count] {
  color: var(--status-info);
}
[data-model-readiness-gate][data-state="denied"] [data-model-readiness-loaded-count],
[data-model-readiness-gate][data-state="error"] [data-model-readiness-loaded-count] {
  color: var(--status-error);
}
[data-model-readiness-gate][data-state="success"] [data-model-readiness-loaded-count] {
  color: var(--status-warning);
}

/* Acquisition scrolls its complete stack, including modes, as text increases.
 * Keeping an enlarged sticky mode strip outside a second scrollport can spend
 * all available block space before the first acquisition control is reached. */
[data-page="capture"]:not([data-session-mode="annotate"]) > [data-state-content] {
  overflow-x: clip;
  overflow-y: auto;
  overscroll-behavior-block: contain;
}
[data-page="capture"]:not([data-session-mode="annotate"]) [data-session-mode-tabs] {
  position: static;
}
[data-page="capture"] [data-session-panel="acquire"]:not([hidden]),
[data-page="capture"] [data-session-panel="acquire"] > [data-dashboard-layout-root] {
  flex: 1 0 auto;
  max-block-size: none;
  overflow: visible;
}

  /* Mobile fluidity fallback when CQ unsupported / outer shell is narrow. */
  @media (max-width: 64rem) {
    .bao-media-workbench,
    [data-page-type="media"] [data-capture-workbench] {
      flex-direction: column;
      flex-wrap: nowrap;
    }
    .bao-media-workbench > *,
    [data-page-type="media"] [data-capture-workbench] > * {
      flex: 0 0 auto;
      max-width: 100%;
      min-width: 0;
      width: 100%;
    }
    /*
     * Capture actions: icon-first (Apple camera chrome). Labels live in
     * aria-label + data-tip; never inflate the shutter row with verbs.
     * Gallery pick is a standard .bao-toolbar-action — the same clip covers
     * it; no gallery-specific rules exist anywhere.
     */
    [data-capture-preview-overlay] .bao-toolbar-action .bao-toolbar-label,
    [data-capture-action-bar] .bao-toolbar-action .bao-toolbar-label {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    /*
     * Phone capture densify — industry camera pattern:
     * tabs → context capsule → gate → device strip → preview owns stage.
     */
    [data-page="capture"] [data-capture-context-strip],
    [data-page="capture"] [data-capture-top-chrome] {
      order: 1;
      flex: 0 0 auto;
    }

/*
 * The top chrome row pans instead of ejecting its children. Its context
 * capsule and device sheet are pinned `flex: 0 0 auto` by design — chips do
 * not crush — but at Dynamic Type 125% on a 390px phone their natural widths
 * exceed the row, and with no scroller the Device Controls disclosure ended
 * at x=417 in a 390px viewport: present, unreachable, invisible. Same
 * contract as the chat context rail: the row scrolls its inline axis, and
 * unclips while a disclosure inside it is open so the expanded controls are
 * never amputated by the pan viewport.
 */
[data-page="capture"] [data-capture-top-chrome] {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
[data-page="capture"] [data-capture-top-chrome]:has([data-capture-device-sheet][open]),
[data-page="capture"] [data-capture-top-chrome]:has([data-capture-context-sheet][open]) {
  overflow-x: visible;
  overflow-y: visible;
}
    [data-page="capture"] [data-model-readiness-gate],
    [data-page="capture"] [data-capture-missing-context] {
      order: 2;
      flex: 0 0 auto;
    }
    [data-page="capture"] [data-capture-command-chrome] {
      order: 3;
      flex: 0 0 auto;
      gap: var(--space-micro);
      padding-block: var(--space-micro);
      padding-inline: var(--space-inline);
      max-height: none;
      border-bottom: 1px solid var(--color-base-300, var(--border-base));
    }
    [data-page="capture"] [data-capture-workbench] {
      order: 4;
      flex: 1 1 auto;
    }
    /* Non-actionable registry status steals a full fieldset on phone — hide. */
    [data-page="capture"] [data-capture-device-status]:not([data-capture-device-sheet] *),
    [data-page="capture"] [data-capture-command-chrome] [data-capture-control][role="status"]:not([data-capture-device-sheet] *) {
      display: none;
    }
    [data-page="capture"] [data-capture-workbench-primary] {
      order: 0;
      flex: 1 1 auto;
    }
    [data-page="capture"] [data-capture-workbench-secondary] {
      order: 1;
      flex: 0 0 auto;
      max-height: none;
      overflow-y: visible;
      pointer-events: none;
    }
    [data-page="capture"] .bao-preview-region {
      flex: 1 1 auto;
    }
    /*
     * Live-only float: idle shutter chrome stays in-flow (static) so tablet
     * columns never paint overlay over session controls (structure audit).
     */
    [data-page="capture"]
      .bao-preview-region[data-preview-state="live"]
      > [data-capture-preview-overlay] {
      z-index: var(--z-overlay, 1200);
      inset-inline: var(--space-inline, 0.5rem);
      bottom: var(--space-section, 0.75rem);
      justify-content: space-between;
      align-items: flex-end;
    }
    [data-page="capture"]
      .bao-preview-region:not([data-preview-state="live"])
      > [data-capture-preview-overlay] {
      position: static;
      inset: auto;
      z-index: auto;
    }
    /*
     * Tablet idle overflow containment: when overlay is position:static,
     * preview region must clip overflow so shutter chrome never paints over
     * session controls below (structure: capture-idle-overlay-session-collision).
     */
    [data-page="capture"]
      .bao-preview-region:not([data-preview-state="live"]) {
      overflow: clip;
    }
    /*
     * Tablet action-bar anti-occlusion: when idle overlay is position:static
     * (not live preview), the action bar inside it must stay above session
     * content below. Ensure action bar has proper stacking context.
     */
    [data-page="capture"]
      .bao-preview-region:not([data-preview-state="live"])
      [data-capture-action-bar] {
      position: relative;
      z-index: var(--z-local, 1);
    }
    /*
     * Tablet portrait (≤64rem) acquire: secondary rail stacks BELOW the stage
     * (order after primary) so idle shutter chrome never intersects session
     * controls (structure: capture-idle-overlay-session-collision). Densify
     * heading + recent captures; keep discovery. Annotate keeps full rail.
     */
    [data-page="capture"][data-session-mode="acquire"] [data-capture-workbench-secondary] [data-capture-session-heading],
    [data-page="capture"]:not([data-session-mode="annotate"]) [data-capture-workbench-secondary] [data-capture-session-heading],
    [data-page="capture"][data-session-mode="acquire"] [data-capture-workbench-secondary] [data-capture-recent-captures],
    [data-page="capture"]:not([data-session-mode="annotate"]) [data-capture-workbench-secondary] [data-capture-recent-captures] {
      display: none;
      max-height: 0;
      min-height: 0;
      overflow: hidden;
      margin: 0;
      padding: 0;
      border-width: 0;
      pointer-events: none;
      visibility: hidden;
    }
    [data-page="capture"][data-session-mode="acquire"] [data-capture-workbench-primary],
    [data-page="capture"]:not([data-session-mode="annotate"]) [data-capture-workbench-primary] {
      order: 0;
      /* Size to in-flow shutter chrome — flex:1 shrink clipped overlay into secondary (structure audit). */
      flex: 0 0 auto;
      height: auto;
      min-width: 0;
      width: 100%;
      overflow: visible;
    }
    [data-page="capture"][data-session-mode="acquire"] [data-capture-workbench-secondary],
    [data-page="capture"]:not([data-session-mode="annotate"]) [data-capture-workbench-secondary] {
      display: flex;
      flex-direction: column;
      /* Room for case picker + specimen without clipping centres under stage. */
      /* No max-height: nested clip left last case cards under host (reachability). */
      max-height: none;
      min-height: 0;
      width: 100%;
      overflow-x: visible;
      overflow-y: visible;
      overscroll-behavior: contain;
      scroll-padding-block: var(--space-section, 0.75rem);
      /* Host PE none; case rows/inputs re-arm auto (reachability centre-hit). */
      pointer-events: none;
      visibility: visible;
      /* After primary stage so shutter (in primary) cannot paint over session. */
      order: 2;
    }
  }
  /* 44px touch floor on session-rail controls (case/specimen pickers,
     add-specimen form) — the command-chrome rule never covered the rail. */


  @media (pointer: coarse) {
    [data-page="capture"] [data-capture-workbench-secondary] .select,
    [data-page="capture"] [data-capture-workbench-secondary] input,
    [data-page="capture"] [data-capture-workbench-secondary] .btn {
      min-height: var(--touch-target-min, 2.75rem);
    }
  }
  @media (max-width: 47.99rem) {
    /*
     * iOS-camera fold order: the viewfinder owns the screen on phones. The
     * device/source fieldsets move BELOW the stage (still reachable by
     * scroll), and the stage bleeds edge-to-edge without panel chrome
     * (§8.6 immersive stage; unlayered token rules beat the card utilities).
     */
    [data-page="capture"] [data-capture-command-chrome]:not([data-capture-top-chrome]) {
      order: 5;
      border: 0;
    }
    [data-page="capture"] .bao-preview-region {
      border: 0;
      border-radius: 0;
    }
    /* Camera idiom: shutter column centered over the stage; every overlay
       row (tuning, shutter form wrapper, action rack) centers its content
       instead of hugging an edge. */
    [data-page="capture"] [data-capture-preview-overlay] {
      align-items: center;
    }
    [data-page="capture"] [data-capture-preview-overlay] > div,
    [data-page="capture"] [data-capture-preview-overlay] [data-capture-action-bar] {
      justify-content: center;
    }
    [data-page="capture"]
      .bao-preview-region[data-preview-state="live"]
      > [data-capture-preview-overlay] {
      bottom: calc(
        var(--dock-height) + var(--dock-safe-area-offset) + var(--space-section)
      );
    }
    /*
     * These three are IN FLOW inside #app-main, which already reserves the dock
     * band on its own bottom padding. Each used to add --dock-height again, so
     * each carried a second copy of that band as dead space. Measured at 390x844
     * with #app-main reserving 64px: the idle overlay's margin computed 76px, and
     * the placeholder's padding computed 120px with 120px empty below its centred
     * content inside a 284px box.
     *
     * The idle overlay really is position:static (tokens-layout-capture-preview),
     * so the absolute bottom offset above never applies to it — but static means
     * the container's padding DOES lift it, which is exactly why it must not lift
     * itself as well.
     *
     * What remains is the clearance these elements genuinely own: --space-section
     * for breathing room. The idle bar is in flow below the placeholder, so the
     * placeholder carries no shutter clearance. Neither is the dock.
     */
    [data-page="capture"]
      .bao-preview-region:not([data-preview-state="live"])
      > [data-capture-preview-overlay] {
      bottom: auto;
      margin-block-end: var(--space-section);
    }
    /* Secondary panel sits below stage — spacing only; #app-main clears the dock. */
    [data-page="capture"] [data-capture-workbench-secondary] {
      padding-block-end: var(--space-section);
    }
    /*
     * iOS Camera idiom (phone acquire): hide the entire session secondary rail
     * so the viewfinder owns the stage (structure:
     * capture-phone-acquire-secondary-visible). Case identity is shell context
     * + journey capsules. Annotate keeps its inspector rail.
     */
    [data-page="capture"][data-session-mode="acquire"] [data-capture-workbench-secondary],
    [data-page="capture"]:not([data-session-mode="annotate"]) [data-capture-workbench-secondary] {
      display: none;
      max-height: 0;
      min-height: 0;
      overflow: hidden;
      margin: 0;
      padding: 0;
      border-width: 0;
      pointer-events: none;
      visibility: hidden;
    }
    /*
     * Phone context completion (CA-01): the capsule above the shutter opens
     * the hidden session rail as a glass-strong bottom sheet so case +
     * specimen can be set without leaving the capture page.
     */
    [data-page="capture"] [data-capture-chrome][data-context-sheet-open="true"] [data-capture-workbench-secondary] {
      display: flex;
      flex-direction: column;
      position: fixed;
      inset-inline: var(--space-inline, 0.5rem);
      inset-block-end: calc(
        var(--dock-height, 4rem) + var(--dock-safe-area-offset, 0px) + var(--space-inline, 0.5rem)
      );
      z-index: var(--z-overlay, 1200);
      max-height: min(70dvh, 28rem);
      min-height: 0;
      width: auto;
      overflow-y: auto;
      overscroll-behavior: contain;
      -webkit-overflow-scrolling: touch;
      margin: 0;
      padding: var(--space-inline, 0.5rem);
      visibility: visible;
      /* Host passes hits; case rows / inputs re-arm PE so list centres work. */
      pointer-events: none;
      border: 1px solid var(--liquid-border-regular);
      border-radius: var(--radius-box, 1rem) var(--radius-box, 1rem) 0 0;
      background: color-mix(
        in oklab,
        var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
        transparent
      );
      -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
      backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
      box-shadow:
        var(--shadow-glass-floating),
        var(--shadow-glass-inner);
    }
    [data-page="capture"] [data-capture-chrome][data-context-sheet-open="true"] [data-capture-session-heading] {
      display: flex;
      max-height: none;
      min-height: 0;
      overflow: visible;
      visibility: visible;
      pointer-events: auto;
    }
    @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
      [data-page="capture"] [data-capture-chrome][data-context-sheet-open="true"] [data-capture-workbench-secondary] {
        background: var(--bg-panel);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
      }
    }
    @media (prefers-reduced-transparency: reduce) {
      [data-page="capture"] [data-capture-chrome][data-context-sheet-open="true"] [data-capture-workbench-secondary] {
        background: var(--color-base-100);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
      }
    }
    @media (forced-colors: active) {
      [data-page="capture"] [data-capture-chrome][data-context-sheet-open="true"] [data-capture-workbench-secondary] {
        background: Canvas;
        color: CanvasText;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border: 1px solid ButtonText;
        box-shadow: none;
      }
    }
    /*
     * Phone action bar: gallery · shutter · overflow only — annotate/AI/scan
     * actions left the bar at EVERY breakpoint and live in the grouped
     * overflow sheet, so no per-action hide chains exist. Bar discipline is
     * one tier rule in tokens-glass-shell-capture-overlay.ts.
     */
  }
  /*
   * Context capsule (persistent top chrome, acquire mode at every
   * breakpoint): opens the pickers bottom sheet on phone; reveals the docked
   * session rail on tablet/desktop. Annotate mode owns the inspector rail —
   * the capsule never paints there. Repeated attribute selectors outrank the
   * shell-wide .btn:not([hidden]):not(.hidden) display rule (0,3,0).
   */
  [data-page="capture"][data-session-mode="annotate"] [data-capture-context-sheet-toggle][data-capture-context-sheet-toggle] {
    display: none;
  }

/*
   * Context capsule is the COMPACT context affordance only.
   *
   * app-shell-capture-actions.ts routes it by width: at <=47.99rem
   * captureContextSheetViewport() opens the bottom sheet,
above that
   * revealCaptureContextRail() just scrolls the docked rail into view — where
   * SESSION CONTROLS already renders the active case,
the specimen picker and
   * a Change case action. So on tablet/desktop the capsule restated context
   * the operator could already see,
while its band cost a full row: measured
   * 263px of chrome above the preview at 1280x900,
of which this row was 48px
   * spanning 720px to hold one 200px button.
   *
   * display:none (not removal) — openCaptureContextSheet/closeCaptureContextSheet
   * resolve the toggle through chrome.querySelector and must still find it to
   * reflect aria-expanded.
   */
  @media (min-width: 48rem) {
    [data-capture-chrome] [data-capture-top-chrome] {
      display: none;
    }
  }
  .bao-preview-region {
    position: relative;
    display: flex;
    min-width: 0;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    container-type: inline-size;
    /* Region box must not steal hits from siblings (device strip) when empty
       planes pass through; interactive descendants re-arm PE:auto below. */
    pointer-events: none;
    background-image: radial-gradient(
      ellipse 120% 80% at 50% 38%,
      color-mix(in oklab, var(--color-primary) 7%, transparent),
      transparent 60%
    );
  }
  .bao-preview-region > [data-camera-preview-video],
  .bao-preview-region button,
  .bao-preview-region a,
  .bao-preview-region input,
  .bao-preview-region select,
  .bao-preview-region textarea,
  .bao-preview-region .btn,
  .bao-preview-region [data-primitive="Button"],
  .bao-preview-region [data-capture-empty-cta],
  .bao-preview-region fieldset {
    pointer-events: auto;
  }.bao-preview-region > [data-camera-preview-video],
.bao-preview-region > [data-camera-preview-placeholder] {
    min-height: 0;
    flex: 1 1 auto;
  }
  /* Idle: keep empty video from painting over the placeholder (mobile void gray). */
  .bao-preview-region > [data-camera-preview-video] {
    z-index: var(--z-base, 0);
    background: transparent;
  }
  /* Mirror preview — document flag from hardware prefs;no element style=. */
  html[data-capture-mirror-preview="true"]
    [data-page="capture"]
    [data-camera-preview-video],
html[data-capture-mirror-preview="true"] [data-page="capture"] video {
    transform: scaleX(-1);
  }
  .bao-preview-region:not([data-preview-state="live"])
    > [data-camera-preview-video] {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
  [data-capture-chrome][data-capture-case-ready="false"] [data-capture-command-chrome]:not([data-capture-top-chrome]) {
    display: none;
  }[data-capture-chrome][data-capture-case-ready="false"] [data-camera-preview-video],
[data-capture-chrome][data-capture-case-ready="false"] [data-capture-preview-overlay] {
    display: none;
  }
  .bao-preview-region > [data-camera-preview-placeholder] {
    z-index: var(--z-raised, 2);
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-inline, 0.5rem);
    padding: var(--space-section, 0.75rem);
    text-align: center;
    /* Empty plane passes hits; CTAs re-arm pointer-events on the action slot. */
    pointer-events: none;
  }/* Case-required shares layout with camera empty. Geometry only — display is
     owned solely by tokens-glass-shell-capture-context-plane.ts,
which must
     stay unlayered to beat the unlayered EmptyState primitive. A display
     declaration here would be dead on arrival. */
  .bao-preview-region > [data-capture-case-required] {
    z-index: var(--z-raised, 2);
    position: absolute;
    inset: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-inline, 0.5rem);
    padding: var(--space-section, 0.75rem);
    text-align: center;
    pointer-events: none;
  }.bao-preview-region > [data-camera-preview-placeholder] [data-capture-empty-cta],
.bao-preview-region > [data-capture-case-required] [data-capture-empty-cta],
.bao-preview-region > [data-camera-preview-placeholder] fieldset,
.bao-preview-region > [data-capture-case-required] fieldset {
    pointer-events: auto;
  }.bao-preview-region[data-preview-state="live"] > [data-camera-preview-video] {
    opacity: 1;
    visibility: visible;
    z-index: var(--z-local, 1);
  }/* Live hide for this plane has ONE owner,
in tokens-glass-shell-capture:
     that copy is unlayered and this module is layered,
so the rule that used
     to sit here could never apply. */
  /* Media capture: primary actions float on the viewport (glass overlay). */
  .bao-preview-region > [data-capture-preview-overlay] {
    pointer-events: none;
  }.bao-preview-region > [data-capture-preview-overlay] .pointer-events-auto,
.bao-preview-region > [data-capture-preview-overlay] form,
.bao-preview-region > [data-capture-preview-overlay] button,
.bao-preview-region > [data-capture-preview-overlay] a {
    pointer-events: auto;
  }
  /* Idle: placeholder and overlay share the column flow instead of stacking
     absolutely — centered placeholder copy must never be occluded by the
     floating action glass (design.pdf §1.1/§5.3: controls carry their own
     layer; content stays legible). Live keeps the floating overlay. */
  .bao-preview-region:not([data-preview-state="live"]) {
    row-gap: var(--space-inline, 0.5rem);
  }
  .bao-preview-region:not([data-preview-state="live"])
    > [data-camera-preview-placeholder] {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
  }

  /* Idle: the case-required plane shares the placeholder's column flow. An absolute inset-0 stack in idle overlapped the context strip and stole the select-case/select-camera hit points (structure audit, phone band). Live keeps both layers stacked over the stream. */
  .bao-preview-region:not([data-preview-state="live"])
    > [data-capture-case-required] {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
  }/*
   * Idle: control chrome is IN FLOW under the placeholder,
docked to the
   * SAME bottom band the live overlay floats over — no geometry jump of the
   * primary control cluster when a stream attaches (CLS guard). Absolute
   * bottom-docked utilities are live-only so the shutter never paints over
   * session controls (mobile composition — design.pdf §5.3 / §8.6).
   *
   * The dock owns its own placement rather than inheriting it as a side
   * effect: an auto block-start margin consumes whatever free space the
   * column has,
so the resting offset is the block-end margin alone. That
   * margin is --space-section,
the same value the live overlay uses for its
   * bottom inset above,
so the cluster does not shift when a stream attaches.
   * The previous --space-inline block margin left idle 4px higher than live,
* and left the dock's position dependent on the placeholder still winning
   * the flex-grow — a coupling that silently breaks if that sibling changes.
   *
   * Phone (<=47.99rem) deliberately overrides the block-end margin with the
   * fixed mobile dock's clearance (tokens-layout-capture-preview phone band):
   * there the stage is full-bleed and the live overlay floats at the same
   * dock-cleared offset,
so the larger idle gap is the CLS guard,
not slack.
   */
  .bao-preview-region:not([data-preview-state="live"])
    > [data-capture-preview-overlay] {
    position: static;
    inset: auto;
    z-index: auto;
    margin-block-start: auto;
    margin-block-end: var(--space-section, 0.75rem);
    margin-inline: var(--space-section, 0.75rem);
    align-self: stretch;
    width: auto;
  }/* Live: float controls over the stream (camera idiom). */
  .bao-preview-region[data-preview-state="live"] > [data-capture-preview-overlay] {
    position: absolute;
    inset-inline: var(--space-section, 0.75rem);
    bottom: var(--space-section, 0.75rem);
    z-index: var(--z-overlay, 1200);
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-inline);
    padding: var(--space-section);
  }
  /* The overlay action row never wraps — one inline media-control bar at
     every breakpoint; secondary actions live in the overflow sheet. */
  [data-capture-preview-overlay] [data-capture-action-bar] {
    flex-wrap: nowrap;
  }

  /*
   * Phone + Dynamic Type: the bar is two grid rows on phones (picker, then
   * gallery · shutter · More — tokens-layout-capture-preview-part-b), so at
   * root font 125% every HIG-floor control fits without a scroller. The
   * former one-row snap scroller (overflow-x: auto) painted a vertical
   * scrollbar inside the bar once the second row existed (measured 390x844).
   */
  /* Camera composition (Apple grammar): gallery · shutter · overflow. The bar
     is the three-track grid owned by tokens-layout-capture-preview-part-b
     ("leading shutter more"); the former flex auto-margins were dead on it. */
  /*
   * Persistent top chrome: the context capsule sits under the segmented mode
   * selector — the only top elements besides the L0 viewfinder. Solid pill
     chrome (persistent control,
not a transient glass layer).
   */
  [data-page="capture"] [data-capture-top-chrome] {
    flex: 0 0 auto;
    padding-block: 0 var(--space-micro, 0.25rem);
  }
  [data-page="capture"] [data-capture-top-chrome] [data-capture-context-sheet-toggle] {
    border-radius: var(--radius-full, 9999px);
    max-width: min(100%, 16rem);
  }

  /* Device command strip: one row when possible; never stretch fieldsets into voids.
   * Re-arm pointer input on real controls while stage planes pass through hits. */
  [data-page="capture"] [data-capture-command-chrome] {
    flex: 0 0 auto;
    overflow: visible;
  }
  [data-page="capture"] [data-capture-command-chrome] > *,
  [data-page="capture"] [data-capture-device-select],
  [data-page="capture"] [data-camera-rescan],
  [data-page="capture"] [data-capture-control] {
    position: relative;
    z-index: var(--z-raised-high, 3);
    pointer-events: auto;
  }
  /* Stage hosts pass hits when their boxes still overlap the device strip;
   * interactive descendants re-arm PE:auto so shutter/CTAs keep working. */
  [data-page="capture"] [data-capture-workbench],
  [data-page="capture"] .bao-media-workbench,
  [data-page="capture"] [data-capture-workbench-primary],
  [data-page="capture"] .bao-media-workbench-primary,
  [data-page="capture"] [data-capture-workbench-secondary],
  [data-page="capture"] .bao-media-workbench-secondary {
    pointer-events: none;
  }
  [data-page="capture"] [data-capture-workbench-secondary] *,
  [data-page="capture"] .bao-media-workbench-secondary * {
    pointer-events: none;
  }
  /* Re-arm only real controls — never non-interactive fieldset hosts. */
  [data-page="capture"] [data-capture-workbench] button,
  [data-page="capture"] [data-capture-workbench] a,
  [data-page="capture"] [data-capture-workbench] input:not([type="hidden"]),
  [data-page="capture"] [data-capture-workbench] select,
  [data-page="capture"] [data-capture-workbench] textarea,
  [data-page="capture"] [data-capture-workbench] .btn,
  [data-page="capture"] [data-capture-workbench] [data-primitive="Button"],
  [data-page="capture"] [data-capture-workbench] [data-case-card],
  [data-page="capture"] [data-capture-workbench] [data-case-option],
  [data-page="capture"] [data-capture-workbench-secondary] button,
  [data-page="capture"] [data-capture-workbench-secondary] a,
  [data-page="capture"] [data-capture-workbench-secondary] input:not([type="hidden"]),
  [data-page="capture"] [data-capture-workbench-secondary] select,
  [data-page="capture"] [data-capture-workbench-secondary] .btn,
  [data-page="capture"] [data-capture-workbench-secondary] [data-case-card],
  [data-page="capture"] [data-capture-workbench-secondary] [data-case-option],
  [data-page="capture"] .bao-media-workbench button,
  [data-page="capture"] .bao-media-workbench a,
  [data-page="capture"] .bao-media-workbench input:not([type="hidden"]),
  [data-page="capture"] .bao-media-workbench select,
  [data-page="capture"] .bao-media-workbench textarea,
  [data-page="capture"] .bao-media-workbench .btn,
  [data-page="capture"] .bao-media-workbench [data-primitive="Button"],
  [data-page="capture"] .bao-media-workbench [data-case-card],
  [data-page="capture"] .bao-media-workbench [data-case-option] {
    pointer-events: auto;
  }
  [data-page="capture"] [data-capture-command-chrome] .bao-capture-device-grid {
    align-items: end;
  }
  /* Highest-specificity re-arm for case picker cards (beat secondary * none). */
  [data-page="capture"] [data-capture-workbench-secondary] button[data-case-option],
  [data-page="capture"] [data-capture-workbench-secondary] button[data-case-card],
  [data-page="capture"] .bao-media-workbench-secondary button[data-case-option],
  [data-page="capture"] .bao-media-workbench-secondary button[data-case-card] {
    pointer-events: auto;
    position: relative;
    z-index: var(--z-raised, 2);
  }

  /* Narrow container: collapse device grid to a single column so fieldsets
     stack instead of shrinking below usable width. */
  @container (max-width: 48rem) {
    [data-page="capture"] [data-capture-command-chrome] .bao-capture-device-grid {
      grid-template-columns: 1fr;
      gap: var(--space-micro);
    }
  }/*
   * Media immersive: hide layout customizer chrome — stage owns the fold.
   * Customize remains on collection/home surfaces and via command palette.
   * Specificity beats page-header action flex rules without importance.
   */
  [data-page-type="media"] [data-page-header-actions] [data-dashboard-edit-toggle].btn,
[data-page-type="media"] [data-dashboard-customizer-dropdown],
[data-page-type="media"] [data-dashboard-customizer-popover],
[data-page-type="media"] [data-dashboard-panel-controls],
[data-page="capture"] [data-page-header-actions] [data-dashboard-edit-toggle].btn,
[data-page="editor"] [data-page-header-actions] [data-dashboard-edit-toggle].btn {
    display: none;
    width: 0;
    min-width: 0;
    max-width: 0;
    height: 0;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    margin: 0;
    border: 0;
    pointer-events: none;
    visibility: hidden;
  }
  [data-page="capture"] [data-capture-workbench-secondary] {
    /* overflow-y:auto re-clipped case-card centres (reachability); page chrome scrolls. */
    overflow-y: visible;
    overscroll-behavior: contain;
    min-height: 0;
  }

  /*
   * Media page header is sr-only (shell title owns naming). Keep a compact
   * escape hatch if a page still emits a visible header.
   */
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only) {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-micro);
    padding-block-end: var(--space-micro);
    /* Width alone inherited preflight currentColor — a full-brightness
       hairline. Divide with the same subtle token mix panels use. */
    border-block-end: 1px solid
      color-mix(in oklab, var(--color-base-300, var(--border-base)) 30%, transparent);
  }
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only) h1 {
    font-size: var(--text-sm, 0.875rem);
    line-height: 1.25;
  }
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only) p {
    display: none;
  }
  /* Session mode tabs sit under shell chrome as primary media navigation.
     Segmented sizing SSOT: tokens-glass-shell-capture.ts. */
  [data-page-type="media"] [data-session-mode-tabs] {
    margin-block-end: 0;
  }/*
   * The phone dock for these header actions is NOT declared here. This module
   * emits inside @layer utilities,
and the canonical
   * .bao-page-header width:100% is unlayered — an unlayered declaration
   * beats a layered one regardless of specificity,
so a width set here is
   * silently discarded (measured: inline-size stayed 359px). It lives in
   * tokens-glass-shell-capture-status.ts,
which emits unlayered.
   */
  /*
   * Media full-bleed belongs to the camera/canvas STAGE only. Structured
   * chrome (page header,
journey band,
control strips) keeps the page
   * gutter — content must never shear against the viewport edge (§8.2/§9.5).
   */
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only),
[data-page-type="media"] [data-capture-workbench-secondary] {
    padding-inline: var(--page-gutter-inline, var(--space-section, 0.75rem));
  }/*
   * Fine pointer: icon-first with tip/hover reveal. Coarse pointer: visible
   * compact labels — hover must not own essential media actions (design.pdf §6).
   *
   * Both branches now honour data-toolbar-density. They did not: the coarse
   * branch below gated on [data-toolbar-density="labeled"] while this one
   * clipped the editor dock unconditionally,
so the same file answered the
   * same question two ways and a dock asking for labels got them on touch and
   * lost them on a mouse.
   *
   * The clip is FINAL for the dock, not rest-only: part-1/part-2's generic
   * hover reveal (max-width/opacity + width:auto on .btn-square) is the
   * contract for STANDALONE square chrome. Inside the joined dock strip it
   * cannot work — measured 2026-09-01 at 1440x900: unclipping on hover made
   * the joined row clip every revealed label to one glyph and squash the key
   * chip, because .join members share the strip's fixed width. The dock's
   * hover surface is therefore the tooltip (data-tip carries "Ruler (M)"),
   * and aria-pressed context is carried by the button's pressed style.
   */
  @media (hover: hover) and (pointer: fine) {[data-page="capture"] [data-capture-preview-overlay] .bao-toolbar-label,
[data-page="capture"] [data-capture-action-bar] .bao-toolbar-label,
[data-editor-toolbar]:not([data-toolbar-density="labeled"]) .bao-toolbar-label,
[data-editor-toolbar]:not([data-toolbar-density="labeled"]) .bao-toolbar-action .bao-toolbar-label,
[data-editor-toolbar]:not([data-toolbar-density="labeled"]) [data-hig-key-hint] {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
  }@media (hover: none),
(pointer: coarse) {[data-page="capture"] [data-capture-preview-overlay] .bao-toolbar-label,
[data-page="capture"] [data-capture-action-bar] .bao-toolbar-label:not(.sr-only),
[data-editor-toolbar][data-toolbar-density="labeled"] .bao-toolbar-label,
[data-editor-toolbar][data-toolbar-density="labeled"] .bao-toolbar-action .bao-toolbar-label {
      position: static;
      width: auto;
      height: auto;
      max-width: 6rem;
      margin: 0;
      margin-inline-start: var(--space-micro, 0.25rem);
      padding: 0;
      overflow: hidden;
      clip: auto;
      white-space: nowrap;
      text-overflow: ellipsis;
      font-size: var(--text-xs, 0.75rem);
      opacity: 1;
    }
  }
  /* Annotate workbench: canvas column owns height;inspector scrolls alone. */
  [data-page-type="media"] [data-editor-chrome],
[data-page="capture"] [data-editor-chrome] {
    display: flex;
    min-height: 0;
    flex: 1 1 0%;
    flex-direction: column;
    overflow: hidden;
  }/* The session wrapper receives a generated section payload. It must join
     the bounded flex chain or its canvas collapses when secondary chrome is
     intentionally absent. */
  [data-page-type="media"] [data-session-panel] > section,
[data-page="capture"] [data-session-panel] > section {
    display: flex;
    min-height: 0;
    max-height: 100%;
    flex: 1 1 0%;
    flex-direction: column;
    overflow: hidden;
  }[data-page-type="media"] .bao-editor-workbench,
[data-page="capture"] .bao-editor-workbench {
    min-height: 0;
    flex: 1 1 0%;
    overflow-x: hidden;
    overflow-y: hidden;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
  }[data-page-type="media"] [data-editor-canvas-overlays],
[data-page="capture"] [data-editor-canvas-overlays] {
    max-height: 100%;
  }
/*
 * Phone capture bar (Apple Camera): the mode picker is its own centred row,
 * the shutter row puts gallery at the start, the shutter in the middle and
 * More at the end. Measured 2026-08-23 at 390x844: one flex row of picker +
 * gallery + shutter + More was 352px in a 330px bar and More painted past
 * the card edge.
 */
/*
 * Capture bar grammar at every width (Apple Camera): the shutter sits on the
 * bar's centre line, the mode picker and gallery lead, More trails — measured
 * 2026-08-23 at 1440x900 the shutter floated wherever the flex row left it.
 */
[data-page="capture"] [data-capture-preview-actions] {
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  grid-template-areas: "leading shutter more";
}
[data-page="capture"] [data-capture-preview-actions] > [data-capture-leading] {
  grid-area: leading;
  justify-self: start;
}
[data-page="capture"] [data-capture-preview-actions] > form {
  grid-area: shutter;
  justify-self: center;
}
[data-page="capture"] [data-capture-preview-actions] > [data-capture-action-bar] {
  grid-area: more;
  justify-self: end;
}
@media (max-width: 40rem) {
  [data-page="capture"] [data-capture-preview-actions] {
    grid-template-areas:
      "mode mode mode"
      "gallery shutter more";
    row-gap: var(--space-inline, 0.5rem);
  }
  /* The leading group dissolves so the picker takes its own row. */
  [data-page="capture"] [data-capture-preview-actions] > [data-capture-leading] {
    display: contents;
  }
  [data-page="capture"] [data-capture-preview-actions] [data-capture-media-mode-switch] {
    grid-area: mode;
    justify-self: center;
  }
  [data-page="capture"] [data-capture-preview-actions] [data-capture-gallery-root] {
    grid-area: gallery;
    justify-self: start;
  }
}
/*
 * The depth status line is a live region for a depth session; a device that
 * simply has no depth capture keeps the sentence for assistive tech and the
 * disabled "3D depth" segment carries it visually (its tip). A permanent
 * caption on every capture bar was noise, not status.
 */
[data-page="capture"] [data-capture-depth-status][data-state="empty"] {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


  /* Context chips are interactive buttons — reset native button chrome. */
  button.bao-context-card {
    appearance: none;
    cursor: pointer;
    text-align: start;
    font: inherit;
    color: inherit;
  }
  /* On full Chat page, hide the rail mini-composer (same surface — no dual chat). */
  body:has([data-page="chat"]) #session-rail-live [data-session-section="ai"],
  body:has([data-page="chat"]) #chat-assistant-rail-live,
  body:has([data-page="chat"]) [data-chat-assistant-rail] {
    display: none;
  }
  /* Media tabs never steal vertical space with trailing margin. */
  [data-page-type="media"] [data-session-mode-tabs] {
    margin-bottom: 0;
  }

  .bao-conversation-empty-panel-body {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-inline, 0.5rem);
  }
  .bao-conversation-empty-header {
    display: flex;
    min-width: 0;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-inline, 0.5rem);
  }
  .bao-conversation-empty-header > [aria-hidden="true"] {
    flex: 0 0 auto;
  }
  .bao-conversation-empty-header > div:last-child {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .bao-composer-shell {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 0;
  }
  [data-page-type="conversation"] > [data-state-content],
  .bao-conversation-shell {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0;
    height: 100%;
  }
  #app-main:has([data-page-type="conversation"]) > [data-page] > [data-state-content]:not([hidden]),
  #app-main:has([data-page-type="media"]) > [data-page] > [data-state-content]:not([hidden]) {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
  }
  .bao-conversation-messages {
    position: relative;
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    overflow: hidden;
  }
  /* Empty conversations scroll as one content flow when enlarged text exhausts
   * the shell. Populated conversations retain their independent message scroller. */
  .bao-conversation-shell:has([data-chat-empty]:not([hidden])) {
    overflow-y: auto;
  }
  .bao-conversation-shell:has([data-chat-empty]:not([hidden])) > .bao-conversation-messages {
    flex: 1 0 auto;
    overflow: visible;
  }
  .bao-conversation-shell:has([data-chat-empty]:not([hidden])) > .bao-conversation-footer {
    position: static;
  }
  .bao-conversation-thread {
    min-height: 0;
    flex: 1 1 auto;
  }
  /*
   * Empty conversation: surface starters in the vertical middle of the message
   * region so the workbench is not a hollow top strip over dead white space.
   */
  .bao-conversation-empty {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
    gap: var(--space-inline, 0.5rem);
    padding-block: var(--space-section, 0.75rem);
  }
  .bao-conversation-empty-panel-body {
    max-width: min(100%, 40rem);
  }
  /*
   * Phone context disclosure stays compact; empty-state instructions remain
   * fully readable as text size and spacing grow.
   */
  @media (max-width: 47.99rem) {
    /* Compact disclosure remains a named 44px icon control beside the model. */
    .bao-chat-context-grid [data-chat-context-more] > .btn {
      --button-inline-padding: 0;
      inline-size: var(--touch-target-min);
    }
    .bao-chat-context-grid [data-chat-context-more] > .btn .bao-toolbar-label {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    .bao-conversation-empty-panel-body {
      padding: var(--space-inline, 0.5rem);
      gap: var(--space-micro, 0.25rem);
    }
  }
  .bao-conversation-empty [class*="conversationEmptyActions"],
  .bao-conversation-empty .flex.w-full.flex-wrap {
    width: 100%;
  }
  /*
   * Dock clearance has ONE owner: #app-main. This footer is in flow inside it
   * (flex: 0 0 auto, no positioning), so the container's bottom padding already
   * lifts it above the fixed dock. Reserving --dock-height here counted the same
   * band twice — measured at 390x844, #app-main reserved 64px and the footer
   * reserved another 64px, leaving 64px empty below the composer inside a 188px
   * box. The safe-area floor is a different concern (home indicator) and stays,
   * now at every width rather than only above 80rem.
   */
  .bao-conversation-footer {
    min-width: 0;
    flex: 0 0 auto;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .bao-conversation-gutter {
    padding-inline-start: max(var(--page-gutter-inline-compact), env(safe-area-inset-left, 0px));
    padding-inline-end: max(var(--page-gutter-inline-compact), env(safe-area-inset-right, 0px));
  }
  @media (min-width: 48rem) {
    .bao-conversation-gutter {
      padding-inline-start: max(var(--page-gutter-inline), env(safe-area-inset-left, 0px));
      padding-inline-end: max(var(--page-gutter-inline), env(safe-area-inset-right, 0px));
    }
  }
  /*
   * Conversation dock + composer: solid form surface (design.pdf §5.5 / §1.5).
   * Glass stays on shell chrome only — not on dense input shells.
   */
  footer.bao-conversation-dock,
  .bao-conversation-footer.bao-conversation-dock,
  .bao-composer-dock-safe {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky, 1100);
    background: transparent;
    border: 0;
    box-shadow: none;
  }
  /* Tokenized dock fade (design.pdf §3) — no raw opacity-40 / gradient utilities. */
  .bao-composer-dock-fade {
    background: linear-gradient(
      to top,
      color-mix(in oklab, var(--color-base-100) 40%, transparent),
      transparent
    );
  }
  /* Context navigation inherits the shared glass surface material. */
  .bao-conversation-context-strip {
    box-sizing: border-box;
    min-width: 0;
    max-width: 100%;
    width: 100%;
    flex: 0 0 auto;
    position: relative;
    top: auto;
    z-index: auto;
    overflow-x: clip;
    border-radius: var(--radius-panel);
    padding: var(--space-inline);
    -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
    backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  }
  .bao-chat-context-grid.bao-conversation-context-strip {
    width: calc(100% - 2 * var(--page-gutter-inline-compact));
    max-width: calc(var(--page-conversation) - 2 * var(--page-gutter-inline-compact));
  }
  @media (min-width: 48rem) {
    .bao-chat-context-grid.bao-conversation-context-strip {
      width: calc(100% - 2 * var(--page-gutter-inline));
      max-width: calc(var(--page-conversation) - 2 * var(--page-gutter-inline));
    }
  }
  /* Always a single horizontal rail — never a multi-row card wall. */
  .bao-chat-context-grid {
    display: flex;
    box-sizing: border-box;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    min-width: 0;
    max-width: 100%;
    width: 100%;
    gap: var(--space-micro);
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }
  .bao-chat-context-grid .bao-context-card,
  .bao-chat-context-grid [data-chat-context-card] {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    flex: 0 0 auto;
    min-height: var(--touch-target-min, 2.75rem);
    max-width: calc(100% - var(--touch-target-min) - var(--space-inline));
    transform: none;
    animation: none;
  }
  /*
   * Context-more dropdown open-state unclip/lift lives in
   * tokens-layout-dock-densify-b (same cascade layer as the rail overflow
   * rule it overrides) — keep this file free of a losing duplicate.
   */
  /*
   * Context strip remounts on capture-dx:context-changed. Never re-run
   * entrance or transform motion — WebView 101 paints continuous vibration.
   */
  [data-chat-context],
  [data-chat-context].htmx-added,
  [data-chat-context] .htmx-added,
  [data-chat-context] .bao-context-card,
  [data-capture-context-strip],
  [data-capture-context-strip].htmx-added,
  [data-capture-context-strip] .htmx-added {
    animation: none;
    transform: none;
  }
  .bao-chat-context-grid [data-chat-context-more] {
    flex: 0 0 auto;
  }
  [data-chat-composer] .bao-toolbar-action .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  [data-chat-composer] [data-chat-model-fieldset] {
    min-width: 0;
    flex: 1 1 var(--touch-target-min);
    max-width: none;
    padding: 0;
    margin: 0;
  }
  .bao-composer-shell > .border-t {
    border-block-start-width: 0;
    padding: var(--space-inline);
    gap: var(--space-inline);
  }
  .bao-composer-shell > .border-t > .flex-1 {
    align-items: flex-end;
    gap: var(--space-inline);
  }
  [data-chat-insert-tools] {
    flex: 0 0 auto;
    gap: var(--space-inline);
  }
  .bao-composer-shell > .fieldset-legend {
    padding-inline: var(--space-inline);
  }
  /*
   * Phone densify: value-first chips (Apple status pill). Mono type labels
   * hide; aria-label on the button still names the field.
   */
  @media (max-width: 47.99rem) {
    /*
     * Chips only. The overflow menu is a DOM descendant of this grid even
     * though its popover paints in the top layer, so every rule here reached
     * the menu rows too: the kind label was clipped to 1px and each row was
     * capped at 9.5rem inside a panel with room for full-width rows. A chip can
     * drop its label to aria-label because the strip around it supplies the
     * context; a menu row is read alone and must name itself.
     */
    .bao-chat-context-grid .bao-context-card:not([role="menuitem"]) > span:first-child,
    .bao-chat-context-grid [data-chat-context-card]:not([role="menuitem"]) > span:first-child {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    .bao-chat-context-grid .bao-context-card:not([role="menuitem"]),
    .bao-chat-context-grid [data-chat-context-card]:not([role="menuitem"]) {
      position: relative;
      max-width: calc(100% - var(--touch-target-min) - var(--space-inline));
      padding-inline: var(--space-inline, 0.5rem);
    }
    .bao-chat-context-grid .bao-context-card > span:last-child,
    .bao-chat-context-grid [data-chat-context-card] > span:last-child {
      font-size: var(--text-xs, 0.75rem);
    }
    /* Starter prompts: one compact wrap row, not a tall button column. */
    [data-chat-empty] fieldset {
      flex-direction: row;
      flex-wrap: wrap;
    }
    [data-chat-empty] fieldset > .btn {
      flex: 1 1 auto;
      flex-basis: min(100%, 9.5rem);
      max-width: 100%;
    }
  }
  /* Empty-state invite: solid content panel (never glass-on-content).
     Static, in-flow body copy = content, not chrome. Was a liquid tint +
     backdrop-filter painting at 0.84 alpha, contradicting this very line;
     now mirrors .bao-composer-shell below (legibility over transparency). */
  .bao-conversation-empty .bao-conversation-empty-panel-body {
    border: 1px solid color-mix(in oklab, var(--border-base, var(--color-base-300)) 70%, transparent);
    border-radius: var(--radius-box, 1rem);
    background: var(--bg-panel, var(--color-base-100));
    box-shadow:
      0 1px 0 color-mix(in oklab, var(--color-base-content) 5%, transparent) inset,
      0 2px 8px color-mix(in oklab, var(--color-base-content) 6%, transparent);
  }
  /* Composer shell: solid interactive panel (legibility over transparency). */
  .bao-composer-shell {
    border: 1px solid color-mix(in oklab, var(--border-base, var(--color-base-300)) 70%, transparent);
    border-radius: var(--radius-box, 1rem);
    background: var(--bg-panel, var(--color-base-100));
    box-shadow:
      0 1px 0 color-mix(in oklab, var(--color-base-content) 5%, transparent) inset,
      0 2px 8px color-mix(in oklab, var(--color-base-content) 6%, transparent);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .bao-chat-context-grid .bao-context-card {
    min-width: 0;
  }

  /* Phone: composer toolbar scrolls; model select flexes with container. */
  @media (max-width: 47.99rem) {
    [data-chat-composer] [data-chat-composer-form] > .flex.items-center.gap-space-inline:first-child,
    [data-chat-composer] .bao-composer-shell > .border-b {
      display: flex;
      overflow-x: auto;
      overscroll-behavior-x: contain;
      -webkit-overflow-scrolling: touch;
      flex-wrap: nowrap;
      touch-action: pan-x;
      scroll-snap-type: x proximity;
      /* Fade trailing edge so clipped tools read as scrollable (HIG). */
      mask-image: linear-gradient(
        to right,
        var(--black) 0%,
        var(--black) calc(100% - 1.25rem),
        transparent 100%
      );
      -webkit-mask-image: linear-gradient(
        to right,
        var(--black) 0%,
        var(--black) calc(100% - 1.25rem),
        transparent 100%
      );
    }
    /* Each tool keeps intrinsic size — never squash into a clipped flex row. */
    [data-chat-composer] [data-chat-composer-form] > .flex.items-center.gap-space-inline:first-child > *,
    [data-chat-composer] .bao-composer-shell > .border-b > * {
      flex: 0 0 auto;
      scroll-snap-align: start;
    }
    [data-chat-composer] [data-chat-model-fieldset] {
      flex-basis: 100%;
      max-width: none;
      min-width: 0;
    }
    [data-chat-composer] [data-chat-model-select] {
      max-width: 100%;
    }
  }

/* The phone composer owns model selection; omit its duplicate context shortcut. */
@media (max-width: 47.99rem) {
  [data-page="chat"] [data-chat-context-card="model"] {
    display: none;
  }
}

  /*
   * Collection pages may embed a shared context-strip (specimens).
   * Never inherit conversation track/gutter chrome on data collections.
   */
  [data-page-type="collection"] [data-chat-context].bao-conversation-context-strip,
  [data-page-type="collection"] [data-chat-context].bao-chat-context-grid {
    max-width: none;
    margin-inline: 0;
    padding-inline: 0;
    border-block-end: 1px solid color-mix(in oklab, var(--border-base, var(--color-base-300)) 70%, transparent);
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  [data-page-type="collection"] [data-chat-context] .bao-context-card {
    background: var(--bg-panel, var(--color-base-100));
    border-color: color-mix(in oklab, var(--border-base, var(--color-base-300)) 88%, transparent);
  }
  @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .bao-conversation-context-strip,
    footer.bao-conversation-dock,
    .bao-conversation-footer.bao-conversation-dock,
    .bao-composer-shell,
    .bao-conversation-empty .bao-conversation-empty-panel-body {
      background-color: var(--color-base-100, var(--bg-panel));
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }

  @media (prefers-reduced-transparency: reduce) {
    .bao-conversation-context-strip,
    footer.bao-conversation-dock,
    .bao-conversation-footer.bao-conversation-dock,
    .bao-composer-shell,
    .bao-conversation-empty .bao-conversation-empty-panel-body {
      background-color: var(--color-base-100, var(--bg-panel));
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }

  .bao-conversation-composer,
  .bao-conversation-empty,
  .bao-context-card {
    min-width: 0;
  }
  /*
   * Happydumpling agent chat empty: top-docked invite (not mid-void center).
   * Messages region still fills remaining height so the composer stays docked.
   */
  .bao-conversation-messages:has(.bao-conversation-empty:not([hidden])) {
    flex: 1 1 auto;
    min-height: 0;
    justify-content: flex-start;
    overflow-y: auto;
  }
  .bao-conversation-messages:has(.bao-conversation-empty:not([hidden])) .bao-conversation-thread {
    /* Do not use visibility:hidden + min-height:100% — WebView 101 reflows and shakes. */
    display: none;
  }
  .bao-conversation-empty:not([hidden]) {
    /* In-flow flex host — absolute overlay made starters feel static/dead. */
    position: relative;
    inset: auto;
    display: flex;
    width: 100%;
    max-width: min(48rem, 100%);
    margin-inline: auto;
    min-height: 0;
    flex: 1 1 auto;
    align-items: stretch;
    justify-content: flex-start;
    box-sizing: border-box;
    z-index: auto;
  }
  /*
   * Beat UA/daisy flex utilities via specificity: class+attr beats
   * single-class .flex / .hidden when empty host is marked [hidden].
   * Flex host only applies via :not([hidden]) above — dual-gated hide.
   */
  [data-page="dashboard"] [data-dashboard-widget]:not([data-widget="recent-cases"]) {
    min-height: 0;
  }
  [data-page="dashboard"] [data-dashboard-widget][data-widget="recent-cases"],
  [data-page="dashboard"] [data-dashboard-widget][data-widget-id="recent-cases"] {
    min-width: 0;
  }
  [data-page="dashboard"] [data-dashboard-widget][data-widget="recent-cases"] .bao-panel-scroll,
  [data-page="dashboard"] [data-dashboard-widget][data-widget-id="recent-cases"] .bao-data-panel,
  [data-page="dashboard"] [data-dashboard-widget][data-widget-id="recent-cases"] .bao-panel-scroll {
    max-height: min(22rem, 42vh);
  }
  /* Form/detail pages: same fluid gutters as collection (HD content track).
   * block-end is dock SSOT — never use padding-block shorthand here. */
  #app-main:has([data-page-type="form"]),
  #app-main:has([data-page-type="detail"]),
  #app-main:has([data-page-type="ops"]) {
    padding-block-start: var(--page-gutter-block);
    padding-inline: var(--page-gutter-inline);
  }
  @media (max-width: 48rem) {
    #app-main:has([data-page-type="form"]),
    #app-main:has([data-page-type="detail"]),
    #app-main:has([data-page-type="ops"]) {
      padding-inline-start: max(var(--page-gutter-inline-compact), env(safe-area-inset-left, 0px));
      padding-inline-end: max(var(--page-gutter-inline-compact), env(safe-area-inset-right, 0px));
    }
  }
  /* Page headers: full-bleed actions row on narrow screens (SSOT + fluid). */
  @media (max-width: 40rem) {
    [data-primitive="PageHeader"] {
      align-items: stretch;
    }
    [data-primitive="PageHeader"] .card-actions {
      width: 100%;
      justify-content: flex-start;
    }
  }
  [data-page-type="form"] > [data-state-content],
  [data-page-type="detail"] > [data-state-content],
  [data-page-type="ops"] > [data-state-content] {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: var(--page-section-gap);
  }
  /* Collection primary table host — solid operational surface (tokens only). */
  [data-page-type="collection"] [data-case-list],
  [data-page-type="collection"] [data-table-host],
  [data-page="cases"] [data-cases-workbench],
  .bao-table-host {
    min-width: 0;
  }


  .bao-conversation-messages > .bao-conversation-empty[hidden],
  .bao-conversation-messages > [data-chat-empty][hidden],
  .bao-conversation-empty[hidden],
  [data-chat-empty][hidden] {
    display: none;
  }
  .bao-conversation-empty-panel-body {
    width: 100%;
    min-width: 0;
  }
  [data-chat-empty] .bao-toolbar-label {
    display: inline;
    white-space: normal;
  }
  /*
   * Full /chat page owns the conversation surface. Hide the rail AI section
   * so operators never face dual composers (page + session rail) — single AI
   * SSOT (enterprise conversation contract).
   */
  body:has([data-page="chat"])
    #session-rail
    [data-session-section="ai"],
  body:has([data-page="chat"])
    #session-rail-sheet
    [data-session-section="ai"] {
    display: none;
  }



  [data-page-type="collection"],
  [data-page-type="detail"] {
    display: flex;
    min-width: 0;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--space-section);
  }
  [data-page-type="collection"] > [data-state-content],
  [data-page-type="detail"] > [data-state-content] {
    flex: 1 1 auto;
  }
  /* Collection pages: last interactive row clears fixed mobile dock. */
  @media (max-width: 47.99rem) {
    [data-page-type="collection"] > [data-state-content] {
      padding-block-end: calc(var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
    }
  }

  @media (max-width: 47.99rem) {
    [data-page-type="collection"] .overflow-y-auto,
    [data-page-type="collection"] [data-scroll-region],
    [data-page-type="collection"] .bao-panel-scroll {
      scroll-padding-bottom: calc(var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
      padding-block-end: calc(var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
    }
  }
  
  
  [data-page-type="media"] [data-capture-device-toolbar],
  [data-page-type="media"] [data-capture-command-chrome] .bao-capture-device-grid {
    gap: var(--space-micro);
    align-items: stretch;
  }
  @media (min-width: 40rem) {
    [data-page-type="media"] [data-capture-command-chrome] .bao-capture-device-grid {
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    }
  }
  [data-page-type="media"] [data-capture-command-chrome] {
    gap: var(--space-micro);
    padding-inline: var(--space-inline);
  }
  [data-page-type="media"] [data-capture-command-chrome] fieldset,
  [data-page-type="media"] [data-capture-command-chrome] [data-capture-control] {
    min-width: 0;
    padding: var(--space-micro) var(--space-inline);
  }
  /*
   * Background only. Layout (display/flex-direction/align/justify/gap) and —
   * critically — VISIBILITY belong to tokens-layout-capture-preview.ts, which
   * hides this placeholder while [data-capture-case-ready="false"] so the
   * case-required overlay can own the plane. Re-declaring display:flex here
   * gave that one question a second owner and defeated the conditional hide:
   * on /capture at 375px the camera-empty and case-required overlays painted
   * on top of each other ("Live Preview" over "Active case", colliding CTAs).
   * That file's own comment already said not to force display here.
   */
  .bao-preview-region > [data-camera-preview-placeholder] {
    background:
      radial-gradient(circle at 50% 40%, color-mix(in oklab, var(--color-base-content) 4%, transparent), transparent 55%);
  }
  [data-page-type="form"] [data-settings-tabs] {
    max-width: var(--max-w-page-wide, 72rem);
    width: 100%;
  }
  [data-page-type="form"] [data-settings-panel] {
    max-width: none;
    width: 100%;
    min-width: 0;
  }


  @media (max-width: 47.99rem) {
    [data-page-type="collection"] {
      padding-block-end: calc(var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
    }
  }

  @media (max-width: 47.99rem) {
    [data-page-type="collection"] input,
    [data-page-type="collection"] .btn,
    [data-page-type="collection"] button {
      scroll-margin-block-end: calc(var(--touch-target-min, 2.75rem));
    }
  }


  /* Physical end-spacer so last controls can scroll fully above fixed dock. */
  @media (max-width: 47.99rem) {
    [data-page-type="collection"] > [data-state-content]::after {
      content: "";
      display: block;
      flex-shrink: 0;
      width: 100%;
      height: calc(var(--dock-height, 4rem) + var(--dock-safe-area-offset, 0px) + var(--touch-target-min, 2.75rem));
      min-height: calc(var(--dock-height, 4rem) + var(--touch-target-min, 2.75rem));
      pointer-events: none;
    }
  }
/*
   * Collection form grids (licensing,
models,
…): stack half/third cards on
   * phone — page-type SSoT,
never per-page licensing/models forks.
   */
  /*
   * Phone stack for collection + form page types (licensing,
settings
   * companion forms). One rule set — never fork per page id.
   */
  @media (max-width: 42rem) {[data-page-type="collection"] .bao-grid-reactive-panels,
[data-page-type="form"] .bao-grid-reactive-panels,
[data-page-type="ops"] .bao-grid-reactive-panels,
[data-page-type="detail"] .bao-grid-reactive-panels,
[data-page-type="detail"] [data-dashboard-grid] {
      grid-template-columns: minmax(0, 1fr);
    }[data-page-type="collection"] .bao-grid-panel-half,
[data-page-type="collection"] .bao-grid-panel-third,
[data-page-type="collection"] .bao-grid-panel-quarter,
[data-page-type="collection"] .bao-grid-panel-narrow,
[data-page-type="collection"] .bao-grid-panel-wide,
[data-page-type="form"] .bao-grid-panel-half,
[data-page-type="form"] .bao-grid-panel-third,
[data-page-type="form"] .bao-grid-panel-quarter,
[data-page-type="form"] .bao-grid-panel-narrow,
[data-page-type="form"] .bao-grid-panel-wide,
[data-page-type="ops"] .bao-grid-panel-half,
[data-page-type="ops"] .bao-grid-panel-third,
[data-page-type="ops"] .bao-grid-panel-quarter,
[data-page-type="detail"] .bao-grid-panel-half,
[data-page-type="detail"] .bao-grid-panel-third,
[data-page-type="detail"] .bao-grid-panel-quarter,
[data-page-type="detail"] .bao-grid-panel-narrow,
[data-page-type="detail"] .bao-grid-panel-wide,
[data-page-type="detail"] .bao-grid-panel-full,
[data-page-type="detail"] [data-dashboard-grid] > [data-dashboard-widget] {
      grid-column: 1 / -1;
      max-width: none;
      min-width: 0;
    }[data-page-type="collection"] .card-body,
[data-page-type="collection"] form,
[data-page-type="form"] .card-body,
[data-page-type="form"] form,
[data-page-type="ops"] .card-body,
[data-page-type="ops"] form,
[data-page-type="detail"] .card-body,
[data-page-type="detail"] form {
      min-width: 0;
      width: 100%;
    }[data-page-type="collection"] .input,
[data-page-type="collection"] .select,
[data-page-type="collection"] .btn:not(.btn-square),
[data-page-type="form"] .input,
[data-page-type="form"] .select,
[data-page-type="form"] .btn:not(.btn-square),
[data-page-type="ops"] .input,
[data-page-type="ops"] .select,
[data-page-type="ops"] .btn:not(.btn-square),
[data-page-type="detail"] .input,
[data-page-type="detail"] .select,
[data-page-type="detail"] .btn:not(.btn-square) {
      min-height: var(--touch-target-min, 2.75rem);
    }
    /* Primary form submit full-width;secondary icon chrome stays square. */
    [data-page-type="collection"] form .btn.btn-primary,
[data-page-type="form"] form .btn.btn-primary,
[data-page-type="ops"] form .btn.btn-primary,
[data-page-type="detail"] form .btn.btn-primary {
      width: 100%;
      justify-content: center;
    }[data-page-type="collection"] form .btn.btn-square,
[data-page-type="form"] form .btn.btn-square,
[data-page-type="detail"] form .btn.btn-square {
      width: var(--touch-target-min, 2.75rem);
      max-width: var(--touch-target-min, 2.75rem);
    }/* Card bodies: no horizontal scroll from long mono paths / table shells.
     * Use :is() so we never emit bare bao-data-panel multi-selectors that
     * the solid-content-plane gate mistakes for glass co-tagging. */
    [data-page-type="collection"] :is(.card, .bao-data-panel):not(.bao-table-scroll-x),
[data-page-type="detail"] :is(.card, .bao-data-panel):not(.bao-table-scroll-x) {
      overflow-x: clip;
      max-width: 100%;
    }/* Form fieldsets: full-bleed rows on phone (settings + licensing). */
    [data-page-type="form"] .bao-settings-group.fieldset,
[data-page-type="collection"] .bao-settings-group.fieldset,
[data-page-type="form"] fieldset.bao-settings-group,
[data-page-type="collection"] fieldset.bao-settings-group {
      width: 100%;
      max-width: none;
    }
  }/* Dashboard widgets: equal card rhythm,
less empty whitespace. */
  [data-page-type="home"] [data-dashboard-grid] {
    align-items: stretch;
    gap: var(--space-inline, 0.5rem);
  }
  [data-page-type="home"] [data-dashboard-widget] {
    min-height: 6rem;
    height: 100%;
  }/* Phone 2-up: tighter gap,
still fluid tracks (no fixed column counts). */
  @media (max-width: 39.99rem) {
    [data-page-type="home"] [data-dashboard-grid] {
      gap: var(--space-micro, 0.25rem);
    }
    [data-page-type="home"] [data-dashboard-widget] {
      min-height: 5.25rem;
    }
  }/* Dashboard embeds one dense case list inside one widget surface. Rows are
     separators,
never a second card layer. */
  [data-page-type="home"] [data-case-list] tbody {
    gap: 0;
  }
  [data-page-type="home"] [data-case-list] tbody > tr {
    border: 0;
    border-block-end: 1px solid var(--border-base);
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    padding: var(--space-inline) 0;
  }
  [data-page-type="home"] [data-case-list] tbody > tr:last-child {
    border-block-end: 0;
  }

  /* Page header densify — phone-first title stack (happydumpling topbar rhythm). */
  [data-primitive="PageHeader"] {
    gap: var(--space-inline);
  }
  [data-primitive="PageHeader"] h1 {
    font-size: var(--text-xl, 1.25rem);
    line-height: 1.2;
  }
  [data-primitive="PageHeader"] p {
    font-size: var(--text-xs, 0.75rem);
    line-height: 1.35;
    color: var(--text-secondary);
  }
  /*
   * Page type answered the viewport and then stopped: the title held
   * --text-xl from 48rem to 4K and the lead paragraph held --text-xs at every
   * width above phone, so a 27" display rendered the page title at the same
   * 20px as a tablet and its supporting copy at 12px. These steps are keyed to
   * the CONTAINER, not the viewport, so a header inside a narrow inspector
   * pane keeps the compact size while the same primitive on a wide page grows
   * — the rest of this layout is already container-driven, and type was the
   * one axis still asking the window how big it was.
   *
   * Every step lands on the canonical tokens.bao ramp (xs -> sm, xl -> 2xl),
   * so computed-token-audit still grades each rendered size against a declared
   * stop instead of a free-floating fluid value.
   */
  @container (min-width: 48rem) {
    [data-primitive="PageHeader"] p {
      font-size: var(--text-sm, 0.875rem);
    }
  }
  @container (min-width: 64rem) {
    [data-primitive="PageHeader"] h1 {
      font-size: var(--text-2xl, 1.5rem);
    }
  }
  @media (max-width: 40rem) {
    [data-primitive="PageHeader"] [data-page-header-actions] {
      gap: var(--space-micro);
    }
    [data-primitive="PageHeader"] [data-page-header-actions] .btn {
      /* WCAG/HIG: never shrink header CTAs below 44px on phone. */
      min-height: var(--touch-target-min, 2.75rem);
      height: auto;
      min-width: var(--touch-target-min, 2.75rem);
      padding-inline: var(--space-inline);
      font-size: var(--text-sm);
    }
  }

  /*
   * Operational solid tiles — dense data surfaces (content plane).
   * Page-type SSoT: collection + home DashboardWidget / bao-data-panel.
   * Glass is chrome-only;:not(.bao-surface-glass-*) is a hard ban so a
   * mis-tagged glass class never inherits content-tile paint.
   */
  [data-page-type="collection"] .bao-data-panel:not(.bao-surface-glass-regular):not(.bao-surface-glass-clear),
[data-page-type="home"] [data-dashboard-widget]:not(.bao-surface-glass-regular):not(.bao-surface-glass-clear),
[data-page-type="home"] .card.bao-data-panel:not(.bao-surface-glass-regular):not(.bao-surface-glass-clear) {
    border: 1px solid color-mix(in oklab, var(--border-base) 88%, var(--text-primary));
    border-radius: var(--radius-panel);
    background: var(--bg-panel);
    box-shadow:
      0 1px 0 color-mix(in oklab, var(--bg-panel) 65%, transparent) inset,
      0 1px 2px color-mix(in oklab, var(--text-primary) 6%, transparent);
  }




  .bao-case-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-inline);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  /*
   * Closed <details> must not paint the list. Author `display:flex` on
   * .bao-case-list outranks the UA closed-content hide in Chromium, so the
   * list kept real boxes (and PE:auto cards) while the host stayed summary-
   * height only — case cards stacked over specimen select/add (capture@tablet
   * reachability). Explicit :not([open]) hide + [open] re-show is the fix.
   */
  details[data-case-picker-disclosure]:not([open]) > .bao-case-list,
  details[data-case-picker-disclosure]:not([open]) > :not(summary) {
    display: none;
  }
  details[data-case-picker-disclosure] > summary {
    /* The disclosure trigger must own its tappable area above the open list:
       the list once painted over the summary's bottom strip (z-raised) and
       stole its HIG 44pt edge — measured on /specimens. One step above the
       list's own token so the trigger always out-ranks it. */
    position: relative;
    z-index: calc(var(--z-raised, 2) + 1);
    pointer-events: auto;
  }
  details[data-case-picker-disclosure][open] > .bao-case-list {
    display: flex;
    flex-direction: column;
  }
  /*
   * Specimen create disclosure: author flex on the form host left the submit
   * ("Add specimen") with a real box while closed — visual gate stamped it
   * then timed out on click (/specimens dual-walk).
   */
  details[data-specimen-create-disclosure]:not([open]) > :not(summary),
  details[data-specimen-create-disclosure]:not([open]) ~ [data-specimen-create-form-panel] {
    display: none;
  }
  .bao-case-card {
    /* visible: auto+intrinsic collapsed layout while cards painted full size (reachability). */
    content-visibility: auto;
    contain-intrinsic-size: auto 5rem;
    position: relative;
    z-index: var(--z-local, 1);
    isolation: isolate;
    scroll-margin-block-end: calc(var(--dock-height, 4rem) + var(--touch-target-min, 2.75rem));
    display: flex;
    flex-direction: column;
    gap: var(--space-micro);
    margin: 0;
    padding: var(--space-section);
    /*
     * The border belongs to the template, not to this class. All three
     * `.bao-case-card` hosts — case-picker, case-option and
     * case-empty-option — compose their own `border border-base-300/70
     * border-s-4 …` utilities, and a shorthand here reset all four widths
     * from a stylesheet that loads after Tailwind's, in the same layer and at
     * the same specificity. Measured on /specimens: the picker's 4px
     * selection accent and the case list's 4px `border-s-primary/40` edge
     * both rendered at 1px, so a selected case was a hairline like every
     * other card. Only the radius stays, because no host states one.
     */
    border-radius: var(--radius-field, 0.75rem);
    background: var(--bg-panel);
    box-shadow: 0 1px 2px color-mix(in oklab, var(--text-primary) 5%, transparent);
    overflow: hidden;
    min-width: 0;
  }

  @media (max-width: 47.99rem) {
    .bao-case-list-host {
      padding: var(--space-micro);
    }
    .bao-case-list {
      gap: var(--space-micro);
    }
    .bao-case-card {
      padding-block: var(--space-micro);
      padding-inline: var(--space-inline);
      gap: var(--space-micro);
    }
  }
  .bao-case-card {
    transition:
      border-color var(--duration-fast, 120ms) var(--easing-standard),
      box-shadow var(--duration-fast, 120ms) var(--easing-standard),
      transform var(--duration-fast, 120ms) var(--easing-standard);
  }
  @media (hover: hover) and (pointer: fine) {
    .bao-case-card:hover {
      border-color: color-mix(in oklab, var(--color-primary) 40%, var(--border-base));
      box-shadow: 0 2px 8px color-mix(in oklab, var(--text-primary) 8%, transparent);
      transform: translateY(-1px);
    }
  }
  .bao-case-card:active {
    transform: scale(0.99);
  }
  .bao-case-card:focus-within {
    border-color: color-mix(in oklab, var(--color-primary) 40%, var(--border-base));
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-case-card {
      transition: none;
    }
    .bao-case-card:hover,
    .bao-case-card:active {
      transform: none;
    }
  }
  [data-case-option][data-selected="true"] .bao-case-card,
  [data-case-option][aria-selected="true"] .bao-case-card {
    border-color: color-mix(in oklab, var(--color-success) 50%, var(--border-base));
    border-inline-start-color: var(--color-success);
    background: color-mix(in oklab, var(--color-success) 10%, var(--bg-panel));
  }
  .bao-case-card-selected-icon {
    display: none;
  }
  [data-case-option][data-selected="true"] .bao-case-card-selected-icon,
  [data-case-option][aria-selected="true"] .bao-case-card-selected-icon {
    display: inline-block;
  }
  .bao-case-status.badge {
    border-radius: var(--radius-full);
    font-size: var(--text-2xs, 0.6875rem);
    font-weight: 600;
  }
  .bao-case-meta {
    color: var(--text-secondary);
    opacity: 1;
  }
  /* Case detail preview fact rows — one owner for the label/value pair chain. */
  .bao-case-fact-row {
    display: flex;
    min-width: 0;
    align-items: baseline;
    gap: var(--space-micro);
    font-size: var(--text-xs, 0.75rem);
  }
  /* Sentence case labels (Apple HIG) — uppercase burned vertical rhythm. */
  .bao-case-fact-label {
    flex-shrink: 0;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
    color: color-mix(in oklab, var(--color-base-content) 72%, var(--color-base-100));
  }
  .bao-case-status {
    display: inline-flex;
    align-items: center;
    padding: var(--space-micro) var(--space-inline);
    border-radius: var(--radius-full);
    border: 1px solid color-mix(in oklab, var(--color-info) 40%, var(--border-base));
    background: color-mix(in oklab, var(--color-info) 12%, transparent);
    color: var(--color-info-content);
    font-size: var(--text-xs, 0.75rem);
    font-weight: 500;
    white-space: nowrap;
  }
  /*
   * Capture strip cards only — never bare .bao-context-card.
   * Chat/shell chips share that class for borders but stay row flex;
   * forcing column here fought horizontal rail CSS and reflow-thrashed
   * (WebView 101 chip vibration) on every HTMX remount.
   */
  [data-capture-context],
  [data-capture-context-strip] > .bao-context-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-micro);
    min-height: 3.25rem;
    padding: var(--space-inline) var(--space-section);
    border: 1px solid color-mix(in oklab, var(--border-base) 88%, var(--text-primary));
    border-inline-start: 3px solid var(--color-primary);
    border-radius: var(--radius-field, 0.75rem);
    background: var(--bg-panel);
    text-align: start;
    box-shadow: 0 1px 2px color-mix(in oklab, var(--text-primary) 4%, transparent);
  }
  #case-list-section,
  [data-case-list] {
    display: block;
    min-height: 8rem;
    width: 100%;
    visibility: visible;
    opacity: 1;
  }

  /*
   * Active clinical case in the worklist — shell context match (Apple list
   * selection). Primary edge + soft fill; never a second chrome plate.
   */
  [data-case-row-primary][data-case-active="true"],
  tr[data-case-row-primary][data-case-active="true"] {
    border-inline-start: 3px solid var(--color-primary);
    background: color-mix(in oklab, var(--color-primary) 8%, var(--bg-panel));
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-primary) 22%, transparent);
  }
  @media (hover: hover) and (pointer: fine) {
    [data-case-row-primary][data-case-active="true"]:hover {
      background: color-mix(in oklab, var(--color-primary) 12%, var(--bg-panel));
    }
  }

  /* Fluid collection grid — auto-fit density */
  .bao-grid-reactive-collection {
    display: grid;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--grid-track-min, 18rem)), 1fr)
    );
    gap: var(--space-inline);
    align-items: stretch;
    width: 100%;
    min-width: 0;
  }

  /* Empty panels — compact dashed host, never sparse white voids */
  .bao-empty-panel {
    min-height: 4.5rem;
    padding: var(--space-section);
  }

  /* The shell owns the ambient field; content panels own their opaque fill. */

  /*
   * Collection densify: workbench owns chrome; avoid stacked section-group voids.
   */
  [data-page-type="collection"] [data-section-group] {
    gap: var(--space-inline);
  }

  /* Filter bar fields: controls align on one baseline (labels are sr-only). */


  /* New Case dialog steps: dense on phone; box height tracks visual viewport. */
  @media (max-width: 40rem) {
    [data-case-create-dialog] .modal-box,
    [data-case-create-dialog] .bao-modal-box-shell {
      max-height: min(92dvh, var(--vv-height, 100%));
    }
    [data-case-create-dialog] .bao-modal-body-scroll {
      max-height: min(
        var(--modal-body-max-height),
        calc(var(--vv-height, 100dvh) * 0.5)
      );
    }
  }

  /*
   * Dashboard recent-cases embed: widget owns card chrome. Strip nested
   * DataSection padding and flatten phone row "mini-cards" so we never get
   * card-in-card nesting (Hick/Apple density).
   */
  [data-dashboard-widget] [data-case-list] {
    padding: 0;
    gap: var(--space-micro);
  }
  @media (max-width: 47.99rem) {
    [data-dashboard-widget] [data-case-list] tbody tr {
      border-radius: 0;
      border-inline: 0;
      border-block-start: 0;
      border-block-end: 1px solid color-mix(in oklab, var(--color-base-300) 70%, transparent);
      background: transparent;
      box-shadow: none;
      padding-inline: 0;
      padding-block: var(--space-micro);
    }
  }

/* Specimens is one operational list plane; rows use separators rather than nested plates. */
[data-page="specimens"] [data-primitive="DataTable"] tr.bao-table-responsive-row[data-specimen-id] {
  border-radius: 0;
  border-inline-width: 0;
  border-block-start-width: 0;
  border-block-end-width: var(--border);
  border-style: solid;
  border-color: var(--border-base);
  background-color: transparent;
  box-shadow: none;
  padding-inline: 0;
}

[data-page="specimens"] [data-primitive="DataTable"] .bao-table-responsive-body > * + * {
  margin-block-start: 0;
}

[data-page="specimens"] [data-primitive="DataTable"] [data-specimen-actions] > [data-dropdown-trigger] {
  inline-size: var(--touch-target-min);
  min-inline-size: var(--touch-target-min);
  min-block-size: var(--touch-target-min);
  justify-content: center;
}

  /* Tablet master-detail composition. The page is the query container so the workbench can react to its usable content width. */
  [data-page="cases"] {
    container-type: inline-size;
    container-name: cases-page;
  }
  [data-case-detail-pane] {
    display: none;
  }
  @media (min-width: 48rem) and (max-width: 63.99rem) {
    @container cases-page (min-width: 40rem) {
      [data-cases-workbench]:has(> [data-case-detail-pane]:not([data-state="empty"])) {
        display: grid;
        grid-template-columns: minmax(18rem, 1fr) minmax(0, min(24rem, 40%));
        gap: var(--space-inline);
        align-items: start;
      }
      [data-cases-workbench]:has(> [data-case-detail-pane]:not([data-state="empty"])) > [data-case-filters] {
        grid-column: 1 / -1;
      }
    }
    [data-case-detail-pane]:not([data-state="empty"]) {
      display: flex;
      flex-direction: column;
      gap: var(--space-inline);
      min-width: 0;
      padding: var(--space-inline);
      border-block-start: 1px solid color-mix(in oklab, var(--border-base) 80%, transparent);
      outline: none;
    }
    @container cases-page (min-width: 40rem) {
      [data-case-detail-pane]:not([data-state="empty"]) {
        border-block-start: none;
        border-inline-start: 1px solid color-mix(in oklab, var(--border-base) 80%, transparent);
      }
    }
    [data-case-detail-pane]:focus-visible {
      outline: var(--focus-ring-width, 2px) solid var(--color-primary);
      outline-offset: var(--focus-ring-offset-tight, -2px);
    }
  }
  .bao-case-detail-pane-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-inline);
  }
  .bao-case-detail-pane-title {
    margin: 0;
    font-size: var(--text-sm, 0.875rem);
    font-weight: 600;
    color: var(--text-primary);
  }
  .bao-case-detail-pane-description {
    margin: 0;
    font-size: var(--text-xs, 0.75rem);
    color: var(--text-secondary);
  }
  .bao-case-detail-pane-body {
    min-height: 8rem;
    min-width: 0;
  }
  .bao-case-detail-pane-error {
    padding: var(--space-inline);
    border-radius: var(--radius-field, 0.75rem);
    border: 1px solid color-mix(in oklab, var(--color-error) 40%, var(--border-base));
    color: var(--color-error);
    font-size: var(--text-xs, 0.75rem);
  }
  .bao-case-detail-pane-error-copy {
    margin: 0;
  }
  .bao-case-detail-facts {
    display: flex;
    flex-direction: column;
    gap: var(--space-micro);
    margin: 0;
  }
  .bao-case-detail-facts dd {
    margin: 0;
    min-width: 0;
  }
  .bao-case-detail-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-micro);
    margin-block-start: var(--space-inline);
    margin-inline: 0;
    padding: 0;
    border: 0;
    min-inline-size: 0;
  }
/*
   * Global page density SSOT — every pageType inherits Apple-tight chrome:
   * form cards,
tables,
and section stacks share the same spacing scale.
   * No per-page bespoke overrides.
   */
  [data-page] .bao-page-track {
    gap: var(--space-section);
  }[data-page-type="collection"] .bao-page-track,
[data-page-type="form"] .bao-page-track,
[data-page-type="detail"] .bao-page-track {
    gap: var(--space-inline);
  }
  [data-page] [data-primitive="DataSection"] {
    min-width: 0;
  }
  [data-page] .bao-data-panel {
    min-width: 0;
    max-width: 100%;
  }/* Overflow containment: no panel may paint outside the page track */
  [data-page] .bao-page-track,
[data-page] [data-state-content] {
    min-width: 0;
    max-width: 100%;
  }
  /* Secondary soft chrome: ban invisible zero-height ghosts from layout thrash */
  [data-page] .btn.btn-soft.btn-square {
    flex-shrink: 0;
  }/* Phone: bound supporting copy and retain command touch targets. */
  @media (max-width: 40rem) {
    [data-page] [data-primitive="PageHeader"] p {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
    }
    [data-page] [data-page-header-actions] > .btn {
      flex: 1 1 auto;
      min-height: var(--touch-target-min, 2.75rem);
    }
  }


  .bao-workbench {
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: 0;
    overflow-x: clip;
  }
  /* Conversation dock SSOT: tokens-layout-page-shells-conversation.ts owns
     all .bao-conversation-dock rules (solid form surface per design.pdf §5.5).
     Skeleton module must not duplicate — DRY single owner. */
  @media (prefers-reduced-motion: reduce) {
.bao-session-mode-tab,
.bao-motion-transition-colors {
      transition-property: none; transition-duration: var(--duration-instant, 0ms);
      animation: none;
    }
  }
  /* Conversation message stream — scroll region host. */
  .bao-conversation-stream {
    scrollbar-gutter: stable;
    scroll-padding-block: var(--space-section);
  }
  /* Conversation page header band. */
  .bao-conversation-header {
    min-height: var(--chrome-min-height, 2.25rem);
  }
  .bao-skeleton-header {
    min-height: var(--density-comfortable-row-height);
    border-radius: var(--radius-field);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-toolbar {
    min-height: var(--density-default-row-height);
    border-radius: var(--radius-field);
    background-color: var(--bg-surface-hover);
  }
  /* Floating glass toolbar — geometry-faithful surface for capture/editor top chrome. */
  .bao-skeleton-toolbar-floating {
    min-height: var(--density-comfortable-row-height);
    width: 100%;
    border-radius: var(--radius-field);
    border: 1px solid color-mix(in oklab, var(--liquid-border-clear) 50%, transparent);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-inspector {
    min-height: var(--skeleton-media-min-height);
    border: 1px solid var(--border-base);
    border-radius: var(--radius-box);
    background-color: var(--bg-surface);
  }
  /* Workbench side panel — captures card card-border geometry + card-body padding. */
  .bao-skeleton-side-panel {
    min-height: var(--skeleton-panel-min-height, 7rem);
    width: 100%;
    border-radius: var(--radius-box);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  .bao-skeleton-side-panel-card {
    min-height: var(--skeleton-panel-min-height, 7rem);
    width: 100%;
    border-radius: var(--radius-box);
    border: 1px solid var(--border-base);
    background-color: var(--bg-panel);
    box-shadow: 0 1px 2px color-mix(in oklab, var(--color-base-content) 6%, transparent);
  }
  /* Workbench preview region — matches camera preview box geometry. */
  .bao-skeleton-preview-region {
    aspect-ratio: 16 / 9;
    min-height: var(--skeleton-media-min-height, 12rem);
    width: 100%;
    max-width: 100%;
    border-radius: var(--radius-box);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  /* Workbench preview empty placeholder — matches centered icon+copy stack. */
  .bao-skeleton-preview-empty {
    min-height: var(--skeleton-media-min-height, 12rem);
    width: 100%;
    border-radius: var(--radius-box);
    border: 1px dashed color-mix(in oklab, var(--color-base-300) 70%, transparent);
    background-color: var(--bg-surface);
  }
  /* Workbench canvas region — matches annotation editor canvas column. */
  .bao-skeleton-canvas-region {
    min-height: var(--canvas-region-min-height, clamp(18rem, 42vh, 30rem));
    width: 100%;
    border-radius: var(--radius-box);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  /* Panel card header — matches card-title band above card-body. */
  .bao-skeleton-panel-header {
    min-height: var(--density-comfortable-row-height);
    width: 100%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-message {
    width: min(75%, var(--page-standard));
  }
  .bao-skeleton-message,
  .bao-skeleton-message-reply {
    min-height: var(--density-comfortable-row-height);
    border-radius: var(--radius-box);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-message-reply {
    margin-inline-start: auto;
    width: min(60%, var(--page-standard));
  }
  /* Assistant chat bubble — mirrors chat-bubble border + radius at chat-start row. */
  .bao-skeleton-message-assistant {
    min-height: var(--density-comfortable-row-height);
    width: min(75%, var(--page-conversation, 48rem));
    border-radius: var(--radius-box);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  /* User chat bubble — mirrors chat-bubble-primary at chat-end row. */
  .bao-skeleton-message-user {
    min-height: var(--density-comfortable-row-height);
    width: min(60%, var(--page-conversation, 48rem));
    border-radius: var(--radius-box);
    background-color: var(--bg-surface-hover);
  }
  /* Conversation context strip — sticky chip band above thread. */
  .bao-skeleton-conversation-context-strip {
    min-height: 3rem;
    width: 100%;
    max-width: var(--page-wide, 96rem);
    margin-inline: auto;
    border-radius: var(--radius-field);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface-hover);
  }
  /* Glass-docked composer — same footprint as final composer footer. */
  .bao-skeleton-conversation-composer-docked {
    min-height: 8rem;
    width: 100%;
    max-width: var(--page-wide, 96rem);
    margin-inline: auto;
    border-radius: var(--radius-box);
    border: 1px solid color-mix(in oklab, var(--color-base-300) 50%, transparent);
    background-color: var(--bg-surface-hover);
  }

.bao-ai-analysis-workbench {
  container-type: inline-size;
  container-name: ai-analysis-workbench;
}

.bao-ai-analysis-primary,
.bao-ai-analysis-inspector {
  min-inline-size: 0;
  max-inline-size: 100%;
}

.bao-ai-analysis-inspector {
  display: grid;
  grid-template-areas: "inputs" "status";
}

.bao-ai-analysis-inspector > form {
  grid-area: inputs;
  max-inline-size: none;
}

.bao-ai-analysis-inspector > [role="status"] {
  grid-area: status;
}

@container ai-analysis-workbench (max-width: 32rem) {
  .bao-ai-analysis-primary {
    border-block-start: var(--border) solid var(--color-base-300);
    padding-block-start: var(--space-section);
  }
  .bao-ai-analysis-workbench [data-viz-dimension-switcher].join {
    max-inline-size: 100%;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
  }
}

.bao-inline-target {
  display: inline-flex;
  min-width: 0;
  align-items: center;
}

.bao-size-attachment-preview {
  inline-size: var(--attachment-preview-size);
  block-size: var(--attachment-preview-size);
  flex: 0 0 var(--attachment-preview-size);
}

@media (max-width: 47.99rem), (pointer: coarse) {
  .bao-inline-target {
    min-block-size: var(--touch-target-min, 2.75rem);
  }
}

  .bao-media-workbench-primary {
    display: flex;
    min-width: 0;
    min-height: 0;
    flex: 999 1 var(--media-primary-basis, 24rem);
    flex-direction: column;
    gap: var(--space-inline);
  }
  .bao-media-workbench-secondary {
    display: grid;
    grid-auto-rows: max-content;
    align-content: start;
    align-self: start;
    min-width: 0;
    min-height: 0;
    max-height: 100%;
    /* Cap inspector rail so it never starves the media stage on ultrawide. */
    flex: 0 1 var(--media-inspector-basis, 20rem);
    max-width: min(24rem, 100%);
    gap: var(--space-inline);
    overflow-y: visible;
  }
  .bao-preview-context-panel {
    min-width: 0;
    width: 100%;
  }

  .bao-form-adaptive {
    display: grid;
    min-width: 0;
    gap: var(--space-section);
  }
  .bao-form-grid-two {
    display: grid;
    min-width: 0;
    gap: var(--space-section);
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--grid-form-min, 16rem)), 1fr)
    );
  }
  .bao-choice-grid {
    display: grid;
    min-width: 0;
    gap: var(--space-inline);
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--grid-choice-min, 14rem)), 1fr)
    );
  }
  /*
   * Collection choice densify: compact padding + single column on phone.
   * Security/permission radiogroups (API keys scopes) KEEP helper copy —
   * title-only chips hide least-privilege meaning (enterprise anti-pattern).
   * Non-security choice grids may densify titles only via data-choice-density.
   */
  @media (max-width: 47.99rem) {
    .bao-choice-grid {
      gap: var(--space-micro);
      grid-template-columns: minmax(0, 1fr);
    }.bao-choice-grid > .label,
.bao-choice-grid > .bao-choice-option {
      padding: var(--space-inline, 0.5rem);
      min-height: var(--touch-target-min, 2.75rem);
      /* HIG Dynamic Type: the inner layout span is display:grid with
         auto-sized tracks, so at root font 400% with WCAG text spacing the
         checkbox column, title, and helper caption each demand their
         natural width and the option outgrows the viewport (api-keys scope
         options measured right edge 1445px on a 1440px viewport). A fluid
         track and wrap-capable captions keep the text inside the option
         instead of pushing past the edge. */
    }
    .bao-choice-grid > .label > span.grid,
    .bao-choice-grid > .bao-choice-option > span.grid {
      grid-template-columns: minmax(0, 1fr);
      min-width: 0;
      max-width: 100%;
    }
    .bao-choice-grid > .label > span.grid > span,
    .bao-choice-grid > .bao-choice-option > span.grid > span {
      min-width: 0;
      overflow-wrap: anywhere;
    }/* Optional dense chips: opt-in only (never default for permission scopes). */
    .bao-choice-grid[data-choice-density="title-only"] > .label > span.grid > span:last-child,
.bao-choice-grid[data-choice-density="title-only"] > .label > span > span.text-xs,
.bao-choice-grid[data-choice-density="title-only"] > .bao-choice-option > span.grid > span:last-child,
.bao-choice-grid[data-choice-density="title-only"] > .bao-choice-option > span > span.text-xs {
      display: none;
    }  }/* Collection dual panels (licensing,
ops): full-width stack under tablet. */
  .bao-context-grid-reactive {
    display: grid;
    min-width: 0;
    align-items: stretch;
    gap: var(--space-micro);
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--grid-context-min, 10rem)), 1fr)
    );
  }
  .bao-page-chrome[data-chat-context] {
    display: flex;
    flex-wrap: wrap;
    min-width: 0;
    gap: var(--space-micro);
  }
  .bao-tabs-scroll {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    width: 100%;
    min-width: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--space-inline);
    /*
     * No mask here. The clipped-content affordance has ONE owner —
     * .bao-scroll-edge-x (tokens-utilities-scroll-edge), which every compiled
     * .bao-tabs-scroll host also carries. That owner is self-hiding: its cover
     * layers are background-attachment:local, so each cue disappears once its
     * edge is fully revealed and nothing is drawn when there is no overflow.
     *
     * The mask that used to live here was unconditional, so a strip with
     * nothing to scroll still faded its trailing 1.25rem — measured on
     * /capture at 375px: scrollWidth === clientWidth === 357 and the tab strip
     * was still dimmed at the right edge. Two affordances on one element, one
     * of them lying about scrollability.
     */
  }
  /* Scroll rails preserve complete destination labels instead of compressing them. */
  .bao-tabs-scroll > .tab {
    flex: 0 0 auto;
    min-width: max-content;
    max-width: none;
    scroll-snap-align: start;
  }
  .bao-dock-reactive { display: flex; flex-wrap: wrap; }
  .bao-dock-reactive-primary { flex: 999 1 var(--dock-primary-basis, 32rem); min-width: 0; }
  /*
   * Cap by the smaller of the rail width and the container. A bare 24rem
   * cap (max-w-sm) is a rem length, so it grew with the root font: at 125%
   * Dynamic Type the Capture device controls measured 406px inside a 390px
   * phone viewport with no scroller to reveal them (Apple HIG Dynamic Type;
   * WCAG 2.2 1.4.4). min(..., 100%) is the pattern .bao-media-workbench-secondary
   * already uses for the same reason.
   */
  .bao-dock-reactive-side { flex: 1 1 var(--dock-side-basis, 15rem); min-width: 0; max-width: min(24rem, 100%); }
  .bao-dock-reactive-side-wide { flex: 1 1 var(--dock-side-wide-basis, 18rem); min-width: 0; }
/* Mobile dock CSS emits UNLAYERED from tokens.ts (tokens-layout-dock) —
   never nest it inside @layer utilities or WebView 101 drops the hide. */
  /* Dynamic Type reflow contracts (HIG / WCAG 1.4.4) — the clusters the
     audit measured past the viewport at 400% root font with WCAG text
     spacing, after the touch-target floor became font-invariant:
     - daisyUI .btn is white-space:nowrap, so its min-content is the whole
       label and overflow-wrap stays inert; release the label at the same
       guard weight the nowrap re-asserters live at (0,4,0) — the doubled
       class carries (0,5,0) and loses to nothing in later files.
     - Tabs carry the same nowrap floor; release them too.
     - A fieldset defaults to min-inline-size:min-content; floor it, or an
       enlarged text pushes the whole cluster past its track.
     - Auto-width ancestors defeat a child's percentage max-width
       (percentage against an indefinite resolved width is inert), so the
       sticky identity hosts and cluster forms declare their own bound for
       the pills and rows inside to clip or wrap against.
     - The sticky badge pills wrap instead of forcing the sticky host wider
       than the viewport. Wrap is preferred over a scroller here — a
       scroller on the sticky host re-clips dropdown panels the editor
       rules explicitly unclip for open menus. */
  .btn.btn:not([hidden]):not(.hidden):not(.bao-fab),
  .tab:not([hidden]):not(.hidden) {
    white-space: normal;
  }
  /* Native control groups share the shrink floor, including join and grid layouts. */
  fieldset {
    min-inline-size: 0;
  }
  [data-editor-chrome] dl.flex.min-w-0,
  dl.m-0.flex,
  form.mt-space-inline.border-t {
    width: 100%;
    max-width: 100%;
  }
  [data-editor-chrome] dl.flex.min-w-0 > div.bao-context-badge,
  dl.bao-ai-provenance-sticky > div.flex.min-w-0 {
    min-width: 0;
    max-width: 100%;
    flex-wrap: wrap;
  }
  [data-editor-chrome] dl.flex.min-w-0 > div.bao-context-badge > * {
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
  }
  dl.bao-ai-provenance-sticky dd,
  dl.bao-ai-provenance-sticky dt {
    min-width: 0;
    max-width: 100%;
  }
  dl.bao-ai-provenance-sticky dd > span {
    min-width: 0;
    max-width: 100%;
  }
  fieldset.flex.flex-col > label.flex > div.flex.w-full {
    flex-wrap: wrap;
    min-width: 0;
  }
  /* Toolbar label spans keep their own nowrap (the reveal rules declare it
     on the span, not the button), so releasing the button does not release
     the label — measured /models "Voice command" at right 449px instead of
     the button's right 293px. The base reveal rule is (0,3,0)
     (.bao-toolbar-action:not(.btn-square) .bao-toolbar-label), so the
     release rides #app-main for the ID weight and loses to nothing
     non-ID in the sheet. */
  #app-main .btn .bao-toolbar-label,
  body [data-voice-command] .bao-toolbar-label {
    min-width: 0;
    max-width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* Sticky badge term/value pills: dt/dd carry an inherited or declared
     nowrap as a chrome convention; at an enlarged root font the pill runs
     past the viewport (measured /editor Custody dt at right 450px on a
     390px viewport). Break the pill terms while keeping the badge bounds. */
  #app-main dl.flex.min-w-0 > div.bao-context-badge > dt,
  #app-main dl.flex.min-w-0 > div.bao-context-badge > dd {
    white-space: normal;
    overflow-wrap: anywhere;
  }

  /*
   * Shared reactive panel cells: equal-height rows (align-items: stretch on
   * .bao-grid-reactive-panels) + full-width track fill. design.pdf / AGENTS:
   * grid macro, consistent density — never ragged card heights in a row.
   * Never allow a panel to paint outside its track (clip overflow).
   */
  .bao-grid-reactive-panels {
    overflow-x: clip;
    max-width: 100%;
    /* 12-track macro (design.pdf / AGENTS): fluid spans via grid-column. */
    grid-template-columns: repeat(12, minmax(0, 1fr));
  }
  .bao-grid-panel-quarter,
  .bao-grid-panel-third,
  .bao-grid-panel-half,
  .bao-grid-panel-narrow,
  .bao-grid-panel-wide,
  .bao-grid-panel-full {
    min-width: 0;
    max-width: 100%;
    height: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
  }
  .bao-grid-panel-full {
    min-height: var(--bao-dashboard-panel-full-min-height, 8rem);
    grid-column: 1 / -1;
  }
  /* Phone: every panel full-bleed (touch-first; no orphan half beside void). */
  .bao-grid-panel-quarter,
  .bao-grid-panel-third,
  .bao-grid-panel-half,
  .bao-grid-panel-narrow,
  .bao-grid-panel-wide {
    min-height: var(--bao-dashboard-panel-min-height, 11rem);
    grid-column: 1 / -1;
  }
  .bao-grid-panel-quarter { --bao-panel-basis: var(--grid-panel-half-min); }
  .bao-grid-panel-third,
  .bao-grid-panel-narrow { --bao-panel-basis: var(--grid-panel-half-min); }
  .bao-grid-panel-half,
  .bao-grid-panel-wide { --bao-panel-basis: var(--grid-panel-half-min); }

  /*
   * design.pdf content-first: max TWO columns on multi-panel workbenches.
   * Every non-full span maps to half (6/12). Third/narrow/quarter never pack
   * into 3-up/4-up rows (span 3/4 left empty track slots and nested-card thrash).
   * Wide = primary half; narrow = supporting half; full = 1 / -1 stack.
   */
  @container (min-width: 40rem) {
    .bao-grid-panel-half,
    .bao-grid-panel-wide,
    .bao-grid-panel-third,
    .bao-grid-panel-narrow,
    .bao-grid-panel-quarter {
      grid-column: span 6;
    }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-sm="3"] { grid-column: span 3; }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-sm="4"] { grid-column: span 4; }
    [data-dashboard-widget][data-dashboard-span-sm="6"] { grid-column: span 6; }
    [data-dashboard-widget][data-dashboard-span-sm="12"] { grid-column: 1 / -1; }
  }
  /*
   * design.pdf §8.4/§8.6 adaptive feed: compact stat widgets are leaf cards
   * (value + label + one action — no nested tables), so their DEFAULT span stays half (6/12) — a default 3-up
   * row with a partial last row leaves an empty column gap (measured on the
   * dashboard: ten compact widgets plus one full-width table produce a ragged
   * 8/12 row at 64rem and up). Feeds with complete row sums declare
   * data-dashboard-span-* values explicitly to pack 3-up/4-up.
   * Rich widgets (data-widget-density="rich": embedded tables/lists) keep
   * the max-2-col rule above — nested content never packs 3-up.
   */
  @container (min-width: 48rem) {
    .bao-grid-reactive-panels > [data-dashboard-widget][data-widget-density="compact"]:not([data-dashboard-span-md]),
    .bao-grid-reactive-panels > [data-primitive="DashboardWidget"][data-widget-density="compact"]:not([data-dashboard-span-md]),
    .bao-grid-reactive-panels > * > [data-dashboard-widget][data-widget-density="compact"]:not([data-dashboard-span-md]),
    .bao-grid-reactive-panels > * > [data-primitive="DashboardWidget"][data-widget-density="compact"]:not([data-dashboard-span-md]) {
      grid-column: span 6;
    }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-md="3"] { grid-column: span 3; }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-md="4"] { grid-column: span 4; }
    [data-dashboard-widget][data-dashboard-span-md="6"] { grid-column: span 6; }
    [data-dashboard-widget][data-dashboard-span-md="12"] { grid-column: 1 / -1; }
  }
  @container (min-width: 64rem) {
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-lg="3"] { grid-column: span 3; }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-lg="4"] { grid-column: span 4; }
    [data-dashboard-widget][data-dashboard-span-lg="6"] { grid-column: span 6; }
    [data-dashboard-widget][data-dashboard-span-lg="12"] { grid-column: 1 / -1; }
  }
  @container (min-width: 80rem) {
    .bao-grid-reactive-panels > [data-dashboard-widget][data-widget-density="compact"]:not([data-dashboard-span-xl]),
    .bao-grid-reactive-panels > [data-primitive="DashboardWidget"][data-widget-density="compact"]:not([data-dashboard-span-xl]),
    .bao-grid-reactive-panels > * > [data-dashboard-widget][data-widget-density="compact"]:not([data-dashboard-span-xl]),
    .bao-grid-reactive-panels > * > [data-primitive="DashboardWidget"][data-widget-density="compact"]:not([data-dashboard-span-xl]) {
      grid-column: span 6;
    }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-xl="3"] { grid-column: span 3; }
    [data-dashboard-widget][data-widget-density="compact"][data-dashboard-span-xl="4"] { grid-column: span 4; }
    [data-dashboard-widget][data-dashboard-span-xl="6"] { grid-column: span 6; }
    [data-dashboard-widget][data-dashboard-span-xl="12"] { grid-column: 1 / -1; }
  }
  /*
   * Nested widget shells (section-group wraps cells): fill equal-height tracks.
   * Direct children and one-level nested cards share the same stretch SSOT.
   */
  .bao-grid-reactive-panels > [data-dashboard-widget],
  .bao-grid-reactive-panels > [data-primitive="DashboardWidget"],
  .bao-grid-reactive-panels > .card,
  .bao-grid-reactive-panels > * > [data-dashboard-widget],
  .bao-grid-reactive-panels > * > [data-primitive="DashboardWidget"],
  .bao-grid-reactive-panels > * > .card {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    width: 100%;
    height: var(--bao-panel-height, 100%);
    min-height: var(--bao-panel-min-height, var(--bao-dashboard-panel-min-height, 11rem));
    min-width: 0;
  }
  /* design.pdf content-first: compact strips (journey) must not stretch to the
   * tallest row or claim the 11rem panel floor — primary tables own height. */
  .bao-grid-reactive-panels > [data-dashboard-widget][data-widget-density="compact"],
  .bao-grid-reactive-panels > [data-primitive="DashboardWidget"][data-widget-density="compact"],
  .bao-grid-reactive-panels > * > [data-dashboard-widget][data-widget-density="compact"],
  .bao-grid-reactive-panels > * > [data-primitive="DashboardWidget"][data-widget-density="compact"] {
    height: auto;
    min-height: 0;
    max-height: none;
    align-self: start;
    flex: 0 0 auto;
  }
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body,
  .bao-grid-reactive-panels > [data-primitive="DashboardWidget"] > .card-body,
  .bao-grid-reactive-panels > .card > .card-body,
  .bao-grid-reactive-panels > * > [data-dashboard-widget] > .card-body,
  .bao-grid-reactive-panels > * > [data-primitive="DashboardWidget"] > .card-body,
  .bao-grid-reactive-panels > * > .card > .card-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    /* Surface content: clip width only; vertical overflow belongs to the page
       or to a table-bearing widget below (HIG scroll-views: no nested
       same-axis scroll views — measured on /reports: every widget card-body
       was its own vertical scroller, a 10-18px scroll trap over the page). */
    overflow-x: clip;
    overflow-y: visible;
  }
  /*
   * The one justified exception: a card-body hosting a scrolling data table
   * keeps its own vertical scrollport so rows are never buried behind the
   * widget footer. Everything else shows overflow; the page scrolls.
   */
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body:has(> [data-widget-value] [data-case-list]),
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body:has(> [data-widget-value] [data-primitive="DataTable"]),
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body:has(> [data-widget-value] [data-saved-reports]),
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body:has(> [data-widget-value] [data-report-templates]),
  .bao-grid-reactive-panels > [data-dashboard-widget] > .card-body:has(> [data-widget-value] [data-column-list]) {
    overflow-y: auto;
  }
  .bao-grid-reactive-panels [data-widget-value],
  .bao-grid-reactive-panels .bao-dashboard-panel-value {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    overflow-x: clip;
    /*
     * HIG scroll-views: no nested same-axis scroll views. A widget value
     * inside a card on a page that scrolls vertically must NOT create its own
     * vertical scroller — measured on /reports: widget-reports-ai-generate-body
     * 1042x136 with scrollHeight 154, an 18px scroll trap over the page.
     * Vertical overflow belongs to the page (or the card-body boundary that
     * hosts a real table); the value host shows it, never scrolls it.
     */
    overflow-y: visible;
  }
  /* Path / mono / secondary copy never blows the card width (design.pdf type). */
  .bao-grid-reactive-panels .bao-dashboard-widget-secondary,
  .bao-grid-reactive-panels .bao-dashboard-widget-meta,
  .bao-grid-reactive-panels [data-widget-secondary],
  .bao-grid-reactive-panels [data-widget-meta] {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Content headings wrap fully, including a single word wider than an enlarged card. */
  .bao-grid-reactive-panels .card-title {
    min-inline-size: 0;
    max-inline-size: 100%;
    flex-wrap: wrap;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .bao-grid-reactive-panels .bao-dashboard-widget-label,
  .bao-grid-reactive-panels [data-widget-title] {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Dense list rows inside dashboard panels (template library, saved reports). */
  .bao-list-row-dense {
    min-height: var(--bao-list-row-min-height, 3.25rem);
  }
  [data-report-templates] .list-row,
  [data-saved-reports] .list-row {
    min-height: var(--bao-list-row-min-height, 3.25rem);
    align-items: center;
  }

  /* Subgrid: children that need cross-sibling column alignment (status rows,
     export action rows, label/value meta grids) opt in via .bao-subgrid.
     The parent grid's column track is inherited; @supports guards older engines. */
  @supports (grid-template-columns: subgrid) {
    .bao-subgrid {
      grid-column: 1 / -1;
      display: grid;
      grid-template-columns: subgrid;
      align-items: center;
      gap: var(--space-inline);
    }
    .bao-subgrid-rows {
      grid-template-rows: subgrid;
    }
  }

  /*
   * Editor key-hint chips (`.bao-toolbar-action .kbd` + `[data-hig-key-hint]`)
   * belong to the toolbar-reveal contract in tokens-glass-chrome-body-part-1
   * + part-2, not to a fixed 2xl always-on regime. The historical gate here
   * targeted the daisyUI `.kbd` class and never matched the chip our
   * editor-toolbar renderer emits, which is exactly why the chip was visible
   * at every width. The reveal/hide contract now lives with the label.
   */

  /* Download cards contain file paths and action controls; direct-touch flow is one card per row. */
  @media (max-width: 42rem), (hover: none) and (pointer: coarse) {
    [data-predefined-models-section] [data-model-download-card] {
      grid-column: 1 / -1;
    }
  }

  /* Choice grid base SSOT: tokens-layout-framework.ts. Checked state only here. */
  .bao-choice-grid > .bao-choice-option:has(> input:checked) {
    border-color: color-mix(in oklab, var(--color-primary) 50%, var(--color-base-300));
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--color-primary) 20%, transparent);
  }




  /* Nested tables inside dashboard cards: no second framed host (SSOT single card). */
  [data-dashboard-widget] [data-case-list] > .bao-data-panel,
  [data-dashboard-widget] [data-primitive="DataTable"].bao-data-panel,
  [data-dashboard-widget] .overflow-x-auto.bao-data-panel {
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }
  /* Nested framed cards under any widget host — unframe (never dual chrome). */
  [data-dashboard-widget] .card.card-border.bao-data-panel,
  [data-dashboard-widget] [data-primitive="Card"].bao-data-panel {
    border-width: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }
  /* Content KPI shells are solid (design.pdf content plane). Neutralize any
   * accidental glass filter so glass-on-content cannot reappear. */
  [data-page="dashboard"] [data-dashboard-widget],
  [data-page-type="home"] [data-dashboard-widget],
  [data-page-type="collection"] [data-dashboard-widget] {
    background-color: var(--bg-panel, var(--color-base-100));
    transition-property: border-color, box-shadow;
    transition-timing-function: var(--easing-standard);
    transition-duration: var(--duration-fast, 120ms);
  }
  /*
   * Pointer hover: elevate border only (never glass/frost on content KPIs).
   * Coarse/touch relies on focus-visible + press from shared motion tokens.
   */
  @media (hover: hover) and (pointer: fine) {
    [data-page="dashboard"] [data-dashboard-widget]:hover,
    [data-page-type="home"] [data-dashboard-widget]:hover,
    [data-page-type="collection"] [data-dashboard-widget]:hover {
      border-color: color-mix(
        in oklab,
        var(--color-primary) 28%,
        var(--color-base-300, var(--border-base))
      );
      box-shadow: 0 1px 0 color-mix(in oklab, var(--color-base-content) 6%, transparent) inset,
        0 4px 14px color-mix(in oklab, var(--color-base-content) 7%, transparent);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    [data-page="dashboard"] [data-dashboard-widget],
    [data-page-type="home"] [data-dashboard-widget],
    [data-page-type="collection"] [data-dashboard-widget] {
      transition-duration: var(--duration-instant);
    }
  }
  /* Dashboard panel body slot — dense KPI geometry. */
  .bao-dashboard-panel-value {
    margin-block-start: var(--space-micro);
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
  }

  /* Dashboard widget value fragment — two-track grid (copy | meta). */
  .bao-dashboard-widget-fragment {
    display: grid;
    min-width: 0;
    align-items: start;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-micro) var(--space-inline);
    text-align: start;
  }
  /*
   * Phone 2-up KPI tiles are ~11rem wide — side meta crushes status pills
   * ("Connect a capture device…"). Stack copy above meta so status wraps
   * full width (Apple Health summary density). Parent overflow must not clip
   * the wrap — card-body/panel-value clip was still ellipsing at 157px.
   */
  @media (max-width: 39.99rem) {
    [data-page="dashboard"] .bao-dashboard-widget-fragment,
    [data-page-type="home"] .bao-dashboard-widget-fragment {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-micro, 0.25rem);
    }
    [data-page="dashboard"] .bao-dashboard-widget-meta,
    [data-page-type="home"] .bao-dashboard-widget-meta {
      justify-self: start;
      max-width: 100%;
      width: 100%;
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      justify-content: flex-start;
    }
    [data-page="dashboard"] .bao-dashboard-widget-status,
    [data-page-type="home"] .bao-dashboard-widget-status,
    [data-page="dashboard"] .bao-dashboard-widget-secondary,
    [data-page-type="home"] .bao-dashboard-widget-secondary {
      white-space: normal;
      overflow: visible;
      text-overflow: unset;
      max-width: 100%;
      overflow-wrap: anywhere;
    }
    [data-page="dashboard"] .bao-dashboard-panel-value,
    [data-page-type="home"] .bao-dashboard-panel-value,
    [data-page="dashboard"] [data-dashboard-widget] .card-body,
    [data-page-type="home"] [data-dashboard-widget] .card-body {
      overflow: visible;
    }
  }
  .bao-dashboard-widget-copy {
    min-width: 0;
  }
  .bao-dashboard-widget-value {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-xl, 1.25rem);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: var(--tracking-tight, -0.025em);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
  }
  .bao-dashboard-widget-value:empty {
    display: none;
  }
  .bao-dashboard-widget-label {
    display: block;
    margin-block-start: var(--space-micro);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-xs, 0.75rem);
    font-weight: 500;
    color: var(--text-tertiary);
  }
  .bao-dashboard-widget-label:empty {
    display: none;
  }
  .bao-dashboard-widget-secondary {
    display: block;
    margin-block-start: var(--space-micro);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-xs, 0.75rem);
    color: var(--text-tertiary);
  }
  /* Paths / mono detail lines never blow out the KPI tile width. */
  .bao-dashboard-widget-secondary[data-path],
  .bao-dashboard-widget-secondary.font-mono,
  [data-dashboard-widget] .bao-dashboard-widget-secondary {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Compact density densifies body; height ownership is panel-grid (content-first). */
  [data-dashboard-widget][data-widget-density="compact"] .card-body,
  [data-dashboard-widget][data-widget-density="compact"] [data-widget-value],
  [data-dashboard-widget][data-widget-density="compact"] .bao-dashboard-panel-value {
    min-height: 0;
    flex: 0 1 auto;
  }
  .bao-dashboard-widget-secondary:empty {
    display: none;
  }
  @media (max-width: 40rem) {
    .bao-dashboard-widget-value {
      font-size: var(--text-lg, 1.125rem);
    }
    [data-dashboard-widget] {
      min-width: 0;
    }
  }
  /*
   * KPI reflow (design.pdf §9.5 "reflow rather than clip text", §9.6 "fixed
   * capsules may need to become rows").
   *
   * The two-track grid above pairs minmax(0,1fr) copy with an `auto` meta
   * track. The meta track holds a nowrap status chip, so it cannot shrink
   * while the copy track floors at 0 — a 168px tile left the value 94px and
   * rendered "Setup required" as "Set...". Below the stacking threshold the
   * chip drops to its own row and the value keeps the full inline size.
   *
   * Container-scoped, not viewport-scoped: the SAME tile is narrow at 2-up on
   * a phone, 3-up on a tablet and 4-up on a wide desktop, so a viewport
   * breakpoint cannot express the condition (design.pdf §8 — adapt to the
   * available window, not a guessed device class).
   *
   * :has() keeps inline-size containment off the recent-cases value slot,
   * whose .dropdown-content must escape the tile box.
   */
  .bao-dashboard-panel-value:has(> .bao-dashboard-widget-fragment) {
    container-type: inline-size;
    container-name: bao-widget-value;
  }
  /*
   * 20rem (not 15rem): desktop 3–4-up KPI tiles often land ~16–19rem wide —
   * enough for a status chip beside the value but not for "Setup required"
   * at text-xl, which previously ellipsed to "Setup r…". Stack earlier so
   * operators always read full KPI words (design.pdf §9.5).
   */
  @container bao-widget-value (max-width: 20rem) {
    .bao-dashboard-widget-fragment {
      grid-template-columns: minmax(0, 1fr);
    }
    .bao-dashboard-widget-meta {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      justify-content: flex-start;
    }
    /* Value and label own the tile width here: wrap instead of ellipsing. */
    .bao-dashboard-widget-value,
    .bao-dashboard-widget-label {
      white-space: normal;
      overflow-wrap: anywhere;
    }
    .bao-dashboard-widget-value {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
    }
  }
  .bao-dashboard-widget-meta {
    display: flex;
    flex-shrink: 0;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-micro);
  }
  /* Trend chip — direction + colour (WCAG 1.4.1). */
  .bao-dashboard-widget-trend {
    display: inline-flex;
    align-items: center;
    gap: var(--space-micro);
    white-space: nowrap;
    border-radius: var(--radius-full);
    padding-inline: var(--space-micro);
    padding-block: var(--space-micro);
    font-size: var(--text-xs, 0.75rem);
    font-weight: 600;
    line-height: 1.2;
    background: color-mix(in oklab, var(--text-primary) 8%, transparent);
    color: var(--text-secondary);
  }
  .bao-dashboard-widget-trend::before {
    content: "\2192";
    font-variant-numeric: tabular-nums;
  }
  .bao-dashboard-widget-trend[data-trend-value="up"] {
    background: color-mix(in oklab, var(--status-success) 14%, transparent);
    color: var(--status-success);
  }
  .bao-dashboard-widget-trend[data-trend-value="up"]::before {
    content: "\2191";
  }
  .bao-dashboard-widget-trend[data-trend-value="down"] {
    background: color-mix(in oklab, var(--status-error) 14%, transparent);
    color: var(--status-error);
  }
  .bao-dashboard-widget-trend[data-trend-value="down"]::before {
    content: "\2193";
  }
  .bao-dashboard-widget-trend:empty {
    display: none;
  }
  .bao-dashboard-widget-status {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: var(--space-micro);
    white-space: nowrap;
    font-size: var(--text-xs, 0.75rem);
    font-weight: 500;
    color: var(--text-tertiary);
  }
  .bao-dashboard-widget-detail {
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    margin-block-start: var(--space-micro);
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--text-xs, 0.75rem);
    line-height: 1.3;
    color: var(--text-tertiary);
  }
  /* Descriptive prose needs more lines when enlarged type consumes its
   * available measure. Ordinary cards retain the two-line density cap. */
  @container bao-widget-value (max-width: 12rem) {
    .bao-dashboard-widget-detail {
      -webkit-line-clamp: unset;
    }
    /* The state word can use its own row rather than being crushed beside the glyph. */
    .bao-dashboard-widget-status > [data-status-indicator] {
      flex-wrap: wrap;
    }
  }
  /* Short metric values retain their normal phone cap until enlarged type
   * leaves fewer than five root-font units for the complete value. */
  @container bao-widget-value (max-width: 5rem) {
    .bao-dashboard-widget-value {
      -webkit-line-clamp: unset;
    }
  }
  .bao-dashboard-widget-detail:empty {
    display: none;
  }
  /* Phone density (Apple HIG): drop long secondary detail; status + value remain. */
  @media (max-width: 40rem) {
    .bao-dashboard-widget-detail {
      display: none;
    }
    .bao-dashboard-widget-secondary:not([data-path]):not(.font-mono) {
      display: none;
    }
    .bao-dashboard-widget-trend:empty,
    .bao-dashboard-widget-trend:not([data-trend-value]),
    .bao-dashboard-widget-trend[data-trend-value=""],
    .bao-dashboard-widget-trend[data-trend-value="flat"],
    .bao-dashboard-widget-trend[data-trend-value="none"] {
      display: none;
    }
  }
  /* Whole-card affordance: the widget action is a stretched cover link over
     the card (design.pdf §5.4 — the card IS the control; no duplicate pill).
     Inner interactive chrome (customize controls) layers above it (z-table-action tier). */
  .bao-dashboard-widget-action {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0;
    min-height: 0;
    min-width: 0;
    height: auto;
    align-self: auto;
  }
  .bao-dashboard-widget-action:empty {
    display: none;
  }
  /* Empty KPI: drop long detail so tiles do not balloon equal-row white voids */
  [data-dashboard-widget-fragment][data-state="empty"] + .bao-dashboard-widget-detail {
    display: none;
  }
 

  /*
   * KPI / collection solid grid densify (happydumpling / uiux-standardizer):
   * solid bao-data-panel widgets, compact headers, no idle indicator bloat.
   * Phone single-column lives in tokens-glass-shell-webview (all data-page).
   * Collection pages (evidence, reports) use the same DashboardWidget shell.
   */
  [data-page-type="home"] [data-dashboard-grid],
  [data-page="dashboard"] [data-dashboard-grid],
  [data-page-type="collection"] [data-dashboard-grid] {
    grid-auto-rows: minmax(0, auto);
  }
  /*
   * No blanket height:auto here.
   *
   * A rule over every dashboard and collection widget used to set
   * `height: auto; align-self: start`, which cancelled the stretch contract
   * `.bao-grid-panel-*` declares in tokens-layout-panel-grid. Cards in one row
   * then sized to their own content: the dashboard KPI row went ragged and
   * evidence, mcp-admin and help left holes where a short card sat beside a
   * tall one. "KPI rows share a row height" is the standing rule, so the grid
   * owns height and the only way out is the explicit compact-density opt-out
   * below — one owner, one escape hatch, both named.
   */

  /*
   * HTMX indicators never reserve layout space — in either state.
   *
   * `position: absolute` used to sit on the idle selector alone, so the
   * moment a request opened, every indicator on every page dropped back to
   * static and pushed whatever followed it down for the length of the
   * fetch. Measured at 1280x800 on /settings?tab=capture-devices: the panel
   * host travelled y=159 to y=183 and back across one request, two layout
   * shifts of 0.198 apiece and a route CLS of 0.4214 against the 0.1 budget
   * this codebase sets for skeleton geometry.
   *
   * Absolute with no offsets keeps the spinner at its static position, so
   * it paints exactly where it always did and displaces nothing. The
   * comment above this rule always claimed indicators reserve no space;
   * now both states honour it.
   */
  .htmx-indicator {
    position: absolute;
  }
  .htmx-indicator:not(:is(.htmx-request, [data-shell-loading])) {
    display: none;
    width: 0;
    height: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
  }
  .htmx-indicator:is(.htmx-request, [data-shell-loading]) {
    display: inline-block;
  }

  [data-page="dashboard"] [data-dashboard-widget]:not([data-widget="recent-cases"]),
  [data-page-type="collection"] [data-dashboard-widget] {
    padding: 0;
    overflow: hidden;
  }

  /*
   * Compact density (design.pdf content-first): progressive strips such as the
   * case journey must not stretch to the tallest grid row or pad like KPI cards.
   */
  [data-dashboard-widget][data-widget-density="compact"] {
    height: auto;
    min-height: 0;
    max-height: none;
    align-self: stretch;
  }
  [data-dashboard-widget][data-widget-density="compact"] > .card-body,
  [data-dashboard-widget][data-widget-density="compact"] > [class*="card-body"] {
    padding-block: var(--space-micro, 0.25rem);
    padding-inline: var(--space-inline, 0.5rem);
    gap: var(--space-micro, 0.25rem);
  }
  [data-dashboard-widget][data-widget-density="compact"] [data-case-journey-host],
  [data-dashboard-widget][data-widget-density="compact"] [data-case-journey] {
    min-height: 0;
  }

  /*
   * Single padding owner: card-body (panelShell). Never re-pad the first child —
   * double padding was the evidence-health / KPI tile spacing sin.
   * Collection pathologist panels share the same geometry contract.
   */
  [data-page="dashboard"] [data-dashboard-widget] > .card-body,
  [data-page="dashboard"] [data-dashboard-widget] > [class*="card-body"],
  [data-page-type="collection"] [data-dashboard-widget] > .card-body,
  [data-page-type="collection"] [data-dashboard-widget] > [class*="card-body"] {
    padding: var(--space-inline);
    gap: var(--space-micro);
    min-height: 0;
  }

  [data-page="dashboard"] [data-dashboard-widget] .card-title,
  [data-page="dashboard"] [data-dashboard-widget] .bao-dashboard-widget-title,
  [data-page-type="collection"] [data-dashboard-widget] .card-title,
  [data-page-type="collection"] [data-dashboard-widget] .bao-dashboard-widget-title {
    font-size: var(--text-sm, 0.875rem);
    font-weight: 600;
    line-height: 1.25;
    margin: 0;
    gap: var(--space-micro);
  }

  [data-page="dashboard"] [data-dashboard-widget] .bao-dashboard-panel-value,
  [data-page-type="collection"] [data-dashboard-widget] .bao-dashboard-panel-value {
    min-height: 0;
    margin-block-start: 0;
    flex: 1 1 auto;
  }

  /* Nested fragment hosts stay unframed — DashboardWidget owns the only chrome. */
  [data-page="dashboard"] [data-dashboard-widget] [data-dashboard-widget-fragment],
  [data-page-type="collection"] [data-dashboard-widget] [data-dashboard-widget-fragment] {
    min-width: 0;
  }
  [data-page="dashboard"] [data-dashboard-widget] .bao-dashboard-widget-action:empty,
  [data-page="dashboard"] [data-dashboard-widget] a.bao-dashboard-widget-action[href=""],
  [data-page="dashboard"] [data-dashboard-widget] a.bao-dashboard-widget-action:not([href]),
  [data-page-type="collection"] [data-dashboard-widget] .bao-dashboard-widget-action:empty,
  [data-page-type="collection"] [data-dashboard-widget] a.bao-dashboard-widget-action[href=""],
  [data-page-type="collection"] [data-dashboard-widget] a.bao-dashboard-widget-action:not([href]) {
    display: none;
  }

  /* Idle HTMX indicators must not reserve layout on home/collection. */
  [data-page="dashboard"] .htmx-indicator:not(:is(.htmx-request, [data-shell-loading])),
  [data-page-type="home"] .htmx-indicator:not(:is(.htmx-request, [data-shell-loading])),
  [data-page-type="collection"] .htmx-indicator:not(:is(.htmx-request, [data-shell-loading])) {
    display: none;
    width: 0;
    height: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
    position: absolute;
  }

  /*
   * Layout customizer exclusivity (Apple edit mode):
   * daisyUI .card-actions is @layer — unlayered SSOT with compound
   * class+attr selectors wins via cascade. Only show size/move/
   * visibility chrome while data-dashboard-editing=true.
   */
  [data-dashboard-panel-controls][hidden],
  [data-dashboard-panel-controls].hidden,
  [data-dashboard-visibility-tray][hidden],
  [data-dashboard-visibility-tray].hidden,
  [data-dashboard-uniform-size-tray][hidden],
  [data-dashboard-uniform-size-tray].hidden,
  [data-dashboard-layout-root][data-dashboard-editing="false"] [data-dashboard-panel-controls],
  [data-dashboard-layout-root][data-dashboard-editing="false"] [data-dashboard-visibility-tray],
  [data-dashboard-layout-root][data-dashboard-editing="false"] [data-dashboard-uniform-size-tray],
  .card-actions[data-dashboard-panel-controls][hidden],
  .card-actions[data-dashboard-panel-controls].hidden,
  .card-actions[data-dashboard-panel-controls][hidden].flex,
  .card-actions[data-dashboard-panel-controls].hidden.flex {
    display: none;
    visibility: hidden;
    pointer-events: none;
    width: 0;
    height: 0;
    min-height: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
    border: 0;
    gap: 0;
  }

  [data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-panel-controls]:not([hidden]):not(.hidden),
  [data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-visibility-tray]:not([hidden]):not(.hidden),
  [data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-uniform-size-tray]:not([hidden]):not(.hidden) {
    display: flex;
    visibility: visible;
    pointer-events: auto;
    width: auto;
    height: auto;
    min-height: 0;
  }

  /* Uniform size tray labels → icon-first density on phone when editing. */
  @media (max-width: 47.99rem) {
    [data-dashboard-uniform-size-tray] > .btn {
      min-width: var(--touch-target-min, 2.75rem);
      max-width: var(--touch-target-min, 2.75rem);
      padding-inline: 0;
      justify-content: center;
    }
  }

  /*
   * Popover customizer (renderAs: "popover"):
   * Source shell under the layout root is display:contents / none until edit.
   * JS reparents trays into [data-dashboard-customizer-dropdown] next to the
   * header customize button — that host must size and show content itself.
   */
  [data-dashboard-customizer-popover] {
    display: contents;
  }
  [data-dashboard-layout-root][data-dashboard-customizer-render="popover"][data-dashboard-editing="false"] [data-dashboard-customizer-popover] {
    display: none;
  }
  [data-dashboard-layout-root][data-dashboard-customizer-render="popover"][data-dashboard-editing="true"] [data-dashboard-customizer-content] {
    display: flex;
    flex-direction: column;
    visibility: visible;
  }
  /*
   * Runtime host + open-tray geometry live UNLAYERED in
   * tokens-layout-customizer-host.ts — inside @layer utilities they lose to
   * unlayered daisyUI dropdown structure and the tray ships 0x0.
   */
  [data-dashboard-customizer-dropdown] [data-dashboard-uniform-size-tray]:not([hidden]):not(.hidden),
  [data-dashboard-customizer-dropdown] [data-dashboard-visibility-tray]:not([hidden]):not(.hidden),
  [data-dashboard-layout-root][data-dashboard-customizer-render="popover"] [data-dashboard-uniform-size-tray]:not([hidden]):not(.hidden),
  [data-dashboard-layout-root][data-dashboard-customizer-render="popover"] [data-dashboard-visibility-tray]:not([hidden]):not(.hidden) {
    display: flex;
    flex-wrap: wrap;
    visibility: visible;
    width: 100%;
    max-width: none;
    min-height: var(--touch-target-min, 2.75rem);
    margin-block: 0;
    gap: var(--space-micro, 0.25rem);
  }
  [data-dashboard-customizer-dropdown] [data-dashboard-customizer-section],
  [data-dashboard-customizer-content] [data-dashboard-customizer-section] {
    display: flex;
    flex-direction: column;
    gap: var(--space-micro, 0.25rem);
    width: 100%;
    min-width: 0;
  }

  /*
   * Phone home densify — widget helper/body copy is secondary to the KPI.
   * Two-line clamp keeps the card grid scannable (Apple widget density);
   * full copy remains in aria when truncated by overflow.
   */
  @media (max-width: 47.99rem) {
    /* Clamp descriptions only; shared table values retain their own flex layout. */
    [data-page="dashboard"] [data-dashboard-widget] [data-widget-description] {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    [data-page="dashboard"] [data-dashboard-widget] .bao-dashboard-panel-value {
      font-size: var(--text-lg, 1.125rem);
      line-height: 1.2;
    }
    /*
     * Drag-handle chrome clutter: fine-pointer grab affordance is desktop-only.
     * Phone / coarse pointers reorder via customize move buttons (HIG 2.5.7).
     */
  }

  /*
   * Solid KPI metric card — never glass material (Apple layer model / glass law).
   * Hard-ban accidental bao-surface-glass-* co-tags on metric hosts.
   */
  
  .bao-stat-card.bao-surface-glass-regular,
  .bao-stat-card.bao-surface-glass-clear,
  .bao-stat-card.bao-surface-glass-strong,
  [data-dashboard-widget].bao-surface-glass-regular,
  [data-dashboard-widget].bao-surface-glass-clear,
  [data-dashboard-widget].bao-surface-glass-strong {
    background-color: var(--color-base-100, var(--bg-panel));
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }

/* Widget titles remain readable at compact widths. */
[data-dashboard-widget] .card-title > .truncate {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}


[data-report-pdf-preview] {
  min-inline-size: 0;
  min-block-size: 0;
}

.bao-report-pdf-frame[data-report-pdf-frame] {
  display: block;
  flex: 1 1 auto;
  inline-size: 100%;
  block-size: min(34rem, calc(var(--vv-height, 100dvh) * 0.55));
  min-inline-size: 0;
  min-block-size: 0;
  max-block-size: 100%;
  border: 1px solid var(--border-base);
  border-radius: var(--radius-field);
  background: var(--color-base-100);
}

@media (max-width: 47.99rem) {
  .bao-report-pdf-frame[data-report-pdf-frame] {
    block-size: min(18rem, calc(var(--vv-height, 100dvh) * 0.34));
  }
}

@media (forced-colors: active) {
  .bao-report-pdf-frame[data-report-pdf-frame] {
    border-color: ButtonText;
    background: Canvas;
  }
}


  /* Page-level skeleton shells — geometry matches final success layout
     (CLS < 0.1). Pair daisyUI skeleton class on children for motion. */
  .bao-page-skeleton {
    box-sizing: border-box;
    width: 100%; min-width: 0; max-width: 100%;
    overflow-x: clip;
  }
  .bao-page-skeleton-default {
    display: flex;
    flex-direction: column;
    gap: var(--space-inline);
    padding-block: var(--space-page);
  }
  .bao-page-skeleton-grid {
    display: grid;
    align-items: stretch;
    grid-auto-rows: auto;
    gap: var(--space-section);
    padding-block: var(--space-page);
    container-type: inline-size;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--grid-track-min, 18rem)), 1fr)
    );
  }
  /* Detail / auth skeleton: centered single column matching AuthShell card
     geometry so the skeleton-to-content swap stays CLS-safe (CLS < 0.1). */
  .bao-page-skeleton-detail {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 0;
    flex: 1 1 auto;
    gap: var(--space-section);
    padding-block: var(--space-empty);
    padding-inline: var(--space-page);
  }
  .bao-page-skeleton-detail > .bao-skeleton-panel {
    width: 100%;
    max-width: var(--auth, 28rem);
  }
  .bao-page-skeleton-table {
    display: flex;
    flex-direction: column;
    gap: var(--space-inline);
    padding-block: var(--space-page);
  }
  .bao-page-skeleton-media {
    display: grid;
    min-width: 0;
    gap: var(--space-section);
    padding-block: var(--space-page);
    container-type: inline-size;
    grid-template-columns: minmax(0, 1fr);
  }
  @container (min-width: 40rem) {
    .bao-page-skeleton-media {
      grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    }
  }
  .bao-page-skeleton-media-side {
    display: flex;
    flex-direction: column;
    gap: var(--space-inline);
    min-width: 0; max-width: 100%;
  }
  /* Conversation skeleton matches context strip + thread + composer geometry. */
  .bao-page-skeleton-conversation {
    display: flex;
    flex-direction: column;
    gap: var(--space-section);
    min-height: 0;
    flex: 1 1 auto;
    padding-block: var(--space-page);
  }
  .bao-skeleton-conversation-chip {
    height: 3rem;
    width: 100%;
    min-width: 0;
    border-radius: var(--radius-field, 0.75rem);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-conversation-thread {
    flex: 1 1 auto;
    min-height: min(28rem, 50vh);
    width: 100%;
    border-radius: var(--radius-box, 1rem);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  .bao-skeleton-conversation-composer {
    height: 8rem;
    width: 100%;
    max-width: var(--page-wide, 96rem);
    margin-inline: auto;
    border-radius: var(--radius-box, 1rem);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface-hover);
  }

  /* Skeleton blocks — solid surfaces for data (no glass on dense chrome). */
  .bao-skeleton-line {
    height: 1rem;
    width: 100%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-line-short {
    height: 1rem;
    width: 75%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-panel {
    box-sizing: border-box; display: flex;
    flex-direction: column;
    gap: var(--space-inline);
    min-height: var(--skeleton-panel-min-height, 7rem);
    width: 100%; min-width: 0; max-width: 100%;
    overflow-x: clip;
    padding: var(--space-page);
    border-radius: var(--radius-box, 1rem);
    border: 1px solid var(--border-base);
    background-color: var(--bg-panel);
    box-shadow: 0 1px 2px color-mix(in oklab, var(--color-base-content) 6%, transparent);
  }
  .bao-skeleton-panel-body {
    height: 3.5rem;
    width: 100%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-home-full {
    min-height: 12rem;
    width: 100%;
    border-radius: var(--radius-box, 1rem);
  }
  .bao-skeleton-media {
    box-sizing: border-box; aspect-ratio: 16 / 9;
    width: 100%; max-width: 100%;
    min-height: var(--skeleton-media-min-height, 12rem);
    overflow-x: clip;
    border-radius: var(--radius-box, 1rem);
    border: 1px solid var(--border-base);
    background-color: var(--bg-surface);
  }
  .bao-skeleton-table-header {
    height: var(--density-default-row-height, 2.25rem);
    width: 100%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }
  .bao-skeleton-table-row {
    height: var(--density-default-row-height, 2.25rem);
    width: 100%;
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-base);
  }
  /* Pathologist journey step skeleton — matches .bao-pathologist-journey step
     label + circle geometry (tokens-glass-journey-workflow). */
  .bao-pathologist-journey .bao-skeleton-journey-step,
  .bao-skeleton-journey-step {
    display: inline-block;
    height: var(--text-2xs, 0.6875rem);
    width: 4.5rem;
    max-width: 100%;
    min-height: var(--touch-target-min, 2.75rem);
    border-radius: var(--radius-sm);
    background-color: var(--bg-surface-hover);
  }



  /* Named layout surfaces — replace arbitrary Tailwind brackets in .bao tokens. */
  .bao-editor-root {
    min-width: 0;
    max-width: 100%;
    min-height: var(--editor-root-min-height);
    overflow-x: clip;
  }
  /*
   * Canvas column owns the specimen stage. Drawing tools live on
   * [data-editor-tool-rail]; tokens-layout-editor-toolbar-container-presentation
   * owns that rail's axis.
   */
  .bao-editor-canvas-column {
    /*
     * Stage density container. The stage header, context bar and status bar
     * live inside this column, and this column's inline size is what decides
     * whether they fit — the workbench's does not.
     */
    container-type: inline-size;
    container-name: editor-stage;
    position: relative;
    display: flex;
    min-width: 0;
    max-width: 100%;
    min-height: 0;
    max-height: 100%;
    flex: 1 1 var(--editor-canvas-basis, 32rem);
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: hidden;
  }
/* Catalog bounds and wrapping are owned by tokens-layout-editor-toolbar-disclosure. Rail axes are owned by tokens-layout-editor-toolbar-container-presentation. */

  [data-editor-tool-rail] > .btn {
    flex: 0 0 auto;
  }
  /*
   * Context status strip — bounded. Badges wrap or ellipsize; they do not pan.
   */
  [data-editor-context-bar] {
    min-width: 0;
    max-width: 100%;
  }
  [data-editor-chrome] > .flex:has(> [data-editor-context-bar]) {
    min-width: 0;
    max-width: 100%;
  }
  /*
   * Context badge value pill — single-line ellipsis at every viewport so
   * long image UUIDs ('30c35c36-bd5e-...-52ee0') never balloon the strip
   * past the editor column. The dock-densify-b phone rule covers ≤47.99rem;
   * this global rule extends the cap to tablet/desktop so Custody never
   * gets pushed off the right edge of the rail.
   */
  [data-editor-context] strong,
  [data-editor-context-bar] .bao-context-badge strong,
  [data-capture-context] strong {
    display: block;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  [data-editor-context],
  [data-editor-context-bar] .bao-context-badge,
  [data-capture-context] {
    min-width: 0;
    max-width: 100%;
  }

  /*
   * Filmstrip tiles are the capture navigation under the stage. The current
   * capture carries the primary ring; the tile itself is the touch target,
   * so the ring never shrinks the hit area.
   */
  [data-editor-filmstrip-list] a[aria-current="true"] {
    border-color: var(--color-primary);
    background: color-mix(in oklab, var(--color-primary) 10%, transparent);
  }
  [data-editor-filmstrip-list] a:hover,
  [data-editor-filmstrip-list] a:focus-visible {
    border-color: var(--border-base, var(--color-base-300));
  }

@media (max-width: 47.99rem) {
  /*
   * Identity badges wrap; they do not scroll sideways.
   */
  [data-editor-context-bar] {
    display: flex;
    max-width: 100%;
    flex-wrap: nowrap;
    gap: var(--space-micro, 0.25rem);
  }
  [data-editor-context-bar] .bao-context-badge {
    flex: 0 1 auto; min-inline-size: 0;
    max-width: min(10.5rem, 100%);
  }
  [data-editor-context-bar] [data-editor-context="image"].bao-context-badge,
  [data-editor-context-bar] [data-editor-context="image"] {
    max-width: min(7.25rem, 100%);
  }
  /*
   * Identity truncates; a numeric readout does not.
   * Status strip does not shrink; context badges absorb the squeeze.
   */
  [data-editor-status-bar] {
    flex: 0 0 auto;
    overflow: hidden;
    gap: var(--space-2xs, 0.125rem);
  }
  [data-toolbar-zone="center"] > [data-editor-context-bar] {
    min-width: 0;
    flex: 0 1 auto;
  }
  /*
   * Model provenance belongs to the AI panel on a phone. The strip measured
   * three rows at 390px with "Provider: litert · Model: MedGemma 1.5 4B
   * Vision" on the last, over a 200px canvas.
   */
  [data-editor-status-bar] [data-editor-status="ai-provider"],
  [data-editor-status-bar] [data-editor-status="ai-model"] {
    display: none;
  }
  /*
   * Status strip density (Apple HUD): keep zoom + review + AI provenance;
   * hide cursor/input/layer chrome that floods the bottom overlay on phone.
   */
  [data-editor-status-bar] [data-editor-status="cursor"],
  [data-editor-status-bar] [data-editor-status="input"],
  [data-editor-status-bar] [data-editor-status="layer"] {
    display: none;
  }
  /*
   * Dimension switcher collapses to its glyphs with the rest of the stage
   * toolbar, so leading and trailing share one row.
   */
  [data-editor-stage-toolbar] [data-viz-dimension-switcher] button > span:not(.sr-only),
  [data-editor-3d-stage-header] [data-viz-dimension-switcher] button > span:not(.sr-only) {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  [data-editor-chrome] [data-editor-save],
  [data-editor-chrome] form[data-editor-save-form] button[type="submit"],
  [data-editor-chrome] [data-bao-action="save-annotations"] {
    position: sticky;
    bottom: var(--space-micro, 0.25rem);
    z-index: var(--z-canvas-overlay, 30);
  }
  /* The 3D header can wrap its actions within its own surface. */
  [data-editor-3d-stage-header] [data-toolbar-zone="trailing"] {
    flex-wrap: wrap;
  }
  [data-editor-chrome] form[data-editor-save-form] {
    flex: 1 1 100%;
    justify-content: flex-end;
  }
  /* The document toolbar uses the shared floating stage geometry in every
   * dimension. The collapsed 3D sheet owns its separate grid row. */
  [data-ai-overlay-item] [data-ai-overlay-coords],
  [data-ai-overlay-item] [data-ai-overlay-space],
  [data-ai-overlay-item] [data-ai-overlay-geometry-projection-row],
  [data-ai-overlay-item] [data-ai-overlay-source-row],
  [data-ai-overlay-item] [data-ai-overlay-created-row] {
    display: none;
  }
  .bao-ai-provenance-sticky {
    grid-template-columns: minmax(0, 1fr);
  }
}

  .bao-editor-canvas {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-width: 100%;
    min-height: max(min(24rem, var(--editor-canvas-height, clamp(26rem, 58vh, 44rem))), min(18rem, 50vh));
    /*
     * No viewport-fraction cap — let the canvas fill its bounded flex chain.
     * The host column owns the height bound; capping at 70vh starved tall
     * viewports and clipped content. WebView Chromium 101 (BlueStacks) does not
     * support dvh (Chrome 108+); use 100% of the bounded host instead.
     */
    max-height: 100%;
    flex: 1 1 auto;
    height: 100%;
    overflow: hidden;
  }
  /* The media and its direct layers stay inside the canvas host at every text size. */
  [data-editor-stage] canvas,
  .bao-editor-canvas canvas,
  .bao-editor-canvas > * {
    max-width: 100%;
  }
  /* Annotate session panel shares the same bounded flex chain as capture stage.
   * Panel + chrome clip; workbench owns overflow-y scroll (never nest hidden). */
  [data-session-panel="annotate"] {
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    flex: 1 1 0%;
    overflow: hidden;
  }
  [data-session-panel="annotate"] [data-editor-chrome] {
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    flex: 1 1 0%;
    overflow: hidden;
  }
  [data-session-panel="annotate"] .bao-editor-workbench {
    min-height: 0;
    max-height: 100%;
    flex: 1 1 0%;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
  }
  .bao-editor-canvas[data-state="success"] [data-editor-canvas-empty] {
    display: none;
  }
  /*
   * Empty / error: Fabric stage paints an opaque white box over the empty
   * CTA (z-canvas-base under fabric). Park fabric below and inert until an
   * image loads; empty panel owns the full stage with solid fill + CTAs.
   */
  .bao-editor-canvas[data-state="empty"] .bao-fabric-stage,
  .bao-editor-canvas[data-state="error"] .bao-fabric-stage {
    opacity: 0;
    pointer-events: none;
    z-index: var(--z-canvas-base, 1);
  }
  .bao-editor-canvas[data-state="empty"] [data-editor-canvas-empty],
  .bao-editor-canvas[data-state="error"] [data-editor-canvas-empty] {
    display: flex;
    z-index: var(--z-canvas-empty, 20);
    pointer-events: auto;
  }
  /* No clinical image: present one acquisition decision, not an inert tool wall. */
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-toolbar] [data-tool],
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-ai-review-form] button[type="submit"],
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-ai-review-form] select,
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-ai-review-form] input:not([type="hidden"]) {
    pointer-events: none;
  }
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar],
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-toolbar],
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-canvas-overlays]:not([data-inspector-expanded="true"]),
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-save-form],
  /*
   * The 3D stage may paint when the operator has explicitly asked for it, even
   * with nothing loaded — it owns an empty state ("No specimen in 3D") that is
   * the whole point of showing it. Without this exemption the dimension toggle
   * was a dead control: measured on /editor at 1440x900 with an empty canvas,
   * clicking "Switch to 3D" flipped data-editor-view-mode to "3d" and
   * aria-pressed to "true" while the 3D column stayed display:none and the 2D
   * column stayed visible. Nothing on screen changed, and assistive tech was
   * told the 3D view was active when it was not.
   */
  [data-editor-chrome]:not([data-annotation-ready="true"]):not([data-editor-view-mode="3d"]) .bao-editor-3d-column {
    display: none;
  }
  /* Dual guard: the canvas island is authoritative when its state is empty. */
  [data-editor-chrome]:has(.bao-editor-canvas[data-state="empty"]) [data-editor-toolbar],
  [data-editor-chrome]:has(.bao-editor-canvas[data-state="empty"]) [data-editor-canvas-overlays]:not([data-inspector-expanded="true"]),
  [data-editor-chrome]:not([data-editor-view-mode="3d"]):has(.bao-editor-canvas[data-state="empty"]) .bao-editor-3d-column,
  [data-editor-chrome]:has(.bao-editor-canvas[data-state="empty"]) [data-editor-save-form] {
    display: none;
  }
  /*
   * Empty / not-ready stage: no readouts. Zoom, save and review state
   * describe an image that is not there, and a provider/model pair names a
   * model that has not run — measured on every audited band, the empty stage
   * header read "Provider: litert · Model: MedGemma 1.5 4B Vision" over a
   * placeholder. The AI panel in the inspector is where a model is chosen;
   * the strip returns with the image.
   */
  [data-editor-chrome]:not([data-annotation-ready="true"]) [data-editor-status-bar],
  [data-editor-chrome]:has(.bao-editor-canvas[data-state="empty"]) [data-editor-status-bar] {
    display: none;
  }
  /* AI finding selection is owned by tokens-glass-shell-overlays. */
  [data-editor-chrome]:not([data-annotation-ready="true"]) .bao-editor-canvas-column {
    width: 100%;
    min-width: 100%;
    flex-basis: 100%;
  }
  /*
   * Immersion idle-hide: fade chrome that OVERLAYS the specimen after
   * inactivity (JS sets data-editor-chrome-idle). Status strip STAYS painted —
   * zoom / AI provenance chips are diagnostic HUD, not chrome thrash (Apple
   * media player keeps the scrubber readable; design.pdf §5.4).
   *
   * The stage header IS the document toolbar now (Save, Export, 2D/3D, the
   * inspector tabs' toggle), so it auto-hides only in immersive maximize —
   * HIG toolbars never vanish from a normal window. Measured 2026-08-23 in
   * the Claude Browser pane at 1440x900: an untouched window showed the stage
   * with no toolbar at all after the idle timer fired.
   */
  [data-editor-chrome][data-canvas-maximized="true"][data-editor-chrome-idle="true"] [data-editor-stage-header],
  body[data-canvas-maximized="true"] [data-editor-chrome][data-editor-chrome-idle="true"] [data-editor-stage-header],
  body[data-canvas-maximized="true"] [data-editor-chrome][data-editor-chrome-idle="true"] [data-editor-toolbar] {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--duration-normal, 200ms) var(--easing-standard, ease);
  }
  /*
   * Immersive maximize: mirror island state on editor chrome (WebViews without
   * :has). Hide inspector sheet; floating toolbar + stage/status keep glass SSOT.
   */
  [data-editor-chrome][data-canvas-maximized="true"] [data-editor-canvas-overlays],
  [data-editor-chrome][data-canvas-maximized="true"] [data-editor-filmstrip-disclosure] {
    display: none;
  }
  /* WebView-safe mirror of the maximized canvas state. The filmstrip is stage
     navigation, not stage: measured maximized at 1440x900 it kept painting at
     y=142 over the specimen. */
  body[data-canvas-maximized="true"] [data-editor-canvas-overlays],
  body[data-canvas-maximized="true"] [data-editor-filmstrip-disclosure] {
    display: none;
  }

  /*
   * Maximized: the palette is absolutely positioned inside the stage, and the
   * stage is what goes full-screen, so the palette rides with it and needs no
   * fixed-strip mirror.
   */
  /*
   * Selected tool affordance (design.pdf §2.11): active tool gets filled-weight
   * icon stroke beyond the glass ring — stroke-only SVGs only; no path fill.
   */
  [data-editor-toolbar] [data-tool][aria-pressed="true"] svg {
    stroke-width: 2.2;
  }
  [data-editor-toolbar] [data-tool][aria-pressed="true"] svg [fill="none"] {
    fill: none;
  }
  @media (prefers-reduced-motion: reduce) {
    [data-editor-chrome][data-editor-chrome-idle="true"] [data-editor-toolbar],
    [data-editor-chrome][data-editor-chrome-idle="true"] [data-editor-stage-header] {
      transition: none;
    }
  }
  [data-editor-requires-image][aria-disabled="true"] {
    pointer-events: none;
  }
  /*
   * Clear-glass density modifier (AE-14): stage header + status strip carry
   * bao-glass-small, which previously existed only for glass-regular, so the
   * class was dead on clear chrome. Small clear chrome drops to the mobile
   * blur step — the lightest live blur in the ladder above none.
   */
  .bao-surface-glass-clear.bao-glass-small {
    -webkit-backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-clear));
    backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-clear));
  }
  /*
   * 2D/3D dimension radios are sr-only (focusable) instead of display:none
   * (AE-06): the visible focus ring lands on the hosting label so keyboard
   * operation of the radiogroup stays perceivable.
   */
  [data-viz-dimension-switcher] label:has(input[type="radio"]:focus-visible) {
    outline: var(--focus-ring-width, 2px) solid var(--color-primary, var(--accent-primary, currentColor));
    outline-offset: var(--focus-ring-offset, 2px);
  }
  /* Phone density: drop AI/spatial panel marketing blurbs; keep controls. */
  @media (max-width: 40rem) {
    [data-editor-ai-review] > p,
    [data-editor-spatial] > p,
    [data-editor-overlays] > p {
      display: none;
    }
    /* Model select already lists options; search is desktop densify only. */
    [data-editor-ai-model-search] {
      display: none;
    }
  }
  /* Disabled Finish/Generate must never look primary-ready (daisy softens). */
  [data-page-header-actions] .btn:disabled,
  [data-page-header-actions] .btn[aria-disabled="true"],
  [data-page-header-actions] .btn.btn-disabled {
    opacity: var(--opacity-muted, 0.55);
    pointer-events: none;
  }

/* The annotate filmstrip is a horizontal thumbnail rail: its capture list
   scrolls sideways with proximity snap so a flick settles on a thumbnail
   instead of drifting between two. */
.bao-editor-filmstrip {
}

.bao-editor-filmstrip [data-editor-filmstrip-list] {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  scroll-snap-type: x proximity;
}


/* The capture rail is intrinsic chrome; it never claims leftover stage height. */
.bao-editor-canvas-column > [data-editor-filmstrip-disclosure],
.bao-editor-canvas-column > [data-editor-filmstrip-disclosure] > [data-editor-filmstrip] {
  min-block-size: 0;
  block-size: fit-content;
  max-block-size: max-content;
  flex: 0 0 auto;
}

/* Thumbnails align their snap edge to the rail's inline start, and the rail
   reserves the same scroll buffer every other interactive surface uses so
   focus rings are never clipped by the strip's padding box. */
.bao-editor-filmstrip-item {
  scroll-snap-align: start;
}

.bao-editor-filmstrip [data-editor-filmstrip-list] {
  scroll-padding-inline-start: var(--space-micro, 0.25rem);
}

/* The workbench's floating toolbar hovers over the canvas: center it on the
   canvas inline axis while the utilities own its vertical placement. */
.bao-editor-floating-toolbar {
  inset-inline: 0;
  margin-inline: auto;
  width: fit-content;
}
/* Flex baseline when grid is unsupported. Named areas live in tokens-layout-editor-workbench-grid. */
  .bao-editor-workbench {
    display: flex;
    flex-wrap: wrap;
    min-width: 0;
    max-width: 100%;
    min-height: 0;
    flex: 1 1 auto;
    align-items: stretch;
    gap: var(--space-section);
    overflow-x: hidden;
    overflow-y: hidden;
    overscroll-behavior-y: contain;
    /*
     * Named so command-bar wrap and compact chrome query the workbench, not
     * the stage column. Command occupies the command grid area (stage +
     * inspector), not the tools column.
     */
    container-type: inline-size;
    container-name: editor-workbench;
  }
  .bao-editor-workbench {
    position: relative;
    padding-block-end: 0;
  }
  /* Save bar clears the mobile dock + home indicator (HIG thumb zone). */
  .bao-editor-save-bar {
    padding-block-end: max(var(--space-micro, 0.25rem), env(safe-area-inset-bottom, 0px));
  }
  /* One disclosure controls compact sheets and workbench inspector columns.
     The session rail owns disclosure when the inspector is physically docked. */
  [data-inspector-toggle] {
    display: inline-flex;
  }
  [data-editor-chrome][data-inspector-dock="rail"] [data-inspector-toggle]:not([hidden]):not(.hidden) {
    display: none;
  }
  .bao-editor-workbench > [data-editor-canvas-overlays] {
    min-width: 0;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    flex: 0 1 var(--editor-inspector-basis);
  }
  .bao-editor-workbench > [data-editor-canvas-overlays]:not([data-inspector-expanded="true"]) {
    display: none;
  }
  /*
   * Small browser viewports keep the workbench vertically scrollable. Column
   * priority is defined separately by the workbench container so a rail-narrowed
   * desktop follows the same stage-first contract.
   */
  @media (max-width: 64rem) {.bao-editor-workbench,
[data-session-panel="annotate"] .bao-editor-workbench {
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }
  }
  .bao-editor-spatial-form {
    margin-top: var(--space-section);
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: end;
    gap: var(--space-inline);
  }
  /*
   * Review-state FSM picker (design.pdf §6 selected-vs-rest): soft buttons at
   * rest; the CURRENT state owns the persistent primary fill; legal targets
   * stay quiet; illegal transitions disable without vanishing.
   */
  [data-editor-review-transitions] [data-review-transition] {
    justify-content: flex-start;
    text-align: start;
  }
  [data-editor-review-transitions] [data-review-transition][aria-pressed="true"] {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    color: var(--accent-primary-content);
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent-primary-content) 35%, transparent);
  }[data-editor-review-transitions] [data-review-transition]:disabled,
[data-editor-review-transitions] [data-review-transition][aria-disabled="true"] {
    opacity: var(--opacity-disabled, 0.5);
    box-shadow: none;
  }
  /* The inspector body owns scrolling; transition choices stay in its content flow. */
  [data-editor-review-transitions] fieldset {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: var(--space-micro, 0.25rem);
    width: 100%;
  }
  [data-editor-review-transitions] fieldset [data-review-transition] {
    width: 100%;
    min-width: 0;
  }
  /* Phone: 2 columns (x/y) with z wrapping — matches capture context-strip
     phone pattern; submit button already spans full-width via grid-column. */
  @media (max-width: 47.99rem) {
    .bao-editor-spatial-form {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
  /* Submit spans its own row so the label never clips in the narrow inspector
     rail; coordinate fields (x/y and optional z) reflow across the row above. */
  .bao-editor-spatial-form > button {
    grid-column: 1 / -1;
  }/* The AI review dock reuses the spatial form shell but carries full-width
     controls (mode,
model,
search) rather than coordinate cells: stack them. */
  .bao-editor-spatial-form[data-editor-ai-review-form] {
    grid-template-columns: minmax(0, 1fr);
  }
  .bao-panel-scroll {
    min-width: 0;
    width: 100%;
    max-height: var(--panel-scroll-max-height);
    overflow-x: auto;
    overflow-y: auto;
  }
  .bao-sidebar-expanded {
    width: var(--sidebar-expanded);
    padding-block: var(--space-section);
    padding-inline: min(var(--space-section), 5%);
  }/*
   * ID + class beats Tailwind overflow-x-hidden on #app-sidebar so tip +
   * focus rings escape the icon rail (daisyUI is-drawer-close pattern).
   */
  #app-sidebar.bao-sidebar-collapsed,
.bao-shell-sidebar.bao-sidebar-collapsed,
.bao-sidebar-collapsed {
    width: var(--sidebar-collapsed);
    padding: var(--space-micro);
    overflow-x: visible;
  }/* Icon-only rail: center targets,
keep ≥44px hit area on touch. */
  .bao-sidebar-collapsed .bao-nav-item {
    justify-content: center;
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
    padding-inline: 0;
  }
  /*
   * Focus ring inset — outline-offset:2 clips under overflow-y:auto parents.
   * daisyUI 5 tips open on :hover / :has(:focus-visible) child only;the tip
   * class lives on the focused <a>,
so keyboard must force tip opacity here.
   */
  .bao-sidebar-collapsed .bao-nav-item:focus-visible {
    outline: var(--focus-ring-width, 2px) solid var(--color-primary, var(--accent-primary, currentColor));
    outline-offset: var(--focus-ring-offset-tight, -2px);
    z-index: var(--z-raised, 2);
    position: relative;
  }.bao-sidebar-collapsed .bao-nav-item.tooltip:focus-visible[data-tip]::before,
.bao-sidebar-collapsed .bao-nav-item.tooltip:focus-visible::after {
    opacity: 100%;
    --tt-pos: 0rem;
  }.bao-sidebar-collapsed .bao-nav-item.menu-active,
.bao-sidebar-collapsed .bao-nav-item[aria-current="page"] {
    box-shadow: none;
    background: color-mix(in oklab, var(--accent-primary) 18%, var(--bg-panel));
  }
  /* Icon rail: hide group titles + expanded labels;tooltips carry names. */
  .bao-sidebar-collapsed .menu-title,
#app-sidebar.bao-sidebar-collapsed .menu-title {
    display: none;
  }.bao-sidebar-collapsed [data-sidebar-expanded-only],
#app-sidebar.bao-sidebar-collapsed [data-sidebar-expanded-only] {
    display: none;
  }.bao-sidebar-collapsed .bao-nav-item.tooltip::before,
.bao-sidebar-collapsed .bao-nav-item.tooltip::after,
#app-sidebar.bao-sidebar-collapsed .bao-nav-item.tooltip::before,
#app-sidebar.bao-sidebar-collapsed .bao-nav-item.tooltip::after {
    display: block;
  }
  @media (min-width: 80rem) {#app-sidebar .bao-nav-item.menu-active,
#app-sidebar .bao-nav-item[aria-current="page"] {
      background: color-mix(in oklab, var(--accent-primary) 20%, var(--bg-panel));
      box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent-primary) 28%, transparent);
      font-weight: 600;
    }
    /*
     * Permanent-rail band: smooth label/icon rescale when collapse toggles.
     * Expanded labels fade/max-width;collapsed icons recenter with a subtle
     * scale cue (design.pdf §8.6 — dock yields to rail at 80rem).
     */
    #app-sidebar .bao-nav-item [data-sidebar-expanded-only],
#app-sidebar .menu-title {
      transition-property: opacity, max-width, margin-inline-start;
      transition-duration: var(--duration-normal, 200ms);
      transition-timing-function: var(--easing-standard);
    }#app-sidebar .bao-nav-item svg,
#app-sidebar .bao-nav-item [aria-hidden="true"] svg {
      transition: transform var(--duration-fast, 120ms) var(--easing-standard);
      transform-origin: center;
    }.bao-sidebar-collapsed .bao-nav-item svg,
#app-sidebar.bao-sidebar-collapsed .bao-nav-item svg {
      transform: scale(1.08);
    }
  }
  @media (min-width: 80rem) and (prefers-reduced-motion: reduce) {#app-sidebar .bao-nav-item [data-sidebar-expanded-only],
#app-sidebar .menu-title,
#app-sidebar .bao-nav-item svg {
      transition: none;
    }.bao-sidebar-collapsed .bao-nav-item svg,
#app-sidebar.bao-sidebar-collapsed .bao-nav-item svg {
      transform: none;
    }
  }
  /* Phone: header-anchored customizer becomes a pinned bottom sheet
     (source popover is display:contents; runtime host is
     [data-dashboard-customizer-dropdown] next to the edit toggle). */
  @media (max-width: 47.99rem) {
    [data-dashboard-customizer-dropdown].dropdown-open {
      position: fixed;
      inset-inline: 0;
      inset-block-end: calc(var(--dock-height, 4rem) + var(--dock-safe-area-offset, 0px) + env(safe-area-inset-bottom, 0px));
      inset-block-start: auto;
      display: block;
      z-index: var(--z-shell-topbar, 30);
    }[data-dashboard-customizer-dropdown].dropdown-open > [data-dashboard-customizer-content],
[data-dashboard-customizer-popover] .dropdown-content,
[data-dashboard-customizer-content] {
      position: static;
      min-width: 0;
      border-radius: var(--radius-box, 1rem) var(--radius-box, 1rem) 0 0;
      border-block-end: none;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior-y: contain;
    }
  }
/*
 * Editor height ownership and empty-stage floor. Column bands live in
 * tokens-layout-editor-workbench-grid (named `editor-surface`).
 * Query the workbench, not an unnamed container and not the viewport:
 * a pinned session rail can leave a desktop window with a tablet-width grid.
 */
/*
 * The empty stage fills its column at every band. A 48vh cap used to leave
 * the placeholder floating over an ~850px void on a 768x1024 tablet and a
 * 174px void on a phone; the grid row (`minmax(0, 1fr)`) already bounds the
 * column, so the cap only ever subtracted from the stage.
 */
[data-session-panel="annotate"] > [data-dashboard-layout-root][data-layout-page="editor"] {
  display: flex;
  min-block-size: 0;
  max-block-size: 100%;
  flex: 1 1 0%;
  flex-direction: column;
  overflow: hidden;
}
[data-dashboard-layout-root][data-layout-page="editor"] > [data-editor-chrome] {
  min-block-size: 0;
  max-block-size: 100%;
  flex: 1 1 0%;
}

.bao-editor-workbench .bao-editor-canvas[data-state="empty"] {
  min-block-size: 0;
  max-block-size: 100%;
  flex: 1 1 auto;
}
[data-editor-inspector-disclosure] > summary {
  flex: none;
  min-block-size: var(--touch-target-min, 2.75rem);
  align-items: center;
}

@container editor-workbench (max-width: 48rem) {
  .bao-editor-workbench {
    position: relative;
    overflow: hidden;
  }
  .bao-editor-workbench > [data-editor-stage] > .bao-editor-canvas-column,
  [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column {
    order: 0;
    inline-size: 100%;
    min-inline-size: 100%;
    min-block-size: 0;
    block-size: 100%;
    max-block-size: 100%;
    flex: 1 1 auto;
  }
  [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-3d-viewport:not([data-editor-3d-collapsed="true"]) {
    min-block-size: 0;
    block-size: 100%;
    max-block-size: 100%;
    flex: 1 1 auto;
  }
  [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column[data-editor-3d-collapsed="true"] {
    min-block-size: 0;
    block-size: auto;
    max-block-size: none;
    flex: 0 0 auto;
  }
  .bao-editor-workbench > [data-editor-canvas-overlays] {
    display: none;
  }
  .bao-editor-workbench > [data-editor-canvas-overlays][data-inspector-expanded="true"] {
    position: absolute;
    inset-inline: 0;
    /* The runtime stamps the measured height of everything the workbench places
       BELOW the stage — the command bar, plus the tool rail when the band lays
       it down under the stage rather than beside it. Measuring the bar alone
       left this sheet 54px short in the compact band and it covered the whole
       tool rail. The calc defaults cover first paint before the stamp lands. */
    inset-block-end: calc(var(--editor-stage-chrome-block-size, 8rem) + var(--editor-stage-chrome-block-size-extra, var(--space-micro, 0.25rem)));
    z-index: var(--z-modal, 1300);
    display: flex;
    inline-size: 100%;
    min-inline-size: 100%;
    max-block-size: var(--editor-compact-sheet-max-block, min(32dvh, 14rem));
    block-size: auto;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    border: 1px solid var(--liquid-border-regular);
    border-radius: var(--radius-box, 1rem) var(--radius-box, 1rem) 0 0;
    background: color-mix(in oklab, var(--bg-panel) calc(var(--liquid-fill-transient) * 100%), transparent);
    -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
    backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
    box-shadow: var(--shadow-glass-floating), var(--shadow-glass-inner);
    padding: var(--space-micro, 0.25rem);
  }
  .bao-editor-workbench > [data-editor-canvas-overlays][data-inspector-expanded="true"] > details,
  .bao-editor-workbench > [data-editor-canvas-overlays][data-inspector-expanded="true"] > [data-editor-filmstrip-disclosure] {
    flex: 0 0 auto;
  }
  [data-editor-filmstrip-list] a {
    scroll-margin-block-end: calc(var(--editor-stage-chrome-block-size, 8rem) + var(--editor-stage-chrome-block-size-extra, var(--space-micro, 0.25rem)));
  }
}




  /*
   * Dimension view modes (annotation-session / voice set_editor_dimension):
   * 2d focus hides the 3D column; 3d focus hides the 2D canvas column; dual
   * keeps both (default). Driven by data-editor-view-mode on chrome host.
   */
  [data-editor-chrome][data-editor-view-mode="2d"] [data-editor-stage] > .bao-editor-3d-column {
    display: none;
  }/*
   * 3D focus may hide the 2D stage ONLY when the 3D column can actually
   * paint. tokens-layout-editor-surfaces hides .bao-editor-3d-column while
   * chrome is not [data-annotation-ready="true"] (no image on the stage),
so
   * an ungated hide here put BOTH columns at display:none and the workbench
   * rendered zero visible children — measured live on /capture,
visible
   * workbench children went 1 -> 0 the moment view-mode="3d" was stamped with
   * no image loaded. That is the "3D geometry is blank" report.
   *
   * mount-lifecycle stamps annotationReady="true" exactly when the canvas
   * host reaches state "success",
so this gate already excludes the
   * empty/error canvas states without needing :has().
   */
  /*
   * The annotation-ready guard is no longer what keeps both columns from
   * hiding at once: the 3D column is now exempt from its own hide whenever
   * view-mode is "3d", so there is always a stage to show. Requiring readiness
   * here is what left the toggle inert on an empty editor.
   * The 3D-focus canvas hide is owned by tokens-layout-editor-stage-structure
   * (same cascade layer + later position than the column flex contract).
   */
  /*
   * Dimension-scoped tool rails: the 2D rail floats over the stage and stayed
   * visible (and enabled) in 3D view, where its drawing tools are inert — the
   * phone visual gate measured Rectangle/Ellipse/Freehand clicks with zero
   * effect. Hide the off-dimension rail so gated controls are never presented
   * as live. 3D tools only exist while the 3D pose owns the stage.
   */
  [data-editor-chrome][data-editor-view-mode="3d"] [data-editor-tool-rail][data-editor-tool-dimension="2d"] {
    display: none;
  }
  [data-editor-chrome]:not([data-editor-view-mode="3d"]) [data-editor-tool-rail][data-editor-tool-dimension="3d"] {
    display: none;
  }
  [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-3d-column {
    flex: 1 1 100%;
    min-width: 100%;
  }/*
   * Desktop dual-pane only: when the inspector is actually live (annotation-ready
   * + side-by-side >64rem),
provider/model chips leave the status strip to avoid
   * dual chrome. Idle empty stage keeps chips so model provenance stays visible
   * while the AI review form is zero-geometry (pointer-events/hidden until ready).
   * Phone + tablet stack the inspector (≤64rem) — always keep chips in the HUD.
   */
  @media (min-width: 64.01rem) {[data-page-type="media"] [data-editor-chrome][data-annotation-ready="true"] [data-editor-status="ai-provider"],
[data-page-type="media"] [data-editor-chrome][data-annotation-ready="true"] [data-editor-status="ai-model"] {
      display: none;
    }
  }

  /* 3D column mirrors the canvas column flex contract in the workbench. */
  .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column {
    display: flex;
    min-width: min(100%, var(--editor-3d-basis, 28rem));
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    flex: 1 1 var(--editor-3d-basis, 28rem);
    flex-direction: column;
    gap: var(--space-inline);
  }/* Viewport region mirrors the 2D canvas geometry: bounded,
clipped,
full-height inside its column. Glass stays on the chrome only. */
  .bao-editor-3d-viewport {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-width: 100%;
    min-height: max(min(24rem, var(--editor-canvas-height, clamp(26rem, 58vh, 44rem))), min(18rem, 50vh));
    max-height: 100%;
    flex: 1 1 auto;
    height: 100%;
    /* The scene is not a scroll container. Focus may otherwise scroll this
       clipped ancestor and shift the entire header; the media toolbar owns scrolling. */
    overflow: clip;
  }
  /* Stage chrome sticky-in-flow — clear glass media controls over the scene. */
  .bao-editor-3d-viewport > [data-editor-3d-stage-header] {
    position: sticky;
    top: 0;
    z-index: var(--z-canvas-overlay, 30);
    flex: 0 0 auto;
    box-sizing: border-box;
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: calc(100% - 2 * var(--space-section, 0.75rem));
    align-self: stretch;
  }
  .bao-editor-3d-viewport > [data-editor-3d-stage-header] .btn {
    flex-shrink: 0;
  }
  /* The WebGL canvas fills the region; OrbitControls owns its gestures. */
  .bao-editor-3d-viewport > canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    touch-action: none;
    cursor: grab;
  }
  .bao-editor-3d-viewport > canvas:active {
    cursor: grabbing;
  }
  /* Keyboard focus ring on the runtime canvas (tabindex 0 via focus-contract). */
  .bao-editor-3d-viewport > canvas:focus-visible {
    outline: var(--focus-ring-width, 2px) solid var(--color-primary, var(--accent-primary, currentColor));
    outline-offset: var(--focus-ring-offset-tight, -2px);
  }/* State machine: exactly one state block visible at a time. */
  .bao-editor-3d-viewport [data-editor-3d-loading],
.bao-editor-3d-viewport [data-editor-3d-empty],
.bao-editor-3d-viewport [data-editor-3d-error] {
    display: none;
  }.bao-editor-3d-viewport[data-state="loading"] [data-editor-3d-loading],
.bao-editor-3d-viewport[data-state="empty"] [data-editor-3d-empty],
.bao-editor-3d-viewport[data-state="error"] [data-editor-3d-error] {
    display: flex;
    z-index: var(--z-canvas-empty, 20);
    pointer-events: auto;
  }/* Media controls only make sense on a live scene. */
  .bao-editor-3d-viewport[data-state="loading"] [data-editor-3d-controls],
.bao-editor-3d-viewport[data-state="empty"] [data-editor-3d-controls],
.bao-editor-3d-viewport[data-state="error"] [data-editor-3d-controls] {
    display: none;
  }
  /* Collapse toggle exists only for the compact sheet transformation. */
  [data-editor-3d-toggle] {
    display: none;
  }/*
   * Narrow / compact: the workbench stacks (canvas first,
3D sheet second,
* inspector third) and scrolls instead of clipping the columns.
   */
  @media (max-width: 63.99rem) {
    .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column {
      order: 1;
      width: 100%;
      min-width: 100%;
      height: auto;
      max-height: none;
      flex: 0 1 auto;
    }

    [data-editor-chrome]:not([data-editor-view-mode="3d"]) .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column .bao-editor-3d-viewport {
      min-height: min(48vh, 30rem);
      max-height: min(60vh, 36rem);
      flex: 0 0 auto;
    }
  }
  /*
   * Compact sheet: a 44px toggle collapses the 3D sheet to its stage
   * header so the 2D canvas keeps the phone stage (HIG sheet pattern).
   */
  @container (max-width: 47.99rem) {
    [data-editor-chrome][data-editor-view-mode="dual"] [data-editor-3d-toggle] {
      display: inline-flex;
    }
    .bao-editor-3d-viewport[data-editor-3d-collapsed="true"] {
      min-height: 0;
      max-height: none;
      height: auto;
      flex: 0 0 auto;
    }.bao-editor-3d-viewport[data-editor-3d-collapsed="true"] > canvas,
.bao-editor-3d-viewport[data-editor-3d-collapsed="true"] > [data-editor-3d-controls] {
      display: none;
    }
    .bao-editor-3d-viewport[data-editor-3d-collapsed="true"] [data-editor-3d-toggle] svg {
      transform: rotate(180deg);
    }
    [data-editor-3d-toggle] svg {
      transition: transform var(--duration-normal, 200ms) var(--easing-standard, ease);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-editor-3d-viewport [data-editor-3d-toggle] svg {
      transition: none;
    }
  }


  @media (max-width: 47.99rem) {
    /* Stage label sr-only is owned by tokens-layout-editor-phone-dock-compact. */
    .bao-editor-3d-stage-actions {
      flex: 1 1 auto;
      justify-content: end;
      gap: var(--space-micro, 0.25rem);
      overflow-x: auto;
      overscroll-behavior-x: contain;
      touch-action: pan-x;
    }
    .bao-editor-3d-stage-actions [data-editor-3d-calibration] {
      flex: 0 0 auto;
      white-space: nowrap;
    }
    [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column[data-editor-3d-collapsed="true"] {
      min-block-size: 0;
      block-size: auto;
      max-block-size: none;
      flex: 0 0 auto;
      align-self: start;
    }
    [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column .bao-editor-3d-viewport:not([data-editor-3d-collapsed="true"]) {
      min-height: 0;
      height: 100%;
      max-height: 100%;
      flex: 1 1 auto;
    }
  }


  @media (max-width: 47.99rem) {
    [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-canvas-overlays]:not([data-inspector-expanded="true"]) {
      inset-block-end: calc(
        anchor(top, var(--editor-inspector-clearance))
        + var(--touch-target-min, 2.75rem)
        + var(--space-inline, 0.5rem)
      );
    }
  }



  /* HD-aligned home skeleton tracks (dashboard-like density). */
  .bao-skeleton-home-narrow {
    min-height: 7rem;
    width: 100%;
    border-radius: var(--radius-box, 1rem);
  }
  .bao-skeleton-home-wide {
    min-height: 12rem;
    width: 100%;
    border-radius: var(--radius-box, 1rem);
  }



  /**
   * Canvas region min-height from layoutSize tokens (no arbitrary min-h-*).
   */
  .bao-canvas-region {
    min-height: var(--canvas-region-min-height, clamp(18rem, 42vh, 30rem));
  }
  /**
   * Fabric stage SSOT — layout via CSS custom properties, never Fabric
   * inline width/height/position.
   */
  .bao-fabric-stage {
    position: relative;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    max-width: 100%;
    overflow: hidden;
    touch-action: none;
    user-select: none;
  }
  /**
   * Dynamic host bound published by canvas island resolveCanvasSize —
   * custom property only (smoke bans layout inline styles).
   */
  .bao-editor-canvas-host-bounded {
    min-height: min(18rem, 50vh);
    max-height: var(--editor-canvas-host-max-height, 100%);
    overflow: hidden;
  }
  /* A flat document toolbar shares the stage edge; only the floating palette
   * needs a separate glass surface. The bounded scrollport keeps enlarged
   * controls reachable without taking their hit areas below the token floor. */
  [data-editor-canvas-region] > [data-editor-stage-header] {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    margin: 0;
    min-inline-size: 0;
    max-block-size: calc(100% - 2 * var(--space-section));
    overflow: auto;
    overscroll-behavior: contain;
  }
  /* Identity and clinical readouts form one wrapping group. Intrinsic content
   * determines whether they share a line, rather than a forced subtitle row. */
  [data-editor-stage-toolbar] > [data-toolbar-zone="center"] {
    display: flex;
    flex: 1 1 0%;
    flex-wrap: wrap;
    align-items: center;
    align-content: center;
    gap: var(--space-micro) var(--space-inline);
    min-inline-size: 0;
    overflow: visible;
    padding-inline: var(--space-micro);
  }
  /* Phone identity needs the full line to stay legible at Dynamic Type sizes. */
  @container editor-stage (max-width: 32rem) {
    [data-editor-stage-toolbar] > [data-toolbar-zone="center"] {
      flex-basis: 100%;
      order: -1;
    }
  }
  [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-stage-title] {
    margin-inline-end: var(--space-inline, 0.5rem);
    white-space: nowrap;
    font-size: var(--text-sm, 0.875rem);
    color: var(--color-base-content);
  }
  [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--space-inline, 0.5rem);
    min-width: 0;
    overflow: hidden;
  }
  [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-status-bar] {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--space-micro, 0.25rem);
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    color: color-mix(in oklab, var(--color-base-content) 70%, transparent);
  }
  /* Identity reads as title text, not as a row of bordered chips. */
  /*
   * Chip-stripping (border, padding, radius, background) is NOT restated here.
   * tokens-layout-editor-hig-accessibility emits the same four resets later, on
   * a strict-child selector, and every badge that reaches this rule also matches
   * that one: `data-editor-context-bar` appears in exactly two partials —
   * editor and capture — and in both the bar is a direct child of the center
   * toolbar zone under the editor chrome, with the badge its direct child. Declaring them twice made one decision look like two.
   * This rule keeps only what nothing else answers: the title-text ramp and the
   * ellipsis chain.
   */
  [data-editor-stage-toolbar] [data-editor-context-bar] .bao-context-badge {
    flex: 0 1 auto;
    min-width: 0;
    font-size: var(--text-sm, 0.875rem);
    color: var(--color-base-content);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  [data-editor-stage-toolbar] [data-editor-context-bar] .bao-context-badge > strong {
    display: inline;
    font-weight: 600;
  }
  [data-editor-stage-toolbar] [data-editor-context-bar] .bao-context-badge > .status {
    display: none;
  }
  /* Subtitle readouts: middle-dot separated, tone dots only for attention states. */
  [data-editor-stage-toolbar] [data-editor-status-bar] > [data-editor-status] {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  [data-editor-stage-toolbar] [data-editor-status-bar] > [data-editor-status] > .status:not(.status-warning):not(.status-error) {
    display: none;
  }
  [data-editor-stage-toolbar] [data-editor-status-bar] > [data-editor-status] + [data-editor-status]::before {
    content: "·";
    margin-inline-end: var(--space-micro, 0.25rem);
  }
  [data-editor-status-bar] {
    min-width: 0;
    flex-wrap: wrap;
    overflow-x: visible;
  }
  [data-editor-canvas-region] > .canvas-container {
    flex: 1 1 auto;
    min-block-size: min(18rem, 50vh);
    width: 100%;
  }
  /**
   * Dropdown escape hatch — fixed to viewport when open so overflow table
   * ancestors cannot clip menus. Geometry vars published by app-shell-dropdowns
   * via adopted CSS host vars (no element style=).
   * The compound selector must out-rank the later-emitted
   * ".bao-dropdown-panel { position: absolute }" glass-chrome reset — a bare
   * class ties on specificity and loses on source order (clipped 44px menus).
   */
  /* Repeated class = (0,2,0): hosts without .bao-dropdown-panel (runtime
     customizer tray) must still out-rank daisyUI's .dropdown .dropdown-content
     position:absolute, which ties a bare class and wins on order. */
  .bao-dropdown-panel-viewport.bao-dropdown-panel-viewport,
  .bao-dropdown-panel.bao-dropdown-panel-viewport {
    position: fixed;
    /* One anchoring owner in the escaped state. The panel also carries daisyUI's
       .dropdown placement classes, which anchor it with position-area; this
       state anchors it with the measured --bao-dropdown-* insets published by
       app-shell-dropdowns-geometry. Both applied at once and the two offsets
       summed: measured on the chat composer at 824px, a trigger at x=501 with a
       published left of 493px put the open menu at 994px — wholly off-screen,
       so clicking the composer's More button appeared to do nothing at all.
       Neutralising position-area here leaves the measured insets as the single
       owner; panels that already resolved correctly do not move. */
    position-area: none;
    z-index: var(--z-dropdown, 1000);
    max-height: var(--bao-dropdown-max-height, 50vh);
    /* Paired with the two-sided inset clamp in app-shell-dropdowns-geometry:
       the clamp keeps both edges on screen only while the panel fits, so a
       panel wider than the phone is narrowed here rather than clipped. */
    max-width: var(--bao-dropdown-max-width, calc(100vw - 1rem));
    overflow-y: auto;
    top: var(--bao-dropdown-top, auto);
    bottom: var(--bao-dropdown-bottom, auto);
    left: var(--bao-dropdown-left, auto);
    right: var(--bao-dropdown-right, auto);
    /* Popover top layer: neutralize the UA's inset:0 + margin:auto centering
       so the trigger-anchored geometry vars above stay authoritative. */
    margin: 0;
  }
  .bao-fabric-stage > .canvas-container {
    position: relative;
    box-sizing: border-box;
    width: var(--bao-canvas-css-width, 100%);
    height: var(--bao-canvas-css-height, 100%);
    max-width: 100%;
    touch-action: none;
    user-select: none;
  }
  .bao-fabric-stage .lower-canvas,
  .bao-fabric-stage .upper-canvas {
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 0;
    left: 0;
    top: 0;
    box-sizing: border-box;
    width: var(--bao-canvas-css-width, 100%);
    height: var(--bao-canvas-css-height, 100%);
    max-width: 100%;
    touch-action: none;
    user-select: none;
  }
  .bao-fabric-stage .upper-canvas {
    z-index: var(--z-local, 1);
  }
  [data-sidebar-rail][data-expanded="true"] [data-sidebar-collapsed-only][hidden],
  [data-sidebar-rail][data-expanded="false"] [data-sidebar-expanded-only][hidden],
  [data-session-rail][data-expanded="true"] [data-session-rail-collapsed-only][hidden],
  [data-session-rail][data-expanded="false"] [data-session-rail-expanded-only][hidden] {
    display: none;
  }
  /* Session-rail width: tokens-layout-session-rail-resize (user-width clamp). */
  /*
   * DaisyUI collapse peer radios/checkboxes default to a ~13px hit box.
   * Stretch the peer across the full collapse row so FAQ / disclosure rows
   * meet HIG 44px (structure audit small-target gate).
   */
  .collapse > input[type="radio"],
  .collapse > input[type="checkbox"] {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: 100%;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
    z-index: var(--z-local, 1);
  }
  .bao-modal-box-shell {
    max-height: min(var(--modal-max-height), var(--vv-height, 100dvh));
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
    min-height: 0;
  }
  /*
   * Dialog chrome wash — translucent over elevated glass shell (not a second
   * glass sheet; solid only when reduced-transparency).
   */
  .bao-dialog-chrome {
    background: color-mix(in oklab, var(--color-base-100, var(--bg-panel)) 78%, transparent);
  }
  html[data-reduced-transparency="true"] .bao-dialog-chrome,
  [data-reduced-transparency="true"] .bao-dialog-chrome {
    background: var(--color-base-100, var(--bg-panel));
  }
  @media (prefers-reduced-transparency: reduce) {
    .bao-dialog-chrome {
      background: var(--color-base-100, var(--bg-panel));
    }
  }
  /*
   * Dialog footer safe-area pad — max(section token, device inset).
   * Replaces ad-hoc pb-[max(...)] and pairs with solid footer fill.
   * Button/badge chrome uses Button.bao / Badge.bao tokens only
   * (no local bao-btn-ghost-xs / bao-badge-soft substitutes).
   */
  .bao-dialog-footer-safe {
    padding-bottom: max(
      var(--space-section, 0.75rem),
      env(safe-area-inset-bottom, 0px)
    );
  }
  /* Compact status row host for integration / MCP list items. */
  .bao-data-row {
    min-width: 0;
  }
  .bao-modal-scroll { max-height: var(--modal-scroll-max-height); overflow-y: auto; }
  .bao-modal-body-scroll {
    max-height: min(
      var(--modal-body-max-height),
      calc(var(--vv-height, 100dvh) * 0.55)
    );
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    min-height: 0;
    flex: 1 1 0%;
  }
  /* One progress owner: explicit markers keep connectors clear of every circle. */
  .bao-dialog-steps {
    --dialog-step-marker-size: var(--control-height-sm, 2rem);
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    inline-size: 100%;
    flex-shrink: 0;
    max-inline-size: 100%;
    margin: 0;
    padding: var(--space-inline) var(--space-section);
    list-style: none;
    font-size: var(--text-xs, 0.75rem);
    line-height: 1.4;
  }
  .bao-dialog-step {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--dialog-step-marker-size) auto;
    justify-items: center;
    align-content: start;
    row-gap: var(--space-inline);
    min-inline-size: 0;
    position: relative;
    text-align: center;
    --step-bg: var(--color-base-300);
    --step-fg: var(--color-base-content);
  }
  .bao-dialog-step + .bao-dialog-step::before {
    content: "";
    position: absolute;
    inset-inline-start: calc(-50% + var(--dialog-step-marker-size) / 2);
    inset-block-start: calc(var(--dialog-step-marker-size) / 2);
    inline-size: calc(100% - var(--dialog-step-marker-size));
    block-size: var(--border-width, 1px);
    background: var(--step-bg);
  }
  .bao-dialog-step-marker {
    display: grid;
    place-items: center;
    inline-size: var(--dialog-step-marker-size);
    block-size: var(--dialog-step-marker-size);
    border-radius: var(--radius-full);
    background: var(--step-bg);
    color: var(--step-fg);
    font-weight: 600;
    line-height: 1;
  }
  .bao-dialog-step[data-case-create-reached] {
    --step-bg: var(--color-primary);
    --step-fg: var(--color-primary-content);
  }
  .bao-dialog-step-label {
    max-inline-size: 100%;
    padding-inline: var(--space-micro);
    white-space: normal;
    overflow-wrap: anywhere;
    color: var(--color-base-content);
  }
  .bao-dialog-step[aria-current="step"] .bao-dialog-step-label {
    font-weight: 600;
  }
  /*
   * Bottom-sheet dialogs (phone). daisyUI .modal is display:grid +
   * justify-items:center — justify-content does NOT stretch the box. Use
   * justify-items + align-items (grid axes) so the sheet pins full-width to
   * the bottom instead of a floating middle slab.
   *
   * Gated to <40rem to match the `sm:` breakpoint the markup switches on. At
   * 47.99rem this band contradicted the markup across a 128px window: every
   * sheet carries sm:modal-middle, sm:modal-end or sm:hidden, so between
   * 40rem and 47.99rem the dialog was pinned bottom and stretched while the
   * box stayed a centered capped slab with its bottom corners squared off —
   * measured at 700px, border-top radius 16px against bottom radius 0.
   *
   * The box's width, max-width and max-height are deliberately NOT set here.
   * They belong to the unlayered .modal-box rule in
   * tokens-glass-containment.css: this module emits inside @layer utilities,
   * so declaring them here lost the cascade silently and left every sheet
   * capped at 100vw - 1rem regardless of what this band asked for.
   */
  @media (max-width: 39.99rem) {
    dialog.modal.modal-bottom[open],
    dialog.modal.modal-bottom.modal-open {
      align-items: end;
      justify-items: stretch;
      justify-content: stretch;
      margin: 0;
      padding: 0;
      /* Keep sheet inside the visual viewport (keyboard / browser chrome). */
      max-height: var(--vv-height, 100dvh);
    }
    dialog.modal.modal-bottom[open] .modal-box,
    dialog.modal.modal-bottom[open] .bao-modal-box-shell,
    dialog.modal.modal-bottom.modal-open .modal-box,
    dialog.modal.modal-bottom.modal-open .bao-modal-box-shell {
      margin: 0;
      border-bottom-left-radius: 0;
      border-bottom-right-radius: 0;
    }
    /*
     * Bottom-sheet scroll bodies (keyboard-shortcuts sheet, create flow)
     * end flush at the sheet edge — pad the scroll end past the
     * home-indicator safe area (Apple HIG), same token family as
     * .bao-dialog-footer-safe. Scoped to the pinned bottom-sheet band so
     * centered modals keep flush scroll geometry.
     */
    dialog.modal.modal-bottom .bao-modal-body-scroll {
      padding-block-end: calc(var(--space-section, 0.75rem) + env(safe-area-inset-bottom, 0px));
    }
  }
  /*
   * Dense create flow: description is redundant with step labels on phone.
   * Stays on the ≤47.99rem densify gate alongside the step-label clip above —
   * this is a copy-density concern, not bottom-sheet geometry, so it does not
   * follow that band down to <40rem.
   */
  @media (max-width: 47.99rem) {
    [data-case-create-dialog] [data-dialog-description] {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
  }
  /* Discrete width changes only — no layout-property animation (design §7.4). */
  .bao-motion-width {
    transition-property: none;
  }
  .bao-session-mode-tab {
    transition-property: color, background-color, border-color, opacity;
    transition-duration: var(--duration-normal, 200ms);
    transition-timing-function: var(--easing-standard);
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-motion-width, .bao-session-mode-tab {
    }
  }
  .bao-context-badge[data-context-status="unset"] {
    color: var(--color-base-content, currentColor);
    opacity: var(--opacity-medium, 0.75);
  }
  .bao-context-badge[data-context-status="set"] {
    border-color: color-mix(in oklab, var(--color-success) 50%, transparent);
  }
  .bao-context-badge[data-context-status="warning"] {
    border-color: color-mix(in oklab, var(--color-warning) 50%, transparent);
  }
  .bao-context-badge[data-context-status="error"] {
    border-color: color-mix(in oklab, var(--color-error) 50%, transparent);
  }
  /* Context cards share the badge's attribute-keyed status wash; the
     data-[context-status]:border-status-*/50 utility form never compiles
     (status-* is not a Tailwind theme color) — this block is the owner. */
  .bao-context-card[data-context-status="set"] {
    border-color: color-mix(in oklab, var(--color-success) 50%, transparent);
  }
  .bao-context-card[data-context-status="warning"] {
    border-color: color-mix(in oklab, var(--color-warning) 50%, transparent);
  }
  .bao-context-card[data-context-status="error"] {
    border-color: color-mix(in oklab, var(--color-error) 50%, transparent);
  }
  /* Session-rail tab strips only — the capture mode switcher has ONE owner
     (tokens-glass-shell-capture) and must not inherit this soft wash. */
  .bao-session-tab[aria-selected="true"]:not(.bao-session-mode-tab) {
    color: var(--color-primary);
    background-color: color-mix(in oklab, var(--color-primary) 16%, transparent);
    border-bottom-color: var(--color-primary);
    font-weight: 600;
  }


/* Native hr/button chrome off — grip is a pure APG separator host. */
.bao-session-rail-resizer {
  margin: 0;
  border: 0;
  padding: 0;
  background: transparent;
  appearance: none;
  color: inherit;
  font: inherit;
}

/* Session-rail width + resize grip — sole owner of expanded/collapsed width.
 * Drag/keyboard publish --session-rail-user-width via setHostCssVars.
 * position:relative on the rail is required: the grip is position:absolute and
 * the content row (.relative overflow-x-clip) must NOT be the containing block
 * or inset-inline-start pins to the main column, not the rail edge.
 *
 * HIG Dynamic Type: at large root font the rail's rem-based width can push the
 * right edge past the viewport because flex-shrink:0 held the track open. The
 * existing clamp(min, user, max, 100cqi-main) on .bao-session-rail-expanded
 * already caps the upper bound, but a stuck shrink-0 on the rail itself
 * defeated the clamp and let the row overflow. Allow the rail to yield
 * space so the row stays inside the visible viewport; the collapsed floor in
 * .bao-session-rail-collapsed keeps the expand toggle inside the rail.
 */
[data-session-rail] {
  position: relative;
  flex-shrink: 1;
  min-width: 0;
  /* HIG Dynamic Type: font-invariant cap so the rem-based clamp (12rem min,
     32rem max) cannot grow 4x at 400% root font and push the rail past the
     viewport. 40vw ≈ --session-rail-max at 1440px and holds at 200%/400%. */
  max-inline-size: 40vw;
}

.bao-session-rail-expanded {
  inline-size: clamp(
    var(--session-rail-min),
    var(--session-rail-user-width, var(--session-rail-expanded)),
    min(
      var(--session-rail-max),
      calc(
        100cqi - var(--sidebar-expanded) - var(--session-rail-main-min)
      )
    )
  );
}

/*
 * Derived from what the rail must contain, not a magic width that almost fits.
 *
 * The collapsed rail holds exactly one control — the expand toggle — and that
 * control carries the HIG touch floor (--touch-target-min) inside the rail
 * header's inline padding (--space-section, from Rail.bao `header`). The old
 * flat --session-rail-collapsed of 3rem left 4px for a 44px target sitting in
 * 12px of padding, so the button overflowed its own rail: measured on /cases
 * at 1440x900, the control ran x=1406..1450 inside a rail ending at 1440, and
 * 10px of a primary control sat outside the viewport entirely.
 *
 * max() keeps the token as the floor, so a future wider --session-rail-collapsed
 * still wins and this never silently shrinks the rail.
 */
.bao-session-rail-collapsed {
  inline-size: max(
    var(--session-rail-collapsed),
    calc(var(--touch-target-min, 2.75rem) + 2 * var(--space-section, 0.75rem))
  );
}

.bao-session-rail-resizer {
  display: none;
}

@media (min-width: 80rem) {
  [data-session-rail][data-expanded="true"] .bao-session-rail-resizer {
    position: absolute;
    z-index: var(--z-content-overlay, 10);
    inset-block: 0;
    /* Center the grip on the rail's inline-start edge (into main by half). */
    inset-inline-start: 0;
    display: block;
    /* The splitter is an <hr>, and the UA sheet gives hr an explicit
       block-size: 0. An explicit size beats inset-block stretching, so the
       grip computed 44x0 — full width, no hit area at all. auto restores the
       stretch that inset-block: 0 is meant to produce. */
    block-size: auto;
    /*
     * One --space-section-wide strip centered on the boundary (±half into each
     * pane), matched to the rail header's own inline padding so the strip can
     * never reach a control: measured on /capture and /editor at desktop, a
     * touch-target-min-wide strip reached 10px INTO the header's first button
     * and the case picker's summary — elementFromPoint gave those pixels to
     * the grip, so a finger aiming at the control's left half dragged the
     * splitter instead (browser-structure-audit hig-touch-target findings).
     * Splitter drag stays a full-height strip with col-resize cursor plus the
     * keyboard path (focusable window-splitter pattern in
     * app-shell-rails-resize.ts); VS Code-class splitters use the same few-
     * pixel band. The visual pill is ::before, unchanged.
     */
    inline-size: var(--space-section, 0.75rem);
    margin-inline-start: calc(-0.5 * var(--space-section, 0.75rem));
    padding: 0;
    border: 0;
    background: transparent;
    cursor: col-resize;
    touch-action: none;
    pointer-events: auto;
    /* Drag must not select rail or canvas text mid-gesture. */
    user-select: none;
    -webkit-user-select: none;
    appearance: none;
    outline-offset: calc(-1 * var(--space-2xs, 0.125rem));
  }

  [data-session-rail][data-expanded="true"] .bao-session-rail-resizer::before {
    position: absolute;
    inset-inline-start: 50%;
    inset-block: calc(var(--space-section, 0.75rem) * 2);
    inline-size: var(--space-2xs, 0.125rem);
    border-radius: var(--radius-full);
    background: color-mix(in oklab, var(--color-base-content) 28%, transparent);
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--color-base-100) 72%, transparent);
    content: "";
    transform: translateX(-50%);
    pointer-events: none;
  }

  [data-session-rail][data-expanded="true"] .bao-session-rail-resizer:hover::before,
  [data-session-rail][data-expanded="true"] .bao-session-rail-resizer:focus-visible::before,
  [data-session-rail][data-session-rail-resizing="true"] .bao-session-rail-resizer::before {
    inline-size: var(--space-micro, 0.25rem);
    background: var(--color-primary);
  }

  [data-session-rail][data-expanded="true"] .bao-session-rail-resizer:focus-visible {
    outline: var(--space-2xs, 0.125rem) solid var(--color-primary);
  }
}

/* Native table structure at every width: headers name cells, so mobile
 * label copies remain hidden and value wrappers do not create new boxes. */
.bao-table-responsive-cell > .bao-table-mobile-label {
  display: none;
}
.bao-table-responsive-cell > .bao-table-mobile-value:not(.bao-table-identity-value) {
  display: contents;
}

section[data-filtered-table],
[data-filtered-table] {
  container-type: inline-size;
  container-name: table;
}

/* The shared wrapper owns overflow, sticky-header bounds, and focus clearance.
 * The page never grows to the intrinsic width of its records. */
.bao-table-scroll-x {
  --scroll-edge-bg: var(--color-base-100, var(--bg-panel));
  max-height: var(--panel-scroll-max-height);
  overflow-y: auto;
  scroll-padding-block-start: var(--touch-target-min, 2.75rem);
  scroll-padding-inline-end: var(--touch-target-min, 2.75rem);
}

/* Existing persisted fit marks are cleared by the controller. */
.bao-table-column-fit-hidden {
  display: none;
}

/* When the table overflows, actions scroll with their record so they cannot
 * cover a data column. No column is discarded to accommodate the shelf. */
.bao-table-pinned-fit-unstuck .bao-table-pinned-action,
.bao-table-pinned-fit-unstuck .bao-table-pinned-divider {
  position: static;
}

/* Keep responsive action groups within the canonical row-action width. */
.bao-table-responsive-cell > .bao-table-mobile-value.bao-table-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-micro);
  max-inline-size: var(--table-row-actions-max-inline);
}
/*
 * Identity stacks (name + mono secondary): always start-align, even when
 * values contain multiple lines. This class owns the identity stack.
 */
.bao-table-identity-value {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: start;
}

/*
 * Empty rows in TABLE mode — keep table-row / table-cell so HTML colspan
 * spans every column. Forcing display:block here (previous bug) made the
 * empty cell shrink to the first column track (~88px tall brick).
 */
.bao-table-responsive-empty-cell,
.bao-table-empty-row > td,
.bao-table-responsive-row[data-state="empty"] > td,
tr[data-state="empty"].bao-table-responsive-row > td {
  border-width: 0;
  padding-inline: var(--space-inline);
  padding-block: var(--space-inline);
  box-sizing: border-box;
  text-align: center;
  vertical-align: middle;
}
.bao-table-empty-panel {
  display: flex;
  width: 100%;
  max-width: min(36rem, 100%);
  min-height: 6rem;
  margin-inline: auto;
  box-sizing: border-box;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-micro, 0.25rem);
  padding-inline: var(--space-section);
  padding-block: var(--space-inline, 0.5rem);
  text-align: center;
}

/*
 * Billing decision row chrome (S04/S20): solid surfaces + semantic border,
 * never opacity-* wash over row text (design.pdf §2.10).
 */
[data-billing-decision="accepted"] {
  border-color: var(--color-success, var(--status-success));
  background-color: color-mix(in oklab, var(--color-success, var(--status-success)) 8%, var(--color-base-100, var(--bg-panel)));
}
[data-billing-decision="rejected"] {
  border-color: var(--border-base, color-mix(in oklab, var(--color-base-content) 12%, transparent));
  background-color: var(--color-base-200, var(--bg-surface));
  color: var(--color-base-content);
}
[data-billing-decision="rejected"] .text-base-content,
[data-billing-decision="rejected"] td {
  color: inherit;
}

/*
 * Pagination join SSOT — phone content columns are ~20rem; five 44px
 * touch targets + labels already press the edge. Controls own max-width
 * + horizontal scroll so page numbers never paint past #app-main (HIG
 * dense toolbar; never stretch join-items with flex-1).
 */
[data-pagination] [data-api-registry-page-buttons],
[data-pagination] .join,
[data-primitive="Pagination"] .join {
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 39.99rem) {
  /* Phone: prev/current/next only — hide non-current page jumps to free
     the strip for 44px targets without horizontal thrash. */
  [data-pagination] button[data-api-registry-page-number]:not([aria-current="page"]),
  [data-primitive="Pagination"] button[data-api-registry-page-number]:not([aria-current="page"]) {
    display: none;
  }
  [data-pagination] .join > span[aria-hidden="true"],
  [data-primitive="Pagination"] .join > span[aria-hidden="true"] {
    display: none;
  }
}

/* Collection metadata and paging belong to the table plane, not striped record cards. */
.table > tbody > tr[data-table-utility-row] {
  background-color: transparent;
  box-shadow: none;
  grid-column: 1 / -1;
}


/* Identity/action cells share an inline primary row; secondary-only identities stay stacked. */
.bao-table-identity-actions {
  display: flex;
  text-align: start;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inline);
}
.bao-table-identity-actions > [data-table-row-actions] {
  flex: 0 0 auto;
  margin-inline-start: auto;
}



/*
 * Column resize (Table.bao resizeHandle/resizeColResizing tokens).
 * Each separator owns a reserved hit area inside its header. Extending it
 * across the boundary lets the neighboring sticky header cover keyboard focus.
 * Width travels
 * via --col-width host vars (css-host-vars.ts — never inline style=).
 * table-layout: fixed engages only after the first explicit width so
 * unsized tables keep the daisyUI auto layout.
 */
.bao-registry-table[data-table-resized] {
  table-layout: fixed;
}
.bao-registry-table[data-table-resized] > thead > tr > th[data-col-resizable] {
  width: var(--col-width, auto);
  min-width: var(--col-min, 4rem);
  max-width: var(--col-max, 32rem);
}
.bao-registry-table thead > tr > th:has(> .bao-table-resize-handle) {
  position: relative;
  padding-inline-end: calc(var(--touch-target-min) + var(--space-inline));
}
.bao-table-resize-handle {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: var(--touch-target-min);
  padding: 0;
  border: 0;
  background: transparent;
  cursor: col-resize;
  touch-action: none;
  z-index: var(--z-local, 1);
}
.bao-table-resize-handle::after {
  content: "";
  position: absolute;
  inset-block: 20%;
  inset-inline-start: 50%;
  width: 2px;
  translate: -50% 0;
  border-radius: var(--radius-selector, 0.25rem);
  background: color-mix(in oklab, var(--text-primary) 18%, transparent);
  transition: background var(--duration-fast, 120ms) var(--easing-standard);
}
.bao-table-resize-handle:hover::after,
.bao-table-resize-handle:focus-visible::after,
.bao-table-resize-handle.bao-table-resize-handle-active::after {
  background: var(--color-primary);
}
.bao-table-resize-handle:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--color-primary);
  outline-offset: var(--focus-ring-offset-tight, -2px);
}
@media (prefers-reduced-motion: reduce) {
  .bao-table-resize-handle::after {
    transition: none;
  }
}
/* Column boundary feedback while a drag/keyboard resize is in flight. */
.bao-registry-table thead > tr > th.bao-table-col-resizing {
  box-shadow: inset -2px 0 0 var(--color-primary);
}

/*
 * Multi-sort (Table.bao sortControl/sortIndicator/sortPrecedence tokens).
 * The control wraps the existing header copy (label = column name); the
 * indicator glyph and precedence badge are decorative siblings exposed via
 * aria-sort on the columnheader.
 */
.bao-table-sort-control {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro, 0.25rem);
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font: inherit;
  /*
   * Never inherit a muted thead / text-label colour — axe color-contrast on
   * the label span (button > span:nth-child(1)) failed /cases and dashboard
   * when inherit resolved to ~2.x:1 against the header plate.
   */
  color: var(--color-base-content);
  text-align: start;
  cursor: pointer;
  user-select: none;
}
/* Explicit label span: first child is the column name from decorateHeader. */
.bao-table-sort-control > span:first-child {
  color: var(--color-base-content);
  opacity: 1;
}
.bao-table-sort-control:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--color-primary);
  outline-offset: var(--focus-ring-offset, 2px);
  border-radius: var(--radius-selector, 0.25rem);
}
.bao-table-sort-indicator {
  flex: 0 0 auto;
  font-size: var(--text-2xs, 0.6875rem);
  line-height: 1;
  color: var(--color-primary);
}
th[aria-sort="none"] .bao-table-sort-indicator,
.bao-table-sort-control > span.bao-table-sort-indicator {
  /*
   * WCAG 1.4.11 non-text ≥3:1 vs base-100. Full base-content (not a muted
   * mix) so text-contrast rules that treat glyphs as text also pass.
   */
  color: var(--color-base-content);
}
.bao-table-sort-precedence {
  flex: 0 0 auto;
  min-width: 1em;
  padding-inline: var(--space-micro, 0.25rem);
  border-radius: var(--radius-selector, 0.25rem);
  background: var(--color-primary);
  color: var(--color-primary-content);
  font-size: var(--text-2xs, 0.6875rem);
  line-height: 1.4;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/*
 * End-aligned headers keep their sort control at the inline end. The
 * action header declares text-end; numeric columns are end-aligned by the
 * data-col-kind rule below. Without this the full-width control's
 * text-align:start pinned the label to the inline start while the body
 * numerals sat at the end (measured drift on /cases captures vs its cell).
 */
th.text-end > .bao-table-sort-control,
th[data-col-kind="numeric"] > .bao-table-sort-control {
  justify-content: flex-end;
}

/*
 * Inline edit (Table.bao inlineEditCell/inlineEditEditor/inlineEditEditing
 * tokens). The editor swaps into the cell, inherits cell typography, and
 * keeps focus rings inside the table density.
 */
td.bao-table-inline-edit-cell {
  cursor: default;
}
td.bao-table-inline-editing {
  padding-block: var(--space-micro, 0.25rem);
}
.bao-table-inline-edit-group {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  gap: var(--space-micro, 0.25rem);
}
.bao-table-inline-edit-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  min-inline-size: 0;
  gap: var(--space-micro, 0.25rem);
}
.bao-table-inline-editor {
  inline-size: 100%;
  min-inline-size: var(--touch-target-min, 2.75rem);
  min-block-size: var(--touch-target-min, 2.75rem);
  font: inherit;
  font-size: var(--text-sm);
  color: inherit;
  background: var(--color-base-100);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-field, 0.5rem);
  padding-inline: var(--space-inline, 0.5rem);
  padding-block: var(--space-micro, 0.25rem);
}
/* Keyboard focus is owned by the shared focus-visible control rule. */

/*
 * Long-press selection mode (Table.bao selectionModeActive token).
 * Coarse pointers suppress the iOS callout/text magnifier over selectable
 * rows so the 500ms gesture is not preempted; selected-row tint follows the
 * same base-200 fill the desktop rowSelected token paints.
 */
@media (pointer: coarse) {
  [data-table-selection-host] tbody {
    -webkit-touch-callout: none;
  }
  [data-table-selection-host].bao-table-selection-mode tbody {
    user-select: none;
    -webkit-user-select: none;
  }
}
[data-table-selection-host].bao-table-selection-mode tr[data-row-id] {
  cursor: pointer;
}
.bao-registry-table > tbody > tr:has(> td [data-row-select]:checked) {
  background-color: color-mix(in oklab, var(--color-primary) 12%, var(--bg-panel));
}
.bao-registry-table > tbody > tr:has(> td [data-row-select]:checked) > td {
  background-color: inherit;
}

/* Inline-edit activator is a real button — meet SC 2.5.8 (24) and HIG 44. */
[data-inline-edit-activator],
td.bao-table-inline-edit-cell > button,
td.bao-table-inline-edit-cell > [role="button"] {
  min-height: var(--touch-target-min, 2.75rem);
  min-width: var(--touch-target-min, 2.75rem);
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  padding-block: var(--space-micro, 0.25rem);
  padding-inline: var(--space-inline, 0.5rem);
  line-height: 1.25;
  height: auto;
}

/*
 * Header cells keep daisyUI's muted thead colour (60% base-content), which
 * measures 4.45:1 on the pinned header plate against a 4.5:1 AA floor — the
 * reason axe fails .z-table-action and .bao-table-pinned-divider on /cases
 * and the dashboard at desktop and tablet. Sortable headers already escape
 * it because the sort control above forces full base-content on its label;
 * Actions and the select-all cell have no control to carry that, so the
 * muted inherit reaches the operator. Full base-content measures 15.68:1.
 */
.bao-registry-table > thead > tr > th {
  color: var(--color-base-content);
}

  /*
   * Numeric columns (G-DATA register): counts, sizes, rates, and amounts are
   * end-aligned with fixed-width numerals so digits line up vertically. The
   * kind is stamped into `data-col-kind` by table-column-kind through the
   * head and body emitters, so one rule reaches every table that declares a
   * numeric column — cases captures, review-queue confidence, billing
   * reimbursement, model size, licensing seats, export steps — and no
   * per-table alignment copy exists.
   */
  .table th[data-col-kind="numeric"],
  .table td[data-col-kind="numeric"] {
    text-align: end;
    font-variant-numeric: tabular-nums;
  }


/* Table cells keep their header-aligned content flow at every width.
 * The retired card conversion no longer needs a label/value gap clamp. */


[data-case-list-surface="cases"] { min-height: 0; }
@container table (width < 40rem) {
  [data-case-list-surface="cases"] .bao-table-responsive-body > * + * { margin-block-start: 0; }
  [data-case-list-surface="cases"] .bao-table-responsive-row { border-radius: 0; border-inline: 0; border-block-start: 0; border-block-end: 1px solid color-mix(in oklab, var(--color-base-300) 70%, transparent); background: transparent; box-shadow: none; padding-block: var(--space-inline); padding-inline: 0; gap: var(--space-micro); }
  [data-case-list-surface="cases"] .bao-table-responsive-row:last-child { border-block-end: 0; }
  [data-case-list-surface="cases"] .bao-case-table-identity-cell { flex-direction: column; align-items: stretch; justify-content: flex-start; gap: var(--space-micro); }
  [data-case-list-surface="cases"] .bao-case-table-identity-value { display: block; width: 100%; max-width: 100%; min-width: 0; text-align: start; overflow-wrap: anywhere; }
  [data-case-list-surface="cases"] .bao-case-table-identity-value .bao-table-cell-stack { align-items: flex-start; text-align: start; }
  [data-case-list-surface="cases"] .bao-case-table-state-cell { min-height: var(--touch-target-min); }
  [data-case-list-surface="cases"] .bao-case-table-actions-cell { min-height: var(--touch-target-min); padding-block: 0; justify-content: flex-end; }
  [data-case-list-surface="cases"] .bao-case-table-actions-cell > .bao-table-row-actions { width: auto; max-width: 100%; flex: 0 0 auto; justify-content: flex-end; }
}



  /*
   * Borders — STYLE only. Tailwind already emits the 1px width for each of
   * these; re-declaring it here shipped the same width a second time from a
   * stylesheet that loads after Tailwind's, in the same `utilities` layer and
   * at the same specificity, so the duplicate won — and `border-width` is the
   * four-side shorthand, so it reset the inline-start width every directional
   * utility had just set. Measured on /specimens: the case picker's selected
   * line carries `border border-s-4`, and its 4px selection accent rendered
   * at 1px, which is the same hairline an unselected card shows.
   *
   * The style half is what this block is for: `--tw-border-style` reaches the
   * width utilities through a registered custom property, and stating `solid`
   * outright keeps the border painted in an engine that does not register it.
   */
  .border,
  .border-t,
  .border-b,
  .border-l,
  .border-r {
    border-style: solid;
  }
  .border-dashed { border-style: dashed; }

  /* Typography/target/radius vars come ONLY from tokens.bao (@layer base
     :root emission) — never re-declare them here: an unlayered duplicate
     silently forks the scale (text-2xs shipped 0.625rem vs 0.6875rem). */
  .text-2xs { font-size: var(--text-2xs); line-height: 0.875rem; }
  .text-xs { font-size: var(--text-xs); line-height: 1rem; }
  .text-sm { font-size: var(--text-sm); line-height: 1.25rem; }
  .text-base { font-size: var(--text-base); line-height: 1.5rem; }
  .text-lg { font-size: var(--text-lg); line-height: 1.75rem; }
  .text-xl { font-size: var(--text-xl); line-height: 1.75rem; }
  .text-3xl { font-size: var(--text-3xl); line-height: 2.25rem; }
  .font-medium { font-weight: 500; }
  .font-semibold { font-weight: 600; }
  .font-bold { font-weight: 700; }
  /* Typography SSOT — tokens.bao font stacks must reach the document. */
  .font-sans { font-family: var(--font-sans); }
  .font-brand { font-family: var(--font-brand); }
  .font-mono { font-family: var(--font-mono); }
  .leading-none { line-height: 1; }
  .leading-tight { line-height: 1.25; }
  .tracking-tight { letter-spacing: var(--tracking-tight, -0.025em); }
  .tracking-wider { letter-spacing: var(--tracking-wider, 0.05em); }
  .tracking-wide { letter-spacing: var(--tracking-wide, 0.025em); }
  /* Opacity wash for body/row mute is banned (S04/S20). Disabled only. */

  /* Transitions and interactions — aliases of bao-motion-* tokens (no raw durations). */
  .transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: var(--easing-standard); transition-duration: var(--duration-normal); }
  .transition-transform { transition-property: transform; transition-timing-function: var(--easing-standard); transition-duration: var(--duration-normal); }
  /* Prefer .bao-motion-transition-colors in markup; this stays as TW utility alias only. */
  .disabled\:pointer-events-none:disabled { pointer-events: none; }
  .disabled\:opacity-50:disabled { opacity: var(--opacity-disabled, 0.5); }
  .placeholder\:text-base-content\/60::placeholder { color: var(--text-secondary); }
  .file\:border-0::file-selector-button { border-width: 0; }
  .file\:bg-transparent::file-selector-button { background-color: transparent; }
  .file\:text-sm::file-selector-button { font-size: var(--text-sm, 0.875rem); }
  .file\:font-medium::file-selector-button { font-weight: 500; }
  .translate-x-0 { transform: translateX(0); }
  .translate-x-4 { transform: translateX(1rem); }
  /* One pulse keyframe owner (bao-pulse-alpha, tokens-glass-motion) — the old
     duplicate 'pulse' keyframe forked the same 1↔0.5 opacity animation. */
  .animate-pulse { animation: bao-pulse-alpha var(--duration-pulse-alpha, 1000ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite; }
  .blur { filter: blur(var(--liquid-blur-clear)); }

  /* The former responsive flex-direction "escape" blocks are gone with the
     structural duplicates that made them necessary — Tailwind's own
     breakpoint-gated variants apply again in their native order. */

  /*
   * Nested solid data panels: table wraps inside form/widget cards must not
   * re-apply card chrome (double border / double shadow). Apple clarity:
   * one surface owns the panel; nested scroll hosts stay transparent.
   */
  .bao-data-panel .bao-data-panel.overflow-x-auto,
  .card .bao-data-panel.overflow-x-auto,
  [data-training-plans-panel] .bao-data-panel.overflow-x-auto,
  [data-training-jobs-table] .bao-data-panel.overflow-x-auto,
  [data-model-lifecycle-section] .bao-data-panel.overflow-x-auto {
    border-width: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }
  /* One owner per fact: the connector strip announces the SELECTED
     provider's state, so the selected roster row hides its duplicate
     state label (probe dots on non-selected rows stay). */
  [data-provider-selected="true"] [data-provider-state-label] {
    display: none;
  }
}

@layer utilities {
  .max-w-page-standard { max-width: var(--page-standard); }
  .max-w-page-wide { max-width: var(--page-wide); }
  .max-w-page-full { max-width: var(--page-full); }
  .max-w-page-conversation { max-width: var(--page-conversation); }
  .max-w-description { max-width: var(--description); }
  .max-w-overlay-panel { max-width: var(--overlay-panel); }
  .max-w-auth { max-width: var(--auth); }
  .max-w-form { max-width: var(--form); }
}


/* Establish the pseudo-element containing block before utilities and the
   late bao-tokens layer. State-owned component geometry therefore retains
   ownership when a glass surface changes between in-flow and overlay modes. */
@layer components {
  :where(.bao-surface-glass-regular, .bao-surface-glass-clear, .bao-surface-glass-strong, .bao-surface-glass-modal) {
    position: relative;
  }
}

@layer bao-tokens {

  /*
   * Responsive SSOT lives on these classes — not Tailwind md:hidden/md:block.
   * A later unscoped display:grid was beating md:hidden and left desktop stuck
   * on the mobile card dump with the table permanently display:none.
   */
  .bao-registry-mobile-list {
    display: grid;
    min-width: 0;
    gap: var(--space-section);
  }
  .bao-registry-desktop-scroll {
    display: none;
    min-width: 0;
    width: 100%;
  }
  @media (min-width: 48rem) {
    .bao-registry-mobile-list {
      display: none;
    }
    .bao-registry-desktop-scroll {
      display: block;
    }
  }
  .bao-registry-meta-grid {
    display: grid;
    min-width: 0;
    gap: var(--space-inline);
  }
  .bao-registry-meta-row {
    display: grid;
    min-width: 0;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    gap: var(--space-inline);
  }
  .bao-registry-mobile-entry {
    min-width: 0;
  }
  .table.bao-registry-table {
    /* Fit available space before scrolling content that cannot wrap. */
    width: 100%;
    min-width: 100%;
    max-width: none;
    table-layout: auto;
  }
  /* Discovery/widget card bodies must not expand main past the viewport. */
  [data-page="models"] .card-body,
  [data-page="models"] [data-widget-value] {
    min-width: 0;
    max-width: 100%;
  }
  .bao-registry-table th,
  .bao-registry-table td {
    min-width: 0;
  }
  /*
     The pinned cell inherits the row-owned content plane so horizontally
     scrolled values never remain legible beneath an action. It used to derive base-100/base-200 from :nth-child itself,
     which made the row and its pinned cell two owners of one colour, and they
     disagreed the moment anything restyled the row: the dashboard's
     home-page case-list rule sets rows transparent at specificity (0,2,2)
     and beats daisyUI's zebra at (0,1,2), so the rows
     went flat while the Actions column carried on painting a stripe nothing
     else had — a plate of white and base-200 floating against unstriped rows,
     and a white header cell beside a base-200 header row.

     The action shelf remains opaque through the row surface while its left depth shadow
     communicates that the column is pinned above the horizontally scrolling
     data plane.
  */
  .table > tbody > tr > .bao-table-pinned-action {
    background-color: inherit;
  }
  /*
     Inheritance alone leaves the shelf transparent on unstriped rows. Zebra
     paints only :nth-child(even), so an odd row computes rgba(0,0,0,0) and the
     pinned cell faithfully inherits nothing. Measured on
     /settings?tab=capture-devices at 1280x800: the devices table lays out at
     783px inside a 713px scrollport, so 70px of the Status column sits under
     the shelf, and its status badge printed straight through the row-actions
     button — half a pill floating over the overflow control.

     The odd-row plate is the panel surface that row already shows, so this is
     not a second stripe owner: even rows keep inheriting daisyUI's zebra, and
     nothing here can disagree with a row about a colour the row does not have.
  */
  .bao-registry-table > tbody > tr:nth-child(odd) > .bao-table-pinned-action {
    background-color: var(--bg-panel, var(--color-base-100));
  }
  /*
     Same rule one level up: the header ROW owns the header surface and every
     header cell inherits it, pinned one included. Previously each th carried
     its own bg-base-200 while the header row stayed white, so the sticky cell
     had nothing correct to inherit and rendered white against base-200
     siblings. One owner per row removes the choice.
  */
  .table > thead > tr {
    background-color: var(--bg-surface);
  }
  .table > thead > tr > th {
    background-color: inherit;
  }
  /*
     The pinned action shelf stays stable during row hover. The row content
     communicates hover and focus while the shelf remains an opaque, reliable
     control plane above horizontally scrolling values.
  */
  /*
   * Column floors belong to TABLE mode only, so they share the card-stack
   * owner's 40rem CONTAINER threshold (tokens-layout-table-responsive.ts)
   * on the NAMED table container. The extra viewport guard closes the
   * hybrid band: at phone viewports (<48rem) the dock densify module card-
   * stacks registry rows by MEDIA query, and a 640–767px container inside
   * that band satisfied width>=40rem — floors then crushed the first
   * stacked cell to 5.5rem ("Accession" → "Ac…" on dashboard cards).
   */
  /*
   * Column floors belong to what a column HOLDS, not to where it sits.
   *
   * These were `nth-child(1..11)` floors: column two took 14rem on every
   * registry in the product whether it held an episode name or a two-letter
   * state, and reordering a table silently swapped its widths. They totalled
   * 62.5rem before the pinned action column — wider than any desktop
   * container — so a second positional block cancelled `nth-child(2..11)`
   * back to `min-width: 0` between 40rem and 84.5rem. Position asserted a
   * width, then position retracted it, and the band between them was the
   * only place tables actually laid out.
   *
   * `data-col-kind` is emitted per cell by pages-renderers-responsive-table
   * from the column's own i18n key, so a floor follows its column through any
   * reorder. Only these three kinds have a real minimum legible width; every
   * other column is content-driven, which is what the cancelling block was
   * asking for.
   *
   * The threshold is the container width at which the floors are affordable,
   * measured rather than assumed: at a 1042px container the floored table
   * lays out at exactly 1042px, and at 884px it wants 954px and scrolls. A
   * floor is a legibility luxury, so below 60rem of container every column
   * goes content-driven and the table fits itself to the room it has. The
   * viewport guard keeps all of this off the phone card stack.
   */
  @media (min-width: 48rem) {
  @container table (width >= 60rem) {
    /*
     * Header only. A column's floor comes from its widest constraint under
     * `table-layout: auto`, so flooring the header floors the column; adding
     * the matching `td` selectors measured identical at every viewport —
     * 1042/1042 at 1440px, 1330/1330 at 1728px, 900/884 at 1280px — which
     * makes them weight without effect. Cells carry `data-col-kind` for a
     * different consumer: `validate:table-cell-column-parity` reads it to hold
     * each row partial's cells against the columns its section declares.
     */
    .bao-registry-table th[data-col-kind="identifier"] {
      min-width: 5.5rem;
      white-space: nowrap;
    }
    .bao-registry-table th[data-col-kind="timestamp"] {
      min-width: 8rem;
    }
    .bao-registry-table th[data-col-kind="actions"] {
      min-width: 6rem;
    }
  }
  }
  .bao-registry-cell-mono {
    max-width: 20rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .bao-registry-cell-prose {
    max-width: 22rem;
    min-width: 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
    word-break: break-word;
  }

}
@layer bao-tokens {
/* Intrinsic table sizing is owned by tokens-layout-registry-surfaces.
 * The shared table scroller and column-fit controller preserve all columns
 * without a breakpoint-specific width or wrapping override. */

}
@layer bao-tokens {
/* design-canon.json liquidGlass.hig — compiled stamps, do not hand-edit */
:root {
  --hig-sticky-chrome-max-layers: mediaPages;
  --hig-glass-layer-content: content;
  --hig-glass-layer-persistent-chrome: persistentChrome;
  --hig-glass-layer-transient: transient;
  --hig-glass-layer-blocking: blocking;
  --hig-clear-glass-dimming: rgba(0, 0, 0, 0.35);
  --hig-type-min: 0.6875rem;
  --hig-bezeled-padding: 0.75rem;
  --hig-unbezeled-padding: 1.5rem;
  --hig-glass-appearance-default: tinted;
  --hig-glass-appearance-stamp: data-liquid-glass;
  --hig-window-radius-token: rounded-box;
}

}
@layer bao-tokens {


/* ============================================================
   Apple Liquid Glass — base layer
   ============================================================
   All glass surfaces share these properties so the highlight ring
   and inner illumination compose consistently across variants. The
   base class is intentionally non-applied (use the named variants
   `.bao-surface-glass-regular`, `.bao-surface-glass-clear`,
   `.bao-surface-glass-strong`, `.bao-surface-glass-modal`, or
   `.bao-surface-glass-solid`).
   Enhanced with better depth, shadows, and accessibility per Apple HIG. */

.bao-surface-glass-regular,
.bao-surface-glass-clear,
.bao-surface-glass-strong,
.bao-surface-glass-modal,
.bao-surface-glass-solid {
  isolation: isolate;
  border-style: solid;
  border-width: 1px;
  transition-property: background-color, border-color;
  transition-timing-function: var(--easing-glass);
  transition-duration: var(--duration-glass);
}

/* Unsupported color mixing keeps the original opaque material fallback.
 * Supported engines get their complete paint from the named variant. */
@supports not (background: color-mix(in oklab, black, transparent)) {
  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-strong,
  .bao-surface-glass-modal,
  .bao-surface-glass-solid {
    background-color: var(--bg-panel);
    background-blend-mode: var(--glass-noise-blend, overlay);
    background-image: var(--glass-noise-texture, none);
    background-size: var(--glass-noise-size, 128px);
  }
}

/* design.pdf §2.8: grain is an enhancement, never a requirement — hosts
   flagged low-performance (product override) drop the texture entirely. */
html[data-low-performance="true"] {
  --glass-noise-texture: none;
}

/* design.pdf §2.8: never paint grain behind micro labels — the topbar's
   11–12px titles and chip captions lose fidelity over the noise, so the
   chrome that hosts them drops the texture entirely. */
#app-header {
  --glass-noise-texture: none;
}

/*
 * daisyUI drawer-side is position:fixed (closed) / sticky (drawer-open).
 * Unlayered glass position:relative would win the cascade and leave a
 * max-content grid track on mobile, crushing #app-main. Never put glass
 * on .drawer-side — glass belongs on #app-sidebar / panel chrome only.
 */
.drawer-side.bao-surface-glass-regular,
.drawer-side.bao-surface-glass-clear {
  position: fixed;
}

/* Specular highlight ring (top edge brightness + bottom edge dim).
   The angle is set by `--lg-pointer-angle` (driven by JS, default
   135deg per Apple HIG NW light direction). The gradient uses
   mask-composite to render only as a hairline ring.
   Enhanced with better opacity values for refined depth perception. */
.bao-surface-glass-regular::before,
.bao-surface-glass-clear::before,
.bao-surface-glass-strong::before,
.bao-surface-glass-modal::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--border-width-hairline, 1px);
  background: linear-gradient(
    var(--lg-pointer-angle, var(--liquid-light-angle)),
    var(--glass-highlight-strong) 0%,
    var(--glass-highlight-soft) 40%,
    var(--glass-highlight-clear) 70%,
    var(--glass-edge-fade) 100%
  );
  -webkit-mask: linear-gradient(var(--glass-mask-source) 0 0) content-box, linear-gradient(var(--glass-mask-source) 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  mix-blend-mode: screen;
  /* Pointer angle updates must be instant — transitioning background
     while --lg-pointer-angle changes every frame flashes the pane. */
  transition-property: none;
  /* Specular sits above fill, BELOW interactive children (z-index 3).
     pointer-events:none is required so chrome controls stay clickable. */
  z-index: var(--z-raised, 2);
}

/* Inner illumination — radial soft-light from upper-left that gives
   the pane its gel-like glow. Must sit BEHIND in-flow content so
   screen-blend does not wash tabpanels / rail copy.
   Enhanced with better gradient stops for refined depth (Apple HIG). */
.bao-surface-glass-regular::after,
.bao-surface-glass-clear::after,
.bao-surface-glass-strong::after,
.bao-surface-glass-modal::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    ellipse 120% 80% at 20% 0%,
    var(--glass-illumination-strong) 0%,
    var(--glass-illumination-soft) 50%,
    var(--glass-highlight-clear) 80%
  );
  mix-blend-mode: screen;
  opacity: 1;
  transition-property: opacity;
  transition-timing-function: var(--easing-glass);
  transition-duration: var(--duration-glass);
  z-index: var(--z-base, 0);
}

/* Child stacking above specular: tokens-glass-fill.ts (S100 narrow hosts). */

/* ============================================================
   Regular variant — adaptive, works over any content
   ============================================================
   Apple HIG: "Regular is the most versatile and the one you will
   be using the most. This variant gives you all the visual and
   adaptive effects". Use for shell chrome (topbar, sidebar,
   rightRail, command palette, dialogs, sheets). */

/* CANON MAPPING (design.pdf §2.2/§3) — do not let these semantics drift:
   --liquid-fill-* is the panel ALPHA (the bg-panel share in the color-mix),
   so a higher value paints a more opaque pane. Canon requires dark glass to
   run MORE opaque than light (it must suppress bright content underneath),
   so every dark fill value is HIGHER than its light twin in tokens.bao, and
   compact/coarse fills are higher still ("fewer blurred surfaces, stronger
   tint"). Ladder (panel alpha): opacity rises clear < regular(persistent)
   < transient < blocking with the depth layer — a higher layer never has a
   weaker edge/shadow, and dialogs (blocking) never bleed more than the
   menus (transient) stacked beneath them. */

.bao-surface-glass-regular {
  background: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-persistent) * 100%),
    transparent
  );
  -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  border-color: var(--liquid-border-regular);
  /* Depth: broad soft lift + inner shadow for glass depth (Apple HIG). */
  box-shadow:
    var(--shadow-glass-elevated),
    var(--shadow-glass-inner);
}

/* design.pdf §10–§11: solid fallback first. Engines without backdrop-filter
   keep an opaque surface — never transparency without blur. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-strong,
  .bao-surface-glass-modal,
  .bao-surface-glass-lens {
    background: var(--bg-panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Density modifier — apply alongside .bao-surface-glass-regular to
   scale the blur and tint for the surface footprint. Apple HIG
   says glass morphs to thicker when larger. Enhanced with inner shadow
   for consistent depth across density variants. */
.bao-surface-glass-regular.bao-glass-large {
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular));
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular));
  border-color: var(--liquid-border-regular);
}
.bao-surface-glass-regular.bao-glass-small {
  -webkit-backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-regular));
  backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-regular));
  border-color: var(--liquid-border-regular);
  box-shadow: var(--shadow-glass-elevated), var(--shadow-glass-inner);
}

/* design.pdf §8.2 compact glass treatment: container-query blur reduction on the
   shell host (not viewport width). Blur steps reuse existing scale tokens;
   tint uses dedicated coarse tokens from tokens.bao color.liquid.
   Pair coarse-pointer tint with @media (pointer: coarse) — not width. */
@container shell (max-width: 48rem) {
  .bao-surface-glass-regular {
    -webkit-backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-mobile));
    backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-mobile));
  }
  .bao-surface-glass-regular.bao-glass-large {
    -webkit-backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-mobile));
    backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-mobile));
  }
}

@media (pointer: coarse) {
  :root {
    --liquid-fill-persistent: var(--liquid-fill-persistent-coarse);
  }
}

/* Tablet optimization: intermediate blur for balanced performance/quality */
@container shell (min-width: 48rem) and (max-width: 63.99rem) {
  .bao-surface-glass-regular {
    -webkit-backdrop-filter: blur(var(--liquid-blur-tablet)) saturate(var(--liquid-saturate-tablet));
    backdrop-filter: blur(var(--liquid-blur-tablet)) saturate(var(--liquid-saturate-tablet));
  }
  .bao-surface-glass-regular.bao-glass-large {
    -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-tablet));
    backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-tablet));
  }
}

/*
 * Dense multi-line hosts solidify in tokens-glass-fill.ts (pathologist
 * report, evidence lists, recent-cases) so this base stays under 400 LOC.
 */

/* ============================================================
   Clear variant — permanently transparent, requires dim layer
   ============================================================
   Apple HIG: "Clear should only be used when these 3 conditions
   are met: element is on top of media-rich content; the content
   layer won't be negatively impacted by a dimming layer; the
   content sitting above it is bold and bright." The dim layer
   (.bao-surface-glass-clear-dim) must be applied to the
   text-bearing child container so text never sits directly on
   glass. */

/* Clear consumes its own --liquid-fill-clear, NOT --liquid-fill-control.
   Sharing that token inverted the ladder: tint is a transparency fraction
   (alpha = 1 - tint), so subtle's 0.08 rendered Clear at 92% alpha — the most
   OPAQUE material in the system, above regular (84%), strong (86%) and
   elevated (88%). Clear is applied over the capture preview and annotation
   canvas, exactly where the media must read through, so the inversion was
   most visible on the worst surfaces.
   Clear-glass dimming is --hig-clear-glass-dimming (Apple HIG Materials:
   35% dark layer on bright content). Never retune it via --liquid-fill-control. */
/* Only Clear retains image layers in supported engines; color shorthands
 * on the other variants reset blending to normal. */
.bao-surface-glass-clear {
  background-blend-mode: var(--glass-noise-blend, overlay);
  background-color: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-clear) * 100%),
    transparent
  );
  /*
   * The dimming layer Apple requires, composed into the Clear fill itself.
   * HIG: Clear "is permanently more transparent" and needs a dimming layer for
   * legibility. `.bao-surface-glass-clear-dim` was defined for that job and
   * shipped in ZERO partials — every Clear surface (annotation command bar,
   * canvas toolbars, 3D controls, capture preview overlay) put text straight
   * onto glass over arbitrary specimen media, so contrast was whatever the
   * slide happened to be.
   *
   * Applied as a background-image layer rather than a child element: these
   * hosts are flex containers whose children are the controls themselves, and
   * their ::before/::after already carry the specular ring and edge. A layered
   * paint needs no markup change and stacks under the content exactly as the
   * companion class would have. The a11y suppressions set `background:`
   * (shorthand), which resets background-image too, so reduced-transparency,
   * increased-contrast and forced-colors still drop it to a solid surface.
   */
  background-image: linear-gradient(
    0deg,
    var(--hig-clear-glass-dimming),
    var(--hig-clear-glass-dimming)
  );
  -webkit-backdrop-filter: blur(var(--liquid-blur-clear)) saturate(var(--liquid-saturate-clear));
  backdrop-filter: blur(var(--liquid-blur-clear)) saturate(var(--liquid-saturate-clear));
  border-color: var(--liquid-border-clear);
  box-shadow: var(--shadow-glass-clear);
}

/* Dimming companion for Clear-variant glass — apply to text containers
   that overlay rich/vibrant content. No visible effect when the parent
   is not Clear glass; safe to attach on text hosts defensively. */
.bao-surface-glass-clear-dim {
  background: var(--hig-clear-glass-dimming);
  border-radius: inherit;
}


@container shell (max-width: 48rem) {
  .bao-surface-glass-clear {
    -webkit-backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-mobile));
    backdrop-filter: blur(var(--liquid-blur-mobile)) saturate(var(--liquid-saturate-mobile));
  }
}

@container shell (min-width: 48rem) and (max-width: 63.99rem) {
  .bao-surface-glass-clear {
    -webkit-backdrop-filter: blur(var(--liquid-blur-tablet)) saturate(var(--liquid-saturate-tablet));
    backdrop-filter: blur(var(--liquid-blur-tablet)) saturate(var(--liquid-saturate-tablet));
  }
}


/* ============================================================
   Refraction lens modifier (opt-in)
   ============================================================
   Apply .bao-surface-glass-lens alongside a glass variant on hero
   surfaces (command palette, dialog, sheet). The SVG filter
   #bao-liquid-lens must be present in the document — see
   apps/server/public/bao-liquid-lens.svg (compiled from tokens.bao
   color.liquid.liquid-lens-*). Browsers without backdrop-filter
   url() support keep the preceding plain blur declaration
   (Safari/Firefox per the backdrop-filter url() spec). */

@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-lens {
  -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  }
}

/*
 * Density + lens: .bao-glass-large is 0,2,0 and was wiping url(#bao-liquid-lens)
 * from plain .bao-surface-glass-lens (0,1,0). Dialogs use large+elevated+lens
 * per design-canon — compound selector must keep refraction + nav blur.
 */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-regular.bao-glass-large.bao-surface-glass-lens {
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  }
}
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-regular.bao-glass-small.bao-surface-glass-lens {
  -webkit-backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  backdrop-filter: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-regular)) url(#bao-liquid-lens);
  }
}

/* ============================================================
   Strong variant — menus, sheets, form panels (design.pdf §3)
   ============================================================
   30-55% tint opacity, 20-32px blur. Use for transient surfaces that
   need stronger visual separation from content: dropdown menus,
   bottom sheets, form panels, and contextual overlays. */

.bao-surface-glass-strong {
  background: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
    transparent
  );
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  border-color: var(--liquid-border-regular);
  box-shadow: var(--shadow-glass-floating), var(--shadow-glass-inner);
}

/* ============================================================
   Modal variant — dialogs above scrim (design.pdf §3)
   ============================================================
   50-75% tint opacity, 24-40px blur. Use for blocking dialogs and
   modals that sit above a dimming scrim. Higher opacity ensures
   content beneath the scrim cannot bleed through.
   Panel alpha consumes --liquid-fill-blocking (light 88% / dark 92%
   opaque) so the fill ladder stays monotonic —
   control < clear < persistent < persistent-coarse < transient <
   blocking — and dark glass runs MORE opaque per §2.2.
   The 88% floor is load-bearing, not decorative: browser-structure-audit
   rejects any .modal-box whose computed alpha falls under 0.82
   (scripts/lib/browser-structure-audit-chrome.mjs), because an
   enterprise/medical dialog must never let the page read through when
   backdrop-filter is suppressed. Blur rides on top of that fill so the
   material still refracts wherever the filter is live. */

.bao-surface-glass-modal {
  background: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-blocking) * 100%),
    transparent
  );
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  border-color: var(--liquid-border-regular);
  box-shadow: var(--shadow-glass-floating), var(--shadow-glass-inner);
}

/* Dialog.bao composes modal + lens. This compound owner follows the modal
   variant so the opt-in refraction survives the modal's blocking blur. */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-modal.bao-surface-glass-lens {
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong)) url(#bao-liquid-lens);
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong)) url(#bao-liquid-lens);
  }
}

/* ============================================================
   Solid variant — accessibility/performance fallback (design.pdf §3)
   ============================================================
   Opaque surface with no blur. Use when backdrop-filter is unsupported,
   disabled via prefers-reduced-transparency, or when performance
   budget prohibits blur layers. Provides visual consistency without
   the glass effect. */

.bao-surface-glass-solid {
  background: var(--bg-panel);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-color: var(--border-base);
  box-shadow: var(--shadow-md);
}

/* Opaque fallback for modal-box with solid material. */
.modal-box.bg-base-100 {
  background-color: var(--color-base-100, var(--bg-panel));
}


/* ============================================================
   Hover-reveal — progressive disclosure for row/card actions
   ============================================================
   Fine-pointer devices: secondary actions fade in on host hover or any
   keyboard focus inside the host. Touch / no-hover devices: always
   visible (parity law — no hover-only affordances). */

@media (hover: hover) and (pointer: fine) {
  .bao-hover-reveal-host .bao-hover-reveal {
    opacity: 0;
    transition-property: opacity;
    transition-timing-function: var(--easing-standard);
    transition-duration: var(--duration-normal);
  }
  .bao-hover-reveal-host:hover .bao-hover-reveal,
  .bao-hover-reveal-host:focus-within .bao-hover-reveal {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-strong,
  .bao-surface-glass-lens {
    transition-duration: var(--reduced-motion-min-duration, 0.001ms);
  }
  .bao-surface-glass-regular::before,
  .bao-surface-glass-clear::before,
  .bao-surface-glass-regular::after,
  .bao-surface-glass-clear::after {
    transition-duration: var(--reduced-motion-min-duration, 0.001ms);
  }
  .bao-hover-reveal-host .bao-hover-reveal {
    transition-duration: var(--reduced-motion-min-duration, 0.001ms);
  }
}


/* ============================================================
   Reactive lensing — hover/focus strengthens the material
   ============================================================
   Ported from the baobeep glass canon: pointer hover (or keyboard
   focus-within) deepens the tint and sharpens the border so
   interactive glass reads as touchable. Material tint/edge/shadow
   states are owned HERE; transforms and the shared transition list
   are owned by tokens-glass-motion.ts (one owner per concern — the
   transition channel list there excludes blur filters, so blur never
   churns; design.pdf §7.4). Hover is gated to fine pointers so touch
   devices never trap a stuck hover state (design.pdf §6). */
@media (hover: hover) and (pointer: fine) {
  /* design.pdf §6/§7.4: hover is a fine-pointer enhancement — tint, edge,
     and shadow carry the interactive strengthening; blur stays fixed.
     Enhanced with inner shadow for better depth perception (Apple HIG). */
  .bao-surface-glass-reactive:hover {
    background: color-mix(
      in oklab,
      var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
      transparent
    );
    border-color: var(--liquid-border-contrast);
    box-shadow:
      var(--shadow-glass-floating, var(--shadow-glass-elevated)),
      var(--shadow-glass-inner);
  }
}

/* Focus strengthening never depends on hover capability (design.pdf §6).
   Enhanced with inner shadow for better visual feedback (Apple HIG). */
.bao-surface-glass-reactive:focus-within {
  background: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
    transparent
  );
  border-color: var(--liquid-border-contrast);
  box-shadow:
    var(--shadow-glass-floating, var(--shadow-glass-elevated)),
    var(--shadow-glass-inner);
}

/* ============================================================
   Disabled glass surfaces — reduced chroma/elevation (design.pdf §6/§2.10)
   ============================================================
   Disabled glass keeps text legible by dropping elevation and chroma
   instead of crushing opacity. Blur reduces to subtle, border fades,
   shadows flatten, and interactive states are suppressed.
   design.pdf §6: "disabled keeps the label readable — drop elevation/chroma
   instead of crushing text via opacity" */
.bao-surface-glass-regular:disabled,
.bao-surface-glass-regular[aria-disabled="true"],
.bao-surface-glass-clear:disabled,
.bao-surface-glass-clear[aria-disabled="true"],
.bao-surface-glass-strong:disabled,
.bao-surface-glass-strong[aria-disabled="true"],
.bao-surface-glass-modal:disabled,
.bao-surface-glass-modal[aria-disabled="true"] {
  /* Reduce blur to subtle level — maintains glass identity without visual weight */
  -webkit-backdrop-filter: blur(var(--liquid-blur-clear, 8px)) saturate(var(--liquid-saturate-clear, 1.35));
  backdrop-filter: blur(var(--liquid-blur-clear, 8px)) saturate(var(--liquid-saturate-clear, 1.35));
  /* Fade border to base-300 — reduces chroma without disappearing */
  border-color: var(--color-base-300);
  /* Flatten shadows — remove depth cues for non-interactive state */
  box-shadow: none;
  /* Suppress pointer events — no hover/focus states on disabled surfaces */
  pointer-events: none;
  /* Maintain text legibility via color-mix instead of opacity reduction */
  color: color-mix(in oklab, var(--color-base-content) 60%, var(--color-base-200));
}

/* Suppress hover/focus on disabled glass surfaces */
.bao-surface-glass-regular:disabled:hover,
.bao-surface-glass-regular[aria-disabled="true"]:hover,
.bao-surface-glass-regular:disabled:focus-within,
.bao-surface-glass-regular[aria-disabled="true"]:focus-within,
.bao-surface-glass-clear:disabled:hover,
.bao-surface-glass-clear[aria-disabled="true"]:hover,
.bao-surface-glass-clear:disabled:focus-within,
.bao-surface-glass-clear[aria-disabled="true"]:focus-within,
.bao-surface-glass-strong:disabled:hover,
.bao-surface-glass-strong[aria-disabled="true"]:hover,
.bao-surface-glass-strong:disabled:focus-within,
.bao-surface-glass-strong[aria-disabled="true"]:focus-within,
.bao-surface-glass-modal:disabled:hover,
.bao-surface-glass-modal[aria-disabled="true"]:hover,
.bao-surface-glass-modal:disabled:focus-within,
.bao-surface-glass-modal[aria-disabled="true"]:focus-within {
  /* Inherit disabled state — no visual change on interaction */
  -webkit-backdrop-filter: blur(var(--liquid-blur-clear)) saturate(var(--liquid-saturate-clear));
  backdrop-filter: blur(var(--liquid-blur-clear)) saturate(var(--liquid-saturate-clear));
  border-color: var(--color-base-300);
  box-shadow: none;
}

/* ============================================================
   Loading glass surfaces — skeleton shimmer overlay (design.pdf §2.10)
   ============================================================
   Loading state reduces glass clarity and adds a skeleton shimmer
   overlay to indicate async content loading. Blur increases slightly
   to soften the surface, border becomes more prominent, and a subtle
   shimmer animation sweeps across the surface. */
.bao-surface-glass-regular[data-state="loading"],
.bao-surface-glass-clear[data-state="loading"],
.bao-surface-glass-strong[data-state="loading"],
.bao-surface-glass-modal[data-state="loading"] {
  /* Increase blur to soften surface during loading */
  -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  /* Strengthen border for better definition */
  border-color: var(--color-base-300);
  /* Add skeleton shimmer overlay via ::after pseudo-element */
  position: relative;
  overflow: hidden;
}

.bao-surface-glass-regular[data-state="loading"]::after,
.bao-surface-glass-clear[data-state="loading"]::after,
.bao-surface-glass-strong[data-state="loading"]::after,
.bao-surface-glass-modal[data-state="loading"]::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    90deg,
    transparent 25%,
    color-mix(in oklab, var(--bg-surface-hover, var(--color-base-300)) 40%, transparent) 50%,
    transparent 75%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: bao-skeleton-sweep var(--duration-skeleton) var(--easing-standard) infinite;
  pointer-events: none;
  z-index: var(--z-local, 1);
}

/* ============================================================
   Error glass surfaces — visual error indicator (design.pdf §2.10)
   ============================================================
   Error state strengthens the border with error color and adds a
   subtle error indicator. Glass maintains its blur/shadow depth
   but the border shifts to error color to signal failure state. */
.bao-surface-glass-regular[data-state="error"],
.bao-surface-glass-clear[data-state="error"],
.bao-surface-glass-strong[data-state="error"],
.bao-surface-glass-modal[data-state="error"] {
  /* Strengthen border with error color — clear visual signal */
  border-color: var(--color-error);
  border-width: 2px;
  /* Maintain glass depth — keep shadows intact */
  box-shadow:
    var(--shadow-glass-elevated, var(--shadow-md)),
    var(--shadow-glass-inner, 0 0 0 0);
}

/* ============================================================
   Glass-on-glass containment (design.pdf §4 / §10 / §14)
   ============================================================
   A backdrop-filter ancestor establishes a new backdrop root, so a
   nested glass surface (e.g. a dropdown panel inside the glass topbar)
   can never re-blur the page — it renders as raw transparency without
   blur, bleeding unblurred content through menu items. Contained
   surfaces inside glass therefore keep their border/shadow but paint
   as the canonical solid fill plate instead of a second material. */
.bao-surface-glass-regular .bao-surface-glass-regular,
.bao-surface-glass-regular .bao-surface-glass-clear,
.bao-surface-glass-regular .bao-surface-glass-strong,
.bao-surface-glass-regular .bao-surface-glass-modal,
.bao-surface-glass-clear .bao-surface-glass-regular,
.bao-surface-glass-clear .bao-surface-glass-clear,
.bao-surface-glass-clear .bao-surface-glass-strong,
.bao-surface-glass-clear .bao-surface-glass-modal,
.bao-surface-glass-strong .bao-surface-glass-regular,
.bao-surface-glass-strong .bao-surface-glass-clear,
.bao-surface-glass-strong .bao-surface-glass-strong,
.bao-surface-glass-strong .bao-surface-glass-modal,
.bao-surface-glass-modal .bao-surface-glass-regular,
.bao-surface-glass-modal .bao-surface-glass-clear,
.bao-surface-glass-modal .bao-surface-glass-strong,
.bao-surface-glass-modal .bao-surface-glass-modal {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: var(--bg-panel);
}

/* ============================================================
   Touch target — minimum 44x44dp per Apple HIG
   ============================================================ */

.bao-touch-target {
  min-height: var(--touch-target-min);
  min-width: var(--touch-target-min);
}

.input.bao-touch-target,
.select.bao-touch-target,
.file-input.bao-touch-target {
  min-height: var(--touch-target-min);
  min-width: var(--touch-target-min);
}

/*
 * A checkbox reaches the touch floor by growing, not by projecting a pseudo.
 *
 * This used to keep the box at its painted 24px and hang a 44x44 `::after`
 * around it. The pseudo renders — computed 44x44 at inset -11px — but it does
 * not extend the input's hit region: measured on /cases at 375x812 with
 * `pointer: coarse`, `elementFromPoint` 21px from the checkbox centre returned
 * the ancestor section, not the checkbox. So every table's row selector
 * carried `bao-touch-target`, satisfied every gate that checks for the class,
 * and offered a 24px target to a finger.
 *
 * Grown at every pointer, not only coarse ones. The first attempt gated this
 * on `(pointer: coarse)` to keep dense pointer-driven tables compact, and the
 * rendered-geometry gate refused it: it measures every control at desktop and
 * reported `input "Select all rows" 24.0x24.0` on /settings. The 44pt floor is
 * the contract that gate enforces, and a control that meets it only on some
 * input devices does not meet it.
 */
.checkbox.bao-touch-target {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  /*
   * The tick keeps its old weight inside the bigger box. daisyUI paints the
   * mark into the content box, so growing the control alone scaled the mark
   * with it and a report form filled with 44px boxes and heavy ticks —
   * measured on /reports at 375px right after the growth landed. Padding the
   * difference restores the painted mark to its 1.5rem while the box, and the
   * hit area, stay at the floor.
   *
   * Growing the BOX is load-bearing, not a shortcut. A 24px box with the area
   * grown by an absolutely positioned ::after was tried and measured: the
   * pseudo computes as rendered (content:"", inset -11px, pointer-events
   * auto) and still never takes the hit inside a table row — elementFromPoint
   * 16px from centre returns the tr, at any z-index on either the pseudo or
   * the input. It DOES take the hit for a checkbox injected into a fixed,
   * high-z-index host, which is how that approach came to look verified.
   * Measure a real row before changing this.
   */
  padding: calc((var(--touch-target-min) - var(--checkbox-mark-size)) / 2);
  /*
   * The 44px box takes the touch; it must not also DRAW one. Left visible,
   * the border outlined the whole target and a phone cases card showed nine
   * 44pt rectangles that read as empty input fields rather than checkboxes —
   * which is the dense-table regression the mark size exists to avoid.
   * Transparent here, redrawn at mark size on ::after below.
   */
  border-color: transparent;
  /* Component shadows must not outline the invisible touch-size box. */
  box-shadow: none;
  position: relative;
}

/*
 * The visible control, at mark size, inside the touch-size box.
 *
 * ::after and not ::before: daisyUI paints the tick on ::before as a 22px
 * clip-path polygon rotated 45deg, so an outline declared there replaces the
 * checkmark — measured, it rendered as a single black wedge overflowing the
 * control. ::after is unused by the component and inherits the padding box,
 * so the same inset arithmetic that centres the tick centres this.
 *
 * pointer-events: none — the input keeps every hit; this is paint only.
 */
/*
 * The daisyUI size modifier decides the painted mark, not the hit box.
 *
 * The touch rule above grows every checkbox to the 44pt floor and paints the
 * visible control at `--checkbox-mark-size`, which was a single global 1.5rem.
 * That made `checkbox-sm` inert: measured on /evidence, a row selector marked
 * `checkbox checkbox-sm bao-touch-target` painted the same 24px mark as an
 * unmodified checkbox, so the modifier every dense table carries changed
 * nothing and the tables it was meant to compact rendered at the default size.
 *
 * The size comes from daisyUI's own `--size-selector` — the variable its
 * `.checkbox-sm` rule multiplies by 5, against 6 for the base control — so the
 * mark tracks the component's scale instead of restating a pixel value. The
 * 44pt box, and the hit region measured through it, are untouched.
 */
.checkbox-sm.bao-touch-target {
  --checkbox-mark-size: calc(var(--size-selector, 0.25rem) * 5);
}

.checkbox.bao-touch-target::after {
  content: "";
  position: absolute;
  inset: calc((100% - var(--checkbox-mark-size)) / 2);
  /*
   * The boundary is the only thing that identifies an unchecked checkbox, so
   * it carries the shared control token rather than a local alpha. At
   * base-content 20% over base-100 it measured 1.53:1 against the surface it
   * sits on — WCAG 2.2 SC 1.4.11 asks 3:1 of a user-interface component
   * boundary, and the sibling radio was already at 4.64:1, so the two
   * selection controls disagreed about how visible a selection control is.
   */
  border: 1px solid var(--border-control, var(--color-base-300));
  /* Inherited from the .checkbox this pseudo belongs to — see the cap there. */
  border-radius: var(--checkbox-mark-radius, var(--radius-xs, 0.25rem));
  pointer-events: none;
}

/* Gated hover utilities, scrim SSOT, and readable-disabled live in
   tokens-glass-hover-scrim.ts (400-LOC monolith gate). */


}
@layer bao-tokens {

/* ============================================================
   Glass control — baobeep nested chrome (no glass-on-glass)
   ============================================================
   Toolbar / workflow controls nested inside a glass shell use a
   vibrancy fill without a second backdrop-filter layer. Tokens from
   resources/bao/tokens/tokens.bao (color.glass + color.liquid). */

.bao-glass-control {
  min-height: var(--touch-target-min, 2.75rem);
  min-width: var(--touch-target-min, 2.75rem);
  background-color: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-control) * 100%),
    transparent
  );
  border-color: var(--liquid-border-clear);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* iOS and iPadOS zoom the whole viewport whenever a focused form control
   computes under 16px, and this product ships into WKWebView through the
   Tauri mobile shell. daisyUI sizes -sm at 0.875rem and -xs smaller still,
   so the badge, PIN, search and settings fields all yanked the layout out
   from under the operator the moment the keyboard opened. Coarse pointer
   rather than viewport width: an iPad at desktop width zooms identically,
   and a narrow desktop window does not. Doubled class selectors outrank
   daisyUI's single-class sizing on specificity, so the floor holds on
   specificity alone — no priority override, no dependence on load order. */
@media (pointer: coarse) {
  .bao-table-inline-editor,
  .input,
  .select,
  .textarea,
  .file-input,
  .input.input-xs,
  .input.input-sm,
  .select.select-xs,
  .select.select-sm,
  .textarea.textarea-xs,
  .textarea.textarea-sm,
  .file-input.file-input-xs,
  .file-input.file-input-sm {
    font-size: var(--text-base);
  }
}

@media (hover: hover) and (pointer: fine) {
  .bao-glass-control:hover {
    background-color: color-mix(
      in oklab,
      var(--color-primary) 18%,
      transparent
    );
    border-color: var(--liquid-border-regular);
    box-shadow: inset 0 1px 0 var(--glass-highlight-soft);
  }
}
.bao-glass-control:focus-visible {
  background-color: color-mix(
    in oklab,
    var(--color-primary) 18%,
    transparent
  );
  border-color: var(--liquid-border-regular);
  box-shadow: inset 0 1px 0 var(--glass-highlight-soft);
  outline: var(--focus-ring-width, 2px) solid var(--color-primary, currentColor);
  outline-offset: var(--focus-ring-offset, 2px);
}

/* One dimension control morphs from a flat plane to a spatial volume. */
.bao-dimension-toggle {
  color: var(--text-primary);
}
.bao-dimension-toggle.swap-active {
  color: var(--color-primary);
  background-color: color-mix(in oklab, var(--color-primary) 20%, var(--glass-control-fill));
  border-color: color-mix(in oklab, var(--color-primary) 42%, var(--liquid-border-clear));
}
@media (prefers-reduced-motion: reduce) {
  .bao-dimension-toggle .swap-on,
  .bao-dimension-toggle .swap-off {
    transition: none;
  }
}

/* Nested soft buttons on glass shells: auto glass-control (no second frost). */
.bao-surface-glass-regular .btn-soft.bao-toolbar-action,
.bao-surface-glass-regular .btn-soft:not(.btn-primary):not(.btn-error),
.bao-surface-glass-clear .btn-soft.bao-toolbar-action {
  background-color: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-control) * 100%),
    transparent
  );
  border-color: var(--liquid-border-clear);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Transient menus enter the top layer so their blur samples page content, not parent glass. */
.bao-dropdown-panel:popover-open,
.bao-dropdown-panel[data-popover-open="true"] {
  background-color: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
    transparent
  );
  border-color: var(--liquid-border-regular);
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  box-shadow: var(--shadow-glass-floating);
}

/* A popover panel's display belongs to its open state, never to a utility
   class in markup. Tailwind's display utilities sit directly in
   @layer utilities, while daisyUI's closed-popover display:none sits in a
   NESTED sublayer of it, and a rule outside a nested sublayer wins over one
   inside it whatever the specificity. Two panels carried the flex utility and
   so stayed laid out while closed: invisible at opacity 0, yet hit-testing
   over the page beneath them and focusable by keyboard. Declaring the layout
   on the open state keeps a closed panel out of layout entirely and gives an
   open one the flex row its wrap and alignment utilities assume. */
[data-dropdown-panel][popover]:popover-open {
  display: flex;
}

/* Scroll-edge: sticky glass topbar gains separation as content scrolls under. */
#app-header.bao-surface-glass-regular {
  box-shadow:
    var(--shadow-glass-elevated, 0 8px 32px -4px var(--glass-shadow-strong), 0 2px 8px -2px var(--glass-shadow-soft)),
    0 1px 0 0 color-mix(in oklab, var(--color-base-300) 60%, transparent);
}

/* Scrolled state (data-scroll-edge from app-shell-glass.ts): deeper, broader
   shadow + stronger bottom edge while content passes beneath using the canonical scroll-edge state. */
#app-header.bao-surface-glass-regular[data-scroll-edge="true"] {
  box-shadow:
    var(--shadow-glass-floating, 0 12px 48px -8px var(--glass-shadow-strong), 0 4px 16px -4px var(--glass-shadow-soft)),
    0 1px 0 0 color-mix(in oklab, var(--color-base-300) 85%, transparent);
}

/* The shadow above is the scrolled affordance and survives every appearance
   mode. The blur is the MATERIAL, and it must not: this selector is (1,2,0)
   and unconditional, so it outranked both suppression paths at once. Read off
   the engine's own matched-rule list under Increased Contrast with glass on,
   `backdrop-filter: none` from the accessibility block — (1,1,0) on
   `#app-header.navbar` — was matched and then overridden right here, and the
   topbar kept blur(20px) for an operator who had asked the OS for more
   contrast. It beat the product toggle too, since (1,2,0) outranks
   `html[data-high-contrast="true"] .bao-glass-small` at (0,2,1); that path
   only looked correct because the audit never scrolled the page, and this
   rule needs `data-scroll-edge="true"` to apply at all.

   Gated the way its sibling in tokens-glass-shell-webview-theme-part-3 already
   is — OS media query and product attribute together, since the two switches
   are independent and either one alone leaves the other unhonoured. */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-header.bao-surface-glass-regular[data-scroll-edge="true"] {
    -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
    backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
  }
}

/* Page content column: reclaim vertical chrome waste from the canonical density tokens. */
.bao-page-track {
  padding-block: var(--space-micro, 0.25rem);
  padding-inline: min(var(--space-inline, 0.5rem), 5%);
  max-width: 100%;
}
.bao-page-header + [data-case-journey-host],
.bao-page-header + section[data-case-journey-host] {
  margin-top: 0;
}

/* Nested grid/flex hosts must not invent columns — fill the panel cell. */
.bao-grid-reactive-panels > * {
  min-width: 0;
  min-height: 0;
}
/* Widget projection: a lone card/primitive fills its panel cell. Stacked
   panel content (e.g. FilterBar above a list section) must NOT inherit the
   fill — percentage heights inside the auto-height panel inflate the first
   child and strand its controls at the bottom of a blank band. */
.bao-grid-reactive-panels > * > .card:only-child,
.bao-grid-reactive-panels > * > [data-dashboard-widget]:only-child,
.bao-grid-reactive-panels > * > [data-primitive]:only-child {
  height: var(--bao-panel-height, 100%);
  min-height: 0;
}

/* ============================================================
   Composite chrome (LAW2) — one class replaces ≥8-token utility clones
   ============================================================ */

/* Empty / error / denied / offline host stack (page state surfaces).

   No :not([hidden]) guard and no [hidden] counter-rule: the global
   [hidden]:not([hidden="until-found"]) owner in tokens-daisyui-emission
   outranks this (0,1,0) display, so the attribute means what it says here
   without this block restating it. */
.bao-state-stack,
[data-primitive="EmptyState"],
[data-primitive="ErrorState"],
[data-primitive="DeniedState"] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-micro, 0.25rem);
  text-align: center;
  padding-block: var(--space-section);
  padding-inline: var(--space-section);
}

/* Case-queue / dropdown menu row chrome. */
.bao-menu-item {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-inline, 0.5rem);
  border-radius: var(--radius-field, 0.75rem);
  padding-inline: var(--space-section);
  padding-block: var(--space-micro, 0.25rem);
  cursor: pointer;
  min-width: 0;
}

.bao-menu-item:focus-visible {
  background-color: var(--color-base-300, var(--bg-surface));
}

/* Page header chrome edge (border + density) — layout is grid SSOT above. */
.bao-page-header {
  flex-shrink: 0;
  border-block-end: 1px solid color-mix(in oklab, var(--color-base-300, var(--border-base)) 40%, transparent);
  padding-block-end: var(--space-micro, 0.25rem);
}

/* Header action cluster. */
.bao-page-header-actions {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs, 0.125rem);
  justify-content: flex-end;
}

.bao-action-inline > .fieldset {
  padding-block: 0;
}


/* Card/panel body density — flex column fill with micro gap. */
.bao-panel-body,
.card-body.bao-panel-body {
  display: flex;
  min-height: 0;
  min-width: 0;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--space-inline);
  padding-block: var(--space-page);
  padding-inline: min(var(--space-page), 5%);
}

/* Stat / data panel shell — card + metric host stack. */
.bao-stat-panel {
  position: relative;
  display: flex;
  min-width: 0;
  flex-direction: column;
  overflow-x: clip;
  background-color: var(--color-base-100, var(--bg-panel));
  scroll-margin-top: var(--space-section, 0.75rem);
}

.bao-stat-panel:focus-within {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--color-primary, var(--accent-primary)) 40%, transparent);
}

/* Form fieldset density for filter/search bars. */
.bao-field-stack {
  display: flex;
  min-width: 0;
  width: 100%;
  flex: none;
  flex-direction: column;
  gap: var(--space-micro, 0.25rem);
}

/* Plain fields take their outer spacing from the form, not inherited fieldset padding. */
.bao-form-field {
  padding-block: 0;
}

/* Explicit labels participate in their parent gap; they need no second margin. */
.fieldset-legend:not(legend) {
  padding-block: 0;
  margin-block: 0;
}

/* Native legends sit outside the flex gap; one spacing owner prevents doubled gaps. */
fieldset > legend.fieldset-legend,
.bao-field-stack > .fieldset-legend {
  padding-block: 0;
  margin-block-start: 0;
  margin-block-end: var(--space-micro);
}

@media (min-width: 40rem) {
  .bao-field-stack {
    flex: 1 1 auto;
  }
}

/* Trailing action cluster in filter bars / headers. */
.bao-action-end {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-micro, 0.25rem);
  align-self: flex-end;
}

@media (max-width: 39.99rem) {
  .bao-action-end {
    width: 100%;
  }
}

/* Filter action spacing belongs to its semantic host, including runtime-created hosts. */
@media (min-width: 40rem) {
  [data-filter-actions] {
    margin-inline-start: auto;
  }
}

/* Dialog chrome header row. */
.bao-dialog-chrome {
  display: flex;
  flex-shrink: 0;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inline, 0.5rem);
  padding-inline: var(--space-section);
  padding-block: var(--space-micro, 0.25rem);
  border-block-end: 1px solid var(--color-base-300, var(--border-base));
}
.bao-dialog-chrome > [data-dialog-title-block] {
  min-width: 0;
  flex: 1 1 auto;
}
.bao-dialog-chrome > form {
  flex: 0 0 auto;
  width: fit-content;
  max-width: fit-content;
}
/*
 * Compact-only back control. Layered `md:hidden` loses to unlayered `.btn`
 * display, so this lives in the token layer with the header.
 */
@media (min-width: 48rem) {
  [data-capability-group-back].btn {
    display: none;
  }
}

/* Full-width control fill (select/input density). */
.bao-control-fill {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  background-color: var(--color-base-100, var(--bg-panel));
  color: var(--color-base-content, var(--text-primary));
}

/* Sidebar nav item density chrome (gap/padding/radius). */
.bao-nav-item-chrome {
  gap: var(--space-inline, 0.5rem);
  border-radius: var(--radius-field, 0.75rem);
  padding-inline: var(--space-inline, 0.5rem);
  padding-block: var(--space-micro, 0.25rem);
}

.bao-nav-item-chrome:focus-visible {
  background-color: var(--color-base-200, var(--bg-surface));
}

/* Shell list / overflow menu row.

   The touch floor lives here rather than on a `bao-touch-target` in markup.
   Its sibling `.bao-menu-item` is paired with that class at every one of its
   call sites, but the 86 command-palette rows in the app shell and the 83 in
   the device shell are not — and the palette is the primary way to reach a
   page on a phone, so its rows computed to roughly 28pt against the 44pt
   minimum the canon declares (hig-canon.json `hitTargetMinPt`). A floor on the
   row class cannot be forgotten by the next caller the way a markup class can.

   The press and focus treatments are here for the same reason: the shared
   `:active` rules in tokens-glass-motion key off `bao-touch-target`, so
   without them a tap on a palette row produced no acknowledgement at all,
   and `.bao-hover-soft:hover` is correctly gated to fine pointers. */
.bao-shell-list-row {
  display: flex;
  width: 100%;
  min-width: 0;
  min-height: var(--touch-target-min, 2.75rem);
  align-items: center;
  gap: var(--space-inline, 0.5rem);
  border-radius: var(--radius-field, 0.75rem);
  padding-inline: var(--space-section);
  padding-block: var(--space-micro, 0.25rem);
  text-align: start;
}

.bao-shell-list-row:active,
.bao-shell-list-row:focus-visible {
  background-color: var(--color-base-300, var(--bg-surface));
}

/* Responsive trailing action stack (filter footers, form actions). */
.bao-action-end-stack {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-inline, 0.5rem);
}

@media (max-width: 39.99rem) {
  .bao-action-end-stack {
    flex-direction: column;
    align-items: stretch;
  }

  /*
   * A stacked action already fills the row, so a label that cannot break runs
   * straight past the viewport instead of wrapping into it. daisyUI's .btn is
   * white-space: nowrap, which makes the control's min-content width the full
   * width of its label, and a stretched flex item cannot shrink below that.
   *
   * Measured on /licensing-admin at 390px with root font 200%: "Activate this
   * device online" rendered 459px wide inside a 276px stack and its right edge
   * reached 516px — off screen, unreachable, and a WCAG 1.4.4 failure, since
   * text must resize to 200% without loss of content or functionality.
   *
   * The label span needs no rule of its own: white-space inherits, so
   * releasing the control releases the text inside it. Scoped to this stack so
   * the editor toolbar's label-reveal, which collapses .bao-toolbar-label to a
   * zero max-inline-size, is untouched.
   *
   * The :not() chain is specificity, not filtering, and it is load-bearing.
   * A plain `.bao-action-end-stack .btn` is (0,2,0) and loses to two later
   * rules that buy weight the same way: `.btn:not([hidden]):not(.hidden)
   * :not(.bao-fab)` at (0,4,0) re-asserts nowrap, and
   * `.bao-touch-target:not([hidden]):not(.hidden)` at (0,3,0) re-asserts the
   * 2.75rem floor. Measured: with the plain selector the emitted rule reached
   * the browser, matched the control, and still lost — computed white-space
   * stayed nowrap and min-width stayed at the token. Carrying the same guards
   * plus the ancestor makes this (0,5,0), which is the smallest weight that
   * actually wins, and it keeps the hidden/fab exclusions those rules intend.
   *
   * white-space alone, deliberately. An earlier revision also set min-width: 0
   * to let the flex item shrink past its content, and that is unnecessary once
   * the text can break — min-content collapses to the longest word, which fits
   * the stack. It is also unsafe: min-width: 0 cancels the 2.75rem touch floor
   * that .bao-touch-target exists to guarantee, and a stack that is not being
   * stretched would then be free to render a control under 44pt.
   *
   * .bao-toolbar-action carries the same release because the same failure
   * reaches it outside any stack. Measured on /setup at 390px with root font
   * 200%: "Open full models page" rendered 359px wide inside a 292px section
   * and reached 408px. Icon-only toolbar buttons share that class and are
   * unaffected — white-space: normal permits a break, it does not force one,
   * and a control with no text has nothing to break.
   */
  .bao-action-end-stack .btn:not([hidden]):not(.hidden):not(.bao-fab),
  .btn.bao-toolbar-action:not([hidden]):not(.hidden):not(.bao-fab) {
    white-space: normal;
  }

  /*
   * A form label wraps on a phone rather than running off the edge. daisyUI
   * declares `.label { white-space: nowrap }` in its own layer, and the text
   * inside inherits it, so a long label cannot use the room its container has.
   *
   * Measured on /integrations at 390px: "Retry on failure" reached 395px with
   * WCAG text spacing applied — a 1.4.12 Text Spacing failure specifically,
   * since at 200% type alone the same row ends at 307px and fits. Letter and
   * word spacing are exactly what a nowrap label cannot absorb.
   *
   * No specificity guard is needed here, unlike the button rules above, and the
   * reason is layer ORDER rather than weight. daisyUI's `.label` lands in its
   * own `daisyui` layer; this sheet emits into `@layer bao-tokens`. Neither
   * name appears in the `@layer theme, base, components, utilities` statement,
   * so both are ordered by first appearance, and `bao-tokens.css` is linked
   * after `tailwind-daisyui.css` — which puts `bao-tokens` last. A later layer
   * wins whatever the selectors say, so a bare `.label` suffices where `.btn`
   * needed (0,5,0) against same-layer rivals.
   *
   * An earlier revision of this comment said the declaration was UNLAYERED and
   * won on that basis. That was wrong. Walking the emitted sheet's brace depth
   * puts this rule inside `@layer bao-tokens`, and the browser's own
   * matched-rule enumeration reports the same layer. The outcome is identical
   * either way, which is exactly why the wrong mechanism survived — the rule
   * worked, so nothing contradicted the explanation.
   *
   * Confined to the phone breakpoint, where wrapping is preferable to clipping.
   */
  .label {
    white-space: normal;
  }
}

/*
 * A disabled control is rendered inside a tooltip host, so the host — not the
 * control — is the flex item the stack stretches. Measured on /robot at 375px:
 * the host reached the full 317px while the button inside it stayed 119px
 * beside two 317px siblings. The host exists to carry the tooltip, so it
 * carries the stretch through to the one control it holds.
 */
/*
 * Inline action rows reflow instead of escaping their viewport.
 * hig-dynatype measured at 390px root font 400% with WCAG text spacing: the
 * class owned only child overrides, so the row held max-content width past
 * the phone viewport. Wrap beats clip at WCAG 1.4.10; min-inline-size keeps
 * every wrapped row shrinkable inside flex ancestors with min-w-0 chains.
 */
.bao-action-inline {
  display: flex;
  flex-wrap: wrap;
  min-inline-size: 0;
  max-inline-size: 100%;
}

@media (max-width: 39.99rem) {
  .bao-action-end-stack > .tooltip,
  .bao-action-inline > .tooltip {
    display: flex;
    width: 100%;
  }

  .bao-action-end-stack > .tooltip > .btn,
  .bao-action-inline > .tooltip > .btn {
    flex: 1 1 auto;
  }
}

/* Card action row at panel foot. */
.bao-card-actions {
  position: relative;
  z-index: var(--z-table-action, 1);
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-micro, 0.25rem);
  padding-inline: var(--space-inline, 0.5rem);
  padding-bottom: var(--space-inline, 0.5rem);
  padding-top: 0;
  min-width: 0;
}

/* Compact bordered inline toolbar (widget tool strips). */
.bao-inline-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-micro, 0.25rem);
  border-radius: var(--radius-field, 0.75rem);
  border: 1px solid color-mix(in oklab, var(--color-base-300, var(--border-base)) 70%, transparent);
  background-color: var(--color-base-100, var(--bg-panel));
  padding: var(--space-inline, 0.5rem);
  min-width: 0;
}

/* Shell topbar overflow menu panel. */
.bao-shell-dropdown {
  z-index: var(--z-shell-topbar, 40);
  border-radius: var(--radius-box, 1rem);
  border: 1px solid var(--color-base-300, var(--border-base));
  background-color: color-mix(
    in oklab,
    var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
    transparent
  );
  -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
  padding: var(--space-micro, 0.25rem);
  width: var(--sidebar-expanded, 16rem);
  gap: var(--space-micro, 0.25rem);
}

}
@layer bao-tokens {

@layer bao-tokens {
/* Dynatype text-reflow (HIG Dynamic Type / WCAG 1.4.4): at enlarged root
   font sizes every control row must stay inside the viewport. Buttons may
   break their labels, inputs flex within their row, join groups and
   description-list cells release both axes, and sticky or floating shells
   never exceed the viewport. No visual change at default type size. The
   whole block ships as one top-level bao-tokens layer so it outranks the
   utility and component classes it intentionally overrides (w-full width,
   nowrap labels and tooltips, fixed minima) — a nested layer would lose
   to every one of them. */
form .btn,
.card .btn,
.bao-action-inline .btn,
.join .btn {
  min-inline-size: 0;
  max-inline-size: 100%;
  flex-shrink: 1;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Truncating labels keep their one-line ellipsis contract: the button
   may wrap its plain labels, but a .truncate child is an explicit
   single-line decision and wins over the wrap default above. */
form .btn .truncate,
.card .btn .truncate,
.bao-action-inline .btn .truncate,
.join .btn .truncate {
  white-space: nowrap;
  max-inline-size: 100%;
}

.bao-action-inline .input,
.bao-action-inline .select,
fieldset .input,
fieldset .select {
  min-inline-size: 0;
  max-inline-size: 100%;
}

dl.flex > div,
dl.flex > div > dt,
dl.flex > div > dd {
  min-inline-size: 0;
  max-inline-size: 100%;
}

dl.flex > div > dt,
dl.flex > div > dd {
  overflow-wrap: anywhere;
}

dl.flex > div {
  flex-wrap: wrap;
}

.list-row > *,
.toast,
.toast .alert,
.bao-context-badge,
.bao-context-badge > *,
.bao-ai-provenance-sticky,
.bao-ai-provenance-sticky dd,
.bao-filter-bar-sticky,
.bao-data-panel .flex > *,
.bao-filter-bar-sticky .flex > *,
.bao-editor-canvas .flex > *,
.mt-space-inline .flex > *,
.bao-composer-shell .flex > * {
  min-inline-size: 0;
  max-inline-size: 100%;
}

.list-row > *,
.toast .alert,
.bao-context-badge,
.bao-context-badge > *,
.bao-ai-provenance-sticky dd {
  overflow-wrap: anywhere;
}

.tooltip::before,
.tooltip::after {
  white-space: normal;
  overflow-wrap: anywhere;
  max-inline-size: 100vw;
}

/* Wrap-capable children of wrapping rows: tab labels and wrapped-row
   buttons release their per-item minimum so an 11px overflow never
   becomes a viewport escape. Progress meters inside description cells
   may compress — the value text beside them stays authoritative. */
.flex.flex-wrap > *,
.tabs-box .tab,
dd .progress {
  min-inline-size: 0;
  max-inline-size: 100%;
}

.tabs-box .tab {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* A button in a wrapping row must release daisyUI's flex-shrink: 0 —
   the row wraps BETWEEN items, but a single item wider than the row
   still has to compress so its label can break. max-inline-size: 100%
   hard-caps the button to its row, so intrinsic label width can never
   push it past the viewport even while the row is still settling. */
.flex.flex-wrap > .btn {
  flex-shrink: 1;
  min-inline-size: 0;
  max-inline-size: 100%;
}

/* Toolbar labels wrap unless explicitly truncating. */
span.bao-toolbar-label {
  white-space: normal;
  overflow-wrap: anywhere;
  min-inline-size: 0;
  max-inline-size: 100%;
}

span.bao-toolbar-label.truncate {
  white-space: nowrap;
}

/* Scoped dynatype release: the models voice-actions panel host. Its action
   button is a nowrap flex row, so at root font 400% the toolbar label cannot
   fold under the glyph and the row escapes the viewport (measured /models
   "Voice command" span right edge 449px on a 390px viewport, audit:hig).
   Let the button wrap glyph and label into two lines inside the panel. */
[data-voice-actions-panel] .btn {
  flex-wrap: wrap;
}

/* Floating editor chrome must stay within the stage column, not the pannable
   canvas surface: 100cqw resolves against the editor-stage inline-size
   container (the canvas column), so the sticky context bar can never track
   the canvas' JS-driven width. The horizontal scroller keeps long context
   rows reachable inside the cap (measured /editor "Custody" dt right edge
   450px on a 390px viewport, audit:hig). */
.bao-editor-canvas .sticky {
  min-inline-size: 0;
  max-inline-size: 100cqw;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
}

}
@layer bao-tokens {
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .btn.bao-glass-control:not([hidden]):not(.hidden) {
    background-color: var(--glass-control-fill);
    border-color: var(--liquid-border-clear);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

}
@layer bao-tokens {

/* Pathologist workflow current step — page SSOT via data-page + session-mode.
   Capture sets data-session-mode (acquire|annotate) from app-shell-session-mode. */
[data-page="capture"][data-session-mode="annotate"] [data-pathologist-workflow-step="annotate"],
[data-page="capture"][data-session-mode="acquire"] [data-pathologist-workflow-step="capture"],
[data-page="capture"]:not([data-session-mode]) [data-pathologist-workflow-step="capture"],
[data-page="ai-analysis"] [data-pathologist-workflow-step="ai-analysis"],
[data-page="evidence"] [data-pathologist-workflow-step="evidence"],
[data-page="reports"] [data-pathologist-workflow-step="reports"],
[data-page="cases"] [data-pathologist-workflow-step="cases"] {
  border-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
  background-color: color-mix(in oklab, var(--color-primary) 12%, var(--bg-panel));
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-primary) 30%, transparent);
}

[data-page="capture"][data-session-mode="annotate"] [data-pathologist-workflow-step="annotate"] .badge,
[data-page="capture"][data-session-mode="acquire"] [data-pathologist-workflow-step="capture"] .badge,
[data-page="capture"]:not([data-session-mode]) [data-pathologist-workflow-step="capture"] .badge,
[data-page="ai-analysis"] [data-pathologist-workflow-step="ai-analysis"] .badge,
[data-page="evidence"] [data-pathologist-workflow-step="evidence"] .badge,
[data-page="reports"] [data-pathologist-workflow-step="reports"] .badge {
  background-color: var(--color-primary);
  color: var(--color-primary-content);
  border-color: transparent;
}

/*
 * Case-row next actions (join) — not a page content workflow panel.
 * Clinical path SSOT is shell sidebar order; row CTAs are contextual shortcuts.
 */
.bao-pathologist-journey {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: 0;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

.bao-pathologist-journey .bao-toolbar-action.btn-square,
.bao-pathologist-journey .bao-toolbar-action {
  width: auto;
}

/*
 * Progressive path CTAs: icon-first on fine pointer; labels on hover/focus
 * (design.pdf hover-to-reveal). Touch keeps ≥44px targets with labels hidden
 * under compact width so the row does not steal case-card geometry.
 */
.bao-pathologist-journey .bao-toolbar-label,
.bao-pathologist-journey .join-item.btn .bao-toolbar-label,
.bao-pathologist-journey .join-item.btn .text-2xs {
  display: inline-block;
  max-width: 4.5rem;
  margin-inline-start: var(--space-micro, 0.25rem);
  overflow: hidden;
  white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {
  .bao-pathologist-journey .bao-toolbar-label,
  .bao-pathologist-journey .join-item.btn .bao-toolbar-label,
  .bao-pathologist-journey .join-item.btn .text-2xs {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    max-width: 0;
    opacity: 0;
  }
  .bao-pathologist-journey .join-item.btn:hover .bao-toolbar-label,
  .bao-pathologist-journey .join-item.btn:focus-visible .bao-toolbar-label,
  .bao-pathologist-journey .join-item.btn:hover .text-2xs,
  .bao-pathologist-journey .join-item.btn:focus-visible .text-2xs {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    margin-inline-start: var(--space-micro, 0.25rem);
    clip: auto;
    max-width: 4.5rem;
    opacity: 1;
  }
}

/* An open action menu is an explicit disclosure surface: keep every action
   verb visible instead of inheriting the journey rail's hover-only captions. */
.bao-pathologist-journey .bao-dropdown-panel-viewport .bao-toolbar-label,
.bao-pathologist-journey .bao-dropdown-panel:popover-open .bao-toolbar-label,
.bao-pathologist-journey .bao-dropdown-panel[data-popover-open="true"] .bao-toolbar-label {
  display: inline-block;
  position: static;
  width: auto;
  height: auto;
  margin-inline-start: var(--space-micro, 0.25rem);
  overflow: visible;
  clip: auto;
  max-width: 100%;
  opacity: 1;
  white-space: normal;
  overflow-wrap: anywhere;
}

.bao-table-responsive-cell .bao-pathologist-journey,
td .bao-pathologist-journey {
  justify-content: flex-end;
  max-width: min(100%, 28rem);
}
/* Phone density: keep ≥44px targets. Labels stay visible on coarse pointers
   (design.pdf: essentials must not depend on hover); fine-pointer hover rules
   above still densify desktop. */
@media (max-width: 47.99rem) {
  .bao-pathologist-journey .join-item.btn {
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    padding-inline: var(--space-inline, 0.5rem);
  }
  @media (hover: hover) and (pointer: fine) {
    .bao-pathologist-journey .join-item.btn .text-2xs,
    .bao-pathologist-journey .join-item.btn .bao-toolbar-label {
      display: none;
    }
  }
}

/* Slim case-journey host — solid in-flow strip (content plane, no glass).
   Placement belongs to the journey-summary layout module. */
[data-case-journey-host] {
  min-width: 0;
  margin-block: 0;
  padding-block: var(--space-2xs, 0.125rem);
  padding-inline: var(--space-micro, 0.25rem);
}
[data-case-journey-host] > h2 {
  display: none;
}
/* One-row journey band: rail + next-step hint share the line — a stacked
   hint row doubled the sticky band height (design.pdf §5.1 density). */
[data-case-journey] {
  min-width: 0;
  width: 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-micro, 0.25rem) var(--space-section, 0.75rem);
}
[data-case-journey] > * {
  margin-block: 0;
}
[data-case-journey] [data-case-journey-hint],
[data-case-journey] > p {
  flex: 1 1 auto;
  min-width: min(8rem, 60vw);
  margin: 0;
  text-align: end;
}
[data-case-journey] [data-case-journey-hint]:empty {
  display: none;
}
/*
 * text-align:end above is correct while the paragraph trails the steps rail.
 * The empty state renders no steps, so the paragraph becomes the *leading*
 * element of the row and end-alignment shoved the lone sentence up against the
 * action button, leaving a ragged gap at the inline start.
 */
[data-case-journey][data-state="empty"] > p {
  text-align: start;
}
/*
 * Dense horizontal journey — re-implement daisyUI steps as a fixed 5-track
 * grid (WebView 101 / design.pdf density). Caps width so connectors never
 * leave huge voids on wide workbenches.
 */
[data-case-journey] .steps,
[data-case-journey] ul.steps,
[data-case-journey] ul[data-case-journey-steps] {
  display: grid;
  /* Auto column tracks — the rail carries however many steps the journey
     declares; a fixed count would silently clip added steps. */
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  width: 100%;
  max-width: min(100%, 40rem);
  margin-inline: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  gap: 0;
  padding-inline: var(--space-micro, 0.25rem);
  min-height: 3.5rem;
  align-items: start;
  justify-items: stretch;
}
/* Journey steps — densify labels, truncate, keep touch targets. */
[data-case-journey] .step {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2xs, 0.125rem);
  min-width: 0;
  max-width: 100%;
  min-height: 0;
  flex: unset;
  grid-template-columns: unset;
  grid-template-rows: unset;
  justify-items: center;
  font-size: var(--text-2xs, 0.6875rem);
  line-height: 1.2;
  padding-inline: var(--space-2xs, 0.125rem);
  padding-block-start: 0;
  overflow: hidden;
  text-align: center;
}
/* Kill daisyUI step min-width / row geometry that forces sprawl. State glyphs
   come from the fragment's data-content (done "✓", current "●", else empty) —
   never a step number (workflow labels stay unnumbered). */
[data-case-journey] .step::before {
  content: attr(data-content);
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  min-width: 1.75rem;
  min-height: 1.75rem;
  margin-inline: auto;
  border-radius: var(--radius-full);
  background: var(--color-base-200, var(--bg-surface));
  color: var(--color-base-content, inherit);
  font-size: var(--text-2xs, 0.6875rem);
  font-weight: 600;
  line-height: 1;
  z-index: var(--z-local, 1);
}
[data-case-journey] .step.step-primary::before,
[data-case-journey] .step[data-journey-state="current"]::before,
[data-case-journey] .step[data-journey-state="done"]::before {
  background: var(--color-primary, var(--accent-primary));
  color: var(--color-primary-content, var(--accent-primary-content));
}
/* Connector between equal tracks — half of each cell, never full viewport. */
[data-case-journey] .step::after {
  content: "";
  position: absolute;
  top: 0.85rem;
  inset-inline-start: calc(50% + 0.9rem);
  inset-inline-end: calc(-50% + 0.9rem);
  height: 2px;
  background: color-mix(in oklab, var(--color-base-300, var(--border-base)) 80%, transparent);
  z-index: var(--z-base, 0);
}
/* Dark-mode contrast: --color-base-300 at 80% transparent is barely
   distinguishable from --color-base-100 (both are near-black blues in the
   capture-dx-dark palette). Lift the alpha so the upcoming-path connector
   stays readable as "not-yet" without overdrawing the "done" path. */
[data-theme="capture-dx-dark"] [data-case-journey] .step::after,
[data-theme="capture-dx-dark"] [data-case-journey] .step:not([data-journey-state="done"])::after {
  background: color-mix(in oklab, var(--color-base-300, var(--border-base)) 96%, transparent);
}
[data-theme="capture-dx-dark"] [data-case-journey] .step[data-journey-state="done"]::after {
  background: color-mix(in oklab, var(--color-primary, var(--accent-primary)) 80%, transparent);
}
[data-case-journey] .step:last-child::after {
  display: none;
}
/* Completed spans read as progress: the connector leaving a done step fills
   with the primary accent instead of the idle border tone. */
[data-case-journey] .step[data-journey-state="done"]::after {
  background: color-mix(in oklab, var(--color-primary, var(--accent-primary)) 60%, transparent);
}
[data-case-journey] .step a,
[data-case-journey] .step > span,
[data-case-journey] .step .truncate,
[data-case-journey] .step .btn,
[data-case-journey] .step .bao-journey-step-label {
  display: block;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: inherit;
  line-height: 1.2;
  height: auto;
  min-height: 0;
  padding-inline: var(--space-2xs, 0.125rem);
  background: transparent;
  border: 0;
  box-shadow: none;
}
/* Grow the step-label tap target past the WCAG 2.5.8 24px floor (Fitts): the
   13px text alone was untappable. Block padding stays below the daisyUI
   numbered circle (which flows above the label), so the label never collides
   with the circle — the tap area just extends down into the step cell. */
[data-case-journey] .step .bao-journey-step-label {
  padding-block: 0.5rem 0.375rem;
  margin-block-start: calc(-1 * var(--space-micro, 0.25rem));
}
@media (max-width: 47.99rem) {
  [data-case-journey] .steps,
  [data-case-journey] ul.steps,
  [data-case-journey] ul[data-case-journey-steps] {
    max-width: 100%;
    grid-auto-columns: minmax(3.5rem, 1fr);
  }
  /* Narrow cells: wrap step labels to two lines instead of ellipsizing
     ("Finish Ca…"). Clamp keeps the rail geometry stable. */
  [data-case-journey] .step a,
  [data-case-journey] .step .bao-journey-step-label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: center;
  }
}
/* The summary names the current step, progress and accession at every width.
 * Its full rail remains hidden by the canonical fragment, so the capsule must
 * never inherit the former phone-only display rule.
 */
.bao-journey-compact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-inline, 0.5rem);
  min-width: 0;
}
.bao-journey-capsule {
  display: flex;
  flex: 0 1 auto;
  min-width: 0;
  align-items: center;
  gap: var(--space-inline, 0.5rem);
  min-height: var(--touch-target-min, 2.75rem);
  padding-inline: var(--space-inline, 0.5rem);
  border-radius: var(--radius-field, 0.75rem);
  border: 1px solid var(--glass-control-border, color-mix(in oklab, var(--color-base-content) 8%, transparent));
  background: var(--glass-control-fill, color-mix(in oklab, var(--color-base-content) 5%, transparent));
}
/* Accession is readable context, visually distinct from the continuation action. */
.bao-journey-compact-hint {
  display: block;
  min-width: 0;
  max-width: 100%;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  white-space: normal;
  overflow-wrap: anywhere;
}

.bao-journey-compact-hint:empty {
  display: none;
}
@media (max-width: 39.99rem) {
  /* nav+ul selectors out-spec the 47.99rem rail rules that pin display:grid. */
  nav[data-case-journey] ul[data-case-journey-steps],
  [data-case-journey] ul[data-case-journey-steps],
  [data-case-journey] [data-case-journey-steps],
  nav[data-case-journey] p[data-case-journey-hint-row],
  [data-case-journey] [data-case-journey-hint-row] {
    display: none;
  }
}


  /* Clinical navigation stays in document flow. A sticky progress rail covered the first action row while scrolling. */
  [data-case-journey-host] {
    position: relative;
    top: auto;
    z-index: auto;
  }
  .bao-journey-summary {
    width: 100%;
  }
  .bao-journey-summary .bao-journey-capsule {
    max-width: 100%;
  }

  /* Context and commands share one in-flow band; DOM order matches focus order. */
  [data-page-context-bar] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-inline) var(--space-section);
    min-inline-size: 0;
  }
  [data-page-context-bar] > [data-case-journey-host] {
    flex: 1 1 auto;
    min-inline-size: 0;
    padding: 0;
  }
  [data-page-context-bar] > [data-primitive="PageHeader"]:not(.sr-only) {
    flex: 0 1 auto;
    grid-template-columns: minmax(0, 1fr);
    min-inline-size: 0;
    inline-size: auto;
    margin-block: 0;
    padding-block: 0;
    border-block: 0;
    align-self: center;
  }
  [data-case-journey][data-case-journey-empty] {
    justify-content: flex-start;
  }
  [data-case-journey][data-case-journey-empty] > p {
    flex: 0 1 auto;
    min-inline-size: 0;
  }
  @supports (display: grid) {
    [data-page-context-bar] {
      display: grid;
      grid-template-areas: "context" "actions";
      grid-template-columns: minmax(0, 1fr);
    }
    [data-page-context-bar] > [data-case-journey-host] {
      grid-area: context;
    }
    [data-page-context-bar] > [data-primitive="PageHeader"]:not(.sr-only) {
      grid-area: actions;
      justify-self: end;
    }
    @container (min-width: 40rem) {
      [data-page-context-bar] {
        grid-template-areas: "context actions";
        grid-template-columns: minmax(0, 1fr) fit-content(50%);
      }
    }
  }


[data-case-journey] [data-case-journey-steps][hidden],
[data-case-journey] [data-case-journey-hint-row][hidden] {
  display: none;
}

}
@layer bao-tokens {
/* Native dialog defaults to block and otherwise defeats the canonical Sheet side/bottom flex tracks. */
dialog[data-primitive="Sheet"][open] { display: flex; }
dialog[data-primitive="Sheet"] > [data-dialog-dismiss] { z-index: var(--z-base, 0); }
dialog[data-primitive="Sheet"] > .bao-sheet-panel { z-index: var(--z-local, 1); }

}
@layer bao-tokens {

/* ============================================================
   Motion transition primitives
   ============================================================
   All motion goes through happydumpling tokens. No raw transitions
   in consumer code. */

.bao-motion-transition-colors {
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-normal);
}

.bao-motion-transition-opacity {
  transition-property: opacity;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-normal);
}

.bao-motion-transition {
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-normal);
}

/* design.pdf §6 press/hover for glass chrome and touch controls.
 * Context chips: color/border only — never transform/opacity. HTMX outerHTML
 * remounts + translateY/scale transitions = WebView 101 vibration thrash. */
.bao-surface-glass-reactive,
.bao-touch-target,
.bao-touch-target.bao-motion-transition-colors,
.btn.bao-touch-target,
.btn:not([hidden]):not(.hidden),
.bao-toolbar-action,
.bao-nav-item {
  transition-timing-function: var(--easing-standard);
}

.bao-context-card,
[data-capture-context],
[data-chat-context-card],
.bao-shell-context-chip {
  transition-property: color, background-color, border-color, box-shadow;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-fast, 120ms);
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  /* Transform only — material tint/edge/shadow for the hover state is
     owned by tokens-glass-base.ts (one owner per concern). */
  .bao-surface-glass-reactive:where(button, a, [role="button"]):hover,
  .bao-nav-item:hover,
  .btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):hover {
    transform: translateY(-1px);
  }
}

.bao-surface-glass-reactive:where(button, a, [role="button"]):active,
.btn.bao-touch-target:active:not(:disabled),
.btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):active,
.bao-nav-item:active {
  transform: scale(0.985);
}


/*
 * The reduced-motion override has to match the specificity of the rules above
 * it, not merely follow them. The press/hover rules are written as
 * `.btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"])`
 * — (0,6,0) — while this block read `.btn:not([hidden]):not(.hidden)` — (0,4,0)
 * — so the cascade never reached it. Measured on /capture at 1440x900 with the
 * query emulated: a pressed `.btn` computed `matrix(0.985, 0, 0, 0.985, 0, 0)`
 * and a hovered one `matrix(1, 0, 0, 1, 0, -1)`. The selectors are mirrored
 * exactly so equal specificity resolves on source order.
 */
@media (prefers-reduced-motion: reduce) {
  .bao-surface-glass-reactive:where(button, a, [role="button"]):hover,
  .bao-surface-glass-reactive:where(button, a, [role="button"]):active,
  .btn.bao-touch-target:active:not(:disabled),
  .btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):hover,
  .btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):active {
    transform: none;
  }
  .bao-nav-item:hover,
  .bao-nav-item:active {
    transform: scale(1);
  }
}

/*
 * Settings → Accessibility writes the same floor onto the root, and no media
 * query fires for it. Product override uses scale(1); media block owns the
 * sole none (S62).
 */
html[data-reduced-motion="true"] .bao-nav-item:hover,
html[data-reduced-motion="true"] .bao-nav-item:active,
html[data-reduced-motion="true"] .bao-surface-glass-reactive:where(button, a, [role="button"]):hover,
html[data-reduced-motion="true"] .bao-surface-glass-reactive:where(button, a, [role="button"]):active,
html[data-reduced-motion="true"] .btn.bao-touch-target:active:not(:disabled),
html[data-reduced-motion="true"] .btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):hover,
html[data-reduced-motion="true"] .btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]):active {
  transform: scale(1);
}

/* Coarse pointers: never rely on hover-only chrome; keep focus-visible primary. */
@media (hover: none), (pointer: coarse) {
  .bao-surface-glass-reactive:where(button, a, [role="button"]):hover,
  .bao-nav-item:hover {
    transform: scale(1);
    background-color: inherit;
  }
}

/* ============================================================
   Shared pulse utility (Bao-Translate rememberPulseFloat contract)
   One class for recording halo, empty mic, model-init — DRY.
   Reduced motion: static rest (no keyframes registered).
   ============================================================ */

@keyframes bao-pulse-scale {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(var(--pulse-scale-peak, 1.12)); }
}

@keyframes bao-pulse-alpha {
  0%, 100% { opacity: 1; }
  50% { opacity: var(--pulse-alpha-floor, 0.55); }
}

.bao-pulse {
  transform-origin: center;
}

.bao-pulse-scale {
  animation: bao-pulse-scale var(--duration-pulse, 800ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite;
}

.bao-pulse-alpha {
  animation: bao-pulse-alpha var(--duration-pulse-alpha, 1000ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite;
}

.bao-pulse-scale.bao-pulse-alpha {
  animation:
    bao-pulse-scale var(--duration-pulse, 800ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite,
    bao-pulse-alpha var(--duration-pulse-alpha, 1000ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .bao-pulse,
  .bao-pulse-scale,
  .bao-pulse-alpha,
  .animate-pulse {
    animation: none;
    opacity: 1;
    will-change: auto;
  }
}

/*
 * Reduced-motion contract. layout-shell-html.ts writes the preference
 * onto html[data-reduced-motion="true"] so server-rendered chrome and
 * JS-disabled surfaces honor the same floor as the OS-level
 * prefers-reduced-motion query. All interactive shell elements stop
 * motion (transitions, pulse halos, view transitions).
 */
html[data-reduced-motion="true"] *,
html[data-reduced-motion="true"] *::before,
html[data-reduced-motion="true"] *::after {
  animation-duration: var(--reduced-motion-min-duration, 0.001ms);
  animation-iteration-count: 1;
  animation-delay: var(--duration-instant);
  transition-delay: var(--duration-instant);
  transition-duration: var(--reduced-motion-min-duration, 0.001ms);
  scroll-behavior: auto;
}
html[data-reduced-motion="true"] .bao-pulse,
html[data-reduced-motion="true"] .bao-pulse-scale,
html[data-reduced-motion="true"] .bao-pulse-alpha,
html[data-reduced-motion="true"] [data-motion-pulse] {
  animation: none;
  opacity: 1;
}

/* Press acknowledgment — sub-100ms, transform-only (design.pdf §6). */
button.bao-touch-target,
a.bao-touch-target,
summary.bao-touch-target,
label.bao-touch-target[for] {
  transition-property: transform, background-color, border-color, color, box-shadow;
  transition-duration: var(--duration-normal);
  transition-timing-function: var(--easing-glass);
}

button.bao-touch-target:active,
a.bao-touch-target:active,
summary.bao-touch-target:active,
label.bao-touch-target[for]:active {
  transform: scale(0.985);
}

@media (prefers-reduced-motion: reduce) {
  button.bao-touch-target:active,
  a.bao-touch-target:active,
  summary.bao-touch-target:active,
  label.bao-touch-target[for]:active {
    transform: none;
  }
}
/* Product override uses scale(1); media block owns the sole none (S62). */
html[data-reduced-motion="true"] button.bao-touch-target:active,
html[data-reduced-motion="true"] a.bao-touch-target:active,
html[data-reduced-motion="true"] summary.bao-touch-target:active,
html[data-reduced-motion="true"] label.bao-touch-target[for]:active {
  transform: scale(1);
}

/* Table row action menus — pin cells stay hittable. */
.bao-table-pinned-action,
td.bao-table-pinned-action,
th.bao-table-pinned-action {
  overflow: visible;
}

/*
 * Pinned column divider (design.pdf §5.5 sticky-column depth): the old hard
 * border-s + opaque white read as a floating white band when the row zebra
 * did not match. Instead the sticky cell inherits its row's own background
 * (occludes scrolled content) and casts a soft LEFT shadow that only reads
 * as depth when content sits behind it — no hard line, no colour mismatch.
 * The zebra background wiring (tokens-layout-registry-surfaces) still sets
 * the per-row tint; this default covers non-zebra tables.
 */
.bao-table-pinned-divider {
  /* Depth only — this class does not choose a colour.

     It used to resolve a token that fell back to base-100 no matter what the
     surrounding row painted, so the pinned header cell rendered white beside
     base-200 siblings. Inheriting was no better: the header row is itself
     white and each header cell carries its own bg-base-200, so `inherit`
     picked up the row and reproduced the mismatch. Whoever owns the cell's
     colour keeps it — bg-base-200 on the header, the row via `inherit` in
     the body (tokens-layout-registry-surfaces) — and this rule stays out of
     it.

     The depth is CONDITIONAL, because depth is a claim: it says row content
     is passing behind this column. Painted unconditionally it was false on
     every table that fits its scrollport — measured on the dashboard's Recent
     Cases and on /specimens, a 707px table in a 707px port still drew the
     shadow, and with nothing behind it the cue read as a short vertical
     smudge floating beside the row actions rather than as an edge. The
     `bao-table-pinned-scrolls` stamp is written by the column-fit runtime
     from the `scrollWidth > clientWidth` measurement it already takes, and
     cleared at the top of every pass, so the cue appears exactly while it is
     true and no second measurement exists to disagree with the first. */
  .bao-table-pinned-scrolls & {
    box-shadow: -0.5rem 0 0.5rem -0.5rem color-mix(in oklab, var(--color-base-content) 12%, transparent);
  }
}
@container (width < 40rem) {
  /* Card mode: the pinned column is not sticky (bao-table-pinned-unstick) —
     drop the depth shadow so a stacked card row has no orphan edge.
     Beat zebra pin plate (table.table-zebra … .bao-table-pinned-action)
     which otherwise paints a solid base-200 action strip on even cards. */
  .bao-table-pinned-divider,
  tr.bao-table-responsive-row > .bao-table-pinned-action,
  .table.table-zebra > tbody > tr.bao-table-responsive-row > .bao-table-pinned-action,
  .table.table-zebra > tbody > tr.bao-table-responsive-row:nth-child(even) > .bao-table-pinned-action {
    box-shadow: none;
    background-color: transparent;
    --bao-table-pinned-action-background: transparent;
  }
}

@media (max-width: 47.99rem) {
  [data-primitive="DataTable"] .bao-table-pinned-action,
  [data-primitive="DataTable"] details.dropdown {
    position: relative;
    z-index: var(--z-local, 1);
  }
  [data-primitive="DataTable"] tr:focus-within,
  [data-primitive="DataTable"] tr:has(details[open]) {
    z-index: var(--z-raised, 2);
    position: relative;
  }
}

/* Sheet / dialog motion: spring default; fade when reduced-motion. */
.bao-motion-sheet-spring,
.bao-sheet-panel {
  transition-property: transform, opacity;
  transition-duration: var(--duration-normal, 200ms);
  transition-timing-function: var(--easing-emphasized, cubic-bezier(0.2, 0, 0, 1));
}
.bao-motion-sheet-fade {
  transition-property: opacity;
  transition-duration: var(--duration-fast, 120ms);
  transition-timing-function: var(--easing-standard);
}
@media (prefers-reduced-motion: reduce) {
  .bao-motion-sheet-spring,
  .bao-sheet-panel {
    transition-property: opacity;
    transition-duration: var(--duration-fast, 120ms);
    transform: none;
  }
}
/* Product override uses scale(1); media block owns the sole none (S62). */
html[data-reduced-motion="true"] .bao-motion-sheet-spring,
html[data-reduced-motion="true"] .bao-sheet-panel {
  transition-property: opacity;
  transform: scale(1);
}

/* Capability-filtered sidebar empty CTA. */
ul[data-widget="sidebar"][data-state="empty"] [data-sidebar-empty] {
  display: list-item;
}
ul[data-widget="sidebar"][data-state="success"] [data-sidebar-empty],
ul[data-widget="sidebar"] [data-sidebar-empty][hidden] {
  display: none;
}
/* Collapsed rail: tooltips on; expanded: hide tip chrome (label visible). */
[data-sidebar-expanded="true"] .bao-nav-item.tooltip::before,
[data-sidebar-expanded="true"] .bao-nav-item.tooltip::after,
.bao-sidebar-expanded .bao-nav-item.tooltip::before,
.bao-sidebar-expanded .bao-nav-item.tooltip::after {
  display: none;
}

}
@layer bao-tokens {

/* ============================================================
   Nested glass fill plate (baobeep concentric anatomy)
   ============================================================
   Apply to text/form/list hosts nested inside a glass shell.
   Never put bare body copy on .bao-surface-glass-*.
   Content widgets use solid bao-data-panel — do not co-tag glass. */

.bao-glass-fill {
  background-color: var(--glass-fill-bg, var(--bg-panel, var(--color-base-100)));
  border: 1px solid var(--glass-fill-border, color-mix(in oklab, var(--text-primary) 8%, transparent));
  color: var(--text-primary, var(--color-base-content));
  border-radius: calc(var(--radius-box, 1rem) - 2px);
  box-shadow: inset 0 1px 0 var(--glass-highlight-soft);
}

/*
 * S100 — stack fill + interactive hosts above specular (z=2). Never
 * position:relative on every child (breaks absolute/fixed/sticky geometry).
 */
.bao-surface-glass-regular > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]),
.bao-surface-glass-clear > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]),
.bao-surface-glass-strong > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]),
.bao-surface-glass-modal > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]) {
  z-index: var(--z-raised-high, 3);
}
.bao-surface-glass-regular > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]):where(:not(.absolute):not(.fixed):not(.sticky)),
.bao-surface-glass-clear > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]):where(:not(.absolute):not(.fixed):not(.sticky)),
.bao-surface-glass-strong > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]):where(:not(.absolute):not(.fixed):not(.sticky)),
.bao-surface-glass-modal > :is(.bao-glass-fill,.bao-dialog-chrome,[data-result-host],.btn,a,button,input,select,textarea,summary,.menu,.navbar-start,.navbar-center,.navbar-end,.bao-touch-target,.bao-shell-brand,[data-primitive],[role="toolbar"],[role="navigation"],search,[role="search"],[role="menu"],[role="listbox"]):where(:not(.absolute):not(.fixed):not(.sticky)) {
  position: relative;
}

/* KPI / metric card body plate — nested under glass metric shells. */
[data-primitive="Card"][data-variant="metric"] .bao-glass-fill,
.card.bao-surface-glass-regular .bao-glass-fill {
  min-width: 0;
}

/*
 * L0 workbench under L1 shell glass — AnnotationWorkbench + AIAssistant
 * content planes stay solid (design-canon liquidGlass.placementRule.content).
 * L2 floating toolbars/menus keep Toolbar/DropdownMenu glass SSOT.
 */
#app-main [data-primitive="AnnotationWorkbench"] .bao-data-panel,
#app-main [data-primitive="AIAssistant"] .bao-data-panel,
#app-main [data-primitive="AIAssistant"] .card.card-border,
[data-editor-chrome] .bao-data-panel,
[data-capture-chrome] .bao-data-panel {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background-color: var(--bg-panel, var(--color-base-100));
}

/*
 * The media stages are the exception to the panel plate: the specimen canvas
 * and the 3D viewport are content the operator inspects, and Apple's layout
 * guidance wants media on a recessed ground, not floating in the same
 * base-100 white as the panels around it — measured 2026-08-22, the aspect-fit
 * letterbox above and below the slide painted the identical white as the page,
 * so the image's bounds were invisible. `--bg-surface` is the same recessed
 * step `.bg-base-200` resolves to, stated here because the panel plate above
 * outranks any utility class on the same element.
 */
#app-main [data-primitive="AnnotationWorkbench"] [data-editor-canvas-region].bao-data-panel,
#app-main [data-primitive="AnnotationWorkbench"] [data-editor-3d-viewport].bao-data-panel,
[data-editor-chrome] [data-editor-canvas-region].bao-data-panel,
[data-editor-chrome] [data-editor-3d-viewport].bao-data-panel {
  background: var(--bg-surface, var(--color-base-200));
}

/* Reduced transparency / high contrast: fill plate is already solid. */
@media (prefers-reduced-transparency: reduce) {
  .bao-glass-fill {
    background-color: var(--bg-surface, var(--bg-panel));
    box-shadow: none;
  }
}
html[data-reduced-transparency="true"] .bao-glass-fill {
  background-color: var(--bg-surface, var(--bg-panel));
  box-shadow: none;
}

}
@layer bao-tokens {


/* ============================================================
   Page chrome — sticky strip only (blur comes from glass classes)
   ============================================================
   Never re-apply raw backdrop-filter here. Toolbars already carry
   .bao-surface-glass-regular.bao-glass-small so one sheet blurs. */

.bao-page-chrome {
  position: sticky;
  top: 0;
  z-index: var(--z-content-overlay, 10);
  box-shadow: 0 1px 0 0 color-mix(in oklab, var(--border-base) 55%, transparent);
}

.bao-page-chrome::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -0.5rem;
  height: 0.5rem;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    color-mix(in oklab, var(--bg-panel) 35%, transparent),
    transparent
  );
  opacity: var(--opacity-near, 0.9);
}

/*
 * Scroll-edge adaptation (design.pdf §5.1/§7.5) lives with the topbar owners:
 * shadow in tokens-glass-control-chrome.ts, tint in
 * tokens-glass-shell-webview-theme.ts. State: app-shell-glass.ts.
 */


/* ============================================================
   Toolbar actions — labeled page chrome + compact icon chrome
   ============================================================
   Page toolbars (data-toolbar-density="labeled"): always show verb labels.
   Compact chrome (btn-square / floating / table row actions): icon + tooltip at
   rest and during interaction; labeled density keeps permanent captions. Soft glass wash on ghost/chrome only. */

.bao-toolbar-action {
  align-items: center;
  justify-content: center;
  gap: var(--space-compact, 0.375rem);
  overflow: visible;
  /*
   * Per-corner, for the same reason the base `.btn` rule is: a toolbar action
   * is the control most often placed inside a `.join` — pagers, segmented
   * view switches, row action clusters — and a `border-radius` shorthand from
   * this layer overwrites the four `--join-*` corners daisyUI 5 publishes.
   * Outside a join the variables are unset and every corner still resolves to
   * `--radius-selector`, so a standalone toolbar button is unchanged.
   */
  transition-property: background-color, color, box-shadow, border-color, max-width, padding;
  transition-duration: var(--duration-normal);
}


/* Table/row icon chrome: keep HIG 44px hit area on touch (btn-xs square is smaller). */
@media (pointer: coarse) {
  .bao-toolbar-action.btn-square,
  .bao-toolbar-action.btn-xs {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
  }
  /* Queue action clusters: labeled soft buttons, never tooltip-only. */
  .bao-queue-actions[data-toolbar-density="labeled"] .bao-toolbar-action .bao-toolbar-label:not(.truncate) {
    display: inline-block;
    position: static;
    width: auto;
    height: auto;
    max-width: min(calc(100% - var(--space-micro, 0.25rem)), var(--toolbar-label-max-width, 8rem));
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    clip: auto;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
    text-overflow: ellipsis;
    font-size: var(--text-xs, 0.75rem);
  }
}

/* Floating panels opt back into overlay positioning after the glass material
   establishes a positioning context for its specular pseudo-elements.
   The viewport-escape state (app-shell-dropdowns pins open menus so overflow
   ancestors cannot clip them) must keep position: fixed — this reset is
   unlayered and would otherwise beat the layered escape rule. */
.bao-dropdown-panel:not(.bao-dropdown-panel-viewport) {
  position: absolute;
}

/* Glass wash is for chrome/ghost actions only — never bleach filled
   semantic buttons (primary create, error revoke, etc.) or settings tabs
   (active destination uses primary fill; wash would white-on-white). */
@media (hover: hover) and (pointer: fine) {
  .bao-toolbar-action:not(.btn-primary):not(.btn-secondary):not(.btn-accent):not(.btn-error):not(.btn-warning):not(.btn-success):not(.btn-info):not(.bao-settings-tab):not(.bao-session-mode-tab):hover,
  .bao-toolbar-action:not(.btn-primary):not(.btn-secondary):not(.btn-accent):not(.btn-error):not(.btn-warning):not(.btn-success):not(.btn-info):not(.bao-settings-tab):not(.bao-session-mode-tab):focus-visible {
    background-color: color-mix(in oklab, var(--bg-panel) 72%, transparent);
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--liquid-border-regular) 80%, transparent);
  }
}

/*
 * Compact chrome: keep icon-only labels out of the visual layout (btn-square only).
 *
 * Excludes any surface that declared labeled density. This file holds BOTH
 * the default-hide and the labeled-reveal, and the default was winning its own
 * argument: .bao-toolbar-action.btn-square .bao-toolbar-label is (0,3,0) while
 * [data-toolbar-density="labeled"] .bao-toolbar-label below is (0,2,0), so a
 * surface that explicitly asked for labels was overruled by the generic
 * default three-to-two.
 *
 * That is why the editor dock rendered as anonymous glyphs while declaring
 * data-toolbar-density="labeled" — measured at 390, 768 and 1280, collapsed
 * and expanded, max-width computed to 0px every time. Patching the four other
 * modules that also clipped this label changed nothing, because none of them
 * was the rule that won.
 *
 * An explicit declaration must outrank a default. The exclusion is by
 * contract, so no surface has to be named here.
 */
.bao-toolbar-action.btn-square:not([data-toolbar-density="labeled"] *) .bao-toolbar-label {
  display: inline-block;
  max-width: 0;
  margin-inline-start: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 500;
  letter-spacing: var(--tracking-label, 0.01em);
  transition-property: max-width, opacity, margin-inline-start;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-normal);
}

/*
 * Editor key-hint chips ([data-hig-key-hint], emitted by
 * pages-renderers-editor-toolbar) travel with the caption: collapsed at rest
 * on icon-density square actions, revealed with the label on hover/focus.
 * The chip carries padding and a border, so the collapse zeroes those too —
 * otherwise a 0-width chip still paints its pill and crowds the glyph inside
 * daisyUI's pinned .btn-square width (icon left-shifted, badge clipped,
 * hover pill broken). Coarse pointers never see the chip at all: the tooltip
 * owns that path (renderer contract, E2 discoverability).
 */
.bao-toolbar-action.btn-square:not([data-toolbar-density="labeled"] *) [data-hig-key-hint] {
  display: inline-block;
  max-width: 0;
  margin-inline-start: 0;
  padding-inline: 0;
  padding-block: 0;
  border-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition-property: max-width, opacity, margin-inline-start, padding-inline, border-width;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-normal);
}
@media (hover: none),
(pointer: coarse) {
  .bao-toolbar-action [data-hig-key-hint] {
    display: none;
  }
}

/* Touch surfaces keep permanent verbs — hover-to-reveal requires a hover.
   Shell icon chrome (theme/voice/user) stays glyph-only by design. */
@media (pointer: coarse) {
  [data-toolbar-density="icon"] .bao-toolbar-action:not(.bao-shell-icon-action) .bao-toolbar-label:not(.truncate) {
    display: inline-block;
    position: static;
    width: auto;
    height: auto;
    clip: auto;
    max-width: min(calc(100% - var(--space-micro, 0.25rem)), var(--toolbar-label-reveal-max-width, 12rem));
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* Labeled page toolbars: always show verb text. */
.bao-toolbar-action:not(.btn-square) .bao-toolbar-label:not(.truncate),
[data-toolbar-density="labeled"] .bao-toolbar-label:not(.truncate) {
  display: inline-block;
  /*
   * flex: none breaks a circular sizing dependency. Without it the reveal is
   * cosmetic: the label carries overflow: hidden, so its min-content
   * contribution is zero and as a flex item it shrinks to nothing; the button
   * then sizes itself to that collapsed content and settles at 44px — which is
   * the very width that made the label shrink. Measured: max-width 192px,
   * opacity 1, rendered width 0, natural width 59px. Revealed and invisible.
   */
  flex: none;
  max-width: min(100%, var(--toolbar-label-reveal-max-width, 12rem));
  margin-inline-start: 0;
  opacity: 1;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-label, 0.01em);
}

/*
 * A revealed label needs a box to live in — at EVERY pointer type.
 *
 * daisyUI's .btn-square pins width AND height to var(--size), so a control
 * that reveals its caption still paints a 2.75rem square and clips it:
 * measured "Rectangle" at scrollWidth 59px inside a clientWidth of 26px. The
 * caption was visible and unreadable, which is worse than hidden.
 *
 * This sits OUTSIDE any media query on purpose. The coarse-pointer copy of
 * this rule is invisible to any environment reporting pointer: fine — which
 * includes the browser pane at phone width — so a touch-only fix reads as no
 * fix at all while a real device would have been fine. Labeled density is a
 * declaration about the CONTROL, not about the input device.
 */
[data-toolbar-density="labeled"] .bao-toolbar-action.btn-square {
  width: auto;
  max-width: none;
  min-width: var(--touch-target-min, 2.75rem);
  min-height: var(--touch-target-min, 2.75rem);
  height: auto;
  padding-inline: var(--toolbar-action-pad-inline, 0.625rem);
}

[data-primitive="FilterBar"] .filter-reset,
[data-primitive="FilterBar"] [data-case-filter-reset],
form[data-case-filters] [data-case-filter-reset] {
  width: auto;
  max-width: none;
  min-width: var(--touch-target-min, 2.75rem);
  min-height: var(--touch-target-min, 2.75rem);
  justify-content: center;
  gap: var(--space-micro, 0.25rem);
  padding-inline: var(--space-inline, 0.5rem);
}
[data-primitive="FilterBar"] .filter-reset .bao-toolbar-label,
[data-primitive="FilterBar"] [data-case-filter-reset] .bao-toolbar-label,
form[data-case-filters] [data-case-filter-reset] .bao-toolbar-label {
  display: inline-block;
  max-width: 12rem;
  margin-inline-start: var(--space-micro, 0.25rem);
  opacity: 1;
  overflow: hidden;
  white-space: nowrap;
  position: static;
  width: auto;
  height: auto;
  clip: auto;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 500;
}

/* Compact icon actions keep stable geometry in every interaction state.
 * Their accessible label and tooltip identify the action; labeled toolbar
 * density is owned by tokens-glass-chrome-body-part-1. */
/*
 * Touch / no-hover chrome densify (Apple HIG + WCAG 2.5.8):
 * Secondary icon actions stay icon-only with aria-label + title/data-tip —
 * never expand labels that reflow toolbars off-screen. Primary form verbs
 * (not btn-square) keep visible labels. Hit targets stay ≥44px.
 */
@media (hover: none),
(pointer: coarse) {/*
   * Same labeled-density exclusion as the fine-pointer default above.
   * Without it this rule contradicted the law stated at the top of this very
   * block — touch surfaces keep permanent verbs — by removing the caption from
   * the box tree entirely on exactly the devices that have no hover to reveal
   * it. display:none also destroys the accessible name,
since the glyph is
   * aria-hidden and S123 forbids a second name via aria-label.
   */
  .bao-toolbar-action.btn-square:not([data-toolbar-density="labeled"] *) .bao-toolbar-label {
    display: none;
    max-width: 0;
    margin-inline-start: 0;
    opacity: 0;
  }/*
   * The square LOCK is part of the same contract as the label clip above.
   * Revealing a caption inside a box pinned to 2.75rem square just truncates
   * it — measured: "Rectangle" reveals with max-width 192px and still paints
   * 20px wide. A surface asking for labels needs room for them,
so labeled
   * density keeps the 44px floor as a MINIMUM and lets the control grow.
   */
  .bao-toolbar-action.btn-square:not([data-toolbar-density="labeled"] *) {
    width: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    max-width: var(--touch-target-min, 2.75rem);
    padding-inline: 0;
  }
  /*
   * Non-square secondary chrome on phone: hide verb text in dense toolbars,
   * keep icon + tip. Page-header clinical CTAs (finish/generate/customize)
   * always keep labels — design.pdf: essentials must not depend on hover —
   * so the page-header action row is excluded here outright;its density is
   * owned by glass-shell-page-header + layout-dock (soft/primary labeled,
* ghost/outline icon-first).
   * Settings destinations keep labels (Apple list) — exclude bao-settings-tab.
   * Session mode tabs (Acquire/Annotate) own their reveal in capture-preview
   * tokens (active tab shows its verb) — exclude bao-session-mode-tab.
   * Filter clear/reset keeps a verb — owned by the .filter-reset block above.
   * :has(> svg) gates to icon-bearing actions: a label-only button must never
   * densify into a blank pill (engines without :has() keep labels — safe).
   */
  /* Exclude chat context-more + header overflow: those are often the sole
     mobile chrome control (model chip hidden / finish collapsed) — a bare
     chevron or ⋯ fails discoverability (Apple HIG labeled essentials). */
  /*
   * Soft clinical secondaries (btn-soft) keep permanent verbs — never hide
   * labels for soft/primary essentials (evidence Generate report,
etc.).
   * Sticky labeled rails own density=labeled and must never densify to blank.
   */
  /*
   * Hidden the sr-only way,
NOT display:none. Densifying to an icon must not
   * strip the control's accessible name: display:none removes the label from
   * the accessibility tree,
and these controls carry no aria-label because
   * S123 forbids naming attrs on a control that has a visible label. The two
   * rules together produced genuinely nameless buttons in the phone band
   * (axe button-name,
critical,
on the annotation editor toolbar).
   *
   * Clipping instead keeps the name readable to assistive tech while staying
   * invisible and out of flow,
so both contracts hold at every width.
   */
  .bao-toolbar-action:not(.btn-primary):not(.btn-soft):not(.btn-error):not(.btn-success):not(.bao-settings-tab):not(.bao-session-mode-tab):not(.filter-reset):not([data-case-filter-reset]):not([data-page-header-actions] *):not([data-chat-context-more] > summary):not([data-header-action-overflow] > summary):not([role="toolbar"].bao-sticky-toolbar *):has(> svg)
    .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .bao-toolbar-action:not(.btn-primary):not(.btn-error):not(.btn-success):not(.bao-settings-tab):not(.filter-reset):not([data-case-filter-reset]).btn {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
  }
  [data-page-header-actions] .bao-toolbar-action .bao-toolbar-label:not(.truncate),
  [data-primitive="PageHeader"] [data-page-header-actions] .bao-toolbar-action .bao-toolbar-label:not(.truncate),
  [data-page-header-actions] .bao-toolbar-action.btn-square .bao-toolbar-label:not(.truncate),
  [data-chat-context-more] > summary .bao-toolbar-label:not(.truncate),
  /* Non-media header overflow keeps verb; media phone densify owns hide. */
  [data-page-type]:not([data-page-type="media"]) [data-header-action-overflow] > summary .bao-toolbar-label:not(.truncate),
  [data-toolbar] .bao-toolbar-action.btn-primary .bao-toolbar-label:not(.truncate),
  [data-toolbar] .bao-toolbar-action.btn-soft:not(.btn-square):not([data-dashboard-edit-toggle]) .bao-toolbar-label:not(.truncate),
  [data-primitive="FilterBar"] .bao-toolbar-action:not(.btn-primary):not(.btn-error):not(.btn-success):not(.filter-reset):not([data-case-filter-reset]):has(> svg) .bao-toolbar-label:not(.truncate),
  /*
   * Sticky clinical action rails (evidence verify/custody/bundle/finish):
   * icon-only squares fail discoverability on phone (Apple HIG labeled
   * essentials). Always paint verb text;horizontal scroll owns overflow.
   */
  [role="toolbar"].bao-sticky-toolbar .bao-toolbar-action .bao-toolbar-label:not(.truncate),
[role="toolbar"].bao-sticky-toolbar .bao-toolbar-action.btn-square .bao-toolbar-label:not(.truncate) {
    display: inline-block;
    max-width: min(calc(100% - var(--space-micro, 0.25rem)), var(--toolbar-label-reveal-max-width, 12rem));
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
  }[data-page-header-actions] .bao-toolbar-action.btn-square,
[role="toolbar"].bao-sticky-toolbar .bao-toolbar-action.btn-square {
    width: auto;
    max-width: none;
    min-width: var(--touch-target-min, 2.75rem);
    padding-inline: var(--space-inline, 0.5rem);
  }
}


/*
 * Phone viewport (not only coarse pointer): headed desktop Chrome emulates
 * mobile width with fine pointer + hover, which skipped the coarse block and
 * left evidence sticky rails icon-only. Width gate restores labeled verbs.
 * Specificity beats .bao-toolbar-action.btn-square .bao-toolbar-label max-width:0.
 */
@media (max-width: 47.99rem) {
  #app-main [role="toolbar"].bao-sticky-toolbar .bao-toolbar-action .bao-toolbar-label:not(.truncate),
  #app-main [role="toolbar"].bao-sticky-toolbar .bao-toolbar-action.btn-square .bao-toolbar-label:not(.truncate),
  #app-main [role="toolbar"].bao-sticky-toolbar .btn.bao-toolbar-action.btn-square .bao-toolbar-label:not(.truncate),
  #app-main [role="toolbar"].bao-sticky-toolbar[data-toolbar-density="labeled"] .bao-toolbar-action .bao-toolbar-label:not(.truncate) {
    display: inline-block;
    position: static;
    width: auto;
    height: auto;
    clip: auto;
    max-width: min(calc(100% - var(--space-micro, 0.25rem)), var(--toolbar-label-reveal-max-width, 12rem));
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  #app-main [role="toolbar"].bao-sticky-toolbar .bao-toolbar-action.btn-square,
  #app-main [role="toolbar"].bao-sticky-toolbar .btn.bao-toolbar-action.btn-square {
    width: auto;
    max-width: none;
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    padding-inline: var(--space-inline, 0.5rem);
  }
}


/*
 * Tablet band (48–79.99rem): page toolbars paint icon + label at rest
 * (imp-t09 — discoverability; phone keeps icon-only densify, ≥80rem keeps
 * fine-pointer hover-reveal). Scoped to #app-main page toolbars: shell chrome
 * (theme/voice/user) stays glyph-only, and the editor's tool palette and stage
 * toolbar own their own per-band label density (tokens-layout-editor-toolbar-
 * container-presentation / tokens-layout-editor-command-tablet-fit): the stage
 * toolbar collapses labels by STAGE width, which a 42rem stage inside a 48rem
 * viewport needs — this viewport-band reveal painted 107px labelled verbs
 * there and pushed the toolbar into a clipped third row (measured 768x1024).
 * The capture bar is icon-first by contract (tokens-layout-page-shells-
 * capture-responsive clips its labels): revealed here, "Gallery" widened the
 * leading track into the centred shutter column (measured 768x1024).
 * #app-main (ID) outranks the long :not() densify chains in the coarse block
 * above, so labels win at any pointer type inside the band.
 */
@media (min-width: 48rem) and (max-width: 79.99rem) {
  #app-main [role="toolbar"]:not([data-editor-toolbar]):not([data-editor-stage-toolbar]):not([data-capture-preview-overlay]) .bao-toolbar-action:not(.bao-shell-icon-action) .bao-toolbar-label:not(.truncate) {
    display: inline-block;
    position: static;
    width: auto;
    height: auto;
    clip: auto;
    max-width: min(calc(100% - var(--space-micro, 0.25rem)), var(--toolbar-label-reveal-max-width, 12rem));
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  #app-main [role="toolbar"]:not([data-editor-toolbar]):not([data-editor-stage-toolbar]):not([data-capture-preview-overlay]) .bao-toolbar-action.btn-square:not(.bao-shell-icon-action) {
    width: auto;
    max-width: none;
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    padding-inline: var(--toolbar-action-pad-inline, 0.625rem);
  }
}


/* ============================================================
   Nav items — reactive solid wash (never a second glass sheet)
   ============================================================ */

.bao-nav-item {
  display: flex;
  align-items: center;
  /* Compact desktop density; touch viewports get HIG 44px below. */
  min-height: var(--touch-target-compact, 2.25rem);
  gap: var(--space-inline);
  border-radius: var(--radius-control, 0.75rem);
  transition-property: background-color, color, box-shadow;
  transition-duration: var(--duration-normal);
}
@media (max-width: 64rem) {
  .bao-nav-item {
    min-height: var(--touch-target-min, 2.75rem);
  }
}
@media (pointer: coarse) {/* Touch latches :hover until the next tap elsewhere,
so a tapped nav item
     keeps the hover fill and reads as permanently selected. Hybrid devices
     (touch laptops,
tablets with a trackpad) report hover:hover AND
     pointer:coarse,
so the fine-pointer gate below does not cover them.
     Active/current items keep their own fill — only the latched hover clears. */
  .bao-nav-item:not(.menu-active):not([aria-current="page"]):hover {
    background: transparent;
  }
  .bao-nav-item {
    min-height: var(--touch-target-min, 2.75rem);
  }
}

@media (hover: hover) and (pointer: fine) {
  .bao-nav-item:hover {
    background: color-mix(in oklab, var(--bg-surface-hover) 80%, transparent);
  }
}
.bao-nav-item:focus-visible {
  background: color-mix(in oklab, var(--bg-surface-hover) 80%, transparent);
}.bao-nav-item.menu-active,
.bao-nav-item[aria-current="page"] {
  background: color-mix(in oklab, var(--accent-primary) 16%, var(--bg-panel));
  color: var(--text-primary);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent-primary) 28%, transparent);
  font-weight: 600;
}.bao-nav-item.menu-active svg,
.bao-nav-item[aria-current="page"] svg {
  stroke-width: 2.1;
  color: var(--accent-primary, var(--color-primary));
}

/* The mobile dock reuses the sidebar's `active` class string from
   resources/bao/partials/sidebar.bao (`menu-active bg-primary/10
   text-primary`). .bao-nav-item reclaims the label colour just above;
   .bao-mobile-dock-item had no such rule, so `text-primary` painted the
   11px dock label in brand blue over the tinted active pill — measured
   4.1:1, under the 4.5:1 WCAG 1.4.3 floor for text that size, and on the
   primary mobile navigation's current item. The tint, weight and icon
   still signal "current"; only the label reverts to readable text. */
.bao-mobile-dock-item:is([aria-current="page"], [aria-pressed="true"]) .dock-label {
  color: var(--text-primary);
}

/*
 * Sidebar rail header (brand + collapse) — when a rail is in-flow: the
 * icon band from 48rem and the permanent rail from 80rem. Below 48rem the
 * phone drawer owns navigation and paints no rail. Never lg: (phones
 * report ≥1024). In the 48–79.99rem icon band the toggle renders
 * brand-only (tokens-glass-shell-drawer-part-1 owns that presentation);
 * ≥80rem it is the collapse control.
 */
.bao-sidebar-rail-header {
  position: relative;
  z-index: var(--z-dropdown);
  display: none;
}
@media (min-width: 48rem) {
  .bao-sidebar-rail-header {
    display: flex;
  }
}

/*
 * Sidebar nav group rows: kill the li's automatic minimum width. A
 * column-flex item's auto min-width is its min-content, so the longest
 * nowrap label (~167px) forced its li to ~205px inside the ~196px
 * scroller at the 1280px expanded width and spilled ~5px past the
 * clipped edge on every route. min-width:0 lets the row shrink so the
 * label span's own truncate engages instead.
 */
.bao-shell-sidebar [data-sidebar-group-member] {
  min-width: 0;
}
/* The row link itself must size against the li, not its content: with
 * flex-basis auto the longest nowrap label keeps the anchor at content
 * width and it spills past the scroller. A definite width lets the
 * label span's flex-1 + truncate do the clamping. */
.bao-shell-sidebar [data-sidebar-group-member] > a {
  width: 100%;
}

/* ============================================================
   Settings surface — one solid sheet; labeled tabs
   ============================================================
   Always show tab labels so settings groups stay scannable.
   Compact icon-only was unreadable with 10+ tabs. */

.bao-settings-tab {
  width: auto;
  min-width: var(--touch-target-compact, 2.25rem);
  flex-shrink: 0;
  padding-inline: var(--space-inline, 0.5rem);
}/* Desktop: labeled settings tabs (scannable groups). */
.bao-settings-tab .bao-toolbar-label,
.bao-settings-tab.tab-active .bao-toolbar-label,
.bao-settings-tab[aria-selected="true"] .bao-toolbar-label {
  max-width: var(--toolbar-label-reveal-max-width, 12rem);
  margin-inline-start: var(--space-compact, 0.375rem);
  opacity: 1;
}

/* Section labels belong to the sidebar material, without separate opaque bands. */
#app-sidebar [data-sidebar-group] {
  position: static;
  background: transparent;
  color: var(--text-secondary);
}



/* Inactive tabpanel concealment has ONE owner:
   tokens-glass-shell-settings-panels-part-1.css, whose rule covers these three
   selectors plus the [data-page="settings"] [data-settings-panel] variants.
   The mirror that used to sit here was a second owner for the same question
   (LAW1) and is deliberately gone — not emptied by accident. */



/* API registry mobile entries — dense list items, not nested data panels. */


/* ============================================================
   Solid data panels — content plane (design.pdf §4)
   ============================================================ */

.bao-data-panel {
  border: 1px solid var(--border-base, var(--color-base-300));
  border-radius: var(--radius-box, 1rem);
  transition-property: border-color, box-shadow, transform;
  transition-duration: var(--duration-glass, 280ms);
  transition-timing-function: var(--easing-glass, cubic-bezier(0.32, 0.72, 0, 1));
}

@media (hover: hover) and (pointer: fine) {
  .bao-data-panel:hover {
    border-color: color-mix(in oklab, var(--border-base) 70%, var(--accent-primary));
    box-shadow: var(--shadow-md);
  }
}
.bao-data-panel:focus-within {
  border-color: color-mix(in oklab, var(--border-base) 70%, var(--accent-primary));
  box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
  .bao-data-panel:hover,
  .bao-data-panel:focus-within {
    transform: none;
  }
}



.modal-box {
  box-sizing: border-box;
  /* Variant tracks come from the existing size utilities. The containment
   * owner clamps them to the viewport without replacing compact dialogs
   * with the settings width or defeating the command/shortcuts size tokens. */
  --modal-box-gutter: calc(var(--space-inline) * 2);
  --modal-box-max-block: min(100dvh - 1.5rem, var(--modal-max-height, min(92dvh, 92vh)));
  inline-size: min(100%, var(--modal-box-track, var(--modal-default-max-width)));
  max-inline-size: min(100vw - var(--modal-box-gutter), var(--modal-box-track, var(--modal-default-max-width)), var(--modal-box-viewport-track, 100vw));
  max-block-size: var(--modal-box-max-block);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}
/*
 * Phone bottom sheet is full-bleed: a compact-width sheet spans the screen and
 * pins to the bottom edge, so a viewport gutter reads as a misaligned panel
 * rather than as a sheet.
 *
 * Gated to <40rem to match the `sm:` breakpoint the markup switches on — every
 * bottom sheet carries sm:modal-middle, sm:modal-end or sm:hidden, so at
 * >=40rem the centered track above is the correct one and this band must not
 * reach it.
 *
 * --vv-height tracks the visual viewport (app-shell-case-create.ts binds it on
 * visualViewport resize/scroll for the whole document), so the sheet shrinks
 * to clear a software keyboard instead of hiding its own controls behind it.
 */
@media (max-width: 39.99rem) {
  dialog.modal.modal-bottom > .modal-box {
    --modal-box-track: 100%;
    --modal-box-gutter: 0px;
    --modal-box-max-block: min(92dvh, var(--vv-height, 92vh));
  }
}
/* Tablet band (t10): centered modal with a token-driven cap instead of the
   near-full-width desktop track — sm:modal-middle already centers the grid,
   so only the width track tightens. */
@media (min-width: 48rem) and (max-width: 63.99rem) {
  .modal-box {
    --modal-box-viewport-track: var(--modal-tablet-max-width);
    --modal-box-gutter: calc(var(--space-page) * 2);
  }
}
.bao-modal-body-scroll,
.modal-box > [class*="dialogBody"],
.modal-box > .px-space-section {
  min-width: 0;
  max-width: 100%;
  overflow-x: hidden;
}
.bao-data-panel fieldset.bao-settings-group.bao-data-panel,
.bao-data-panel .bao-settings-group.bao-data-panel {
  background: transparent;
  border-width: 0;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
}
/* An open dropdown explains itself — its summary tooltip would overlap
   (and clip against chrome edges above) the menu it just opened. */
details[open] > summary.tooltip::before,
details[open] > summary.tooltip::after {
  display: none;
}
/* Label density is owned by toolbar chrome; context disclosures retain their visible verb. */
@media (hover: hover) and (pointer: fine) {
  [data-chat-context-more] > summary.bao-toolbar-action .bao-toolbar-label,
  [data-header-action-overflow] > summary.bao-toolbar-action .bao-toolbar-label {
    display: inline-block;
    max-width: var(--toolbar-label-reveal-max-width, 12rem);
    margin-inline-start: var(--space-micro, 0.25rem);
    opacity: 1;
    overflow: hidden;
    white-space: nowrap;
  }
}

}
@layer bao-tokens {

/*
 * Toolbar zones. Primary claims the free inline space so the secondary cluster
 * and the compact overflow trigger dock to the trailing edge; both stay
 * shrinkable (min-width:0) so a long verb label wraps inside its zone instead of
 * widening the toolbar and forcing the row to scroll.
 */
.bao-toolbar-primary {
  flex: 1 1 auto;
  min-width: 0;
}
.bao-toolbar-secondary {
  flex: 0 1 auto;
  min-width: 0;
  justify-content: flex-end;
}/*
 * FilterBar secondary dimensions (status,
priority,
…) as opposed to the
 * primary search field. They pair up beneath the full-width search on the
 * compact 2-column grid,
and the floor keeps each control at the 44px Apple HIG
 * target once its legend is painted.
 */
.bao-filter-secondary {
  min-width: 0;
}.bao-filter-secondary > .select,
.bao-filter-secondary > select,
.bao-filter-secondary > .input,
.bao-filter-secondary > input {
  min-height: var(--touch-target-min, 2.75rem);
  min-width: 0;
  width: 100%;
}/*
 * Overflow host. At >=48rem it stays display:contents so its children take part
 * directly in the FilterBar grid. On compact it must become a real box,
or the
 * col-span-full it carries has nothing to apply to — display:contents generates
 * no box and cannot be a grid item. Becoming a grid row is what lets collapsed
 * secondary dimensions stack full width under the search.
 */
@media (max-width: 47.99rem) {
  .bao-filter-overflow {
    display: grid;
    grid-column: 1 / -1;
    gap: var(--space-micro);
    min-width: 0;
  }
}

/*
 * Collection FilterBar — sticky filter band under page header / journey host.
 * Primitive classes carry sticky;this pins z-index + solid surface so table
 * scroll never paints over filter controls (one owner,
every collection page).
 */
[data-primitive="FilterBar"] {
  position: sticky;
  top: 0;
  z-index: calc(var(--z-sticky, 1100) - 1);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/*
 * Toolbar phone overflow — secondary zone hides; overflow trigger opens a
 * bottom sheet with full-width touch targets (Apple HIG dense toolbar).
 */
@media (max-width: 47.99rem) {
  [data-primitive="Toolbar"] .bao-toolbar-secondary {
    display: none;
  }
  [data-primitive="Toolbar"] [data-toolbar-overflow]:not(:checked) ~ .bao-toolbar-overflow-panel {
    display: none;
  }
  [data-primitive="Toolbar"] [data-toolbar-overflow]:checked ~ .bao-toolbar-overflow-panel {
    display: flex;
  }
  [data-primitive="Toolbar"] .bao-toolbar-overflow-panel {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: var(--z-modal, 50);
    flex-direction: column;
    gap: var(--space-micro);
    padding: var(--space-section);
    padding-block-end: max(var(--space-section), env(safe-area-inset-bottom, 0px));
    background: var(--color-base-100, var(--bg-panel));
    border-block-start: 1px solid color-mix(in oklab, var(--color-base-content) 12%, transparent);
    box-shadow: 0 -4px 24px color-mix(in oklab, var(--color-base-content) 8%, transparent);
  }
  [data-primitive="Toolbar"] .bao-toolbar-overflow-panel .btn {
    width: 100%;
    justify-content: flex-start;
    min-height: var(--touch-target-min, 2.75rem);
  }
}

}
@layer bao-tokens {

/*
 * App-shell drawer host — mobile-first, WebView-hardened (BlueStacks ships
 * Chromium ~101; do not rely solely on daisyUI is-drawer-close variants).
 * - Side rail is position:fixed off-canvas until the checkbox opens it
 * - ≥80rem permanent in-flow rail (never daisyUI lg:drawer-open)
 * - Content fills remaining track; no stacked dual sidebars
 */
/*
 * No overflow here: the drawer-part-b block owns it with `overflow: clip`
 * (same visual clip, no scroll container). Declaring `hidden` earlier only
 * created a dead declaration the ratchet had to flag.
 */
.bao-shell-drawer {
  position: relative;
  display: flex;
  width: 100%;
  max-width: 100%;
  container-type: inline-size;
  container-name: shell;
}

.bao-shell-drawer > .drawer-toggle {
  position: fixed;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
}

.bao-shell-drawer > .drawer-content {
  display: flex;
  min-width: 0;
  flex-direction: column;
  width: 100%;
  max-width: 100%;
}

/*
 * Closed rail host: fixed, fully opaque host (children own the slide).
 * daisyUI sets opacity:0% / visibility:hidden on .drawer-side — reset both.
 */
.bao-shell-drawer > .drawer-side {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  /*
   * Off-canvas rank: above the mobile dock (--z-overlay: 1200) AND above the
   * sticky topbar (--z-shell-topbar: 1250). Neither may paint over an open
   * rail/scrim. One named rank, shared with the peek overlay below, so the
   * two transient rail presentations can never disagree.
   */
  z-index: var(--z-shell-drawer-overlay, 1300);
  display: flex;
  flex-direction: column;
  width: min(var(--shell-drawer-width), var(--shell-drawer-max-viewport));
  max-width: var(--shell-drawer-width);
  height: 100%;
  margin: 0;
  translate: none;
  opacity: 1;
  visibility: visible;
  pointer-events: none;
  overscroll-behavior: contain;
  background-color: transparent;
  transition-property: none; transition-duration: var(--duration-instant, 0ms);
}

/*
 * Panel slides; host stays painted so open/close is not an opacity fade.
 * #app-sidebar owns its Liquid Glass material through bao-surface-glass-*.
 * This geometry rule must not reset its background: transparent paint erases
 * the accessibility variant, while solid paint erases the normal material.
 * Accessibility solidification stays with the material owner.
 */
.bao-shell-drawer > .drawer-side > *:not(.drawer-overlay) {
  pointer-events: none;
  height: 100%;
  width: 100%;
  max-width: 100%;
  /* Closed: clip layout overflow so mobile smoke does not count off-canvas nav. */
  overflow: hidden;
  visibility: hidden;
  overscroll-behavior: contain;
  /* Material classes own paint, including opaque accessibility variants. */
  color: var(--text-primary, var(--color-base-content));
  transform: translate3d(-100%, 0, 0);
  translate: none;
  transition-property: transform; transition-duration: var(--duration-normal, 200ms); transition-timing-function: var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
}

/* Promote during the open/close transition only — reverts to auto at rest. */
.bao-shell-drawer:has(> .drawer-toggle:checked) > .drawer-side > *:not(.drawer-overlay),
.bao-shell-drawer:has(> .drawer-toggle:focus) > .drawer-side > *:not(.drawer-overlay) {
  will-change: transform;
}

.bao-shell-drawer:not(:has(> .drawer-toggle:checked)):not(:has(> .drawer-toggle:focus))
  > .drawer-side
  > *:not(.drawer-overlay) {
  will-change: auto;
}

.bao-shell-drawer > .drawer-side > .drawer-overlay {
  position: fixed;
  inset: 0;
  /* Scrim must cover the viewport for tap-outside-to-close.
     z-index -1 painted under the rail panel but still receives hits
     across the full viewport once pointer-events are enabled. */
  z-index: var(--z-base, 0);
  /* Theme-safe scrim — never raw black-only; readable on light/dark. */
  background: color-mix(in oklab, var(--color-base-content) 40%, transparent);
  opacity: 0;
  pointer-events: none;
  transition-property: opacity; transition-duration: var(--duration-normal, 200ms); transition-timing-function: var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
  cursor: pointer;
}

/* Open: checkbox checked (mobile menu / dock More) */
.bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side {
  pointer-events: auto;
  /* Expand hit host to full viewport so the fixed scrim is reachable. */
  width: 100%;
  max-width: none;
}

/* Hide dock while the rail owns the viewport — prevents dock-on-scrim stacking. */
.bao-shell-drawer > .drawer-toggle:checked ~ .drawer-content .bao-mobile-dock,
.bao-shell-drawer:has(> .drawer-toggle:checked) .bao-mobile-dock {
  visibility: hidden;
  pointer-events: none;
}

.bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
  pointer-events: auto;
  overflow-y: auto;
  visibility: visible;
  transform: translate3d(0, 0, 0);
  translate: none;
  /* Keep panel at mobile rail width while host spans viewport for scrim. */
  width: min(var(--shell-drawer-width), var(--shell-drawer-max-viewport));
  max-width: var(--shell-drawer-width);
  z-index: var(--z-local, 1);
}

.bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > .drawer-overlay {
  opacity: 1;
  pointer-events: auto;
}

/*
 * Permanent rail from 80rem (1280px) only.
 * Phones report 1080 CSS px (hits Tailwind lg: 1024) — must stay off-canvas.
 * Do not use daisyUI lg:drawer-open on phones; this media query is the SSOT.
 *
 * Topbar hamburger SSOT (matches webview-drawer; single owner):
 * - Phone (<48rem): dock "More" opens drawer — hide header hamburger.
 * - Tablet (48–79.99rem): persistent icon rail owns nav — hamburger retired
 *   (the drawer retires in this band; see the tablet rail block below).
 * - Permanent rail (≥80rem): hide header hamburger — #desktop-sidebar-collapse
 *   in the sidebar owns collapse (no dual collapse controls).
 */


/*
 * Sidebar visibility follows the toolbar's leading-edge navigation convention.
 * It stays in document flow; responsive bands own only whether it is shown.
 */
.bao-shell-sidebar-peek-trigger {
  position: static;
  inset: auto;
  width: auto;
  min-height: var(--touch-target-min, 2.75rem);
  z-index: auto;
  border-radius: var(--radius-field, 0.75rem);
}
/*
 * Visibility ladder. The trigger carries the btn class, whose shell-wide
 * .btn:not([hidden]):not(.hidden) display rule is (0,3,0) — it silently
 * re-asserted inline-flex over a (0,1,0) display:none, so the affordance
 * painted at DESKTOP on top of the expanded sidebar's nav items (it covered
 * the "Chat" entry at 90rem). Every rung repeats the class to outrank it,
 * mirroring the .bao-fab precedent in tokens-layout-dock-base.ts.
 */
.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger {
  display: none;
}

/*
 * Tablet band (48–79.99rem): persistent in-flow icon rail — the canonical
 * dock → rail → sidebar transformation (design.pdf §8.3). The rail IS the
 * collapsed sidebar vocabulary rendered in-flow: grid track at
 * --sidebar-collapsed, labels/titles hidden, tooltips carry names. Mirrors
 * the .bao-sidebar-collapsed SSOT rules in tokens-layout-editor-workbench.ts
 * so the band reads identically whether the stored pref is expanded or
 * collapsed; the band (not the pref) owns rail state, so the hamburger and
 * the in-rail collapse toggle both retire here.
 */
#app-sidebar .menu,
#app-sidebar ul.menu {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  overflow-x: hidden;
}

@media (min-width: 48rem) and (max-width: 79.99rem) {
  .bao-shell-drawer {
    display: grid;
    grid-template-columns: var(--sidebar-collapsed) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }

  .bao-shell-drawer > .drawer-content {
    grid-column: 2;
    grid-row: 1;
    width: auto;
    max-width: none;
    min-height: 0;
  }

  .bao-shell-drawer > .drawer-side,
  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side {
    position: relative;
    inset: auto;
    grid-column: 1;
    grid-row: 1;
    z-index: var(--z-local, 1);
    width: 100%;
    max-width: none;
    height: 100%;
    min-height: 0;
    align-self: stretch;
    translate: none;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    overflow: visible;
  }

  .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay),
  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
    pointer-events: auto;
    visibility: visible;
    overflow: visible;
    transform: none;
    translate: none;
    will-change: auto;
    transition-property: none; transition-duration: var(--duration-instant, 0ms);
    height: 100%;
    width: 100%;
    max-width: none;
  }

  .bao-shell-drawer > .drawer-side > .drawer-overlay,
  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > .drawer-overlay {
    display: none;
    opacity: 0;
    pointer-events: none;
  }

  /* In-flow tablet rail — LAYOUT ONLY. The material is owned by the
     bao-surface-glass-regular + bao-glass-large pair app-shell.bao puts on
     #app-sidebar; this block previously repainted it solid and killed the
     specular/illumination plates, so one property had two owners and the
     .bao declaration was dead. Apple HIG Sidebars: sidebars float above
     content in the Liquid Glass layer, as toolbars and tab bars do; HIG
     Materials names sidebars as a regular-variant surface because they
     carry a significant amount of text. Reduced transparency and increased
     contrast still solidify it — tokens-glass-a11y-prefers owns that.
     Padding stays with .bao-shell-sidebar/p-space-micro — no shorthand here
     or the safe-area-aware block padding is clobbered on notched tablets. */
  #app-sidebar,
  #app-sidebar.bao-surface-glass-regular,
  #app-sidebar.bao-sidebar-expanded,
  #app-sidebar.bao-sidebar-collapsed {
    width: 100%;
    /* Inline padding, never the shorthand — the note above is right that a
       shorthand here clobbers the safe-area-aware block padding on notched
       tablets. It has to be stated all the same: this band owns the rail mode,
       but the expanded/collapsed CLASS is owned by the toggle, and the toggle
       does not know the band exists. A rail sitting in the 3.5rem track while
       still classed .bao-sidebar-expanded took that class's
       padding: var(--space-section) shorthand — 12px a side, sized for the
       16rem sidebar — which outranks the p-space-micro utility the note relies
       on. Measured at 1100px: 56px rail, 32px left after the rail padding, 20px
       after .menu's own inline padding, holding a 44px HIG target that then
       ran from x=18 to x=62 and was clipped by the rail's own edge. The band
       decides the padding for both classes; .menu adds none inside a rail. */
    padding-inline: var(--space-micro);
  }

  #app-sidebar .menu {
    padding-inline: 0;
  }

  /* Icon-rail presentation (mirror of the .bao-sidebar-collapsed vocabulary). */
  #app-sidebar .menu-title,
  #app-sidebar [data-sidebar-expanded-only] {
    display: none;
  }
  #app-sidebar .bao-nav-item {
    justify-content: center;
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
    padding-inline: 0;
  }
  #app-sidebar .bao-nav-item.tooltip::before,
  #app-sidebar .bao-nav-item.tooltip::after {
    display: block;
  }

  /* One affordance per function: the band owns rail state. */
  #app-header .bao-drawer-toggle-control,
  #app-header [data-drawer-toggle] {
    display: none;
  }

  /*
   * Brand-only rail header for the icon band. The rail toggle button hosts
   * the collapsed-only brand logo, so display:none on the button beheaded
   * the rail's whole identity across 48–79.99rem (measured: logo AND
   * toggle both gone while nav icons painted). The band still owns the
   * rail state, so the button must not act, focus, or announce:
   * visibility:hidden drops it from paint, the tab order, and the
   * accessibility tree, while the child logo re-establishes visibility
   * (children may opt back in). The chevron svg is expanded-only and
   * already hidden by the icon-rail presentation above; the button's own
   * chrome paints only with the button, so no background/border resets.
   */
  #app-sidebar [data-sidebar-rail-toggle] {
    pointer-events: none;
    visibility: hidden;
  }
  #app-sidebar [data-sidebar-rail-toggle] [data-sidebar-collapsed-only] {
    visibility: visible;
  }

  /*
   * Sidebar peek (imp-t02): the edge affordance rides the collapsed rail
   * while unpinned; peeking or pinning retires it (the expanded sidebar
   * itself then owns the pin control).
   */
  .bao-shell-drawer .bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger {
    display: inline-flex;
  }
  .bao-shell-drawer.bao-shell-sidebar-peek .bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger,
  .bao-shell-drawer.bao-shell-sidebar-pinned .bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger {
    display: none;
  }


  /*
   * Pinned (explicit expanded preference via the existing rail toggle + the
   * ui.sidebar.expanded seam): the band yields its fixed icon track to the
   * expanded track. The pin is a deliberate user action — reflow is intended.
   */
  .bao-shell-drawer.bao-shell-sidebar-pinned {
    grid-template-columns: var(--sidebar-expanded) minmax(0, 1fr);
  }

  /*
   * Expanded vocabulary while peeking/pinned — mirrors the ≥80rem expanded
   * sidebar: group titles and labels paint, nav items align inline-start.
   * Scoped selectors (drawer class + #app-sidebar) outrank the band's
   * icon-rail presentation rules above.
   */
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar .menu-title,
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .menu-title {
    display: list-item;
  }
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar [data-sidebar-expanded-only],
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-expanded-only] {
    display: inline;
  }
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar .bao-nav-item,
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item {
    justify-content: flex-start;
    padding-inline: var(--space-inline, 0.5rem);
  }
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar .bao-nav-item.tooltip::before,
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar .bao-nav-item.tooltip::after,
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::before,
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::after {
    display: none;
  }
  /* The pin control lives inside the expanded sidebar while peeking/pinned. */
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar [data-sidebar-rail-toggle],
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle] {
    display: inline-flex;
    visibility: visible;
    pointer-events: auto;
  }
  /*
   * Expanded presentation paints the chevron; the collapsed-only logo must
   * not stack under it when the pin arrives without a prior toggle (the
   * [hidden] mirror for collapsed-only is toggle-driven and never fired).
   */
  .bao-shell-drawer.bao-shell-sidebar-peek #app-sidebar [data-sidebar-rail-toggle] [data-sidebar-collapsed-only],
  .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle] [data-sidebar-collapsed-only] {
    display: none;
  }
}


@media (min-width: 80rem) {

  .bao-drawer-toggle-control {
    display: none;
  }

  /*
   * Dock ↔ permanent-rail CQ ownership (viewport rem SSOT, unlayered):
   * phone (<48rem) paints #mobile-dock; tablet (48–79.99rem) paints the
   * in-flow icon rail (dock already hidden in tokens-layout-dock-base);
   * ≥80rem replaces both with the full in-flow left sidebar. Must stay unlayered —
   * @layer utilities hide was dropped on WebView 101 so the dock still
   * painted at 1280 beside the rail.
   */

  /*
   * Right session rail: CSS owns visibility (never Tailwind xl:flex), and
   * this module is its one owner across both bands — the hide lives in the
   * `max-width: 79.99rem` block below.
   *
   * The in-flow geometry moved here from tokens-layout-editor-workbench-grid,
   * which declared it from `min-width: 64rem`. That was an editor-scoped file
   * making a shell-wide claim, and because it repeated `display` at equal
   * specificity from a later module it also outranked the hide across the
   * 64–80rem band. On /capture at 1200x900 the rail then painted beside the
   * in-page session card, which only stands down at 80rem
   * (`body:has(#session-rail) [data-capture-workbench-secondary]`): two live
   * session-control surfaces, the preview crushed to 464px, and a Collapse
   * that left the in-page copy behind so the rail read as relocating into the
   * preview instead of collapsing.
   */
  #session-rail.bao-shell-session-rail,
  #session-rail {
    display: flex;
    visibility: visible;
    pointer-events: auto;
    align-self: stretch;
    flex: 0 0 auto;
    min-block-size: 0;
  }

  /* Session-rail sheet trigger retires once the permanent rail is in-flow.
     The selector must carry the full icon-geometry weight
     (#id + .navbar-end + .bao-shell-topbar-actions + attr + .btn +
     .bao-shell-icon-action). The plain "#app-header [attr]" form here scored
     (1,1,0) and lost every cascade to the unlayered icon-geometry rules at
     (1,4,0), so this hide NEVER applied: the phone sheet trigger shipped in
     the desktop topbar next to the permanent rail's own collapse control —
     two visible session-rail affordances at >=80rem. The <40rem band in
     tokens-glass-shell-topbar-zones-narrow.ts already matches this weight;
     this band had been left behind. */
  #app-header .navbar-end .bao-shell-topbar-actions [data-session-rail-sheet-open].btn.bao-shell-icon-action {
    display: none;
  }

  /*
   * These two style the shell host itself, so this band must stay @media.
   * .bao-shell-drawer declares container-name: shell, and a container query
   * never matches the element establishing the container — nesting them in
   * @container shell (min-width: 80rem) dropped them silently: base
   * display:flex won, .drawer-side kept width:100%, .drawer-content
   * collapsed to 0px, and the desktop shell painted as a full-viewport
   * sidebar with no content.
   */
  .bao-shell-drawer {
    display: grid;
    /* The rail grid track is the ONE owner of sidebar width. Collapse swaps
       the track var (drawer-level class from applySidebarRailState) — aside
       children are width:100% of the track and must never fight it.
       HIG Dynamic Type: the bare minmax(14rem, 16rem) computes 896–1024px
       at root font 400%, leaving the content column ~30% of a 1440px window
       and pushing every topbar control past the right edge (measured capsule
       right 1691px, badge left 1057px). The vw terms are font-invariant:
       18vw resolves to 16rem at 1440px and 14vw resolves to 14rem at 1024px,
       so the default-font arithmetic is unchanged while enlarged text cannot
       starve the shell. */
    grid-template-columns: var(--shell-rail-track, minmax(min(14rem, 14vw), min(16rem, 18vw))) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }

  .bao-shell-drawer.bao-shell-rail-collapsed {
    --shell-rail-track: var(--sidebar-collapsed);
  }
  /*
   * Collapsing the rail narrowed the grid track and nothing else, so the nav
   * kept painting full-width labels inside a 56px rail and the track clipped
   * them mid-word — "Primary" rendered as "Primar", "Annotation Review" as
   * "An". A collapsed rail is an ICON rail (design.pdf 8.3: medium width
   * becomes a navigation rail), so label text and section headings stop
   * painting at that width.
   *
   * Safe to remove from the box rather than sr-only-clip: every .bao-nav-item
   * carries its own aria-label AND the .tooltip class, so the accessible name
   * survives and hover/focus still reveals the text (9.6 "icon-only controls
   * must retain accessible names").
   */
  .bao-shell-drawer.bao-shell-rail-collapsed .bao-nav-item > span {
    display: none;
  }
  .bao-shell-drawer.bao-shell-rail-collapsed .menu-title {
    display: none;
  }
  .bao-shell-drawer.bao-shell-rail-collapsed .bao-nav-item {
    justify-content: center;
    padding-inline: var(--space-micro, 0.25rem);
  }
  /*
   * The 44px touch floor on .bao-nav-item is non-negotiable, so the list
   * padding has to yield instead: the expanded rail's inline padding left the
   * item starting 17px in, pushing its right edge to 61px inside a 56px rail
   * and clipping the glyph. Trim the inset at rail width so the full target
   * fits (design.pdf 8.2 large touch areas).
   */
  .bao-shell-drawer.bao-shell-rail-collapsed .menu,
  .bao-shell-drawer.bao-shell-rail-collapsed .bao-shell-sidebar > nav,
  .bao-shell-drawer.bao-shell-rail-collapsed .bao-shell-sidebar {
    padding-inline: var(--space-micro, 0.25rem);
  }

  .bao-shell-drawer > .drawer-toggle {
    display: none;
  }

  .bao-shell-drawer > .drawer-content {
    grid-column: 2;
    grid-row: 1;
    width: auto;
    max-width: none;
    min-height: 0;
  }

  .bao-shell-drawer > .drawer-side {
    position: relative;
    inset: auto;
    grid-column: 1;
    grid-row: 1;
    z-index: var(--z-local, 1);
    width: 100%;
    max-width: none;
    height: 100%;
    min-height: 0;
    align-self: stretch;
    translate: none;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    flex-shrink: 0;
    overflow: visible;
  }

  /*
   * Permanent rail is never toggle-checked. Base closed rules leave the panel
   * at pointer-events:none + visibility:hidden — without this override the
   * entire sidebar is dead on desktop (clicks fall through).
   */
  .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay) {
    pointer-events: auto;
    visibility: visible;
    /* The menu owns scrolling; the header tooltip must escape the rail. */
    overflow: visible;
    /* Closed-state off-canvas shift uses transform (not translate) — reset
       BOTH or the permanent rail renders as an empty reserved track. */
    transform: none;
    translate: none;
    will-change: auto;
    transition-property: none; transition-duration: var(--duration-instant, 0ms);
    height: 100%;
    width: 100%;
    max-width: none;
  }

  .bao-shell-drawer > .drawer-side > .drawer-overlay {
    display: none;
    pointer-events: none;
  }

  /* Permanent left sidebar (≥80rem) keeps the regular Liquid Glass material
     app-shell.bao declares on #app-sidebar. Apple HIG Sidebars: sidebars
     float above content in the Liquid Glass layer, as toolbars and tab bars
     do; HIG Materials assigns the regular variant to text-heavy chrome and
     names sidebars explicitly. The solid content-plane override that used to
     live here repainted the surface and hid the specular/illumination
     plates, so the .bao declaration was dead markup and the desktop rail
     rendered a different material from the phone drawer. Accessibility still
     wins: tokens-glass-a11y-prefers and the data-reduced-transparency /
     data-high-contrast branches solidify every glass surface including this
     one. The permanent full-height backdrop is the cost of that compliance. */

  /* Permanent right session rail (≥80rem) is a dense list pane — solid base;
     glass only when the rail is not a permanent divider. */
  #session-rail.bao-surface-glass-regular {
    background: var(--bg-panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  #session-rail.bao-surface-glass-regular::before,
  #session-rail.bao-surface-glass-regular::after {
    display: none;
  }
}

/* Below permanent-rail: session rail out of flow (CSS SSOT, not xl:).
 * display:none + visibility:hidden so HTMX intersect never fires and AT
 * skips the complementary pane until ≥80rem. JS mirrors inert (HTML). */
@media (max-width: 79.99rem) {
  #session-rail.bao-shell-session-rail,
  #session-rail {
    display: none;
    visibility: hidden;
    pointer-events: none;
  }

  /* RTL: off-canvas from the inline end (mobile / tablet only) */
  [dir="rtl"] .bao-shell-drawer > .drawer-side {
    inset-inline-start: auto;
    inset-inline-end: 0;
  }

  [dir="rtl"] .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay) {
    transform: translate3d(100%, 0, 0);
  }

  [dir="rtl"] .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
    transform: translate3d(0, 0, 0);
  }
}

/* RTL tablet band: the in-flow icon rail never sits off-canvas — reset the
   RTL off-canvas shift above (equal specificity; later source order wins). */
@media (min-width: 48rem) and (max-width: 79.99rem) {
  [dir="rtl"] .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay),
  [dir="rtl"] .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay),
  .bao-shell-drawer > .drawer-side > .drawer-overlay {
    transition-property: none; transition-duration: var(--duration-instant, 0ms);
  }
}

/* Product Settings → reduced motion must match OS prefers-reduced-motion. */
html[data-reduced-motion="true"] .bao-shell-drawer > .drawer-side > *:not(.drawer-overlay),
html[data-reduced-motion="true"] .bao-shell-drawer > .drawer-side > .drawer-overlay {
  transition-property: none; transition-duration: var(--duration-instant, 0ms);
}

/*
 * Sidebar capability-empty state. The nav list is a .menu, so the empty row has
 * to opt out of menu item geometry (hover/active plate, inline padding) or it
 * paints as a misleading, hoverable destination. The row also stops being a flex item
 * that stretches to the rail width so the centred copy stays readable while the
 * rail is collapsed.
 */
.bao-sidebar-empty-item {
  display: block;
  width: 100%;
  min-width: 0;
  /* Kill .menu li hover/active plate for every pointer — no :hover gate needed. */
  background: none;
}
.bao-sidebar-empty {
  min-width: 0;
  border-radius: var(--radius-field, 0.75rem);
  background: var(--bg-panel, var(--color-base-100));
  color: var(--text-secondary, var(--color-base-content));
}
/* Tablet peek expands the shell track so content remains fully accessible. */
@media (min-width: 48rem) and (max-width: 79.99rem) {
  .bao-shell-drawer.bao-shell-sidebar-peek {
    grid-template-columns: var(--sidebar-expanded) minmax(0, 1fr);
  }
}


}
@layer bao-tokens {

/* A phone More sheet is an expanded touch menu, independent of the persisted rail preference. */
@media (max-width: 47.99rem) {
  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > #app-sidebar {
    width: 100%;
    max-width: none;
    padding-block: var(--space-section, 0.75rem);
    padding-inline: min(var(--space-section), 5%);
  }

  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > #app-sidebar [data-sidebar-expanded-only] {
    display: inline;
  }

  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > #app-sidebar .menu-title {
    display: list-item;
  }

  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > #app-sidebar .bao-nav-item {
    justify-content: flex-start;
    padding-inline: min(var(--space-inline, 0.5rem), 5%);
  }

  .bao-shell-drawer > .drawer-toggle:checked ~ .drawer-side > #app-sidebar [data-sidebar-rail-toggle] {
    display: none;
  }
}

}
@layer bao-tokens {


/*
 * WebView 101 (BlueStacks Chromium ~101) does not support color-mix() or :has().
 * Solid token paints from tokens.bao (theme-palette-from-tokens); progressive
 * color-mix enhancements after. daisyUI theme :has() is stripped at emit.
 */
[data-theme="capture-dx"] {
  color-scheme: light;
  --color-accent: #F56E28;
  --color-accent-content: #18181b;
  --color-base-100: #ffffff;
  --color-base-content: #18181b;
  --color-base-200: #eeeef0;
  --color-base-300: #d9d9de;
  --color-error: #b91c1c;
  --color-error-content: #ffffff;
  --color-info: #0369a1;
  --color-info-content: #ffffff;
  --color-neutral: #18181b;
  --color-neutral-content: #ffffff;
  --color-primary: #1766A8;
  --color-primary-content: #ffffff;
  --color-secondary: #eeeef0;
  --color-secondary-content: #18181b;
  --color-success: #065f46;
  --color-success-content: #ffffff;
  --color-warning: #b05109;
  --color-warning-content: #ffffff;
  --bg-app: #f5f5f7;
  --bg-panel: #ffffff;
  --bg-surface: #eeeef0;
  --bg-surface-hover: #e5e5ea;
  --border-base: #d9d9de;
  --text-primary: #18181b;
  --text-secondary: #52525b;
  --text-tertiary: #67676f;
}
[data-theme="capture-dx-dark"] {
  color-scheme: dark;
  --color-accent: #F56E28;
  --color-accent-content: #18181b;
  --color-base-100: #1c1c1e;
  --color-base-content: #f4f4f5;
  --color-base-200: #2c2c2e;
  --color-base-300: #3a3a3c;
  --color-error: #f87171;
  --color-error-content: #0c1017;
  --color-info: #41B6E6;
  --color-info-content: #0c1017;
  --color-neutral: #f4f4f5;
  --color-neutral-content: #1c1c1e;
  --color-primary: #41B6E6;
  --color-primary-content: #0c1017;
  --color-secondary: #2c2c2e;
  --color-secondary-content: #f4f4f5;
  --color-success: #25ac83;
  --color-success-content: #0c1017;
  --color-warning: #e58212;
  --color-warning-content: #18181b;
  --bg-app: #101012;
  --bg-panel: #1c1c1e;
  --bg-surface: #2c2c2e;
  --bg-surface-hover: #3a3a3c;
  --border-base: #3a3a3c;
  --text-primary: #f4f4f5;
  --text-secondary: #b6b6bd;
  --text-tertiary: #8b8b96;
}

/* Ambient field behind glass chrome so blur reads as liquid glass so material blur remains perceptible.
   Soft color fields only — never high-frequency texture under L1 chrome. */
.bao-shell-ambient {
  background-color: var(--bg-app, var(--color-base-100));
  background-image:
    radial-gradient(
      ellipse 120% 70% at 2% -20%,
      color-mix(in oklab, var(--color-primary) 26%, transparent),
      transparent 68%
    ),
    radial-gradient(
      ellipse 90% 58% at 106% 4%,
      color-mix(in oklab, var(--color-secondary) 20%, transparent),
      transparent 62%
    ),
    radial-gradient(
      ellipse 70% 50% at 48% 118%,
      color-mix(in oklab, var(--color-info) 14%, transparent),
      transparent 66%
    ),
    radial-gradient(
      ellipse 48% 34% at 74% 30%,
      color-mix(in oklab, var(--color-accent, var(--color-primary)) 10%, transparent),
      transparent 72%
    ),
    linear-gradient(
      165deg,
      color-mix(in oklab, var(--color-base-200) 70%, transparent) 0%,
      transparent 50%
    );
  background-attachment: fixed;
}

/* Elevation from the canonical token (theme-aware via --glass-shadow-*),
   plus the shared inset top-light line — no bespoke shadow recipes. */
.bao-surface-glass-regular.bao-glass-large,
[data-theme="capture-dx-dark"] .bao-surface-glass-regular.bao-glass-large {
  box-shadow:
    var(--shadow-glass-floating),
    0 1px 0 0 color-mix(in oklab, var(--glass-highlight-strong) 70%, transparent) inset;
}

details.dropdown:not([open]) > .dropdown-content,
  details.dropdown:not([open]) > [data-dropdown-panel],
  details.dropdown:not([open]) > .bao-dropdown-panel {
  display: none;
}

/*
 * daisyUI 5 only paints .dropdown-content on .dropdown-open / :focus-within.
 * Android WebView opens <details> without focusing the summary, so the menu
 * stayed at opacity 0. Force open visibility from the native open attribute.
 *
 * align-content is load-bearing, not decoration. This rule replaces daisyUI's
 * .menu (display: flex; flex-direction: column) with a grid, and a grid whose
 * align-content is left at the initial 'normal' resolves to STRETCH: every
 * implicit row is sized by the panel box instead of by its own content. The
 * panel is content-sized today only by accident — nothing currently resolves
 * both of its block edges — so the defect stayed latent until a host pinned
 * the panel's block size, and then the rows split it evenly between them.
 *
 * Measured at 390x844 on /capture with the two-row page-toolbar overflow, the
 * panel forced to a definite 600px block size: rows landed at y=156 and y=453
 * (297px apart — 600/2 per track) with blank space between and after them,
 * while the controls stayed 44px tall. The reported production panel is the
 * same arithmetic one size up: 1340px tall, two rows, second row ~670px below
 * the first. With align-content: start the same 600px box lays the rows at
 * y=156 and y=204 — 44px plus the 4px gap, a uniform list — and the panel
 * collapses back to its content height wherever the block size is free.
 *
 * A menu's height is its content's height. Declare it; never infer it.
 */
details.dropdown[open] > .dropdown-content,
details.dropdown.dropdown-open > .dropdown-content,
details.dropdown[open] > [data-dropdown-panel],
details.dropdown.dropdown-open > [data-dropdown-panel] {
  display: grid;
  align-content: start;
  opacity: 1;
  scale: 1;
  visibility: visible;
  pointer-events: auto;
  z-index: var(--z-dropdown, 1000);
}

/*
 * Compact scrim behind an open menu (§8.6: popovers become sheets on compact).
 *
 * Measured at 390x844 and 320x720: every open dropdown panel — page-toolbar
 * overflow, account menu, capture overflow, row overflow — floated over a live,
 * fully lit page with nothing separating the layers and nothing to dismiss
 * against. The panel's own backdrop-filter blurred what was directly behind it
 * and stopped there.
 *
 * The scrim is the panel's own ::backdrop, not a rendered element: the shell
 * dropdown controller already promotes open panels to the top layer via
 * popover, so the pseudo-element costs no markup and no class strings composed
 * in TypeScript. It is also not hit-testable — measured with
 * elementFromPoint(40, panelBottom + 200), which resolves to the page content
 * underneath with and without the scrim — so the controller's outside-click
 * dismissal keeps working unchanged (verified: the menu closes on a scrim tap).
 *
 * Recipe mirrors .bao-filter-sheet-backdrop (the canonical compact scrim) so
 * both sheets read as one material. Phone band only: a pointer-first dropdown
 * on tablet/desktop is an anchored popover, and HIG does not scrim those.
 */
@media (max-width: 47.99rem) {
  .bao-dropdown-panel:popover-open::backdrop {
    background-color: color-mix(in oklab, var(--color-base-100) 60%, transparent);
    -webkit-backdrop-filter: blur(var(--liquid-blur-clear, 8px));
    backdrop-filter: blur(var(--liquid-blur-clear, 8px));
  }
  @media (forced-colors: active) {
    .bao-dropdown-panel:popover-open::backdrop {
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }
}

/*
 * Host-relative end anchoring, for the ABSOLUTELY POSITIONED panel only.
 *
 * :not(.bao-dropdown-panel-viewport) is load-bearing. app-shell-dropdowns
 * promotes an open panel to position: fixed in the popover top layer and
 * publishes trigger-anchored geometry in --bao-dropdown-left /
 * --bao-dropdown-right. Once the panel is fixed, inset-inline-end: 0 resolves
 * against the VIEWPORT rather than the host, so this rule silently discarded
 * that geometry and pinned every end-aligned menu to the window edge —
 * measured at 1440x900 on a Cases row overflow, the trigger's right edge sat
 * at x=1353 while the panel's landed at x=1440, 87px adrift, with
 * --bao-dropdown-right: 252.27px computed and thrown away.
 *
 * Specificity could never have settled this: the rule is unlayered and the
 * anchoring rule it outranked sits in @layer utilities, which loses to
 * unlayered no matter how many classes it repeats. Scoping by mode gives each
 * positioning scheme exactly one owner instead.
 */
.dropdown.dropdown-end > [data-dropdown-panel]:not(.bao-dropdown-panel-viewport) {
  inset-inline-end: 0;
  inset-inline-start: auto;
}

/* Topbar paint only; topbar-zones owns layout and safe-area geometry. */
#app-header.navbar,
#app-header[data-shell-topbar] {
  /* Solid first — WebView 101 drops color-mix declarations entirely. */
  background-color: var(--bg-panel, var(--color-base-100));
  border-bottom: 1px solid var(--border-base);
  box-sizing: border-box;
  -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
  backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
}
/* WebView 101: color-mix declarations are dropped there, so the solid panel
 * fill above is the resting style. Capable engines re-enable the translucent
 * glass fill via feature detection through the canonical capability rule, and
 * strengthen the tint while content scrolls beneath when content reaches the scroll edge.
 * Translucency stays off for every reduced-transparency / increased-contrast
 * signal — OS preference and product toggle alike for every accessibility preference signal. */
#app-header.navbar,
#app-header[data-shell-topbar] {
  background-image: none;
}
@supports (background-color: color-mix(in oklab, red 50%, transparent)) {
  @media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) {
    html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-header.navbar,
    html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-header[data-shell-topbar] {
      background-color: color-mix(in oklab, var(--bg-panel) calc(var(--liquid-fill-persistent) * 100%), transparent);
      background-image: linear-gradient(var(--liquid-light-angle), var(--glass-illumination-strong), var(--glass-highlight-clear));
    }
    html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-header[data-shell-topbar][data-scroll-edge="true"] {
      background-color: color-mix(in oklab, var(--bg-panel) calc(var(--liquid-fill-transient) * 100%), transparent);
      -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
      backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-strong));
    }
  }
}



/*
 * Topbar zone/strip/search/status rules live in ONE owner:
 * tokens-glass-shell-context-density.ts. This module keeps only
 * WebView-specific hardening that is not topbar chrome.
 */
/*
 * Keyboard cheatsheet control only — .btn.hidden permanently beats sm:inline-flex.
 * Theme toggle + voice must NEVER use this class (always-visible chrome).
 */
#app-header .bao-shell-kbd-control {
  display: none;
}
@media (min-width: 40rem) {
  #app-header .bao-shell-kbd-control {
    display: inline-flex;
  }
}

/*
 * Topbar hamburger:
 * - Phone (<48rem): dock "More" opens the drawer — hide duplicate hamburger.
 * - Tablet (48–79.99rem): persistent icon rail owns nav — hamburger retired
 *   (tokens-glass-shell-drawer.ts tablet band renders the rail in-flow).
 * - Permanent sidebar (≥80rem): hide hamburger — rail collapse lives in
 *   #app-sidebar [data-sidebar-rail-toggle] (no dual collapse controls).
 */
#app-header [data-drawer-toggle],
#app-header .bao-drawer-toggle-control {
  display: none;
}
@media (min-width: 80rem) {
  #app-header [data-drawer-toggle],
  #app-header .bao-drawer-toggle-control {
    display: none;
  }
}


@media (max-width: 63.99rem) {
  /*
   * Dual-title ban on phone (design.pdf §4 / §5.1): when content PageHeader
   * owns naming, keep shell title demoted. Restore shell title only when
   * shell mode owns naming (immersive/media demoted H1).
   */
  body[data-page-header-mode="shell"] #shell-page-title,
  body[data-page-header-mode="shell"] [data-shell-page-title] {
    position: static;
    width: auto;
    height: auto;
    max-width: 9rem;
    margin: 0;
    padding: 0;
    overflow: hidden;
    clip: auto;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  /* Page body breadcrumbs still hide on phone — H1 owns in-page naming. */
  [data-page-breadcrumbs] {
    display: none;
  }
}


#app-header details.dropdown > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Floor via the font-invariant token, not a rem literal — 2.75rem is
     176px at a 400% root font and inflated the topbar controls past a
     phone viewport with no scroller (HIG Dynamic Type). */
  min-width: var(--touch-target-min, 2.75rem);
  min-height: var(--touch-target-min, 2.75rem);
  cursor: pointer;
}

#app-header details.dropdown > summary::-webkit-details-marker {
  display: none;
}

#app-header [data-user-avatar],
#app-header .avatar-placeholder > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  min-width: 1.75rem;
  min-height: 1.75rem;
  max-width: 1.75rem;
  max-height: 1.75rem;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: var(--color-neutral);
  color: var(--color-neutral-content);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  line-height: 1;
  text-overflow: clip;
  white-space: nowrap;
}

/* Topbar account control — square circle only (never expand to label width). */
#app-header details.dropdown[data-topbar-user-menu] > summary {
  width: var(--touch-target-min, 2.75rem);
  height: var(--touch-target-min, 2.75rem);
  overflow: hidden;
}
#app-header details.dropdown[data-topbar-user-menu] > summary .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Shell scroll chain — nested daisyUI drawer flex is ignored on WebView 101. */
html.h-full,
html {
  height: 100%;
  max-height: 100%;
  /* tokens.bao typography SSOT — platform-native Apple stack on every surface. */
  font-family: var(--font-sans);
}

body.bao-shell-ambient,
body {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-height: 100%;
  min-height: 0;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.bao-shell-drawer {
  flex: 1 1 0%;
  min-height: 0;
  max-height: 100%;
  /*
   * The drawer itself was still `overflow-x: hidden` — a scroll container.
   * Measured on /cases at 1440x900: the topbar account control's tooltip
   * pseudo (111px, absolute, centered under a 44px control) extends 24px
   * past the header's inline end, so the drawer reported scrollWidth 1464
   * in a 1440 box; opening any row menu focused into it and scrolled the
   * whole shell 24px sideways — the sidebar painted at x=-24 with no way
   * back. `clip` keeps the visual clip and removes the scroll container.
   */
  overflow: clip;
}

/*
 * These two are layout columns, not scrollports: `clip`, never `hidden`.
 *
 * `hidden` clips visually but still creates a scroll container the browser may
 * scroll programmatically — a focus() or scrollIntoView() on anything wider
 * than the column shoves the entire content column sideways, with no scrollbar
 * to bring it back. Measured on /cases at 1440x900: .drawer-content sat at
 * scrollLeft 23.5 and its child at 10, so #app-main painted at x=23 while the
 * 56px rail painted over it — elementFromPoint at the breadcrumb's leading
 * edge returned NAV#app-sidebar, and the breadcrumb lost its leading glyphs.
 *
 * Both axes, not just the inline one. CSS Overflow 3: when one axis is `clip`
 * and the other is neither `clip` nor `visible`, the `clip` computes back to
 * `hidden` — so `overflow-x: clip; overflow-y: hidden` is a no-op that still
 * measures as `hidden` on both. Neither column ever needed to scroll: #app-main
 * is the scrollport, and it is a descendant, so sticky offsets inside it are
 * unaffected by removing the scroll container here.
 *
 * The child already carries the `overflow-x-clip` utility, which asked for
 * exactly this — but that utility lives in @layer utilities while these rules
 * are unlayered, so the `overflow` shorthand silently overrode it.
 */
.bao-shell-drawer > .drawer-content {
  flex: 1 1 0%;
  min-height: 0;
  max-height: 100%;
  overflow: clip;
}

.bao-shell-drawer > .drawer-content > .relative {
  flex: 1 1 0%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: clip;
}
/* Permanent-rail band (80rem SSOT): #app-main and #session-rail share the
   row — column direction here stacked the rail beneath main and painted it
   over the page content. */
@media (min-width: 80rem) {
  .bao-shell-drawer > .drawer-content > .relative {
    flex-direction: row;
  }
}

#app-main {
  
  
  
  overflow-x: hidden;
  
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* Happydumpling page gutter: section rhythm, not raw rem hardcodes.
   * Do not set padding-block shorthand — dock clearance owns block-end
   * (tokens-layout-dock) so collection pages keep dock+safe-area room. */
  padding-inline: min(var(--page-gutter-inline, var(--space-section, 1rem)), 5%);
  padding-block-start: var(--page-gutter-block, var(--space-section, 0.75rem));
}

/*
 * Non-media pages under permanent-rail: reserve fixed-dock scroll room.
 * WebView 101 has no reliable :has(); data-main-page-type is the portable key.
 */
@media (max-width: 79.99rem) {
  #app-main:not([data-main-page-type="media"]):not([data-main-page-type="conversation"]) {
    padding-block-end: calc(
      var(--dock-height, 4rem) + env(safe-area-inset-bottom, 0px) + var(--space-section, 0.75rem) + var(--touch-target-min, 2.75rem)
    );
  }
}
@media (min-width: 80rem) {
  #app-main:not([data-main-page-type="media"]):not([data-main-page-type="conversation"]) {
    padding-block-end: var(--page-gutter-block, var(--space-section, 0.75rem));
  }
}

/*
 * Sign-in renders NO dock, so reserving dock clearance for it leaves a band of
 * bare #app-main below the auth shell. The shell paints the ambient gradient;
 * main does not — so the reserved strip read as a white cut-off under the
 * sign-in card (measured 84px at 375x812, of which 72px was this padding).
 *
 * data-page is the page's own key and is present in the SSR markup, so this
 * needs no JS mirror. Engines without :has() simply keep the old clearance —
 * extra space, never a broken layout.
 */
#app-main:has([data-page="login"]):not([data-main-page-type="media"]):not([data-main-page-type="conversation"]) {
  padding-block-end: 0;
}

/*
 * Immersive media/conversation: zero side/top gutters so workbench fills,
 * but keep dock clearance so shutter/composer never sits under fixed dock.
 * WebView 101 has no :has() — use data-main-page-type mirrored by app-shell JS.
 */
#app-main[data-main-page-type="media"],
#app-main[data-main-page-type="conversation"],
#app-main:has([data-page-type="media"]),
#app-main:has([data-page-type="conversation"]) {
  
  
  padding-block-end: calc(
    var(--dock-height, 4rem) + env(safe-area-inset-bottom, 0px)
  );
  /*
   * Stays hidden at every width. The stacked phone layout does NOT need
   * #app-main to scroll: the acquire panel's own section is already the
   * scrollport (measured on /capture at 320x720 — clientHeight 528,
   * scrollHeight 564), so opening a second one here would nest scrollers for
   * no reachability gain.
   */
  overflow: hidden;
}
@media (min-width: 80rem) {
  #app-main[data-main-page-type="media"],
  #app-main[data-main-page-type="conversation"],
  #app-main:has([data-page-type="media"]),
  #app-main:has([data-page-type="conversation"]) {
    
  }
}
/*
 * Mobile-dock material is NOT declared here. tokens-layout-dock-base is the
 * single owner (border/background/color + backdrop-filter). TOKENS_LAYOUT_DOCK_CSS
 * emits after TOKENS_GLASS_CSS (tokens.ts), so a dock surface rule in this
 * module would be dead weight that only looks authoritative.
 */

/* Solid cards/panels — theme tokens, never raw white (dark mode WebView SSOT). */
.card,
[data-dashboard-widget],
.bao-data-panel,
.fieldset.bao-settings-group {
  background: var(--bg-panel, var(--color-base-100));
  color: var(--text-primary, var(--color-base-content));
}
/* Data panel borders belong to chrome-part, including their image reset. */
.card,
[data-dashboard-widget],
.fieldset.bao-settings-group {
  border-color: var(--border-base, var(--color-base-300));
}

/*
 * Form controls — solid theme vars (no color-mix/oklch required).
 *
 * The boundary is --border-control, not --border-base. These controls paint
 * their background with the panel they sit on, so the border is the ONLY
 * thing that says "this is a field": measured on /api-keys at 1280x800, the
 * input background matched its host exactly (1.00:1) and --border-base gave
 * the outline 1.31:1, against the 3:1 WCAG 2.2 SC 1.4.11 asks of a component
 * boundary. --border-base stays where it belongs — the decorative hairlines
 * between cards and panels, which identify nothing.
 */
.input,
.select,
.textarea,
.file-input {
  background-color: var(--bg-panel, var(--color-base-100));
  color: var(--text-primary, var(--color-base-content));
}
/* Input/select/textarea borders belong to shell-controls-part-1. */
.file-input {
  border-color: var(--border-control, var(--color-base-300));
}
.input::placeholder,
.textarea::placeholder {
  color: var(--text-secondary, var(--color-base-content));
  opacity: var(--opacity-medium, 0.75);
}

/* Ghost/outline chrome that daisyUI paints with color-mix on modern Chromium. */
.btn-soft,
.btn-outline {
  color: var(--text-primary, var(--color-base-content));
  border-color: var(--border-base, var(--color-base-300));
}
/* Button variant paint is owned by shell-controls-part-1. */

body,
body.bao-shell-ambient {
  background-color: var(--bg-app, var(--color-base-100));
  color: var(--text-primary, var(--color-base-content));
}

/* Shell sidebar surface — safe-area insets clear mobile notches and
   rounded desktop edges; smooth scroll honors reduced-motion. */
.bao-shell-sidebar {
  padding-top: max(var(--safe-area-inset-top, var(--space-micro, 0.25rem)), env(safe-area-inset-top));
  padding-bottom: max(var(--safe-area-inset-bottom, var(--space-micro, 0.25rem)), env(safe-area-inset-bottom));
  scroll-behavior: smooth;
}
/*
 * The sidebar surface has ONE owner: the regular Liquid Glass material on
 * #app-sidebar itself. This block used to paint an opaque var(--bg-panel)
 * plate across the nav list, which covered the entire visible rail and left
 * the material showing only in the padding gutter — glass in the class list,
 * solid on screen. Apple HIG Materials answers legibility over a material
 * with the regular variant ("blurs and adjusts the luminosity of background
 * content") plus vibrant foreground colors, not with an opaque nested plate.
 * The radius and text color stay; the fill does not.
 */
#app-sidebar.bao-shell-sidebar > [data-widget="sidebar"],
#app-sidebar.bao-shell-sidebar > .menu,
#app-sidebar.bao-shell-sidebar > ul.menu {
  border-radius: var(--radius-field, 0.75rem);
  color: var(--text-primary, var(--color-base-content));
}
#app-sidebar.bao-shell-sidebar > [data-sidebar-brand],
#app-sidebar.bao-shell-sidebar > [data-sidebar-rail-header] {
  background: color-mix(in oklab, var(--bg-panel, var(--color-base-100)) 92%, transparent);
  border-radius: var(--radius-field, 0.75rem);
}
@media (prefers-reduced-motion: reduce) {
  .bao-shell-sidebar {
    scroll-behavior: auto;
  }
}

/* When measured chrome leaves no interaction row, one outer scrollport owns the entire shell. */
.bao-shell-drawer[data-shell-chrome-flow] > .drawer-content {
  display: block;
  overflow-y: auto;
  overflow-x: clip;
}
.bao-shell-drawer[data-shell-chrome-flow] #app-header {
  position: relative;
}
.bao-shell-drawer[data-shell-chrome-flow] .bao-shell-content {
  flex: none;
  height: auto;
  min-height: min-content;
  overflow: visible;
}
.bao-shell-drawer[data-shell-chrome-flow] #app-main {
  display: block;
  height: auto;
  min-height: min-content;
  overflow: visible;
  padding-block-end: var(--space-section);
}
.bao-shell-drawer[data-shell-chrome-flow] #app-main > [data-page] {
  height: auto;
  min-height: min-content;
}
.bao-shell-drawer[data-shell-chrome-flow] #mobile-dock {
  position: relative;
  inset: auto;
}


/*
 * Form action rows — full-width labeled verbs on phone (save is primary,
 * never a floating icon-only square under WebView 101 / no-hover).
 */
@media (max-width: 47.99rem) {
  [data-licensing-admin-form] form > .flex.flex-row,
  [data-licensing-serial-admin] form > .flex.flex-row,
  form .flex.flex-row.flex-wrap.items-center.justify-end {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: var(--space-inline, 0.5rem);
  }
  [data-licensing-admin-form] .btn,
  [data-licensing-serial-admin] .btn,
  form .flex.flex-row.flex-wrap.items-center.justify-end > .btn {
    width: 100%;
    min-width: 0;
    min-height: var(--touch-target-min, 2.75rem);
    justify-content: center;
  }
  form .bao-toolbar-action.btn-square {
    width: 100%;
    min-width: 0;
    padding-inline: 0.75rem;
  }
  form .bao-toolbar-action.btn-square .bao-toolbar-label,
  form .btn .bao-toolbar-label {
    display: inline;
    max-width: none;
    opacity: 1;
    margin-inline-start: 0.375rem;
  }
  /*
   * Collection/home bento grids on phone: generic reactive panels collapse to
   * one column. Compact KPI stat widgets on home pair two-across (Apple Health
   * summary rhythm — design.pdf §8.2). Rich/full/form workbench widgets always
   * stack full-bleed — never crush setup/training/models forms to half width.
   */
  [data-page] .bao-grid-reactive-panels,
  [data-page] [data-section-group] > .bao-grid-reactive-panels {
    grid-template-columns: minmax(0, 1fr);
  }
  /*
   * Default phone dashboard grids: single column (forms/setup/training).
   * Home KPI strip re-opens 2-up AFTER this rule so cascade wins.
   */
  [data-page] [data-dashboard-grid] {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-inline, 0.5rem);
  }
  /* Home KPI strip only: 2-up compact stats (higher cascade than base). */
  [data-page-type="home"] [data-dashboard-grid],
  [data-page="dashboard"] [data-dashboard-grid] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-inline, 0.5rem);
  }
  /* Uniform full layout / detail workbenches never 2-up on phone. */
  [data-page] [data-dashboard-layout-root][data-dashboard-uniform-size="full"] [data-dashboard-grid],
  [data-page-type="detail"] [data-dashboard-grid],
  [data-page-type="ops"] [data-dashboard-grid],
  [data-page-type="form"] [data-dashboard-grid] {
    grid-template-columns: minmax(0, 1fr);
  }
  [data-page] .bao-grid-panel-quarter,
  [data-page] .bao-grid-panel-third,
  [data-page] .bao-grid-panel-half,
  [data-page] .bao-grid-panel-narrow,
  [data-page] .bao-grid-panel-wide,
  [data-page] .bao-grid-panel-full {
    grid-column: 1 / -1;
  }
  /*
   * Home only: auto placement restores 2-across for compact KPI cards.
   * Explicit full/rich/form widgets keep the full row (setup, models, etc.).
   */
  [data-page-type="home"] [data-dashboard-grid] > [data-dashboard-widget][data-widget-density="compact"],
  [data-page="dashboard"] [data-dashboard-grid] > [data-dashboard-widget][data-widget-density="compact"] {
    grid-column: auto;
  }
  /* Data-dense / rich / full widgets keep the full row on every page. */
  [data-page] [data-dashboard-grid] > [data-dashboard-widget][data-widget-density="rich"],
  [data-page] [data-dashboard-grid] > [data-dashboard-widget][data-dashboard-panel-size="full"],
  [data-page] [data-dashboard-grid] > [data-dashboard-widget].bao-grid-panel-full,
  [data-page] [data-dashboard-grid] > [data-dashboard-widget][data-widget="recent-cases"],
  [data-page] [data-dashboard-grid] > [data-dashboard-widget]:has(table),
  [data-page] [data-dashboard-grid] > [data-dashboard-widget]:has(form),
  [data-page] [data-dashboard-grid] > [data-dashboard-widget]:has([data-case-list]) {
    grid-column: 1 / -1;
  }
  [data-page-header-actions] > .btn {
    flex: 1 1 auto;
    min-height: var(--touch-target-min, 2.75rem);
    justify-content: center;
  }
}


/*
 * Collection grids (models/cards) on WebView 101 — never 12 thin tracks with
 * span-1 children (crushes card titles into vertical glyphs). Phone = 1-col.
 */
@media (max-width: 47.99rem) {[data-page="models"] .bao-grid-reactive-panels,
[data-page="models"] .bao-stagger-children.bao-grid-reactive-panels {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-section, 0.75rem);
  }[data-page="models"] [data-model-download-card],
[data-page="models"] .bao-grid-panel-third,
[data-page="models"] .bao-grid-panel-quarter {
    grid-column: 1 / -1;
    width: 100%;
    min-width: 0;
  }/* Freeze infinite motion that reflows under HTMX load on Chromium 101. */
  .skeleton-shimmer,
.bao-stagger-children > *,
.htmx-added,
[data-page] [data-state-content] {
    animation: none;
  }
}


}
@layer bao-tokens {

/*
 * WebKit hardening: daisyUI emits unprefixed `appearance: none` only, so iOS
 * Safari still paints the NATIVE checkbox/toggle/select control UNDER the
 * styled one (the doubled toggle: outline pill + filled native square). The
 * prefixed reset is required on every appearance-styled control — this is the
 * single SSOT owner (never a per-page override).
 */
/* Specificity above daisyUI .checkbox { appearance: none } alone so WebKit
 * never reverts to native double-paint under dialog stacking (audit:structure
 * dialog@phone native-control-paint). Input-typed selectors win UA+daisy. */
.toggle,
input.toggle[type="checkbox"],
.checkbox,
input.checkbox[type="checkbox"],
.radio,
input.radio[type="radio"],
.select,
select.select,
.range,
input.range[type="range"],
.file-input,
input.file-input[type="file"] {
  -webkit-appearance: none;
  appearance: none;
}
/* The input retains the shared touch floor. Track and thumb paint have their
 * own geometry: growing the input must never grow the filled slider shadow.
 * Engine pseudo-elements remain separate because an unsupported selector
 * invalidates the entire selector list in older WebViews. */
input.range[type="range"] {
  background-color: transparent;
  background-image: none;
  overflow: visible;
  clip-path: none;
  padding: 0;
}
input.range[type="range"]::-webkit-slider-runnable-track {
  display: block;
  height: var(--space-micro);
  border-radius: var(--radius-selector);
  background-color: var(--border-control);
  background-image: none;
}
input.range[type="range"]::-moz-range-track {
  display: block;
  height: var(--space-micro);
  border-radius: var(--radius-selector);
  background-color: var(--border-control);
  background-image: none;
}
input.range[type="range"]::-webkit-slider-thumb {
  /* Native margin owns centering; component inset/transform must not shift it again. */
  inset-block-start: 0;
  transform: none;
  -webkit-appearance: none;
  appearance: none;
  width: var(--space-xl);
  height: var(--space-xl);
  margin-block-start: calc((var(--space-micro) - var(--space-xl)) / 2);
  border: var(--border) solid var(--color-primary);
  border-radius: var(--radius-selector);
  background-color: var(--color-primary);
  background-image: none;
  box-shadow: none;
  clip-path: none;
}
input.range[type="range"]::-moz-range-thumb {
  width: var(--space-xl);
  height: var(--space-xl);
  border: var(--border) solid var(--color-primary);
  border-radius: var(--radius-selector);
  background-color: var(--color-primary);
  background-image: none;
  box-shadow: none;
  clip-path: none;
}

/*
 * Checkbox affordance: daisyUI couples checkbox+toggle radius on one
 * --radius-selector token; at the theme's capsule value a small checkbox
 * reads as a RADIO (multi-select sections looked single-choice). Toggles
 * keep the capsule; the checkbox mark caps at the canon 0.25rem step so
 * square = multi-select, circle = single-select stays legible. Single
 * owner — never per-page.
 */
/* Switch tracks and thumbs share the capsule token; checkbox corners remain separate. */
.toggle {
  --radius-selector: var(--radius-full);
}
/* Track carries the state; the thumb remains a single white circular surface. */
input.toggle[type="checkbox"] {
  background-color: var(--bg-surface-hover);
  background-image: none;
  border-color: var(--border-control);
  box-shadow: none;
}
input.toggle[type="checkbox"]:checked {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
}
input.toggle[type="checkbox"]::before,
input.toggle[type="checkbox"]:checked::before {
  background-color: var(--white);
  background-image: none;
  color: var(--white);
  box-shadow: var(--shadow-sm);
  outline: none;
}
@media (forced-colors: active) {
  input.toggle[type="checkbox"] {
    background-color: Canvas;
    border-color: ButtonText;
  }
  input.toggle[type="checkbox"]:checked {
    background-color: Highlight;
    border-color: Highlight;
  }
  input.toggle[type="checkbox"]::before,
  input.toggle[type="checkbox"]:checked::before {
    background-color: ButtonText;
    box-shadow: none;
  }
}

.checkbox {
  /*
   * Published as a custom property, not just applied: the painted affordance
   * is drawn by `.checkbox.bao-touch-target::after`, and a pseudo-element
   * inherits its originating element's custom properties. Declaring the cap
   * here and reading it there keeps ONE owner for the shape. It was applied
   * here alone once, and the pseudo went on resolving --radius-selector
   * directly — so the input the reader cannot see was square while the box
   * the reader does see stayed a 12px circle on a 24px mark, i.e. a radio.
   */
  --checkbox-mark-radius: min(var(--radius-selector, 0.75rem), var(--radius-xs, 0.25rem));
  border-radius: var(--checkbox-mark-radius);
}
/* WebView button metrics; variant selectors below retain equal specificity.
 * `.bao-fab` owns radius and band display in the dock/FAB tokens — leaving it
 * in this (0,3,0) rule forced a quadruple-class outrank. */
.btn:not([hidden]):not(.hidden):not(.bao-fab) {
  display: inline-flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--button-content-gap, var(--space-compact, 0.375rem));
  /*
   * Four corners, not a shorthand: `.join` is a per-corner contract.
   *
   * daisyUI 5 joins a control group by publishing `--join-ss/-se/-es/-ee` on
   * `.join` and re-declaring them on its first, last and only child, then
   * reading them per corner — its own `.btn` resolves
   * `border-start-start-radius: var(--join-ss, var(--radius-field))`. A
   * `border-radius` shorthand here set all four corners from a layer that
   * loads after daisyUI's, so every joined button won back its full radius
   * and the group painted as separate pills butted together instead of one
   * segmented control. Measured on /specimens: the table pager's Previous
   * and Next both computed `border-radius: 12px` on all four corners while
   * daisyUI's border collapse still ran, so the pair showed a doubled seam.
   * Reading the same four variables restores the group and leaves a
   * standalone button unchanged, because outside a `.join` the variables are
   * unset and each corner falls back to `--radius-field`.
   */
  border-width: 1px;
  border-style: solid;
  font-weight: var(--font-weight-medium);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  padding: var(--space-inline) var(--button-inline-padding, var(--space-section));
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
}


/* Labeled controls grow around wrapped captions; explicit icon shapes keep their dimensions. */
.btn:not([hidden]):not(.hidden):not(.bao-fab):not(.btn-circle):not(.btn-square) {
  height: auto;
}

/* Circle controls use the canonical capsule radius instead of a component literal. */
.btn.btn-circle {
  border-radius: var(--radius-full);
}

.btn:not([hidden]):not(.hidden):not(.bao-fab):not(.btn-circle) {
  border-start-start-radius: var(--join-ss, var(--radius-full));
  border-start-end-radius: var(--join-se, var(--radius-full));
  border-end-start-radius: var(--join-es, var(--radius-full));
  border-end-end-radius: var(--join-ee, var(--radius-full));
}/* Default paint carries one class of specificity. Semantic variants and
 * aria-driven selection own their colors without competing with visibility
 * exclusions, which belong to the button geometry contract above. */
.btn:where(:not([hidden]):not(.hidden):not(.bao-fab)) {
  justify-content: center;
  white-space: normal;
  overflow-wrap: anywhere;
  border-color: var(--border-base, var(--color-base-300));
  background-color: var(--button-neutral-fill, var(--bg-panel));
  background-image: var(--button-material-sheen, none);
  box-shadow: var(--button-material-shadow, none);
  -webkit-backdrop-filter: var(--button-material-blur, none);
  backdrop-filter: var(--button-material-blur, none);
  color: var(--text-primary, var(--color-base-content));
}
/* Shared button material. Variants supply tint; the base owns illumination.
   Accessibility preferences retain solid paint without optical effects. */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .btn:where(:not(:disabled):not([aria-disabled="true"]):not(.btn-disabled):not(.btn-ghost):not(.btn-link)) {
    --button-neutral-fill: var(--glass-fill-bg);
    --button-tint-opacity: var(--glass-button-tint-opacity);
    --button-material-sheen: linear-gradient(var(--liquid-light-angle), var(--glass-button-highlight), var(--glass-highlight-clear) 52%, var(--glass-illumination-soft));
    --button-material-shadow: var(--shadow-glass-clear), var(--shadow-glass-inner);
    --button-material-blur: blur(var(--liquid-blur-small)) saturate(var(--liquid-saturate-regular));
  }
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) :where(.bao-surface-glass-regular, .bao-surface-glass-strong, .bao-surface-glass-modal, .bao-surface-glass-clear) .btn {
    --button-material-blur: none;
  }
}

/* Hidden controls retain their semantic state independently of button styling. */
.btn[hidden],
.btn.hidden,
button.btn[hidden],
button.btn.hidden,
[hidden].btn,
button.hidden,
button[hidden] {
  display: none;
}

.btn svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

/* Match base :not() specificity so primary/error fills actually paint. */
.btn-primary:not([hidden]):not(.hidden),
.btn.btn-primary:not([hidden]):not(.hidden),
.btn:where([data-training-submit-open])[aria-disabled="false"]:not([hidden]):not(.hidden) {
  background-color: var(--color-primary);
  background-color: color-mix(in oklab, var(--color-primary) calc(var(--button-tint-opacity, 1) * 100%), transparent);
  border-color: var(--color-primary);
  color: var(--color-primary-content);
}

.btn-error:not([hidden]):not(.hidden),
.btn.btn-error:not([hidden]):not(.hidden) {
  background-color: var(--color-error);
  background-color: color-mix(in oklab, var(--color-error) calc(var(--button-tint-opacity, 1) * 100%), transparent);
  border-color: var(--color-error);
  color: var(--color-error-content);
}

.btn-success:not([hidden]):not(.hidden),
.btn.btn-success:not([hidden]):not(.hidden) {
  background-color: var(--color-success);
  background-color: color-mix(in oklab, var(--color-success) calc(var(--button-tint-opacity, 1) * 100%), transparent);
  border-color: var(--color-success);
  color: var(--color-success-content);
}

.btn-warning:not([hidden]):not(.hidden),
.btn.btn-warning:not([hidden]):not(.hidden) {
  background-color: var(--color-warning);
  background-color: color-mix(in oklab, var(--color-warning) calc(var(--button-tint-opacity, 1) * 100%), transparent);
  border-color: var(--color-warning);
  color: var(--color-warning-content);
}

/* Link actions retain their canonical text treatment inside shared button geometry. */
.btn.btn-link:not([hidden]):not(.hidden) {
  --button-neutral-fill: transparent;
  --button-material-sheen: none;
  --button-material-shadow: none;
  --button-material-blur: none;
  background-color: transparent;
  border-color: transparent;
  color: var(--color-primary);
  text-decoration: underline;
}

.btn-outline:not([hidden]):not(.hidden),
.btn.btn-outline:not([hidden]):not(.hidden) {
  background-color: transparent;
  border-color: var(--border-base, var(--color-base-300));
  color: var(--text-primary, var(--color-base-content));
}

.btn-soft:not([hidden]):not(.hidden),
.btn.btn-soft:not([hidden]):not(.hidden),
.btn:where([data-training-submit-open])[aria-disabled="true"]:not([hidden]):not(.hidden) {
  background-color: var(--button-neutral-fill, var(--bg-surface));
  border-color: var(--border-base);
  color: var(--text-primary, var(--color-base-content));
}

/* Hover is a fine-pointer enhancement (design.pdf §6) — touch keeps the
   resting fill and acknowledges presses via the :active rule below. */
@media (hover: hover) and (pointer: fine) {
  .btn-soft:not([hidden]):not(.hidden):hover,
  .btn.btn-soft:not([hidden]):not(.hidden):hover,
  .btn:where([data-training-submit-open])[aria-disabled="true"]:not([hidden]):not(.hidden):hover {
    background-color: var(--bg-surface-hover);
    border-color: var(--border-control);
  }
}

.btn-soft:not([hidden]):not(.hidden):active,
.btn.btn-soft:not([hidden]):not(.hidden):active,
.btn:where([data-training-submit-open])[aria-disabled="true"]:not([hidden]):not(.hidden):active,
.btn-soft:not([hidden]):not(.hidden).btn-active,
.btn.btn-soft:not([hidden]):not(.hidden).btn-active,
.btn:where([data-training-submit-open])[aria-disabled="true"]:not([hidden]):not(.hidden).btn-active,
.btn-soft:not([hidden]):not(.hidden)[aria-pressed="true"],
.btn.btn-soft:not([hidden]):not(.hidden)[aria-pressed="true"],
.btn:where([data-training-submit-open])[aria-disabled="true"]:not([hidden]):not(.hidden)[aria-pressed="true"] {
  background-color: var(--bg-surface-hover);
  border-color: var(--border-control);
  color: var(--color-base-content);
}

/* Filled segmented tabs use primary; border tabs retain their underline treatment.
   Session mode tabs are single-owned by tokens-glass-shell-capture (the
   reactive-state-owner gate) — only the generic canon arm may touch them. */
.tab.tab-active:not([hidden]):not(.hidden):not(.tabs-border > *),
.tab[aria-selected="true"]:not([hidden]):not(.hidden):not(.tabs-border > *),
.bao-settings-tab[aria-selected="true"]:not([hidden]):not(.hidden):not(.tabs-border > *),
.bao-settings-tab.tab-active:not([hidden]):not(.hidden):not(.tabs-border > *) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-content);
}

.btn-primary:not([hidden]):not(.hidden):active,
.btn.btn-primary:not([hidden]):not(.hidden):active,
.btn:where([data-training-submit-open])[aria-disabled="false"]:not([hidden]):not(.hidden):active,
.btn-primary:not([hidden]):not(.hidden)[aria-pressed="true"],
.btn.btn-primary:not([hidden]):not(.hidden)[aria-pressed="true"],
.btn:where([data-training-submit-open])[aria-disabled="false"]:not([hidden]):not(.hidden)[aria-pressed="true"] {
  background-color: color-mix(in oklab, var(--color-primary) 88%, var(--color-base-content));
  border-color: color-mix(in oklab, var(--color-primary) 88%, var(--color-base-content));
  color: var(--color-primary-content);
}

/* Ensure soft/ghost/outline remain hit-testable even under dense shell CSS. */
.btn:not([hidden]):not(.hidden):not(:disabled):not([aria-disabled="true"]) {
  pointer-events: auto;
  cursor: pointer;
}

/* Dense daisyUI sizes yield to the canonical touch-target utility. */
.btn:not([hidden]):not(.hidden):not(.bao-touch-target) {
  min-height: 2.5rem;
  padding: 0.5rem 0.875rem;
  font-size: var(--text-sm, 0.875rem);
  line-height: 1.2;
}

.btn-sm:not([hidden]):not(.hidden):not(.bao-touch-target),
.btn-xs:not([hidden]):not(.hidden):not(.bao-touch-target) {
  min-height: 2rem;
  padding: 0.25rem 0.625rem;
  font-size: var(--text-sm, 0.875rem);
}

.btn-square:not([hidden]):not(.hidden):not(.bao-touch-target) {
  width: 2.5rem;
  min-width: 2.5rem;
  padding-inline: 0;
}

/* Canonical touch floor yields to component minimums; visibility guards add no specificity. The floor reads the
 * touch-target token, NOT a rem literal: 2.75rem computes 176px at a 400%
 * root font, inflating every control past a phone viewport (measured
 * /models + /chat rail at right 458/1473). The token is font-invariant
 * (min(2.75rem, 44px)), so the floor stays 44px while text scales — the
 * HIG Dynamic Type contract, not a per-row escape. */
.bao-touch-target:where(:not([hidden]):not(.hidden)) {
  min-height: var(--touch-target-min, 2.75rem);
}
.bao-touch-target:not([hidden]):not(.hidden) {
  min-width: var(--touch-target-min, 2.75rem);
}

/*
 * The square width applies only where the control is genuinely icon-only.
 *
 * This is a 44px touch FLOOR, not a cap, and it was acting as both. At
 * (0,4,0) it outranked the labeled-density contract at (0,3,0) — so a toolbar
 * that declared data-toolbar-density="labeled" revealed its caption and then
 * painted it inside a box pinned to 44px. Measured on the editor dock:
 * "Rectangle" natural width 59px, button 44px, and an inline width:auto probe
 * returned 97px, proving the layout was fine and only this rule was in the
 * way. Six modules clipped that label; this is the one that actually won.
 *
 * min-width keeps the WCAG 2.5.8 / HIG floor intact for labelled controls too.
 *
 * The cap outlived that fix. Excluding only labelled density left it capping
 * every deliberately-larger variant, and the capture shutter is one: it
 * declares `btn-circle btn-lg` and its own rule asks for 3.5rem square, but
 * this selector scores (0,5,0) against that rule's (0,2,0), so the width was
 * clamped to 44px while the height it does not touch stayed at 56px.
 * Measured on /capture at 1440x900: the sole primary action of the page, the
 * shutter, rendered 44x56 — a vertical rectangle, the only control on the
 * surface whose height was not 44, and not the circle its classes describe.
 *
 * `btn-lg`/`btn-xl` are excluded for the same reason labelled density is: a
 * control that asked to be large is not an icon button, and a floor may not
 * decide its size. The sibling coarse-pointer rule below already draws the
 * line in exactly this place.
 */
.btn-square.bao-touch-target:not([hidden]):not(.hidden):not(.btn-lg):not(.btn-xl):not([data-toolbar-density="labeled"] *),
.btn-circle.bao-touch-target:not([hidden]):not(.hidden):not(.btn-lg):not(.btn-xl):not([data-toolbar-density="labeled"] *) {
  width: var(--touch-target-min, 2.75rem);
  min-width: var(--touch-target-min, 2.75rem);
  padding-inline: 0;
  --button-content-gap: 0px;
}
[data-toolbar-density="labeled"] .btn-square.bao-touch-target:not([hidden]):not(.hidden) {
  width: auto;
  min-width: var(--touch-target-min, 2.75rem);
}

/* Enforce the touch floor on coarse pointers without shrinking larger variants. */
@media (pointer: coarse), (hover: none) {
  .btn:not([hidden]):not(.hidden):not(.bao-touch-target):not(.btn-lg):not(.btn-xl),
  .btn.btn-sm:not([hidden]):not(.hidden):not(.bao-touch-target),
  .btn.btn-xs:not([hidden]):not(.hidden):not(.bao-touch-target) {
    min-height: var(--touch-target-min, 2.75rem);
  }
  .btn.btn-circle:not([hidden]):not(.hidden):not(.bao-touch-target),
  .btn.btn-square:not([hidden]):not(.hidden):not(.bao-touch-target) {
    min-width: var(--touch-target-min, 2.75rem);
  }
  /* Text controls join the floor — input-sm/select-sm computed 30-36px on
     phones (WCAG 2.5.8 / design.pdf §8.2 primary mobile controls 44-48). */
  .input:not([hidden]):not(.hidden),
  .select:not([hidden]):not(.hidden),
  .range:not([hidden]):not(.hidden) {
    min-height: var(--touch-target-min, 2.75rem);
  }
}

/*
 * Switch/checkbox/radio target floor (ALL pointers — not coarse-only).
 * Playwright audits measure the INPUT border box; packing toggle-sm (≈20px)
 * next to another control failed WCAG 2.5.8 on phone-narrow. Keep daisyUI 5
 * pill morphology (≈2:1 track, thumb slides on grid) — never force a 44×44
 * square that collapses the thumb animation. Floor: mark ≥24px (WCAG 2.5.8);
 * label row ≥44px isolates packing so the spacing exception is not required.
 */
.toggle:not([hidden]):not(.hidden),
.checkbox:not([hidden]):not(.hidden),
.radio:not([hidden]):not(.hidden) {
  position: relative;
  flex-shrink: 0;
  box-sizing: border-box;
  min-height: 1.5rem;
  min-width: 1.5rem;
}
/*
 * The switch is sized through daisyUI's own variable, never by pinning width
 * and height.
 *
 * daisyUI 5 derives the whole control from `--size`:
 * `width: calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)`,
 * `height: var(--size)`, `--toggle-p: calc(var(--size) * 0.125)`, and the knob
 * rides a `grid-template-columns: 0fr 1fr 1fr` track. Hand-set width/height
 * moved the track without moving that math: measured on /reports, a switch
 * carrying the generic 44pt floor computed 44x44 with `padding: 10px 3px` and
 * a 24px knob, so the knob overflowed a square track and the control read as
 * half-drawn. The previous fix layered a second 1.5rem track on `::after` and
 * `border: 0` on the real one, which hid the symptom and left two elements
 * claiming to be the same switch.
 *
 * Raising `--size` to the touch floor keeps the 2:1 pill, scales knob, padding
 * and corner radius together, and satisfies WCAG 2.2 SC 2.5.8 on BOTH axes —
 * the 44x24 track this replaced met the floor only horizontally.
 */
.toggle.bao-touch-target:not([hidden]):not(.hidden) {
  --size: var(--touch-target-min, 2.75rem);
  max-inline-size: 100%;
}
/* Label rows that host a switch grow to the touch floor so packing is isolated. */
.label:has(> .toggle),
.label:has(> .checkbox),
.label:has(> .radio) {
  min-height: var(--touch-target-min, 2.75rem);
  align-items: center;
  gap: var(--space-inline, 0.5rem);
}

/*
 * Touch: labeled square buttons may grow for visible verbs. Icon-only shell
 * chrome (bao-shell-icon-action) stays fixed — reasserted in topbar-icons CSS.
 */
@media (hover: none), (pointer: coarse) {
  .btn-square:not([hidden]):not(.hidden):not(.bao-shell-icon-action) {
    width: auto;
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    padding-inline: 0.625rem;
  }
  .btn-square:not(.bao-shell-icon-action) .bao-toolbar-label {
    display: inline-block;
  }
}

/* Sticky toolbar under shell chrome — tokenized offset with no arbitrary positioning utility. */
.bao-sticky-toolbar {
  position: sticky;
  top: calc(var(--chrome-min-height, 2.25rem) + env(safe-area-inset-top, 0px));
}

/* AI analysis overlay stage — reserved geometry (CLS), no min-h-48 raw utility. */
.bao-min-h-overlay-stage {
  min-height: var(--overlay-stage-min-height, 12rem);
}

/* Stage empty CTA only while state is empty (success paints canvas overlays). */
[data-ai-analysis-overlay-stage]:not([data-state="empty"]) [data-ai-analysis-overlay-empty] {
  display: none;
}

/* Textarea default height — tokenized (no min-h-32 raw utility). */
.textarea.bao-min-h-textarea {
  min-block-size: var(--textarea-min-height, 8rem);
}
/* Composer sizing belongs with textarea controls, above generic touch floors. */
.textarea.bao-min-h-composer {
  min-block-size: var(--composer-min-height);
}

/* PageHeader grid, H1, and phone collapse live in
 * tokens-glass-shell-page-header.ts (400-LOC monolith gate). */

/* background-color LONGHAND only: the background shorthand resets
 * background-image and erases the daisyUI select caret gradients app-wide
 * (unlayered rule beats the layered component). Selects also keep the
 * 1.75rem inline-end pad — that band IS the caret zone. */
.input,
.select {
  min-height: 2.5rem;
}
.input,
.select,
.textarea {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  /*
   * --border-control, matching the shorthand-free declaration in
   * tokens-glass-shell-webview-surfaces. Both rules sit in @layer bao-tokens
   * at equal specificity, so the later one decides — and while this one still
   * named --border-base, moving the other to the control token changed
   * nothing on screen. A field's border is its only affordance: measured on
   * /reports, the control background matched its host exactly and this border
   * gave the outline 1.31:1 in light and 1.46:1 in dark, against the 3:1 WCAG
   * 2.2 SC 1.4.11 asks of a component boundary.
   */
  border: var(--control-border-width, 1px) solid var(--border-control, var(--color-base-300));
  /*
   * Four corners, for the reason the base `.btn` rule states above: a text
   * field is joined too. The specimen and login search rows compose
   * `input input-sm … join-item grow` against a trailing button, and a
   * `border-radius` shorthand from this layer resolved all four corners, so
   * the field kept its full radius where it meets the button and the pair
   * read as two controls that happen to touch. Outside a `.join` the four
   * variables are unset and every corner still resolves to `--radius-field`.
   */
  border-start-start-radius: var(--join-ss, var(--radius-field));
  border-start-end-radius: var(--join-se, var(--radius-field));
  border-end-start-radius: var(--join-es, var(--radius-field));
  border-end-end-radius: var(--join-ee, var(--radius-field));
  /* Form-control paint is owned by webview-surfaces. */
  font-size: var(--text-sm, 0.875rem);
  box-sizing: border-box;
}
.textarea {
  --field-control-radius: var(--radius-panel);
}
:where(.input, .select, .textarea).border-0 {
  --control-border-width: 0px;
  box-shadow: none;
}
.select {
  padding-inline-end: 1.75rem;
}

.input-sm,
.select-sm {
  min-height: 2.25rem;
  padding: 0.375rem 0.625rem;
  font-size: var(--text-sm, 0.875rem);
}
.select-sm {
  padding-inline-end: 1.75rem;
}

/* Inline filter controls share the parent content plane; the open sheet owns its surface. */
[data-case-filters],
[data-primitive="FilterBar"] {
  padding: var(--space-inline) 0;
  gap: var(--space-inline);
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
/*
 * FilterBar host layout is CSS-only (FilterBar.bao no longer bakes flex/wrap).
 * Desktop/tablet: horizontal wrap row. Phone: 2-col grid (cascade owner late).
 */
form[data-case-filters],
form[data-primitive="FilterBar"],
form[data-filter-bar],
[data-case-filters],
[data-primitive="FilterBar"],
[data-filter-bar] {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  width: 100%;
  min-width: 0;
}


/* Disabled controls remain legible while clearly leaving the active action hierarchy. */
.btn:disabled:not([hidden]):not(.hidden),
.btn[aria-disabled="true"]:not([hidden]):not(.hidden),
.btn.btn-disabled:not([hidden]):not(.hidden) {
  background: color-mix(in oklab, var(--bg-panel, var(--color-base-100)) 88%, var(--color-base-200));
  border-color: var(--border-base, var(--color-base-300));
  color: var(--text-secondary, var(--color-base-content));
  box-shadow: none;
  cursor: not-allowed;
  opacity: var(--opacity-disabled, 0.5);
}
@media (max-width: 47.99rem) {
  form[data-case-filters],
  form[data-primitive="FilterBar"],
  form[data-filter-bar],
  [data-case-filters],
  [data-primitive="FilterBar"],
  [data-filter-bar] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: end;
    gap: var(--space-micro);
    flex-wrap: unset;
  }
  form[data-case-filters] fieldset:has(input[type="search"]),
  form[data-case-filters] fieldset:has([data-case-search]),
  [data-case-filters] fieldset:has(input[type="search"]),
  [data-case-filters] .fieldset:has(input[type="search"]),
  [data-case-filters] fieldset:has([data-case-search]),
  [data-primitive="FilterBar"] fieldset:has(input[type="search"]),
  [data-primitive="FilterBar"] .fieldset:has(input[type="search"]),
  [data-primitive="FilterBar"] fieldset:has([data-filter-search]),
  /* Actions row only — never bare .flex (fieldsets also carry class flex). */
  form[data-case-filters] > [data-filter-actions],
  [data-case-filters] > [data-filter-actions],
  [data-primitive="FilterBar"] > [data-filter-actions],
  form[data-case-filters] > div.flex.shrink-0,
  [data-case-filters] > div.flex.shrink-0,
  [data-primitive="FilterBar"] > div.flex.shrink-0,
  form[data-case-filters] > div.flex.items-center,
  [data-case-filters] > div.flex.items-center,
  [data-primitive="FilterBar"] > div.flex.items-center {
    grid-column: 1 / -1;
  }
  /* Only SEARCH legends are AT-only on phone (placeholder carries the cue).
     Select legends stay visible: status and priority both default to "All",
     so value-only selects are ambiguous side by side (HIG: controls
     identifiable without a tooltip). */
  form[data-case-filters] fieldset:has(input[type="search"]) .fieldset-legend,
  form[data-case-filters] fieldset:has(input[type="search"]) legend,
  form[data-case-filters] fieldset:has([data-case-search]) .fieldset-legend,
  form[data-case-filters] fieldset:has([data-case-search]) legend,
  [data-case-filters] fieldset:has(input[type="search"]) .fieldset-legend,
  [data-case-filters] fieldset:has(input[type="search"]) legend,
  [data-case-filters] fieldset:has([data-case-search]) .fieldset-legend,
  [data-case-filters] fieldset:has([data-case-search]) legend,
  [data-primitive="FilterBar"] fieldset:has(input[type="search"]) .fieldset-legend,
  [data-primitive="FilterBar"] fieldset:has(input[type="search"]) legend,
  [data-primitive="FilterBar"] fieldset:has([data-filter-search]) .fieldset-legend,
  [data-primitive="FilterBar"] fieldset:has([data-filter-search]) legend {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}


[data-case-filters] legend,
[data-case-filters] .fieldset-legend {
  padding: 0;
  /* Sentence case — HIG typography: all-caps/tracking-eyebrow labels shout
     and hurt legibility; the settings SSOT already moved to sentence case
     (Apple HIG: avoid shouting labels). Same here, same reason. */
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-secondary);
}

[data-case-filters] fieldset,
[data-case-filters] .fieldset,
[data-primitive="FilterBar"] fieldset,
[data-primitive="FilterBar"] .fieldset {
  display: flex;
  /*
   * flex-basis must stay auto. A fixed rem basis becomes HEIGHT when the bar
   * is flex-direction:column on phone — ADB S26 measured ~192px voids under
   * Debug Search. Inline min uses tokens.bao --grid-context-min only.
   */
  flex: 0 1 auto;
  flex-direction: column;
  gap: var(--space-micro);
  min-width: min(100%, var(--grid-context-min));
  margin: 0;
  padding: 0;
  border: 0;
}

@media (min-width: 48rem) {
  [data-case-filters] fieldset,
  [data-case-filters] .fieldset,
  [data-primitive="FilterBar"] fieldset,
  [data-primitive="FilterBar"] .fieldset {
    flex: 1 1 var(--grid-context-min);
    min-width: 0;
  }
}

/* Phone FilterBar fields fill their grid track without growing vertically. */
@media (max-width: 47.99rem) {
  [data-case-filters] fieldset,
  [data-case-filters] .fieldset,
  [data-primitive="FilterBar"] fieldset,
  [data-primitive="FilterBar"] .fieldset,
  [data-filter-bar] fieldset,
  [data-filter-bar] .fieldset {
    flex: 0 0 auto;
    min-width: 0;
    width: 100%;
    max-width: none;
    min-height: 0;
  }
  [data-case-filters] .bao-field-basis,
  [data-primitive="FilterBar"] .bao-field-basis,
  [data-filter-bar] .bao-field-basis {
    flex-basis: auto;
  }
}

/*
 * Case list, case card, case status, context card, context grid,
 * card/card-body/card-title, dashboard widget — all SSoT in
 * tokens-layout-page-shell-surfaces.ts (token-driven, no hardcoded hex).
 * Do NOT redefine them here.
 */

/* Conversation composer — unique WebView harden (no token source). */
[data-page="chat"] .bao-conversation-composer,
[data-page-type="conversation"] [data-composer] {
  border: 1px solid var(--border-base);
  border-radius: var(--radius-field, 0.75rem);
  background: var(--bg-panel);
  box-shadow: 0 4px 16px color-mix(in oklab, var(--color-neutral, var(--color-base-content)) 10%, transparent);
}

}
@layer bao-tokens {

/*
 * PageHeader follows the enterprise-page.bao title/action grid.
 *
 * `:not(.sr-only)` because a conversation page demotes its header to the
 * document outline — pages-renderers-header emits `class="sr-only"` on the
 * same element that still carries `data-primitive="PageHeader"` for the state
 * contract. `.sr-only` lives in @layer utilities and this grid lives in
 * @layer bao-tokens, so the grid won on width and margin and the hidden
 * header laid out at `width: 100%` with a painted `margin-block-end`.
 * Measured on /chat at 1280x800: the 1px-tall clipped node was 956px wide
 * inside a 902px shell and pushed #app-main to scrollWidth 982 against
 * clientWidth 956 — 26px of horizontal scroll owned by something no reader
 * can see. A header that paints nothing owns no painted layout.
 */
/* Breadcrumbs reserve the full touch-target height in PageHeader.bao.
 * Clip the block axis so horizontal trail scrolling never creates a nested
 * vertical scroll region; links fit within the declared navigation height. */
.breadcrumbs {
  overflow-y: clip;
}

.bao-page-header:not(.sr-only),
[data-primitive="PageHeader"]:not(.sr-only) {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: end;
  justify-items: stretch;
  column-gap: var(--space-section, 0.75rem);
  row-gap: var(--space-inline, 0.5rem);
  margin-block-end: var(--space-section, 0.75rem);
  width: 100%;
  min-width: 0;
  text-align: start;
}

@media (min-width: 40rem) {
  .bao-page-header:not(.sr-only),
  [data-primitive="PageHeader"]:not(.sr-only) {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

/* A content heading and its commands share one intrinsic row. Flex wrapping
 * follows available space instead of forcing a second band on every phone. */
[data-primitive="PageHeader"][data-page-header-mode="content"] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
[data-primitive="PageHeader"][data-page-header-mode="content"] > div:first-child {
  flex-basis: auto;
}

/* Header action row — touch targets without arbitrary child selectors in tokens. */
.bao-page-header-actions > .btn {
  min-height: var(--touch-target-min, 2.75rem);
}

[data-primitive="PageHeader"] > .flex.min-w-0.flex-col,
[data-primitive="PageHeader"] > div:first-child {
  min-width: 0;
  justify-self: stretch;
  text-align: start;
}

[data-page-header-actions],
[data-primitive="PageHeader"] [data-page-header-actions] {
  /* Action contents own width; breakpoint shards must not claim a whole row. */
  inline-size: fit-content;
  flex: 0 1 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-micro, 0.25rem);
  min-width: 0;
  max-width: 100%;
}

@media (min-width: 40rem) {
  [data-page-header-actions],
  [data-primitive="PageHeader"] [data-page-header-actions] {
    justify-content: flex-end;
    justify-self: end;
  }
}

[data-page-header-actions] .btn {
  flex-direction: row;
}

[data-primitive="PageHeader"] h1 {
  margin: 0;
  font-weight: var(--font-weight-semibold, 600);
  letter-spacing: var(--tracking-tight, -0.025em);
  color: var(--text-primary);
  text-align: start;
}

/*
 * Phone page headers: H1 only (dock + shell orient). Subtitles/leads hide.
 * One primary action remains full-width touch target. Icon-only chrome
 * (btn-square / soft tool buttons) must stay compact — never stretched into
 * full-width bars that read as accidental blocks (design.pdf §5.4 hierarchy).
 */
@media (max-width: 47.99rem) {
  [data-primitive="PageHeader"] p.text-sm.leading-snug,
  [data-primitive="PageHeader"] .max-w-description {
    display: none;
  }
  /*
   * Phone: shell already paints page title in chrome — page H1 becomes a
   * second title band that wastes vertical density and can overflow at
   * 320px when actions wrap. Visually hide H1 (keep in a11y tree via
   * clip) so the action row is the sole page chrome under the topbar.
   */
  [data-primitive="PageHeader"][data-page-header-mode="shell"] h1,
  [data-primitive="PageHeader"][data-page-header-mode="shell"] [id^="page-header-title-"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  /*
   * Phone: shell already paints page title — breadcrumbs are secondary
   * orientation. Keep them for AT (visually hide) so the action row owns
   * the full width without a second chrome band (design.pdf §5.1 / HIG).
   */
  [data-primitive="PageHeader"] [data-page-breadcrumbs] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  [data-page-header-actions] {
    justify-content: flex-end;
  }
  [data-page-header-actions] > .btn.btn-primary,
  [data-page-header-actions] > a.btn.btn-primary,
  [data-page-header-actions] > [data-disabled-tip-host]:has(.btn-primary) {
    flex: 1 1 auto;
    justify-content: center;
    min-height: var(--touch-target-min, 2.75rem);
  }
  [data-page-header-actions] > .btn:not(.btn-primary),
  [data-page-header-actions] > a.btn:not(.btn-primary),
  [data-page-header-actions] > [data-disabled-tip-host]:not(:has(.btn-primary)) {
    flex: 0 1 auto;
    min-height: var(--touch-target-min, 2.75rem);
  }
  /*
   * Icon-first secondary chrome on phone: ghost/outline toolbar labels hide
   * so 3–5 actions fit one row without wrapping into a cramped second band
   * (Apple HIG dense toolbar; labels stay in aria). Soft + primary clinical
   * CTAs (finish case, generate report) keep permanent verb labels — touch
   * has no hover, so an unlabeled icon square is undiscoverable
   * (design.pdf §6: essentials must not depend on hover).
   */
  [data-page-header-actions] .bao-toolbar-action:not(.btn-primary):not(.btn-soft) .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    max-width: 0;
    opacity: 0;
  }
  [data-page-header-actions] .bao-toolbar-action:not(.btn-primary):not(.btn-soft):focus-visible .bao-toolbar-label,
  [data-page-header-actions] .bao-toolbar-action:not(.btn-primary):not(.btn-soft)[aria-pressed="true"] .bao-toolbar-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    clip: auto;
    max-width: var(--toolbar-label-max-width, 8rem);
    opacity: 1;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Soft clinical secondaries: permanent compact verb next to the glyph. The verb may WRAP: hig-dynatype measured at 390px root font 400% with WCAG text spacing — "Refresh" (/robot), "Create organization", "Create workflow", "Open editor" escaped the viewport because nowrap makes min-content equal the whole label; wrap beats clip at WCAG 1.4.10, and ellipsis goes too since it requires nowrap. */
  [data-page-header-actions] .bao-toolbar-action.btn-soft .bao-toolbar-label {
    display: inline-block;
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    margin-inline-start: var(--space-micro, 0.25rem);
    clip: auto;
    max-width: var(--toolbar-label-max-width, 8rem);
    opacity: 1;
    overflow-wrap: anywhere;
    white-space: normal;
  }
  /*
   * Layout customizer is chrome, not a clinical verb (Apple HIG dense toolbar).
   * Icon-only on phone; aria-label + tip retain the name. Overrides soft
   * permanent-label rule above so lone "Customize" does not own a full row.
   */
  [data-dashboard-edit-toggle].bao-toolbar-action .bao-toolbar-label,
  [data-page-header-actions] [data-dashboard-edit-toggle].bao-toolbar-action.btn-soft .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    max-width: 0;
    opacity: 0;
    display: block;
  }
  /*
   * Media workbenches already own a thumb dock / overflow sheet — header
   * "More actions" stays discoverable via glyph + aria, not a second verb.
   */
  [data-page-type="media"] [data-header-action-overflow] > summary.bao-toolbar-action .bao-toolbar-label,
  [data-page-type="media"] [data-page-header-actions] [data-header-action-overflow] > summary.btn-soft .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    max-width: 0;
    opacity: 0;
    display: block;
  }
  /*
   * Collection/ops sticky toolbar already paints primary + overflow on phone.
   * Header overflow densifies to icon so two labeled "More actions" never
   * stack (reports composition fail). AT keeps aria-label.
   */
  body:has([data-toolbar-responsive="primary-overflow"]) [data-header-action-overflow] > summary .bao-toolbar-label,
  [data-page]:has([data-toolbar-responsive="primary-overflow"]) [data-header-action-overflow] > summary .bao-toolbar-label,
  [data-toolbar-action-overflow] > summary .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    max-width: 0;
    opacity: 0;
    display: block;
  }
}

/* Tablet: keep labels for soft secondaries — more horizontal room. */
@media (min-width: 48rem) and (max-width: 63.99rem) {
  [data-page-header-actions] {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  /*
   * Tablet overflow "More actions" must keep a permanent verb (orphanOverflows
   * gate). Phone densify may icon-only; tablet has room and no hover alternate.
   */
  [data-page-header-actions] [data-header-action-overflow] > summary .bao-toolbar-label,
  [data-header-action-overflow] > summary.bao-toolbar-action .bao-toolbar-label {
    position: static;
    display: inline-block;
    width: auto;
    height: auto;
    min-width: 1ch;
    max-width: var(--toolbar-label-reveal-max-width, 12rem);
    margin: 0;
    margin-inline-start: var(--space-micro, 0.25rem);
    padding: 0;
    overflow: hidden;
    clip: auto;
    opacity: 1;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

}
@layer bao-tokens {

/*
 * Shell icon action — theme, voice, account, drawer toggle.
 * Fixed 44px square;absolute stack centers glyph + daisyUI swap layers.
 * Nested under .bao-shell-topbar-actions (not navbar-end > .btn direct child).
 */
#app-header .bao-shell-icon-action.btn,
#app-header .bao-shell-icon-action.btn.btn-square,
#app-header .bao-shell-icon-action.btn.tooltip,
#app-header .bao-shell-icon-action.btn.swap,
#app-header .bao-shell-icon-action.btn.swap-rotate,
#app-header .bao-shell-topbar-actions .bao-shell-icon-action.btn,
#app-header .bao-shell-topbar-actions summary.bao-shell-icon-action,
#app-header button.bao-shell-icon-action,
#app-header summary.bao-shell-icon-action {
  position: relative;
  display: inline-grid;
  place-content: center;
  place-items: center;
  width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  min-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  max-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  min-height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  max-height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  padding: 0;
  margin: 0;
  gap: 0;
  line-height: 0;
  flex-shrink: 0;
  box-sizing: border-box;
  /* visible: tooltips; icon layers use absolute so overflow does not clip glyph */
  overflow: visible;
  vertical-align: middle;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-template-areas: "stack";
}/* Kill bao-toolbar-action overflow:hidden clip + label-expand geometry on shell. */
#app-header .bao-toolbar-action.bao-shell-icon-action,
#app-header .bao-shell-topbar-actions .bao-toolbar-action.bao-shell-icon-action {
  overflow: visible;
  gap: 0;
}#app-header .bao-shell-icon-action.btn:hover,
#app-header .bao-shell-icon-action.btn:focus-visible,
#app-header .bao-shell-icon-action.btn:focus-within,
#app-header .bao-shell-icon-action.btn[aria-pressed="true"],
#app-header .bao-shell-icon-action.btn[aria-expanded="true"],
#app-header .bao-shell-icon-action.btn.swap-active,
#app-header details[open] > summary.bao-shell-icon-action,
#app-header .bao-shell-topbar-actions .bao-shell-icon-action.btn:hover,
#app-header .bao-shell-topbar-actions .bao-shell-icon-action.btn[aria-pressed="true"] {
  width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  min-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  max-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  min-height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  padding: 0;
  gap: 0;
}/*
 * Absolute stack: every glyph layer shares the same box center.
 * swap-rotate's 45deg must not invent a second layout box.
 */
#app-header .bao-shell-icon-action > svg,
#app-header .bao-shell-icon-action > .swap-on,
#app-header .bao-shell-icon-action > .swap-off,
#app-header .bao-shell-icon-action > .avatar,
#app-header .bao-shell-icon-action > span:not(.sr-only):not(.htmx-indicator):not(.loading) {
  grid-area: stack;
  place-self: center;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 0;
  pointer-events: none;
  /* kill daisyUI swap-rotate optical skew for shell chrome */
  rotate: none;
  transform: none;
}
#app-header .bao-shell-icon-action > .swap-on,
#app-header .bao-shell-icon-action > .swap-off,
#app-header .bao-shell-icon-action > .avatar,
#app-header .bao-shell-icon-action > span:not(.sr-only):not(.htmx-indicator):not(.loading) {
  display: inline-flex;
  width: 100%;
  height: 100%;
  margin: 0;
}#app-header .bao-shell-icon-action.swap-rotate > .swap-on,
#app-header .bao-shell-icon-action.swap-rotate > .swap-off,
#app-header .bao-shell-icon-action.swap-active.swap-rotate > .swap-on,
#app-header .bao-shell-icon-action.swap-active.swap-rotate > .swap-off {
  rotate: none;
  transform: none;
}#app-header .bao-shell-icon-action > svg,
#app-header .bao-shell-icon-action .swap-on svg,
#app-header .bao-shell-icon-action .swap-off svg {
  display: block;
  width: 1rem;
  height: 1rem;
  margin: 0;
  flex-shrink: 0;
}/* Avatar glyph inside account summary — circle centered in square. */
#app-header .bao-shell-icon-action .avatar > span,
#app-header .bao-shell-icon-action [data-user-avatar] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  min-width: 1.75rem;
  min-height: 1.75rem;
  margin: 0;
  line-height: 1;
  border-radius: var(--radius-full);
}#app-header .bao-shell-icon-action > .sr-only,
#app-header .bao-shell-icon-action .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}/* Coarse pointer: re-assert fixed square after general btn-square grow rule. */
@media (hover: none),
(pointer: coarse) {#app-header .bao-shell-icon-action.btn,
#app-header .bao-shell-icon-action.btn.btn-square,
#app-header .bao-shell-topbar-actions .bao-shell-icon-action.btn,
#app-header .bao-shell-topbar-actions summary.bao-shell-icon-action,
#app-header button.bao-shell-icon-action,
#app-header summary.bao-shell-icon-action,
#app-header .navbar-end .btn.btn-square,
#app-header .navbar-start .btn.btn-square {
    width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    min-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    max-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    min-height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    padding: 0;
      gap: 0;
  }
}

/*
 * Topbar action cluster — single dense toolbar row (design.pdf §5.1 chrome).
 * Indicators absolute so HTMX spinners never reflow the icon row.
 */
#app-header .bao-shell-topbar-actions {
  display: inline-flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2xs, 0.125rem);
  position: relative;
  flex-shrink: 0;
  min-width: 0;
}#app-header .bao-shell-topbar-actions > .htmx-indicator,
#app-header .bao-shell-topbar-actions > .loading,
#app-header .bao-shell-topbar-actions > [id$="-indicator"],
#app-header .bao-shell-topbar-actions > [id$="-status"],
#app-header .navbar-end > .htmx-indicator,
#app-header .navbar-end > .loading {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  pointer-events: none;
}/* Voice status is optional text — never crush icon alignment. */
#app-header .navbar-end [data-voice-command-status],
#app-header .bao-shell-topbar-actions [data-voice-command-status] {
  max-width: min(6rem, 40vw);
  min-width: 0;
  flex-shrink: 1;
}

/*
 * Responsive drawer-toggle contract must survive the icon-geometry override
 * above (its display: inline-grid outranks the drawer modules' hide rules).
 * Phone (<48rem): dock "More" owns the drawer. Tablet (48–79.99rem): the
 * persistent icon rail owns nav. Permanent rail (≥80rem): #app-sidebar
 * [data-sidebar-rail-toggle] owns collapse — one affordance per function
 * (design.pdf §5.1).
 */
#app-header button.bao-shell-icon-action.bao-drawer-toggle-control[data-drawer-toggle] {
  display: none;
}/*
 * Forced colors (SH-09,
WCAG 1.4.11): the UA strips glass fills and the
 * quiet transparent rest borders,
leaving glyph-only controls with no
 * perceivable boundary. Restore system-color boundaries + glyphs for shell
 * icon actions,
the status chip,
and dock items.
 */
@media (forced-colors: active) {#app-header .bao-shell-icon-action,
#app-header summary.bao-shell-icon-action,
#app-header .bao-shell-status-badge,
.bao-mobile-dock .bao-mobile-dock-item {
    border: 1px solid ButtonBorder;
    background: Canvas;
    color: ButtonText;
  }
  #app-header .bao-shell-status-badge[data-registry-offline="true"] {
    border-color: ButtonBorder;
    color: CanvasText;
  }#app-header .bao-shell-icon-action:focus-visible,
.bao-mobile-dock .bao-mobile-dock-item:focus-visible {
    outline: var(--focus-ring-width, 2px) solid Highlight;
    outline-offset: var(--focus-ring-offset, 2px);
  }
}

/* The theme control semantic state owns both direct and nested swap glyphs. */
[data-theme-toggle][aria-pressed="true"] .swap-on {
  opacity: 1;
}
[data-theme-toggle][aria-pressed="true"] .swap-off {
  opacity: 0;
}
[data-theme-toggle][aria-pressed="true"].swap-rotate .swap-on,
[data-theme-toggle][aria-pressed="true"] [data-theme-toggle-swap].swap-rotate .swap-on {
  rotate: 0deg;
}
[data-theme-toggle][aria-pressed="true"].swap-rotate .swap-off,
[data-theme-toggle][aria-pressed="true"] [data-theme-toggle-swap].swap-rotate .swap-off {
  rotate: calc(45deg * -1);
}

}
@layer bao-tokens {


/* Runtime preference fragments share one vertical content stack. */
.bao-settings-state-strip {
  display: flex;
  flex-direction: column;
  gap: var(--space-inline);
  min-inline-size: 0;
}

/* The wrapper owns query measurement; the settings surface owns named tracks. */
.bao-settings-workspace {
  display: grid;
  grid-template-rows: auto minmax(min-content, 1fr);
  grid-template-areas: "header" "settings";
  flex: 1;
  min-block-size: min-content;
  gap: var(--space-section);
  container-type: inline-size;
  container-name: settings-tabs;
}
[data-settings-tabs] {
  grid-area: settings;
  min-block-size: min-content;
  display: grid;
  grid-template-rows: auto auto minmax(var(--panel-body-min-height), 1fr);
  grid-template-areas: "search" "navigation" "panels";
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-section);
}
[data-settings-search] { grid-area: search; min-inline-size: 0; }
[data-settings-tab-trigger], [data-settings-tab-picker], .bao-settings-source-list { grid-area: navigation; }
.bao-settings-panels-host {
  position: relative;
  grid-area: panels;
  min-block-size: var(--panel-body-min-height);
  contain: size;
  overflow-y: auto;
  overscroll-behavior-block: contain;
  scrollbar-gutter: stable;
}
#settings-panels-indicator { grid-area: panels; justify-self: end; }
.bao-settings-source-item {
  display: flex;
  align-items: center;
  gap: var(--space-inline);
  min-inline-size: 0;
  inline-size: 100%;
}
[data-settings-search-list] {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-micro);
  max-block-size: var(--palette-max-height);
  overflow-y: auto;
  padding: 0;
  margin: 0;
  list-style: none;
}
@container settings-tabs (max-width: 56rem) {
  [data-settings-tabs] > [data-settings-tab-picker] { display: flex; }
  [data-settings-tabs] > .bao-settings-source-list { display: none; }
}
/* The phone sheet uses the same viewport boundary as its Tabs primitive. */
@media (max-width: 39.99rem) {
  [data-settings-tabs] > [data-settings-tab-picker] { display: none; }
}
@container settings-tabs (min-width: 56.01rem) {
  [data-settings-tabs] {
    grid-template-areas: "search search" "navigation panels";
    grid-template-rows: auto minmax(var(--panel-body-min-height), 1fr);
    grid-template-columns: minmax(var(--grid-panel-quarter-min), var(--sidebar-expanded)) minmax(0, 1fr);
  }
  [data-settings-tabs] > [data-settings-tab-picker] { display: none; }
  [data-settings-tabs] > .bao-settings-source-list {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
    min-block-size: 0;
    align-self: stretch;
    overflow-y: auto;
    scrollbar-gutter: stable;
  }
}

/*
 * Settings destination visibility is single-owned by
 * tokens-glass-shell-settings-responsive.
 *
 * This module previously declared a 56.01rem-96rem container band that revealed
 * the compact select and hid the tablist, on the reasoning that "a full label
 * rail is only useful when every destination is visible". The premise was right
 * and the remedy was not: twelve destinations do not fit a horizontal rail, so
 * the fix is a vertical source list at regular width, not a phone control on
 * every desktop. Because that band spanned 896-1536px of container width, the
 * tablist was unreachable on every realistic display.
 */


/* Settings — solid group panels only (never every input fieldset — design.pdf §1.1). */
[data-page="settings"] .bao-settings-group.fieldset,
[data-page="settings"] fieldset.bao-settings-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-form-min)), 1fr));
  align-items: start;
  gap: var(--space-inline);
  min-width: 0;
  margin: 0;
  padding: var(--space-section);
  background: var(--bg-panel);
}

/* Group titles keep section weight without uppercase shout. */
/* A floated legend participates in the fieldset layout instead of cutting its border. */
[data-page="settings"] .bao-settings-group > legend.fieldset-legend {
  float: inline-start;
  grid-column: 1 / -1;
  inline-size: 100%;
  margin: 0;
  padding: 0;
}

.bao-settings-group {
  container-type: inline-size;
  container-name: settings-group;
}

/*
 * Settings control rows — DataSection.controlRow (toggle flex/grid) and
 * controlField (fieldset column). This semantic row owns layout and rhythm.
 */
.bao-settings-control-row {
  min-width: 0;
  margin: 0;
  padding-block: var(--space-micro);
  border-block-end: var(--pane-divider) solid var(--border-base);
}

.bao-settings-control-row:first-child {
  padding-block-start: 0;
}

.bao-settings-control-row:last-child {
  padding-block-end: 0;
  border-block-end: 0;
}

/* Reserve the indicator track at rest so a save cannot move the switch. */
.bao-settings-control-row[data-settings-control-kind="toggle"] {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: minmax(0, 1fr) auto var(--text-base);
  align-items: center;
  gap: var(--space-inline);
}

.bao-settings-control-row[data-settings-control-kind="toggle"] > label {
  min-width: 0;
  overflow-wrap: anywhere;
  white-space: normal;
}

.bao-settings-control-row[data-settings-control-kind="toggle"] > .htmx-indicator {
  grid-column: 3;
  grid-row: 1;
}

/* Input/select fieldset SSOT — stacked legend + control */
.bao-settings-control-row[data-settings-control-kind="input"],
.bao-settings-control-row[data-settings-control-kind="select"] {
  display: flex;
  flex-direction: column;
  gap: var(--space-micro);
  min-width: 0;
}

/* HTMX indicators must not reserve vertical space when idle. */
.bao-settings-control-row .htmx-indicator,
[data-settings-control-kind="input"] > .htmx-indicator,
[data-settings-control-kind="select"] > .htmx-indicator,
[data-page="settings"] .htmx-indicator {
  display: none;
  width: 0;
  height: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

.bao-settings-control-row .htmx-indicator:is(.htmx-request, [data-shell-loading]),
[data-settings-control-kind="input"] > .htmx-indicator:is(.htmx-request, [data-shell-loading]),
[data-settings-control-kind="select"] > .htmx-indicator:is(.htmx-request, [data-shell-loading]),
[data-page="settings"] .htmx-indicator:is(.htmx-request, [data-shell-loading]) {
  display: inline-block;
  inline-size: var(--text-base);
  block-size: var(--text-base);
}

[data-page="settings"] .bao-settings-control-row[data-settings-control-kind="toggle"] > input[type="checkbox"] {
  justify-self: end;
}

/* Hardware capability groups are supplied as newline-delimited runtime state. */
[data-page="settings"] [data-settings-control="capture.hardware-summary"] {
  display: block;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.bao-settings-pref-field {
  margin: 0;
  padding: var(--space-inline);
  border: var(--pane-divider) solid var(--border-base);
  border-radius: var(--radius-field);
  background: var(--bg-panel);
}


  .bao-settings-pair-actions {
    display: grid;
    min-width: 0;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-context-min), 100%), 1fr));
    gap: var(--space-inline);
  }
  .bao-settings-pair-actions > button {
    min-width: 0;
    width: 100%;
    justify-content: center;
  }

/* One layout owner for groups, device management and contract summaries. */
.bao-settings-panel-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--space-section);
  min-inline-size: 0;
}

/*
 * Settings value readout — read-only current-value display (rendered as
 * <output>). Semantic hook composed with the typography/width utilities in
 * READOUT_CLASS and READOUT_MONO_CLASS (packages/shared/settings-controls-classes.ts).
 * Owns the value baseline: a readout never overflows its settings cell.
 */
.bao-settings-value-readout {
  min-width: 0;
  width: 100%;
  max-width: 100%;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
  overflow-wrap: anywhere;
  user-select: text;
}

.bao-settings-value-readout-mono {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  word-break: break-all;
}

/*
 * Long settings values: collapsed by default, revealed in full on demand.
 *
 * `DataSection.bao` declared `valueReadoutDisclosure` / `valueReadoutFull` and
 * shipped them with no selector at all, so both rendered unstyled — the same
 * dangling-class defect class as the glass surfaces before them.
 *
 * One owner, here, beside the readout these rules compose with. The same three
 * selectors also lived in tokens-glass-shell-settings-tabs, and the two copies
 * of `.bao-settings-value-disclosure` disagreed: this module set
 * `display: -webkit-box` with `-webkit-line-clamp: 2` and `overflow: hidden`;
 * that one set `min-width: 0; max-width: 100%` and gave the summary
 * `display: list-item`. Equal specificity, so source order decided and this
 * copy came last — the identifier disclosure shipped as a two-line clamped box
 * with its expanded value cut off and its native triangle gone, which is
 * precisely the failure the surviving comment below warns about. The clamp
 * goes with the duplicate: the summary already carries the short segment, so
 * there is nothing left to truncate.
 */
.bao-settings-value-disclosure {
  min-width: 0;
  max-width: 100%;
}

/*
 * display: list-item keeps the native disclosure triangle. Setting display to
 * flex here removed it, leaving a control with no affordance that it opens —
 * the value looked like plain truncated text and nothing invited the tap.
 */
.bao-settings-value-disclosure > summary {
  display: list-item;
  list-style-position: inside;
  min-height: var(--touch-target-min);
  min-inline-size: var(--touch-target-min);
  inline-size: fit-content;
  max-inline-size: 100%;
  line-height: var(--touch-target-min);
  padding-block: 0;
  padding-inline: var(--space-micro);
  cursor: pointer;
  border-radius: var(--radius-field);
}

/*
 * The full identifier, revealed. Its own mono/wrap treatment rather than the
 * readout's: this text is selected and copied, so it wraps at any character
 * and never collapses whitespace.
 */
.bao-settings-value-full {
  display: block;
  padding-block-start: var(--space-micro);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: pre-wrap;
  user-select: text;
}

/*
 * Settings tabpanels — exclusive visibility (daisyUI tabs content pattern).
 * Inactive panels must leave layout completely so Appearance/theme cannot
 * paint under every tab when all panels were stacked.
 * Cover attribute, utility class, and aria-hidden for WebView 101 + HTMX.
 */
.bao-settings-tab-panel[hidden],
.bao-settings-tab-panel.hidden,
.bao-settings-tab-panel[aria-hidden="true"],
[data-page="settings"] [data-settings-panel][hidden],
[data-page="settings"] [data-settings-panel].hidden,
[data-page="settings"] [data-settings-panel][aria-hidden="true"] {
  display: none;
  visibility: hidden;
  pointer-events: none;
}

.bao-settings-tab-panel:not([hidden]):not(.hidden):not([aria-hidden="true"]),
[data-page="settings"] [data-settings-panel]:not([hidden]):not(.hidden):not([aria-hidden="true"]) {
  display: block;
  visibility: visible;
  pointer-events: auto;
  min-width: 0;
  width: 100%;
}

/* Appearance control layout is owned by the generated DataSection primitive. */

/*
 * Dual Settings sheet banned — theme/locale only on Settings → Accessibility.
 * No #settings-dialog chrome; residual class tokens are compile placeholders only.
 */


/* ---- Topbar zones: start flexes, center is content-sized, end is fixed. ---- */
#app-header.navbar,
#app-header[data-shell-topbar] {
  display: flex;
  flex-direction: row;
  /* Two-row topbar at large Dynamic Type: when the row cannot fit (capsule
     value, search field, account menu, status badges all inflate with the
     root font), items wrap to a second row instead of pushing past the
     viewport. Apple HIG Toolbars allows multi-row bars when content overflows;
     the unwrapped one-row bar at 1.0x root font is the default. The phone
     narrow band re-declares a fixed two-row grid that wins by source order. */
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  row-gap: var(--space-2xs, 0.125rem);
  column-gap: var(--space-inline, 0.5rem);
  /* Canonical chrome height and insets include both host-reported safe areas
     and browser environment insets without dropping the control floor. */
  min-height: calc(var(--chrome-min-height, var(--control-regular, 2.75rem)) + max(var(--safe-area-inset-top, 0px), env(safe-area-inset-top, 0px)));
  height: auto;
  padding-block-start: calc(var(--space-inline) + max(var(--safe-area-inset-top, 0px), env(safe-area-inset-top, 0px)));
  padding-block-end: var(--space-inline);
  padding-inline: max(var(--space-inline, 0.5rem), var(--safe-area-inset-left, 0px), env(safe-area-inset-left, 0px))
    max(var(--space-inline, 0.5rem), var(--safe-area-inset-right, 0px), env(safe-area-inset-right, 0px));
  min-width: 0;
  width: 100%;
  max-width: 100%;
}
/* The shell title and page content share the same desktop reading inset,
 * with either the expanded sidebar or its collapsed rail. Safe areas retain
 * priority; narrow chrome owns its independent compact presentation. */
@media (min-width: 48rem) {
  #app-header.navbar,
  #app-header[data-shell-topbar] {
    padding-inline:
      max(min(var(--page-gutter-inline, var(--space-section)), 5%), var(--safe-area-inset-left, 0px), env(safe-area-inset-left, 0px))
      max(min(var(--page-gutter-inline, var(--space-section)), 5%), var(--safe-area-inset-right, 0px), env(safe-area-inset-right, 0px));
  }
}
#app-header .navbar-start,
#app-header .navbar-center,
#app-header .navbar-end {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: auto;
  max-width: none;
  min-width: 0;
  min-height: var(--chrome-min-height, 2.25rem);
}
#app-header .navbar-center,
#app-header .navbar-end {
  flex-wrap: nowrap;
  gap: var(--space-2xs, 0.125rem);
}
/*
 * HIG toolbar item groupings: a leading edge (sidebar control, view title,
 * context menu) and a trailing edge that carries an optional search field,
 * the status glyph and the actions. Apple HIG Toolbars assigns search to the
 * TRAILING edge; the centre area is for common controls or the title.
 *
 * Centring the search column with margin-inline:auto measured 318px of dead
 * space on BOTH sides at 1440x900 — 636px of an 1184px bar, 54% of it empty.
 * The old comment here was trying to avoid a dead void between edge-pinned
 * clusters and produced two of them instead of one. The leading zone now
 * absorbs the slack, so the bar has exactly one spacer, between the groups.
 */
#app-header .navbar-start {
  flex: 1 1 auto;
  flex-wrap: wrap;
  justify-content: flex-start;
  overflow: hidden;
  /* The 2xs gap above is right for the trailing icon clusters and wrong here.
     The leading zone holds the view title next to the workspace capsule — two
     TEXT elements, not two glyphs — and 2px between them reads as a collision:
     measured at 1100px, the title ended at x=182 and the capsule began at
     x=184. Apple HIG Toolbars groups the leading edge as distinct items, so
     they get the same inline gap the bar uses between its zones. */
  gap: var(--space-inline, 0.5rem);
}
#app-header .navbar-center {
  flex: 0 1 auto;
  justify-content: flex-end;
}
#app-header .navbar-end {
  flex: 0 0 auto;
  justify-content: flex-end;
  /* Anchored menus must remain visible outside the trailing layout zone. */
  overflow: visible;
}
/*
 * One affordance per function. Apple HIG Toolbars: do not make people guess
 * what a toolbar item does. Phone navigation belongs to the dock's More
 * control, the tablet band owns peek, and the permanent sidebar owns its own
 * collapse control. The peek button therefore belongs only to 48–79.99rem.
 *
 * The drawer family already declares the trigger hidden by default, with
 * quadrupled class specificity, but that declaration is emitted inside
 * @layer bao-tokens and daisyUI ships an UNLAYERED .btn { display:inline-flex }.
 * Layer order beats specificity, so the base hide never applied at any width.
 * Unlayered here so it binds, and the class is repeated to clear the (1,4,0)
 * unlayered icon-geometry rule that paints these buttons display:grid.
 */
@media (max-width: 47.99rem), (min-width: 80rem) {
  #app-header .btn.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger.bao-shell-sidebar-peek-trigger {
    display: none;
  }
}
/* Brand title: fluid clamp, never crushes the context strip. */
#app-header .bao-shell-brand {
  display: flex;
  min-width: 0;
  max-width: clamp(4.5rem, 18vw, 12rem);
  flex-shrink: 0;
  align-items: center;
}

@media (max-width: 39.99rem) {
  #app-header .navbar-center,
  #app-header .navbar-end .bao-shell-topbar-actions [data-theme-toggle].btn,
  #app-header .navbar-end .bao-shell-topbar-actions [data-voice-command].btn,
  #app-header .navbar-end .bao-shell-topbar-actions [data-session-rail-sheet-open].btn {
    display: none;
  }

  /* Compact navigation retains the canonical control row and full-width title row. */
  #app-header.navbar {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "menu context actions"
      "title title title";
    align-items: center;
    column-gap: var(--space-inline, 0.5rem);
    row-gap: var(--space-inline);
    padding-block-end: var(--space-inline);
  }

  #app-header.navbar .navbar-start {
    display: contents;
  }

  #app-header .navbar-start > [data-drawer-toggle] {
    grid-area: menu;
  }

  #app-header .navbar-start > .bao-shell-brand {
    grid-area: title;
    justify-content: center;
    text-align: center;
    min-width: 0;
    max-width: none;
    overflow: hidden;
  }

  #app-header .navbar-start > [data-context-capsule].btn {
    grid-area: context;
  }

  #app-header .navbar-end {
    grid-area: actions;
    gap: var(--space-2xs, 0.125rem);
  }

  #app-header .navbar-start > [data-context-capsule].btn.bao-shell-context-capsule {
    width: 100%;
    max-width: 100%;
    justify-content: space-between;
  }

  body[data-page-header-mode="content"] #app-header .bao-shell-brand {
    display: none;
  }

  body[data-page-header-mode="shell"] #app-header .bao-shell-brand #shell-page-title,
  body[data-page-header-mode="shell"] #app-header .bao-shell-brand [data-shell-page-title] {
    display: block;
    max-width: 100%;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;

  }
}


@media (min-width: 40rem) {
  /* Medium/expanded: persistent search field is the ONE search affordance. */
  #app-header .navbar-end .bao-shell-topbar-actions [data-command-palette-trigger].btn {
    display: none;
  }
}
/* Voice status text: compact shells never show it; wider shells only when
   the runtime wrote content (owner: this module — never markup utilities). */
#app-header [data-voice-command-status] {
  display: none;
}
@media (min-width: 40rem) {
  #app-header [data-voice-command-status]:not(:empty) {
    display: inline;
  }
}
/* Drawer-toggle visibility is owned by tokens-glass-shell-drawer.ts
   (dock "More" on phone · hamburger 48–80rem · rail collapse ≥80rem). */
/* ---- Compact context capsule (visibility owned above). ---- */
/* Content sets the required row height; enlarged text may claim another header row. */
#app-header [data-context-capsule].bao-shell-context-capsule {
  display: inline-flex;
  flex-wrap: wrap;
  max-width: 100%;
  min-width: 0;
  height: auto;
  flex: 0 1 auto;
  padding-inline: min(var(--space-section), 5%);
  justify-content: flex-start;
  white-space: normal;
}
#app-header .bao-shell-context-capsule > svg {
  flex: none;
}
/* The capsule carries NO status dot.
   It used to paint a 6px ::before that turned success-green once a context was
   set. That was a second green dot in the same topbar as the registry status
   chip, and the two meant different things — context selected vs registry
   reachable — while looking identical. It also signalled by colour alone,
   which the accessibility canon forbids for status.

   The capsule already states its own status in text: the value span names the
   active workspace, and data-context-status drives the sr-only Active/Unset
   label. That is the readable, non-colour-dependent signal, so the dot was
   pure redundancy. Connection state is the registry chip's job, and that chip
   now tints a real glyph per state rather than painting a dot. */

/* Context actions are rendered once in the context sheet; the capsule is its trigger. */

@media (max-width: 47.99rem) {
  /* Compact right cluster: tight gap keeps ≥44px targets without crowding. */
  #app-header .bao-shell-topbar-actions {
    gap: var(--space-2xs, 0.125rem);
  }
}

/* ---- Context sheet (compact bottom sheet, §8.6). ---- */
/* Sheet plate caps at the visual viewport and scrolls internally so the row
   list never pushes the dismiss affordance off a small phone.
   Row-list grid SSOT lives in tokens-glass-shell-overlays.ts
   (.bao-shell-context-sheet-list) — never re-grid the nav wrapper here: a
   second grid on nav halves the list track and quarters every row. */
.bao-shell-context-sheet {
  max-height: min(70vh, var(--vv-height, 100dvh));
  overflow-y: auto;
  overscroll-behavior-y: contain;
}
.bao-shell-context-sheet-item {
  min-height: var(--touch-target-min, 2.75rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-context-min)), 1fr));
}
.bao-shell-context-sheet-item[data-context-status="set"] {
  border-inline-start: 2px solid var(--color-success);
  background: color-mix(in oklab, var(--color-success) 6%, transparent);
}
.bao-shell-context-sheet-item[data-context-status="unset"] [data-context-value] {
  opacity: var(--opacity-soft, 0.65);
}
/* Set rows: success text on the state badge — text is the signal, color decorates. */
.bao-shell-context-sheet-item[data-context-status="set"] [data-context-state] {
  color: var(--color-success);
}

/*
 * Account menu → compact bottom sheet (§8.6 canonical transformation:
 * popovers become sheets on compact, matching the context sheet idiom).
 * One SSoT: the same dropdown panel is re-anchored to the dock's top edge;
 * >=40rem keeps the anchored dropdown. Specificity must beat the
 * .dropdown.dropdown-end > .dropdown-content inline-end anchoring.
 *
 * The block-end offset clears the dock, and the dock band is the whole reason
 * this needs saying. Anchoring to the raw safe-area inset put the sheet's lower
 * edge at the viewport floor: measured at 390x844 the account sheet ran to
 * y=836 while nav#mobile-dock starts at y=780, so 56px of the sheet — a full
 * row — sat on top of the dock's first tab. The dock's own box is
 * --dock-height + --dock-safe-area-offset (tokens-layout-dock-base), which is
 * what the filter sheet and the dashboard customizer tray already clear, and
 * it collapses to the bare safe area on its own wherever the dock is retired
 * (--dock-height: 0rem at >=48rem).
 */
@media (max-width: 39.99rem) {
  [data-topbar-user-menu] > [data-dropdown-panel] {
    position: fixed;
    inset-inline: var(--space-inline, 0.5rem);
    inset-block-start: auto;
    inset-block-end: calc(
      var(--dock-height, 4rem) +
      var(--dock-safe-area-offset, env(safe-area-inset-bottom, 0px)) +
      var(--space-inline, 0.5rem)
    );
    margin: 0;
    width: auto;
    max-width: none;
    max-height: min(70vh, var(--vv-height, 100dvh));
    overflow-y: auto;
    overscroll-behavior-y: contain;
    border-radius: var(--radius-box, 1rem);
    box-shadow: var(--shadow-glass-floating);
    z-index: var(--z-modal, 1300);
  }
  /* Sheet rows read as destinations: full-width, 44px targets (coarse). */
  [data-topbar-user-menu] > [data-dropdown-panel] [role="menuitem"] {
    width: 100%;
    min-height: var(--touch-target-min, 2.75rem);
  }
}

/*
 * Session rail sheet (SH-06): bottom sheet on compact → side sheet ≥40rem
 * (§8.6 inspector transformation: full sheet → side sheet → persistent pane).
 * daisyUI modal-end owns the side anchoring; these rules only cap the track
 * and give the sheet body an internal scroll so the header never scrolls off.
 */
.bao-session-rail-sheet {
  display: flex;
  flex-direction: column;
  max-height: min(80vh, var(--vv-height, 100dvh));
}
.bao-session-rail-sheet-body {
  padding: var(--space-section);
  min-height: 0;
  flex: 1 1 auto;
}
@media (min-width: 40rem) {
  .bao-session-rail-sheet.modal-box {
    width: min(26rem, 92vw);
    max-width: 92vw;
    height: calc(100% - 2 * var(--space-inline));
    max-height: calc(100% - 2 * var(--space-inline));
    margin: var(--space-inline);
    border-radius: var(--radius-panel);
  }
}

/* Transient session inspector shares the blocking glass material. */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) and (forced-colors: none) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-session-rail-sheet.modal-box {
    background: color-mix(in oklab, var(--bg-panel) calc(var(--liquid-fill-blocking) * 100%), transparent);
    -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
    backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
    border-color: var(--liquid-border-regular);
    box-shadow: var(--shadow-glass-floating), var(--shadow-glass-inner);
  }
}

[data-session-rail-session-controls] > .flex {
  gap: var(--space-inline);
}
[data-session-rail-case-picker] [data-case-picker] {
  gap: var(--space-inline);
}

/* Tooltips must paint outside chrome boxes (daisyUI ::before / ::after). */
#app-header.navbar {
  overflow-x: visible;
  overflow-y: visible;
}
/*
 * Topbar blur — nav token. The bar carries .bao-surface-glass-regular.bao-glass-small,
 * which defaults to --liquid-blur-small (12px). Apple's Liquid Glass nav-bar
 * reads as more diffuse at the bar's stationary height, so this rule overrides
 * the density modifier's blur with --liquid-blur-nav (32px). Phones keep the
 * small blur via the @container shell (max-width: 48rem) override in
 * tokens-glass-base-core.css so a 32px blur never hits a 320px-wide viewport.
 *
 * Gated the way the scroll-edge sibling in tokens-glass-base-variants is: this
 * selector is (1,2,0) and unconditional, so it outranked the (0,1,0)/(0,2,0)
 * suppression paths AND the (0,2,1) product toggle at once — measured under
 * Emulation.setEmulatedMedia, the topbar kept the live 32px nav blur with
 * full saturation for a user who asked the OS for reduced transparency or
 * more contrast. OS media query and product attribute gate together because
 * the two switches are independent and either one alone leaves the other
 * unhonoured.
 */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) {
  html:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-header.navbar.bao-surface-glass-regular {
    -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular));
    backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-regular));
  }
}
/* Theme + voice live in the user menu — keep topbar sparse. That intent is
   now honoured at the SOURCE: layout-shell-html no longer emits a second
   theme or voice button into the toolbar, so there is nothing left here to
   hide. Apple HIG Toolbars asks for items chosen deliberately rather than
   crowded, and hig-canon H2 bans emitting a control and then hiding it with
   display:none instead of routing it through the menu. Only the theme live
   region and its indicator remain in the bar, and neither is a control. */
/* Registry status: icon glyph only (label is aria + title). */
#app-header .bao-shell-status-badge {
  position: relative;
  place-content: center;
  /* Dynamic Type guard (HIG): 2.75rem computes 176px at root font 400% and
     three such controls in the phone actions track overflow a 390px viewport
     (badge left measured -187px). The vw term in min() is font-invariant, so
     the hit target stays 44px at the default font and never outgrows a
     narrow viewport at 200%/400% text. */
  width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
  padding: 0;
  border-radius: var(--radius-field, 0.75rem);
}
#app-header .bao-shell-status-badge [data-status-label] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
/*
 * Dynamic Type viewport guard for every topbar icon control (HIG Dynamic
 * Type): the rem-based touch floor computes 176px per control at root font
 * 400%, and the icon clusters (drawer toggle, search trigger, account menu,
 * session rail, status badge) together exceeded every phone viewport while
 * no single control could give way. Capping min-width in vw keeps the 44px
 * floor at the default font (12vw is the smaller term only below ~367px of
 * viewport) and bounds each control to its share of the bar at 200%/400%
 * text, where HIG expects chrome metrics to stay stable while text scales.
 */
#app-header .navbar-end .bao-shell-topbar-actions .btn,
#app-header .navbar-start [data-drawer-toggle-button].btn,
#app-header .navbar-start .btn.bao-shell-sidebar-peek-trigger {
  min-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
}

/*
 * ---- Search (§8.6 canonical): compact = palette overlay, medium/expanded =
 * the ONE persistent field, rest-compact so center chrome never steals
 * workbench width; it expands while focused. Field surface uses the glass
 * FILL tokens — never nested backdrop blur inside already-blurred chrome.
 */
@media (max-width: 39.99rem) {
  /* Compact: palette trigger owns search — inline field must not compete (§8.6). */
  #app-header [data-topbar-search] {
    display: none;
  }
}
@media (min-width: 40rem) and (max-width: 63.99rem) {
  #app-header .navbar-start {
    flex: 1 1 auto;
    min-width: 0;
  }
  #app-header .navbar-center {
    flex: 0 0 auto;
    max-width: min(12rem, 30vw);
  }
  #app-header [data-topbar-search] {
    width: 100%;
    max-width: 100%;
  }
}
/* Canonical label.input host (icon inline — no absolute positioning). */
#app-header [data-topbar-search] .input {
  /* background-color LONGHAND: the shorthand resets background-image and
   * erases daisyUI component chrome (form-control-bg-longhand gate). */
  background-color: var(--glass-control-fill, color-mix(in oklab, var(--color-base-content) 5%, transparent));
  /*
   * The FILL is glass; the BOUNDARY is not. --glass-control-border is the
   * decorative hairline of glass chrome, and spending it here left the
   * shell's most-used field identified by nothing: measured on /cases at
   * 1280x800 in light, the border composited to 1.18:1 against the surface
   * behind it while the fill — rgba(255,255,255,0.1) over a white host —
   * added nothing either. A field on glass is still a field, so it carries
   * the same control boundary every other field does.
   */
  border-color: var(--border-control, var(--color-base-300));
}
#app-header [data-topbar-search] .input > svg {
  opacity: var(--opacity-soft, 0.65);
}
/*
 * Tertiary icon actions rest QUIET (design.pdf §6: rest = quiet border, low
 * emphasis) — boxed btn-soft squares made every tertiary action shout.
 * Hover/focus paint the control fill; pressed keeps immediate feedback.
 */
#app-header .navbar-end .bao-shell-topbar-actions .btn.bao-shell-icon-action,
#app-header .navbar-end details.dropdown > summary.bao-shell-icon-action,
#app-header .navbar-start [data-drawer-toggle-button].btn,
#app-header .navbar-start .btn.bao-shell-sidebar-peek-trigger,
#app-header .navbar-end .bao-shell-status-badge:not([data-registry-state="offline"]) {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
#app-header .navbar-end .bao-shell-topbar-actions .btn.bao-shell-icon-action:focus-visible,
#app-header .navbar-end details.dropdown > summary.bao-shell-icon-action:focus-visible,
#app-header .navbar-start [data-drawer-toggle-button].btn:focus-visible,
#app-header .navbar-start .btn.bao-shell-sidebar-peek-trigger:focus-visible,
#app-header .navbar-end .bao-shell-status-badge:focus-visible {
  background: var(--glass-control-fill, color-mix(in oklab, var(--color-base-content) 5%, transparent));
  border-color: var(--glass-control-border, color-mix(in oklab, var(--color-base-content) 8%, transparent));
}
/* Hybrid hover (imp-t04): fill paints only where hover is real — touch taps
   must not leave a sticky-hover fill on header chrome. */
@media (hover: hover) and (pointer: fine) {
  #app-header .navbar-end .bao-shell-topbar-actions .btn.bao-shell-icon-action:hover,
  #app-header .navbar-end details.dropdown > summary.bao-shell-icon-action:hover,
  #app-header .navbar-start [data-drawer-toggle-button].btn:hover {
    background: var(--glass-control-fill, color-mix(in oklab, var(--color-base-content) 5%, transparent));
    border-color: var(--glass-control-border, color-mix(in oklab, var(--color-base-content) 8%, transparent));
  }
}
@media (hover: hover) and (pointer: fine) {
  #app-header [data-topbar-search] {
    max-width: min(11rem, 30vw);
    transition: none;
  }
  #app-header [data-topbar-search]:focus-within {
    max-width: min(100%, 20rem, 45vw);
  }
  #app-header [data-topbar-search] .input {
    min-height: var(--touch-target-min);
    height: var(--touch-target-min);
  }
}
/* Focus expansion breathes with motion tokens; reduced motion keeps it instant. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  #app-header [data-topbar-search] {
    transition: max-width var(--duration-fast, 120ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
  }
}
@media (pointer: coarse) {
  #app-header [data-topbar-search] {
    max-width: min(100%, 14rem, 40vw);
  }
  #app-header [data-topbar-search] .input {
    min-height: var(--touch-target-min);
  }
}
/*
 * ---- Registry status (navbar-end): glyph-first. Offline keeps shape+tint.
 * The accessible label is visually clipped by the topbar policy owner;
 * connection state uses distinct silhouettes and never color alone.
 */
#app-header .bao-shell-status-badge {
  display: inline-flex;
  align-items: center;
  min-width: 1.5rem;
  min-height: var(--chrome-min-height, 2.25rem);
  /* Content-sized cap — warning/error labels must never clip mid-word
     (design.pdf §2.5/§9.1: status text legible in every state, all locales). */
  max-width: 14rem;
  gap: var(--space-micro, 0.25rem);
  padding-inline: var(--space-micro, 0.25rem);
}
/*
 * Status glyph trio (design.pdf §2.11): one simple silhouette per state —
 * network while connected/checking, monitor when only local surfaces serve
 * (a healthy workstation must not wear the brand-adjacent network mark),
 * wifi-off when the runtime stamps data-registry-offline. Shape + tone +
 * label/AT text; never color alone.
 */
#app-header .bao-shell-status-badge [data-status-glyph] {
  display: inline-flex;
  align-items: center;
}
#app-header .bao-shell-status-badge [data-status-glyph="local"],
#app-header .bao-shell-status-badge [data-status-glyph="offline"] {
  display: none;
}
#app-header .bao-shell-status-badge[data-registry-state="local"] [data-status-glyph="local"] {
  display: inline-flex;
}
#app-header .bao-shell-status-badge[data-registry-state="local"] [data-status-glyph="connected"] {
  display: none;
}
#app-header .bao-shell-status-badge[data-registry-offline="true"] [data-status-glyph="offline"],
#app-header .bao-shell-status-badge[data-registry-state="offline"] [data-status-glyph="offline"] {
  display: inline-flex;
}
#app-header .bao-shell-status-badge[data-registry-offline="true"] [data-status-glyph="connected"],
#app-header .bao-shell-status-badge[data-registry-offline="true"] [data-status-glyph="local"],
#app-header .bao-shell-status-badge[data-registry-state="offline"] [data-status-glyph="connected"] {
  display: none;
}
/* Checking: the network glyph breathes until the runtime resolves a state
   (stable dimensions — the pulse is opacity-only, never layout). */
#app-header .bao-shell-status-badge[data-registry-state="checking"] [data-status-glyph="connected"],
#app-header .bao-shell-status-badge:not([data-registry-state]) [data-status-glyph="connected"] {
  animation: bao-status-checking var(--duration-slow, 1.6s) var(--easing-standard, ease-in-out) infinite alternate;
}
@keyframes bao-status-checking {
  from { opacity: 1; }
  to { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  #app-header .bao-shell-status-badge[data-registry-state="checking"] [data-status-glyph="connected"],
  #app-header .bao-shell-status-badge:not([data-registry-state]) [data-status-glyph="connected"] {
    animation: none;
  }
}
/* OPERATOR-CONFIRMED DESIGN DECISION (supersedes plan P2 wording — do not
   resurrect the text pills): the chip is glyph-only at EVERY width. wifi-off
   is an unambiguous silhouette (§2.11); words live in title/aria-label and
   the fine-pointer hover/focus reveal above. */
/* Offline: the wifi-off glyph in error tone carries the signal at rest —
   never a permanent alarm-tinted slab in global chrome (SH-10, §4 tint
   selectively). The severity plate appears on hover/focus, where the chip's
   retry affordance is actually targeted; forced-colors keeps a system border. */
#app-header .bao-shell-status-badge[data-registry-offline="true"] {
  background: transparent;
  border-color: transparent;
  color: var(--color-error);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  #app-header .bao-shell-status-badge[data-registry-offline="true"]:hover,
  #app-header .bao-shell-status-badge[data-registry-offline="true"]:focus-visible,
  #app-header .bao-shell-status-badge[data-registry-offline="true"]:focus-within {
    background: color-mix(in oklab, var(--color-error) 10%, transparent);
    border-color: color-mix(in oklab, var(--color-error) 30%, transparent);
  }
}
/* Offline chip is actionable (retry registry) — match touch target + keyboard. */
#app-header button.bao-shell-status-badge[data-registry-offline="true"] {
  min-height: var(--touch-target-min, 2.75rem);
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
}
@media (pointer: coarse) {
  #app-header .bao-shell-status-badge {
    min-height: var(--touch-target-min, 2.75rem);
  }
}
/*
 * Hover-to-reveal: shell topbar densify only (design.pdf §6).
 * Page-header clinical CTAs (finish/generate/soft secondaries) keep permanent
 * labels — never hide essentials until hover on fine pointer either.
 */
@media (hover: hover) and (pointer: fine) {
  #app-header .bao-toolbar-action:not(.btn-primary) .bao-toolbar-label {
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    margin-inline-start: 0;
    transition: none;
  }
  #app-header .bao-toolbar-action:not(.btn-primary):hover .bao-toolbar-label,
  #app-header .bao-toolbar-action:not(.btn-primary):focus-visible .bao-toolbar-label,
  #app-header .bao-toolbar-action:not(.btn-primary):focus-within .bao-toolbar-label {
    max-width: 12rem;
    opacity: 1;
    margin-inline-start: var(--space-micro, 0.25rem);
    transition: none;
  }
}

/*
 * AI overlay findings: selectable assessment rows (provider/model/confidence/shape).
 * Solid content plane inside glass chrome; selected uses a primary tint and ring.
 */
[data-ai-overlay-item] {
  cursor: pointer;
  border-radius: var(--radius-field, 0.75rem);
  border: 1px solid transparent;
  padding: var(--space-micro, 0.25rem);
  min-height: var(--touch-target-min, 2.75rem);
  transition: background-color var(--duration-fast) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1)),
    border-color var(--duration-fast) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
}
[data-ai-overlay-item]:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset, 2px);
}
[data-ai-overlay-item][data-selected="true"],
[data-ai-overlay-item][aria-selected="true"] {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset, 2px);
  background: color-mix(in oklab, var(--color-primary) 8%, var(--color-base-100));
  border-color: color-mix(in oklab, var(--color-primary) 35%, var(--color-base-300));
}
@media (hover: hover) and (pointer: fine) {
  [data-ai-overlay-item]:hover:not([data-selected="true"]):not([aria-selected="true"]) {
    background: color-mix(in oklab, var(--color-base-200) 55%, transparent);
  }
}

/*
 * Tablet topbar: context strip scrolls in start; search stays a capped center
 * column (visibility SSOT lives in tokens-glass-shell-topbar-zones.ts).
 */
@media (min-width: 40rem) and (max-width: 63.99rem) {

}

/*
 * Context sheet (compact bottom sheet): single column on phone, two-up on
 * tablet so five context rows fit without a tall scroll wall.
 */
.bao-shell-context-sheet-list,
.bao-shell-context-sheet .bao-shell-context-sheet-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-micro, 0.25rem);
  width: 100%;
  min-width: 0;
  list-style: none;
  margin: 0;
  padding: var(--space-section, 0.75rem);
  padding-block-start: var(--space-micro, 0.25rem);
  /*
   * Bottom sheet pins to the screen edge on phone — the last rows must
   * clear the home-indicator safe area (Apple HIG). Same token/formula
   * family as .bao-dialog-footer-safe (tokens-layout-fabric-stage.ts),
   * additive here because the section pad stays.
   */
  padding-block-end: calc(var(--space-section, 0.75rem) + env(safe-area-inset-bottom, 0px));
}
@media (min-width: 40rem) and (max-width: 63.99rem) {
  .bao-shell-context-sheet-list,
  .bao-shell-context-sheet .bao-shell-context-sheet-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.bao-shell-context-sheet-list > li,
.bao-shell-context-sheet .bao-shell-context-sheet-list > li {
  min-width: 0;
}

/*
 * Command palette — glass box chrome (CommandPalette.bao) + solid list rows.
 * Active row uses base-200 fill; hover is pointer-gated only.
 */
#command-palette [data-command-palette-results] [data-command-palette-item] {
  display: flex;
  align-items: center;
  min-height: var(--touch-target-min, 2.75rem);
  width: 100%;
  min-width: 0;
  padding-inline: var(--space-inline, 0.5rem);
  padding-block: var(--space-micro, 0.25rem);
  border-radius: var(--radius-field, 0.75rem);
  border: 1px solid transparent;
  background: var(--color-base-100, var(--bg-panel));
  color: var(--color-base-content);
}
/*
 * Filtered-out rows: the rank module hides non-matches by toggling .hidden
 * ([hidden] parity), but the display:flex rule above sits at (1,2,0) and
 * beats Tailwind's (0,1,0) utility — this scoped (1,3,0) override lets
 * hidden rows actually leave the painted tree.
 */
#command-palette [data-command-palette-results] [data-command-palette-item].hidden,
#command-palette [data-command-palette-results] [data-command-palette-item][hidden] {
  display: none;
}
#command-palette [data-command-palette-results] [data-command-palette-item][data-command-palette-active="true"] {
  background: var(--color-base-200);
  border-color: color-mix(in oklab, var(--color-base-content) 8%, transparent);
  color: var(--color-base-content);
}
@media (hover: hover) and (pointer: fine) {
  #command-palette [data-command-palette-results] [data-command-palette-item]:hover:not([data-command-palette-active="true"]) {
    background: var(--bg-surface-hover);
  }
}
#command-palette [data-command-palette-results] .menu {
  gap: var(--space-2xs, 0.125rem);
}

/* Settings row and group rhythm is owned by tokens-glass-shell-settings-part-1.
 * Shared data-panel radius is owned by tokens-glass-chrome-part. */


}
@layer bao-tokens {

/* Align collapse is global (all breakpoints) — not phone-media nested. */

/* Preview overlay control rows wrap at Dynamic Type scales: the camera
   picker and rescan action sit over a fixed-size video region, so the row
   must break onto extra lines instead of sliding past the viewport. */
.bao-preview-region fieldset {
  flex-wrap: wrap;
  max-inline-size: 100%;
}

.bao-preview-region fieldset > * {
  min-inline-size: 0;
  max-inline-size: 100%;
}

/* Only active alignment fades secondary controls. Live preview alone must
   keep More actions reachable after dialogs close (all breakpoints).
   The overlay is ONE bottom bar (shutter row + action bar) — only the action
   bar fades while retaining its layout space, so revealing it on pointerdown
   cannot move the shutter before pointerup. */
.bao-preview-region[data-preview-aligning="true"] [data-capture-action-bar] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(0.5rem);
  transition-property: opacity, transform;
  transition-duration: var(--duration-fast, 120ms);
  transition-timing-function: var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
}.bao-preview-region[data-preview-state="live"][data-preview-controls="expanded"] [data-capture-action-bar] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {.bao-preview-region[data-preview-aligning="true"] [data-capture-action-bar] {
    transition-property: none; transition-duration: var(--duration-instant, 0ms);
    transform: none;
  }
}

/*
 * Capture session mode layout — shared Tabs tokens and control radius.
 * Settings tab list SSOT lives in tokens-glass-shell-settings + layout-framework
 * (desktop horizontal rail / phone vertical Apple list). Never force settings
 * to row here or phone destinations overflow the viewport.
 * The explicit radius uses the shared capsule token in older WebViews too.
 */
[data-session-mode-tabs],
[data-page-type="media"] .tabs.tabs-box.bao-tabs-scroll {
  display: inline-flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-micro, 0.25rem);
  /* Content-sized modes wrap inside their owning row when translated or enlarged.
   * A field radius fits both a single row and stacked rows without clipping
   * the first and last targets through the host's rounded scroll boundary. */
  width: auto;
  align-self: flex-start;
  max-width: min(100%, 24rem);
  margin-inline: 0;
  min-height: var(--touch-target-min, 2.75rem);
  padding: var(--space-micro, 0.25rem);
  /* The page stack owns separation; the control adds no second gap. */
  margin-block: 0;
  border: 1px solid var(--color-base-300, var(--border-base));
  border-radius: var(--radius-field);
  /* Solid segmented control host — not frosted data skin (Liquid Glass: chrome only). */
  background: var(--color-base-200, var(--bg-panel));
  box-shadow: none;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
[data-session-mode-tabs] .tab,
[data-session-mode-tabs] .bao-session-mode-tab,
.tabs.tabs-box .bao-session-mode-tab,
[data-page-type="media"] .tabs.tabs-box .bao-session-mode-tab {
  display: inline-flex;
  flex: 1 1 auto;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-micro, 0.25rem);
  /* WCAG/HIG: never below 44px on phone/coarse. */
  min-height: var(--touch-target-min, 2.75rem);
  min-width: var(--touch-target-min, 2.75rem);
  max-inline-size: 100%;
  block-size: auto;
  overflow-wrap: anywhere;
  margin: 0;
  padding: var(--space-inline, 0.5rem) var(--space-section, 0.75rem);
  border: 1px solid transparent;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--color-base-content);
  opacity: 1;
  font-size: var(--text-sm, 0.875rem);
  font-weight: 500;
  line-height: 1.2;
  white-space: normal;
  cursor: pointer;
  box-shadow: none;
}

[data-session-mode-tabs] .tab:not(.tab-active) {
  color: color-mix(in oklab, var(--color-base-content) 72%, transparent);
}

[data-session-mode-tabs] .tab-active,
[data-session-mode-tabs] .bao-session-mode-tab.tab-active,
[data-session-mode-tabs] .bao-session-mode-tab[aria-selected="true"],
.tabs.tabs-box .bao-session-mode-tab.tab-active,
[data-page-type="media"] .tabs.tabs-box .bao-session-mode-tab.tab-active,
[data-page-type="media"] .tabs.tabs-box .bao-session-mode-tab[aria-selected="true"] {
  background: var(--color-primary);
  color: var(--color-primary-content);
  opacity: 1;
  border-color: var(--color-primary);
  /* One primary tint only — no elevation spectacle under dense workbench.
     Selector mirrors the base chrome's strongest arm so the active state
     always outranks the transparent base (one owner, no specificity war). */
  box-shadow: none;
}

[data-session-mode-tabs] .tab svg,
[data-session-mode-tabs] .bao-session-mode-tab svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

/*
 * Phone session tabs: icon-first segmented control (not full pill + long label).
 * aria-label on the tab keeps the mode name for assistive tech.
 */
@media (max-width: 47.99rem) {
  /* Two-segment mode switch keeps its labels on phones — icon-only camera/pen
     glyphs are ambiguous for a mode change and the strip has the width
     (HIG segmented controls stay labeled). Tabs just densify. */
  [data-session-mode-tabs] .tab,
  [data-session-mode-tabs] .bao-session-mode-tab {
    position: relative;
    flex: 1 1 auto;
    min-width: var(--touch-target-min, 2.75rem);
    max-width: none;
    padding-inline: var(--space-inline, 0.5rem);
    gap: var(--space-micro, 0.25rem);
    justify-content: center;
  }
}

/* Top actions grow with translated and enlarged labels; the row wraps before clipping. */
[data-page="capture"] [data-capture-top-chrome] {
  padding-inline: 0;
}
/*
 * Secondary context and device commands use the shared Button geometry and
 * interaction states without four competing filled capsules. Configure its
 * existing material inputs here; the Button owner retains hover, press and
 * focus paint. The mode selector remains the selected navigation emphasis.
 */
[data-page="capture"] [data-capture-top-chrome] .btn.btn-soft {
  --button-neutral-fill: transparent;
  --button-material-sheen: none;
  --button-material-shadow: none;
  --button-material-blur: none;
  border-color: transparent;
}
/* Grouped and direct buttons share the same wrapping contract and touch floor. */
[data-page="capture"] [data-capture-top-chrome] button.btn:not([hidden]):not(.hidden) {
  --button-inline-padding: var(--space-inline);
  block-size: auto;
  max-inline-size: 100%;
  flex-wrap: wrap;
  white-space: normal;
}
[data-page="capture"] [data-capture-top-chrome] .btn:not(.btn-square) > span {
  min-inline-size: 0;
  max-inline-size: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}

/*
 * Capture device fieldsets — flat, content-sized groups. The command chrome
 * band owns the surface; per-field card boxes multiplied five empty panels
 * across the capture fold, so fields carry no border/background of their own.
 */
fieldset[data-capture-control] {
  display: flex;
  flex-direction: column;
  gap: var(--space-micro, 0.25rem);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

fieldset[data-capture-control] legend {
  padding-inline: var(--space-micro, 0.25rem);
  font-size: var(--text-xs, 0.75rem);
  font-weight: var(--font-weight-semibold, 600);
  letter-spacing: normal;
  /* Sentence case (HIG): typography.avoidUppercaseCss bans uppercase legends;
     text-2xs is the badge/timestamp floor, field labels use text-xs. */
  /* Label copy uses solid text-secondary — not translucent body (design.pdf §2.10). */
  color: var(--text-secondary, var(--color-base-content));
}

[data-capture-command-chrome] select,
[data-capture-command-chrome] .select,
fieldset[data-capture-control] select {
  width: 100%;
  min-height: var(--touch-target-min, 2.75rem);
  /* Control boundary token — same floor as every other field (SC 1.4.11). */
  border: 1px solid var(--border-control, var(--color-base-300));
  border-radius: var(--radius-field, 0.75rem);
  /* background-color LONGHAND: the shorthand resets background-image and
   * erased these selects' daisyUI caret (form-control-bg-longhand gate).
   * padding keeps the 1.75rem inline-end caret zone for the same reason. */
  background-color: var(--color-base-100, var(--bg-panel));
  padding: var(--space-micro, 0.25rem) var(--space-inline, 0.5rem);
  padding-inline-end: 1.75rem;

}

/*
 * Capture workbench: preview + shutter above fold on phone.
 * Side panel (specimen) scrolls below; never starve the stage.
 */
/* Desktop + tablet: stage owns the viewport (design.pdf content plane first). */
[data-page="capture"] [data-capture-workbench],
[data-page-type="media"] [data-capture-workbench] {
  min-height: min(62vh, 36rem);
}
[data-page="capture"] .bao-preview-region,
[data-page-type="media"] .bao-preview-region {
  min-height: min(48vh, 28rem);
  flex: 1 1 auto;
}
/* Media pathologist workflow: one dense horizontal strip, never a second page. */
[data-page-type="media"] > [data-related-links][data-pathologist-workflow],
[data-page="capture"] > [data-related-links][data-pathologist-workflow],
[data-page="editor"] > [data-related-links][data-pathologist-workflow],
[data-page="ai-analysis"] [data-related-links][data-pathologist-workflow] {
  flex: 0 0 auto;
  max-height: 3.25rem;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  margin-block: 0;
  padding-block: var(--space-2xs, 0.125rem);
}
[data-page-type="media"] [data-related-links][data-pathologist-workflow] > h2,
[data-page="capture"] [data-related-links][data-pathologist-workflow] > h2,
[data-page="editor"] [data-related-links][data-pathologist-workflow] > h2,
[data-page="ai-analysis"] [data-related-links][data-pathologist-workflow] > h2 {
  display: none;
}
[data-page-type="media"] [data-related-links][data-pathologist-workflow] ol,
[data-page="capture"] [data-related-links][data-pathologist-workflow] ol,
[data-page="editor"] [data-related-links][data-pathologist-workflow] ol,
[data-page="ai-analysis"] [data-related-links][data-pathologist-workflow] ol {
  flex-wrap: nowrap;
  width: max-content;
  min-width: 100%;
}
[data-page-type="media"] [data-related-links][data-pathologist-workflow] ol > li,
[data-page="capture"] [data-related-links][data-pathologist-workflow] ol > li,
[data-page="editor"] [data-related-links][data-pathologist-workflow] ol > li,
[data-page="ai-analysis"] [data-related-links][data-pathologist-workflow] ol > li {
  flex: 0 0 auto;
  min-width: 0;
  max-width: none;
}

@media (max-width: 64rem) {
  [data-page-type="media"] [data-capture-workbench] {
    display: flex;
    gap: var(--space-section);
  }

  [data-capture-workbench-primary],
  .bao-media-workbench-primary {
    order: 0;
    flex: 1 1 auto;
    min-height: min(48vh, 22rem);
  }

  [data-page="capture"]
    .bao-preview-region:not([data-preview-state="live"]) {
    flex: 0 0 auto;
    height: auto;
  }

  [data-capture-workbench-secondary],
  .bao-media-workbench-secondary {
    order: 1;
    flex: 0 0 auto;
    max-height: none;
  }

  .bao-preview-region {
    min-height: 0;
    overflow: hidden;
    background: var(--color-base-200, var(--bg-panel));
  }
  /* Idle empty state: strong hierarchy so mobile WebView never shows a blank slab. */
  .bao-preview-region > [data-camera-preview-placeholder] {
    color: var(--color-base-content, var(--text-primary));
  }
  /*
   * HERO glyph only. This used to be a descendant selector, so it also
   * captured the SVGs inside the empty-state's own action buttons: measured
   * on /capture, the "Select camera" and "Gallery" icons rendered 48x48 in
   * --color-primary instead of 16x16 in their button's foreground. On the
   * primary-filled "Select camera" that is blue-on-blue — the icon was there
   * in the DOM and invisible on screen.
   *
   * The hero glyph is a direct child of the placeholder; the actions sit one
   * level down inside a fieldset. A child combinator therefore separates them
   * exactly, with no exclusion list to keep in sync.
   */
  .bao-preview-region > [data-camera-preview-placeholder] > svg {
    width: 3rem;
    height: 3rem;
    color: var(--color-primary, var(--accent-primary));
    /* Decorative icon mute via token — not body text opacity (design.pdf §2.10). */
    opacity: var(--pulse-alpha-floor, 0.55);
  }
  .bao-preview-region > [data-camera-preview-placeholder] p:first-of-type {
    font-size: var(--text-sm, 0.875rem);
    font-weight: var(--font-weight-semibold, 600);
    /* High-opacity semantic text tokens — never semitransparent body copy. */
    color: var(--color-base-content, var(--text-primary));
  }
  .bao-preview-region > [data-camera-preview-placeholder] p:last-of-type {
    font-size: var(--text-xs, 0.75rem);
    color: var(--text-secondary, var(--color-base-content));
    max-width: 18rem;
  }
  @media (max-width: 47.99rem) {
    .bao-preview-region {
      min-height: 0;
    }
  }

  [data-capture-preview-overlay] .btn,
  [data-capture-action-bar] .btn {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
  }
}


/* Mode labels belong to their tabs at every width; generic toolbar reveal must not clamp them. */
[data-session-mode-tabs] > [role="tab"][data-session-mode-tab] > .bao-toolbar-label {
  display: inline-block;
  min-inline-size: 0;
  max-inline-size: 100%;
  flex: 0 1 auto;
  opacity: 1;
  overflow: visible;
  white-space: normal;
  overflow-wrap: anywhere;
}

  /*
   * Capture preview overlay bar — glass-standard media control layer
   * (design.pdf §5.11: controls attached to media are a canonical glass
   * layer; L1 chrome floating over the L0 stream). Unlayered token CSS
   * outranks the element's bg-base-100 utility. Solid-first fallback +
   * reduced-transparency / forced-colors variants per the a11y matrix.
   */
  /*
   * The glass material establishes a relative containing block for its
   * illumination layers. State-specific overlay positioning belongs to the
   * capture preview layout source; this material source owns paint only.
   */
  [data-capture-preview-overlay] {
    background: color-mix(
      in oklab,
      var(--bg-panel) calc(var(--liquid-fill-persistent) * 100%),
      transparent
    );
    -webkit-backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
    backdrop-filter: blur(var(--liquid-blur-regular)) saturate(var(--liquid-saturate-regular));
    border-color: var(--liquid-border-regular);
    box-shadow:
      var(--shadow-glass-elevated),
      var(--shadow-glass-inner);
  }
  @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    [data-capture-preview-overlay] {
      background: var(--bg-panel);
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }

  /*
   * Overflow affordance panel — L2 transient glass-strong (menus/sheets).
   * daisyUI .dropdown-content owns positioning; tokens own the material.
   */
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
    background: color-mix(
      in oklab,
      var(--bg-panel) calc(var(--liquid-fill-transient) * 100%),
      transparent
    );
    -webkit-backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
    backdrop-filter: blur(var(--liquid-blur-nav)) saturate(var(--liquid-saturate-strong));
    border: 1px solid var(--liquid-border-regular);
    border-radius: var(--radius-box, 1rem);
    box-shadow:
      var(--shadow-glass-floating),
      var(--shadow-glass-inner);
    padding: var(--space-inline, 0.5rem);
  }
  @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
      background: var(--bg-panel);
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }
  /*
   * Overflow panel scroll containment (long action lists on small hosts).
   * The anchored panel's ceiling is the room the dropdown geometry
   * controller measures between the trigger and the viewport edge and
   * publishes as --bao-dropdown-max-height (app-shell-dropdowns-geometry).
   * A fixed rem cap layered over that var overrode the measurement: at
   * 1280x720 the nine-row menu was cut to 24rem, the Review group (AI
   * Analysis, Evidence, Reports) scrolled out of sight behind no scroll cue,
   * and 570px of stage stood empty above the trigger. The literal ceiling now
   * serves only hosts where the controller has not stamped; the row cue is
   * the shared bao-scroll-edge-y mask on the panel token.
   */
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
    max-height: var(--bao-dropdown-max-height, min(60dvh, 24rem));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /*
   * §8.6 canonical transformation: the overflow panel is a bottom sheet on
   * compact — same idiom as the account-menu sheet in
   * tokens-glass-shell-topbar-context-sheet.ts. The panel is popover-promoted
   * (top layer) because the page track's container-type traps fixed panels
   * under the toolbar clip; these rules must out-rank the controller's
   * .bao-dropdown-panel-viewport geometry and the popover UA inset defaults.
   *
   * The block-end offset clears the dock band, not just the safe area:
   * measured at 390x844 the sheet's lower edge landed at y=836 against a
   * nav#mobile-dock whose top edge is y=780, burying the "Capture" tab under
   * the last two rows. --dock-height + --dock-safe-area-offset is the dock's
   * own box (tokens-layout-dock-base) and it zeroes itself at >=48rem, so the
   * same expression is correct in every band.
   */
  @media (max-width: 39.99rem) {
    [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
      position: fixed;
      inset-inline: var(--space-inline, 0.5rem);
      inset-block-start: auto;
      inset-block-end: calc(
        var(--dock-height, 4rem) +
        var(--dock-safe-area-offset, env(safe-area-inset-bottom, 0px)) +
        var(--space-inline, 0.5rem)
      );
      margin: 0;
      width: auto;
      max-width: none;
      /*
       * The sheet pins to the dock, not to the trigger, so the controller's
       * trigger-relative ceiling does not describe it: measured at 375x812 it
       * held the nine rows to 250px with 400px of viewport free above. Size
       * it to the visual viewport above the dock, capped so the topbar band
       * stays clear; rows past the cap scroll behind the shared edge cue.
       */
      max-height: min(80vh, calc(var(--vv-height, 100dvh) - var(--dock-height, 4rem) - var(--dock-safe-area-offset, env(safe-area-inset-bottom, 0px)) - var(--space-inline, 0.5rem) * 2));
      z-index: var(--z-modal, 1300);
    }
  }
  /* ≥40rem keeps the anchored dropdown, sized to its rows — never the 44px
     trigger column daisyUI infers from the icon-button host. */
  @media (min-width: 40rem) {
    [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
      width: max-content;
      min-width: 14rem;
      max-width: min(92vw, 24rem);
    }
  }
  /*
   * Tier discipline (Apple camera grammar): the persistent bar is gallery ·
   * shutter · overflow only. Any tiered action rendered directly into the
   * bar is a contract violation — it belongs in the grouped overflow sheet.
   * ONE rule replaces the retired per-action hide chains (geometry nuke
   * idiom: the shell-wide .btn display chains tie a lone display:none).
   */
  [data-capture-action-bar] > [data-capture-tier],
  [data-capture-action-bar] > [data-disabled-tip-host] > [data-capture-tier] {
    display: none;
    width: 0;
    min-width: 0;
    max-width: 0;
    height: 0;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    margin: 0;
    border: 0;
    pointer-events: none;
    visibility: hidden;
  }
  /* Grouped sheet sections read as one list with hairline separators. */
  [data-capture-overflow-group] + [data-capture-overflow-group] {
    border-block-start: 1px solid
      color-mix(in oklab, var(--color-base-300, var(--border-base)) 60%, transparent);
    padding-block-start: var(--space-micro, 0.25rem);
  }
  /* 44px touch floor on plain menu rows (route items carry no .btn). */
  @media (pointer: coarse) {
    [data-capture-overflow-panel] [role="menuitem"] {
      min-height: var(--touch-target-min, 2.75rem);
    }
  }
  /*
   * Overflow menu rows are labeled list items rendered with the menuItemRow
   * token (route-row parity) — never btn-square toolbar chrome. The retired
   * .bao-toolbar-action/.bao-toolbar-label restore blocks fought the
   * icon-first hover-reveal cascade property-by-property and lost to a
   * higher-specificity fine-pointer clipper; fixing the row markup at the
   * emitter ends that war (pages-renderers-capture-actions.ts).
   */
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
    flex-direction: column;
    align-items: stretch;
  }
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] [data-disabled-tip-host] {
    width: 100%;
  }

  /* The floating bar owns contrast over the camera. Secondary controls share
   * that surface; selected segments and the shutter own their state fills. */
  [data-capture-preview-overlay] .btn:not(.btn-primary):not(.bao-capture-shutter),
  [data-capture-action-bar] .btn:not(.btn-primary) {
    color: var(--color-base-content);
    background: transparent;
    border-color: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  @media (hover: hover) and (pointer: fine) {
    [data-capture-preview-overlay] .btn:not(.btn-primary):not(.bao-capture-shutter):hover,
    [data-capture-action-bar] .btn:not(.btn-primary):hover {
      background: color-mix(in oklab, var(--color-base-content) 12%, transparent);
      color: var(--color-base-content);
    }
  }
  [data-capture-preview-overlay] .btn.btn-primary,
  [data-capture-preview-overlay] .bao-capture-shutter {
    color: var(--color-primary-content);
    background: var(--color-primary);
    border-color: transparent;
    min-width: var(--touch-target-min, 2.75rem);
    min-height: var(--touch-target-min, 2.75rem);
    /* Primary glow: the shutter shares the bar's 44px control size, so tint
       and glow carry the emphasis (a lone .bao-capture-shutter shadow rule
       never painted — measured box-shadow: none at every width). */
    box-shadow: 0 4px 16px color-mix(in oklab, var(--color-primary) 35%, transparent);
  }
  [data-capture-preview-overlay] .btn.btn-primary:disabled,
  [data-capture-preview-overlay] .bao-capture-shutter:disabled {
    box-shadow: none;
  }

/* Capture floating glass nests only — session mode tabs are chromeSurface solid
 * (sticky page chrome law). Blur SSOT: tokens-glass-base; a11y: tokens-glass-a11y. */
[data-capture-chrome] .bao-surface-glass-regular {
  background: color-mix(in oklab, var(--color-base-100, var(--bg-panel)) 86%, transparent);
}

@media (prefers-reduced-transparency: reduce) {
  /* .bao-glass-control listed mid-group so S77 does not treat this as the base block. */
  [data-capture-chrome] .bao-glass-control,
  [data-capture-chrome] .bao-surface-glass-regular,
  [data-capture-preview-overlay],
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
    background: var(--color-base-200);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (forced-colors: active) {
  [data-capture-preview-overlay],
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel] {
    background: Canvas;
    color: CanvasText;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 1px solid ButtonText;
    box-shadow: none;
  }
}

/* Inspector disclosures: keep long AI/spatial panels inside the rail. */
[data-editor-inspector-disclosure] .collapse-content {
  max-height: min(24rem, 50vh);
  overflow-y: auto;
}

/*
 * The capture bar opens its tips upward. It rests on the stage floor, so a
 * tooltip-bottom tip (the shared icon-button and disabled-why-host tokens)
 * painted below the stage, over the next card — measured 768x1024:
 * "Requires: Device" crossed the session controls. Geometry mirrors daisyUI's
 * tooltip-top; the tokens stay shared. (The 3D controls pill is a scroll
 * container and clips every tip; its controls are named by aria-label.)
 */
[data-capture-preview-overlay] .tooltip-bottom[data-tip]:before,
[data-capture-preview-overlay] .tooltip-bottom > .tooltip-content {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, 0.25rem));
  inset: auto auto var(--tt-off) 50%;
}
[data-capture-preview-overlay] .tooltip-bottom:after {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, 0.25rem));
  inset: auto auto var(--tt-tail) 50%;
}
/*
 * Primary shutter — the bar's primary action at the bar's control size: the
 * circle and touch-target tokens own the 44px geometry shared with the gallery
 * and overflow controls; tint and glow live with the overlay vibrancy rules
 * above. The former 56/72px discs fought those tokens and painted a 44x72
 * ringed pill (measured 390x844 and 768x1024).
 */


/* ============================================================
   Capture pipeline phase + recording overlay (Bao-Translate UX)
   SINGLE definition — the class-based twin in
   tokens-glass-shell-capture-recording.ts was removed (SSOT: one
   scrim treatment, one data-phase state driver).
   Phase truth on [data-capture-chrome][data-phase]; CSS reflects only.
   ============================================================ */

[data-capture-chrome][data-phase="idle"] [data-capture-recording-overlay],
[data-capture-chrome][data-phase="error"] [data-capture-recording-overlay],
[data-capture-chrome][data-phase] [data-capture-recording-overlay][hidden] {
  display: none;
}

[data-capture-chrome][data-phase="starting"] [data-capture-recording-overlay][open],
[data-capture-chrome][data-phase="recording"] [data-capture-recording-overlay][open],
[data-capture-chrome][data-phase="processing"] [data-capture-recording-overlay][open] {
  display: flex;
}

[data-capture-recording-overlay] {
  position: absolute;
  inset: 0;
  z-index: var(--z-modal, 1300);
  inline-size: 100%;
  block-size: 100%;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  border: 0;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-inline, 0.5rem);
  padding: var(--space-section, 0.75rem);
  padding-block-end: calc(var(--space-section, 0.75rem) + env(safe-area-inset-bottom, 0px));
  background: color-mix(in oklab, var(--color-base-content) 55%, transparent);
}

[data-capture-recording-overlay]::backdrop {
  background: var(--glass-scrim, transparent);
}

[data-capture-recording-dock] {
  width: min(100%, var(--max-w-form, 28rem));
}

[data-capture-recording-halo] {
  display: grid;
  place-items: center;
  inline-size: 4.5rem;
  block-size: 4.5rem;
  border-radius: var(--radius-full);
  background: color-mix(in oklab, var(--color-error) 88%, transparent);
  color: var(--color-error-content, var(--color-base-100));
}

[data-capture-chrome][data-phase="recording"] [data-capture-action="record-audio"],
[data-capture-chrome][data-phase="recording"] [data-capture-action="record-video"],
[data-capture-chrome][data-phase="starting"] [data-capture-action="record-audio"],
[data-capture-chrome][data-phase="starting"] [data-capture-action="record-video"] {
  color: var(--color-error-content, var(--color-base-100));
  background: var(--color-error);
  border-color: transparent;
}

[data-capture-live-source] {
  font-style: italic;
  opacity: var(--opacity-strong, 0.85);
  max-inline-size: min(36rem, 100%);
  text-align: center;
}

[data-capture-live-final] {
  font-style: normal;
  font-weight: 600;
  max-inline-size: min(36rem, 100%);
  text-align: center;
}

[data-capture-waveform] {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--space-2xs, 0.125rem);
  inline-size: min(16rem, 80%);
  block-size: 2.5rem;
}

[data-capture-waveform] span {
  flex: 1 1 0;
  min-inline-size: 0.125rem;
  block-size: 30%;
  border-radius: var(--radius-full);
  background: color-mix(in oklab, var(--color-primary) 70%, var(--color-base-content));
  transform-origin: bottom center;
  /* Amplitude via data-waveform-level 0..10 — never inline CSS custom props. */
  transform: scaleY(0.22);
}

[data-capture-waveform] span[data-waveform-level="0"] { transform: scaleY(0.22); }
[data-capture-waveform] span[data-waveform-level="1"] { transform: scaleY(0.3); }
[data-capture-waveform] span[data-waveform-level="2"] { transform: scaleY(0.38); }
[data-capture-waveform] span[data-waveform-level="3"] { transform: scaleY(0.46); }
[data-capture-waveform] span[data-waveform-level="4"] { transform: scaleY(0.54); }
[data-capture-waveform] span[data-waveform-level="5"] { transform: scaleY(0.62); }
[data-capture-waveform] span[data-waveform-level="6"] { transform: scaleY(0.7); }
[data-capture-waveform] span[data-waveform-level="7"] { transform: scaleY(0.78); }
[data-capture-waveform] span[data-waveform-level="8"] { transform: scaleY(0.86); }
[data-capture-waveform] span[data-waveform-level="9"] { transform: scaleY(0.94); }
[data-capture-waveform] span[data-waveform-level="10"] { transform: scaleY(1); }

[data-capture-recording-status] {
  font-variant-numeric: tabular-nums;
}

[data-capture-recording-actions] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-inline, 0.5rem);
}

/* Tuning scrubbers: 44px touch floor on the primary touch surface. */
@media (pointer: coarse) {
  [data-uvc-tuning-panel] input,
  [data-uvc-tuning-panel] .btn {
    min-height: var(--touch-target-min, 2.75rem);
  }
}



/*
 * Capture side panel recent-captures slot fetches the section-param rail
 * variant (?sections=captures) — the server emits only the captures section,
 * so no consumer-side CSS hiding of annotation/AI rail sections exists here.
 */

/*
 * Permanent-rail band (≥80rem container query — parity with
 * tokens-glass-shell-drawer session-rail show; the page track is the
 * @container context). Right rail already lists captures; hide the
 * side-panel twin so one HTMX consumer stays visible.
 */
@container (min-width: 80rem) {
  [data-page="capture"] [data-capture-recent-captures] {
    display: none;
  }
}

/* Context strip: muted skeleton pulse while HTMX hydrates live context. */
[data-capture-context-strip][aria-busy="true"] [data-capture-context],
[data-capture-context-strip][data-state="loading"] [data-capture-context] {
  opacity: var(--pulse-alpha-floor, 0.55);
  pointer-events: none;
}

/* Live preview: hide placeholder when stream is attached (WebView 101). */
[data-camera-preview][data-preview-state="live"] [data-camera-preview-placeholder] {
  display: none;
}

/* WKWebView paint-recovery kick: app-shell-camera-preview.ts sets the
   attribute for two animation frames once the stream is live. The promoted
   layer forces WebKit to rebuild the accelerated compositing layer that a
   stalled first frame never created (Tauri grey-preview state; Safari and
   Chromium composite the same stream without it). */
[data-camera-preview-video][data-preview-kick="true"] {
  transform: translateZ(0);
}

/*
 * WKWebView raster preview: WebKit decodes a MediaStream attached to a
 * dynamically inserted <video> (soft navigation) but never composites it —
 * the stage stays grey until a hard reload rebuilds the document.
 * app-shell-camera-preview-raster.ts mirrors each decoded frame into a
 * sibling canvas, whose compositing path is unaffected. The canvas overlays
 * the region only while the region is live and rastering. The video element
 * stays rendered underneath (WebKit suspends decode of non-rendered videos,
 * which would starve drawImage of frames); the opaque canvas paints above it
 * as a later sibling in the same stacking order.
 */
.bao-preview-region > [data-camera-preview-raster] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: none;
  pointer-events: none;
}
.bao-preview-region[data-preview-state="live"][data-preview-raster="true"]
  > [data-camera-preview-raster] {
  display: block;
}
html[data-capture-mirror-preview="true"]
  [data-page="capture"]
  [data-camera-preview-raster] {
  transform: scaleX(-1);
}


/*
 * Stacked workbench: never shrink below the stage's own height.
 *
 * Once the column stacks, .bao-media-workbench is a flex:1 1 auto item in a
 * scrollport whose free space is already spent, so flex-shrink pulled it 22px
 * BELOW its content while its only visible child (the primary column) is
 * flex:0 0 auto and refuses to give the space back. With overflow visible the
 * surplus painted straight over the disclosure beneath it — measured on
 * /capture at 320x720: workbench 446px around 468px of content, #camera-preview
 * ending at 648 while the "Device Controls" details started at 634, a 14px
 * collision that the structure audit reports as an occluded control.
 *
 * Refusing to shrink is the fix, not a min-height: the scrollport absorbs the
 * extra (scrollHeight 564 -> 586), the details lands 8px clear of the stage,
 * and the stage keeps the height its own content asked for.
 */
@media (max-width: 64rem) {
  [data-page-type="media"] .bao-media-workbench,
  [data-page-type="media"] [data-capture-workbench] {
    flex-shrink: 0;
  }
}

/* Status readout chips (not fieldsets) share the same solid control shell. */
[data-capture-command-chrome] [data-capture-control][role="status"] {
  display: flex;
  flex-direction: column;
  gap: var(--space-micro, 0.25rem);
  min-width: 0;
  margin: 0;
  padding: var(--space-micro, 0.25rem) var(--space-inline, 0.5rem);
  border: 1px solid var(--color-base-300, var(--border-base));
  border-radius: var(--radius-field, 0.75rem);
  background: var(--color-base-100, var(--bg-panel));
}

[data-capture-command-chrome] [data-capture-control][role="status"] .badge,
[data-capture-command-chrome] [data-capture-control][role="status"] span:last-child {
  font-size: var(--text-sm, 0.875rem);
  font-weight: 500;
  color: var(--color-base-content);
}

/*
 * AI analysis context collapse is global (all breakpoints).
 * Shell context chips already surface case/specimen/patient/accession.
 * Keep readonly fields in the DOM (sr-clip) for form submit + app-shell
 * prefill — never paint raw UUIDs or duplicate chrome on desktop/tablet.
 */
[data-ai-analysis-form] fieldset:has([data-ai-analysis-context-field]),
[data-ai-analysis-form] [data-ai-analysis-context-hint] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Phone: status is informational only — reclaim stage for shutter. */
@media (max-width: 47.99rem) {
  /*
   * Dock the media page-header actions beside the mode strip instead of
   * giving them a band of their own.
   *
   * The media page header's title block is sr-only (the shell topbar owns
   * naming) and both desktop actions are max-md:hidden, so below 48rem the
   * only thing left in the header is the "More actions" overflow menu —
   * measured on /capture at 375px: 57px of bordered chrome around one 44px
   * control, stacked above the mode strip behind its own divider line. It
   * read as an empty section.
   *
   * The header is the immediate previous sibling of the strip inside
   * .bao-workbench, so taking it out of flow promotes the strip to the first
   * row and the actions dock at that row's end. Padding plus a transparent
   * border mirror the control row height, so the button lands on its centre
   * line from tokens rather than from a measured offset.
   *
   * Declared here, not in tokens-layout-capture-preview-part-b: that module
   * emits inside @layer utilities and the canonical
   * .bao-page-header width:100% is unlayered, which wins over any layered
   * width no matter how specific.
   */
  [data-page-type="media"] > [data-state-content] {
    position: relative;
  }
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only) {
    /*
     * In flow, not docked. Taking the header out of flow put it on the strip's
     * own row, and that only works while the dock is ONE control — which is
     * the assumption the strip's max-width below is written against ("one
     * touch target plus a gutter each side"). /capture docks two labelled
     * buttons: measured at 390x844 the actions box is 265px wide starting at
     * x=108 while the mode tabs run x=8..187, so "Annotate" and "Finish Case"
     * painted through each other, and the tabs (z-index 1100, sticky) took
     * the button's centre hit test. A screenshot shows the overprint plainly.
     *
     * No width cap can fix that class of overlap, because the dock's width is
     * its localized labels: whatever number clears "Finish Case" fails for a
     * longer translation of it, and this repo ships ten locales. In flow the
     * two can never meet, at any string length, for one row of vertical cost.
     * The docked treatment stays at >=48rem, where the row is wide enough to
     * hold both.
     */
    position: static;
    inset: auto;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    width: max-content;
    max-width: 100%;
    margin-inline-start: auto;
    margin-block-end: var(--space-micro, 0.25rem);
    padding-block: var(--space-micro, 0.25rem);
    padding-inline: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-field, 0.75rem);
  }
  /*
   * The header's leading slot is a flex-1 spacer that separates title from
   * actions on desktop. Out of flow with the title sr-only it has nothing to
   * separate, and it grew the docked box back to the full column width
   * (measured 341px), dragging the menu to the start edge.
   */
  [data-page-type="media"] > [data-state-content] > [data-primitive="PageHeader"] > div:first-child:not(.sr-only):not(.bao-page-header-actions):empty {
    display: none;
  }
  /* The page header is in flow above the modes; the mode strip owns its full row. */
  [data-session-mode-tabs][data-session-mode-tabs],
  [data-page-type="media"] .tabs.bao-tabs-scroll[data-session-mode-tabs] {
    max-width: min(100%, 24rem);
  }
  [data-page="capture"] [data-capture-command-chrome] [data-capture-control][role="status"]:not([data-capture-device-sheet] *),
  [data-page="capture"] [data-capture-device-status]:not([data-capture-device-sheet] *) {
    display: none;
  }
  /* HIG/WCAG: enforce ≥44px touch targets on command chrome controls. */
  [data-page="capture"] [data-capture-command-chrome] .select,
  [data-page="capture"] [data-capture-command-chrome] .btn {
    min-height: var(--touch-target-min, 2.75rem);
  }
  [data-page="capture"] [data-capture-command-chrome] .bao-capture-device-grid {
    gap: var(--space-micro);
  }
  /* Model select in Analyze is SSOT on phone; catalog search is secondary. */
  [data-page="ai-analysis"] [data-model-search-bar] {
    display: none;
  }
}
/*
 * The permanent rail owns case/specimen controls and recent captures.
 * Its presence keeps those copies out of the workbench even when collapsed.
 * Capture mode tabs stay in the page, outside refreshable rail content, so
 * changing modes preserves the keyboard target and its position.
 */
@media (min-width: 80rem) {
  body:has(#session-rail)
    [data-page="capture"]
    [data-capture-recent-captures] {
    display: none;
  }
  /*
   * Full-bleed canvas: case/specimen pickers live in the session rail
   * (data-session-rail-session-controls) whenever that rail is present.
   */
  body:has(#session-rail)
    [data-page="capture"]
    [data-capture-workbench-secondary] {
    display: none;
  }
  /*
   * One column in both rail states. Re-deriving the track count on every
   * collapse reflowed the whole workbench under the operator's cursor.
   */
  body:has(#session-rail)
    [data-page="capture"]
    [data-capture-workbench] {
    grid-template-columns: minmax(0, 1fr);
  }
  body:has(#session-rail)
    [data-page="capture"]
    .bao-media-workbench {
    grid-template-columns: minmax(0, 1fr);
  }
}

[data-capture-chrome][data-capture-case-ready="false"] [data-camera-preview-placeholder] {
  display: none;
}
[data-capture-chrome][data-capture-case-ready="false"] [data-capture-case-required] {
  display: flex;
}
[data-capture-chrome][data-capture-case-ready="true"] [data-capture-case-required] {
  display: none;
}
/* Camera preflight keeps device setup separate from patient capture. The Device Controls trigger and native Sheet remain reachable independently of case readiness. */
[data-capture-chrome][data-capture-case-ready="false"] [data-camera-preview][data-preview-state="live"] [data-capture-case-required] {
  display: none;
}
[data-capture-chrome][data-capture-case-ready="false"] [data-camera-preview][data-preview-state="live"] [data-camera-preview-video] {
  display: block;
}
/* Device Controls is a native Sheet sibling of top chrome, never a disclosure nested inside it. */ dialog[data-primitive="Sheet"][data-capture-device-sheet]:not([open]) { display: none; } dialog[data-primitive="Sheet"][data-capture-device-sheet][open] { position: fixed; inset: 0; inline-size: 100%; block-size: 100dvh; max-inline-size: none; max-block-size: none; margin: 0; padding: 0; overflow: visible; border: 0; border-radius: 0; background: transparent; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; } [data-capture-chrome]:has(dialog[data-capture-device-sheet][open]) [data-camera-preview-placeholder] fieldset { display: none; } @media (prefers-reduced-transparency: reduce) { [data-page="capture"] [data-capture-device-sheet][open] { background: var(--color-base-100); -webkit-backdrop-filter: none; backdrop-filter: none; } } @media (min-width: 48rem) { [data-page="capture"] [data-capture-top-chrome] { display: flex; } [data-page="capture"] [data-capture-top-chrome] [data-capture-context-sheet-toggle] { display: none; } }
/*
 * Context readiness owns the empty plane, in both directions. Unlayered and
 * terminal: it must follow the generic empty-state geometry.
 */
/* Keep the selected workbench mode legible across the canonical tab variants. */
[data-session-mode-tabs] > .bao-session-mode-tab[aria-selected="true"] {
  --tab-bg: var(--color-primary);
  --tab-color: var(--color-primary-content);
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-content);
}



/* Selected capture medium remains visible over both idle and live preview materials. */
/* Same selected-segment paint as the editor's 2D/3D control (tokens-layout-editor-command-tablet-fit). */
html [data-page="capture"] .bao-preview-region[data-preview-state] [data-capture-preview-overlay] .btn.bao-capture-media-mode-option:has(input:checked) {
  color: var(--color-primary-content);
  background-color: var(--color-primary);
  border-color: transparent;
  box-shadow: var(--shadow-sm);
}

[data-capture-preview-overlay] .bao-capture-media-mode-option:has(input:disabled):not(:has(input:checked)) {
  color: var(--text-secondary, var(--color-base-content));
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
  opacity: var(--opacity-muted, 0.55);
}

[data-capture-preview-overlay] .bao-capture-media-mode-option:has(input:focus-visible) {
  outline: var(--focus-ring-width, 2px) solid var(--color-primary);
  outline-offset: var(--focus-ring-offset, 2px);
}
[data-page="capture"] [data-capture-preview-overlay] [data-capture-depth-status] { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
[data-page="capture"] [data-capture-preview-overlay] [data-capture-preview-actions] { display: flex; inline-size: max-content; max-inline-size: 100%; flex-wrap: nowrap; }
[data-page="capture"] .bao-preview-region:not([data-preview-state="live"]) > [data-capture-preview-overlay] { align-self: center; inline-size: max-content; max-inline-size: calc(100% - 2 * var(--space-section, 0.75rem)); margin-inline: auto; }
[data-page="capture"] .bao-preview-region[data-preview-state="live"] > [data-capture-preview-overlay] { inset-inline: auto; left: 50%; inline-size: max-content; max-inline-size: calc(100% - 2 * var(--space-section, 0.75rem)); transform: translateX(-50%); }

}
@layer bao-tokens {

/* Ownership note: the tablet acquire sizing facts for the capture workbench primary (flex, height, overflow) are owned by tokens-layout-page-shells-capture-responsive under the single-owner selector law; this module deliberately declares none. */

}
@layer bao-tokens {

/* ============================================================
   Focus visibility — 2px outline per WCAG 2.2 SC 2.4.11
   ============================================================ */

/* Inputs wrapped by a daisyUI label control (.input/.select) announce focus
   on the WRAPPER via :focus-within — outlining the inner element too paints
   a double ring (one owner per reactive state; focus stays visible). */
.btn:focus-visible,
a:focus-visible,
button:focus-visible,
input:focus-visible:not(.input input, .select select),
textarea:focus-visible:not(.textarea textarea),
select:focus-visible:not(.select select),
.bao-touch-target:focus-visible,
.bao-toolbar-action:focus-visible,
.bao-settings-tab:focus-visible,
.tab:focus-visible,
.bao-nav-item:focus-visible,
[tabindex]:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, var(--accent-primary));
  outline-offset: var(--focus-ring-offset, 2px);
}

.input input:focus-visible,
.select select:focus-visible,
.textarea textarea:focus-visible {
  outline: none;
}

:focus:not(:focus-visible) {
  outline: none;
}

/*
 * Programmatic focus targets (skip link destination, htmx history element).
 * `outline-none` on the host keeps mouse focus clean, but keyboard focus
 * landing on #app-main itself must stay visible (WCAG 2.4.7) — the inset
 * ring reads as the pane boundary without fighting the scrolled content.
 */
main#app-main:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, var(--accent-primary));
  outline-offset: var(--focus-ring-offset-tight, -2px);
}

/* ============================================================
   HTMX indicator + swap motion (CSP-safe; no runtime style inject)
   Aligned with the happydumpling app-shell contract.
   ============================================================ */

.htmx-indicator {
  opacity: 0;
  visibility: hidden;
}

:is(.htmx-request, [data-shell-loading]) .htmx-indicator,
:is(.htmx-request, [data-shell-loading]).htmx-indicator {
  opacity: 1;
  visibility: visible;
  transition-property: opacity;
  transition-timing-function: var(--easing-entrance);
  transition-duration: var(--duration-normal);
}

/* Form controls inherit readable base-content (theme-safe). */


/* Card border polish — data panels lift without nested glass.
   design.pdf §7.2: hover/focus color transitions run in the 120–180ms band
   (duration-fast = 120ms), never the 200ms menu band. */
.card-border.bao-data-panel,
.bao-data-panel.card-border {
  transition-property: border-color, box-shadow;
  transition-timing-function: var(--easing-standard);
  transition-duration: var(--duration-fast);
}
@media (hover: hover) and (pointer: fine) {
  .card-border.bao-data-panel:hover,
  .bao-data-panel.card-border:hover {
    border-color: color-mix(in oklab, var(--text-primary) 18%, var(--border-base));
    box-shadow: 0 1px 0 0 color-mix(in oklab, var(--border-base) 40%, transparent);
  }
}

/* Named main content for view transitions (SPA-like HTMX swaps). */
#app-main {
  view-transition-name: main-content;
}

/* ============================================================
   Skeleton shimmer a11y overrides (phone densify + reduced motion)
   Base declaration + bao-skeleton-sweep keyframes live in
   tokens-glass-runtime-interactions.ts (single owner — DRY).
   ============================================================ */

/* Phone densify: freeze shimmer — infinite anim + HTMX load loops = vibration. */
@media (max-width: 47.99rem) {
  .skeleton-shimmer {
    background-image: none;
  }
}

/*
 * Entrance motion is transform-only. Opacity-from-0 stuck on WebView 101
 * (BlueStacks Chromium, tile_manager pressure) painted pure-white voids
 * over real SSR case rows while the header still painted. Never gate
 * readability on an animation that may not complete.
 */
@keyframes slide-up-fade {
  from {
    transform: translateY(var(--entrance-offset));
  }
  to {
    transform: translateY(0);
  }
}

/* Standardized entrance motion.
   Desktop main-nav uses View Transitions (hx-swap with VT modifier) —
   do NOT also run page-content entrances on those swaps (double refresh
   flash). Toasts and in-page .htmx-added fragments still animate. */
@media (prefers-reduced-motion: no-preference) {
  #toast-region > * {
    animation: slide-up-fade var(--duration-normal) var(--easing-entrance);
  }
  /* Desktop only — on phone HTMX re-adds nodes every poll and re-animates = vibrate.
   * Context strips remount on every context-changed — never entrance-animate them. */
  @media (min-width: 48rem) {
    .htmx-added:not([data-chat-context]):not([data-capture-context-strip]):not(.bao-context-card):not(.bao-shell-context-chip) {
      animation: slide-up-fade var(--duration-normal) var(--easing-entrance) both;
    }
  }
  [data-chat-context].htmx-added,
  [data-chat-context] .htmx-added,
  [data-capture-context-strip].htmx-added,
  [data-capture-context-strip] .htmx-added,
  .bao-context-card.htmx-added,
  .bao-shell-context-chip.htmx-added {
    animation: none;
    transform: none;
  }
  /* design.pdf §7.2: button color/edge feedback is hover+press — 120ms
     (duration-fast) sits at the press ceiling and hover floor. */
  .btn {
    transition-property: color, background-color, border-color, box-shadow;
    transition-timing-function: var(--easing-standard);
    transition-duration: var(--duration-fast);
  }
  /* View transitions thrash WebView 101 compositing; desktop only. */
  @media (min-width: 48rem) {
    ::view-transition-old(main-content) {
      animation: fade-out-content var(--duration-normal) var(--easing-exit) both;
    }
    ::view-transition-new(main-content) {
      animation: slide-up-fade var(--duration-normal) var(--easing-entrance) var(--view-transition-new-delay) both;
    }
  }
}
@media (max-width: 47.99rem) {
  #app-main {
    view-transition-name: none;
  }
  
}
.bao-toast-exit {
  opacity: 0;
  transition-property: opacity;
  transition-timing-function: var(--easing-exit);
  transition-duration: var(--duration-slow);
}

@keyframes fade-out-content {
  to { opacity: 0; }
}

/* Empty / error surfaces: solid panel, generous padding, no sparse float.
 *
 * The descendant selectors deliberately exclude two classes of element that
 * carry [role="status"]/[role="alert"] without being empty-state *content*:
 *
 *   :not([data-state])    — an element that declares its own state contract
 *                           owns its own presentation. Without this guard the
 *                           slim inline case-journey strip
 *                           ([data-case-journey][data-state="empty"]
 *                           [role="status"]) matched as a descendant of its own
 *                           empty wrapper and was inflated from a one-line flex
 *                           row into a 192px dashed column, overriding
 *                           [data-case-journey] purely on source order.
 *   :not(.htmx-indicator) — request indicators are transient chrome, not
 *                           content. loading-xs spinners were being given
 *                           min-block-size:12rem, so every indicator reserved
 *                           192px of layout even while at opacity 0.
 *   :not([data-editor-canvas-empty])
 *                         — the annotation stage's empty overlay is full-bleed
 *                           (absolute inset-0 over the canvas), not a card. It
 *                           was taking the panel's rounded corner while the
 *                           stage beneath stayed square, so each corner showed
 *                           the square edge through the curve as a notch.
 *
 * The two guards below stop that list growing an entry per indicator. Both
 * state the same invariant: a COSMETIC rule must never resurrect an element
 * something else deliberately hid.
 *
 *   :not([hidden])        — the runtime hid it by attribute. This selector is
 *                           ancestor-qualified, so validate-hidden-attribute-
 *                           authority's "conditional rules may outrank
 *                           [hidden]" carve-out let it win: the tablet case
 *                           detail pane's [data-case-detail-loading] span ships
 *                           `hidden`, and was still painted as a 192px dashed
 *                           panel with a lone spinner in it.
 *   :not([data-transient-chrome])
 *                         — chrome its own component contract hides in CSS
 *                           rather than by attribute, where an attribute would
 *                           break the states that must show it. The cases
 *                           pull-to-refresh indicator is display:none off
 *                           touch, but this rule outranked that (0,6,0 beats
 *                           0,1,0) and reserved 192px above every desktop case
 *                           list.
 *
 * Genuine empty panels (a [role="status"] child inside an empty host, or an
 * explicit .bao-empty-panel) still receive the full treatment.
 *
 *   :not(:is([data-state]:not([data-state="empty"]) *))
 *                         — the nearest declared state owns the chrome. Every
 *                           exception above was one surface discovering that a
 *                           descendant combinator crosses state boundaries, and
 *                           answering with another attribute to opt out; this
 *                           says the thing they were each working around.
 *                           Measured on the dashboard: the widget card still
 *                           read data-state="empty" from its initial render
 *                           while its body and fragment had both swapped to
 *                           "success", so every widget's status chip — a
 *                           62px-wide "Active" pill — was painted as a
 *                           192px dashed empty panel. 22 elements app-wide, on
 *                           surfaces that had loaded their data correctly.
 *                           A nested success subtree is not empty, whatever an
 *                           ancestor still claims.
 */
[data-page] [data-state="empty"] [role="status"]:not([data-state]):not([hidden]):not(.htmx-indicator):not([data-editor-canvas-empty]):not([data-transient-chrome]):not(:is([data-state]:not([data-state="empty"]) *)),
[data-page] [data-state="error"] [role="alert"]:not([data-state]):not([hidden]):not(.htmx-indicator):not([data-editor-canvas-empty]):not([data-transient-chrome]):not(:is([data-state]:not([data-state="error"]) *)),
.bao-empty-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-inline);
  text-align: center;
  min-block-size: 12rem;
  padding-block: var(--space-page);
  padding-inline: var(--space-section);
  border: 1px dashed color-mix(in oklab, var(--border-base) 90%, transparent);
  border-radius: var(--radius-box, 1rem);
  background: color-mix(in oklab, var(--bg-panel) 88%, var(--bg-surface));
}

/* daisyUI paints the field focus ring in base-content (a black 2px ring on
   light themes). HIG keyboard focus is the accent colour on every control,
   so the field wrappers take the shared focus-ring colour. */
.input:focus,
.input:focus-within,
.select:focus,
.select:focus-within,
.textarea:focus,
.textarea:focus-within {
  --input-color: var(--focus-ring-color, var(--accent-primary));
}

/* ============================================================
   Apple HIG accessibility preference modes
   ============================================================
   These media queries are emitted unconditionally so the system
   respects accessibility settings automatically. They are NOT
   opt-in. */

/* Reduced Transparency: solid chrome surface, no backdrop blur.
   WCAG/governance: glass loses to reduced-transparency prefs.
   Also honors product override data-reduced-transparency on html. */
@media (prefers-reduced-transparency: reduce) {
  .bao-glass-control {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
    border-color: var(--color-base-300);
    box-shadow: none;
    background: var(--bg-panel, var(--color-base-100));
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .dropdown-content,
  .bao-dropdown-panel {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /*
   * The shell topbar is painted by ID-specificity rules —
   * #app-header[data-shell-topbar] at (1,1,0) and
   * #app-header.navbar.bao-surface-glass-regular at (1,2,0) — which outrank
   * every class selector above, so the header kept its blur and saturation
   * under both OS preferences while the app's own data-attribute toggles
   * flattened it correctly. Honouring the settings switch but ignoring the
   * operating system is the defect; these selectors match that specificity
   * rather than reaching for !important.
   */
  #app-header[data-shell-topbar],
  #app-header.navbar.bao-surface-glass-regular,
  #app-header.navbar.bao-surface-glass-regular.bao-glass-small {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-clear-dim,
  .bao-surface-glass-strong,
  .bao-surface-glass-modal,
  .bao-surface-glass-modal.bao-surface-glass-lens {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .bao-surface-glass-regular.bao-glass-large,
  .bao-surface-glass-regular.bao-glass-small,
  .bao-surface-glass-strong.bao-glass-large,
  .bao-surface-glass-strong.bao-glass-small,
  .bao-surface-glass-clear.bao-glass-large,
  .bao-surface-glass-clear.bao-glass-small {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .bao-surface-glass-lens {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .bao-surface-glass-regular .btn-soft.bao-toolbar-action,
  .bao-surface-glass-regular .btn-soft:not(.btn-primary):not(.btn-error),
  .bao-surface-glass-clear .btn-soft.bao-toolbar-action {
    background-color: var(--color-base-100, var(--bg-panel));
    border-color: var(--color-base-300);
    box-shadow: none;
  }
  .bao-surface-glass-regular::before,
  .bao-surface-glass-regular::after,
  .bao-surface-glass-clear::before,
  .bao-surface-glass-clear::after,
  .bao-surface-glass-strong::before,
  .bao-surface-glass-strong::after,
  .bao-surface-glass-modal::before,
  .bao-surface-glass-modal::after {
    display: none;
  }
  /* Raw-blur surfaces outside the variant classes (GLASS-06): the sticky
     journey band and the filter-sheet scrim must solidify too. */
  .bao-filter-sheet-backdrop,
  [data-case-journey-host] {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Disabled, loading and open-popover glass are re-declared at (0,2,0) in
     tokens-glass-base-reactive and tokens-glass-control-chrome — the disabled
     rule drops to an 8px blur, the loading rule RAISES it to the regular blur,
     and an open dropdown panel paints the 32px nav blur. All three outrank the
     (0,1,0) suppressions above, so an operator who asked the OS to reduce
     transparency kept a live backdrop-filter on every disabled control, every
     surface mid-fetch, and every menu they opened. A media query adds no
     specificity, so the suppression has to be written at the same reach the
     rule it is answering. */
  .bao-surface-glass-regular:disabled,
  .bao-surface-glass-regular[aria-disabled="true"],
  .bao-surface-glass-clear:disabled,
  .bao-surface-glass-clear[aria-disabled="true"],
  .bao-surface-glass-strong:disabled,
  .bao-surface-glass-strong[aria-disabled="true"],
  .bao-surface-glass-modal:disabled,
  .bao-surface-glass-modal[aria-disabled="true"],
  .bao-surface-glass-regular[data-state="loading"],
  .bao-surface-glass-clear[data-state="loading"],
  .bao-surface-glass-strong[data-state="loading"],
  .bao-surface-glass-modal[data-state="loading"],
  .bao-dropdown-panel:popover-open,
  .bao-dropdown-panel[data-popover-open="true"],
  .bao-shell-dropdown {
    background-color: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Reactive hover/focus is (0,2,0) and the suppression selectors above are
     (0,1,0); a media query adds no specificity, so an OS-level
     reduce-transparency user got the translucent material back on hover and
     on keyboard focus — transparency without blur, over live content, on the
     exact surface they asked to be made solid. */
  .bao-surface-glass-reactive:hover,
  .bao-surface-glass-reactive:focus-within {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* The RESTING state needed the same (0,2,0) reach and never got it. Measured
     on the deployed build under Increased Contrast: every other glass surface
     computed backdrop-filter:none while
     .bao-surface-glass-regular.bao-surface-glass-reactive still computed a live
     14px blur with saturation — the one surface that kept live material for a
     user who asked for maximum contrast, and it only solidified once they
     hovered it. Repeated class buys the specificity with no id selector and no
     cascade override. */
  .bao-surface-glass-reactive.bao-surface-glass-reactive {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
html[data-reduced-transparency="true"] .bao-surface-glass-regular,
html[data-reduced-transparency="true"] .bao-surface-glass-clear,
html[data-reduced-transparency="true"] .bao-surface-glass-clear-dim,
html[data-reduced-transparency="true"] .bao-surface-glass-strong,
html[data-reduced-transparency="true"] .bao-surface-glass-modal,
html[data-reduced-transparency="true"] .bao-surface-glass-lens {
  background: var(--bg-surface);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
html[data-reduced-transparency="true"] .bao-surface-glass-regular::before,
html[data-reduced-transparency="true"] .bao-surface-glass-regular::after,
html[data-reduced-transparency="true"] .bao-surface-glass-clear::before,
html[data-reduced-transparency="true"] .bao-surface-glass-clear::after,
html[data-reduced-transparency="true"] .bao-surface-glass-strong::before,
html[data-reduced-transparency="true"] .bao-surface-glass-strong::after,
html[data-reduced-transparency="true"] .bao-surface-glass-modal::before,
html[data-reduced-transparency="true"] .bao-surface-glass-modal::after {
  display: none;
}
html[data-reduced-transparency="true"] .bao-filter-sheet-backdrop,
html[data-reduced-transparency="true"] [data-case-journey-host] {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Shell density: compact clinical chrome (HIG 44px min chrome). */
html[data-density="compact"] {
  --space-page: 0.75rem;
  --space-section: 0.75rem;
  --space-inline: 0.5rem;
  --chrome-min-height: 2.5rem;
}
html[data-density="comfortable"] {
  --space-page: 1.25rem;
  --space-section: 1rem;
  --space-inline: 0.75rem;
  --chrome-min-height: 2.75rem;
}
html[data-density="spacious"] {
  --space-page: 1.5rem;
  --space-section: 1.25rem;
  --space-inline: 1rem;
  --chrome-min-height: 3rem;
}

/* Increased Contrast: drop the blur and tint entirely; rely on a solid
   border for the panel edge so the pane reads against any background.
   Apple HIG: "Increased contrast makes elements predominantly black or
   white and highlights them with a contrasting border".
   Also honors product override data-high-contrast on html so the
   Settings → Accessibility toggle can force the same behavior. */
@media (prefers-contrast: more) {
  .dropdown-content,
  .bao-dropdown-panel,
  .bao-glass-control {
    border-color: var(--liquid-border-contrast);
  }
  /*
   * The shell topbar is painted by ID-specificity rules —
   * #app-header[data-shell-topbar] at (1,1,0) and
   * #app-header.navbar.bao-surface-glass-regular at (1,2,0) — which outrank
   * every class selector above, so the header kept its blur and saturation
   * under both OS preferences while the app's own data-attribute toggles
   * flattened it correctly. Honouring the settings switch but ignoring the
   * operating system is the defect; these selectors match that specificity
   * rather than reaching for !important.
   */
  #app-header[data-shell-topbar],
  #app-header.navbar.bao-surface-glass-regular,
  #app-header.navbar.bao-surface-glass-regular.bao-glass-small {
    background: var(--bg-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-clear-dim,
  .bao-surface-glass-strong,
  .bao-surface-glass-modal,
  .bao-surface-glass-modal.bao-surface-glass-lens,
  .bao-surface-glass-lens,
  .bao-surface-glass-reactive,
  .bao-glass-large,
  .bao-glass-small,
  .bao-glass-fill,
  .bao-glass-control,
  .dropdown-content,
  .bao-dropdown-panel,
  /* Mobile dock needs ID-level reach: tokens-layout-dock-base is emitted
     after this module, so a plain .bao-mobile-dock.dock ties on specificity
     and loses — the dock kept its blur under Increased Contrast.
     Conversation dock (form shell, already solid) gets the contrast border. */
  #mobile-dock.dock.bao-mobile-dock,
  nav#mobile-dock[data-mobile-dock],
  .bao-conversation-dock,
  footer.bao-conversation-dock {
    background: var(--bg-panel);
    border-width: 2px;
    /* The comment above promises the blur is dropped "entirely". Only the
       lens/strong follow-up rule used to do that, so regular, clear and modal
       kept a live 20-36px backdrop-filter behind an opaque fill — invisible
       output at full GPU cost. */
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Density modifiers are (0,2,0) too, and the same reasoning applies to
     them — but only the reduced-transparency block above ever got these
     pairs. Measured live under Increased Contrast with glass on:
     #app-sidebar kept blur(32px) because .bao-surface-glass-regular
     .bao-glass-large (0,2,0) outranked the (0,1,0) suppression, while the
     identical sidebar went solid correctly under reduced transparency. The
     product toggle path was never affected: html[data-high-contrast="true"]
     .bao-glass-large is (0,2,1) and already won, which is exactly why this
     survived every check that used the in-app switch. */
  .bao-surface-glass-regular.bao-glass-large,
  .bao-surface-glass-regular.bao-glass-small,
  .bao-surface-glass-strong.bao-glass-large,
  .bao-surface-glass-strong.bao-glass-small,
  .bao-surface-glass-clear.bao-glass-large,
  .bao-surface-glass-clear.bao-glass-small {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Reactive hover/focus is (0,2,0); the suppression selectors above are
     (0,1,0) and a media query adds no specificity, so without these the
     material came back translucent the moment a pointer or focus entered. */
  .bao-surface-glass-reactive:hover,
  .bao-surface-glass-reactive:focus-within {
    background: var(--bg-panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .bao-surface-glass-regular::before,
  .bao-surface-glass-regular::after,
  .bao-surface-glass-clear::before,
  .bao-surface-glass-clear::after,
  .bao-surface-glass-strong::before,
  .bao-surface-glass-strong::after,
  .bao-surface-glass-modal::before,
  .bao-surface-glass-modal::after {
    display: none;
  }
  /* GLASS-06: raw-blur surfaces outside the variant classes lose blur. */
  .bao-filter-sheet-backdrop,
  [data-case-journey-host] {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
html[data-high-contrast="true"] .bao-surface-glass-regular,
html[data-high-contrast="true"] .bao-surface-glass-clear,
html[data-high-contrast="true"] .bao-surface-glass-clear-dim,
html[data-high-contrast="true"] .bao-surface-glass-strong,
html[data-high-contrast="true"] .bao-surface-glass-modal,
html[data-high-contrast="true"] .bao-surface-glass-lens,
html[data-high-contrast="true"] .bao-surface-glass-reactive,
html[data-high-contrast="true"] .bao-glass-large,
html[data-high-contrast="true"] .bao-glass-small,
html[data-high-contrast="true"] .bao-glass-fill,
html[data-high-contrast="true"] .bao-glass-control {
  min-height: var(--touch-target-min, 2.75rem);
  background: var(--bg-panel);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* Increase Contrast (HIG): the plate goes opaque and its edge goes to the
     full-contrast hairline. It stays a hairline — a 2px outline on every
     toolbar, palette, sheet and control read as a wireframe (measured
     2026-08-23 on an operator with the setting persisted: 2px rgba(24,24,27,.65)
     on all four editor plates), and the edge is not what carries contrast;
     the opaque fill against the stage is. */
  border-color: var(--liquid-border-contrast);
  border-width: 1px;
}
html[data-high-contrast="true"] .bao-surface-glass-regular::before,
html[data-high-contrast="true"] .bao-surface-glass-regular::after,
html[data-high-contrast="true"] .bao-surface-glass-clear::before,
html[data-high-contrast="true"] .bao-surface-glass-clear::after,
html[data-high-contrast="true"] .bao-surface-glass-strong::before,
html[data-high-contrast="true"] .bao-surface-glass-strong::after,
html[data-high-contrast="true"] .bao-surface-glass-modal::before,
html[data-high-contrast="true"] .bao-surface-glass-modal::after {
  display: none;
}
html[data-high-contrast="true"] .bao-filter-sheet-backdrop,
html[data-high-contrast="true"] [data-case-journey-host] {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-page] [data-state-content]:not([hidden]),
  #toast-region > *,
  .htmx-added {
    animation: none;
  }
  .bao-pulse,
  .bao-pulse-scale,
  .bao-pulse-alpha {
    transform: none;
  }
  ::view-transition-old(main-content),
  ::view-transition-new(main-content) {
    animation: none;
  }
  :is(.htmx-request, [data-shell-loading]) .htmx-indicator,
  :is(.htmx-request, [data-shell-loading]).htmx-indicator {
    transition-duration: var(--duration-instant, 0ms);
    transition-property: none;
  }
}

/* Reduced Motion: keep the global 1ms clamp that the existing media query
   already applies; nothing additional is needed here because the glass
   material itself has no continuous animation. The pointer-tracking
   script also disables itself when this media query matches. */
@media (prefers-reduced-motion: reduce) {
  *,
  ::before,
  ::after {
    animation-duration: var(--reduced-motion-min-duration, 0.001ms);
    animation-iteration-count: 1;
    animation-delay: var(--duration-instant);
    transition-delay: var(--duration-instant);
    transition-duration: var(--reduced-motion-min-duration, 0.001ms);
    scroll-behavior: auto;
  }
}

/* Accessibility preference modes must win over the topbar's ID-specificity
 * chrome above: drop the (now pointless) blur behind the solid fill and give
 * increased contrast a stronger bottom edge for every accessibility preference signal. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  #app-header.navbar,
  #app-header[data-shell-topbar] {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
html[data-reduced-transparency="true"] #app-header.navbar,
html[data-reduced-transparency="true"] #app-header[data-shell-topbar],
html[data-high-contrast="true"] #app-header.navbar,
html[data-high-contrast="true"] #app-header[data-shell-topbar] {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
@media (prefers-contrast: more) {
  #app-header.navbar,
  #app-header[data-shell-topbar] {
    border-bottom-width: 2px;
  }
}
html[data-high-contrast="true"] #app-header.navbar,
html[data-high-contrast="true"] #app-header[data-shell-topbar] {
  border-bottom-width: 2px;
}


/* WWDC26 Golden Gate / iOS 26.1: Tinted Liquid Glass raises opacity
   and darkens the edge so chrome stays readable. Reduced transparency
   and increased contrast still win — they solidify the material. */
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-regular,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-clear,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-clear-dim,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-strong,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-lens,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-reactive,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-glass-large,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-glass-small {
  /* background-COLOR, never the shorthand. Tinting is a fill change; the
     shorthand also resets background-image, and that layer carries two
     required paints: the 35% dark dimming plate Apple HIG Materials asks for
     behind clear Liquid Glass over bright content, and the glass grain.
     Tinted is the default appearance, so the shorthand silently deleted both
     on every glass surface in the app. Degrade paths (reduced transparency,
     increased contrast, forced colors) DO use the shorthand on purpose —
     there the layered paints must go with the translucency.

     The alpha is a token, not a literal, and the rule below is why: as a bare
     78% it silently outranked --liquid-fill-blocking on the one material that
     must occlude, and nothing in the token family recorded that a tinted fill
     existed at all. A fill that overrides other fills belongs beside them. */
  background-color: color-mix(in oklab, var(--bg-surface) calc(var(--liquid-fill-tinted) * 100%), transparent);
  border-color: color-mix(in oklab, var(--color-base-content) 22%, transparent);
}

/* The blocking material tints from its OWN fill. Tinted raises opacity —
   true of every surface above, whose fills sit below 78%, and false of the
   modal alone: sweeping it into that list dropped dialogs from
   --liquid-fill-blocking (0.88 light / 0.92 dark) to 0.78, under the 0.82
   floor set for enterprise dialogs, so the page read through every dialog
   wherever backdrop-filter was unavailable. Tinted is the default appearance
   and glass-off carries data-high-contrast, so the only configuration showing
   the intended fill was the one excluded here. Keeps the tint hue
   (--bg-surface) and the tinted edge; changes the alpha alone, back to the
   token that states what a blocking material is for. */
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-surface-glass-modal {
  background-color: color-mix(in oklab, var(--bg-surface) calc(var(--liquid-fill-blocking) * 100%), transparent);
  border-color: color-mix(in oklab, var(--color-base-content) 22%, transparent);
}
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-glass-control {
  min-height: var(--touch-target-min, 2.75rem);
  min-width: var(--touch-target-min, 2.75rem);
  background-color: color-mix(in oklab, var(--bg-surface) calc(var(--liquid-fill-tinted) * 100%), transparent);
  border-color: color-mix(in oklab, var(--color-base-content) 22%, transparent);
}
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) .bao-shell-sidebar,
html[data-liquid-glass="tinted"]:not([data-reduced-transparency="true"]):not([data-high-contrast="true"]) #app-sidebar.bao-shell-sidebar {
  background-color: color-mix(in oklab, var(--bg-surface) calc(var(--liquid-fill-tinted) * 100%), transparent);
}


/* ============================================================
   Forced colors — respect user-selected system colors
   ============================================================ */

@media (forced-colors: active) {
  .bao-surface-glass-regular,
  .bao-surface-glass-clear,
  .bao-surface-glass-clear-dim,
  .bao-surface-glass-strong,
  .bao-surface-glass-modal,
  .bao-surface-glass-modal.bao-surface-glass-lens,
  .bao-surface-glass-solid,
  .bao-surface-glass-lens,
  #app-header.navbar,
  #app-header[data-shell-topbar],
  #mobile-dock.dock.bao-mobile-dock,
  nav#mobile-dock[data-mobile-dock],
  .bao-conversation-dock,
  footer.bao-conversation-dock,
  .bao-dropdown-panel {
    color: CanvasText;
    background: Canvas;
    border: 1px solid ButtonText;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  /* Density modifiers need equal selector reach because media queries add no
     specificity and backdrop-filter is not force-adjusted by the browser. */
  .bao-surface-glass-regular.bao-glass-large,
  .bao-surface-glass-regular.bao-glass-small,
  .bao-surface-glass-strong.bao-glass-large,
  .bao-surface-glass-strong.bao-glass-small,
  .bao-surface-glass-clear.bao-glass-large,
  .bao-surface-glass-clear.bao-glass-small {
    color: CanvasText;
    background: Canvas;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .bao-glass-control {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
    color: CanvasText;
    background: Canvas;
    border: 1px solid ButtonText;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .bao-surface-glass-reactive.bao-surface-glass-reactive,
  .bao-surface-glass-reactive:hover,
  .bao-surface-glass-reactive:focus-within {
    color: CanvasText;
    background: Canvas;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  /* Specular ring and inner illumination are decorative gradient layers. */
  .bao-surface-glass-regular::before,
  .bao-surface-glass-regular::after,
  .bao-surface-glass-clear::before,
  .bao-surface-glass-clear::after,
  .bao-surface-glass-strong::before,
  .bao-surface-glass-strong::after,
  .bao-surface-glass-modal::before,
  .bao-surface-glass-modal::after {
    display: none;
  }

  .bao-filter-sheet-backdrop,
  [data-case-journey-host] {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .skeleton-shimmer {
    animation: none;
    background-image: none;
  }

  .btn:focus-visible,
  a:focus-visible,
  button:focus-visible,
  input:focus-visible,
  textarea:focus-visible,
  select:focus-visible,
  [tabindex]:focus-visible {
    outline: var(--focus-ring-width, 2px) solid Highlight;
    outline-offset: var(--focus-ring-offset, 2px);
  }

  .bao-nav-item.menu-active,
  .bao-nav-item[aria-current="page"] {
    background: SelectedItem;
    color: SelectedItemText;
    box-shadow: none;
    border: 1px solid SelectedItemText;
  }
}

/*
 * Dynamic Type reach (HIG Text & Custom Settings / accessibility zoom).
 */

@media (max-width: 63.99rem) {
  [data-page="login"] [data-login-pin-pad] {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    padding-block-end: var(--space-micro, 0.25rem);
  }
  [data-page="login"] [data-login-pin-pad] button {
    min-width: var(--touch-target-min, 2.75rem);
  }
  /* A <fieldset> that becomes a scroll container collapses to a zero block
     size in Chromium — the rendered-legend box stops contributing, and the
     44px input inside is then clipped out of hit testing entirely. Measured
     on /login at 390 and 768: join height 0 against a 44px input, and
     elementFromPoint at the input centre returning the OUTER fieldset, so a
     pointer tap on the badge credential field timed out. Desktop was fine
     only because the rule stops at 63.99rem. The floor keeps the scroller
     (the reason this rule exists) while giving the box back the height its
     control already has. */
  [data-page="login"] fieldset.join {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    min-width: 0;
    min-block-size: var(--touch-target-min, 2.75rem);
  }
  [data-page="login"] fieldset.join .input {
    min-width: min(100%, 50vw);
  }
}

[data-camera-preview-placeholder] [data-capture-empty-cta],
[data-capture-case-required] [data-capture-empty-cta] {
  max-width: 100%;
  min-width: 0;
  white-space: normal;
  height: auto;
  min-height: var(--touch-target-min, 2.75rem);
}


/* ---- 4. Page-root pan: content blows past, the page reveals it. ---- */
/*
 * At 400% root font a DataTable's rem-padded columns demand more than the
 * viewport; the grid track stretches to min-content and every sibling
 * control rides past the edge (measured: headings and buttons at
 * right 1583px on a 1440px viewport across /specimens, /training, /debug,
 * /setup, /settings). densify-b clips the page root, which hides the
 * content but provides no way to reach it — the audit fails every rider.
 * The page root owns the scroll: one pan reveals the whole blown track.
 * min-width: 0 on the state-content column stops the grid itself from
 * stretching where the layout allows it.
 */
[data-page] > [data-state-content] {
  min-width: 0;
  max-width: 100%;
}
[data-page-type="collection"],
[data-page-type="form"],
[data-page-type="detail"],
[data-page-type="home"] {
  max-width: 100%;
  /*
   * clip, never auto: overflow-x:auto forces computed overflow-y:auto,
   * turning every page track into a second vertical scroller nested in
   * #app-main — wheel momentum then overruns the real content into a
   * blank void. Horizontal Dynamic-Type reach lives on the inner wide
   * widgets (table wrap, code, QR panes), each already its own x-scroller.
   */
  overflow-x: clip;
}


}
@layer bao-tokens {

/* ============================================================
   Pointer-gated hover utilities (design.pdf §6: hover is an
   enhancement for fine pointers only — touch never traps a
   stuck hover state). Replace raw `hover:` utilities with these
   canonical classes so every hover effect is media-gated.
   ============================================================ */

@media (hover: hover) and (pointer: fine) {
  .bao-hover-row:hover {
    background-color: color-mix(in oklab, var(--color-base-content) 5%, transparent);
  }
  .bao-hover-soft:hover {
    background-color: var(--bg-surface-hover);
  }
  .bao-hover-soft-veil:hover {
    background-color: color-mix(in oklab, var(--color-base-200) 80%, transparent);
  }
  .bao-hover-strong:hover {
    background-color: var(--color-base-300);
  }
  .bao-hover-panel-border:hover {
    border-color: color-mix(in oklab, var(--color-primary) 40%, transparent);
  }
  .bao-hover-text-primary:hover {
    color: var(--color-primary);
  }
  .bao-hover-underline:hover {
    text-decoration-line: underline;
  }
  .bao-hover-panel:hover {
    background-color: var(--color-base-100);
  }
  .bao-hover-border-s-primary:hover {
    border-inline-start-color: var(--color-primary);
  }
  .bao-hover-overlay-btn:hover {
    background-color: var(--overlay-btn-secondary-bg);
  }
}

/* ============================================================
   Scrim SSOT — one overlay-backdrop token owns every blocking
   scrim. daisyUI .modal-backdrop defaults to a raw black tint;
   align it with the canonical overlay token (design.pdf §5.8).
   ============================================================ */

.modal .modal-backdrop,
.modal-backdrop {
  background: var(--overlay-backdrop);
}

/* design.pdf §6: disabled keeps the label readable — drop elevation/chroma
   instead of crushing text via daisyUI opacity (design.pdf §2.10). */
.btn:disabled,
.btn-disabled {
  opacity: 1;
  background-color: var(--color-base-200, var(--bg-panel));
  border-color: var(--color-base-300);
  /* Readable chroma drop via solid mix against panel — never element opacity
   * (design.pdf §2.10 / §6; gate S113 requires ≥45% content in the mix). */
  color: color-mix(in oklab, var(--color-base-content) 55%, var(--color-base-200, var(--bg-panel)));
  pointer-events: none;
  box-shadow: none;
}


}
@layer bao-tokens {

/* ═══════════════════════════════════════════════════════════════════
   FLUID TRANSITIONS — Apple Liquid Glass depth-aware animations
   ═══════════════════════════════════════════════════════════════════ */

/*
 * :active tap states compress the glass surface. The press timing is
 * stated on the bare surface states: the breakpoint-tiered scale rules
 * (tokens-glass-fluid-responsive) already scope the transform to
 * interactive elements, and a transition on a non-interactive surface's
 * :active state is inert — nothing changes its transform there.
 */
.bao-surface-glass-regular:active,
.bao-surface-glass-strong:active,
.bao-surface-glass-clear:active {
  transition: transform var(--duration-press) var(--easing-standard);
}

/*
 * Sidebar collapse/expand: smooth width transition.
 */
.bao-shell-sidebar {
  transition: width var(--duration-slow, 320ms) var(--easing-glass, cubic-bezier(0.32, 0.72, 0, 1));
}

/*
 * Card hover lift: subtle elevation increase on hover.
 * Gated to fine pointers only (S41/S75/S76) — touch devices use :active.
 */
@media (hover: hover) and (pointer: fine) {
  .card.bao-motion-transition-colors:hover {
    box-shadow: var(--shadow-md);
    transition: box-shadow var(--duration-normal, 200ms) var(--easing-standard);
  }

  /*
   * Table row hover: background highlight with smooth transition.
   */
  .table tbody tr {
    transition: background-color var(--duration-fast, 120ms) var(--easing-standard);
  }
  .table tbody tr:hover {
    background-color: var(--bg-surface-hover);
  }
}

/* Dock item transitions are owned by tokens-layout-dock-base. */

/* Reduced-motion behavior is owned by tokens-glass-fluid-reduce. */



/*
 * Capture page fixes: prevent tooltip host occlusion and shutter/session collision.
 */
/* Fix tooltip host occlusion on capture action buttons (Evidence/Reports) */
[data-capture-action-bar] .tooltip {
  pointer-events: none;
}
[data-capture-action-bar] .tooltip > .btn,
[data-capture-action-bar] .tooltip > a.btn {
  pointer-events: auto;
}
/* Ensure tooltip host doesn't extend beyond button bounds on tablet.
   Exclusive upper boundary: the repo band convention keeps adjacent min/max
   queries from both matching at exactly one width. */
@container shell (min-width: 48rem) and (max-width: 63.99rem) {
  [data-capture-action-bar] .tooltip.inline-flex {
    display: inline-block;
    width: auto;
    height: auto;
  }
  /* Fix: toolbar labels in btn-square must not extend button bounds.
   * Invisible-box recipe mirrors the shared sr-only utility, stated in
   * logical properties. */
  [data-capture-action-bar] .btn-square .bao-toolbar-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   RESPONSIVE BREAKPOINTS — mobile-first fluid layout
   ═══════════════════════════════════════════════════════════════════ */

/* Mobile: stack layouts, full-width components */
@container shell (max-width: 47.99rem) {
  .bao-dashboard-widget-dragging {
    transform: scale(0.95);
  }
}

/* Desktop: enhanced animations, wider layouts */
@container shell (min-width: 64rem) {
  @media (hover: hover) and (pointer: fine) {
    .bao-surface-glass-strong:where(button, a, [role="button"]):hover {
      transform: translateY(-2px);
      transition: transform var(--duration-normal, 200ms) var(--easing-standard);
    }
  }
}


}
@layer bao-tokens {

/* ═══════════════════════════════════════════════════════════════════
   RESPONSIVE — breakpoint-tiered glass :active spring scale
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 47.99rem) {/* Phone: reduce spring scale to avoid layout shift on small screens */
  .bao-surface-glass-regular:where(button, a, [role="button"]):active,
.bao-surface-glass-strong:where(button, a, [role="button"]):active {
    transform: scale(0.97);
  }
  /* Phone: dock items get larger touch targets */
  .bao-mobile-dock-item {
    min-height: var(--touch-target-min, 2.75rem);
    min-width: var(--touch-target-min, 2.75rem);
  }
}

@media (min-width: 48rem) and (max-width: 63.99rem) {/* Tablet: moderate spring scale */
  .bao-surface-glass-regular:where(button, a, [role="button"]):active,
.bao-surface-glass-strong:where(button, a, [role="button"]):active {
    transform: scale(0.98);
  }
}

@media (min-width: 64rem) {/* Desktop: subtle spring scale */
  .bao-surface-glass-regular:where(button, a, [role="button"]):active,
.bao-surface-glass-strong:where(button, a, [role="button"]):active {
    transform: scale(0.985);
  }
}

}
@layer bao-tokens {

/* ═══════════════════════════════════════════════════════════════════
   REDUCED MOTION — disable all fluid animations
   Specificity: scoped to each class + media query (no cascade force).
   ═══════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .bao-surface-glass-regular:where(button, a, [role="button"]):active,
  .bao-surface-glass-clear:where(button, a, [role="button"]):active {
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bao-dashboard-widget-dragging,
  .bao-surface-glass-strong:where(button, a, [role="button"]):hover,
  .bao-surface-glass-strong:where(button, a, [role="button"]):active {
    animation: none;
    transition: none;
    transform: scale(1);
  }
}

/* The in-app preference has the same motion contract as the OS setting. */
html[data-reduced-motion="true"] .bao-dashboard-widget-dragging,
html[data-reduced-motion="true"] .bao-surface-glass-regular:where(button, a, [role="button"]):active,
html[data-reduced-motion="true"] .bao-surface-glass-clear:where(button, a, [role="button"]):active,
html[data-reduced-motion="true"] .bao-surface-glass-strong:where(button, a, [role="button"]):hover,
html[data-reduced-motion="true"] .bao-surface-glass-strong:where(button, a, [role="button"]):active {
  animation: none;
  transition: none;
  transform: scale(1);
}

}
@layer bao-tokens {

/* Touch target minimum sizing (44x44px per Apple HIG) */
.min-h-touch-target {
  min-height: var(--touch-target-min, 2.75rem);
}

/*
 * Drag handles only when customize mode is on. Default browse must not
 * expose grab affordances on every metric card (Apple HIG: chrome vs content).
 */
[data-dashboard-layout-root]:not([data-dashboard-editing="true"]) [data-dashboard-drag-handle] {
  cursor: default;
  pointer-events: none;
}
/*
 * touch-action:none is required for the coarse-pointer drag path: without it
 * the browser claims the gesture for scrolling and pointermove never reaches
 * handleDragStart. Scoped to customize mode so browse-mode scrolling over a
 * widget is untouched.
 */
[data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-drag-handle] {
  cursor: grab;
  touch-action: none;
  transition: background-color var(--duration-fast, 120ms) var(--easing-standard);
}

@media (hover: hover) and (pointer: fine) {
  [data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-drag-handle]:hover {
    background-color: var(--bg-surface-hover);
    border-radius: var(--radius-sm, 0.375rem);
  }
}

/* The handle state owns its active cursor as well as its feedback paint. */
[data-dashboard-layout-root][data-dashboard-editing="true"] [data-dashboard-drag-handle]:active {
  cursor: grabbing;
  background-color: var(--bg-surface);
}/* Widget dragging state — geometry from setHostCssVars (--bao-drag-*). */
.bao-dashboard-widget-dragging,
[data-dashboard-widget][data-bao-dragging="true"] {
  position: fixed;
  left: var(--bao-drag-left, 0);
  top: var(--bao-drag-top, 0);
  width: var(--bao-drag-width, auto);
  transition: none;
  pointer-events: none;
}/* Drag placeholder — size from setHostCssVars (--bao-placeholder-*).
   The dashed primary outline lives here,
not on the element: the browser bundle
   used to append rounded-box,
border-2,
border-dashed,
border-primary/50 and
   bg-primary/5 to this class in app-shell-dashboard-drag-geometry.ts,
so half the
   placeholder's appearance was decided in TypeScript where no canon gate reads
   it. */
.bao-dashboard-widget-placeholder {
  width: var(--bao-placeholder-width, 100%);
  height: var(--bao-placeholder-height, 8rem);
  border-radius: var(--radius-box, 1rem);
  border: 2px dashed color-mix(in oklab, var(--color-primary) 50%, transparent);
  background-color: color-mix(in oklab, var(--color-primary) 5%, transparent);
  transition: box-shadow var(--duration-normal, 200ms) var(--easing-standard),
    opacity var(--duration-normal, 200ms) var(--easing-standard),
    transform var(--duration-normal, 200ms) var(--easing-standard);
}

/* ═══════════════════════════════════════════════════════════════════
   CAPTURE PAGE LAYOUT FIXES
   ═══════════════════════════════════════════════════════════════════ */

/* Fix shutter chrome z-index on tablet to avoid occluding session controls */
@media (min-width: 48rem) and (max-width: 63.99rem) {
  [data-capture-shutter-chrome] {
    z-index: var(--z-canvas-controls, 30);
  }

  [data-session-controls] {
    z-index: var(--z-canvas-controls, 30);
    position: relative;
  }
}

/* Ensure Evidence/Reports buttons are not occluded */
[data-capture-actions] .btn {
  position: relative;
  z-index: var(--z-canvas-actions, 40);
}

/* Header promotion stamps identity only; layout remains in the canonical owner. */
[data-dashboard-drag-handle] {
  position: relative;
  z-index: var(--z-content-overlay);
  min-height: var(--touch-target-min, 2.75rem);
  padding-block: var(--space-inline);
}

/* Compact and coarse-pointer chrome shares this owner with the desktop handle. */
@media (max-width: 47.99rem), (pointer: coarse) {
  [data-dashboard-drag-handle] {
    cursor: default;
    min-height: 0;
    padding-block: 0;
  }
}

}
@layer bao-tokens {

/* ═══════════════════════════════════════════════════════════════════
   VIEW TRANSITIONS — Apple HIG page navigation
   ═══════════════════════════════════════════════════════════════════ */

/*
 * Same-document transitions only (htmx transition:true swaps).
 * No @view-transition navigation opt-in: cross-document transitions
 * permanently freeze the post-login document in Chromium (see module doc).
 */

::view-transition-old(root) {
  animation: bao-view-transition-fade-out var(--duration-normal, 200ms) var(--easing-exit);
}

::view-transition-new(root) {
  animation: bao-view-transition-fade-in var(--duration-slow, 320ms) var(--easing-entrance);
}

@keyframes bao-view-transition-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes bao-view-transition-fade-in {
  from {
    opacity: 0;
    transform: scale(1.02);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/*
 * Shared element transitions for navigation items
 */
.bao-nav-item {
  view-transition-name: var(--vt-nav-item, none);
}

[data-route-id="dashboard"] {
  --vt-nav-item: nav-dashboard;
}

[data-route-id="cases"] {
  --vt-nav-item: nav-cases;
}

[data-route-id="capture"] {
  --vt-nav-item: nav-capture;
}

[data-route-id="editor"] {
  --vt-nav-item: nav-editor;
}

[data-route-id="models"] {
  --vt-nav-item: nav-models;
}::view-transition-group(nav-dashboard),
::view-transition-group(nav-cases),
::view-transition-group(nav-capture),
::view-transition-group(nav-editor),
::view-transition-group(nav-models) {
  animation-duration: var(--duration-normal, 200ms);
  animation-timing-function: var(--easing-standard);
}

/*
 * Page header transitions
 */
[data-primitive="PageHeader"] {
  view-transition-name: page-header;
}

::view-transition-group(page-header) {
  animation-duration: var(--duration-slow, 320ms);
  animation-timing-function: var(--easing-entrance);
}

/*
 * Dashboard widget transitions
 */
[data-dashboard-widget] {
  view-transition-name: var(--vt-widget, none);
}

[data-widget="cases"] {
  --vt-widget: widget-cases;
}

[data-widget="capture"] {
  --vt-widget: widget-capture;
}

[data-widget="models"] {
  --vt-widget: widget-models;
}

[data-widget="ai-analysis"] {
  --vt-widget: widget-ai;
}::view-transition-group(widget-cases),
::view-transition-group(widget-capture),
::view-transition-group(widget-models),
::view-transition-group(widget-ai) {
  animation-duration: var(--duration-slow, 320ms);
  animation-timing-function: var(--easing-entrance);
}

/*
 * Reduced motion: disable all view transitions
 */
@media (prefers-reduced-motion: reduce) {::view-transition-old(root),
::view-transition-new(root),
::view-transition-group(*) {
    animation-duration: var(--reduced-motion-min-duration);
  }
}

/*
 * Fallback for browsers without View Transitions API
 */
@supports not (view-transition-name: none) {
  [data-page] {
    animation: bao-page-fallback-enter var(--duration-slow, 320ms) var(--easing-entrance);
  }

  @keyframes bao-page-fallback-enter {
    from {
      opacity: 0;
      transform: translateY(var(--entrance-offset, 0.375rem));
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

/* Respect the product preference even when the OS preference is unchanged. */
html[data-reduced-motion="true"]::view-transition-old(*),
html[data-reduced-motion="true"]::view-transition-new(*),
html[data-reduced-motion="true"]::view-transition-group(*) {
  animation: none;
}

}
@layer bao-tokens {


/* ═══════════════════════════════════════════════════════════════════
   RUNTIME INTERACTIONS — JS-driven CSS custom properties + data attrs
   ═══════════════════════════════════════════════════════════════════ */

/* ─── Success toasts never block the surface beneath ───
   Center-top toasts can render over the compact tool dock the instant the
   pointer clicks there, and a parked pointer pauses auto-dismissal. Passive
   success notices pass pointer events straight through except their close
   control; interactive error toasts (retry affordances) keep capturing. */
#toast-region > .alert-success {
  pointer-events: none;
}

#toast-region > .alert-success :is(button, a[href], [role="button"]) {
  pointer-events: auto;
}

/* ─── Card 3D Tilt (driven by app-shell-card-interactions.ts) ───
   JS sets --card-tilt-x/--card-tilt-y via setHostCssVars and gates
   with data-bao-tilt (never style=). CSS owns the transform. */

/*
   The tilt carries its own perspective in the transform function below, so
   the standalone `perspective` and `transform-style` here bought nothing and
   cost the whole product its overlay positioning: each one establishes a
   containing block for fixed-position descendants, on every bordered card, at
   all times. app-shell-dropdowns escapes clipping ancestors by promoting an
   open panel to position: fixed and publishing viewport-relative geometry in
   --bao-dropdown-left; inside a card that geometry resolved against the CARD
   instead. Measured on the chat composer at 824px: a trigger at x=501 with a
   published left of 493px put the open menu at 994px — entirely off-screen,
   and forcing position: fixed moved it not at all. */
.card.card-border,
  [data-dashboard-widget] {
  --card-tilt-x: 0deg;
  --card-tilt-y: 0deg;
}

@media (hover: hover) and (pointer: fine) {
  .card.card-border[data-bao-tilt="true"],
  [data-dashboard-widget][data-bao-tilt="true"] {
    transform: perspective(800px) rotateX(var(--card-tilt-x)) rotateY(var(--card-tilt-y)) translateY(-2px);
    transition: transform var(--duration-fast, 120ms) var(--easing-standard);
  }
}

/* Reduced motion suppresses the movement itself. This used to reset the
   perspective properties, which is a no-op now that the tilt owns its own
   perspective — and a reset of something never set would have left the motion
   running. */
@media (prefers-reduced-motion: reduce) {
  .card.card-border[data-bao-tilt="true"] {
    transform: none;
    transition: none;
  }
}

/* ─── Touch Press Feedback (driven by app-shell-touch-feedback.ts) ───
   JS sets data-bao-press="true" on touchstart, removes on touchend.
   CSS applies instant visual feedback — zero-latency feel. */

.bao-touch-target[data-bao-press="true"],
.btn[data-bao-press="true"],
a[href][data-bao-press="true"],
[role="button"][data-bao-press="true"],
[data-action][data-bao-press="true"],
summary[data-bao-press="true"] {
  transform: scale(0.97);
  opacity: var(--opacity-strong, 0.85);
  transition: none;
}

/* OS + product reduced-motion: no press spring scale (dock/topbar haptic). */
@media (prefers-reduced-motion: reduce) {
  .bao-touch-target[data-bao-press="true"],
  .btn[data-bao-press="true"],
  a[href][data-bao-press="true"],
  [role="button"][data-bao-press="true"],
  [data-action][data-bao-press="true"],
  summary[data-bao-press="true"] {
    transform: none;
    opacity: 1;
  }
}
html[data-reduced-motion="true"] .bao-touch-target[data-bao-press="true"],
html[data-reduced-motion="true"] .btn[data-bao-press="true"],
html[data-reduced-motion="true"] a[href][data-bao-press="true"],
html[data-reduced-motion="true"] [role="button"][data-bao-press="true"],
html[data-reduced-motion="true"] [data-action][data-bao-press="true"],
html[data-reduced-motion="true"] summary[data-bao-press="true"] {
  transform: none;
  opacity: 1;
}

/* ─── Toast Swipe Tracking (driven by app-shell-toast-swipe.ts) ───
   JS writes --bao-swipe-dx and --bao-swipe-alpha via setProperty
   during touchmove. CSS reads them for live transform + opacity. */

#toast-region > [data-bao-swiping="true"] {
  transform: translateX(var(--bao-swipe-dx, 0px));
  opacity: var(--bao-swipe-alpha, 1);
  transition: none;
}

#toast-region > [data-toast-dismissing] {
  transform: translateX(100%);
  opacity: 0;
  transition: transform var(--duration-normal, 200ms) var(--easing-standard),
              opacity var(--duration-normal, 200ms) var(--easing-standard);
}

@media (min-width: 48rem) {
  #toast-region > [data-toast-dismissing] {
    transform: translateX(120%);
  }
}

/* ─── Toast Entrance Animation ─── */

@keyframes bao-toast-enter {
  from {
    opacity: 0;
    transform: translateY(var(--space-section, 0.75rem)) scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

#toast-region > *:not([data-toast-dismissing]):not([data-bao-swiping]) {
  animation: bao-toast-enter var(--duration-slow, 320ms) var(--easing-entrance) both;
}

/* ─── Toast Region Top-Layer Promotion (driven by app-shell-toast.ts) ───
   JS promotes #toast-region via popover=manual (shown persistently) so
   error toasts paint — and stay interactive — above open native dialogs
   and the mobile drawer. The popover UA box (centered inset, border,
   padding, canvas background) must not fight the daisyUI .toast geometry,
   which already owns placement; neutralize it here. */
#toast-region[popover] {
  inset: auto;
  inset-inline-start: 50%;
  inset-inline-end: auto;
  --toast-x: -50%;
  inset-block-start: calc(var(--chrome-min-height) + var(--space-section) + var(--space-inline));
  inset-block-end: auto;
  inline-size: min(var(--toast-max-inline-size), calc(100vw - (2 * var(--space-inline))));
  max-inline-size: min(var(--toast-max-inline-size), calc(100vw - (2 * var(--space-inline))));
  margin: 0;
  padding: 0;
  border: none;
  overflow: visible;
  background: transparent;
  color: inherit;
}

#toast-region[popover] > * {
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

/* ─── Selection Highlight ─── */

::selection {
  background-color: color-mix(in oklab, var(--accent-primary, var(--color-primary)) 20%, transparent);
  color: var(--text-primary, var(--color-base-content));
}

/* ─── Spring Hover Scale (driven by app-shell-fluid-interactions.ts) ───
   JS sets data-bao-hover="true" on pointerenter, removes on pointerleave.
   Spring easing overshoots then settles — Apple HIG material feel. */

/* Only explicit Button primitives participate; content panels keep stable bounds. */
@media (hover: hover) and (pointer: fine) {
  [data-primitive="Button"][data-bao-hover="true"] {
    transform: scale(var(--hover-scale));
    transition: transform var(--duration-slow) var(--easing-glass);
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-bao-hover="true"] {
    transform: none;
    transition: none;
  }
}
html[data-reduced-motion="true"] [data-bao-hover="true"] {
  transform: none;
  transition: none;
}

/* ─── Mobile Dock Snap (driven by app-shell-fluid-interactions.ts) ─── */

#mobile-dock[data-bao-dock-snap="true"] {
  scroll-snap-type: x mandatory;
}

#mobile-dock[data-bao-dock-snap="true"] .bao-mobile-dock-item {
  scroll-snap-align: center;
}

/* ─── Pull-to-Refresh Indicator (driven by app-shell-swipe-navigation.ts) ───
   JS publishes --pull-distance via setHostCssVars (data-bao-var-host),
   never inline style attrs. CSS pseudo-element renders the indicator. */

#app-main[data-bao-var-host]::before {
  content: "";
  position: fixed;
  top: 0;
  left: 50%;
  width: 2rem;
  height: 2rem;
  transform: translateX(-50%) translateY(var(--pull-distance, 0px));
  border-radius: var(--radius-full, 9999px);
  background: var(--bg-surface, var(--color-base-200));
  border: 1px solid var(--border-base, var(--color-base-300));
  box-shadow: var(--shadow-md);
  z-index: var(--z-toast, 1400);
  pointer-events: none;
}

/* ─── Skeleton Shimmer Enhancement ─── */

@keyframes bao-skeleton-sweep {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton-shimmer {
  background-color: var(--bg-surface-hover, var(--color-base-200));
  background-image: linear-gradient(
    90deg,
    var(--bg-surface, var(--color-base-200)) 25%,
    color-mix(in oklab, var(--bg-surface-hover, var(--color-base-300)) 60%, transparent) 50%,
    var(--bg-surface, var(--color-base-200)) 75%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: bao-skeleton-sweep var(--duration-skeleton) var(--easing-standard) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-shimmer {
    animation: none;
    background-image: none;
    background-color: var(--bg-surface, var(--color-base-200));
  }
}

/* ─── Modal Glass Enhancement + Entrance ─── */

@keyframes bao-modal-enter {
  from {
    opacity: 0;
    transform: translateY(var(--space-section, 0.75rem)) scale(0.97);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Open state owns entrance motion only. Material paint stays with the
   canonical modal + lens variants, and :where() prevents state markup from
   outranking accessibility preference selectors. */
:where(dialog.modal[open], dialog.modal.modal-open) .bao-modal-box-shell {
  animation: bao-modal-enter var(--duration-slow, 320ms) var(--easing-entrance) both;
}

@media (prefers-reduced-motion: reduce) {
  :where(dialog.modal[open], dialog.modal.modal-open) .bao-modal-box-shell {
    animation: none;
  }
}


/* ═══════════════════════════════════════════════════════════════════
   UX ENHANCEMENTS — hover, focus, mobile interactions
   ═══════════════════════════════════════════════════════════════════ */

/* ─── Glass Hover Illumination (design.pdf §2.7) ───
   A subtle radial glow near the pointer on glass surfaces. JS sets
   --glass-pointer-x/y via pointermove; CSS composes the glow as an
   additional background layer on the existing ::after inner illumination.
   Falls back gracefully when JS is unavailable (no glow, no break). */

@media (hover: hover) and (pointer: fine) {
  :is(.bao-surface-glass-regular, .bao-surface-glass-clear) {
    --glass-pointer-x: 50%;
    --glass-pointer-y: 50%;
  }
  .bao-surface-glass-regular:hover::after,
  .bao-surface-glass-clear:hover::after {
    background:
      radial-gradient(
        600px circle at var(--glass-pointer-x) var(--glass-pointer-y),
        var(--glass-illumination-strong),
        transparent 40%
      ),
      radial-gradient(
        ellipse 120% 80% at 20% 0%,
        var(--glass-illumination-strong) 0%,
        var(--glass-illumination-soft) 50%,
        var(--glass-highlight-clear) 80%
      );
  }
}

@media (prefers-reduced-motion: reduce) {
  .bao-surface-glass-regular:hover::after,
  .bao-surface-glass-clear:hover::after {
    background: radial-gradient(
      ellipse 120% 80% at 20% 0%,
      var(--glass-illumination-strong) 0%,
      var(--glass-illumination-soft) 50%,
      var(--glass-highlight-clear) 80%
    );
  }
}

/* ─── Focus Ring Pulse (design.pdf §6) ───
   Subtle pulse on focus-visible to draw attention without being distracting.
   Only triggers on keyboard focus (focus-visible), not mouse clicks. */

@keyframes bao-focus-pulse {
  0%, 100% { outline-offset: var(--focus-ring-offset, 2px); }
  50% { outline-offset: calc(var(--focus-ring-offset, 2px) + 1px); }
}

:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, var(--color-primary));
  outline-offset: var(--focus-ring-offset, 2px);
  animation: bao-focus-pulse var(--duration-focus-pulse) var(--easing-standard) 1;
}

@media (prefers-reduced-motion: reduce) {
  :focus-visible {
    animation: none;
  }
}

/* ─── Sidebar Hover-Expand — DELETED (second width owner) ───
   `.bao-sidebar-collapsed:hover { width: 16rem }` widened the NAV while the
   grid track on .bao-shell-drawer stayed at --sidebar-collapsed, and
   .drawer-side clips (overflow:hidden). Measured at 1440px: nav 256px inside
   a 56px track, elementFromPoint(150, item) landed on page content, and the
   label spans stayed display:none because the drawer kept
   .bao-shell-rail-collapsed — so the hover revealed no labels at all. It did
   produce two artefacts: a 200px dead hit area, and .bao-nav-item.tooltip
   bubbles laid out against a 238px-wide item whose body fell outside the clip,
   leaving a stray arrow at the rail edge. The drawer grid track is the ONE
   owner of rail width (tokens-glass-shell-drawer-part-b) and the peek
   controller is the one owner of hover expansion — it moves the panel out of
   the track first, so nothing clips. The [data-sidebar-*-only] display swaps
   keep their real owners: app-shell-rails.ts (hidden class, per state) plus
   the peek/pinned/checked rules in tokens-glass-shell-drawer-part-1. */

/* ─── Smooth Theme Transition (light↔dark crossfade) ───
   Crossfade between themes when data-theme changes. Only applies to
   color properties — not layout/transform (would cause reflow). */

html {
  transition: background-color var(--duration-slow, 320ms) var(--easing-standard),
              color var(--duration-slow, 320ms) var(--easing-standard);
}

@media (prefers-reduced-motion: reduce) {
  html {
    transition: none;
  }
}

/* ─── Empty State Entrance Animation (transform-only stagger) ───
   Empty states enter with a subtle upward motion for perceived performance.
   Children stagger by --stagger-step (30ms default from tokens.bao).
   WebView 101 law (tokens-glass-a11y.ts): entrance motion is
   transform-only — opacity-from-0 can stick and paint voids over real
   content, so readability never depends on an animation completing. */

@keyframes bao-empty-enter {
  from {
    transform: translateY(var(--entrance-offset, 0.375rem));
  }
  to {
    transform: none;
  }
}

[data-state="empty"] [data-state-content],
[data-primitive="EmptyState"] {
  animation: bao-empty-enter var(--duration-slow, 320ms) var(--easing-entrance) both;
}

@media (prefers-reduced-motion: reduce) {
  [data-state="empty"] [data-state-content],
  [data-primitive="EmptyState"] {
    animation: none;
  }
}

/* ─── Card Hover Lift (design.pdf §5.3) ───
   Clickable cards lift on hover with increased shadow and slight translateY.
   Only on fine pointers — touch devices use press feedback instead.
   Dashboard KPI tiles with a cover action share the same affordance so the
   whole card reads as the control (design.pdf §5.4). */

@media (hover: hover) and (pointer: fine) {
  [data-primitive="Card"][data-clickable="true"],
  .card.card-border[data-clickable="true"],
  [data-dashboard-widget]:has(.bao-dashboard-widget-action),
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action) {
    transition: transform var(--duration-fast, 120ms) var(--easing-standard),
                box-shadow var(--duration-fast, 120ms) var(--easing-standard),
                border-color var(--duration-fast, 120ms) var(--easing-standard);
  }
  [data-primitive="Card"][data-clickable="true"]:hover,
  .card.card-border[data-clickable="true"]:hover,
  [data-dashboard-widget]:has(.bao-dashboard-widget-action):hover,
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action):hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-glass-elevated, var(--shadow-lg));
    border-color: color-mix(in oklab, var(--text-primary) 18%, var(--border-base, var(--color-base-300)));
  }
  [data-primitive="Card"][data-clickable="true"]:active,
  .card.card-border[data-clickable="true"]:active,
  [data-dashboard-widget]:has(.bao-dashboard-widget-action):active,
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action):active {
    transform: translateY(0) scale(0.99);
    box-shadow: var(--shadow-sm);
  }
}

/* Coarse pointer: press scale on whole-card KPI tiles (Apple HIG feedback). */
@media (hover: none), (pointer: coarse) {
  [data-dashboard-widget]:has(.bao-dashboard-widget-action):active,
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action):active,
  [data-primitive="Card"][data-clickable="true"]:active,
  .card.card-border[data-clickable="true"]:active {
    transform: scale(0.985);
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-primitive="Card"][data-clickable="true"],
  .card.card-border[data-clickable="true"],
  [data-dashboard-widget]:has(.bao-dashboard-widget-action),
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action) {
    transition: none;
  }
  [data-primitive="Card"][data-clickable="true"]:hover,
  .card.card-border[data-clickable="true"]:hover,
  [data-dashboard-widget]:has(.bao-dashboard-widget-action):hover,
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action):hover,
  [data-dashboard-widget]:has(.bao-dashboard-widget-action):active,
  [data-primitive="DashboardWidget"]:has(.bao-dashboard-widget-action):active {
    transform: none;
  }
}

/* ─── Dialog Drag Handle (mobile bottom sheets) ───
   Visual indicator on bottom-sheet dialogs for drag-to-dismiss affordance.
   Gated at the same sm breakpoint where Dialog.bao swaps modal-bottom for
   sm:modal-middle: above 40rem the dialog centers, and a grabber on a
   centered sheet is a phantom affordance — and only where the dismiss gesture
   actually binds: the sheet drag answers touch on coarse pointers
   (app-shell-sheet-gesture binds touchstart there, never mouse), so a fine
   pointer in a narrow window would otherwise see a pill promising a drag the
   surface cannot perform. */

@media (pointer: coarse) and (max-width: 39.99rem) {
  dialog.modal.modal-bottom .modal-box::before {
    content: "";
    display: block;
    width: 2.5rem;
    height: 0.25rem;
    border-radius: var(--radius-full, 9999px);
    background: color-mix(in oklab, var(--text-tertiary) 30%, transparent);
    margin: var(--space-micro) auto var(--space-inline);
  }
}

/* Filter sheet, row-swipe, dashboard drag-reorder, fuzzy-search, and the
   bao-sheet-enter keyframes are owned solely by
   tokens-glass-runtime-interactions-mobile.ts (single owner — DRY, S42).
   The scroll-to-top button was deleted: zero markup/JS consumers exist. */


/* ─── Scrollbar Refinement (Webkit) ─── */

::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--text-tertiary, var(--color-base-content)) 30%, transparent);
  border-radius: var(--radius-full, 9999px);
}

[data-theme="capture-dx-dark"] ::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--text-tertiary, var(--color-base-content)) 25%, transparent);
}

/* Hybrid hover (imp-t04): thumb emphasis only where a pointer can hover —
   overlay scrollbars on touch never receive a hoverable thumb. */
@media (hover: hover) and (pointer: fine) {
  ::-webkit-scrollbar-thumb:hover {
    background: color-mix(in oklab, var(--text-secondary, var(--color-base-content)) 50%, transparent);
  }

  [data-theme="capture-dx-dark"] ::-webkit-scrollbar-thumb:hover {
    background: color-mix(in oklab, var(--text-secondary, var(--color-base-content)) 40%, transparent);
  }
}



/* ─── Filter Bar Mobile Bottom Sheet ───
   On mobile (<48rem), filter overflow transforms into a bottom sheet
   pattern. The overflow summary button triggers a details/summary
   disclosure that slides up from the bottom. */

@media (max-width: 47.99rem) {
  [data-primitive="FilterBar"] details.bao-filter-overflow {
    position: relative;
  }
  [data-primitive="FilterBar"] details.bao-filter-overflow[open] > summary ~ * {
    animation: bao-sheet-enter var(--duration-slow, 320ms) var(--easing-entrance) both;
  }
  [data-primitive="FilterBar"] details.bao-filter-overflow > summary ~ * {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: var(--z-overlay, 1200);
    max-height: 70dvh;
    overflow-y: auto;
    background: var(--bg-panel, var(--color-base-100));
    border-top: 1px solid var(--border-base, var(--color-base-300));
    border-radius: var(--radius-box, 1rem) var(--radius-box, 1rem) 0 0;
    padding: var(--space-section);
    box-shadow: var(--shadow-glass-floating);
  }
}

@keyframes bao-sheet-enter {
  from {
    opacity: 0;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-primitive="FilterBar"] details.bao-filter-overflow[open] > summary ~ * {
    animation: none;
  }
}

/* ─── Data Table Row Swipe Actions (mobile) ───
   Swipe left on a table row to reveal action buttons.
   JS (app-shell-table-row-swipe) sets --row-swipe-dx via setHostCssVars.
   Only on coarse pointers (touch devices). Card-stack rows keep actions
   visible via opacity:1 rules; swipe still works for denser table mode. */

@media (hover: none) and (pointer: coarse) {
  [data-primitive="DataTable"] tbody tr.bao-table-responsive-row,
  [data-primitive="DataTable"] tbody tr.bao-hover-row {
    position: relative;
    touch-action: pan-y;
  }
  [data-primitive="DataTable"] tbody tr[data-bao-swiping="true"] {
    transform: translateX(var(--row-swipe-dx, 0px));
    transition: none;
    z-index: var(--z-local, 1);
  }
  [data-primitive="DataTable"] tbody tr[data-bao-swipe-open="true"]:not([data-bao-swiping]) {
    transform: translateX(var(--row-swipe-dx, -88px));
    transition: transform var(--duration-fast, 120ms) var(--easing-standard);
  }
  [data-primitive="DataTable"] tbody tr:not([data-bao-swiping]):not([data-bao-swipe-open]) {
    transition: transform var(--duration-fast, 120ms) var(--easing-standard);
  }
  /* Keep action cluster tappable while the row is translated. */
  [data-primitive="DataTable"] tbody tr[data-bao-swipe-open="true"] .bao-table-pinned-action,
  [data-primitive="DataTable"] tbody tr[data-bao-swiping="true"] .bao-table-pinned-action {
    position: relative;
    z-index: var(--z-raised, 2);
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-primitive="DataTable"] tbody tr {
    transition: none;
  }
}

/* ─── Sheet Back Gesture (mobile) ───
   iOS-style swipe right from the left edge dismisses the topmost sheet.
   JS (app-shell-sheet-gesture) publishes --sheet-gesture-dx on the dialog
   via setHostCssVars and flips data-bao-sheet-gesture: "drag" follows the
   finger with no transition, "settle" transitions back to 0 on cancel.
   Sheet wrappers stay fixed (backdrop must not move); the panel translates.
   Reduced-motion never receives the attribute — dismiss is instant. */

@media (hover: none) and (pointer: coarse) and (max-width: 47.99rem) {
  dialog[data-bao-sheet-gesture] {
    transform: translateX(var(--sheet-gesture-dx, 0px));
  }
  dialog[data-bao-sheet-gesture="drag"] {
    transition: none;
  }
  dialog[data-bao-sheet-gesture="settle"] {
    transition: transform var(--duration-fast, 120ms) var(--easing-standard);
  }
  dialog[data-primitive="Sheet"][data-bao-sheet-gesture] {
    transform: none;
  }
  dialog[data-primitive="Sheet"][data-bao-sheet-gesture] .bao-sheet-panel {
    transform: translateX(var(--sheet-gesture-dx, 0px));
  }
  dialog[data-primitive="Sheet"][data-bao-sheet-gesture="drag"] .bao-sheet-panel {
    transition: none;
  }
  dialog[data-primitive="Sheet"][data-bao-sheet-gesture="settle"] .bao-sheet-panel {
    transition: transform var(--duration-fast, 120ms) var(--easing-standard);
  }
}

/* ─── Dashboard Widget Drag-to-Reorder ───
   Widgets get a drag handle on hover (desktop) and long-press (mobile).
   JS sets data-bao-dragging="true" during drag; CSS handles visual state. */

[data-dashboard-widget][data-bao-dragging="true"] {
  opacity: var(--opacity-strong, 0.85);
  box-shadow: var(--shadow-glass-floating);
  transform: scale(1.02);
  z-index: var(--z-overlay, 1200);
  cursor: grabbing;
}

[data-dashboard-widget][data-bao-drag-over="true"] {
  outline: var(--focus-ring-width, 2px) dashed var(--accent-primary, var(--color-primary));
  outline-offset: var(--focus-ring-offset-tight, -2px);
}

@media (prefers-reduced-motion: reduce) {
  [data-dashboard-widget][data-bao-dragging="true"] {
    transform: none;
    transition: none;
  }
}

/* ─── Command Palette Fuzzy Search ───
   Enhanced search results with highlighted matching characters.
   JS sets data-bao-match="true" on matching characters; CSS styles them. */

[data-bao-match="true"] {
  font-weight: var(--font-weight-semibold, 600);
  color: var(--accent-primary, var(--color-primary));
}

/* ─── Case Card Swipe-Right Quick Claim (imp-m06, mobile) ───
   Swipe right on a worklist case row to slide the claim affordance in from
   the inline-start edge. JS (app-shell-card-swipe) sets --case-swipe-dx via
   setHostCssVars and data-case-swiping / data-case-swipe-armed on the row.
   The row itself never transforms — the table-row swipe-left contract owns
   row translation, so the two gestures compose without a transform race. */

[data-case-swipe-affordance] {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  [data-case-swipe-affordance] {
    position: absolute;
    inset-inline-start: 0;
    inset-block: 0;
    z-index: var(--z-raised, 2);
    display: flex;
    align-items: center;
    padding-inline: var(--space-inline);
    background: color-mix(in oklab, var(--color-primary) 16%, var(--bg-panel));
    color: var(--color-primary);
    transform: translateX(-100%);
    opacity: 0;
    visibility: hidden;
    transition:
      transform var(--duration-fast, 120ms) var(--easing-standard),
      opacity var(--duration-fast, 120ms) var(--easing-standard);
  }
  tr[data-case-swiping] [data-case-swipe-affordance] {
    transform: translateX(calc(-100% + var(--case-swipe-dx, 0px)));
    opacity: 1;
    visibility: visible;
    transition: none;
  }
  tr[data-case-swipe-armed] [data-case-swipe-affordance] {
    background: color-mix(in oklab, var(--color-primary) 30%, var(--bg-panel));
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-case-swipe-affordance] {
    transition: none;
  }
}

/* ─── Cases List Pull-to-Refresh (imp-m07, mobile) ───
   Pull down at scrollTop 0 to re-issue the list's canonical hx-get. JS
   (app-shell-pull-refresh) sets --pull-refresh-dy and data-pull-refresh-state
   on the list host; the spinner markup is generated from
   partials/case-list-table.bao. Reduced-motion removes the rubber-band
   transform entirely (mission constraint) — the state spinner still reports. */

[data-pull-refresh-indicator] {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  [data-case-list] {
    position: relative;
    overscroll-behavior-y: contain;
  }
  [data-pull-refresh-indicator] {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: var(--z-raised, 2);
    display: flex;
    justify-content: center;
    padding-block: var(--space-micro);
    color: var(--text-secondary);
    transform: translateY(calc(-100% + var(--pull-refresh-dy, 0px)));
    opacity: 0;
    pointer-events: none;
  }
  [data-case-list][data-pull-refresh-state="pulling"] [data-pull-refresh-indicator],
  [data-case-list][data-pull-refresh-state="armed"] [data-pull-refresh-indicator] {
    opacity: 1;
  }
  [data-case-list][data-pull-refresh-state="armed"] [data-pull-refresh-indicator],
  [data-case-list][data-pull-refresh-state="refreshing"] [data-pull-refresh-indicator] {
    color: var(--color-primary);
  }
  [data-case-list][data-pull-refresh-state="refreshing"] [data-pull-refresh-indicator] {
    position: static;
    transform: none;
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-case-list] [data-pull-refresh-indicator] {
    transform: none;
  }
}


/* ─── Filter Bar Bottom Sheet (Mobile) ───
   Transforms filter bar into bottom sheet on mobile (<48rem).
   JS sets --filter-sheet-open (0/1) and --filter-sheet-drag-y (px).
   CSS owns all visual state: backdrop, slide-up, drag feedback. */

@media (max-width: 47.99rem) {
  [data-primitive="FilterBar"] {
    --filter-sheet-open: 0;
    --filter-sheet-drag-y: 0px;
  }

  /*
   * Collapse inline filters only after the shared trigger exists. Before
   * enhancement, or when its template cannot load, search and selects remain
   * usable instead of leaving an empty strip. The same controls become the
   * sheet contents once its trigger is mounted; no duplicate mobile form.
   */
  [data-primitive="FilterBar"]:has(> [data-filter-actions] > [data-filter-sheet-trigger]):not([data-filter-sheet-open="true"]) {
    flex-wrap: nowrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-micro, 0.25rem);
    padding-block: var(--space-micro, 0.25rem);
  }
  [data-primitive="FilterBar"]:has(> [data-filter-actions] > [data-filter-sheet-trigger]):not([data-filter-sheet-open="true"]) > :not([data-filter-actions]):not([data-filter-sheet-backdrop]) {
    display: none;
  }
  [data-primitive="FilterBar"]:has(> [data-filter-actions] > [data-filter-sheet-trigger]):not([data-filter-sheet-open="true"]) > [data-filter-actions] {
    width: 100%;
    max-width: 100%;
    margin-inline-start: 0;
    justify-content: flex-end;
  }
  /* Closed chip row: only the sheet trigger — clear/export stay in the open sheet. */
  [data-primitive="FilterBar"]:has(> [data-filter-actions] > [data-filter-sheet-trigger]):not([data-filter-sheet-open="true"]) > [data-filter-actions] > :not([data-filter-sheet-trigger]) {
    display: none;
  }

  /*
   * Backdrop overlay.
   * Closed: pointer-events none + visibility hidden.
   * Never multiply --filter-sheet-open into pointer-events: numeric 0 is
   * invalid there and browsers fall back to auto, blocking the page.
   */
  /*
   * Scrim lives as a body sibling (not a bar child). The bar uses isolation
   * isolate for sticky dropdown stacking, so a child backdrop at z-overlay
   * paints over the bar's own un-z-indexed fieldsets and cannot cover the
   * page. Body portal + data-filter-sheet-open keeps the scrim full-viewport.
   */
  .bao-filter-sheet-backdrop {
    position: fixed;
    inset: 0;
    background: color-mix(in oklab, var(--color-base-100) 60%, transparent);
    /* Glass on chrome overlay only — reduced-transparency solidifies via a11y tokens. */
    -webkit-backdrop-filter: blur(var(--liquid-blur-clear, 8px));
    backdrop-filter: blur(var(--liquid-blur-clear, 8px));
    /* The body portal mounts the sheet after its scrim at the modal layer.
       Both must cover shell topbar/dropdown chrome, not only page content. */
    z-index: var(--z-modal, 1300);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--duration-normal, 200ms) var(--easing-standard);
  }

  .bao-filter-sheet-backdrop[data-filter-sheet-open="true"] {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  /* Open sheet: fixed bottom panel with safe-area dock clearance.
     z-modal (1300) outranks the sticky shell topbar (--z-shell-topbar:
     1250). Clear sticky top:0 via inset-block-start:auto — otherwise
     top + bottom + max-height over-constrain and CSS 2.1 section 10.6.4
     drops bottom, pinning the bottom sheet to the viewport top. */
  [data-primitive="FilterBar"][data-filter-sheet-open="true"] {
    position: fixed;
    inset-inline: 0;
    inset-block-start: auto;
    top: auto;
    inset-block-end: calc(
      var(--dock-height, 4rem) + var(--dock-safe-area-offset, 0px) +
      env(safe-area-inset-bottom, 0px)
    );
    max-height: min(80dvh, 36rem);
    overflow-y: auto;
    z-index: var(--z-modal, 1300);
    border-radius: var(--radius-box, 1rem) var(--radius-box, 1rem) 0 0;
    box-shadow: var(--shadow-glass-floating);
    transition: transform var(--duration-normal, 200ms) var(--easing-standard);
    background: var(--color-base-100);
    border: 1px solid var(--color-base-300);
    padding: var(--space-inline, 0.5rem);
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
  }

  /* Fieldsets above any residual in-bar chrome; relative stacks within the bar. */
  [data-primitive="FilterBar"][data-filter-sheet-open="true"] > :not([data-filter-actions]) {
    position: relative;
    z-index: var(--z-local, 1);
  }

  /*
   * Drag transform is gated on an ACTIVE drag (JS stamps
   * data-filter-sheet-dragging): any non-none transform makes the bar the
   * containing block for the fixed backdrop, which would shrink the scrim to
   * the bar's own box and leave the page (incl. the sticky header)
   * un-scrimmed and tappable.
   */
  [data-primitive="FilterBar"][data-filter-sheet-open="true"][data-filter-sheet-dragging="true"] {
    transform: translateY(var(--filter-sheet-drag-y));
  }

  /* Drag handle indicator */
  [data-primitive="FilterBar"][data-filter-sheet-open="true"]::before {
    content: "";
    display: block;
    width: 2.5rem;
    height: 0.25rem;
    border-radius: var(--radius-full, 9999px);
    background: color-mix(in oklab, var(--text-tertiary) 30%, transparent);
    margin: var(--space-micro) auto var(--space-inline);
  }

  /* Trigger button: full-width filter chip when closed. */
  .bao-filter-sheet-trigger {
    gap: var(--space-micro);
  }
  [data-primitive="FilterBar"]:has(> [data-filter-actions] > [data-filter-sheet-trigger]):not([data-filter-sheet-open="true"]) .bao-filter-sheet-trigger {
    flex: 1 1 auto;
    justify-content: center;
    min-height: var(--touch-target-min, 2.75rem);
  }
  /* Active filter count badge (Mail/Photos idiom) — only when count > 0. */
  .bao-filter-active-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding-inline: var(--space-micro, 0.25rem);
    border-radius: var(--radius-full, 9999px);
    font-size: var(--text-2xs, 0.6875rem);
    font-weight: 700;
    line-height: 1;
    background: var(--color-primary);
    color: var(--color-primary-content);
    font-variant-numeric: tabular-nums;
  }
  .bao-filter-active-count[hidden] {
    display: none;
  }
  [data-primitive="FilterBar"][data-filter-active="true"]:not([data-filter-sheet-open="true"]) .bao-filter-sheet-trigger {
    border-color: color-mix(in oklab, var(--color-primary) 40%, var(--border-base, var(--color-base-300)));
    background: color-mix(in oklab, var(--color-primary) 10%, var(--bg-panel, var(--color-base-100)));
  }

  /*
   * Progressive hide when a FilterBar has nothing to filter (training connector
   * offline, empty API keys inventory, etc.). JS stamps
   * data-training-filters-deferred or data-filter-bar-deferred. Specificity
   * must beat phone FilterBar display:grid + closed-sheet chip layout so the
   * sheet trigger leaves the layout (not just aria-hidden).
   */
  form[data-primitive="FilterBar"][data-training-filters][data-training-filters-deferred],
  [data-primitive="FilterBar"][data-training-filters][data-training-filters-deferred],
  form[data-training-filters][data-training-filters-deferred],
  [data-training-filters][data-training-filters-deferred],
  form[data-primitive="FilterBar"][data-filter-bar-deferred],
  [data-primitive="FilterBar"][data-filter-bar-deferred],
  form[data-filter-bar][data-filter-bar-deferred],
  [data-filter-bar][data-filter-bar-deferred] {
    display: none;
    visibility: hidden;
    pointer-events: none;
    height: 0;
    max-height: 0;
    min-height: 0;
    overflow: hidden;
    margin: 0;
    padding: 0;
    border-width: 0;
  }
}

/* Desktop/tablet: same deferred hide (filter noise without inventory). */
form[data-primitive="FilterBar"][data-training-filters][data-training-filters-deferred],
[data-primitive="FilterBar"][data-training-filters][data-training-filters-deferred],
form[data-training-filters][data-training-filters-deferred],
[data-training-filters][data-training-filters-deferred],
form[data-primitive="FilterBar"][data-filter-bar-deferred],
[data-primitive="FilterBar"][data-filter-bar-deferred],
form[data-filter-bar][data-filter-bar-deferred],
[data-filter-bar][data-filter-bar-deferred] {
  display: none;
  visibility: hidden;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .bao-filter-sheet-backdrop,
  [data-primitive="FilterBar"][data-filter-sheet-open="true"] {
    transition: none;
  }
}

/* ─── Print Optimization ─── */

@media print {
  #app-header,
  #app-sidebar,
  /* ID-level: tokens-layout-dock-base is emitted after this module and its
     .bao-mobile-dock.dock display:flex outranks a plain class here, so the
     dock printed on every page. */
  #mobile-dock.dock.bao-mobile-dock,
  nav#mobile-dock[data-mobile-dock],
  #toast-region,
  .drawer-toggle,
  [data-page-header-actions],
  .htmx-indicator {
    display: none;
  }
  #app-main {
    overflow: visible;
    padding: 0;
  }
  .card.card-border,
  [data-dashboard-widget] {
    break-inside: avoid;
    box-shadow: none;
    border: 1px solid color-mix(in oklab, var(--color-base-content) 15%, transparent);
  }
}


/* ─── Desktop File Drop Zone (drag-and-drop upload affordance) ───
   JS sets data-bao-drop-active + data-state via app-shell-file-drop.ts;
   CSS owns every visual. Surface styles are gated to fine-pointer devices;
   preview list + error styles apply everywhere. */

@media (hover: hover) and (pointer: fine) {
  .bao-file-drop-zone {
    display: flex;
    flex-direction: column;
    gap: var(--space-inline, 0.5rem);
    border: 2px dashed var(--border-base, var(--color-base-300));
    border-radius: var(--radius-box, 1rem);
    padding: var(--space-page, 1.5rem);
    text-align: center;
    cursor: pointer;
    background: color-mix(in oklab, var(--bg-panel, var(--color-base-100)) 80%, transparent);
    transition: border-color var(--duration-normal, 200ms) var(--easing-standard, ease),
                background var(--duration-normal, 200ms) var(--easing-standard, ease),
                box-shadow var(--duration-normal, 200ms) var(--easing-standard, ease);
  }
  .bao-file-drop-zone[data-bao-drop-active="true"] {
    border-color: var(--color-primary);
    background: color-mix(in oklab, var(--color-primary) 10%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary) 20%, transparent);
  }
  .bao-file-drop-zone[data-state="error"] {
    border-color: var(--color-error);
    background: color-mix(in oklab, var(--color-error) 8%, transparent);
  }
  .bao-file-drop-zone:focus-visible {
    outline: var(--focus-ring-width, 2px) solid var(--color-primary);
    outline-offset: var(--focus-ring-offset, 2px);
  }
}
.bao-file-drop-zone__prompt {
  margin: 0;
  font-size: var(--text-sm, 0.875rem);
  color: var(--text-secondary, var(--color-base-content));
}
.bao-file-drop-zone__files {
  display: flex;
  flex-direction: column;
  gap: var(--space-micro, 0.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.bao-file-drop-zone__file-item {
  display: flex;
  align-items: center;
  gap: var(--space-inline, 0.5rem);
  padding: var(--space-micro, 0.25rem) var(--space-inline, 0.5rem);
  border: 1px solid var(--border-base, var(--color-base-300));
  border-radius: var(--radius-field, 0.75rem);
  background: var(--bg-panel, var(--color-base-100));
  text-align: start;
}
.bao-file-drop-zone__file-thumb {
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-xs, 0.25rem);
  object-fit: cover;
  flex-shrink: 0;
}
.bao-file-drop-zone__file-meta {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-2xs, 0.125rem);
}
.bao-file-drop-zone__file-name {
  font-size: var(--text-sm, 0.875rem);
  color: var(--text-primary, var(--color-base-content));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bao-file-drop-zone__file-size {
  font-size: var(--text-xs, 0.75rem);
  color: var(--text-tertiary, var(--color-base-content));
  font-variant-numeric: tabular-nums;
}
.bao-file-drop-zone__more {
  font-size: var(--text-xs, 0.75rem);
  color: var(--text-tertiary, var(--color-base-content));
  text-align: start;
  padding-inline: var(--space-inline, 0.5rem);
}
.bao-file-drop-zone__error {
  margin: 0;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-error-content, var(--color-error));
}
.bao-file-drop-zone__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
@media (prefers-reduced-motion: reduce) {
  .bao-file-drop-zone {
    transition: none;
  }
}

/* ─── Dockable Panels (Desktop) ───
   State lives on data-bao-dock-edge / data-bao-dock-floating; geometry on
   host CSS vars --bao-dock-{x,y,w,h} published by app-shell-dockable-panels.
   Only mounted on desktop fine-pointer hosts; mobile/tablet untouched. */

.bao-dockable-panel {
  position: relative;
  transition:
    box-shadow var(--duration-normal, 200ms) var(--easing-standard),
    opacity var(--duration-normal, 200ms) var(--easing-standard);
}

.bao-dockable-panel[data-bao-docking="true"] {
  opacity: var(--opacity-strong);
  box-shadow: var(--shadow-glass-floating);
  z-index: var(--z-overlay);
  transition: none;
}

.bao-dockable-panel[data-bao-dock-floating="true"] {
  position: fixed;
  inset-inline-start: var(--bao-dock-x, 0);
  inset-block-start: var(--bao-dock-y, 0);
  width: var(--bao-dock-w, 20rem);
  height: var(--bao-dock-h, 24rem);
  z-index: var(--z-overlay);
}

.bao-dockable-panel[data-bao-dock-resizing="true"] {
  transition: none;
  box-shadow: var(--shadow-glass-floating);
}

[data-bao-dock-resize] {
  position: absolute;
  inline-size: 0.75rem;
  block-size: 0.75rem;
  cursor: nwse-resize;
  background: transparent;
}[data-bao-dock-resize="n"],
[data-bao-dock-resize="s"] { inset-inline: 0; cursor: ns-resize; block-size: 0.5rem; }[data-bao-dock-resize="e"],
[data-bao-dock-resize="w"] { inset-block: 0; cursor: ew-resize; inline-size: 0.5rem; }
[data-bao-dock-resize="n"] { inset-block-start: -0.25rem; }
[data-bao-dock-resize="s"] { inset-block-end: -0.25rem; }
[data-bao-dock-resize="w"] { inset-inline-start: -0.25rem; }
[data-bao-dock-resize="e"] { inset-inline-end: -0.25rem; }
[data-bao-dock-resize="ne"] { inset-block-start: -0.25rem; inset-inline-end: -0.25rem; cursor: nesw-resize; }
[data-bao-dock-resize="nw"] { inset-block-start: -0.25rem; inset-inline-start: -0.25rem; cursor: nwse-resize; }
[data-bao-dock-resize="se"] { inset-block-end: -0.25rem; inset-inline-end: -0.25rem; cursor: nwse-resize; }
[data-bao-dock-resize="sw"] { inset-block-end: -0.25rem; inset-inline-start: -0.25rem; cursor: nesw-resize; }

[data-bao-dock-handle] {
  cursor: grab;
  touch-action: none;
}
[data-bao-dock-handle]:active { cursor: grabbing; }

.bao-dock-zone {
  position: fixed;
  inset: auto;
  pointer-events: none;
  border-radius: var(--radius-box, 1rem);
  transition:
    background-color var(--duration-fast, 120ms) var(--easing-standard),
    border-color var(--duration-fast, 120ms) var(--easing-standard);
}
.bao-dock-zone[data-bao-dock-zone="left"] {
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 22%;
}
.bao-dock-zone[data-bao-dock-zone="right"] {
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: 22%;
}
.bao-dock-zone[data-bao-dock-zone="top"] {
  inset-inline: 0;
  inset-block-start: 0;
  block-size: 22%;
}
.bao-dock-zone[data-bao-dock-zone="bottom"] {
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 22%;
}
.bao-dock-zone[data-bao-dock-zone="float"] {
  inset: 25% 30%;
}
.bao-dock-zone[data-bao-drop-target="true"] {
  background: color-mix(in oklab, var(--accent-primary) 15%, transparent);
  border: 2px dashed var(--accent-primary);
}

@media (prefers-reduced-motion: reduce) {.bao-dockable-panel,
.bao-dock-zone {
    transition: none;
  }
}


/* Keep a co-hosted toast out of mobile dialog actions. The toast remains in the top layer so it is dismissible, but it moves to the dialog's upper edge instead of covering the sticky footer. */
#toast-region.toast[data-toast-cohosted="true"] {
  inset-block-start: max(var(--space-inline, 0.5rem), env(safe-area-inset-top, 0px));
  inset-block-end: auto;
  inset-inline-start: auto;
  inset-inline-end: max(var(--space-inline, 0.5rem), env(safe-area-inset-right, 0px));
}

/*
 * While a stage is live the top-centre banner lands on the stage toolbar's
 * title block (measured 2026-08-23 at 1440x900: "Annotations saved." painted
 * over "Image 1 of 18" at y=64–112, the toolbar's own rows). HIG banners sit
 * over content, not controls: drop the region below the shell header and the
 * toolbar so it floats over the specimen.
 */
body:has([data-editor-chrome][data-annotation-ready="true"]) #toast-region.toast-top:not([data-toast-cohosted="true"]) {
  inset-block-start: calc(var(--chrome-min-height, 3rem) + var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem) * 3 + env(safe-area-inset-top, 0px));
}


}
@layer bao-tokens {

/* ─── Dashboard: widget actions are always visible ───
   The same hover-reveal that was removed from table rows below also ran on
   every .card-actions inside a widget host, and it was not clutter control —
   it was a second owner of visibility fighting the real one.

   [data-dashboard-widget] is not only the dashboard's KPI tiles: the Models
   page wraps its model catalogue in data-widget="models-predefined", so every
   model card's .card-actions footer matched. Each Download button — the
   primary CTA of that page — painted at opacity 0 with pointer-events intact
   until the operator happened to sweep the pointer across the panel, while
   assistive tech announced the whole row the entire time.

   The widget's own control cluster never needed the fade either: the layout
   customizer fieldset carries [hidden], and
   tokens-layout-dashboard-widgets-part-b.ts owns that state unlayered so it
   beats daisyUI's .card-actions. Visibility has one owner again.

   No @media (hover: none) / (pointer: coarse) escape replaces this. A pointer
   query cannot rescue a control that should never have been hidden: a desktop
   with a touchscreen reports both, and a keyboard user reports neither. */

/* ─── Dashboard: Staggered Widget Entrance Animation ───
   Sequential fade-in for dashboard widgets on page load. */

@keyframes bao-widget-entrance {
  from {
    opacity: 0;
    transform: translateY(var(--space-section, 0.75rem));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

[data-dashboard-widget] {
  animation: bao-widget-entrance var(--duration-slow, 320ms) var(--easing-entrance) both;
}

/*
   * One progress cue per widget at first paint: the body slot's skeleton
   * already says loading, so ten concurrent first-load fetches painted ten
   * spinners over ten skeletons (HIG: one clear progress indicator). The
   * indicator span keeps role=status for refresh announcements once the
   * shell settles; it only stays hidden while the shell is still in its
   * initial data-state="loading".
   */
[data-dashboard-widget][data-state="loading"] .htmx-indicator {
  display: none;
}

[data-dashboard-widget]:nth-child(1) { animation-delay: var(--duration-instant); }
[data-dashboard-widget]:nth-child(2) { animation-delay: calc(var(--stagger-step) * 2); }
[data-dashboard-widget]:nth-child(3) { animation-delay: calc(var(--stagger-step) * 4); }
[data-dashboard-widget]:nth-child(4) { animation-delay: calc(var(--stagger-step) * 6); }
[data-dashboard-widget]:nth-child(5) { animation-delay: calc(var(--stagger-step) * 8); }
[data-dashboard-widget]:nth-child(6) { animation-delay: calc(var(--stagger-step) * 10); }
[data-dashboard-widget]:nth-child(7) { animation-delay: calc(var(--stagger-step) * 12); }
[data-dashboard-widget]:nth-child(8) { animation-delay: calc(var(--stagger-step) * 14); }
[data-dashboard-widget]:nth-child(9) { animation-delay: calc(var(--stagger-step) * 16); }
[data-dashboard-widget]:nth-child(10) { animation-delay: calc(var(--stagger-step) * 18); }

@media (prefers-reduced-motion: reduce) {
  [data-dashboard-widget] {
    animation: none;
  }
}

/* ─── Table row actions: always visible ───
   SSOT action host is bao-table-pinned-action (Table.bao pinnedActionCell);
   bao-table-row-actions is the inner control cluster stamped on the value span.

   These controls used to fade in on row hover. That is removed, not tuned.
   Hover-reveal put opacity 0 on controls that kept pointer-events auto,
   so a worklist row shipped six invisible-but-clickable buttons under a
   visibly EMPTY "Actions" column — a pathologist could click a row action
   they could not see, and could not discover the actions at all without
   sweeping the mouse across every row. Assistive tech announced them the
   whole time, so sighted and screen-reader users saw different UIs.

   A clinical worklist is not a marketing table: row actions are primary
   affordances and stay painted in every state, at every density and pointer
   type. Keeping them visible also deletes an entire reactive state — there is
   no hover/touch/container-width matrix left to get wrong. */

/* Field focus paint is owned by tokens-glass-a11y-part-1. */

/* ─── AI Analysis: Progress Animation ───
   Smooth progress bar fill with gradient sweep. */

@keyframes bao-progress-fill {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

.progress::-webkit-progress-value {
  background-image: linear-gradient(
    90deg,
    var(--color-primary) 0%,
    color-mix(in oklab, var(--color-primary) 80%, var(--color-accent)) 50%,
    var(--color-primary) 100%
  );
  background-size: 200% 100%;
  animation: bao-progress-fill var(--duration-progress) var(--easing-standard) infinite;
  transition: width var(--duration-normal, 200ms) var(--easing-standard);
}

@media (prefers-reduced-motion: reduce) {
  .progress::-webkit-progress-value {
    animation: none;
    background-image: none;
    background-color: var(--color-primary);
  }
}

/* FAQ copy keeps its complete words when root text and spacing consume the narrow track. */
[data-faq-section] {
  overflow-wrap: anywhere;
}
[data-faq-section] .collapse,
[data-faq-section] .collapse-title,
[data-faq-section] .collapse-content {
  min-inline-size: 0;
}
/* ─── Help: FAQ Accordion Animation ───
   Smooth expand/collapse for FAQ sections. */

details.collapse[open] .collapse-content {
  animation: bao-accordion-expand var(--duration-normal, 200ms) var(--easing-entrance) both;
}

@keyframes bao-accordion-expand {
  from {
    opacity: 0;
    max-height: 0;
  }
  to {
    opacity: 1;
    max-height: 1000px;
  }
}

@media (prefers-reduced-motion: reduce) {
  details.collapse[open] .collapse-content {
    animation: none;
  }
}

/* ─── Card Hover Lift with Shadow ───
   Subtle elevation increase on card hover for depth hierarchy. */

/* Lift only when tilt is inactive — one transform owner per state. */
@media (hover: hover) and (pointer: fine) {
  .card.card-border:hover:not([data-bao-tilt="true"]),
  [data-dashboard-widget]:hover:not([data-bao-tilt="true"]):not([data-bao-dragging="true"]) {
    transform: translateY(-2px);
    box-shadow: var(--shadow-lg);
    transition: box-shadow var(--duration-normal, 200ms) var(--easing-standard);
  }
}

@media (prefers-reduced-motion: reduce) {
  .card.card-border:hover,
  [data-dashboard-widget]:hover {
    transform: none;
  }
}

}
@layer bao-tokens {


  :root {
    --dock-height: 4rem;
    --dock-icon-size: 1.25rem;
    --dock-label-size: 0.6875rem;
    --dock-safe-area-offset: env(safe-area-inset-bottom, 0px);
  }
  .bao-mobile-dock.dock,
  .bao-mobile-dock {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: stretch;
    justify-content: space-around;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: var(--z-overlay, 1200);
    width: 100%;
    max-width: 100%;
    min-height: calc(var(--dock-height) + var(--dock-safe-area-offset));
    height: calc(var(--dock-height) + var(--dock-safe-area-offset));
    padding-bottom: var(--dock-safe-area-offset);
    border-top: 1px solid color-mix(in oklab, var(--border-base, var(--color-base-300)) 40%, transparent);
    color: var(--color-base-content, var(--text-primary));
    /* Material: bao-surface-glass-regular on .bao-mobile-dock (app-shell.bao). */
    box-shadow:
      0 -1px 0 0 color-mix(in oklab, var(--border-base, var(--color-base-300)) 50%, transparent),
      var(--shadow-glass-elevated);
    /* Swipe-friendly overflow when locale labels widen past 5 slots. */
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    touch-action: pan-x;
  }
  [data-reduced-transparency="true"] .bao-mobile-dock.dock,
  [data-reduced-transparency="true"] .bao-mobile-dock {
    /* Glass a11y solidifies .bao-surface-glass-regular — dock keeps edge only. */
    box-shadow:
      0 -1px 0 0 color-mix(in oklab, var(--border-base, var(--color-base-300)) 50%, transparent);
  }
  @media (prefers-reduced-transparency: reduce) {
    .bao-mobile-dock.dock,
    .bao-mobile-dock {
      box-shadow:
        0 -1px 0 0 color-mix(in oklab, var(--border-base, var(--color-base-300)) 50%, transparent);
    }
  }
  /*
   * Tablet band and up (≥48rem / 768px at 16px root): the topbar hamburger is
   * the single nav affordance (Apple HIG — iPad-class widths get the sidebar,
   * not a phone tab bar), so the dock yields. Without this the 48–79.99rem band
   * painted BOTH the dock and the hamburger.
   *
   * Zeroing --dock-height is what retires the reserved bottom gutter: every
   * clearance consumer (#app-main padding, conversation footer, capture shutter
   * offsets, canvas-island inset, scroll-padding) reads the token, so no
   * per-surface media forks are needed and none can drift out of sync.
   *
   * This is a nav-affordance band, NOT the permanent-rail band. The rail stays
   * ≥80rem only (Android/WebView phones report ≥1024 CSS px) — see the separate
   * block below and tokens-glass-shell-drawer.
   */
  @media (min-width: 48rem) {
    :root {
      --dock-height: 0rem;
    }
    #mobile-dock.dock.bao-mobile-dock,
    nav#mobile-dock[data-mobile-dock],
    .bao-mobile-dock.dock,
    .bao-mobile-dock {
      display: none;
      height: 0;
      min-height: 0;
      overflow: hidden;
      pointer-events: none;
      visibility: hidden;
    }
  }
  /*
   * Permanent-rail band (≥80rem / 1280px at 16px root): dock yields to the
   * in-flow sidebar. ID selectors beat daisyUI .dock display:flex (components
   * layer) without !important. Mirror lives in tokens-glass-shell-drawer.
   * Subsumed by the ≥48rem hide above; kept explicit because the rail band is
   * co-owned and WebView 101 needs the ID-level rule stated at this breakpoint.
   */
  @media (min-width: 80rem) {
    #mobile-dock.dock.bao-mobile-dock,
    nav#mobile-dock[data-mobile-dock],
    .bao-mobile-dock.dock,
    .bao-mobile-dock {
      display: none;
      height: 0;
      min-height: 0;
      overflow: hidden;
      pointer-events: none;
      visibility: hidden;
    }
  }
  /* Keep base paint below every selected, hover, and focus state for links and buttons. */
  .bao-mobile-dock :where(a, .bao-mobile-dock-item, [data-mobile-dock-more]) {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2xs, 0.125rem);
    /* Reserve each destination label before distributing remaining space.
     * Longer locales use the dock scrollport instead of losing their labels. */
    min-width: max-content;
    max-width: none;
    min-height: var(--touch-target-min, 2.75rem);
    flex: 1 0 var(--touch-target-min);
    padding-block: var(--space-micro, 0.375rem);
    padding-inline: var(--space-2xs, 0.125rem);
    margin: 0;
    color: color-mix(in oklab, var(--color-base-content, currentColor) 75%, transparent);
    text-decoration: none;
    cursor: pointer;
    border: 0;
    border-radius: var(--radius-field, 0.75rem);
    background: transparent;
    box-shadow: none;
    appearance: none;
    -webkit-appearance: none;
    font: inherit;
    line-height: inherit;
    transition-property: background-color, color, opacity, transform;
    transition-duration: var(--duration-fast, 120ms);
    transition-timing-function: var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
    scroll-snap-align: center;
  }
  /* More is a <button> — never look selected unless the rail is open. */
  .bao-mobile-dock :where([data-mobile-dock-more])[aria-expanded="true"] {
    color: var(--color-primary, var(--accent-primary, currentColor));
    background: color-mix(in oklab, var(--color-primary) 12%, transparent);
  }
  .bao-mobile-dock a:active,
  .bao-mobile-dock-item:active,
  .bao-mobile-dock [data-mobile-dock-more]:active {
    background: color-mix(in oklab, var(--color-primary) 16%, transparent);
    opacity: var(--opacity-chrome, 0.92);
    transform: scale(0.96);
  }
  .bao-mobile-dock a:focus-visible,
  .bao-mobile-dock-item:focus-visible,
  .bao-mobile-dock [data-mobile-dock-more]:focus-visible {
    outline: var(--focus-ring-width, 2px) solid var(--color-primary, currentColor);
    outline-offset: var(--focus-ring-offset, 2px);
  }
  .bao-mobile-dock a.dock-active,
  .bao-mobile-dock a[aria-current="page"],
  .bao-mobile-dock-item.dock-active,
  .bao-mobile-dock-item[aria-current="page"],
  .bao-mobile-dock-item[aria-pressed="true"] {
    color: var(--color-primary, var(--accent-primary, currentColor));
    background: color-mix(in oklab, var(--color-primary) 14%, var(--color-base-100));
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-primary) 28%, transparent);
  }
  /* Apple HIG active: filled glyph weight via stroke; inactive stays outline. */
  .bao-mobile-dock a.dock-active svg,
  .bao-mobile-dock a[aria-current="page"] svg,
  .bao-mobile-dock-item.dock-active svg,
  .bao-mobile-dock-item[aria-current="page"] svg,
  .bao-mobile-dock-item[aria-pressed="true"] svg {
    stroke-width: 2.25;
    fill: color-mix(in oklab, var(--color-primary) 18%, transparent);
  }
  .bao-mobile-dock svg,
  .bao-mobile-dock .bao-dock-icon {
    width: var(--dock-icon-size);
    height: var(--dock-icon-size);
    flex-shrink: 0;
  }
  .bao-mobile-dock .dock-label {
    font-size: var(--dock-label-size);
    font-weight: 500;
    line-height: 1.1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-mobile-dock a,
    .bao-mobile-dock-item,
    .bao-mobile-dock [data-mobile-dock-more] {
      transition-property: none;
      transition-duration: var(--duration-instant, 0ms);
    }
    .bao-mobile-dock a:active,
    .bao-mobile-dock-item:active,
    .bao-mobile-dock [data-mobile-dock-more]:active {
      transform: none;
    }
  }
  [data-reduced-motion="true"] .bao-mobile-dock a:active,
  [data-reduced-motion="true"] .bao-mobile-dock-item:active,
  [data-reduced-motion="true"] .bao-mobile-dock [data-mobile-dock-more]:active {
    transform: none;
  }

  /* ─── FAB (imp-t08): floating page-primary action ───
     Position SSOT: floats above the bottom dock + home-indicator safe area on
     phone, and above the page gutter on tablet; inline-end honors the trailing
     safe-area inset. Visible only below the 64rem desktop band — the header
     CTA owns the verb on desktop (one primary affordance per band).
     Geometry (56px target) and surface live in FAB.bao tokens; this block
     owns placement, band visibility, and the scroll-aware hidden state
     (app-shell-fab.ts stamps data-fab-hidden). */
  .bao-fab {
    position: fixed;
    inset-block-end: calc(var(--dock-height, 4rem) + var(--dock-safe-area-offset, env(safe-area-inset-bottom, 0px)) + var(--space-section, 1rem));
    inset-inline-end: max(var(--space-section, 1rem), env(safe-area-inset-right, 0px));
    z-index: var(--z-sticky, 1100);
    display: inline-flex;
    box-shadow: var(--shadow-glass-floating);
    transition-property: transform, opacity;
    transition-duration: var(--duration-normal, 200ms);
    transition-timing-function: var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
  }
  /* Scroll-aware hide: slide below the dock edge and drop hit-testing. */
  .bao-fab[data-fab-hidden="true"] {
    transform: translateY(calc(100% + var(--space-section, 1rem)));
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
  }
  /* Desktop band: header CTA owns the primary verb — FAB yields entirely.
     The repeated class selector lifts this rule to (0,4,0) so the shell-wide
     .btn:not([hidden]):not(.hidden) display rule (0,3,0) cannot re-assert
     inline-flex above 64rem — the same outrank pattern documented in
     tokens-layout-page-shells-capture-responsive (attribute repetition). */
  @media (min-width: 64rem) {
    .bao-fab {
      display: none;
    }
  }
  /* Phone band (< 40rem): the page header's action zone goes full-width in
     flow there (max-sm:w-full, the same 40rem threshold), so the header CTA
     is already a full-bleed primary button a thumb reaches without leaving the
     document flow. The FAB is a fixed second copy of that one verb, and being
     fixed it prints over the list: at 390x844 on /cases it covered the card
     stack's row-select control outright — elementFromPoint at the checkbox
     centre [348,725] returned button.bao-fab, so no tap could reach it. One
     primary action per surface: the in-flow CTA survives because it cannot
     occlude content, and the FAB yields here for the same reason it already
     yields above 64rem. It keeps the 40-64rem band, where the header CTA
     collapses to a trailing btn-sm well out of thumb reach.
     Same (0,4,0) repetition as the desktop rule, for the same reason. */
  @media (max-width: 39.99rem) {
    .bao-fab {
      display: none;
    }
  }
  /* Tablet band has no dock: drop the FAB to the page gutter clearance. */
  @media (min-width: 48rem) and (max-width: 63.99rem) {
    .bao-fab {
      inset-block-end: calc(var(--space-section, 1rem) + var(--dock-safe-area-offset, env(safe-area-inset-bottom, 0px)));
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .bao-fab {
      transition-property: none;
      transition-duration: var(--duration-instant, 0ms);
    }
    .bao-fab[data-fab-hidden="true"] {
      transform: none;
    }
  }
.bao-fab {
  border-radius: var(--radius-full);
}

@media (min-width: 40rem) and (max-width: 63.99rem) {
  .btn[data-header-primary-fab-mirrored]:not([hidden]):not(.hidden) {
    display: none;
  }
}

@media (max-width: 47.99rem) {
  [data-page="capture"] [data-capture-workbench] {
    flex: 0 0 auto;
    min-height: 0;
  }

  [data-page="capture"] [data-capture-workbench-primary] {
    flex: 0 0 auto;
    min-height: 0;
    order: 0;
    width: 100%;
  }

  [data-page="capture"] [data-camera-preview].bao-capture-preview {
    flex: 0 0 auto;
    min-height: 0;
  }
  /*
   * Live: the stream owns a 4:3 frame and the bar floats over it. Idle: the
   * placeholder and the in-flow capture bar size the stage — a fixed ratio
   * under overflow:hidden clipped the two-row phone bar (measured 390x844:
   * a 146px bar inside a 281px frame).
   */
  [data-page="capture"] [data-camera-preview].bao-capture-preview[data-preview-state="live"] {
    aspect-ratio: 4 / 3;
  }
}
[data-primitive="ScannerPreview"].bao-scanner-preview {
  aspect-ratio: 16 / 9;
  flex: 0 0 auto;
  min-height: 0;
}


  /*
   * PIN pad default: hidden at all widths. The top-level phone + tablet
   * media (max-width 63.99rem) below turns it into a 3-column grid. The
   * default must stay outside any media so desktop never paints an
   * on-screen keypad as a block div.
   */
  /* Auth submit labels remain complete when enlarged text needs another line.
   * The auth state owns this exception to compact shared button dimensions. */
  [data-page="login"] .bao-login-sticky-submit {
    block-size: auto;
    flex-wrap: wrap;
    white-space: normal;
  }
  [data-page="login"] .bao-login-sticky-submit > span {
    min-inline-size: 0;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* Scanner actions wrap when enlarged touch targets exhaust the credential row.
   * At normal text size the growing input and both scanner controls still share one row. */
  [data-page="login"] [data-login-scanner-root] .join > [data-operator-credential] {
    flex-basis: var(--touch-target-min);
  }
  .bao-login-pin-pad {
    display: none;
    width: 100%;
    min-width: 0;
  }
  .bao-login-pin-pad-spacer {
    display: none;
    min-width: 0;
    min-height: 0;
    pointer-events: none;
  }
  /* Login PIN otp — solid daisyUI slots; password input remains SSOT. */
  [data-page="login"] .bao-login-pin-otp.otp {
    width: 100%;
    justify-content: center;
    margin-block-end: var(--space-micro, 0.25rem);
  }
  [data-page="login"] .bao-login-pin-otp input[data-operator-pin] {
    letter-spacing: var(--tracking-otp, 0.35em);
  }
  /*
   * OTP tracking applies to the entered digits only. Letting it inherit into
   * the placeholder stretches the prompt copy into unreadable spaced capitals.
   */
  [data-page="login"] .bao-login-pin-otp input[data-operator-pin]::placeholder {
    letter-spacing: normal;
  }
  [data-page="login"] .bao-login-pin-otp + .validator-hint {
    display: none;
  }
  [data-page="login"] .bao-login-pin-otp:has(input:user-invalid) + .validator-hint {
    display: block;
  }
  /*
   * daisyUI OTP slots paint four slot marks over the pin input. The input
   * must stay directly clickable (HIG clickable area, keyboard parity):
   * pointer-events:none keeps the slot marks as visual chrome only, so a
   * click anywhere on the label line focuses the password field itself.
   */
  [data-page="login"] .bao-login-pin-otp span {
    pointer-events: none;
  }
  /*
   * Dock clearance SSOT — must beat higher-specificity gutter rules
   * (#app-main:not(:has(...))) and WebView 101 data-main-page-type mirrors.
   * Permanent rail (≥80rem) has no fixed dock; clearance drops to zero there.
   */
  @media (max-width: 79.99rem) {
    html[data-plane="workbench"] #app-main,
    html[data-plane="workbench"] #app-main:not([data-main-page-type="media"]):not([data-main-page-type="conversation"]),
    html[data-plane="workbench"] #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
      padding-bottom: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--space-section, 0.75rem) + var(--touch-target-min, 2.75rem));
      padding-block-end: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--space-section, 0.75rem) + var(--touch-target-min, 2.75rem));
    }
    html[data-plane="workbench"] #app-main[data-main-page-type="media"],
    html[data-plane="workbench"] #app-main[data-main-page-type="conversation"],
    html[data-plane="workbench"] #app-main:has([data-page-type="media"]),
    html[data-plane="workbench"] #app-main:has([data-page-type="conversation"]) {
      padding-bottom: calc(var(--dock-height) + var(--dock-safe-area-offset));
      padding-block-end: calc(var(--dock-height) + var(--dock-safe-area-offset));
    }
  }
  @media (min-width: 80rem) {
    html[data-plane="workbench"] #app-main,
    html[data-plane="workbench"] #app-main:not([data-main-page-type="media"]):not([data-main-page-type="conversation"]),
    html[data-plane="workbench"] #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
      padding-bottom: var(--page-gutter-block, var(--space-section, 0.75rem));
      padding-block-end: var(--page-gutter-block, var(--space-section, 0.75rem));
    }
  }
  /*
   * On-screen PIN keypad — phone + tablet (max-width 64rem).
   * Touch-first enterprise badge stations use pad; desktop uses keyboard
   * password field only (base display:none outside this media).
   * Do not use HTML hidden for the pad (phone/tablet CSS owns visibility).
   *
   * Top level, NOT nested inside the phone densify media below: a nested
   * max-width query intersects with the phone band's own range, so the pad
   * vanishes on tablet widths — measured hidden at 768px. Commit b7cf8a580
   * re-nested this block from a stale editor buffer; the browser geometry
   * suite ui-ssot-login-pin-otp-geometry presses a pad key at 768px and
   * fails if the block ever moves back inside the phone media.
   */
  @media (max-width: 63.99rem) {
    [data-page="login"] [data-login-pin-pad].bao-login-pin-pad,
    [data-page="login"] [data-login-pin-pad] {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: var(--space-micro, 0.25rem);
      margin-block-start: var(--space-micro, 0.25rem);
    }
    [data-page="login"] .bao-login-pin-pad-spacer {
      display: block;
      min-height: var(--touch-target-min, 2.75rem);
    }
    [data-page="login"] [data-login-pin-pad] button {
      min-height: var(--touch-target-min, 2.75rem);
      justify-content: center;
      align-items: center;
      font-size: var(--text-lg, 1.125rem);
      font-weight: 600;
      line-height: 1;
      transition: transform var(--duration-fast, 120ms) var(--easing-standard, cubic-bezier(0.2, 0, 0, 1));
    }
    [data-page="login"] [data-login-pin-pad] button:active {
      transform: scale(0.96);
    }
    @media (prefers-reduced-motion: reduce) {
      [data-page="login"] [data-login-pin-pad] button {
        transition: none;
      }
      [data-page="login"] [data-login-pin-pad] button:active {
        transform: none;
      }
    }
    [data-page="login"] [data-login-pin-pad] button svg {
      width: 1.25rem;
      height: 1.25rem;
    }
  }
  /* Phone densify: tighter page gutters so workbench fills the docked viewport. */
  @media (max-width: 47.99rem) {
    html[data-plane="workbench"] #app-main,
    html[data-plane="workbench"] #app-main:not([data-main-page-type="media"]):not([data-main-page-type="conversation"]),
    html[data-plane="workbench"] #app-main:not(:has([data-page-type="media"])):not(:has([data-page-type="conversation"])) {
      padding-inline: var(--space-inline, 0.5rem);
      padding-block-start: var(--space-micro, 0.25rem);
      padding-bottom: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--space-section, 0.75rem) + var(--touch-target-min, 2.75rem));
      padding-block-end: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--space-section, 0.75rem) + var(--touch-target-min, 2.75rem));
    }
    html[data-plane="workbench"] #app-main[data-main-page-type="media"],
    html[data-plane="workbench"] #app-main[data-main-page-type="conversation"],
    html[data-plane="workbench"] #app-main:has([data-page-type="media"]),
    html[data-plane="workbench"] #app-main:has([data-page-type="conversation"]) {
      padding-inline: 0;
      padding-block-start: 0;
      padding-bottom: calc(var(--dock-height) + var(--dock-safe-area-offset));
      padding-block-end: calc(var(--dock-height) + var(--dock-safe-area-offset));
    }
    [data-page="settings"] .bao-settings-group {
      padding: var(--space-inline, 0.5rem);
    }
    [data-page="settings"] .bao-tabs-scroll {
      margin-inline: 0;
    }
    /* Capture context grid: 2-up on phone, not 4 tiny truncated chips. */
    [data-page="capture"] [data-capture-context-strip],
    [data-capture-context-strip] {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    /*
     * Login AuthShell densify — single card, phone-first scan strip, no dock.
     * Hides chrome that only applies to authenticated workbench pages.
     */
    [data-page="login"] {
      max-width: 100%;
      width: 100%;
      padding-inline: 0;
      padding-block-end: var(--space-section, 0.75rem);
    }
    /* Kill nested daisyUI fieldset border cages inside solid AuthShell panel. */
    [data-page="login"] [data-auth-panel] fieldset,
    [data-page="login"] [data-auth-panel] .fieldset,
    [data-page="login"] [data-operator-login] fieldset.fieldset {
      border: 0;
      padding: 0;
      margin: 0;
      background: transparent;
      box-shadow: none;
      outline: none;
    }
    [data-page="login"] [data-auth-panel] .fieldset-legend,
    [data-page="login"] [data-auth-panel] legend {
      padding-inline: 0;
      margin-bottom: var(--space-micro, 0.25rem);
      float: none;
    }
    /* Phone: AuthShell full-bleed; brand mark must not dominate first viewport. */
    [data-page="login"] [data-operator-login] {
      width: 100%;
      max-width: none;
      padding-inline: var(--space-inline, 0.5rem);
    }
    [data-page="login"] [data-operator-login] > .relative.z-content-overlay,
    [data-page="login"] [data-operator-login] > [class*="max-w-auth"] {
      max-width: none;
      width: 100%;
    }
    [data-page="login"] [data-auth-panel] {
      width: 100%;
      border-radius: var(--radius-box, 0.5rem);
    }
    [data-page="login"] .size-brand-mark {
      width: 1.75rem;
      height: 1.75rem;
    }
    [data-page="login"] .size-brand-mark svg {
      width: 1rem;
      height: 1rem;
    }
    [data-page="login"] [data-operator-login] {
      justify-content: flex-start;
      /* Dense AuthShell so 1280×720 desktop smoke fits without vertical overflow. */
      padding-block: var(--space-micro, 0.25rem);
      gap: var(--space-micro, 0.25rem);
      min-height: 0;
    }
    [data-page="login"] [data-operator-login] .card-body {
      padding: var(--space-inline, 0.5rem);
      gap: var(--space-micro, 0.25rem);
    }
    [data-page="login"] [data-state-content] {
      gap: var(--space-micro, 0.25rem);
    }
    /* Non-success auth states stay out of layout when [hidden]. */
    [data-page="login"] [data-state]:not([data-state="success"])[hidden] {
      display: none;
    }
    [data-page="login"] [data-login-scanner-root] [data-login-nfc-scan],
    [data-page="login"] [data-login-scanner-root] [data-login-barcode-scan] {
      min-width: var(--touch-target-min, 2.75rem);
      min-height: var(--touch-target-min, 2.75rem);
      justify-content: center;
    }
    [data-page="login"] [data-login-scanner-root] [data-login-barcode-cancel] {
      width: 100%;
      min-height: var(--touch-target-min, 2.75rem);
      justify-content: center;
    }
    /*
     * Badge and PIN inputs take the HIG floor from the AuthShell
     * scannerJoinInput / pinInput tokens, which carry .bao-touch-target at
     * every width — measured 44px at 375px and 1280px with the join row flush.
     * A duplicate phone-scoped min-height here was a second owner of one
     * contract, and it also listed [data-operator-name], a field
     * pages-renderers-login.test.ts bans from the emitted markup. Only the
     * sticky submit is not an AuthShell input, so only it needs a floor here.
     */
    [data-page="login"] .bao-login-sticky-submit {
      min-height: var(--touch-target-min, 2.75rem);
    }
    /* Sticky primary CTA — short viewports keep Sign-in reachable. */
    [data-page="login"] .bao-login-submit-dock,
    [data-page="login"] .bao-login-sticky-submit {
      position: sticky;
      bottom: 0;
      z-index: var(--z-content-overlay, 10);
    }
    [data-page="login"] .bao-login-submit-dock {
      background: var(--color-base-100);
      padding-block-start: var(--space-micro, 0.25rem);
      padding-block-end: max(var(--space-inline, 0.5rem), env(safe-area-inset-bottom, 0px));
    }
    /* Join scan toggles stay icon-first; cancel reveals as full-width soft. */
    [data-page="login"] [data-login-scanner-root] .join .bao-toolbar-label {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    [data-page="login"] [data-login-barcode-preview-shell]:not([hidden]) {
      margin-block-start: var(--space-micro, 0.25rem);
    }
    /*
     * Phone densify (≤47.99rem): demote credential helper copy to AT-only.
     * Placeholders + field legends carry the scan path (HIG density).
     */
    [data-page="login"] #operator-login-credential-hint {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }

/* Collection phone: keep last row above fixed dock for reachability centres. */
@media (max-width: 47.99rem) {
  html[data-plane="workbench"] #app-main:has([data-page-type="collection"]),
  html[data-plane="workbench"] #app-main[data-main-page-type="collection"] {
    padding-block-end: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
    scroll-padding-bottom: calc(var(--dock-height) + var(--dock-safe-area-offset) + var(--touch-target-min, 2.75rem) + var(--space-section, 0.75rem));
  }
}

    /* Chat context: horizontal chip rail, keep ≥44px touch. */
    [data-page="chat"] [data-chat-context],
    [data-chat-context] {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      align-items: center;
      gap: var(--space-micro);
      overflow-x: auto;
      overscroll-behavior-x: contain;
      -webkit-overflow-scrolling: touch;
    }
    /*
     * Context-more dropdown escape hatch (same layer, later position): the
     * chip rail's overflow-y clip amputates the open menu on phone widths
     * and the strip's backdrop-filter traps its stacking context under the
     * message thread. Unclip and lift only while the menu is open.
     */
    [data-page="chat"] [data-chat-context]:has([data-chat-context-more][open]),
    [data-chat-context]:has([data-chat-context-more][open]) {
      overflow-x: visible;
      overflow-y: visible;
      z-index: var(--z-dropdown, 1250);
    }
    [data-page="chat"] [data-chat-context] button,
    [data-chat-context] button {
      min-height: var(--touch-target-min, 2.75rem);
    }
    /*
     * Collection FilterBar densify — 2-col phone grid (Apple dense filters).
     * Search + action row span full width; status/priority pair side-by-side.
     * Replaces column stack that burned 3+ viewport rows on cases/billing.
     */
    [data-page-type="collection"] [data-primitive="FilterBar"],
    [data-page="cases"] [data-case-filters],
    [data-case-filters],
    [data-primitive="FilterBar"],
    [data-filter-bar] {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      justify-content: stretch;
      height: auto;
      min-height: 0;
    }
    [data-case-filters] fieldset,
    [data-case-filters] .fieldset,
    [data-primitive="FilterBar"] fieldset,
    [data-primitive="FilterBar"] .fieldset,
    [data-filter-bar] fieldset,
    [data-filter-bar] .fieldset {
      height: auto;
      align-self: stretch;
    }
    [data-case-filters] fieldset:has(input[type="search"]),
    [data-case-filters] .fieldset:has(input[type="search"]),
    [data-case-filters] fieldset:has([data-case-search]),
    [data-primitive="FilterBar"] fieldset:has(input[type="search"]),
    [data-primitive="FilterBar"] .fieldset:has(input[type="search"]),
    [data-primitive="FilterBar"] fieldset:has([data-filter-search]),
    [data-filter-bar] fieldset:has(input[type="search"]),
    /* Actions row only — fieldsets also use class "flex"; never select them. */
    [data-case-filters] > [data-filter-actions],
    [data-primitive="FilterBar"] > [data-filter-actions],
    [data-case-filters] > div.flex.shrink-0,
    [data-primitive="FilterBar"] > div.flex.shrink-0,
    [data-case-filters] > div.flex.items-center,
    [data-primitive="FilterBar"] > div.flex.items-center {
      grid-column: 1 / -1;
    }
    [data-case-filters] .select,
    [data-case-filters] .input,
    [data-primitive="FilterBar"] .select,
    [data-primitive="FilterBar"] .input {
      width: 100%;
      min-height: var(--touch-target-min, 2.75rem);
    }
    [data-case-filters] .btn,
    [data-primitive="FilterBar"] .btn {
      min-height: var(--touch-target-min, 2.75rem);
      justify-content: center;
    }
    /* Reset/clear stays full-width only when alone in the actions row. */
    [data-case-filters] > .flex .btn,
    [data-primitive="FilterBar"] > .flex .btn {
      width: auto;
      min-width: var(--touch-target-min, 2.75rem);
    }
    /* Capture context: 2-up; chat context stays the horizontal rail above. */
    [data-capture-context-strip] {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--space-micro);
    }
    [data-capture-context-strip] button,
    [data-capture-context-strip] [data-capture-context-button] {
      min-height: var(--touch-target-min, 2.75rem);
      max-width: 11rem;
    }
    /*
     * Editor/capture context badges — long image UUIDs single-line ellipsis
     * (never multi-line wrap blowout on phone chips).
     */


    /* Topbar context chips: single owner is tokens-glass-shell-context-density.ts. */
    /*
     * Collection pages (models, licensing, specimens, …): WebView 101 lacks
     * :has() — force single-column panels and full-width form controls.
     * Never fork per data-page id.
     */

    [data-page-type="collection"] .card-body,
    [data-page-type="collection"] .bao-data-panel,
    [data-page-type="collection"] [data-primitive="DataTable"],
    [data-page-type="collection"] .overflow-x-auto {
      max-width: 100%;
      min-width: 0;
    }
    [data-page-type="collection"] table {
      max-width: 100%;
    }
    [data-page-type="collection"] [role="tab"].tab,
    [data-page-type="collection"] [data-lifecycle-tab] {
      min-height: var(--touch-target-min, 2.75rem);
      height: auto;
    }
    [data-page-type="collection"] form .input,
    [data-page-type="collection"] form .select,
    [data-page-type="collection"] form .btn {
      width: 100%;
      min-height: var(--touch-target-min, 2.75rem);
    }
    [data-page="specimens"] [data-specimen-create-form] .btn {
      width: auto;
      min-width: var(--touch-target-min, 2.75rem);
    }
    /* Pagination join controls always ≥44px on phone. */
    [data-pagination] .btn,
    .join .btn.join-item {
      min-height: var(--touch-target-min, 2.75rem);
      min-width: var(--touch-target-min, 2.75rem);
    }
    /*
     * Collection sticky toolbars (evidence, models, …): ONE horizontal rail.
     *
     * The rail is the design this comment always described; wrapping was what
     * it actually did. Measured at 320x720: /reports wrapped 8 soft actions
     * into 246px of STICKY toolbar and /evidence 5 into 198px — a third of
     * the viewport, pinned, painting over the form beneath it. Fifteen
     * controls on /reports lost their centre hit test to that slab
     * (browser-touch-reachability, blocker div.rounded-field at z-index
     * 1100). The wrap was sized for the two or three actions these bars
     * carried when it was written and does not survive eight.
     *
     * `nowrap` with the overflow-x below turns it back into the rail: one
     * 52px row that scrolls, with .bao-scroll-edge-x fading the cut edge so
     * the overflow is visible. The primary CTA leads (order: -1) and sizes to
     * its own label rather than claiming a full-width second row, so the
     * essential action needs no scrolling and long verbs still never clip —
     * they scroll instead of breaking mid-word.
     */
    [data-page-type="collection"] [role="toolbar"] {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      align-items: center;
      justify-content: flex-start;
      gap: var(--space-micro);
      overflow-x: auto;
      overscroll-behavior-x: contain;
      -webkit-overflow-scrolling: touch;
      max-width: 100%;
    }
    [data-page-type="collection"] [role="toolbar"] .btn {
      flex: 0 0 auto;
      min-height: var(--touch-target-min, 2.75rem);
      min-width: var(--touch-target-min, 2.75rem);
    }
    /* Why-tip hosts wrap disabled primaries — the host is the flex item. */
    [data-page-type="collection"] [role="toolbar"] > .btn-primary,
    [data-page-type="collection"] [role="toolbar"] > [data-disabled-tip-host]:has(.btn-primary),
    [data-page-type="collection"] [role="toolbar"] > span:has(> .btn-primary),
    [data-page-type="collection"] [role="toolbar"] > span:has(> button.btn-primary) {
      flex: 0 0 auto;
      order: -1;
      max-width: 100%;
      width: auto;
    }
    [data-page-type="collection"] [role="toolbar"] .btn-primary {
      justify-content: center;
      width: auto;
      max-width: 100%;
    }
    [data-page-type="collection"] [role="toolbar"] .btn-primary .bao-toolbar-label {
      display: inline-block;
      max-width: none;
      margin-inline-start: var(--space-micro, 0.25rem);
      opacity: 1;
      overflow: visible;
      white-space: nowrap;
    }

  }/*
   * Touch SSOT — header actions + settings tabs must meet 44px on phone /
   * BlueStacks coarse pointers (btn-sm alone is ~32–36px).
   */
  @media (max-width: 47.99rem),
(pointer: coarse) {
    [data-page-header-actions] .btn {
      height: auto;
    }
    [data-settings-tab].tab,
[data-settings-tab].bao-settings-tab,
.tab.bao-settings-tab,
.bao-session-mode-tab.tab,
[data-session-mode-tab].tab,
[data-capture-context-strip] button,
[data-chat-context] button,
.bao-context-card {
      min-height: var(--touch-target-min, 2.75rem);
      height: auto;
      min-width: var(--touch-target-min, 2.75rem);
    }
    /*
     * Icon-first density (Apple HIG phone chrome) — hide verb captions under
     * toolbar actions. Single owner for: shell topbar, page headers, chat
     * composer, capture action strips, editor chrome toolbars. Never
     * per-page forks.
     * Page-header soft/primary clinical CTAs (finish case, generate report,
     * new case) keep permanent verbs — touch has no hover to reveal them
     * (design.pdf §6); the labeled reveal lives in glass-shell-page-header.
     *
     * Clipped, NOT display:none. The caption is the only accessible name
     * these controls have — the glyph is aria-hidden and S123 (one
     * accessible name) forbids also stamping aria-label, so removing the
     * caption from the box tree removes the name outright. Proven by the
     * rendered-tree audit: axe reported button-name critical on
     * /editor@phone for the tool-filter buttons, whose captions this rule
     * collapsed. sr-only clipping keeps them silent to sighted users and
     * intact for assistive tech.
     *
     * The editor dock is NOT in this list. It declares its own label density
     * through data-toolbar-density, which tokens-glass-chrome-body.ts owns;* clipping it here made this file a second owner of that pixel and the
     * dock's declaration meaningless. Density is the contract — a surface that
     * asks for labels gets labels.
     */
    [data-chat-composer] .bao-toolbar-action .bao-toolbar-label,
#app-header .bao-toolbar-action .bao-toolbar-label,
[data-page-header-actions] .bao-toolbar-action:not(.btn-primary):not(.btn-soft) .bao-toolbar-label,
[data-page-header-actions] .btn:not(.btn-primary):not(.btn-soft) .bao-toolbar-label,
[data-capture-action-bar] .bao-toolbar-action .bao-toolbar-label,
[data-primitive="ExportBar"] .bao-toolbar-action .bao-toolbar-label {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
    }
    /*
     * Topbar densify — hide brand detail + long status chrome;keep icon
     * actions at 44px. Context chips stay horizontal scroll (not 5-up crush).
     */
    .bao-shell-brand-detail,
.navbar .bao-brand-detail,
#app-header .bao-brand-detail {
      display: none;
    }#app-header .navbar-end .btn,
#app-header .navbar-end button.btn,
#app-header .navbar-end .bao-shell-status-badge,
#app-header .navbar-end [data-registry-status],
[data-page-header-actions] .btn,
[data-page-header-actions] a.btn {
      /* HIG Dynamic Type: the rem floor computes 176px at root font 400%,
         and three such controls in the actions track overflow the 390px
         viewport (badge left measured -187px). The vw term is font-invariant,
         so the floor stays 44px at the default font and never outgrows a
         narrow viewport at 200%/400% text. Mirrors the icon-action guard in
         tokens-glass-shell-topbar-policy.css.bao at equal specificity so
         source order cannot reintroduce the escape. */
      min-height: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
      min-width: min(var(--touch-target-min, 2.75rem), max(12vw, 44px));
    }
    /* Status badges: label is sr-only on phone;keep HIG 44px hit target. */
    #app-header .bao-shell-status-badge [data-status-label],
#app-header [data-registry-status] [data-status-label] {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    /* Square primary CTAs on phone when caption is hidden (finish/new case). */
    [data-page-header-actions] .btn:not(.btn-square):has(> svg):has(> .bao-toolbar-label) {
      padding-inline: var(--toolbar-action-pad-inline, 0.625rem);
    }
/*
     * Collection dual-panel forms (licensing credentials | serials,
models
     * lifecycle | discover): one full-width column — never half-width crush.
     * Reinforces page-shell-surfaces without per-page forks.
     */
    [data-page-type="collection"] .bao-grid-reactive-panels {
      grid-template-columns: minmax(0, 1fr);
    }
    [data-page-type="collection"] .bao-grid-reactive-panels > * {
      grid-column: 1 / -1;
      max-width: none;
      min-width: 0;
    }
    /*
     * FilterBar phone densify — 2-col for select/status filters;search +
     * actions span full width. Beats max-sm:flex-col stack that wasted 3+
     * viewport rows on cases/billing/models (Apple dense filter pattern).
     * Specificity must beat utility flex class on the same host (no !important).
     */
    #app-main [data-primitive="FilterBar"],
#app-main div[data-primitive="FilterBar"] {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      align-items: end;
      gap: var(--space-micro, 0.25rem);
    }#app-main [data-primitive="FilterBar"] > .fieldset,
#app-main [data-primitive="FilterBar"] > fieldset {
      min-width: 0;
      width: 100%;
    }#app-main [data-primitive="FilterBar"] > .fieldset:has(input[type="search"]),
#app-main [data-primitive="FilterBar"] > fieldset:has(input[type="search"]),
#app-main [data-primitive="FilterBar"] > .fieldset:has([data-filter-search]),
#app-main [data-primitive="FilterBar"] > fieldset:has([data-filter-search]),
#app-main [data-primitive="FilterBar"] > .fieldset:has([data-case-search]),
#app-main [data-primitive="FilterBar"] > [data-filter-actions],
#app-main [data-primitive="FilterBar"] > div.flex.shrink-0,
#app-main [data-primitive="FilterBar"] > div.flex.items-center {
      grid-column: 1 / -1;
    }/*
     * Form field helpers restate the label/placeholder on phone — hide the
     * visual row,
keep aria-describedby for AT (sr-only geometry).
     */
    #app-main [data-field-helper] {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border-width: 0;
    }/*
     * Empty AI overlay hosts must not reserve a tall gray slab on phone —
     * empty copy + border is enough (Apple form density).
     */
    #app-main [data-ai-overlays-empty],
#app-main [data-ai-overlays-empty] .bao-min-h-panel-body,
#app-main [data-page="ai-analysis"] [data-primitive="VisualizationLayers"][data-state="empty"],
#app-main [data-page="ai-analysis"] [data-primitive="VisualizationLayers"] .bao-min-h-panel-body {
      min-height: 0;
    }
  }/*
   * Phone FilterBar legends stay VISIBLE for select groups: status and
   * priority both default to the same "All" text, so hiding legends left two
   * identical unlabeled selects (HIG: controls identifiable without a
   * tooltip). The sr-only clip for value-carrying search groups is owned by
   * tokens-glass-shell-controls-b (search-scoped, sentence case). This
   * blanket width-only clip was removed: its #app-main id outranked that SSOT
   * and re-hid every phone legend.
   */

}
@layer bao-tokens {
@media (max-width: 47.99rem) {
  [data-page-type="media"] .bao-editor-canvas-column {
    min-block-size: 0;
  }
  [data-page-type="media"] .bao-editor-canvas.bao-editor-canvas-host-bounded {
    block-size: auto;
    max-block-size: none;
    max-height: none;
    border-radius: 0;
    border-inline-width: 0;
  }
}

/*
 * One edge-anchored icon row at every width, deliberately.
 *
 * The gesture twins (orbit/pan/zoom) are a single row pinned to the canvas edge;
 * `tokens-layout-regressions-capture` pins that contract by name. Wrapping them
 * would push controls out over the canvas they manipulate, so this row scrolls
 * instead. Native scrollbar presentation is owned by tokens-utilities-scroll-edge;
 * this row must not hide its scrollbar. The `.bao` token's `flex-wrap` never
 * applied here — this rule is the owner.
 */
[data-editor-3d-controls] {
  /* Floating centred pill over the scene (HIG media controls); the glass
     surface reset paints position:relative, so the owner states absolute. */
  position: absolute;
  /* Both inline insets + auto margins centre the pill at its content width
     with the whole viewport available; a 50% inset + translate capped
     max-content at half the container (measured 2026-08-23 at 390x844: a
     181px pill scrolled past its first controls). */
  inset-inline: var(--space-inline, 0.5rem);
  inset-block-end: var(--space-inline, 0.5rem);
  margin-inline: auto;
  inline-size: fit-content;
  max-inline-size: calc(100% - 2 * var(--space-inline, 0.5rem));
  /* Centred content in a scroll container clips its start; safe centring
     falls back to start alignment once the pill overflows (phone). */
  justify-content: flex-start;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* No scroll snap on a toolbar scroller: Chromium snaps a fresh overflow
     row to an item on first layout — measured 2026-08-23 at 390x844 the
     pill opened scrolled past its first four controls. */
  touch-action: pan-x;
}
[data-editor-3d-controls] > button {
  flex: 0 0 var(--touch-target-min, 2.75rem);
}
#app-main [data-editor-3d-controls] button[aria-label] .bao-toolbar-label {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
#app-main [data-editor-3d-controls] button[aria-label] {
  inline-size: var(--touch-target-min, 2.75rem);
  min-inline-size: var(--touch-target-min, 2.75rem);
  flex-basis: var(--touch-target-min, 2.75rem);
  padding-inline: 0;
}

}
@layer bao-tokens {
/*
 * Sheet-band tool-rail axis lives in
 * tokens-layout-editor-toolbar-container-presentation ([data-editor-tool-rail]).
 * This archive no longer restates overflow, label clip, or spacer hide against
 * a canvas-column toolbar that the workbench grid does not emit.
 */

}
@layer bao-tokens {
/*
 * Compact inspector sheet stacks on --z-modal, above in-flow workbench chrome.
 * Named `editor-workbench`: an unnamed query rebinds to the nearest container.
 */
@container editor-workbench (max-width: 48rem) {
  .bao-editor-workbench[data-editor-open-surface="inspector"] > [data-editor-canvas-overlays] {
    z-index: var(--z-modal, 1300);
  }
}

}
@layer bao-tokens {
[data-editor-tool-rail] [data-editor-toolbar-controls],
[data-editor-tool-rail] [data-tool-group-label] {
  display: none;
}

[data-editor-tool-rail] [data-tool-group][hidden] {
  display: none;
}

[data-editor-tool-rail] [data-editor-more-tools] {
  display: inline-flex;
}

[data-editor-tool-catalog] {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-inline, 0.5rem);
}

/* Category segments keep their full label width inside the joined scroller. */
[data-editor-tool-catalog] [data-editor-tool-filter] {
  flex: 0 0 auto;
  min-inline-size: max-content;
}

[data-editor-tool-catalog] [data-tool-group] {
  min-width: 0;
  max-width: 100%;
}

[data-editor-tool-catalog] [data-tool-group]:not([data-tool]) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
  align-items: start;
  gap: var(--space-micro, 0.25rem);
}

[data-editor-tool-catalog] [data-tool-group][hidden] {
  display: none;
}

[data-editor-tool-catalog] [data-tool-group-label] {
  grid-column: 1 / -1;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
}

[data-editor-tool-catalog] [data-tool] {
  width: 100%;
  min-width: 0;
  justify-content: flex-start;
  gap: var(--space-inline, 0.5rem);
  padding-inline: var(--space-inline, 0.5rem);
}

#app-main [data-editor-tool-catalog] .bao-toolbar-action.btn-square .bao-toolbar-label {
  display: inline-block;
  max-width: var(--toolbar-label-reveal-max-width, 12rem);
  margin-inline-start: var(--space-micro, 0.25rem);
  opacity: 1;
  overflow: hidden;
  white-space: nowrap;
}

@media (max-width: 47.99rem) {
  #app-main [data-editor-tool-catalog] [data-editor-tool-filter] .bao-toolbar-label {
    position: static;
    display: inline;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: nowrap;
  }

  [data-editor-tool-catalog] [data-tool-group]:not([data-tool]) {
    grid-template-columns: minmax(0, 1fr);
  }
}

  [data-editor-tool-catalog] fieldset[data-tool-group]:not([data-tool]) {
    inline-size: 100%;
    min-inline-size: 0;
    margin: 0;
    padding: 0;
    border: 0;
  }

  /* Tool color and selection use the shared toolButton/toolButtonActive tokens. */

  @media (forced-colors: active) {
    [data-editor-toolbar] [data-tool-group][data-tool],
    [data-editor-tool-catalog] [data-tool-group][data-tool] {
      border-color: ButtonText;
      color: ButtonText;
    }
  }

  #app-main [data-editor-tool-catalog] .btn.bao-toolbar-action.btn-square[data-tool] {
    width: 100%;
    min-width: 0;
    justify-content: flex-start;
    gap: var(--space-inline, 0.5rem);
    padding-inline: var(--space-inline, 0.5rem);
  }
[data-editor-tool-palette-dialog] [data-editor-tool-catalog] > [data-editor-toolbar-controls] {
  flex: 0 0 auto;
}
@media (min-width: 48rem) and (max-height: 32rem) {
  [data-page-type="media"] .bao-media-page-root {
    position: relative;
    gap: 0;
  }

  [data-page-type="media"] .bao-media-page-root > [data-primitive="PageHeader"] {
    position: absolute;
    inset-block-start: 0;
    inset-inline-end: var(--page-gutter-inline, var(--space-section));
    z-index: var(--z-canvas-overlay);
    width: auto;
    min-width: 0;
    padding-block-end: 0;
    border-block-end: 0;
  }

  [data-page-type="media"] [data-editor-context-bar] {
    display: none;
  }

  [data-page-type="media"] [data-session-mode-tabs]:not([data-session-rail-mode-tabs]) {
    position: relative;
    inset: auto;
    z-index: auto;
  }

  [data-page-type="media"] .bao-editor-workbench,
  [data-page-type="media"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-canvas-column,
  [data-page-type="media"] .bao-editor-canvas {
    min-height: 0;
    max-height: 100%;
  }


  [data-page-type="media"] [data-editor-toolbar] .bao-toolbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  [data-page-type="media"] .bao-editor-canvas-column > [data-editor-filmstrip-disclosure] > [data-editor-filmstrip] {
    position: absolute;
    inset-block-start: 0;
    inset-inline-end: 0;
    z-index: var(--z-canvas-overlay);
    inline-size: calc(50% - var(--space-micro));
    margin: 0;
  }
}

}
@layer bao-tokens {
/* Bottom action bar: discard leading, save form trailing. */
.bao-editor-bottom-bar {
  display: flex;
  min-width: 0;
  flex-shrink: 0;
  align-items: center;
  gap: var(--space-inline, 0.5rem);
  border-block-start: 1px solid color-mix(in oklab, var(--color-base-300) 60%, transparent);
  background-color: var(--color-base-100);
  padding-inline: var(--space-inline, 0.5rem);
  padding-block: var(--space-micro, 0.25rem);
}

}
@layer bao-tokens {
[data-editor-tool-palette-dialog] [data-editor-tool-catalog] {
  overscroll-behavior-y: contain;
}

/* Dialog dismissal is a labeled action, not an unexplained glyph. */
[data-editor-tool-palette-dialog] [data-editor-tool-palette-close].btn-square {
  inline-size: auto;
  min-inline-size: var(--touch-target-min);
  padding-inline: var(--space-inline);
  gap: var(--space-micro);
}
[data-editor-tool-palette-dialog] [data-editor-tool-palette-close] .bao-toolbar-label {
  position: static;
  inline-size: auto;
  block-size: auto;
  max-inline-size: none;
  margin: 0;
  opacity: 1;
  overflow: visible;
  clip: auto;
  clip-path: none;
}

@media (min-width: 48rem) {
  [data-editor-tool-palette-dialog] .bao-modal-body-scroll {
    max-block-size: var(--modal-scroll-max-height);
  }
}

.bao-editor-tool-palette-drag-handle {
  display: none;
}

@media (max-width: 47.99rem) {
  .bao-editor-tool-palette-drag-handle {
    display: block;
    width: var(--touch-target-min, 2.75rem);
    height: var(--space-micro, 0.25rem);
    margin: var(--space-micro, 0.25rem) auto 0;
    border-radius: var(--radius-full);
    background: color-mix(in oklab, var(--color-base-content) 30%, transparent);
    touch-action: pan-y;
  }

  [data-editor-tool-palette-dialog][data-editor-tool-palette-dismiss-pending] .modal-box {
    animation: bao-editor-tool-palette-dismiss var(--duration-fast, 120ms) var(--easing-standard) forwards;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-editor-tool-palette-dialog][data-editor-tool-palette-dismiss-pending] .modal-box {
    animation: none;
  }
}

@keyframes bao-editor-tool-palette-dismiss {
  to {
    transform: translateY(100%);
    opacity: 0;
  }
}

}
@layer bao-tokens {

  [data-editor-tool-palette-dialog] [data-editor-tool-palette-close] {
    flex: 0 0 auto;
  }

}
@layer bao-tokens {
/* Palette control layout is owned by enterprise-page.bao toolbarControls and toolbarFilterGroup. */

}
@layer bao-tokens {

/*
 * Annotation is canvas-first. A persisted expanded navigation preference is
 * useful elsewhere, but it cannot consume the editor canvas at tablet width.
 *
 * Two scopes, one declaration set, because neither signal covers both cases:
 * the SSR body stamp is correct on first paint and in WebViews without :has(),
 * and the :has() branch keeps the rule live across an HTMX swap that replaces
 * the page without a document load. app-shell-page-type.ts re-stamps the body
 * attribute after each swap, so the two agree once script runs.
 */
@media (min-width: 48rem) and (max-width: 79.99rem) {
  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned,
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned,
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned,
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned {
    grid-template-columns: var(--sidebar-collapsed) minmax(0, 1fr);
  }

  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .menu-title,
  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-expanded-only],
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .menu-title,
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-expanded-only],
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .menu-title,
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-expanded-only],
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .menu-title,
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-expanded-only] {
    display: none;
  }

  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item,
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item,
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item,
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item {
    justify-content: center;
    padding-inline: 0;
  }

  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::before,
  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::after,
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::before,
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::after,
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::before,
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::after,
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::before,
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar .bao-nav-item.tooltip::after {
    display: block;
  }

  body[data-shell-page="editor"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle],
  body[data-shell-page="capture"] .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle],
  body:has(#app-main [data-page="editor"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle],
  body:has(#app-main [data-page="capture"]) .bao-shell-drawer.bao-shell-sidebar-pinned #app-sidebar [data-sidebar-rail-toggle] {
    display: none;
  }
}

/*
 * The status HUD is passive: pointer events stay on the specimen, and none of
 * its chips are focus targets. It cannot own a scrollport. Tablet preserves
 * review, save, zoom, and AI provenance in flow while demoting draw telemetry.
 */
@media (min-width: 48rem) and (max-width: 63.99rem) {
  [data-page-type="media"] [data-editor-status-bar] {
    max-block-size: none;
    overflow: visible;
  }

  [data-page-type="media"] [data-editor-status-bar] [data-editor-status="cursor"],
  [data-page-type="media"] [data-editor-status-bar] [data-editor-status="input"],
  [data-page-type="media"] [data-editor-status-bar] [data-editor-status="layer"] {
    display: none;
  }
}
/* Canvas-first tablet pin yield lives in tokens-layout-editor-tablet-canvas-priority.css (editor + capture). This archive stays so the style identity is not deleted. */

/*
 * Immersive maximize, WebView-safe shell band.
 *
 * This archive owns the maximize rules that must hold on engines without
 * :has() AND without @layer support (the Tauri WebView ~101 tier): the
 * session rail is shell chrome, not editor chrome, so no descendant selector
 * from the emission contract can reach it — the body mirror is the only
 * hook. The island, inspector, command band, and tool chrome geometry all
 * live in tokens-daisyui-emission.css.bao (unlayered, with body-mirror
 * selector variants); nothing here may restate or revert them.
 *
 * This archive does not revert island or toolbar position.
 */
body[data-canvas-maximized="true"] #session-rail {
  display: none;
}

/*
 * The immersive stage must paint above the pinned desktop rail.
 *
 * The editor chrome chain carries container-type ancestors (layout
 * containment = stacking contexts), so the island z-index (--z-modal)
 * competes only INSIDE #app-main. The pinned drawer-side follows #app-main
 * in tree order, so at equal z the rail painted over the "fullscreen" canvas:
 * measured 2026-08-23 at 1440x900 maximized, elementFromPoint across the
 * whole inline-start band returned #app-sidebar — a translucent glass rail
 * floating on top of the specimen, swallowing every pointer event beneath
 * it. Raising main itself for the duration of the mode wins on z at the page
 * level (the rail and the canvas never overlap the top bar or the bottom
 * dock geometrically, so nothing else re-stacks); the mobile overlay drawer
 * is untouched because its band sits above the island by geometry.
 */
body[data-canvas-maximized="true"] #app-main {
  z-index: var(--z-local, 1);
}

/* …and the pinned rail band must not out-z it. The utility on drawer-side
   (--z-shell-drawer, 1125) would sit above main's --z-local step outright;
   neutralized to tree order, main wins. The mobile overlay keeps its utility: the
   :not(.drawer-open) guard leaves an open navigation sheet above the
   maximized canvas, where a system panel belongs. */
body[data-canvas-maximized="true"] .bao-shell-drawer:not(.drawer-open) .drawer-side {
  z-index: auto;
}

}
@layer bao-tokens {
/* Reserve paint room for button hover motion inside the tool scroller. */
[data-editor-tool-scroll] {
  padding-block: var(--space-micro, 0.25rem);
}
/* Stage HUD density follows the available canvas width. Below 64rem,
 * the stage title yields to image identity and the command row may wrap.
 * At a measured 872px toolbar width, keeping the title reserved 189px
 * and compressed metadata into a 5px column. The 64rem rung removes
 * only the redundant stage title; identity stays beside commands when it fits. */
@container editor-stage (max-width: 64rem) {
  [data-editor-stage-title] {
    display: none;
  }
  /*
   * Identity outranks telemetry; clinical readouts stay. Never hide the whole
   * status strip: zoom, calibration, review state and save state are the
   * readouts this rung keeps painted. What gives on a narrow stage is the
   * selected model's provenance pair — the AI panel names the model where it
   * is chosen, and measured at 768x1024 the two chips alone pushed the strip
   * onto a second row over the specimen.
   */
  [data-editor-status-bar] [data-editor-status="ai-provider"],
  [data-editor-status-bar] [data-editor-status="ai-model"] {
    display: none;
  }
}
/*
 * Narrow stage: keep Case and Custody; demote Specimen and Image (restated
 * by the case chip and the filmstrip).
 */
@container editor-stage (max-width: 32rem) {
  /* Attribution remains with each layer; compact chrome keeps review and save readouts. */
  [data-editor-status-bar] [data-editor-status="ai-confidence"],
  [data-editor-status-bar] [data-editor-status="ai-shape"] {
    display: none;
  }

  [data-editor-context-bar] > .bao-context-badge[data-editor-context="specimen"] {
    display: none;
  }
  [data-editor-context-bar] > .bao-context-badge {
    max-inline-size: min(10.5rem, 100%);
  }

  /* One line at phone width: identity and readouts share the caption size. */
  [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] > .bao-context-badge {
    font-size: var(--text-xs, 0.75rem);
  }
}
/* Toolbar groups share available inline space and wrap only when their
 * contents need it. A container breakpoint must not reserve empty metadata
 * rows on a roomy stage with few actions or a short document identity. */
[data-editor-stage-toolbar] {
  flex-wrap: wrap;
}
[data-editor-stage-toolbar] > [data-toolbar-zone="leading"] {
  flex: 0 1 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
}
[data-editor-stage-toolbar] > [data-toolbar-zone="trailing"] {
  flex: 0 1 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
  margin-inline-start: auto;
}
/* The document toolbar must preserve actionable clinical identity at every width. */
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-inline);
  min-inline-size: 0;
  max-inline-size: 100%;
  padding-inline-end: 0;
  overflow: visible;
  mask-image: none;
}
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] > .bao-context-badge {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  flex-wrap: wrap;
  white-space: normal;
  overflow-wrap: anywhere;
  overflow: visible;
  text-overflow: clip;
}
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] > .bao-context-badge > .status {
  display: none;
}
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] dd {
  display: flex;
  align-items: center;
  min-inline-size: 0;
  margin: 0;
  overflow: visible;
  text-overflow: clip;
}
/* Readouts wrap as whole facts; passive text cannot rely on scrolling or a fading edge. */
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-status-bar] > [data-editor-status] {
  flex: 0 0 auto;
  overflow: visible;
  text-overflow: clip;
}
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-status-bar] {
  flex-wrap: wrap;
  overflow: visible;
  padding-inline-end: 0;
  mask-image: none;
}

/*
 * The floating palette's footprint: one control plus its own padding and the
 * stage gutter. Declared once on the root so every band reads the same value —
 * the stage pads for it on the inline-start (roomy) or block-end (compact)
 * edge, and the compact inspector sheet lifts above it by the same token
 * (tokens-layout-editor-workbench-grid).
 */
.bao-editor-root {
  /* What the compact inspector sheet must rise above: the horizontal palette
     at the stage's block end (≤40rem root); nothing when the rail is vertical. */
  --editor-sheet-palette-clearance: 0px;
}

/*
 * Tool rail presentation: the axis is a band decision, the markup is one.
 *
 * `renderEditorToolRail` emits one element for every width. Above the sheet
 * band it is the column at the stage's inline start; at or below it, it lies
 * down under the stage as the horizontal dock this surface shipped before.
 * Both forms are the same buttons with the same ids and the same runtime
 * bindings, so nothing downstream has to know which one is on screen.
 *
 * The rail exists because a horizontal band could not hold the tools without
 * taking the room from something else. Measured 2026-08-22 at 1440x900 with
 * the tools in the command bar: the dock was squeezed to 448px against the
 * 926px its labelled row wants, wrapped four rows deep, and stood the command
 * band at 212px of a 746px workbench. In the rail the same nine controls take
 * 396px of BLOCK size — which a 666px stage has and a 1060px row does not —
 * and the command band is one 68px row of document actions.
 */
/*
 * Palette axis band. 40rem, not the 48rem sheet threshold: the vertical rail
 * needs one 44px tile plus gutters, and the stage keeps ≥584px of drawing
 * width beside it at the floor. Keyed to 48rem the rail dock on a 1440px
 * desktop (stage-only root 741px) and a 768px tablet (673px) both fell into
 * the phone layout — horizontal palette under the stage — measured 2026-08-23.
 */
@container editor-surface (min-width: 40.01rem) {
  /*
   * Regular-desktop rails stay icon-first. The viewport-band label reveals
   * (tokens-layout-framework-part-2) and the coarse-pointer labeled density
   * are #app-main-scoped, so an ID outranks the rail's own class-only
   * collapse; the rail restates the collapse at (1,4,1) so a band reveal can
   * never paint a clipped half-label on a 44px rail square. The label keeps
   * its accessible name: max-inline-size collapses the box, not the text.
   */
  html #editor-tool-dock[data-editor-tool-rail] .bao-toolbar-action.btn-square .bao-toolbar-label,
  html #editor-tool-dock-3d[data-editor-tool-rail] .bao-toolbar-action.btn-square .bao-toolbar-label,
  html [data-editor-tool-rail] .bao-toolbar-action.btn-square .bao-toolbar-label,
  html #editor-tool-dock[data-editor-tool-rail] .bao-toolbar-action.btn-square [data-hig-key-hint],
  html #editor-tool-dock-3d[data-editor-tool-rail] .bao-toolbar-action.btn-square [data-hig-key-hint],
  html [data-editor-tool-rail] .bao-toolbar-action.btn-square [data-hig-key-hint] {
    /* A hidden caption must not wrap into a tall zero-width flex item.
     * These glyph rails retain aria-label and native shortcut captions;
     * compact 3D rows restore visible text through their own band owner. */
    position: absolute;
    inline-size: 0;
    block-size: 0;
    max-inline-size: 0;
    margin-inline-start: 0;
    padding: 0;
    border-width: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
  }
  /*
   * The palette floats at the stage's inline start, centred on the block
   * axis. The drawing surface reserves the palette footprint.
   */
  /*
   * The rail is a start-anchored band: the end inset must resolve to auto,
   * never 0. `.bao-editor-floating-toolbar` (filmstrip owner) declares
   * `inset-inline: 0` for the horizontal pill's centering; with inline-size
   * auto and both insets set, the absolutely positioned vertical rail
   * stretched from 4px to the canvas's right edge — a full-canvas frosted
   * slab over the specimen (measured 2026-08-24 at 1440x850, 868x464,
   * backdrop blur 20px). The maximized variant already pins end to auto.
   */
  [data-editor-tool-rail] {
    position: absolute;
    inset-inline-start: var(--space-section, 0.75rem);
    inset-inline-end: auto;
    inset-block-start: calc(50% + (var(--editor-stage-toolbar-block-size, 6rem) + var(--space-section)) / 2);
    translate: 0 -50%;
    z-index: var(--z-canvas-overlay, 30);
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: flex-start;
    inline-size: auto;
    block-size: auto;
    max-block-size: calc(100% - var(--editor-stage-toolbar-block-size, 6rem) - 3 * var(--space-section));
    min-block-size: 0;
    /*
     * The rail itself never scrolls: the favourites track below owns the one
     * scrolling axis, and the rail's fixed children — More tools, the spacer,
     * undo and redo — fit inside any stage the grid can produce. The rail must
     * therefore stay `overflow: visible`, and this is a load-bearing choice,
     * not a default: `overflow-y: auto` here forces overflow-x to compute to
     * auto as well, and that clipped every hover caption at the rail's inline
     * edge. Measured 2026-08-22 at 1440x900 — the caption box escaped the 54px
     * rail by 62px, `elementFromPoint` at its midpoint hit the canvas, and an
     * operator hovering nine icon-only buttons was shown nothing. A toolbar of
     * glyphs with unreachable names fails the first thing Apple's toolbar
     * guidance asks of it.
     */
    overflow: visible;
    /*
     * A uniform inset, because the rail's corners have to stay concentric with
     * the corners of the controls inside it. The rail is a bar, and Apple's
     * toolbar guidance is that a custom component in a bar carries a radius
     * concentric with the bar's; concentric means outer = inner + inset, which
     * has one answer only when the inset is equal on both axes. Rail 16px
     * (--radius-box) - 4px (--space-micro) = the 12px (--radius-field) the tool
     * buttons already paint. The block inset read 8px, which left the rail's
     * top and bottom corners rounder than the controls at its ends by exactly
     * that difference.
     */
    padding-inline: var(--space-micro, 0.25rem);
    padding-block: var(--space-micro, 0.25rem);
    row-gap: var(--space-micro, 0.25rem);
  }
  /*
   * The favourites track is the one child that scrolls, in the rail's axis.
   * More tools, Undo and Redo stay `flex: 0 0 auto` below it, so the controls
   * that leave the rail's viewport are only ever tools — and the catalog
   * behind More tools holds every one of those with its verb painted.
   */
  [data-editor-tool-rail] [data-editor-tool-scroll] {
    display: flex;
    /*
     * `0 1 auto`, not `1 1 auto`. A track that grows takes half the rail's
     * slack and drops More tools into the middle of an empty column — measured
     * a 187px gap between the last favourite and the catalog trigger at
     * 1440x900. The track hugs its tools, the catalog trigger sits directly
     * under them, and the spacer below takes every remaining pixel so undo and
     * redo settle at the rail's block end.
     */
    flex: 0 1 auto;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: center;
    min-block-size: 0;
    inline-size: auto;
    max-inline-size: none;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    touch-action: pan-y;
    row-gap: var(--space-micro, 0.25rem);
  }
  /*
   * Tool groups keep their identity as groups: a `.join` stacks vertically and
   * a hairline separates one group from the next, so select / draw / measure
   * read as three sets of tools rather than one undifferentiated column.
   */
  [data-editor-tool-rail] fieldset[data-tool-group] {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: center;
    inline-size: auto;
    max-inline-size: none;
    row-gap: 0;
  }
  /*
   * A hairline between groups, in the regular border tone rather than the
   * clear one: at 54px the separator is the only thing telling an operator
   * that select, draw and measure are three sets rather than six loose glyphs,
   * and the clear tone did not survive the glass fill behind it.
   */
  [data-editor-tool-rail] fieldset[data-tool-group] + fieldset[data-tool-group] {
    margin-block-start: var(--space-micro, 0.25rem);
    padding-block-start: var(--space-micro, 0.25rem);
    border-block-start: 1px solid var(--liquid-border-regular);
  }
  /* The palette hugs its controls; the spacer is a hairline between tools and history verbs. */
  [data-editor-tool-rail] > .bao-min-w-toolbar-spacer {
    flex: 0 0 auto;
    min-block-size: var(--space-micro, 0.25rem);
    inline-size: 100%;
  }
  /*
   * Icon squares, named by the platform tooltip.
   *
   * The buttons sit inside the favourites scroller, and a box that scrolls on
   * its block axis cannot keep a visible inline axis — the spec computes
   * `overflow-x` to auto the moment `overflow-y` is auto. Every CSS caption
   * scheme tried here painted nothing: the overlay computed a box beside the
   * rail and the scroller clipped it, so hovering nine glyphs showed nine
   * nothings (measured 2026-08-22 — `elementFromPoint` at the caption's
   * midpoint hit the canvas). The names therefore ride the buttons' native
   * tooltip, which the readiness sync paints from the caption parked in
   * `data-tip-original` only while the control is reachable (S122 — a gated
   * control never receives the hover or focus that would surface a static
   * tip, so no tip ships in the markup). The native channel is still what
   * renders outside every clip in the chain. The caption span stays collapsed in the
   * box tree; it is not the accessible name (`aria-label` is), but it is the
   * text the sheet band grows back into a visible verb.
   */
  [data-editor-tool-rail] .bao-toolbar-action.btn-square {
    flex: 0 0 auto;
    inline-size: var(--touch-target-min, 2.75rem);
    min-inline-size: var(--touch-target-min, 2.75rem);
    max-inline-size: var(--touch-target-min, 2.75rem);
    min-block-size: var(--touch-target-min, 2.75rem);
    padding-inline: 0;
  }
  /* The rail's own stage title has no room and no job beside the glyphs. */
  [data-editor-tool-rail] > .bao-toolbar-label:not([data-tool-strip]) {
    display: none;
  }
}

/*
 * Sheet band: the palette lies down along the stage's block end.
 *
 * Below the two-column threshold the only thing left to say is the axis and
 * what gives when the pill is short: the favourites track pans, and More
 * tools, Undo and Redo never do. Captions ride inside the controls
 * here — a horizontal row has the inline size for them and a touch pointer has
 * no hover to reveal them with.
 */
@container editor-surface (max-width: 40rem) {
  /* On the workbench, not the root: a container query cannot style its own
     container, and the sheet (a workbench child) reads the variable. */
  .bao-editor-workbench {
    --editor-sheet-palette-clearance: var(--editor-palette-gutter);
  }
  /*
   * The palette floats over the drawing surface at its block-end edge.
   */
  /* The 3D stage keeps its rail vertical at every width: its media controls
     already own the block end (a horizontal rail would sit on the gesture
     pill), and the rail floats over the scene like the pill does. */
  [data-editor-3d-viewport] [data-editor-tool-rail] {
    inset-inline: var(--space-micro, 0.25rem) auto;
    inset-block: 50% auto;
    translate: 0 -50%;
    flex-direction: column;
    align-items: stretch;
    inline-size: max-content;
    max-inline-size: min(var(--toolbar-label-reveal-max-width, 12rem), calc(100% - 2 * var(--space-micro, 0.25rem)));
    max-block-size: calc(100% - 2 * var(--space-micro, 0.25rem));
    overflow-x: visible;
    overflow-y: hidden;
  }
  /* The same column geometry the roomy band gives the 2D rail's track and
     groups — measured 390x844: the row rules below painted the 3D tools as a
     190x150 two-row slab. */
  [data-editor-3d-viewport] [data-editor-tool-rail] [data-editor-tool-scroll] {
    flex: 0 1 auto;
    flex-direction: column;
    align-items: stretch;
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: 100%;
    min-block-size: 0;
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    column-gap: 0;
    row-gap: var(--space-micro, 0.25rem);
  }
  [data-editor-3d-viewport] [data-editor-tool-rail] fieldset[data-tool-group] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    inline-size: auto;
  }
  /* Touch users read the 3D tools in place. A labeled column needs intrinsic
   * row height and width; icon-square bounds clip Sphere and Volume brush.
   * Auto-sized, stretched children contribute their captions to the rail
   * intrinsic width; percentage-sized children collapsed that width to 44px. */
  [data-editor-3d-viewport] [data-editor-tool-rail][data-editor-tool-dimension="3d"] .bao-toolbar-action.btn-square {
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: nowrap;
    align-self: stretch;
    justify-content: flex-start;
    align-items: center;
    gap: var(--space-inline);
    inline-size: auto;
    min-inline-size: var(--touch-target-min);
    max-inline-size: 100%;
    block-size: auto;
    min-block-size: var(--touch-target-min);
    max-block-size: none;
    aspect-ratio: auto;
    padding-inline: var(--toolbar-action-pad-inline, 0.625rem);
  }
  [data-editor-3d-viewport] [data-editor-tool-rail][data-editor-tool-dimension="3d"] .bao-toolbar-action.btn-square .bao-toolbar-label {
    display: block;
    position: static;
    flex: 1 1 auto;
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: 100%;
    block-size: auto;
    margin: 0;
    opacity: 1;
    clip: auto;
    clip-path: none;
    overflow: visible;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  [data-editor-tool-rail] {
    position: absolute;
    inset-inline: var(--space-section, 0.75rem);
    inset-block-start: auto;
    inset-block-end: var(--space-section, 0.75rem);
    translate: none;
    z-index: var(--z-canvas-overlay, 30);
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    inline-size: auto;
    max-inline-size: calc(100% - 2 * var(--space-micro, 0.25rem));
    block-size: auto;
    max-block-size: none;
    overflow-x: hidden;
    overflow-y: visible;
  }
  [data-editor-tool-rail] [data-editor-tool-scroll] {
    display: flex;
    flex: 1 1 auto;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    min-inline-size: 0;
    max-inline-size: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-x;
    column-gap: var(--space-micro, 0.25rem);
  }
  [data-editor-tool-rail] fieldset[data-tool-group] {
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: nowrap;
  }
  /*
   * The whole dock is icon-square in this band, not just on phones.
   *
   * A phone media rung (max-width: 47.99rem) already locks these controls to
   * the 44px square, which is why a 390px dock fits eight controls with room
   * to spare. Between 48rem and the two-column threshold the same dock kept
   * its grown labeled buttons: measured 2026-08-22 at 768x1024, the favourites
   * track wanted 528px in the 389px it had, and the fifth tool painted as a
   * bare "F" behind a hard clip — the scroll-edge cue could not help because
   * it paints on the track's background and an opaque button covers it at
   * exactly the clipped edge. Locking the square across the band ends the
   * overflow instead of decorating it: five favourites plus More tools, undo
   * and redo need ~390px, which every workbench in this band has.
   *
   * The label is squeezed by the width lock and `overflow: hidden`, never
   * `display: none`, so it keeps contributing the control's accessible name —
   * the glyph is aria-hidden and must not be the only name. The active tool's
   * verb stays painted in the dock's live strip, and the catalog behind More
   * tools carries every verb in full, which is the same compensation the
   * phone rung has always relied on.
   */
  [data-editor-tool-rail][data-editor-tool-dimension="2d"] .bao-toolbar-action.btn-square {
    inline-size: var(--touch-target-min, 2.75rem);
    min-inline-size: var(--touch-target-min, 2.75rem);
    max-inline-size: var(--touch-target-min, 2.75rem);
    min-block-size: var(--touch-target-min, 2.75rem);
    padding-inline: 0;
  }
  /*
   * The width lock alone decides which child loses the squeeze, and flex
   * picked the glyph: the label painted a truncated verb ("Selec", "Rectan")
   * beside a sliver of icon. The collapse must be stated on the label — the
   * same `max-width: 0` + `opacity: 0` idiom every icon-first densify in the
   * app uses, which keeps the text in the box tree contributing the control's
   * accessible name where `display: none` would erase it.
   */
  [data-editor-tool-rail][data-editor-tool-dimension="2d"] .bao-toolbar-action.btn-square .bao-toolbar-label {
    max-width: 0;
    margin-inline-start: 0;
    opacity: 0;
  }
}

/*
 * Vertical rail + open inspector sheet (40.01–48rem roots: tablets, and a
 * rail-docked desktop stage): the sheet rises from the stage's block end and
 * covered the rail's last tiles — measured 2026-08-23 at 768x1024, Ruler,
 * More tools, Undo and Redo under the Layers sheet. While the sheet is open the
 * rail hangs from the top and caps its height above the sheet's detent; its
 * scroller (data-editor-tool-scroll) takes any overflow.
 */
@container editor-surface (min-width: 40.01rem) {
  .bao-editor-workbench[data-editor-open-surface="inspector"] [data-editor-tool-rail] {
    /* Below the floating toolbar (runtime-stamped height; a two-row toolbar is
       ~6rem), above the sheet's detent. */
    inset-block-start: calc(var(--editor-stage-toolbar-block-size, 6rem) + var(--space-micro, 0.25rem));
    translate: none;
    max-block-size: calc(
      100% - var(--editor-stage-toolbar-block-size, 6rem)
      - var(--editor-compact-sheet-max-block, min(35dvh, 18rem))
      - var(--editor-stage-chrome-block-size-extra, var(--space-micro, 0.25rem))
      - (3 * var(--space-micro, 0.25rem))
    );
  }
}

}
@layer bao-tokens {
@media (max-width: 47.99rem) {
  [data-page="editor"] [data-session-mode-tabs] {
    box-sizing: border-box;
    block-size: var(--touch-target-min, 2.75rem);
    min-block-size: var(--touch-target-min, 2.75rem);
    margin-block: 0;
    padding: 0;
    border: 0;
    background: transparent;
  }
  [data-page="editor"] [data-session-mode-tabs] .bao-session-mode-tab {
    box-sizing: border-box;
    min-block-size: var(--touch-target-min, 2.75rem);
  }
  [data-page="editor"] > [data-state-content] > [data-primitive="PageHeader"]:not(.sr-only) {
    box-sizing: border-box;
    block-size: var(--touch-target-min, 2.75rem);
    min-block-size: var(--touch-target-min, 2.75rem);
    padding-block: 0;
  }
  [data-page="editor"] [data-editor-stage-header] {
    min-block-size: var(--touch-target-min, 2.75rem);
  }
  /*
   * Identity and state take a line each rather than competing for one.
   *
   * Both groups sit in the same wrapping flex line, both carry `shrink`, and
   * only one of them holds anything that can actually give: the status chips
   * are nowrap pills, so the context strip absorbed the whole squeeze and
   * measured 0px wide with its case chip clipped the moment the phone HUD
   * regained Review and Save. A phone stage header has room for two rows —
   * `bao-canvas-region` caps it at two touch targets — so spend it rather than
   * crush one group to nothing.
   */
  [data-page="editor"] [data-editor-stage-header] [data-editor-context-bar],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] {
    min-inline-size: 0;
    max-inline-size: 100%;
  }
  /*
   * Phone HUD: demote cursor/layer telemetry (they belong in the inspector,
   * not the title strip); keep the clinical survivors zoom, calibration,
   * review, and persistence pinned visible.
   */
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="cursor"],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="layer"] {
    display: none;
  }
  /*
   * The selected model's provenance pair belongs to the AI panel on a phone —
   * measured 390x844, the pair alone put a third row on the stage header.
   */
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="ai-provider"],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="ai-model"] {
    display: none;
  }
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="zoom"],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="review"],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="persistence"],
  [data-page="editor"] [data-editor-stage-header] [data-editor-status-bar] [data-editor-status="calibration"]:not([hidden]) {
    display: flex;
  }
}

}
@layer bao-tokens {
/* Compact stage clearance; inspector disclosure lives with the workbench. */
@container editor-workbench (max-width: 48rem) {
  /*
   * First-paint clearance estimate for the chrome under the stage — the
   * filmstrip. Runtime stamps the measured height as
   * --editor-stage-chrome-block-size on .bao-editor-workbench.
   */
  [data-page-type="media"] .bao-editor-workbench {
    --editor-stage-chrome-block-size: calc(var(--touch-target-min, 2.75rem) + 2.5rem);
  }
}

@media (max-width: 47.99rem) {
  /*
   * The 3D stage header now leads with the 2D/3D switcher (two 44px tiles);
   * with "Specimen stage", the calibration badge, More tools and the collapse
   * toggle that is six items for a 23rem row. The label is the one that is
   * decoration here — the switcher's pressed state already says which stage
   * this is — so it goes sr-only on phones.
   */
  [data-editor-3d-stage-header] .bao-editor-3d-stage-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  [data-page-type="media"] .bao-editor-workbench {
    --editor-stage-chrome-block-size: calc(var(--touch-target-min, 2.75rem) + 2.5rem);
    padding-block-end: 0;
  }
  /*
   * Phone toolbar: glyphs only in the trailing zone, so the leading and
   * trailing zones share one 44px row and the centre zone takes the second.
   * Labels collapse the sr-only way, keeping every control's name.
   */
  [data-page-type="media"] [data-editor-stage-toolbar] [data-toolbar-zone="trailing"] > [data-ai-suggestion-layer] {
    display: none;
  }
  [data-page-type="media"] [data-editor-stage-toolbar] [data-toolbar-zone="trailing"] > [data-ai-suggestion-layer]:not([hidden]):not([data-ai-suggestion-state="empty"]) {
    display: flex;
    flex: 1 1 100%;
    flex-wrap: wrap;
    max-inline-size: 100%;
  }
  [data-page-type="media"] [data-editor-stage-toolbar] .bao-toolbar-label,
  [data-page-type="media"] [data-editor-stage-toolbar] [data-editor-export-menu] [data-dropdown-trigger] > span:not(.sr-only),
  [data-page-type="media"] [data-editor-stage-toolbar] [data-header-action-overflow] [data-dropdown-trigger] > span:not(.sr-only) {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  [data-page-type="media"] [data-editor-stage-toolbar] [data-editor-save-form] [data-disabled-tip-host],
  [data-page-type="media"] [data-editor-stage-toolbar] [data-editor-save-form] button[type="submit"] {
    inline-size: auto;
  }
}

/*
 * Phone rail favourites. Nine 44px controls need 396px; a 390px phone has
 * 374. The favourites track scrolled and painted the fifth tool as a
 * half-tile under More tools. Two favourites leave the rail on this band
 * (three at 320px) so every painted control is whole; the catalog behind
 * More tools still lists every one of them (HIG canon H2: a band swap, not a
 * dead control). Lives in this bao-tokens module at (0,4,1) because the
 * `.btn:not([hidden]):not(.hidden):not(.bao-fab)` re-enabler in the same
 * layer is (0,4,0) and beat the utilities-layer form of this rule outright.
 */
@media (max-width: 47.99rem) {
  [data-editor-tool-rail] fieldset[data-tool-group] > .btn[data-tool="draw-arrow"],
  [data-editor-tool-rail] fieldset[data-tool-group] > .btn[data-tool="draw-ruler"] {
    display: none;
  }
  [data-editor-tool-rail] > .bao-min-w-toolbar-spacer {
    display: none;
  }
}
@media (max-width: 22rem) {
  /*
   * 22rem: the 3D stage header holds the glyph switcher (two tiles), the
   * calibration badge, More tools and the collapse toggle. The badge's word is
   * what does not fit ("…ncalibrated" clipped at 320x720); its status dot and
   * sr-only text keep the meaning, the word returns above this floor.
   */
  [data-editor-3d-stage-header] [data-editor-3d-calibration] > span:last-child {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  [data-editor-tool-rail] fieldset[data-tool-group] > .btn[data-tool="draw-ellipse"] {
    display: none;
  }
}

/*
 * Sheet band: the stage keeps a floor of two header rows plus the island's
 * 160px usable-surface minimum (canvas-stage-metrics MIN_USABLE_STAGE_PX).
 * The floor cannot exceed the bounded column: at 400% text the rem-based
 * value reached 1024px inside a 271px host and clipped its recovery scroller.
 * The full-stage canvas now keeps its drawing area under floating controls.
 */
@container editor-workbench (max-width: 48rem) {
  .bao-editor-workbench > [data-editor-stage] > .bao-editor-canvas-column > .bao-editor-canvas {
    min-block-size: min(100%, calc(2 * var(--touch-target-min, 2.75rem) + 10rem + var(--space-inline, 0.5rem)));
    min-height: min(100%, calc(2 * var(--touch-target-min, 2.75rem) + 10rem + var(--space-inline, 0.5rem)));
  }
}
/*
 * 320px floor: the filmstrip yields the row to the stage. A 320x720 phone
 * leaves ~300px for the whole stage column once the topbar, mode tabs, rail,
 * command bar and dock are paid for; a 64px strip under a two-row header
 * left the drawing surface below the island's usable floor and it fell back
 * to a 600px canvas that painted nothing. The Sessions sheet lists the same
 * captures on this band (HIG canon H2: a band swap, not a dead control).
 */
@media (max-width: 22rem) {
  [data-page="editor"] [data-editor-filmstrip-disclosure] {
    display: none;
  }
}

}
@layer bao-tokens {
@media (max-width: 47.99rem) {
  /*
   * Phone toolbar density: the zones and the save form tighten to the 2xs
   * step so the leading and trailing zones share one 44px row.
   */
  [data-page-type="media"] [data-editor-stage-toolbar],
  [data-page-type="media"] [data-editor-stage-toolbar] [data-toolbar-zone],
  [data-page-type="media"] [data-editor-stage-toolbar] [data-editor-save-form] {
    gap: var(--space-2xs, 0.125rem);
  }
}

}
@layer bao-tokens {
@media (max-width: 47.99rem) {
  [data-page-type="media"] [data-viz-dimension-option] {
    min-inline-size: var(--touch-target-min, 2.75rem);
    padding-inline: var(--space-micro, 0.25rem);
    font-size: var(--text-sm, 0.875rem);
  }
}

}
@layer bao-tokens {
[data-viz-dimension-option][aria-pressed="true"] {
  color: var(--color-primary-content);
  background-color: var(--color-primary);
  border-color: var(--color-primary);
}

}
@layer bao-tokens {
@media (min-width: 48rem) and (max-height: 32rem) {
  [data-editor-canvas-region] {
    position: relative;
  }
  [data-editor-canvas-region] > .canvas-container {
    flex: 1 1 auto;
    min-block-size: 0;
    max-block-size: none;
  }
}

}
@layer bao-tokens {
@media (min-width: 48rem) and (max-height: 32rem) {
  [data-editor-canvas-region] > [data-editor-stage-header],
  [data-editor-3d-stage-header] {
    min-block-size: var(--touch-target-min, 2.75rem);
    padding: var(--space-micro, 0.25rem);
  }
  [data-editor-canvas-region] > [data-editor-stage-header] [data-editor-stage-title],
  [data-editor-canvas-region] > [data-editor-stage-header] [data-viz-dimension-full],
  [data-editor-3d-stage-header] .bao-editor-3d-stage-label,
  [data-editor-3d-stage-header] [data-viz-dimension-full] {
    display: none;
  }
  [data-editor-canvas-region] > [data-editor-stage-header] [data-viz-dimension-compact],
  [data-editor-3d-stage-header] [data-viz-dimension-compact] {
    display: inline;
  }
  [data-editor-canvas-region] > [data-editor-stage-header] [data-editor-status-bar] {
    position: static;
    flex: 0 0 auto;
    max-block-size: var(--touch-target-min, 2.75rem);
    overflow: visible;
  }
  [data-editor-canvas-region] > [data-editor-stage-header] [data-editor-status]:not([data-editor-status="zoom"]):not([data-editor-status="calibration"]) {
    display: none;
  }
}

}
@layer bao-tokens {
[data-editor-tool-palette-dialog] .bao-dialog-chrome {
  background: transparent;
  border-block-end-color: var(--liquid-border-regular);
}

/* SearchField.bao owns the search control touch minimum. */

}
@layer bao-tokens {

  /*
   * The toolbar fits the STAGE, so its density keys on the stage container.
   * Below 48rem of stage (a 628px stage beside a desktop inspector, every
   * tablet, every phone) the document verbs keep their glyphs and drop their
   * words the sr-only way; Export and Save keep theirs while the row allows.
   * Measured with labels painted at 1440x900: "2D image · 3D geometry ·
   * Inspector · Review mode · More actions · Export · Save Annotations" ran to
   * ~1000px in a 628px stage and the trailing zone wrapped under the
   * toolbar's two-row cap, clipping Save.
   */
  @container editor-stage (max-width: 48rem) {
    /*
     * Narrow stage: the toolbar wraps. The metadata grid
     * (tokens-layout-editor-toolbar-container-presentation) owns the
     * 48.01-64rem band with fixed rows; below it the verbs outrun the
     * stage, so the row wraps instead of clipping a trailing control.
     */
    [data-editor-stage-toolbar] {
      flex-wrap: wrap;
    }
    [data-editor-stage-toolbar] [data-viz-dimension-switcher] button > span:not(.sr-only),
    [data-editor-stage-toolbar] [data-inspector-toggle] .bao-toolbar-label,
    [data-editor-stage-toolbar] [data-review-mode-toggle] .bao-toolbar-label,
    [data-editor-stage-toolbar] [data-editor-export-menu] [data-dropdown-trigger] > span:not(.sr-only),
    [data-editor-stage-toolbar] [data-editor-save-form] button[type="submit"] .bao-toolbar-label,
    [data-editor-stage-toolbar] [data-header-action-overflow] [data-dropdown-trigger] .bao-toolbar-label {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
    }
  }
  /*
   * Phone-wide stage (32rem): eight 44px controls need 366px of a 374px row,
   * so Export drops its word too and the maximize control leaves the row —
   * a phone stage is already the whole screen.
   */
  /*
 * Stages up to 32rem (390px and 320px phones): glyph-only, the two switcher
 * tiles plus six trailing verbs — inspector, review, more, export, save and
 * maximize — need ~380px, more than either stage. The leading zone keeps its
 * place; the trailing zone becomes a scroller with Save parked at its start
 * and maximize kept (the phone journey contract needs immersive reachable),
 * so nothing is clipped and the verbs stay in one row instead of wrapping
 * under the 2D/3D switcher — measured 2026-08-23 at 390x844 and 320x720. Its menus are
 * top-layer popovers, so the scroll clip cannot cut them. No scroll snap: with
 * inline snapping Chromium re-snapped the fresh scroller to 44px on first
 * layout (measured with nothing focused and no script scrolling it), which
 * hid the first verb behind the fold before anyone touched the row.
 */
@container editor-stage (max-width: 32rem) {
  /* Six 44px verbs with 2px gaps need 274px beside the switcher; a 374px
     stage leaves 270px while the switcher host pads 2px each side. The host
     gives up that inline padding here (its segments keep their own corners),
     so the maximize tile clears the fold — measured 2026-08-23 at 390x844.
     The toolbar's own 4px inset stays: the concentric-corner audit wants
     bar radius 16 − inset 4 = the 12px control corner the verbs paint. */
  [data-editor-stage-toolbar] [data-viz-dimension-switcher] {
    padding-inline: 0;
  }
  /* The review toggle's status dot rides the glyph's corner (a badge) instead
     of widening the tile to 56px; that 12px is what stood between the sixth
     verb and the fold at 390x844. */
  [data-editor-stage-toolbar] [data-review-mode-toggle] {
    position: relative;
  }
  [data-editor-stage-toolbar] [data-review-mode-toggle] > .status {
    position: absolute;
    inset-block-start: var(--space-micro, 0.25rem);
    inset-inline-end: var(--space-micro, 0.25rem);
  }
  [data-editor-stage-toolbar] > [data-toolbar-zone="trailing"] {
    min-inline-size: 0;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  [data-editor-stage-toolbar] > [data-toolbar-zone="trailing"] > * {
    flex: 0 0 auto;
  }
  /* Keep persistence controls at the start of the compact action row. */
  [data-editor-stage-toolbar] > [data-toolbar-zone="trailing"] > [data-editor-save-form] {
    order: -1;
    margin-inline-start: 0;
    /* Preserve the save control hit area alongside its peer actions. */
    flex: 0 0 auto;
  }
  /* Export and Save labels collapse to glyphs here; the names survive for AT. */
  [data-editor-stage-toolbar] [data-editor-export-menu] [data-dropdown-trigger] > span:not(.sr-only),
  [data-editor-stage-toolbar] [data-editor-save-form] button[type="submit"] .bao-toolbar-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}


  /* Save remains grouped with Inspector and More instead of creating an empty command track. */
  [data-editor-stage-toolbar] [data-editor-save-form] {
    flex: 0 1 auto;
    min-inline-size: 0;
    max-inline-size: 100%;
    margin-inline-start: 0;
  }

/*
 * Toolbar items on glass (Apple HIG toolbars): the plate is the container,
 * so items are borderless glyphs at rest — no tinted tile, no hairline per
 * button. One owner for every editor plate (stage toolbar, floating palette,
 * 3D header, 3D controls). The primary verb keeps its accent; a pressed tool
 * keeps the pressed tint (tokens-layout-editor-phone-dimension-label-paint
 * and toolButtonActive own those). Measured 2026-08-23 before this: three
 * button treatments shared one row — white outlined squares, tinted soft
 * tiles and a solid primary — which is what made the toolbar read as a
 * collection of widgets rather than one control.
 *
 * Properties, in the bao-tokens layer: daisyUI paints .btn-soft from its own
 * variables but bao-tokens re-declares border-color (0,3,0) and the glass
 * plate re-declares background-color on `.bao-surface-glass-regular .btn-soft`
 * (0,3,0), so the owner has to sit in the same layer at (0,6,0).
 */
[data-editor-stage-toolbar] .btn-soft.bao-toolbar-action:not(.btn-primary):not([aria-pressed="true"]):not(.btn-active),
[data-editor-tool-rail] .btn-soft.bao-toolbar-action:not(.btn-primary):not([aria-pressed="true"]):not(.btn-active),
[data-editor-3d-stage-header] .btn-soft.bao-toolbar-action:not(.btn-primary):not([aria-pressed="true"]):not(.btn-active),
[data-editor-3d-controls] .btn-soft.bao-toolbar-action:not(.btn-primary):not([aria-pressed="true"]):not(.btn-active) {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
  background-image: none;
  color: var(--color-base-content);
}
/* Layer names wrap within their selectable row. The list is the stable
 * owner: inspector content can move from the stage to the session rail. */
[data-layers-list] [data-layer-select]:not([hidden]) {
  justify-content: flex-start;
  text-align: start;
  white-space: normal;
  overflow-wrap: anywhere;
  block-size: auto;
  height: auto;
}

}
@layer bao-tokens {
/* Toolbar labels may collapse on compact chrome; an expanded menu is a labeled command list at every pointer density. */
:is(
  [data-editor-stage-toolbar] [data-header-action-overflow] [role="menu"],
  [data-page="capture"] [data-capture-action-disclosure] > [data-capture-overflow-panel]
) .bao-menu-item .bao-toolbar-label {
  position: static;
  inline-size: auto;
  block-size: auto;
  padding: 0;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  border: 0;
}

}
@layer bao-tokens {
/* Context labels, values, and status readouts share one metadata text ramp. */
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar],
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] [data-editor-context-label],
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-context-bar] dd,
[data-editor-chrome] [data-editor-stage-toolbar] > [data-toolbar-zone="center"] > [data-editor-status-bar] {
  font-size: var(--text-xs, 0.75rem);
  line-height: var(--text-xs--line-height, 1.33333);
  font-weight: var(--font-weight-normal, 400);
}

[data-editor-stage-toolbar] [data-viz-dimension-switcher] {
  max-inline-size: 100%;
}
[data-editor-stage-toolbar] [data-viz-dimension-switcher] > [data-viz-dimension-option] {
  min-inline-size: var(--touch-target-min, 2.75rem);
  max-inline-size: 100%;
  block-size: auto;
  white-space: normal;
  overflow-wrap: anywhere;
}
[data-editor-context-bar] [data-editor-context] dd,
[data-editor-context-bar] [data-editor-context] [data-status-indicator] {
  min-inline-size: 0;
  max-inline-size: 100%;
  flex-wrap: wrap;
  white-space: normal;
  overflow-wrap: anywhere;
}

}
@layer bao-tokens {
/* Grid is the primary workbench layout; the earlier flex declaration is the non-grid baseline. */
@supports (display: grid) {
  .bao-editor-root {
    container-type: inline-size;
    container-name: editor-surface;
  }

  .bao-editor-workbench {
    display: grid;
    /* Closed inspector reserves no column. Opening uses a bounded token track,
     * independent of the selected tab and its intrinsic content width. */
    grid-template-areas: "stage";
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    padding-block-end: 0;
    overflow: hidden;
  }

  .bao-editor-workbench:where(:has(> [data-editor-canvas-overlays][data-inspector-expanded="true"])) {
    grid-template-areas: "stage inspector";
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--editor-inspector-basis));
  }

  .bao-editor-workbench > [data-editor-canvas-overlays] {
    grid-area: inspector;
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: 100%;
    block-size: 100%;
    min-block-size: 0;
    max-block-size: 100%;
  }

  /* Named areas stay in flow at every band. Rows cannot overlap. */
  @container editor-surface (max-width: 64rem) {
    .bao-editor-workbench {
      --editor-inspector-basis: var(--sidebar-expanded, 16rem);
    }
  }

  /*
   * Sheet band: one stage track; the inspector becomes a bottom sheet and
   * the palette lies along the stage's block end
   * (tokens-layout-editor-toolbar-container-presentation).
   */
  @container editor-surface (max-width: 48rem) {
    .bao-editor-workbench {
      grid-template-areas: "stage";
      grid-template-columns: minmax(0, 1fr);
      grid-template-rows: minmax(0, 1fr);
      padding-block-end: 0;
      overflow: hidden;
    }

    /* Inspector is a bottom sheet here, so it sizes to content. */
    .bao-editor-workbench > [data-editor-canvas-overlays]:not([data-inspector-expanded="true"]) {
      block-size: auto;
      max-block-size: none;
    }
    /*
     * The open sheet is a bounded detent, not a takeover: it scrolls inside a
     * block cap and sits ABOVE the floating palette, which lives at the block
     * end of the stage behind `--editor-palette-gutter`. The runtime stamps
     * `--editor-stage-chrome-block-size` with everything below the stage (the
     * filmstrip and its gap); `--editor-sheet-palette-clearance` (the palette
     * gutter while the palette is horizontal, 0 beside a vertical rail) lifts
     * the sheet the rest of the way.
     * Measured 2026-08-23 before this: the Review sheet filled 446px of a
     * 446px stage at 320x720, and at 768x1024 the 114px sheet painted over the
     * 56px palette. 35dvh is the largest detent that still clears the
     * toolbar's context row at 320x720 (a 40dvh sheet covered its last 15px).
     */
    .bao-editor-workbench > [data-editor-canvas-overlays][data-inspector-expanded="true"] {
      /* The detent caps populated panels; an empty history sizes to its content. */
      block-size: auto;
      align-self: end;
      inset-block-end: calc(
        var(--editor-stage-chrome-block-size, 8rem) +
        var(--editor-stage-chrome-block-size-extra, var(--space-micro, 0.25rem)) +
        var(--editor-sheet-palette-clearance, 0px) +
        var(--space-micro, 0.25rem)
      );
      max-block-size: var(--editor-compact-sheet-max-block, min(35dvh, 18rem));
    }
    /* The browser owns body scrolling inside the padded sheet. Pin to its
     * own edge: a negative sheet-padding offset moves tabs when focus scrolls. */
    .bao-editor-workbench > [data-editor-canvas-overlays][data-inspector-expanded="true"] [data-editor-inspector-tabs] {
      position: sticky;
      inset-block-start: 0;
      z-index: var(--z-local, 1);
    }
  }
}

/*
 * Rail dock (permanent-rail band, ≥64rem): the inspector lives inside
 * #session-rail (app-shell-session-rail-dock), so the workbench is the
 * stage alone and the shell's trailing rail is the one trailing sidebar —
 * before this the page painted its own 24rem inspector column beside a
 * collapsed 66px rail holding a single toggle (measured 2026-08-23 at
 * 1440x900: two trailing sidebars, one of them empty).
 */
@supports (display: grid) {
  [data-editor-chrome][data-inspector-dock="rail"] .bao-editor-workbench {
    grid-template-areas: "stage";
    grid-template-columns: minmax(0, 1fr);
  }
}
#session-rail [data-editor-canvas-overlays][data-inspector-dock="rail"] {
  grid-area: auto;
  inline-size: 100%;
  min-inline-size: 0;
  max-inline-size: 100%;
  block-size: auto;
  min-block-size: 0;
  max-block-size: none;
  flex: 0 1 auto;
}
/* The docked inspector needs the inspector column's width, not the rail's 18rem default; a dragged width still wins. */
body:has([data-editor-chrome][data-inspector-dock="rail"]) #session-rail {
  --session-rail-expanded: 24rem;
}

/*
 * Inactive inspector tabs: daisyUI's `.tab` rests at half-strength text, which
 * the concentric-legibility audit measured at 3.31:1 (12px "Review" on the
 * rail's glass, light theme) — under AA 4.5:1. 78% base-content keeps the
 * resting/selected hierarchy and clears AA on both themes.
 */
[data-editor-inspector-tabs] .tab:not(.tab-active):not([aria-selected="true"]) {
  color: color-mix(in oklab, var(--color-base-content) 78%, transparent);
}
  /*
   * One list of captures. The rail's capture section feeds the filmstrip
   * (app-shell-editor-filmstrip reads its rows, hidden or not), so while the
   * filmstrip paints under the stage the rail does not paint the same
   * captures again beside it — measured 2026-08-23 at 1440x900: eighteen
   * tiles under the stage and the same eighteen rows in the rail.
   */
  body:has([data-editor-chrome][data-annotation-ready="true"] [data-editor-filmstrip]:not([hidden])) #session-rail [data-session-section="captures"] {
    display: none;
  }
  /*
   * Acquire mode has no stage: the docked inspector (AI review, layers, …)
   * describes an image that is not being annotated — measured 2026-08-23 at
   * 1440x900 the rail opened on "AI review · Loading models…" above the
   * capture controls. Only annotate paints it.
   */
  body:has([data-page="capture"][data-session-mode="acquire"]) #session-rail [data-editor-canvas-overlays][data-inspector-dock="rail"] {
    display: none;
  }
  /* The inspector docked in the rail is rail content: no framed card inside the rail plate. */
  #session-rail [data-editor-canvas-overlays][data-inspector-dock="rail"] [data-editor-inspector-browser] {
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }
  /* The spatial summary pill only exists once it has a sentence; an empty
     live region painted as a bare grey bar under "Add point" (2026-08-23). */
  [data-editor-spatial-summary]:empty {
    display: none;
  }

/* One physical right rail owns annotation context at the lg shell band. */
@media (min-width: 64rem) {
  .bao-shell-drawer > .drawer-content > .relative {
    flex-direction: row;
  }
  .bao-shell-drawer > .drawer-content > .relative > #app-main {
    flex: 1 1 0%;
    inline-size: auto;
    max-inline-size: none;
  }
}

/*
 * The rail's own in-flow geometry used to be declared here, from
 * `min-width: 64rem`. It now lives with its one owner,
 * tokens-glass-shell-drawer-part-b, at `min-width: 80rem`:
 * `MEDIA_QUERY_PERMANENT_RAIL` (app-shell-media-queries) is 80rem, that
 * module hides the rail below 79.99rem, and capture only surrenders its
 * in-page session controls at 80rem. Declaring `display` for the same
 * selector from a second module let source order decide the winner, and the
 * 64rem band then painted a rail the rest of the stack believed was hidden.
 */


}
@layer bao-tokens {
/*
 * The canvas rail is a compact, icon-first tool palette. Tool titles and
 * accessible names provide discovery without turning the stage into a text
 * column; the complete, labeled catalog remains behind More tools.
 */
.bao-editor-root {
  --editor-palette-gutter: calc(var(--touch-target-min, 2.75rem) + 3 * var(--space-micro, 0.25rem));
}
/* Rail geometry and label density belong to editor-toolbar-container-presentation. */

}
@layer bao-tokens {
/*
 * The annotation rail is an inspector, and it also carries the one case and
 * specimen picker the operator has on this page.
 *
 * `#session-rail-live` used to be hidden here while the inspector was docked,
 * on the reading that the rail must not become "a second session page". That
 * was true when the region shipped capture's whole session stack; it is not
 * true now. `session-rail-session-controls` is deliberately page-agnostic
 * (see its module comment) and `/api/session/rail?page=/editor` returns the
 * case picker and the specimen picker and nothing else — no mode tabs, no
 * capture list. The hide therefore suppressed exactly the pickers and nothing
 * more: measured on /editor at 1440x900 against a seeded case, the region sat
 * at `data-state="loading"` forever, and `display: none` also starved its own
 * fetch trigger, so the Cases control was permanently blank.
 *
 * Unhiding it is necessary but was not sufficient — the region still has to
 * fetch. Its trigger is declared in `layouts/app-shell.bao` (`sessionRail`),
 * where `intersect once` was replaced by `load once` for the same incident;
 * see the note there.
 */
@media (min-width: 64rem) {
  #session-rail [data-editor-inspector-tabs] {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    gap: var(--space-micro, 0.25rem);
    inline-size: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-inline: contain;
  }

  #session-rail [data-editor-inspector-tabs] .tab {
    flex: 1 0 max-content;
    justify-content: center;
    min-block-size: var(--touch-target-min, 2.75rem);
    text-align: center;
  }
}

}
@layer bao-tokens {
.bao-editor-workbench > [data-editor-stage] {
  display: flex;
  min-inline-size: min(100%, var(--editor-canvas-basis));
  max-inline-size: 100%;
  block-size: 100%;
  max-block-size: 100%;
  flex: 1 1 var(--editor-canvas-basis, 32rem);
  flex-direction: column;
  gap: var(--space-inline);
}

[data-editor-stage] > .bao-editor-canvas-column {
  display: flex;
  min-inline-size: 0;
  min-block-size: 0;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--space-inline);
}

/* 3D view owns the stage: hide the 2D canvas column when the 3D pose is
   selected, scoped so a nested editor mirror can never hide both columns.
   The deliberate collapsed-3D sheet is exempt so the canvas keeps the
   stage while the 3D header shows (the container-scoped sheet contract
   re-enables it). */
  [data-editor-chrome][data-editor-view-mode="3d"]:not([data-editor-3d-collapsed="true"]) [data-editor-stage] > .bao-editor-canvas-column {
    display: none;
  }
@supports (display: grid) {
  .bao-editor-workbench > [data-editor-stage] {
    grid-area: stage;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: none;
    block-size: 100%;
    min-block-size: 0;
    max-block-size: 100%;
    gap: var(--space-inline);
    flex: 1 1 var(--editor-canvas-basis, 32rem);
  }

  [data-editor-stage] > .bao-editor-canvas-column,
  [data-editor-stage] > .bao-editor-3d-column {
    grid-column: 1;
    grid-row: 1;
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: none;
    block-size: 100%;
    min-block-size: 0;
    max-block-size: 100%;
  }

  [data-editor-chrome][data-editor-view-mode="dual"] [data-editor-stage] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  [data-editor-chrome][data-editor-view-mode="dual"] [data-editor-stage] > .bao-editor-canvas-column,
  [data-editor-chrome][data-editor-view-mode="dual"] [data-editor-stage] > .bao-editor-3d-column {
    grid-column: auto;
    grid-row: 1;
  }
}

@container editor-workbench (max-width: 48rem) {
  [data-editor-chrome][data-editor-view-mode="3d"][data-editor-3d-collapsed="true"] .bao-editor-workbench {
    flex-direction: column;
  }
  /* The stage is a grid: reserve separate rows for the 2D image and the
     collapsed 3D header so the document toolbar cannot cover Expand. */
  [data-editor-chrome][data-editor-view-mode="3d"][data-editor-3d-collapsed="true"] .bao-editor-workbench > [data-editor-stage] {
    grid-template-rows: minmax(0, 1fr) max-content;
  }
  [data-editor-chrome][data-editor-view-mode="3d"][data-annotation-ready="true"][data-editor-3d-collapsed="true"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-canvas-column {
    display: flex;
    order: 0;
    inline-size: 100%;
    min-inline-size: 100%;
    min-block-size: 0;
    block-size: auto;
    max-block-size: 100%;
    flex: 1 1 0;
  }
  [data-editor-chrome][data-editor-view-mode="3d"] .bao-editor-workbench > [data-editor-stage] > .bao-editor-3d-column[data-editor-3d-collapsed="true"] {
    order: 1;
    grid-row: 2;
    inline-size: 100%;
    min-inline-size: 100%;
    align-self: stretch;
  }
}

}
@layer bao-tokens {
.bao-editor-stage {
  container-type: inline-size;
  container-name: editor-stage;
}

@container editor-stage (max-width: 40rem) {


  [data-editor-tool-rail][data-editor-tool-dimension="2d"] {
    position: absolute;
    inset-inline: var(--space-section, 0.75rem);
    inset-block-start: auto;
    inset-block-end: var(--space-section, 0.75rem);
    translate: none;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    inline-size: auto;
    max-inline-size: calc(100% - 2 * var(--space-micro, 0.25rem));
    block-size: auto;
    max-block-size: none;
    overflow-x: hidden;
    overflow-y: visible;
  }

  [data-editor-tool-rail][data-editor-tool-dimension="2d"] [data-editor-tool-scroll] {
    flex: 1 1 auto;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    min-inline-size: 0;
    max-inline-size: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    column-gap: var(--space-micro, 0.25rem);
  }

  [data-editor-tool-rail][data-editor-tool-dimension="2d"] fieldset[data-tool-group] {
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: nowrap;
  }

}

}
@layer bao-tokens {
/* Canvas work keeps the header quiet and preserves search through its command trigger. */
@media (min-width: 64rem) {
  body:has([data-editor-chrome]) #app-header [data-topbar-search] {
    display: none;
  }

  body:has([data-editor-chrome]) #app-header .navbar-end .bao-shell-topbar-actions [data-command-palette-trigger].btn {
    display: inline-flex;
  }

}

}
@layer bao-tokens {
/* Rail presentation owns responsive geometry; generic toolbars exclude this surface. */
[data-editor-tool-rail] {
  display: flex;
}

}
@layer bao-tokens {
/* Final icon-rail density; workbench grid owns inspector tracks. */
body:has([data-editor-chrome][data-inspector-dock=rail]) #session-rail {
  --session-rail-expanded: var(--sidebar-expanded);
}
/* Rail geometry and label density belong to editor-toolbar-container-presentation. */

}
@layer bao-tokens {
/* Review is an inspector action. It remains reachable on every workbench size. */
[data-editor-inspector-panel="review"] [data-review-mode-toggle] {
  display: inline-flex;
}

/* Focus scrolling must use the final native disclosure extent. Its content
 * pseudo-element transitions height independently of the outer grid, and
 * the content animation can continue after a newly focused control scrolls.
 * Cancel both while keyboard focus is within the disclosure; preserve
 * native open/closed semantics and the inspector tab strip scroll owner. */
[data-editor-inspector-panel] details.collapse:focus-within,
[data-editor-inspector-panel] details.collapse:focus-within::details-content,
[data-editor-inspector-panel] details.collapse:focus-within > .collapse-content {
  transition: none;
  animation: none;
}

/* Inspector categories retain full text height inside the horizontal tab scroller. */
[data-editor-inspector-tabs] > [data-editor-inspector-tab] {
  block-size: auto;
  min-block-size: var(--touch-target-min);
  white-space: normal;
  overflow-wrap: anywhere;
}
[data-annotation-history-list] {
  flex: 0 0 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
}
[data-annotation-history-list] > li {
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

}
@layer bao-tokens {
/* An empty annotation stage exposes only recovery actions, never inert editor chrome. */
[data-editor-chrome][data-annotation-ready="false"] [data-editor-tool-rail] {
  display: none;
}

/* Recovery text retains its intrinsic height at large text sizes. Safe centering
 * yields to a start-aligned scrollport when the canvas cannot hold the content. */
[data-editor-canvas-region] > [data-editor-canvas-empty] {
  justify-content: flex-start;
  justify-content: safe center;
  padding-block: var(--space-page);
}
[data-editor-canvas-empty] > * {
  flex: 0 0 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}
[data-editor-canvas-empty] > fieldset > .btn {
  min-inline-size: 0;
  max-inline-size: 100%;
  block-size: auto;
  white-space: normal;
  overflow-wrap: anywhere;
}

}
@layer bao-tokens {
/* Apple HIG control ladder + pointer-adaptive chrome (hig-canon.json). */
:root {
  --hig-control-size: var(--control-regular, 2.75rem);
  --hig-sidebar-row: var(--sidebar-row-medium, 2.25rem);
  --hig-sidebar-glyph: 1rem;
}

@media (pointer: fine) {
  :root {
    --hig-control-size: var(--control-small, 2rem);
    --hig-sidebar-row: var(--sidebar-row-small, 1.75rem);
    --hig-sidebar-glyph: 0.875rem;
  }
}

@media (pointer: coarse) {
  :root {
    --hig-control-size: var(--control-regular, 2.75rem);
    --hig-sidebar-row: var(--sidebar-row-large, 2.75rem);
    --hig-sidebar-glyph: 1.125rem;
  }
}

#app-header.navbar::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: var(--scroll-edge-fade, 1.5rem);
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    color-mix(in oklab, var(--color-base-100) 0%, transparent),
    color-mix(in oklab, var(--color-base-100) 55%, transparent)
  );
  opacity: var(--opacity-strong);
}

#app-header.navbar {
  position: sticky;
  border-block-end-width: 0;
}

/*
 * Pointer-adaptive row height, floored at the product's 44pt target. The
 * fine-pointer rung alone resolves to 1.75rem, and the rendered-geometry audit
 * read every sidebar link on every page as a 222x28 sub-floor control. HIG
 * permits a 28px row for a precise pointer; this product does not, because the
 * same markup ships to touch through Tauri. The floor wins, the glyph ladder
 * below still adapts.
 */
#app-sidebar .bao-nav-item {
  min-height: max(
    var(--hig-sidebar-row, var(--sidebar-row-medium, 2.25rem)),
    var(--touch-target-min, 2.75rem)
  );
}

#app-sidebar .bao-nav-item > svg {
  inline-size: var(--hig-sidebar-glyph, 1rem);
  block-size: var(--hig-sidebar-glyph, 1rem);
  color: var(--color-primary);
}

#app-sidebar .bao-nav-item.menu-active > svg,
#app-sidebar .bao-nav-item[aria-current="page"] > svg {
  color: var(--color-primary);
}

.bao-shell-drawer::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-end: -1.5rem;
  inline-size: 1.5rem;
  pointer-events: none;
  background: linear-gradient(
    to inline-end,
    color-mix(in oklab, var(--color-base-200) 40%, transparent),
    transparent
  );
}

.bao-editor-workbench {
  --editor-inspector-track-min: var(--inspector-pane-min, 14rem);
  --editor-inspector-track-max: var(--inspector-pane-max, 32rem);
}

.bao-editor-inspector-divider {
  inline-size: var(--pane-divider, 1px);
  background: color-mix(in oklab, var(--color-base-content) 18%, transparent);
  cursor: col-resize;
  touch-action: none;
}

[data-hig-key-hint] {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 1;
  padding-inline: var(--space-micro, 0.25rem);
  padding-block: var(--space-2xs, 0.125rem);
  border-radius: var(--radius-field);
  border: 1px solid color-mix(in oklab, var(--color-base-content) 20%, transparent);
  background: var(--color-base-200);
  color: var(--color-base-content);
}

/*
 * Command-palette rows are keyboard-first on fine pointers, but phone users
 * tap them. Floor the hit area at the product's 44pt target on touch only;
 * fine-pointer density keeps its compact keyboard-first row height.
 */
@media (pointer: coarse) {
  .bao-shell-list-row {
    min-height: var(--touch-target-min, 2.75rem);
  }
}

}
@layer bao-tokens {

/* ---- Runtime customizer host (unlayered structural geometry). ---- */
/* Header-anchored host (created at runtime next to the edit toggle). */
[data-dashboard-customizer-dropdown] {
  position: relative;
  display: inline-flex;
  z-index: var(--z-shell-topbar, 30);
}
[data-dashboard-customizer-dropdown]:not(.dropdown-open) > [data-dashboard-customizer-content] {
  display: none;
  visibility: hidden;
  pointer-events: none;
}
/*
 * Open tray geometry lives HERE, unlayered, at (0,3,0): daisyUI's
 * .dropdown .dropdown-content position:absolute is unlayered too and ties
 * lower specificity; the shared .bao-dropdown-panel-viewport rule cannot be
 * relied on for this host (no .bao-dropdown-panel base class). Anchor vars
 * come from the promote step (edit-toggle rect); absent vars mean the toggle
 * is collapsed and the compact sheet band below takes over.
 */
[data-dashboard-customizer-dropdown].dropdown-open > [data-dashboard-customizer-content] {
  display: flex;
  flex-direction: column;
  visibility: visible;
  pointer-events: auto;
  position: fixed;
  top: var(--bao-dropdown-top, auto);
  right: var(--bao-dropdown-right, var(--space-inline, 0.5rem));
  bottom: auto;
  left: auto;
  margin: 0;
  z-index: var(--z-dropdown, 1000);
  min-width: min(18rem, calc(100vw - 1.5rem));
  width: max-content;
  max-width: min(22rem, calc(100vw - 1.5rem));
  max-height: min(70vh, 28rem);
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
/*
 * §8.6 compact transformation: below the header-collapse band the customize
 * toggle is folded into the overflow menu (0x0 — nothing to anchor to), so
 * the open tray pins to the viewport bottom as a sheet.
 */
@media (max-width: 47.99rem) {
  [data-dashboard-customizer-dropdown].dropdown-open > [data-dashboard-customizer-content] {
    top: auto;
    bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-inline, 0.5rem));
    left: var(--space-inline, 0.5rem);
    right: var(--space-inline, 0.5rem);
    width: auto;
    max-width: none;
    max-height: min(70vh, var(--vv-height, 100dvh));
    z-index: var(--z-modal, 1300);
  }
}

}
@layer bao-tokens {

  /* Dialogs always sit above the shell drawer when both are open. */
  dialog.modal,
  dialog[open].modal {
    z-index: var(--z-modal, 1300);
  }
  /*
   * Closed dialogs must not paint or steal hits. daisyUI .modal uses display:grid
   * + pointer-events:none while closed; unlayered [hidden] must still win so a
   * pre-open dialog never sits over the shell as an invisible sheet.
   */
  dialog.modal[hidden]:not([open]),
  dialog.modal[hidden]:not(.modal-open) {
    display: none;
    pointer-events: none;
    visibility: hidden;
  }
  /* Native dialog visibility must stay UNLAYERED so WebView hosts cannot
   * drop open dialogs behind daisyUI @layer modal { display:none }. The
   * modal-open state class is pinned here by the canonical layout-regression
   * contract (tokens-layout-regressions-shell.test): WebView 101 hosts that
   * miss the [open] attribute still honor the class, so both keys stay. */
  dialog.modal.modal-open,
  dialog.modal[open] {
    pointer-events: auto;
    visibility: visible;
  }
  /* Collection/home: single content track — header + primary surface, less chrome stacking. */
  [data-page-type="collection"] > [data-state-content],
  [data-page-type="home"] > [data-state-content] {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: var(--page-section-gap);
  }
  /* .bao-page-track structural SSOT lives in tokens-layout-framework.ts only. */
  [data-page-type="collection"] [data-primitive="PageHeader"],
  [data-page-type="home"] [data-primitive="PageHeader"] {
    margin-bottom: 0;
  }
  /* Toolbar under header: no second bordered island. Longhand color only —
     the background shorthand was wiping background-image and with it the
     bao-table-scroll-x scroll-edge cue on the same element.

     Sticky toolbars (.bao-sticky-toolbar, z-sticky) are excluded: once the
     bar pins over scrolling rows a transparent surface bleeds the table
     through the chrome (evidence Finish Case band read as overlapping the
     case-journey strip). Pinned chrome keeps its bg-base-100 plate. */
  [data-page-type="home"] > [data-state-content] > [role="toolbar"]:not(.bao-sticky-toolbar),
  [data-page-type="collection"] > [data-state-content] > [role="toolbar"]:not(.bao-sticky-toolbar) {
    border: 0;
    background-color: transparent;
    padding-inline: 0;
    padding-block: 0;
  }
  /* Dynamic Type reflow (HIG / WCAG 1.4.10): daisyUI .btn hard-codes
     flex-shrink:0 and flex-wrap:nowrap, so an enlarged root font pushes the
     active-case journey action past the viewport with no scroller. Cap the
     action to its host and let it shrink; the strip has room to grow, and
     the inner span keeps min-width:0 so its label can ellipsize rather than
     escape. */
  [data-case-journey] a.btn,
  [data-case-journey] .btn {
    min-width: 0;
    max-width: 100%;
    flex-shrink: 1;
  }
  [data-case-journey] a.btn > *,
  [data-case-journey] .btn > * {
    min-width: 0;
  }
  /* Reflow (HIG Dynamic Type / WCAG 1.4.10): the daisyUI .fieldset-legend
     flex row and the journey payload keep their intrinsic min-content width,
     so an enlarged root font pushes each past the viewport with no scroller.
     min-width:0 + max-width:100% let the box shrink; overflow-wrap:anywhere
     lowers the intrinsic floor so the text reflows instead of driving the
     host wider than the track. Overlaps the existing .btn rule above at the
     same specificity so source order cannot reintroduce the escape. */
  legend.fieldset-legend,
  [data-case-journey] p {
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
  }
  /* Control reflow (HIG Dynamic Type / WCAG 1.4.10): daisyUI .btn is
     flex-shrink:0 + flex-wrap:nowrap, so every labeled action's intrinsic
     min-content pushes it past the viewport at an enlarged root font.
     Wrapping is inert at the default text size (labels fit one line) and
     engages only when space actually runs out; overflow-wrap:anywhere
     lowers the intrinsic floor so the label can break inside a word. */
  .btn {
    flex-wrap: wrap;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Plain text bearers in content panels (permission descriptions, mono
     readouts, status copy): same intrinsic-floor rule so a long line
     reflows instead of stretching its row past the viewport. */
  #app-main :is(span, label, summary, li, p, dd, output):not(.btn):not(.bao-touch-target),
  dialog :is(span, label, summary, li, p, dd, output):not(.btn):not(.bao-touch-target) {
    min-width: 0;
  }

  
  /* FilterBar control heights: one row, one target. The search host (label.input
     plus leading icon) and the select controls computed different heights
     (measured 46px vs 36px on /evidence at 1440), so the row never reads as
     aligned; min-height keeps every control at the Apple HIG 44pt target and
     the min() cap keeps it from outgrowing a narrow viewport at 400% text. */
  [data-primitive="FilterBar"] :is(.input, .select, input, select) {
    min-height: min(var(--touch-target-min, 2.75rem), 12vw);
    align-self: center;
  }
  /* Inline action clusters (robot forms, annotation-collab action rows):
     intrinsic-min-content rows. The row itself — display, flex-wrap,
     min-inline-size, max-inline-size — is owned by
     tokens-glass-control-chrome; repeating min-width/flex-wrap here put a
     second, unreachable owner of flex-wrap in the same @layer. Only the
     child floor lives here, because the wrapped children need a shrink
     floor that the component base does not set. */
  .bao-action-inline > * {
    min-width: 0;
  }
  /* Chart/form stat rows (models card dl rows, budget fieldsets) and
     composer fieldset clusters: same intrinsic-floor rule. */
  dl.m-0.flex > div,
  fieldset.fieldset.flex dl > div,
  div.group.rounded-field > div.flex.items-start {
    min-width: 0;
    flex-wrap: wrap;
  }
  /* Disclosure rows (specimens "Add specimen" summary) and their panels:
     allow the trigger and content to reflow instead of pushing the card
     past the viewport. */
  details.min-w-0 > summary.btn,
  details.min-w-0 > summary {
    min-width: 0;
    max-width: 100%;
  }
  /* Form-grid cells (integrations save rows, rpa toolbar rows): the grid
     cell is full-span but its intrinsic min-content at an enlarged root
     font still widens the track; floor the cell so the button inside
     reflows instead of the track. */
  form.bao-form-grid-two > *,
  .bao-grid-reactive-panels [data-section-group] > * {
    min-width: 0;
  }
  /* Page-toolbar rows with wrap (rpa create toolbar): the toolbar host
     keeps its child rows inside the track. */
  div.flex.flex-wrap {
    min-width: 0;
  }
  /* Sticky page-toolbar rows (rpa/api-routes filter bars, ai-analysis
     provenance, editor canvas sticky): the sticky host keeps its intrinsic
     min-content at an enlarged root font. Floor the host and let its rows
     wrap; text-bearing children also get the intrinsic floor. */
  div.bao-filter-bar-sticky > div.flex,
  [data-page="rpa-workflows"] div.flex.flex-wrap,
  [data-page="api-routes"] div.space-y-space-inline,
  dl.bao-ai-provenance-sticky,
  fieldset.join.shrink-0 {
    min-width: 0;
    max-width: 100%;
  }
  div.bao-filter-bar-sticky > div.flex > *,
  dl.bao-ai-provenance-sticky > *,
  fieldset.join.shrink-0 > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Form rows (models budget, training plan rows, editor context badges):
     intrinsic-floor on the label + value rows. */
  fieldset.flex.flex-col label.flex,
  fieldset.flex.flex-col dl > div,
  div.bao-context-badge,
  dd.inline-flex.min-w-0,
  form div.flex.items-center,
  section div.flex.items-center {
    min-width: 0;
  }
  form div.flex.items-center > *,
  section div.flex.items-center > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Chat composer send row and models budget rows: the leaf label/value rows
     keep intrinsic min-content at 400%. Floor the row + its children so the
     text reflows inside the track. */
  form.bao-composer-shell.card div.flex.items-center > div.flex.items-center,
  form.mt-space-inline.border-t fieldset label.flex,
  form.mt-space-inline.border-t fieldset div.flex.w-full,
  form.mt-space-inline.border-t dl.m-0.flex > div {
    min-width: 0;
    flex-wrap: wrap;
  }
  form.mt-space-inline.border-t dl.m-0.flex > div > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Toast alert close row (capture): the fixed toast host splits the alert
     across the viewport edges; keep the alert viewport-bounded and let its
     contents reflow. */
  .toast .alert {
    max-width: min(100vw - 1rem, 100%);
    min-width: 0;
  }
  .toast .alert > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Editor canvas sticky context badges / ai-analysis provenance rows:
     children of the sticky hosts already get floors; the direct badge nodes
     also need the intrinsic floor. */
  fieldset.join.min-w-0 > :not(.btn),
  dl.bao-ai-provenance-sticky div.flex.min-w-0 > *,
  dl.flex.min-w-0 > div.bao-context-badge > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* The models budget dl hosts stat rows SIDE BY SIDE (dl = row flex), so
     the last row lands past the viewport at 400% (self-identity chain:
     `dl.m-0.flex > div.flex.items-baseline > dd` right 430px). GLOBAL
     `dl.m-0.flex { flex-wrap }` was measured net-negative (74 — registry
     dls share the class and wrapping them broke their rows), so the wrap is
     scoped to the budget fieldset's dl only; the row floors already exist
     above (same fieldset family). */
  fieldset.flex.flex-col dl.m-0.flex {
    min-width: 0;
    flex-wrap: wrap;
  }
  /* Editor sticky context badges: the badge row keeps intrinsic min-content
     (dt right 450px at 400%, self-identity chain). Floor + wrap the badge,
     and give its label/value cells the intrinsic wrap. */
  div.bao-context-badge.inline-flex {
    min-width: 0;
    flex-wrap: wrap;
  }
  div.bao-context-badge.inline-flex > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Flex cell hosts without min-width:0 let their content drive the column
     (measured: models "Voice command" row at 449px inside
     `div.flex.min-h-0`, editor badge dt at 450 — both parents lack the
     zero floor, so the wrap rules above never engage). The floor is inert
     unless a child's min-content exceeds the track. */
  div.flex.min-h-0 {
    min-width: 0;
  }
  div.bao-context-badge.inline-flex {
    max-width: 100%;
  }
  /* Editor workbench columns lack min-width:0 — content-width drove the
     canvas sticky to 450px at 400% (8-hop chain: workbench > stage >
     canvas-column > canvas > sticky > badge > dt). The floors are inert
     unless min-content exceeds the track. */
  div.bao-editor-workbench,
  div.bao-editor-stage,
  div.bao-editor-canvas-column {
    min-width: 0;
  }
  /* Main content grids (models page): grid items stretch to content-width
     when the grid host lacks the zero floor (8-hop chain ends
     `section > div.grid.items-stretch > section.card`); floor the host and
     its cells. */
  div.grid.items-stretch {
    min-width: 0;
  }
  div.grid.items-stretch > * {
    min-width: 0;
  }
  /* Editor sticky context-badge dl (dt "Custody" right 450px): wrap the
     badge row and floor its cells — the dl host is a row flex whose items
     keep intrinsic min-content. */
  dl.flex.min-w-0 {
    min-width: 0;
    flex-wrap: wrap;
  }
  dl.flex.min-w-0 > div {
    min-width: 0;
    flex-wrap: wrap;
  }
  dl.flex.min-w-0 dt,
  dl.flex.min-w-0 dd {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Voice-action label fold (HIG Dynamic Type / WCAG 1.4.10; measured round
     21: span right 449px on a 390px viewport at 400% + WCAG text spacing).
     The voice button measures 196px while its one-line label lays out past
     it, so the label itself needs the fold contract at a specificity no
     later same-layer rule can contest: shrink, cap, and break anywhere.
     Inert at the default type size — the row fits on one line. */
  div.flex.flex-wrap > .btn:not(.btn-square):not(.btn-circle) .bao-toolbar-label {
    white-space: normal;
    min-width: 0;
    max-width: 100%;
    flex-shrink: 1;
    overflow-wrap: anywhere;
  }
  /* Editor canvas sticky toolbar (measured round 21: context dt right 450px
     on a 390px viewport at 400%): the sticky is a shrink-0 flex item over an
     overflow-x:hidden canvas column, so its intrinsic min-content escaped
     with no scroller to reveal it. The row pans its inline axis under the
     same contract as the tool-rail and 3D-controls scrollers, and stays
     capped to the canvas width. */
  .bao-editor-canvas > .pointer-events-auto.sticky {
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }

}
@layer bao-tokens {

/* Collection rails retain the full block size of their wrapped controls. */
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar {
  flex-shrink: 0;
  overflow-y: clip;
}

/* Oversized chrome follows the page scrollport until every action row fits again. */
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar[data-toolbar-flow] {
  position: static;
}

/* Disabled explanations and overflow menus reserve the same bounded cell as direct actions. */
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar > :is(.btn, [data-disabled-tip-host], [data-toolbar-action-overflow]) {
  flex: 0 0 auto;
  min-inline-size: 0;
  max-inline-size: 100%;
}
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar > :is([data-disabled-tip-host], [data-toolbar-action-overflow]) > .btn {
  max-inline-size: 100%;
}

/* Toolbar children own their full touch box above the rail's scroll-edge paint. */
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar > :is(.btn, [data-disabled-tip-host]),
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar > [data-disabled-tip-host] > .btn {
  position: relative;
  z-index: var(--z-local, 1);
}

/* Preserve a paint-safe lower buffer when the rail is horizontally scrolled. */
[data-page-type="collection"] [role="toolbar"].bao-sticky-toolbar {
  padding-block-end: var(--space-inline, 0.5rem);
}

/* Joined controls keep each touch target's border box independently hittable. */
.join.join > .join-item.join-item + .join-item.join-item {
  margin-inline-start: 0;
  border-inline-start-width: 0;
}

/* Collection controls scroll below the pinned toolbar on every platform. */
#app-main[data-main-page-type="collection"],
#app-main:has([data-page-type="collection"]) {
  scroll-padding-block-start: calc(var(--scroll-pt-main, 3rem) + var(--touch-target-min, 2.75rem) + var(--space-micro, 0.25rem));
}

[data-page-type="collection"] .bao-table-responsive-row :is(input, select, textarea, button, a) {
  scroll-margin-block-start: calc(var(--touch-target-min, 2.75rem) + var(--space-micro, 0.25rem));
}

/* Pagination inserts an ellipsis between page jumps and tail controls. */
.join.join > .join-item.join-item:not(:first-child) {
  margin-inline-start: 0;
  border-inline-start-width: 0;
}

}
@layer bao-tokens {
/*
 * Tooltip geometry overrides — UNLAYERED on purpose: the daisyui layer
 * follows utilities in cascade-layer order,
so only unlayered author CSS
 * beats daisyUI's own .tooltip rules (z-index: var(--z-raised, 2),
max-width: 20rem).
 */
.tooltip > .tooltip-content,
.tooltip[data-tip]:before,
.tooltip:after {
  /* Toast-tier overlay: tips paint above sticky chrome and dropdowns. */
  z-index: var(--z-tooltip, 1400);
}.tooltip > .tooltip-content,
.tooltip[data-tip]:before {
  /* Viewport-aware cap: the tip never exceeds the viewport safe area. */
  max-width: min(var(--tooltip-max-width, 20rem), calc(100vw - (2 * var(--tooltip-viewport-inset, 0.75rem))));
  max-width: min(var(--tooltip-max-width, 20rem), calc(100dvw - (2 * var(--tooltip-viewport-inset, 0.75rem))));
}

/* Touch-pinned explanations share the tooltip owner and motion preferences. */
.tooltip:is([data-tooltip-pinned], :focus-visible)[data-tip]:not([data-tip=""])::before,
.tooltip:is([data-tooltip-pinned], :focus-visible):is([data-tip]:not([data-tip=""]), :has(.tooltip-content:not(:empty)))::after,
.tooltip:is([data-tooltip-pinned], :focus-visible) > .tooltip-content:not(:empty) {
  opacity: 1;
  --tt-pos: 0rem;
  transition: none;
}
@media (prefers-reduced-motion: no-preference) {
  html:not([data-reduced-motion="true"]) .tooltip:is([data-tooltip-pinned], :focus-visible)::before,
  html:not([data-reduced-motion="true"]) .tooltip:is([data-tooltip-pinned], :focus-visible)::after,
  html:not([data-reduced-motion="true"]) .tooltip:is([data-tooltip-pinned], :focus-visible) > .tooltip-content {
    transition: opacity var(--duration-normal) var(--easing-standard), transform var(--duration-normal) var(--easing-standard);
  }
}

/* Collision placement overrides direction while retaining the canonical tooltip geometry. */
.tooltip[data-tooltip-placement="top"] > .tooltip-content,
.tooltip[data-tooltip-placement="top"][data-tip]::before {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, 0.25rem));
  inset: auto auto var(--tt-off) 50%;
}
.tooltip[data-tooltip-placement="top"]::after {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, 0.25rem));
  inset: auto auto var(--tt-tail) 50%;
}
.tooltip[data-tooltip-placement="bottom"] > .tooltip-content,
.tooltip[data-tooltip-placement="bottom"][data-tip]::before {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, -0.25rem));
  inset: var(--tt-off) auto auto 50%;
}
.tooltip[data-tooltip-placement="bottom"]::after {
  transform: translateX(var(--tt-trans, -50%)) translateY(var(--tt-pos, -0.25rem)) rotate(180deg);
  inset: var(--tt-tail) auto auto 50%;
}

/* Glass and hover transforms create a host stacking context. Elevate that owner
 * while its explanation is visible, so the child tooltip can clear later cards. */
.tooltip:is(:hover, :focus-within, .tooltip-open, [data-tooltip-pinned]) {
  z-index: var(--z-tooltip, 1400);
}

/* Page headers are shared grid items preceding animated cards. Keep their
 * active explanation in the overlay tier across that sibling paint boundary. */
.bao-page-header:has(.tooltip:is(:hover, :focus-within, .tooltip-open, [data-tooltip-pinned])) {
  position: relative;
  z-index: var(--z-tooltip, 1400);
}

/* Drawer chrome owns a stacking context below the shell header. Lift the
 * active explanation owner so collapsed-rail tips can cross that boundary. */
.drawer-side:has(.tooltip:is(:hover, :focus-within, .tooltip-open, [data-tooltip-pinned])) {
  z-index: var(--z-tooltip, 1400);
}

/* Native manual popovers escape scroll clips while retaining the source's
 * canonical tooltip pseudo-elements, theme, and typography. */
.tooltip[data-tooltip-layer][popover] {
  position: fixed;
  inset: var(--bao-tooltip-top) auto auto var(--bao-tooltip-left);
  inline-size: var(--bao-tooltip-width);
  block-size: var(--bao-tooltip-height);
  min-inline-size: 0;
  min-block-size: 0;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
  pointer-events: none;
  font: inherit;
  color: inherit;
  z-index: var(--z-tooltip, 1400);
}
.tooltip[data-tooltip-layer][popover]:popover-open {
  display: block;
}
/* The authored source remains the accessible control; only duplicate visual
 * paint is suppressed, and only while native promotion or Escape owns it. */
.tooltip:is([data-tooltip-layer-active], [data-tooltip-dismissed])[data-tip]:not([data-tip=""])::before,
.tooltip:is([data-tooltip-layer-active], [data-tooltip-dismissed])[data-tip]:not([data-tip=""])::after {
  opacity: 0;
  visibility: hidden;
  transition: none;
}

/* Native documents have one tooltip box owner. Dormant authored pseudo boxes
 * otherwise enlarge scrollports even at zero opacity. Unsupported documents
 * and an explicitly failed source retain the canonical inline paint. */
html[data-tooltip-native="true"] .tooltip[data-tip]:not([data-tooltip-layer]):not([data-tooltip-inline])::before,
html[data-tooltip-native="true"] .tooltip[data-tip]:not([data-tooltip-layer]):not([data-tooltip-inline])::after {
  display: none;
  content: none;
}

}
@layer bao-tokens {
/* Reduced-motion token clamp: class-level `transition: … var(--duration-*)`
   rules out-specificity the universal `*` clamp, so the durations themselves
   collapse at the variable level. Last in bao-tokens beats every :root above. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: var(--reduced-motion-min-duration);
    --duration-normal: var(--reduced-motion-min-duration);
    --duration-slow: var(--reduced-motion-min-duration);
    --duration-glass: var(--reduced-motion-min-duration);
    --duration-press: var(--reduced-motion-min-duration);
    --duration-focus-pulse: var(--reduced-motion-min-duration);
    --duration-skeleton: var(--reduced-motion-min-duration);
    --duration-progress: var(--reduced-motion-min-duration);
    --duration-reduced-motion-calm: var(--reduced-motion-min-duration);
    --duration-pulse: var(--reduced-motion-min-duration);
    --duration-pulse-alpha: var(--reduced-motion-min-duration);
    --stagger-step: 0ms;
    --view-transition-new-delay: 0ms;
  }
}

}
@layer bao-tokens {
/* Toolbar icons are decoration: joined buttons overlap by the join radius, so
 * an icon must never capture the pointer for the seam it overlaps. */
.bao-toolbar-action > svg {
  pointer-events: none;
}
/* A maximized pointer-first stage keeps the tool palette a thin vertical band at every width the rail rides the island vertically (>=40rem surface, the rail axis band). */
@container editor-surface (min-width: 40.01rem) {
  body[data-canvas-maximized="true"] [data-editor-chrome][data-annotation-ready="true"] [data-canvas-island][data-maximized="true"] [data-editor-toolbar]#editor-tool-dock[data-editor-tool-rail],
body[data-canvas-maximized="true"] [data-editor-chrome][data-annotation-ready="true"] [data-canvas-island][data-maximized="true"] [data-editor-toolbar]#editor-tool-dock-3d[data-editor-tool-rail],
body[data-canvas-maximized="true"] [data-editor-chrome][data-annotation-ready="true"] [data-canvas-island][data-maximized="true"] [data-editor-toolbar][data-editor-tool-rail] {
    position: absolute;
    inset-inline-start: var(--space-micro, 0.25rem);
    inset-inline-end: auto;
    inset-block-start: 50%;
    inset-block-end: auto;
    translate: 0 -50%;
    inline-size: var(--editor-palette-gutter);
    min-inline-size: var(--editor-palette-gutter);
    max-inline-size: var(--editor-palette-gutter);
    block-size: auto;
    min-block-size: 0;
    max-block-size: calc(100% - 2 * var(--touch-target-min, 2.75rem) - 2 * var(--space-inline, 0.5rem));
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: flex-start;
    overflow: visible;
  }
}

}
@layer bao-tokens {
/* A compact horizontal 2D rail has no flexible spacer. The spacer is a
 * vertical-rail alignment aid; at tablet width its 100% inline size
 * consumed the whole rail and collapsed the actual tool track to 0px. */
@media (max-width: 64rem) {
  [data-editor-tool-rail][data-editor-tool-dimension="2d"] > .bao-min-w-toolbar-spacer {
    display: none;
  }
}

}

