/* Praxis 0.1.15 — bundled stylesheet
 *
 * Generated by build-package.py from src/. Do not edit: edit the
 * source sheet and rebuild.
 *
 * Contains the foundation (tokens, core) and every component sheet, in
 * cascade order. praxis-reset.css is NOT included — load it separately
 * if you want the element resets.
 */

/* ===========================================================================
   praxis-tokens.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS TOKENS — the single source of the EHSQ-E foundation layer.

   Generated 2026-08-12 from the values 11 pages were each carrying in their
   own :root block. Those 11 copies agreed on 70 of 75 tokens; the five they
   disagreed on are resolved here and marked inline.

   WHY THIS FILE EXISTS
   Every page except four was self-supplying the base palette in its own
   <style>, so "the value of --praxis-radius-md" was a fact about which page you
   were on. This file is now the only place the foundation is declared.

   LOAD ORDER — this file FIRST, then praxis-core.css:
       <link rel="stylesheet" href="praxis-tokens.css" />
       <link rel="stylesheet" href="praxis-core.css" />
   praxis-core.css layers the Praxis light overrides, the dark remaps and the
   --px-* material tokens on top, scoped to body[data-variant="praxis"] — a
   higher specificity than the :root below, so it wins regardless of order.
   Keeping the order anyway means the cascade reads the way it behaves.

   Values are the RESOLVED ones measured in-browser across 27 pages x 2
   themes, not what any single source file claimed.
   ===================================================================== */
:root{
  /* Neutrals */
  --praxis-color-neutral-05:#f4f5f6;
  --praxis-color-neutral-10:#edf0f2;
  --praxis-color-neutral-100:#202a35;
  --praxis-color-neutral-15:#dadfe4;
  --praxis-color-neutral-20:#c8cfd7;
  --praxis-color-neutral-30:#adb8c4;
  --praxis-color-neutral-40:#8898a9;
  --praxis-color-neutral-50:#718499;
  --praxis-color-neutral-60:#4d657f;
  --praxis-color-neutral-70:#465c74;
  --praxis-color-neutral-80:#37485a;
  --praxis-color-neutral-90:#2a3846;
  --praxis-color-white:#ffffff;

  /* Hue scales */
  --praxis-color-blue-10:#edf0fd;
  --praxis-color-blue-50:#6882ef;
  /* #4766eb in the EHSQ-E base; Praxis tunes it down one notch so the
     solution-accent icons (Create New groups, .admin-banner) sit beside the
     teal without competing. Was declared under the variant, which froze
     --praxis-color-status-info at the base value. */
  --praxis-color-blue-60:#4361c4;
  --praxis-color-green-10:#e6f3ee;
  --praxis-color-green-60:#098b53;
  --praxis-color-orange-10:#fdf2e6;
  --praxis-color-orange-60:#ef8100;
  --praxis-color-pink-50:#e82e8b;
  --praxis-color-pink-60:#e30072;
  --praxis-color-pink-90:#810041;
  /* Solution accent, Praxis-only — no purple ramp in the EHSQ-E base. */
  --praxis-color-purple-60:#805ad5;
  --praxis-color-red-10:#fceaeb;
  --praxis-color-red-50:#e7535c;
  --praxis-color-red-60:#e22d38;
  --praxis-color-teal-10:#e8f3f3;
  --praxis-color-teal-20:#c8e1e3;
  --praxis-color-teal-50:#4499a0;
  --praxis-color-teal-60:#1b838b;
  --praxis-color-teal-70:#176f76;
  --praxis-color-teal-80:#135d63;
  --praxis-color-yellow-10:#fdf9e6;
  --praxis-color-yellow-50:#f2d02e;
  --praxis-color-yellow-90:#887100;

  /* Semantic — text / surface / border / interactive */
  /* Praxis strokes are lighter than the base neutral-20 (#c8cfd7): the shell
     leans on lit edges and shadow for separation, so a hairline that strong
     reads as a drawn box. */
  --praxis-color-border-default:#E2E5E9;
  --praxis-color-border-focus:var(--praxis-color-teal-50);
  /* WCAG 1.4.11: this is the control-boundary token (checkbox boxes, input
     underlines, --px-check-stroke), so it has to clear 3:1 against the surface
     it sits on. neutral-40 measured 2.95:1 on white — a rounding error short.
     Repointed to neutral-50 (3.84:1 white, 3.36:1 on surface-subtle, 4.09:1 on
     the dark card) rather than editing the neutral-40 rung, because
     --praxis-color-text-disabled resolves to the same rung and must not move
     with it. Audited 2026-08-20. */
  --praxis-color-border-strong:var(--praxis-color-neutral-50);
  --praxis-color-interactive-active:var(--praxis-color-teal-80);
  --praxis-color-interactive-default:var(--praxis-color-teal-60);
  --praxis-color-interactive-hover:var(--praxis-color-teal-70);
  --praxis-color-surface-default:var(--praxis-color-white);
  --praxis-color-surface-subtle:var(--praxis-color-neutral-10);
  --praxis-color-text-disabled:var(--praxis-color-neutral-40);
  --praxis-color-text-inverse:var(--praxis-color-white);
  --praxis-color-text-link:var(--praxis-color-teal-60);
  /* Praxis ink is a slate rather than the base neutrals, to match the
     layered slate surfaces. Both are literals, not rungs: nothing else
     should move if the neutral ramp is retuned. */
  --praxis-color-text-primary:#2F4051;
  --praxis-color-text-secondary:#5D6977;
  --praxis-color-text-tertiary:#616f7e;   /* promoted from page-local; one value */

  /* Semantic — status */
  --praxis-color-status-danger:var(--praxis-color-red-60);
  --praxis-color-status-info:var(--praxis-color-blue-60);
  --praxis-color-status-success:var(--praxis-color-green-60);
  /* orange-60 is 2.69:1 on white — it fails 1.4.11 as an icon, dot or 2px state
     border, which is what this token is for. orange-70 is 3.64:1 and still reads
     as orange. For warning TEXT use --praxis-tone-warning-fg, not this. */
  --praxis-color-status-warning:var(--praxis-color-orange-70);

  /* Typography */
  --praxis-type-font-sans:'Gilroy','Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --praxis-type-size-base:0.875rem;
  --praxis-type-size-lg:1.125rem;
  --praxis-type-size-md:1rem;
  --praxis-type-size-sm:0.8125rem;
  --praxis-type-size-xl:1.25rem;
  --praxis-type-size-xs:0.75rem;

  /* Spacing */
  --praxis-space-12:.75rem;
  --praxis-space-16:1rem;
  --praxis-space-24:1.5rem;
  --praxis-space-32:2rem;
  --praxis-space-4:.25rem;
  --praxis-space-8:.5rem;

  /* Radius */
  --praxis-radius-full:9999px;
  --praxis-radius-lg:16px;   /* Praxis 8/12/16 — the value that already renders on all 27 pages */
  --praxis-radius-md:12px;   /* Praxis 8/12/16 — the value that already renders on all 27 pages */
  --praxis-radius-sm:8px;   /* Praxis 8/12/16 — the value that already renders on all 27 pages */

  /* Elevation */
  /* Praxis elevation is a hairline plus a contact shadow, not a soft drop —
     the lit-edge material depends on the .5px ring. */
  --praxis-elevation-1:0 0 0 .5px rgba(16,36,58,.05),0 1px 2px rgba(16,36,58,.04);
  --praxis-elevation-2:0 1px 3px 0 rgb(32 42 53 / .1), 0 1px 2px -1px rgb(32 42 53 / .06);
  --praxis-elevation-3:0 4px 6px -1px rgb(32 42 53 / .1), 0 2px 4px -2px rgb(32 42 53 / .06);
  --praxis-elevation-4:0 10px 15px -3px rgb(32 42 53 / .1), 0 4px 6px -4px rgb(32 42 53 / .05);

  /* Motion */
  /* normal/slow existed only inside praxis-filters.css's adapter block; promoted
     here when that adapter was removed, values unchanged. */
  --praxis-motion-normal:180ms;
  --praxis-motion-slow:260ms;
  --praxis-ease-default:cubic-bezier(.32,.72,0,1);   /* Praxis spring — already renders on all 27 pages */
  --praxis-motion-fast:120ms;   /* DECISION 2026-08-12: 120ms canonical (was 100ms on 7 pages) */
  --praxis-motion-slowest:420ms;  /* the one --dur-* step with no equivalent here */

  /* Layout constants promoted from page <head>s, 2026-08-13. Each was identical
     everywhere it appeared, so it was a shared decision written down N times. */
  --praxis-record-rail-w:300px;  /* .record__guide / .record__tree; narrows at breakpoints */
  --praxis-control-h:32px;       /* compact action control height */

  /* ============ Completed from praxis-chrome-legacy.css, 2026-08-12 ============
     praxis-chrome-legacy.css was the only definition of these, and just 4 of 27
     pages load it — so --praxis-color-border-subtle (used unguarded in
     praxis-mazlan.css) resolved to nothing on the other 23. A foundation
     layer has to carry the whole ramp, not the subset the current screens
     happen to use. */

  /* Chrome metrics */
  --praxis-navrail-width:56px;
  --praxis-navrail-width-expanded:240px;

  /* Elevation semantics — mapped onto the --px-* materials below so any
     component that references --praxis-elevation-* inherits the lit-edge
     look. card and card-raised deliberately share --praxis-card; the
     raised TIER is --px-card-raised and is opt-in per component. */
  --praxis-elevation-card:var(--praxis-card);
  --praxis-elevation-card-raised:var(--praxis-card);
  --praxis-elevation-popover:var(--px-overlay);

  /* Palette rungs completed from the EHSQ-E base */
  --praxis-color-blue-100:#202e6a;
  --praxis-color-blue-20:#d3dafa;
  --praxis-color-blue-30:#b0bdf6;
  --praxis-color-blue-40:#8b9ff2;
  --praxis-color-blue-70:#3c57c8;
  --praxis-color-blue-80:#3248a7;
  --praxis-color-blue-90:#283a86;
  --praxis-color-green-100:#043f25;
  --praxis-color-green-20:#c4e3d6;
  --praxis-color-green-30:#95cdb5;
  --praxis-color-green-40:#64b693;
  --praxis-color-green-50:#35a072;
  --praxis-color-green-70:#087647;
  --praxis-color-green-80:#06633b;
  --praxis-color-green-90:#054f2f;
  --praxis-color-orange-100:#6c3a00;
  --praxis-color-orange-20:#fbe1c2;
  --praxis-color-orange-30:#f8c991;
  --praxis-color-orange-40:#f5b05e;
  --praxis-color-orange-50:#f2982e;
  --praxis-color-orange-70:#cb6e00;
  --praxis-color-orange-80:#aa5c00;
  --praxis-color-orange-90:#884a00;
  --praxis-color-pink-10:#fce6f1;
  --praxis-color-pink-100:#660033;
  --praxis-color-pink-20:#f8c2dd;
  --praxis-color-pink-30:#f391c2;
  --praxis-color-pink-40:#ed5ea6;
  --praxis-color-pink-70:#c10061;
  --praxis-color-pink-80:#a10051;
  --praxis-color-red-100:#661419;
  --praxis-color-red-20:#f8cdcf;
  --praxis-color-red-30:#f3a5a9;
  --praxis-color-red-40:#ed7b82;
  --praxis-color-red-70:#c02630;
  --praxis-color-red-80:#a02028;
  --praxis-color-red-90:#811a20;
  --praxis-color-teal-100:#0c3b3f;
  --praxis-color-teal-30:#9dcacd;
  --praxis-color-teal-40:#6fb1b6;
  --praxis-color-teal-90:#0f4b4f;
  --praxis-color-yellow-100:#6c5900;
  --praxis-color-yellow-20:#fbf1c2;
  --praxis-color-yellow-30:#f8e691;
  --praxis-color-yellow-40:#f5db5e;
  --praxis-color-yellow-60:#efc600;
  --praxis-color-yellow-70:#cba800;
  --praxis-color-yellow-80:#aa8d00;

  /* Radius completed */
  --praxis-radius-card:12px;   /* Praxis 8/12/16 geometry, not the base 20px */
  --praxis-radius-xl:16px;

  /* Semantic colour completed */
  --praxis-color-border-subtle:var(--praxis-color-neutral-10);
  --praxis-color-surface-muted:var(--praxis-color-neutral-10);

  /* Spacing completed */
  --praxis-space-20:1.25rem;
  --praxis-space-40:2.5rem;
  --praxis-space-48:3rem;

  /* Typography completed */
  --praxis-type-size-2xl:1.5rem;
  --praxis-type-size-2xs:0.6875rem;
  --praxis-type-size-3xl:1.875rem;

  /* Chrome geometry. --praxis-appbar-h was previously --appbar-height, defined only in
     two page <head>s and in the legacy chrome sheet, while three shared component
     sheets referenced it — so every other page fell through to a hard-coded 64px
     fallback. Canonicalised 2026-08-12. praxis-admin.css still declares the local
     --appbar-h / --navrail-w / --adminnav-w trio for its own grid maths. */
  --praxis-appbar-h:64px;

  /* Drawer motion. The Mazlan drawer's transitions referenced --dur-base,
     --ease-spring and --ease-spring-soft with NO fallback, and those three were
     defined only in contextual-awareness.html — so on every other page the
     declarations were invalid at computed-value time and the drawer had no
     transition at all. Values taken from that page, where the motion was correct. */
  --praxis-motion-drawer:280ms;
  --praxis-ease-spring:cubic-bezier(.34,1.56,.64,1);
  --praxis-ease-spring-soft:cubic-bezier(.22,1,.36,1);

  /* The superseded --praxis-status-* / --praxis-encoding-* families that used to sit
     here were kept 'for parity with the EHSQ-E base'. That base is defunct, so parity
     with it is not a reason. Nothing referenced them; pruned 2026-08-12. */

  /* ============ Centralised from page <head>s, 2026-08-13 ============
     Extracting Praxis surfaced 75 custom properties that pages were defining
     for themselves. Most were aliases for tokens that already existed here
     (--s-16 for --praxis-space-16, --t-sm for --praxis-type-size-sm, and so
     on) and were simply deleted at the call site. The families below were
     not aliases: they are real design decisions that had no canonical home,
     so several pages each invented their own copy and the copies drifted.
     ================================================================== */

  /* Motion: see --praxis-motion-* above. The canonical scale is 120/180/260,
     and the page-level --d-* vocabulary was that same scale under another name.
     The competing --dur-* vocabulary on one page mapped exactly onto
     motion-normal (180) and motion-drawer (280); only its slowest step had no
     equivalent, so --praxis-motion-slowest is defined beside the others. */

  --praxis-ease-spring-out:cubic-bezier(.5,0,.75,0);
  --praxis-ease-spring-bouncy:cubic-bezier(.175,.885,.32,1.275);

  /* Quick-rail motion. Seven pages defined this identically, which is about as
     clear a signal as a token gets. The rail's WIDTH is deliberately not here:
     it was genuinely different on every page (100% / 260px / 300px), so it
     stays a per-page decision. */
  --praxis-rail-duration:480ms;
  --praxis-rail-ease:cubic-bezier(.34,.01,.1,1);
  --praxis-rail-travel:-56px;

  /* Profile-menu motion, from the two pages that animate it. */
  --praxis-menu-duration:520ms;
  --praxis-menu-ease:cubic-bezier(.4,0,.2,1);

  /* Glass material. A frosted translucent surface used by drawers and floating
     panels on three pages, each with its own slightly different recipe. These
     are the light values; praxis-core.css remaps them for dark. */
  --praxis-glass-bg:rgba(255,255,255,.5);
  --praxis-glass-drawer:rgba(255,255,255,.82);
  --praxis-glass-border:rgba(255,255,255,.6);
  --praxis-glass-blur:blur(22px) saturate(1.8);
  --praxis-glass-inset:inset 0 1px 0 rgba(255,255,255,.6);
  --praxis-glass-sheen:linear-gradient(135deg,rgba(255,255,255,.45),rgba(255,255,255,0));
  --praxis-glass-shadow:0 10px 32px -14px rgba(16,26,40,.45);
  --praxis-glass-hover:rgba(255,255,255,.28);
  --praxis-glass-on:rgba(27,131,139,.16);

  /* Status tones — the paired background/foreground used by status chips and
     badges. Two pages defined these, one in literals and one in var()s that
     disagreed with the literals. Resolved to the palette references. */
  --praxis-tone-neutral-bg:var(--praxis-color-neutral-15);
  --praxis-tone-neutral-fg:var(--praxis-color-neutral-70);
  --praxis-tone-info-bg:var(--praxis-color-blue-10);
  --praxis-tone-info-fg:var(--praxis-color-blue-70);
  --praxis-tone-success-bg:var(--praxis-color-green-10);
  --praxis-tone-success-fg:var(--praxis-color-green-70);
  --praxis-tone-warning-bg:var(--praxis-color-orange-10);
  --praxis-tone-warning-fg:var(--praxis-color-orange-90);   /* -70 is 3.30:1 on orange-10; -90 is 6.28:1 */
  --praxis-tone-danger-bg:var(--praxis-color-red-10);
  --praxis-tone-danger-fg:var(--praxis-color-red-70);

  /* ============ The --px-* material layer, moved here 2026-08-26 ============
     These are the surfaces and shadows that carry the Praxis look. They were
     declared under body[data-variant="praxis"] in praxis-core.css, which had
     two costs: a reader who grepped this file for --px-surface concluded it did
     not exist, and any :root token that aliased one of them computed against
     the value this file declared rather than the one that rendered.
     ================================================================== */

  /* Praxis surface/material palette (LIGHT) */
  --px-page:#F0F2F4; --px-surface:#ffffff; --px-surface-2:#F4F6F8; --px-tool:#ffffff;
  --px-drawer:#ffffff;  /* shared drawer/panel surface (Mazlan + filter); dark deepens to navy */
  --px-chip:#EEF1F4; --px-glass:rgba(255,255,255,.82); --px-hover:rgba(16,36,58,.025); --px-dot:rgba(16,36,58,.11);
  /* Gap between a toolbar band and the first card of page content. One token
     because it was set independently per section and drifted: 8px on the record
     page, 6px on the admin pages and profile. Not theme-dependent. */
  --px-toolbar-gutter:16px;
  /* Form-field fill. Fields carry no border, so this is the whole affordance —
     it has to read as a step off --px-surface without going dark enough to
     fight the value inside it. Shared by the record fields (.rfield) and the
     admin/profile fields (.admin-field), which is why it lives here. */
  --px-field:#EEF1F4; --px-field-hover:#E6EAEF;
  /* --px-hover is 2.5% — deliberately faint for large row targets, but invisible
     on a small raised tool. Buttons use this stronger wash instead. */
  --px-hover-btn:rgba(16,36,58,.075);
  --px-scroll:rgba(16,36,58,.28); --px-scroll-hover:rgba(16,36,58,.45);
  --praxis-card:0 0 0 .5px rgba(16,36,58,.06),0 1px 1.5px rgba(16,36,58,.045),0 10px 28px -14px rgba(16,36,58,.10),inset 0 .5px 0 rgba(255,255,255,.7);
  --px-tool-shadow:0 0 0 .5px rgba(16,36,58,.07),0 1px 2px rgba(16,36,58,.05);
  --px-tool-shadow-hover:0 0 0 .5px rgba(16,36,58,.09),0 2px 8px -2px rgba(16,36,58,.16);
  --px-overlay:0 0 0 .5px rgba(16,36,58,.06),0 2px 6px rgba(16,36,58,.06),0 18px 38px -16px rgba(16,36,58,.18);
  --px-edge:rgba(255,255,255,.6);

  /* Elevation TIERS — a single card shadow can't express hierarchy, so a page
     of cards reads as one flat plane. These flank --praxis-card (unchanged, so
     nothing that already uses it moves) to give a page a subject and a support
     cast. Additive only; opt in per component.
       rail   ← recedes: tighter, shallower, no long throw
       card   ← --praxis-card, the default
       raised ← the subject: wider spread, longer throw, brighter lit edge */
  --px-card-rail:0 0 0 .5px rgba(16,36,58,.055),0 1px 1px rgba(16,36,58,.035),inset 0 .5px 0 rgba(255,255,255,.6);
  --px-card-raised:0 0 0 .5px rgba(16,36,58,.07),0 1px 2px rgba(16,36,58,.05),0 2px 6px -2px rgba(16,36,58,.06),0 24px 48px -22px rgba(16,36,58,.20),inset 0 .5px 0 rgba(255,255,255,.85);

  /* primary button — refined teal with lit edge (used by Submit / Create New /
     Reassign task on the record page and the quick-actions on the workspace)

     WCAG 1.4.3 — white on the whole fill, not just the bottom of it. The stops
     were #1f8f97 → #1b838b, which put --px-primary-fg (#fff) at 3.86:1 over the
     top of the gradient and exactly 4.50:1 over the bottom: the label failed
     across the upper half of every primary button in the app.

     Both stops moved down, not just the top one. The old bottom stop was
     already at the 4.50 threshold, so any top lighter than it fails and any top
     darker than it inverts the gradient — the fix has to shift the pair. The
     original step between stops is preserved, so the lit-edge shading reads the
     same, just a slightly deeper teal: 5.00:1 at the top, 5.88:1 at the bottom.

     Not --praxis-color-teal-60 (#1b838b): that token also carries text and
     borders, where it is measured against a light background and is fine.
     Only the fill needed to move. */
  --px-primary-grad:linear-gradient(180deg,#197b83,#156f77);
  --px-primary-fg:#fff;
  --px-primary-shadow:0 0 0 .5px rgba(16,36,58,.2),0 1px 2px rgba(16,36,58,.15),0 8px 18px -8px rgba(25,123,131,.5),inset 0 .5px 0 rgba(255,255,255,.28);
  /* Dots stop short of the top chrome: the grid running up behind the toolbar
     and page header made them read as part of the header rather than the page.
     Page colour is composited over the dots down to --px-dot-clear, then fades
     out over 48px. Fixed attachment, so the stop is a viewport coordinate and
     pages set it to their own chrome height. */
  --px-dot-clear:192px;

}

/* ===========================================================================
   praxis-core.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS CORE — shared Mazlan design-language token + material layer.

   Single source of truth consumed by BOTH the record page and the
   workspace so they read as one design system. Scoped to
   body[data-variant="praxis"]; supports light + dark via body[data-theme].

   Owns: --px-* surface/material tokens, Praxis geometry (8/12/16 radii,
   12px cards), Praxis ink/strokes, the primary-button gradient, the
   generic --praxis-elevation-* → Praxis-material remaps, and the dot-grid
   page texture. Component rules stay in each page, but both pages now share
   the same chrome vocabulary (.appbar, .praxis-navrail) after reconciliation.

   Mazlan DNA: slate layered elevation w/ lit edges, frosted glass,
   12px geometry, lighter strokes, teal→magenta AI signature, brighter
   cyan accent in the dark.
   ===================================================================== */

body[data-variant="praxis"]{
  /* Every TOKEN this block used to declare now lives on :root in
     praxis-tokens.css. See "One declaration site" in that file's header: a
     token declared here was computed on <body>, so any :root alias of it
     silently kept the pre-variant value. What is left is the one thing that
     genuinely needs an element rather than the root — the page material
     painted on <body>. */
  /* dotted-grid page texture — faded at left/right edges (page-color gradient
     over the dots). Fixed to the viewport so it reads as page material. */
  background-color:var(--px-page);
  background-image:
    /* The horizontal fade is symmetric about the CONTENT area, not the
       viewport. Left of the rail's right edge is fully covered — the Praxis
       rail is transparent, so dots would otherwise run behind it — then the
       fade spans the same 7% the right edge uses, measured from that edge
       rather than from x=0. Before this, the left fade ran 0→7% of the
       viewport, so the rail ate half of it and the visible ramp was half the
       width of the one on the right. */
    linear-gradient(to right,
      var(--px-page) 0,
      var(--px-page) var(--navrail-w, 56px),
      transparent calc(var(--navrail-w, 56px) + 7%),
      transparent 93%,
      var(--px-page) 100%),
    linear-gradient(to bottom, var(--px-page) 0, var(--px-page) var(--px-dot-clear),
                    transparent calc(var(--px-dot-clear) + 48px)),
    radial-gradient(circle, var(--px-dot) 1px, transparent 1px);
  background-size:100% 100%, 100% 100%, 18px 18px;
  background-repeat:no-repeat, no-repeat, repeat;
  background-attachment:fixed, fixed, fixed;
  color:var(--praxis-color-text-primary);
}

/* WHY :root:has(body[...]) AND NOT body[...] — see praxis-tokens.css.
   Every token declaration in Praxis lives on :root. A custom property is
   substituted at the element where the DECLARATION lives, so a token declared
   on <body> is invisible to any :root alias of it: `:root{--a:var(--b)}` plus
   `body{--b:x}` computes --a on :root against the OLD --b, and body inherits
   that. Four tokens shipped broken this way. Keeping every declaration on one
   element makes the whole class impossible rather than merely absent.

   The theme attribute stays on <body>, so this is not a breaking change and the
   ~220 component rules keyed on body[data-theme="dark"] are untouched — they
   only READ tokens. :has() lets :root see the attribute without moving it.
   Specificity is deliberate: :root:has(body[data-theme="dark"]) is (0,2,1) and
   the variant-scoped form below is (0,3,1), which preserves the exact
   precedence the two body-scoped blocks had. */
/* ---- base DARK: recolors token-driven styles (covers Miramar too) ---- */
:root:has(body[data-theme="dark"]){
  color-scheme:dark;
  --praxis-color-surface-default:#192336; --praxis-color-surface-subtle:#161c27;
  --praxis-color-neutral-05:#10151e; --praxis-color-neutral-10:#222b39;
  --praxis-color-neutral-15:#2a3342; --praxis-color-neutral-20:#3a4556;
  --praxis-color-border-default:rgba(255,255,255,.10);
  --praxis-color-text-primary:#e7ebf1; --praxis-color-text-secondary:#9aa7b4; --praxis-color-text-disabled:#62707e;
  --praxis-color-text-tertiary:#8b98a6;  /* 4.6:1 on the dark card (was 3.65:1) */

  /* FROZEN ALIASES, fixed 2026-08-18. Both are declared on :root as
     var(--praxis-color-neutral-10). Custom-property substitution happens at the
     element where the DECLARATION lives, so they were computed on :root against
     the LIGHT neutral-10 and body inherited that value — the remap two lines
     above never reached them, and both resolved to #edf0f2 in dark. Restating
     them here is the fix; --praxis-color-surface-subtle above never had the bug
     because it was always given its own value rather than relying on the alias
     to carry the theme.

     Verified before and after with getComputedStyle in both themes, not by
     reading the file: a resolver asked for the dark value substitutes the dark
     rung and reports a difference the browser does not produce, which is exactly
     why this was invisible for so long. */
  --praxis-color-border-subtle:#222b39; --praxis-color-surface-muted:#222b39;
}

/* ---- Praxis DARK: Mazlan dark surfaces + materials ---- */
:root:has(body[data-variant="praxis"][data-theme="dark"]){
  /* Glass and status tones, centralised from page <head>s 2026-08-13. The
     light values live in praxis-tokens.css; these are the dark counterparts
     the three glass pages and two tone pages were each carrying locally. The
     tone foregrounds are literals rather than palette references because the
     palette's -70 steps are tuned for light surfaces and go muddy on dark. */
  --praxis-glass-bg:rgba(30,38,52,.52);
  --praxis-glass-drawer:rgba(28,35,48,.86);
  --praxis-glass-border:rgba(255,255,255,.14);
  --praxis-glass-blur:blur(24px) saturate(1.6);
  --praxis-glass-inset:inset 0 1px 0 rgba(255,255,255,.06);
  --praxis-glass-sheen:linear-gradient(135deg,rgba(255,255,255,.08),rgba(255,255,255,0));
  --praxis-glass-shadow:0 14px 36px -14px rgba(0,0,0,.6);
  --praxis-glass-hover:rgba(255,255,255,.06);
  --praxis-glass-on:rgba(41,210,215,.16);

  --praxis-tone-neutral-bg:rgba(255,255,255,.08); --praxis-tone-neutral-fg:#9aa7b4;
  --praxis-tone-info-bg:rgba(71,102,235,.18);     --praxis-tone-info-fg:#8aa0f5;
  --praxis-tone-success-bg:rgba(9,139,83,.16);    --praxis-tone-success-fg:#4cd08a;
  --praxis-tone-warning-bg:rgba(239,129,0,.16);   --praxis-tone-warning-fg:#f5b45c;
  --praxis-tone-danger-bg:rgba(226,45,56,.18);    --praxis-tone-danger-fg:#f28b91;

  /* The teal scale had no dark treatment at all, so four pages each patched it
     locally. teal-80 is a light-mode ink and measured 1.55:1 on a dark panel —
     illegible — while teal-10/20 are near-white and glared as light blocks.
     Values from process-map-drill.html, where the remap was done properly.
     This is the same class of bug as the filters sheet flipping palette
     primitives in its own scope: the fix belongs in the theme, not the page. */
  --praxis-color-teal-10:#12313c; --praxis-color-teal-20:#16404c;
  --praxis-color-teal-80:#5CE0E5;

  /* Same gap, blue-10: no dark treatment at all, so it stayed #edf0fd — near-
     white — in both themes. Every other tint rung in this scale (teal, green,
     orange, red above/below) gets one; blue-10 was the one left out. Neither
     of its two real consumers in src/ is actually exposed by this — the info
     tone (`--praxis-tone-info-bg`) and `.admin-banner` both already restate
     their own dark value directly rather than reading blue-10 through the
     alias — so this is a consistency fix for any *future* direct read of the
     token (a Beakon reskin build reached for it directly for a table-row
     hover fill and got the light value in dark mode), not a fix to a known-
     broken shipped component.
     #131c33 follows the same recipe as teal-10 above — a dark, muted rung of
     blue-10's own hue rather than the brightened accent hue blue-60 uses —
     but unlike teal-10 it is a first proposed value, not lifted from an
     existing correct implementation and not yet checked in a browser against
     real content. Treat the exact hex as a starting point for a design pass
     if anything ever renders ink directly on it. */
  --praxis-color-blue-10:#131c33;

  /* Status inks brighten on dark for the same reason. */
  --praxis-color-status-success:#3ecf8e; --praxis-color-status-warning:#ffa32e;

  /* status-danger was the one status ink with no dark treatment, and unlike its
     siblings that was a measurable failure rather than a stylistic gap: red-60
     (#e22d38) is 3.50:1 on the dark card #192336, against the 4.5:1 that WCAG
     1.4.3 asks of text. Its three siblings sit at 5.30, 7.88 and 7.89:1.

     #ed7b82 is red-40, an existing palette rung rather than a new value, at
     5.81:1 — inside the band the other three occupy. red-50 was the closer step
     in hue but measures 4.34:1 and still fails. Added 2026-08-18. */
  --praxis-color-status-danger:#ed7b82;

  /* status-info was the third of these and was missed: it aliases blue-60 on
     :root, and blue-60 IS remapped for the Praxis variant, so the alias froze at
     the light #4766eb while its two siblings themed correctly. #7a93e0 is the
     dark blue-60 this variant already declares — 5.30:1 on the dark card. */
  --praxis-color-status-info:#7a93e0;

  /* THE INTERACTIVE TRIAD, fixed 2026-08-18.

     --praxis-color-interactive-active aliases teal-80, which the remap below
     turns into #5CE0E5 — but the alias is declared on :root, so it froze at the
     light #135d63. In dark that made the resting state bright cyan and the
     PRESSED state a dark ink: pressing a control made it go darker. Inverted
     feedback, and the most user-visible of the four frozen aliases.

     interactive-hover is restated too, and that is a deliberate widening of the
     fix rather than an accident. It aliases teal-70, which has no dark treatment
     at all, so it was not frozen — merely left behind. Fixing active alone would
     have produced default #29D2D7 -> hover #176f76 (dark) -> active #5CE0E5
     (bright): a triad that goes dark then bright, which is worse than the bug it
     replaces.

     #42D9DE is the one invented value in this fix, the arithmetic midpoint of the
     two existing theme values it sits between. The progression now mirrors light
     in DIRECTION: each interaction step increases contrast against the ink on the
     fill. Measured with --px-primary-fg (#08313a in dark):
         light, ink #fff:    4.50 -> 5.88 -> 7.57
         dark,  ink #08313a: 7.48 -> 8.08 -> 8.77 */
  --praxis-color-interactive-hover:#42D9DE;
  --praxis-color-interactive-active:#5CE0E5;

  --praxis-color-text-primary:#e7ebf1; --praxis-color-text-secondary:#9aa7b4;
  --praxis-color-border-default:rgba(255,255,255,.10);
  --praxis-color-surface-default:#192336; --praxis-color-surface-subtle:#161c27;
  --praxis-color-interactive-default:#29D2D7; --praxis-color-text-link:#5CE0E5;
  /* Solution accents brighten on dark so they read on tinted chips */
  --praxis-color-blue-60:#7a93e0; --praxis-color-orange-60:#ffa94d; --praxis-color-purple-60:#b088e8;
  --px-page:rgb(14,19,36); --px-surface:#192336; --px-surface-2:#232c3b; --px-tool:rgba(255,255,255,.06);
  --px-drawer:#0e1324;  /* deep navy — matches the external Mazlan prototype */
  --px-chip:rgba(255,255,255,.07); --px-glass:rgba(26,32,45,.72); --px-hover:rgba(255,255,255,.05); --px-dot:rgba(255,255,255,.09);
  /* Opaque, not a translucent white: fields sit on several different surfaces
     (card, drawer, admin panel) and a translucent fill would land on a
     different colour on each one. */
  --px-field:#262F3F; --px-field-hover:#2C3646;
  --px-hover-btn:rgba(255,255,255,.10);
  --px-scroll:rgba(255,255,255,.26); --px-scroll-hover:rgba(255,255,255,.42);
  --px-edge:rgba(255,255,255,.08);
  --praxis-card:0 0 0 .5px rgba(255,255,255,.08),0 1px 2px rgba(0,0,0,.4),0 16px 40px -18px rgba(0,0,0,.6),inset 0 .5px 0 rgba(255,255,255,.07);
  --px-tool-shadow:0 0 0 .5px rgba(255,255,255,.08),0 1px 2px rgba(0,0,0,.35);
  --px-tool-shadow-hover:0 0 0 .5px rgba(255,255,255,.12),0 4px 12px -2px rgba(0,0,0,.5);
  --px-overlay:0 0 0 .5px rgba(255,255,255,.08),0 2px 6px rgba(0,0,0,.4),0 24px 50px -20px rgba(0,0,0,.7);
  /* Elevation tiers, dark: depth carried by shadow density + edge light, since
     dark surfaces can't lean on a cast shadow the way light ones do. */
  --px-card-rail:0 0 0 .5px rgba(255,255,255,.07),0 1px 2px rgba(0,0,0,.30),inset 0 .5px 0 rgba(255,255,255,.05);
  --px-card-raised:0 0 0 .5px rgba(255,255,255,.11),0 1px 2px rgba(0,0,0,.45),0 4px 10px -3px rgba(0,0,0,.40),0 32px 60px -24px rgba(0,0,0,.75),inset 0 .5px 0 rgba(255,255,255,.10);
  /* Dark primary CTA: subtle cyan gradient from the bright interactive color,
     with dark ink (mirrors the light-mode teal gradient treatment). */
  --px-primary-grad:linear-gradient(180deg,#29d2d7,#1fb4b9);
  --px-primary-fg:#08313a;
  /* The light --px-primary-shadow is built from navy (rgba(16,36,58,…)) and a
     white lit edge — on a dark page the navy is invisible, so bold CTAs were
     wearing a shadow that did nothing. Same four layers, retuned: hairline,
     contact shadow, coloured ambient glow, lit top edge. */
  --px-primary-shadow:0 0 0 .5px rgba(41,210,215,.30),0 1px 2px rgba(0,0,0,.45),0 8px 18px -8px rgba(41,210,215,.45),inset 0 1px 0 rgba(255,255,255,.22);

  /* Soft primary — the translucent-teal treatment the quick actions and the
     nav-rail Create button use in dark instead of the bold cyan CTA. Same
     construction as the light primary so it reads as a raised object: a
     top-lit gradient, a hairline border, a contact shadow and a teal glow. */
  /* Opaque, not translucent: these sit directly on the page and the dot grid
     was reading through them. The stops are the exact composite of the old
     20%/10% cyan over --px-page, so the colour is unchanged — only the page
     behind it stops showing. (Tuned for the page; a soft-primary button on a
     card would need its own composite.) */
  --px-primary-soft:linear-gradient(180deg,rgb(19,57,72) 0%,rgb(17,38,54) 100%);
  --px-primary-soft-fg:#5CE0E5;
  --px-primary-soft-shadow:0 0 0 .5px rgba(41,210,215,.32),0 1px 2px rgba(0,0,0,.40),0 8px 18px -10px rgba(41,210,215,.40),inset 0 1px 0 rgba(255,255,255,.12);
  --px-primary-soft-hover:linear-gradient(180deg,rgb(22,72,86) 0%,rgb(18,48,63) 100%);
  --px-primary-soft-shadow-hover:0 0 0 .5px rgba(41,210,215,.44),0 1px 2px rgba(0,0,0,.40),0 10px 22px -10px rgba(41,210,215,.55),inset 0 1px 0 rgba(255,255,255,.16);
  /* Quieter variant for rows of soft-primary buttons — four quick actions side
     by side against the page pooled the full ambient glow into a continuous
     band. Same hairline and lit edge, glow reduced to a hint. */
  --px-primary-soft-shadow-sm:0 0 0 .5px rgba(41,210,215,.28),0 1px 2px rgba(0,0,0,.28),0 4px 10px -8px rgba(41,210,215,.22),inset 0 1px 0 rgba(255,255,255,.10);
  --px-primary-soft-shadow-sm-hover:0 0 0 .5px rgba(41,210,215,.40),0 1px 2px rgba(0,0,0,.28),0 6px 14px -8px rgba(41,210,215,.32),inset 0 1px 0 rgba(255,255,255,.14);
}

/* Dark mode: primary CTAs use dark ink on the cyan gradient. Higher specificity
   than the per-page color:var(--praxis-color-white) rules, so it wins regardless of load order. */
body[data-variant="praxis"][data-theme="dark"] .tbtn--run,
body[data-variant="praxis"][data-theme="dark"] .tbtn--primary,
body[data-variant="praxis"][data-theme="dark"] .pill-btn,
body[data-variant="praxis"][data-theme="dark"] .btn--primary,
body[data-variant="praxis"][data-theme="dark"] .qa,
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__btn--create,
body[data-variant="praxis"][data-theme="dark"] .mz-input__send,
body[data-variant="praxis"][data-theme="dark"] .tbtn--run .icon,
body[data-variant="praxis"][data-theme="dark"] .tbtn--primary .icon,
body[data-variant="praxis"][data-theme="dark"] .tbtn--primary .material-symbols-rounded,
body[data-variant="praxis"][data-theme="dark"] .pill-btn .icon{
  color:var(--px-primary-fg);
}

/* ---- Checkboxes ----------------------------------------------------------
   Custom appearance so the UNFILLED box takes the card surface (--px-surface:
   slate in dark, white in light) — it reads as part of the card, showing only
   a hairline, instead of a bright system box. Checked fills with the pink
   selection accent used app-wide. Excludes the toggle switches (.switch),
   whose <input> is a hidden full-size hit target. */
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *){
  -webkit-appearance:none; appearance:none; margin:0; flex-shrink:0;
  width:16px; height:16px; border-radius:4px;
  border:1.5px solid var(--praxis-color-border-default);
  background:var(--px-surface);
  cursor:pointer; display:inline-grid; place-content:center;
  /* An empty inline-grid synthesises its baseline from the box's own bottom
     edge; :checked adds a real ::after item into the grid and the baseline
     is taken from that instead — same 16x16 box, different line-box
     measurement. Inside an .admin-table row that read as the row itself
     resizing by a pixel on check (measured 46px unchecked / 45px checked in
     a bare single-row table). vertical-align:middle removes the dependency
     on either baseline. */
  vertical-align:middle;
  transition:background var(--praxis-motion-fast,120ms) var(--praxis-ease-default),
             border-color var(--praxis-motion-fast,120ms) var(--praxis-ease-default);
}
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):hover{
  border-color:var(--praxis-color-text-tertiary);
}
/* Checked / indeterminate fill: neutral, not the pink selection accent. Pink is
   the app's attention colour, and a page of ticked boxes turned into a field of
   accent marks competing with everything that genuinely needed noticing.

   Both values are theme tokens rather than fixed colours, so the pair inverts
   with the theme and the mark always contrasts its fill:
     light  ink #2F4051 box, white  tick
     dark   #e7ebf1 box,   slate tick
   --px-surface is the mark on purpose — it's whatever the surface behind the
   box is, so the tick reads as a cut-out rather than a painted-on colour. */
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *){
  --px-check-fill:var(--praxis-color-text-primary);
  --px-check-mark:var(--px-surface);
}
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):checked{
  background:var(--px-check-fill);
  border-color:var(--px-check-fill);
}
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):checked::after{
  content:''; width:4px; height:8px; margin-top:-1px;
  border:solid var(--px-check-mark); border-width:0 2px 2px 0; transform:rotate(45deg);
}
/* Indeterminate — a partial selection. appearance:none removes the platform
   control entirely, so the browser's own dash never renders and an
   indeterminate box looked identical to an empty one. Same filled ground as
   :checked so it reads as "acted upon", with a dash instead of a tick. Placed
   after :checked because the two can be set at once: an indeterminate box also
   carries checked=true in some states, and the dash is the truthful mark. */
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):indeterminate{
  background:var(--px-check-fill);
  border-color:var(--px-check-fill);
}
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):indeterminate::after{
  content:''; width:8px; height:0; margin:0; transform:none;
  border:solid var(--px-check-mark); border-width:0 0 2px 0; border-radius:1px;
}
body[data-variant="praxis"] input[type="checkbox"]:not(.switch):not(.switch *):focus-visible{
  outline:2px solid color-mix(in srgb, var(--praxis-color-interactive-default) 60%, transparent);
  outline-offset:2px;
}


/* =====================================================================
   Scrollbars — hidden until the scrolling element is hovered

   Uses the standard scrollbar-color only — the one scrollbar property that
   doesn't change the gutter, so Chrome/Firefox keep their overlay scrollbars
   and nothing reflows when the thumb appears. (Declaring ::-webkit-scrollbar
   or scrollbar-width would switch those elements to classic scrollbars and
   permanently narrow the content.)

   :focus-within is included so keyboard users, who never trigger :hover, still
   get the position indicator while arrowing through a scrolled region.
   ===================================================================== */
/* The reset has to land on every element, not just :root. scrollbar-color is
   inherited, so a single :root declaration lets a hovered ancestor cascade its
   revealed value into every descendant — hover anywhere and the whole page's
   scrollbars light up. Declaring it on * re-breaks that inheritance at each
   level, so an element is only revealed when it is itself under the pointer. */
*{ scrollbar-color: transparent transparent; }
*:hover, *:focus-within{ scrollbar-color: var(--px-scroll) transparent; }

/* Safari has no scrollbar-color yet, but its overlay scrollbars already fade
   out on their own, so it needs nothing here. Engines with classic scrollbars
   and no scrollbar-color support fall back to always-visible, which is the
   safe direction — the content stays reachable. */

/* =====================================================================
   Press feedback — buttons shrink slightly while held

   Follows the Mazlan prototype: hover lifts, press returns to the baseline and
   scales down, around 150ms. Small controls take 0.95 and larger ones 0.97
   there, so the travel reads the same at both sizes; the split below is by
   footprint rather than by component.

   Written with :where() so every selector here sits at specificity 0. A
   component that wants different press behaviour — or none — overrides it with
   any class rule, without needing !important or a more specific selector.
   ===================================================================== */
@media (prefers-reduced-motion: no-preference){
  :where(button, [role="button"], .btn, a.btn, .tbtn, .qa, .pill-btn, .icon-btn,
         .praxis-navrail__btn, .card__link, .rep__pin, .segswitch > button){
    transition: transform 150ms var(--praxis-ease-default, cubic-bezier(.32,.72,0,1));
  }
  /* Small controls: icon buttons and compact pills */
  :where(button, [role="button"], .icon-btn, .card__link, .rep__pin,
         .praxis-navrail__btn, .segswitch > button):active:not(:disabled):not([aria-disabled="true"]){
    transform: scale(.95);
  }
  /* Larger surfaces travel less, or the shrink reads as a jump */
  :where(.btn, a.btn, .tbtn, .qa, .pill-btn):active:not(:disabled):not([aria-disabled="true"]){
    transform: scale(.97);
  }
  /* Anything that already moves on press keeps its own treatment — these carry a
     lift or offset that scaling would fight. */
  :where(.upnext__open, .lg-btn):active{ transform: scale(.97); }
}


/* =====================================================================
   HOVER — every control shifts its background, not just its glyph.

   Several families only recoloured their icon on hover: the toolbar tools,
   the view switch, the pager, the nav-rail buttons. An icon-only change reads
   as a rendering artefact rather than a target, and it disappears entirely for
   anyone who isn't looking straight at the glyph.

   Prefixed with body[data-variant="praxis"] purely for specificity — this
   sheet loads before the page stylesheets that declare the bare `.tbtn:hover`
   rules, so a plain class selector here would lose on source order.

   Excluded: controls whose hover is already a background change (primary CTAs,
   the selected segswitch option, the rail's Create button), and disabled or
   already-selected states, which shouldn't invite a click.
   ===================================================================== */
body[data-variant="praxis"] :where(
  .tbtn, .viewswitch__btn, .pager__btn, .icon-btn, .filter-toggle,
  .praxis-navrail__btn, .praxis-navrail__link, .card__link, .rep__pin, .cn-item
):not(:disabled):not([aria-disabled="true"]):not(.praxis-navrail__btn--active):hover{
  background-color:var(--px-hover-btn);
}

/* The active rail item is a filled pink square. The generic hover above was
   repainting it with the surface colour, so it went white on hover and its
   white glyph all but disappeared. It keeps its fill and shifts one step
   lighter instead — pink-40, the tint above pink-50. */
body[data-variant="praxis"] .praxis-navrail__btn--active:hover{
  background-color:var(--praxis-color-pink-40,#ed5ea6);
  background-image:none;
  color:var(--praxis-color-white);
  box-shadow:none;
}
/* The tools and rail buttons are raised on their own surface, so a flat wash
   would erase them — tint over the surface and deepen the shadow instead.
   .tbtn also needed :not(.tbtn--primary):not(.btn--primary):not(.pill-btn):
   without them this rule's (0,5,1) beat the primary's own hover rule's
   (0,3,1) outright — :is() takes its highest-weight argument, one class, so
   no amount of source order could fix it — and painted a near-white wash
   under white label text on every primary CTA rendered as a .tbtn. */
body[data-variant="praxis"] .tbtn:not(.tbtn--run):not(.tbtn--primary):not(.btn--primary):not(.pill-btn):not(:disabled):hover,
body[data-variant="praxis"] .pager__btn:not(.pager__btn--active):not(:disabled):hover,
body[data-variant="praxis"] .praxis-navrail__btn:not(.praxis-navrail__btn--create):not(.praxis-navrail__btn--active):hover,
body[data-variant="praxis"] .praxis-navrail__link:hover{
  background-color:var(--px-surface);
  background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  box-shadow:var(--px-tool-shadow-hover);
}
body[data-variant="praxis"] .pager__btn:disabled:hover{
  background-image:none; background-color:var(--px-surface); box-shadow:none;
}
/* Already-selected options keep their raised treatment; only the wash changes. */
body[data-variant="praxis"] .viewswitch__btn--active:hover{
  background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
}


/* =====================================================================
   CANONICAL PRIMARY BUTTON — one definition for the whole application.

   Before this, the primary CTA rendered four different ways: the search
   toolbar's treatment (40px, 18px padding, radius 10, gradient + lit-edge
   shadow), the record pages' near-copy with 16px padding and a vestigial 1px
   border, the inspection variant 4px shorter than everything else, and
   Contextual Awareness's flat teal fill that never received the Praxis
   treatment at all.

   The search treatment wins: it was the majority and the most refined. This
   rule is the single source; the per-page copies are deleted.

   :is() not :where() — this sheet loads before the page styles, so it needs
   real specificity to win on source order (see 7.1).
   ===================================================================== */
body[data-variant="praxis"] :is(.btn--primary, .tbtn--primary, .tbtn--run, .pill-btn){
  height:40px; padding:0 18px; border:0; border-radius:10px;
  background:var(--px-primary-grad); color:var(--px-primary-fg);
  box-shadow:var(--px-primary-shadow);
}
body[data-variant="praxis"] :is(.btn--primary, .tbtn--primary, .tbtn--run, .pill-btn):hover{
  filter:brightness(1.06); background:var(--px-primary-grad);
}

/* =====================================================================
   Toolbar primaries — one treatment everywhere.

   Light already agreed: every toolbar CTA renders the teal --px-primary-grad.
   Dark had drifted three ways — Run on search used the soft chip, Submit and
   New Report used the bold cyan CTA, and the inspection variant had no dark
   treatment at all and was still painting the light-mode teal on a dark page.

   The soft chip wins because it's what the quick actions and the nav-rail
   Create button already use: a toolbar CTA sits among neutral tools, and the
   bold cyan made it shout across the whole bar.

   Scoped to toolbars on purpose — the login button and in-dialog CTAs are
   isolated on their own surfaces, where the bold treatment still reads right.

   :is() rather than :where() on the button classes: :where() contributes zero
   specificity, which tied this with record-page.html's own
   body[data-variant] .btn--primary and lost on source order.
   ===================================================================== */
body[data-variant="praxis"][data-theme="dark"]
  :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar)
  :is(.btn--primary, .tbtn--primary, .tbtn--run, .pill-btn){
  background:var(--px-primary-soft);
  color:var(--px-primary-soft-fg);
  box-shadow:var(--px-primary-soft-shadow-sm);
}
/* All three parts are :is(), not :where(): the older "dark ink on cyan" rule
   above reaches these icons at (0,4,1) via `[data-theme] .tbtn--primary .icon`,
   and a zero-specificity :where() chain lost to it — the glyphs went
   near-black on the dark chip and read as missing. */
body[data-variant="praxis"][data-theme="dark"]
  :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar)
  :is(.btn--primary, .tbtn--primary, .tbtn--run, .pill-btn) :is(.icon, svg, .material-symbols-rounded){
  color:var(--px-primary-soft-fg);
}
body[data-variant="praxis"][data-theme="dark"]
  :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar)
  :is(.btn--primary, .tbtn--primary, .tbtn--run, .pill-btn):hover{
  background:var(--px-primary-soft-hover);
  color:var(--px-primary-soft-fg);
  box-shadow:var(--px-primary-soft-shadow-sm-hover);
  filter:none;
}

/* =====================================================================
   Dark — secondary toolbar buttons take the inactive nav-rail treatment

   They were flat: --px-tool in dark is a 6% white wash, so a row of tool
   pills read as text floating on the page rather than as buttons. The rail's
   inactive items solve the same problem one level over — a solid slate chip
   with a hairline and an inset top highlight, which is what gives them their
   raised edge — so they're matched here rather than invented again. Hover
   matches too: the same chip with the --px-hover-btn overlay and a brighter
   hairline.

   Only the secondaries. The primaries above keep the soft teal CTA, and the
   label stays --praxis-color-text-primary rather than the rail's #c8cfd7: that
   value is tuned for an icon-only glyph, and text needs the extra contrast.
   ===================================================================== */
body[data-variant="praxis"][data-theme="dark"]
  :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar)
  :is(.tbtn, .sortbtn):not(.btn--primary):not(.tbtn--primary):not(.tbtn--run):not(.pill-btn){
  background:var(--px-surface-2,#232c3b);
  box-shadow:0 0 0 .5px rgba(255,255,255,.08),inset 0 .5px 0 rgba(255,255,255,.06);
}
body[data-variant="praxis"][data-theme="dark"]
  :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar)
  :is(.tbtn, .sortbtn):not(.btn--primary):not(.tbtn--primary):not(.tbtn--run):not(.pill-btn):hover{
  color:var(--praxis-color-white);
  background-color:var(--px-surface-2,#232c3b);
  background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  box-shadow:0 0 0 .5px rgba(255,255,255,.14),inset 0 .5px 0 rgba(255,255,255,.08);
}

/* =====================================================================
   WCAG 1.4.3 — dark ink on the bright cyan fill

   In dark, --praxis-color-interactive-default brightens to #29D2D7, a LIGHT
   colour (luminance .55). Anything using it as a FILL with white text
   measures 1.86:1 against a 4.5 minimum. Selected segments, count badges and
   the workspace Create button all did.

   --px-primary-fg is the token the rest of the app already uses for exactly
   this: dark ink on cyan, 12.4:1.
   ===================================================================== */
body[data-variant="praxis"][data-theme="dark"] :is(
  .capa-prio__step--active, .subtab__count,
  .nav-menu-drawer__item--active, .nav-menu-drawer__item--active span
){ color:var(--px-primary-fg); }

/* ---------------------------------------------------------------------
   WCAG 2.4.1 Bypass blocks — skip to content

   Every page opens with an app bar and a nav rail, so a keyboard user
   traverses ~10 controls before reaching content. Off-screen until focused,
   then it lands in the top-left as a normal Praxis pill. Nothing is visible
   at rest, so the default rendering is unchanged.
   --------------------------------------------------------------------- */
.px-skip{
  position:fixed; top:8px; left:8px; z-index:1000;
  display:inline-flex; align-items:center; height:36px; padding:0 16px;
  border-radius:10px; background:var(--px-surface); color:var(--praxis-color-text-link);
  font-size:.875rem; font-weight:600; text-decoration:none;
  box-shadow:var(--px-tool-shadow-hover);
  transform:translateY(-150%); transition:transform 120ms var(--praxis-ease-default,ease);
}
.px-skip:focus-visible, .px-skip:focus{ transform:none; outline:2px solid var(--praxis-color-teal-50); outline-offset:2px; }
@media (prefers-reduced-motion: reduce){ .px-skip{ transition:none; } }

/* ============================================================================
   PHONE GUTTER — one value for the app bar, the page header and the content
   ============================================================================
   These three were set independently and none of them agreed. Measured at
   390px before this rule:

     app bar        8px left  / 24px right   (its own padding, plus a 16px
                                              margin-right on the right cluster)
     page header    16-84px   / 0            (--ph-pad-x, set per page: 20, 24,
                                              32, and 84 on admin)
     content        10-24px                  (--home-gutter, --sp-gutter, or a
                                              hardcoded padding per family)

   --px-gutter is now the single source below 640px. Everything else derives
   from it, so the three edges line up on every page and there's one number to
   change.

   Specificity note: these need body[data-variant="praxis"] because
   praxis-core.css loads BEFORE each page's own <style>, and the per-page
   `.pageheader{--ph-pad-x:24px}` rules would otherwise win on source order.
   ========================================================================= */
@media (max-width:640px){
  body[data-variant="praxis"]{ --px-gutter:16px; }

  /* App bar. The right-hand cluster carried an extra margin-right on top of
     the bar's padding, which is what made the bar asymmetric — 8 left, 24
     right. The padding alone sets the inset now. */
  body[data-variant="praxis"] .appbar{ padding-inline:var(--px-gutter); }
  body[data-variant="praxis"] .appbar__right{ margin-right:0; }

  /* Page header. Also feeds .admin-body and .rm-body, which already derive
     their padding from --ph-pad-x, so those align for free. */
  /* Both the token AND the padding: most pages derive their page-header inset
     from --ph-pad-x, but a few (the record variants) declare padding directly,
     so the token alone doesn't reach them. */
  /* The toolbar band comes too, so its closing hairline stays flush with the
     header's at this width instead of stepping in or out by a few px. */
  body[data-variant="praxis"] .pageheader,
  body[data-variant="praxis"] .toolbar{ --ph-pad-x:var(--px-gutter); padding-inline:var(--px-gutter); }

  /* The admin side nav is 60px of unlabelled icons at this width — unusable,
     and it offsets the page header and content so they can't line up with the
     full-width app bar. Hidden here; its destinations are folded into the nav
     drawer instead (praxis-navdrawer.js), so nothing becomes unreachable. */
  body[data-variant="praxis"] .adminnav{ display:none !important; }

  /* The page switcher duplicates what the nav drawer and the profile menu both
     offer, and at 390px it pushed the app bar's right cluster 7px past the
     viewport on the record variants. */
  body[data-variant="praxis"] .appswitch{ display:none !important; }

  /* Content, per page family. The two tokens come first so anything else
     derived from them follows. */
  body[data-variant="praxis"]{ --home-gutter:var(--px-gutter); --sp-gutter:var(--px-gutter); }
  body[data-variant="praxis"] .home,
  body[data-variant="praxis"] .view,
  body[data-variant="praxis"] .record,
  body[data-variant="praxis"] .ws-body,
  body[data-variant="praxis"] .admin-body,
  body[data-variant="praxis"] .rm-body,
  body[data-variant="praxis"] .demo{ padding-inline:var(--px-gutter); }
}

/* ===========================================================================
   praxis-appbar.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS APP HEADER — the single, global app-header component.

   One definition of the top app bar, consumed by both the record page and
   the workspace so the header is literally the same component everywhere.
   Faithfully carries the record page's styling (the canonical look).

   Scope note: this file owns the header's VISUAL treatment (bar fill, brand,
   search pill, Mazlan pill, avatar, icon buttons) but deliberately does NOT
   set the bar's positioning — each page's shell keeps that (the record page
   uses a relative bar inside a flex column; the workspace uses a fixed bar
   with a .page top margin). Loaded LAST on each page so it is authoritative.
   ===================================================================== */

/* Bar fill + spacing (Praxis). Position/height stay in each page's shell. */
body[data-variant="praxis"] .appbar{
  background:var(--px-page); color:var(--praxis-color-text-primary);
  gap:var(--praxis-space-16); padding:0 var(--praxis-space-16);
}

/* Brand + logo */
.appbar__brand{display:flex;align-items:center;flex-shrink:0;text-decoration:none;}
.appbar__brand:hover{text-decoration:none;}
.appbar__logo-img{height:26px;width:auto;display:block;}
.appbar__logo-img--onlight{display:none;}
.appbar__logo-img--ondark{display:block;}
body[data-variant="praxis"] .appbar__logo-img--onlight{display:block;}
body[data-variant="praxis"] .appbar__logo-img--ondark{display:none;}
body[data-variant="praxis"][data-theme="dark"] .appbar__logo-img--onlight{display:none;}
body[data-variant="praxis"][data-theme="dark"] .appbar__logo-img--ondark{display:block;}

/* Search — Miramar base: white boxed field with module divider + teal button */
/* min(), not a fixed 840: the pill is absolutely positioned and centred on the
   viewport, so nothing stops it running under the Mazlan button. The 1024
   breakpoint was catching the tablet range but the band just above it was
   unguarded — at 1025 the pill overlapped Mazlan by 73px, clearing only at
   ~1200. The same 420px reserve the narrower rules use now applies from the
   top down. */
.appbar__search{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(840px, calc(100vw - 420px));margin:0;
  display:flex;align-items:center;height:48px;border-radius:var(--praxis-radius-md);overflow:hidden;background:var(--praxis-color-white);}
.appbar__module{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;width:256px;height:100%;padding:0 12px;
  background:var(--praxis-color-neutral-05);color:var(--praxis-color-text-secondary);font-size:.8125rem;font-weight:600;white-space:nowrap;
  border:0;border-right:1px solid var(--praxis-color-border-default);font-family:inherit;cursor:pointer;}
/* text-overflow applies to the placeholder as well as typed text, so the
   prompt truncates instead of being cut mid-glyph once the pill shrinks. */
.appbar__search input{flex:1;min-width:0;text-overflow:ellipsis;border:0;outline:0;height:100%;padding:0 14px;font-family:inherit;font-size:.875rem;font-weight:600;
  background:transparent;color:var(--praxis-color-text-primary);min-width:0;}
.appbar__search-btn{width:40px;height:100%;border:0;background:var(--praxis-color-interactive-default);color:var(--praxis-color-white);
  display:flex;align-items:center;justify-content:center;cursor:pointer;}
.appbar__search-btn:hover{background:var(--praxis-color-interactive-hover);}
/* Praxis: fully-rounded floating pill; module + magnifier are borderless */
body[data-variant="praxis"] .appbar__search{height:44px;border-radius:var(--praxis-radius-full);overflow:visible;
  background:var(--praxis-color-surface-default);padding:0 6px 0 4px;box-shadow:0 0 0 1px rgba(16,36,58,.08),0 1px 2px rgba(16,36,58,.05);}
/* Dark: give the whole search pill the workspace components' elevation +
   border (the card treatment) instead of a flat ring, and lift the
   placeholder (neutral-40) + magnifier (neutral-20) out of the murk. */
body[data-variant="praxis"][data-theme="dark"] .appbar__search{box-shadow:var(--praxis-elevation-card);}
body[data-variant="praxis"][data-theme="dark"] .appbar__search > input::placeholder{color:var(--praxis-color-neutral-40);}
body[data-variant="praxis"][data-theme="dark"] .appbar__search-btn{color:#c8cfd7;}
body[data-variant="praxis"] .appbar__module{justify-content:flex-start;gap:6px;width:auto;height:56%;padding:0 16px 0 14px;
  background:transparent;border-right:1px solid var(--praxis-color-border-default);}
body[data-variant="praxis"] .appbar__search-btn{background:transparent;color:var(--praxis-color-text-secondary);border-radius:var(--praxis-radius-full);}
body[data-variant="praxis"] .appbar__search-btn:hover{background:transparent;color:var(--praxis-color-text-primary);}

/* Right-side cluster */
/* margin-right adds a 16px inset on top of the bar's 16px padding, so the
   Mazlan trigger + avatar sit 32px in from the page edge on every page. */
.appbar__right{display:flex;align-items:center;gap:var(--praxis-space-8);margin-left:auto;margin-right:var(--praxis-space-16);}
.appbar__ai{position:relative;}

/* Mazlan pill (branded four-dot mark + label) */
.appbar__mazlan{display:inline-flex;align-items:center;gap:var(--praxis-space-8);height:36px;padding:0 var(--praxis-space-12);
  background:rgba(255,255,255,.12);color:var(--praxis-color-text-inverse);border:1px solid rgba(255,255,255,.16);
  border-radius:999px;font-family:inherit;font-size:var(--praxis-type-size-sm);font-weight:600;cursor:pointer;flex-shrink:0;
  transition:background .15s,border-color .15s;}
.appbar__mazlan:hover{background:rgba(255,255,255,.2);}
/* Light neutral pill — same lit-edge surface convention as the nav-rail tools
   and the search pill. The four-dot mark keeps its teal→pink signature (its
   dark-slate dot reads on the light surface, so no white-dot override needed). */
body[data-variant="praxis"] .appbar__mazlan{
  background:var(--px-surface); color:var(--praxis-color-text-secondary); border:0;
  box-shadow:0 0 0 .5px rgba(16,36,58,.06),0 1px 2px rgba(16,36,58,.05);
}
body[data-variant="praxis"] .appbar__mazlan:hover{
  filter:none; color:var(--praxis-color-text-primary);
  box-shadow:0 0 0 .5px rgba(16,36,58,.09),0 2px 6px -1px rgba(16,36,58,.14);
}
body[data-variant="praxis"][data-theme="dark"] .appbar__mazlan{
  background:var(--px-surface-2,#232c3b); color:var(--praxis-color-text-secondary); border:0;
  box-shadow:var(--praxis-elevation-card);
}
body[data-variant="praxis"][data-theme="dark"] .appbar__mazlan:hover{
  background:var(--px-surface-2,#232c3b); color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(255,255,255,.12),0 6px 18px -6px rgba(0,0,0,.6),inset 0 .5px 0 rgba(255,255,255,.08);
}

/* Avatar */
.appbar__avatar{width:34px;height:34px;border-radius:50%;background:var(--praxis-color-teal-60) center/cover no-repeat;color:var(--praxis-color-white);
  border:2px solid rgba(255,255,255,.35);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.8125rem;overflow:hidden;}
body[data-variant="praxis"] .appbar__avatar{border-color:rgba(16,36,58,.12);}
body[data-variant="praxis"][data-theme="dark"] .appbar__avatar{border-color:rgba(255,255,255,.22);}

/* Icon buttons */
.appbar__iconbtn{width:40px;height:40px;border:0;background:transparent;color:rgba(255,255,255,.85);border-radius:var(--praxis-radius-md);
  display:flex;align-items:center;justify-content:center;transition:background var(--praxis-motion-fast),color var(--praxis-motion-fast);}
.appbar__iconbtn:hover{background:rgba(255,255,255,.12);color:var(--praxis-color-white);}
body[data-variant="praxis"] .appbar__iconbtn{color:var(--praxis-color-text-secondary);}
body[data-variant="praxis"] .appbar__iconbtn:hover{background:rgba(16,36,58,.06);color:var(--praxis-color-text-primary);}
body[data-variant="praxis"][data-theme="dark"] .appbar__iconbtn:hover{background:rgba(255,255,255,.10);}

/* =====================================================================
   RESPONSIVE — app-wide breakpoints (used consistently across the app):
     lg  ≤1024px  tablet landscape / small laptop
     md  ≤768px   tablet portrait / large phone — the bar compresses and the
                  centered search pill stops floating and flows inline
     sm  ≤480px   phone — tightest spacing
   The 840px absolutely-centered search pill is the main thing that must adapt;
   below md it becomes an in-flow flex child that takes the remaining width.
   ===================================================================== */
/* The centred pill is absolutely positioned, so it can't know what sits beside
   it — the reserve is the only thing keeping it off the Mazlan button and the
   avatar. 340px wasn't enough anywhere in this band: 10px of clearance at 950
   and 4px at 834. 420px holds ~45px clear all the way down to 769, where the
   pill goes in-flow and the reserve stops mattering. */
@media (max-width:1024px){
  .appbar__search{ width:min(600px, calc(100vw - 420px)); }
}
/* ---- The magnifier that replaces the pill below md ------------------------
   Hidden by default and only shown inside the ≤768px block, so a bar that never
   narrows never renders it. praxis-appbar-search.js injects the element; a page
   that wants to place it itself authors [data-appbar-search-toggle] and the
   script adopts that instead.

   Sized and painted as .appbar__iconbtn, but NOT by borrowing that class: the
   toggle is injected, and pointing it at another component's class would make
   this behaviour break the next time that component is retuned. Same values,
   own selector. */
.appbar__searchtoggle{
  display:none; width:40px; height:40px; flex-shrink:0;
  align-items:center; justify-content:center;
  border:0; border-radius:var(--praxis-radius-full);
  background:transparent; color:inherit; cursor:pointer;
  font-family:inherit; padding:0;
}
.appbar__searchtoggle:hover{ background:rgba(255,255,255,.12); }
body[data-variant="praxis"] .appbar__searchtoggle{ color:var(--praxis-color-text-secondary); }
body[data-variant="praxis"] .appbar__searchtoggle:hover{
  background:rgba(16,36,58,.06); color:var(--praxis-color-text-primary);
}
body[data-variant="praxis"][data-theme="dark"] .appbar__searchtoggle:hover{
  background:rgba(255,255,255,.10);
}

@media (max-width:768px){
  /* ---- Pill → magnifier + stacked panel ---------------------------------
     The bar has room for ONE of the two search controls at this width, and the
     previous answer was to keep the input and hide the module selector — which
     left the scope unreachable on a phone while still silently filtering. So
     neither stays in the bar: the whole pill moves into a panel under it,
     behind the magnifier, where both controls get a full row.

     top:100% off the same containing block the centred pill already resolves
     against above this breakpoint. Praxis deliberately does NOT set the bar's
     position (each page's shell owns it — see the header of this file), so this
     inherits exactly the assumption the absolutely-centred pill has always
     made, rather than introducing a new one.

     The panel is display:none rather than a height/opacity transition: it
     contains the module selector, whose menu is position:fixed and positioned
     in JS FROM this element, so a collapsed-but-present panel would anchor that
     menu to a zero-height box. */
  .appbar__searchtoggle{ display:flex; }

  .appbar__search{
    display:none;
    position:absolute; left:0; right:0; top:100%; transform:none;
    width:auto; margin:0; z-index:20;
    height:auto; overflow:visible; border-radius:0;
    padding:var(--praxis-space-12) var(--px-gutter, var(--praxis-space-16));
    background:var(--px-page);
    box-shadow:0 1px 2px rgba(16,36,58,.06), 0 12px 24px -12px rgba(16,36,58,.18);
    /* Two rows out of three children, without a wrapper the markup does not
       have: wrap, and let the module selector claim a full basis. Anything the
       host adds beside the input (an Attachments switch, a scope chip) lands on
       the second row with it instead of being positioned by name.
       row-gap only — the input and its button are one joined field, so the
       column gap has to stay 0. */
    flex-wrap:wrap; align-items:center;
    row-gap:var(--praxis-space-8); column-gap:0;
  }
  body[data-variant="praxis"] .appbar__search{
    width:auto; height:auto; border-radius:0; padding:var(--praxis-space-12) var(--px-gutter, var(--praxis-space-16));
    background:var(--px-page); box-shadow:0 1px 2px rgba(16,36,58,.06), 0 12px 24px -12px rgba(16,36,58,.18);
  }
  body[data-appbar-search="open"] .appbar__search{ display:flex; }

  /* Row 1 — the module selector, whichever of the two forms the host used:
     .msel (the typeable scope field) or .appbar__module (the plain button). */
  .appbar__search > .msel,
  .appbar__search > .appbar__module,
  body[data-variant="praxis"] .appbar__search > .msel,
  body[data-variant="praxis"] .appbar__search > .appbar__module{
    flex:0 0 100%; width:100%; height:44px; box-sizing:border-box;
    /* praxis-module-selector.css clamps .msel to max-width:132px at this
       breakpoint (96px at ≤480px) — the old "compact scope chip", sized to share
       a narrow bar with the term input. It owns a full row here, so the clamp has
       to be released explicitly: this selector outranks it and would otherwise
       set a width the max-width silently overrides. */
    max-width:none; min-width:0;
    padding:0 var(--praxis-space-12); justify-content:space-between;
    background:var(--px-surface); color:var(--praxis-color-text-primary);
    border:0; border-radius:var(--praxis-radius-md);
    box-shadow:0 0 0 1px rgba(16,36,58,.08);
  }
  /* The hairline that divided scope from term inside the pill has nothing left
     to divide once they are on separate rows. */
  .appbar__search > .msel::after{ display:none; }

  /* Row 2 — term input and its button, joined into one field. Radii meet at the
     seam so the pair reads as a single control rather than two adjacent ones.

     The edge is a BORDER here, not the `0 0 0 1px` spread ring row 1 uses, and
     each half drops the side it shares with the other. A ring is drawn around
     every element that carries one, so two adjacent rings painted a hairline
     down the middle of a field that is meant to read as ONE — the seam was
     visible on every phone and tablet. A border can be removed per side, so the
     pair gets a single continuous edge and nothing down the middle. The two
     shared corners are already square, so no radius is lost with them.

     box-sizing is declared rather than assumed: praxis-reset.css is optional for
     consumers, and without border-box a 1px border would make these two 46px
     tall in a 44px row. */
  .appbar__search > input{
    flex:1 1 auto; min-width:0; height:44px; box-sizing:border-box;
    padding:0 var(--praxis-space-12);
    background:var(--px-surface); color:var(--praxis-color-text-primary);
    border-radius:var(--praxis-radius-md) 0 0 var(--praxis-radius-md);
    border:1px solid rgba(16,36,58,.08); border-right:0;
    box-shadow:none;
  }
  .appbar__search .appbar__search-btn{
    flex-shrink:0; width:48px; height:44px; box-sizing:border-box;
    border-radius:0 var(--praxis-radius-md) var(--praxis-radius-md) 0;
  }
  body[data-variant="praxis"] .appbar__search .appbar__search-btn{
    border-radius:0 var(--praxis-radius-md) var(--praxis-radius-md) 0;
    background:var(--px-surface);
    border:1px solid rgba(16,36,58,.08); border-left:0;
    box-shadow:none;
  }
  /* Dark: the 1px light-mode edge is invisible on a dark surface, so the fields
     take the shared edge token instead — as a ring on row 1, which still has
     one, and as a border colour on the joined pair. */
  body[data-theme="dark"] .appbar__search > .msel,
  body[data-theme="dark"] .appbar__search > .appbar__module{
    box-shadow:0 0 0 1px var(--px-edge);
  }
  body[data-theme="dark"] .appbar__search > input,
  body[data-theme="dark"] .appbar__search .appbar__search-btn{
    border-color:var(--px-edge);
  }

  /* ---- No suggestion popup inside the panel -----------------------------
     .ss-pop opens on the term input's focus — and opening the panel focuses
     that input, so the single gesture that reveals the search field also buried
     the page under a "Recent searches" list nobody asked for. Above this
     breakpoint the pill is always in the bar and focusing it is a separate,
     deliberate act; here it is not, so the surface is suppressed rather than
     re-timed. The drivers only toggle [hidden], never an inline display, so
     this rule is the whole fix and needs no !important.

     Scoped to the bar's own popup: a page may use .ss-pop elsewhere, and that
     is not this component's business. */
  .appbar__search .ss-pop{ display:none; }

  /* Mazlan pill is too wide for a phone bar — drop it (still reachable via
     the nav rail / profile menu) so the bar keeps room. */
  .appbar__mazlan{ display:none; }
  /* NOTE: the two rules that used to live here — hiding .msel off the Search
     page, and hiding .appbar__right .iconbtn-ghost on it — were both there to
     buy horizontal room for a pill that no longer sits in the bar. Removed
     rather than kept: with the pill behind the magnifier the bar has room for
     the secondary icon buttons again, and hiding the module selector is the
     exact problem this panel exists to fix. */
}
@media (max-width:480px){
  /* Extra squeeze: shorten the page switcher so brand + switcher don't crowd
     out the search field on the narrowest phones. */
  .appswitch__trigger{ max-width:104px; padding:0 4px 0 6px; }
  .appswitch__trigger span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
}
@media (max-width:480px){
  /* Keep the gap squeeze, but NOT the padding: the horizontal inset is the
     shared --px-gutter now, so the bar lines up with the page header and the
     content. (This file loads after praxis-core.css, so an 8px padding here
     would win and reintroduce the mismatch.) */
  body[data-variant="praxis"] .appbar{ gap:8px; }
  .appbar__logo-img{ height:22px; }
  .appbar__avatar{ width:30px; height:30px; }
}

/* ===========================================================================
   praxis-navrail.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS NAV RAIL — the primary navigation rail, shared by every page so
   it is identical everywhere (workspace, record, …). Canonical version is
   the record-page rail: an icon-only column led by the product hub logo.

   This file styles the rail's CONTENTS (core, items, hub link, buttons).
   The .praxis-navrail CONTAINER positioning stays per-page (the record page
   is an in-flow flex child of .shell; the workspace is a fixed sidebar),
   since that is dictated by each page's overall layout — only the visual
   treatment (transparent fill, hairline border) is unified here.
   ===================================================================== */

.praxis-navrail__core{ display:flex; flex-direction:column; align-items:center; }
.praxis-navrail__item{
  display:flex; align-items:center; justify-content:center;
  width:var(--navrail-w, var(--praxis-navrail-width, 56px)); height:56px; flex-shrink:0;
}

.praxis-navrail__link{
  display:flex; align-items:center; justify-content:center; width:40px; height:40px;
  padding:0; border-radius:var(--praxis-radius-md);
  transition:background var(--praxis-motion-fast) var(--praxis-ease-default);
}
.praxis-navrail__link:hover{ background:var(--praxis-color-neutral-20); text-decoration:none; }
.praxis-navrail__link:focus-visible{ outline:2px solid var(--praxis-color-teal-50); outline-offset:2px; }
.praxis-navrail__link img{ display:block; width:35px; height:40px; }

.praxis-navrail__btn{
  display:flex; align-items:center; justify-content:center; width:40px; height:40px;
  padding:0; gap:0; border:0; background:transparent; border-radius:var(--praxis-radius-md);
  color:var(--praxis-color-neutral-70); cursor:pointer;
  transition:background-color var(--praxis-motion-fast) var(--praxis-ease-default), transform 150ms var(--praxis-ease-default);
}
.praxis-navrail__btn:hover{ background-color:var(--praxis-color-neutral-20); }
.praxis-navrail__btn:focus-visible{ outline:2px solid var(--praxis-color-teal-50); outline-offset:2px; }
.praxis-navrail__btn svg{ display:block; }
/* The selected item wears the same gradient + elevation as every unselected one
   beside it (see the praxis-variant rules below), in pink rather than surface.
   It was a flat pink-50 fill until 0.1.12 — the one button in the rail with
   neither, which read as unfinished next to its neighbours.

   A 180deg stop either side of pink-50, so the perceived colour is unchanged.
   The :hover rule restates background-image deliberately: background-color alone
   would leave the base gradient in place. pink-50 is a brand token and is not
   remapped for dark, and --px-tool-shadow is theme-aware, so one pair of rules
   covers both themes. */
.praxis-navrail__btn--active{
  background-color:var(--praxis-color-pink-50);
  background-image:linear-gradient(180deg,#ee4198,#e32680);
  box-shadow:var(--px-tool-shadow);
  color:var(--praxis-color-white);
}
.praxis-navrail__btn--active:hover{
  background-color:var(--praxis-color-pink-50);
  background-image:linear-gradient(180deg,#f1559f,#e82e8b);
  box-shadow:var(--px-tool-shadow-hover);
}
.praxis-navrail__btn--create{ background-color:var(--praxis-color-teal-60); color:var(--praxis-color-white); }
.praxis-navrail__btn--create:hover{ background-color:var(--praxis-color-teal-70); }

body[data-theme="dark"] .praxis-navrail__btn{ color:var(--praxis-color-text-secondary); }
/* The selected item keeps its pink rect + a WHITE glyph in dark — the base
   .btn dark rule above otherwise has enough specificity to grey it out. */
body[data-theme="dark"] .praxis-navrail__btn--active{ color:var(--praxis-color-white); }

/* ---- Praxis: transparent/borderless rail; buttons become neutral filled
        containers with a lit edge (matches the toolbar tools). ---- */
body[data-variant="praxis"] .praxis-navrail{ background:transparent; border-right:1px solid var(--praxis-color-border-default); }
body[data-variant="praxis"] .praxis-navrail__btn:not(.praxis-navrail__btn--active):not(.praxis-navrail__btn--create){
  color:var(--praxis-color-text-secondary);
  background:var(--px-surface); box-shadow:0 0 0 .5px rgba(16,36,58,.06),0 1px 2px rgba(16,36,58,.05);
}
/* Hover shifts the fill, not just the glyph — the shadow-only version read as
   a rendering artefact. The wash rides over the surface so the button keeps
   reading as raised. (This rule outranks the shared pass in praxis-core.css,
   and its `background` shorthand would reset the image anyway.) */
body[data-variant="praxis"] .praxis-navrail__btn:not(.praxis-navrail__btn--active):not(.praxis-navrail__btn--create):hover{
  background-color:var(--px-surface);
  background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  box-shadow:0 0 0 .5px rgba(16,36,58,.09),0 2px 6px -1px rgba(16,36,58,.14);
}
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__btn:not(.praxis-navrail__btn--active):not(.praxis-navrail__btn--create){
  color:#c8cfd7; /* neutral-20 (light value) — a lighter glyph on the slate chip */
  background:var(--px-surface-2,#232c3b);
  box-shadow:0 0 0 .5px rgba(255,255,255,.08),inset 0 .5px 0 rgba(255,255,255,.06);
}
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__btn:not(.praxis-navrail__btn--active):not(.praxis-navrail__btn--create):hover{
  color:var(--praxis-color-white);
  background-color:var(--px-surface-2,#232c3b);
  background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  box-shadow:0 0 0 .5px rgba(255,255,255,.14),inset 0 .5px 0 rgba(255,255,255,.08);
}
body[data-variant="praxis"] .praxis-navrail__link{ background:var(--px-surface); box-shadow:0 0 0 .5px rgba(16,36,58,.06),0 1px 2px rgba(16,36,58,.05); }
body[data-variant="praxis"] .praxis-navrail__link:hover{ background-color:var(--px-surface); background-image:linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn)); box-shadow:0 0 0 .5px rgba(16,36,58,.09),0 2px 6px -1px rgba(16,36,58,.14); }
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__link{ background:var(--px-surface-2,#232c3b); box-shadow:0 0 0 .5px rgba(255,255,255,.08),inset 0 .5px 0 rgba(255,255,255,.06); }
body[data-variant="praxis"] .praxis-navrail__link img{ width:auto; height:28px; }
body[data-variant="praxis"] .praxis-navrail__btn--create{
  background:var(--px-primary-grad); color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(16,36,58,.18),0 2px 6px -1px rgba(27,131,139,.45),inset 0 .5px 0 rgba(255,255,255,.25);
}
/* Dark: Create matches the quick-action buttons — a soft translucent-teal chip
   with a bright-teal icon (not the bold gradient CTA). */
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__btn--create{
  background:var(--px-primary-soft); color:var(--px-primary-soft-fg);
  /* -sm: the full soft shadow's ambient glow was too heavy for a 40px chip.
     Same level the quick actions use, so all three primaries match. */
  box-shadow:var(--px-primary-soft-shadow-sm);
  transition:background var(--praxis-motion-fast) var(--praxis-ease-default),
             box-shadow var(--praxis-motion-fast) var(--praxis-ease-default),
             transform 150ms var(--praxis-ease-default);
}
body[data-variant="praxis"][data-theme="dark"] .praxis-navrail__btn--create:hover{
  background:var(--px-primary-soft-hover); box-shadow:var(--px-primary-soft-shadow-sm-hover);
}
body[data-variant="praxis"] .praxis-navrail__btn:focus-visible,
body[data-variant="praxis"] .praxis-navrail__btn--create:focus-visible{
  outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent);
}

/* ============================================================================
   PHONE: the rail becomes a drawer  (see praxis-navdrawer.js)
   ============================================================================
   At 56px the icon rail costs a sixth of a 360px viewport and its items carry
   no labels. Below 640px it's hidden and the same destinations appear in a
   drawer opened from a hamburger in the app bar's left corner — with labels.
   ========================================================================= */
.px-navtoggle{ display:none; }

@media (max-width:640px){
  .praxis-navrail{ display:none !important; }
  /* Pages offset their content by the rail width in several different ways
     (margin-left, padding-left, a grid column). Zeroing the token covers all
     of them at once. */
  body{ --navrail-w:0px; }

  .px-navtoggle{
    display:inline-flex; align-items:center; justify-content:center;
    width:40px; height:40px; flex-shrink:0; margin-right:2px;
    border:0; border-radius:var(--praxis-radius-md); background:transparent;
    color:var(--praxis-color-text-secondary); cursor:pointer;
    transition:background-color var(--praxis-motion-fast) var(--praxis-ease-default),
               transform 150ms var(--praxis-ease-default);
  }
  .px-navtoggle:hover{ background-color:var(--px-hover-btn); color:var(--praxis-color-text-primary); }
  .px-navtoggle:active{ transform:scale(.95); }
  .px-navtoggle:focus-visible{ outline:2px solid var(--praxis-color-teal-50); outline-offset:2px; }
  .px-navtoggle .material-symbols-rounded{ font-size:24px; }
  .px-navtoggle svg{ width:24px; height:24px; }
}

/* ---- Drawer -------------------------------------------------------------
   Styled after the Mazlan prototype's nav-menu-drawer, with one structural
   difference: the highlight lives on the ICON WELL, not the row. The rail
   marks state on a 40px button, so carrying that into the drawer keeps the
   two forms recognisably the same control — and it stops a long label from
   dragging a block of colour across the panel.

   The surface is a fixed slate (rgb(36,48,60)) in both themes rather than
   --px-surface: the drawer is chrome over the page, not a page surface, and a
   constant panel colour reads the same wherever it's opened from.
   ------------------------------------------------------------------------ */
.px-navdrawer__scrim{
  position:fixed; inset:0; z-index:140;
  background:rgba(16,22,30,.55); backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px);
  opacity:0; transition:opacity 200ms var(--praxis-ease-default);
}
.px-navdrawer__scrim.is-open{ opacity:1; }
.px-navdrawer__scrim[hidden]{ display:none; }

.px-navdrawer{
  position:fixed; top:0; left:0; bottom:0; z-index:141;
  width:280px; max-width:85vw; display:flex; flex-direction:column;
  background:rgb(36,48,60); color:#e7ebf1;
  box-shadow:4px 0 20px rgba(0,0,0,.32);
  transform:translateX(-100%); transition:transform 220ms var(--praxis-ease-default);
}
.px-navdrawer.is-open{ transform:none; }
.px-navdrawer[hidden]{ display:none; }
@media (prefers-reduced-motion: reduce){
  .px-navdrawer, .px-navdrawer__scrim{ transition:none; }
}

.px-navdrawer__head{
  display:flex; align-items:center; justify-content:space-between;
  height:var(--appbar-h, 64px); padding:0 12px 0 16px; flex-shrink:0;
  border-bottom:1px solid rgba(255,255,255,.10);
}
.px-navdrawer__brand{
  display:flex; align-items:center; gap:12px;
  font-size:.9375rem; font-weight:700; color:#fff;
}
.px-navdrawer__brand svg{ width:28px; height:32px; display:block; flex-shrink:0; }
.px-navdrawer__close{
  width:40px; height:40px; display:grid; place-items:center;
  background:transparent; border:0; border-radius:var(--praxis-radius-md);
  color:rgba(255,255,255,.72); cursor:pointer;
  transition:background-color var(--praxis-motion-fast), transform 150ms var(--praxis-ease-default);
}
.px-navdrawer__close:hover{ background:rgba(255,255,255,.10); color:#fff; }
.px-navdrawer__close:active{ transform:scale(.95); }
.px-navdrawer__close .material-symbols-rounded{ font-size:22px; }

.px-navdrawer__list{
  flex:1; overflow-y:auto; padding:10px 8px 20px; margin:0; list-style:none;
  display:flex; flex-direction:column; gap:4px;
}
/* The row is a hit target and a label holder; it carries no fill of its own. */
.px-navdrawer__item{
  display:flex; align-items:center; gap:12px; width:100%;
  min-height:48px; padding:4px 8px;
  border:0; background:transparent; border-radius:10px;
  font:inherit; font-size:.875rem; font-weight:600; text-align:left;
  color:rgba(255,255,255,.86); text-decoration:none; cursor:pointer;
  transition:background-color var(--praxis-motion-fast), color var(--praxis-motion-fast),
             transform 150ms var(--praxis-ease-default);
}
.px-navdrawer__item:hover{ background:rgba(255,255,255,.06); color:#fff; text-decoration:none; }
.px-navdrawer__item:active{ transform:scale(.98); }
.px-navdrawer__item:focus-visible{ outline:2px solid var(--praxis-color-teal-50); outline-offset:-2px; }

/* The icon well is the state carrier — same 40px square the rail uses. */
.px-navdrawer__icon{
  width:40px; height:40px; flex-shrink:0; border-radius:var(--praxis-radius-md);
  display:grid; place-items:center;
  background:rgba(255,255,255,.07); color:rgba(255,255,255,.78);
  transition:background-color var(--praxis-motion-fast), color var(--praxis-motion-fast);
}
.px-navdrawer__item:hover .px-navdrawer__icon{ background:rgba(255,255,255,.12); color:#fff; }
.px-navdrawer__icon svg{ width:20px; height:20px; }
.px-navdrawer__icon img{ width:20px; height:auto; }
.px-navdrawer__icon .material-symbols-rounded{ font-size:20px; }

/* Active page: pink on the icon only, matching the rail's active button. */
.px-navdrawer__item--active{ color:#fff; }
.px-navdrawer__item--active .px-navdrawer__icon,
.px-navdrawer__item--active:hover .px-navdrawer__icon{
  background:var(--praxis-color-pink-50); color:var(--praxis-color-white);
}
/* Create: the rail's own treatment, on the icon well. */
.px-navdrawer__item--create .px-navdrawer__icon{
  background:var(--px-primary-grad); color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(16,36,58,.18),0 2px 6px -1px rgba(27,131,139,.45),
             inset 0 .5px 0 rgba(255,255,255,.25);
}
.px-navdrawer__item--create:hover .px-navdrawer__icon{ filter:brightness(1.06); }

.px-navdrawer__label{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.px-navdrawer__group{
  padding:16px 12px 4px; font-size:11px; font-weight:800; letter-spacing:normal; color:var(--praxis-color-text-tertiary);
}

/* =====================================================================
   WORKSPACE / DASHBOARD FLYOUT — item states
   The flyout is a nav-rail surface, so its states are defined here next to
   the rail's own, and it borrows the same two markers the rail already uses:
   pink fill on the icon well for "this is what you're looking at", and the
   primary-action gradient on the icon well for the create affordance. Both
   land on the icon, never the label.

   Base layout/colour for .ws-item lives in praxis-admin.css and (duplicated)
   in the record/search page <style> blocks; these rules are deliberately
   scoped one level deeper (.ws-pop) so they win over both without either
   copy needing to change. Pink tokens carry literal fallbacks because the
   record and search pages don't load a stylesheet that defines them.
   ===================================================================== */

/* ---- Panel ------------------------------------------------------------
   .ws-pop and not .px-pop: the other popovers sharing .px-pop (search
   suggestions, module selector) keep their own treatment. Kept at single-class
   specificity deliberately — the record and search pages carry their own
   .ws-pop copy including a phone-width override that switches it to a fixed
   bottom sheet, and those declarations come later, so they must still win. */
.ws-pop{position:absolute;z-index:200;top:0;left:calc(100% + 8px);width:300px;
  max-height:min(70vh,560px);overflow-y:auto;padding:6px;border-radius:12px;
  background:var(--praxis-color-surface-default);border:1px solid var(--praxis-color-border-default);
  box-shadow:var(--praxis-elevation-4);}
.ws-pop[hidden]{display:none;}
body[data-variant="praxis"] .ws-pop{
  background:color-mix(in srgb, var(--px-surface) 60%, transparent);
  -webkit-backdrop-filter:saturate(1.35) blur(16px);backdrop-filter:saturate(1.35) blur(16px);
  border:0;box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
  border-radius:14px;padding:10px;transform-origin:top left;}
body[data-variant="praxis"] .ws-pop .px-menu__head{
  font-size:16px;font-weight:700;letter-spacing:0;text-transform:none;
  color:var(--praxis-color-text-primary);padding:6px 8px 12px;}

/* ---- Base component ---------------------------------------------------
   index.html loads neither praxis-admin.css nor an inline copy, so its
   dashboard flyout rendered as bare links. These are the same values the
   other two copies carry, at plain specificity, so pages that do define
   their own still win and nothing there changes. */
.ws-pop .ws-item{display:flex;align-items:center;gap:12px;width:100%;padding:8px 10px;border:0;
  background:transparent;border-radius:10px;cursor:pointer;text-align:left;font-family:inherit;
  color:var(--praxis-color-text-primary);text-decoration:none;}
.ws-pop .ws-item:hover{background:var(--px-hover);text-decoration:none;}
.ws-pop .ws-item:focus-visible{outline:2px solid var(--praxis-color-teal-50);outline-offset:-2px;}
.ws-pop .ws-item__icon{width:34px;height:34px;flex-shrink:0;border-radius:9px;display:flex;
  align-items:center;justify-content:center;
  background:var(--praxis-color-neutral-10);color:var(--praxis-color-text-secondary);}
.ws-pop .ws-item__icon .icon{width:19px;height:19px;}
.ws-pop .ws-item__text{display:flex;flex-direction:column;min-width:0;flex:1;}
.ws-pop .ws-item__name{font-size:.875rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ws-pop .ws-item__sub{font-size:.75rem;color:var(--praxis-color-text-secondary);}
.ws-pop .ws-item__badge{margin-left:auto;flex-shrink:0;padding:3px 9px;border-radius:999px;
  font-size:.6875rem;font-weight:700;}

/* ---- New dashboard = the flyout's primary action ----------------------
   The CTA treatment goes on the ICON WELL, not the row: the teal gradient in
   light and the soft translucent-teal chip in dark, matching
   .praxis-navrail__btn--create directly above it. That button is itself an icon
   button, so the well is the true like-for-like — and the label and its
   sub-line stay ordinary text on an ordinary row, reading the same as every
   other entry in the list.

   Previously the well was transparent with only the plus glyph tinted, so the
   one action in the panel that creates something looked like another list item.

   Scoped through .ws-pop and body[data-variant] so it clears the per-page
   .ws-item / .ws-item--new copies (.ws-item--new is declared in nine files),
   whichever order they load in. */
body[data-variant="praxis"] .ws-pop .ws-item--new .ws-item__icon,
body[data-variant="praxis"] .ws-pop .ws-item--new:hover .ws-item__icon{
  background:var(--px-primary-grad); color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(16,36,58,.18),0 2px 6px -1px rgba(27,131,139,.45),inset 0 .5px 0 rgba(255,255,255,.25);
}
/* The row itself stays a plain list row — same hover as its neighbours. */
body[data-variant="praxis"] .ws-pop .ws-item--new{ background:transparent; box-shadow:none; }
body[data-variant="praxis"] .ws-pop .ws-item--new:hover{ background:var(--px-hover); }

body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item--new .ws-item__icon,
body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item--new:hover .ws-item__icon{
  background:var(--px-primary-soft); color:var(--px-primary-soft-fg);
  box-shadow:var(--px-primary-soft-shadow-sm);
}

/* ---- Current workspace ------------------------------------------------
   Only the dashboard actually in view is marked, via .ws-item--current.
   This used to be conflated with .ws-item--home in dark mode, which lit the
   home entry pink on pages where no dashboard was on screen at all. Home is
   a property of the workspace; current is a property of the view. */
body[data-variant="praxis"] .ws-pop .ws-item--current .ws-item__icon,
body[data-variant="praxis"] .ws-pop .ws-item--current:hover .ws-item__icon{
  background:var(--praxis-color-pink-50,#e82e8b);
  color:var(--praxis-color-white,#fff);
  box-shadow:none;
}
/* Undo the dark-mode pink that .ws-item--home used to claim (praxis-admin.css) —
   the fill now tracks .ws-item--current in both themes. */
body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item--home:not(.ws-item--current) .ws-item__icon{
  background:var(--px-surface); color:var(--praxis-color-text-secondary);
}
body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item--home:not(.ws-item--current){
  background:transparent;
}
body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item--home:not(.ws-item--current):hover{
  background:var(--px-hover);
}

/* ---- Home badge -------------------------------------------------------
   Light pink in both themes: it labels the home workspace, it isn't a
   selected state, so it stays a tint rather than the solid pink above. */
body[data-variant="praxis"] .ws-pop .ws-item__badge{
  background:var(--praxis-color-pink-10,#fce6f1);
  color:var(--praxis-color-pink-60,#e30072);
}
body[data-variant="praxis"][data-theme="dark"] .ws-pop .ws-item__badge{
  background:rgba(227,0,114,.18); color:#ff8ac2;
}

/* ---- New dashboard ----------------------------------------------------
   The rail's create treatment, on the icon well — identical to
   .px-navdrawer__item--create above so the two read as the same action. */
body[data-variant="praxis"] .ws-pop .ws-item--new .ws-item__icon{
  background:var(--px-primary-grad); color:var(--praxis-color-white,#fff);
  box-shadow:0 0 0 .5px rgba(16,36,58,.18),0 2px 6px -1px rgba(27,131,139,.45),
             inset 0 .5px 0 rgba(255,255,255,.25);
}
body[data-variant="praxis"] .ws-pop .ws-item--new:hover .ws-item__icon{ filter:brightness(1.06); }

/* ===========================================================================
   praxis-pageheader.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS MASTHEAD — the shared two-band block every page carries under the
   app bar: the page header (breadcrumb + icon + title) and the toolbar band
   beneath it. Both sit on a --px-page fill that covers the fixed body
   dot-grid, and each closes with an inset hairline. Content differs per page;
   the underpinnings — and, critically, the combined height — are shared, so
   page content begins at the same y on every page. Each page sets --ph-pad-x
   to its own horizontal content rhythm (workspace 32px, record 24px, search
   20px). Header icons use the Material Symbols font (loaded on every page).

   The masthead is 128px tall: 68px header + 60px toolbar band. With the 64px
   app bar above it, page content starts at 192px. Anything that changes a
   band's height breaks that promise on one page only — which is exactly the
   drift this file exists to prevent, so change the tokens, not a page.
   ===================================================================== */
:root{
  /* The two bands of the masthead. Held here rather than per page so the
     content-start y is one number in one place. */
  --ph-h:68px;
  --px-toolbar-h:60px;
  --px-toolbar-pad-y:10px;
}

/* No top padding: the breadcrumb sits directly under the app bar, which already
   carries its own vertical space. --ph-pad-top is here for any page that needs
   it back. Height is a floor, not a fix: the breadcrumb is allowed to wrap at
   narrow widths and take the header with it. */
.pageheader{position:relative;box-sizing:border-box;min-height:var(--ph-h,68px);
  padding:var(--ph-pad-top,0) var(--ph-pad-x,24px) 10px;background:var(--px-page);}
.pageheader::after{content:"";position:absolute;left:var(--ph-pad-x,24px);right:var(--ph-pad-x,24px);
  bottom:0;height:1px;background:var(--praxis-color-border-default);}

/* =====================================================================
   TOOLBAR BAND — the row of page-level actions directly under the header.
   Every page has one, whatever it holds: record tools, search controls, the
   admin actions, the history tabs, the workspace quick actions. The band owns
   the height and the closing hairline; the page owns the contents.

   The band is always full-bleed so its hairline lines up with the header's
   directly above it. Pages whose content is a centred max-width column (the
   record family) put their controls in .toolbar__inner and centre that
   instead, so the controls align with the cards below while the two rules
   still run edge to edge.
   ===================================================================== */
.toolbar{position:relative;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;
  gap:var(--praxis-space-8);min-height:var(--px-toolbar-h,60px);
  padding:var(--px-toolbar-pad-y,10px) var(--ph-pad-x,24px);
  background:linear-gradient(to bottom, var(--px-page) 0%, transparent 100%);}
.toolbar::after{content:"";position:absolute;left:var(--ph-pad-x,24px);right:var(--ph-pad-x,24px);
  bottom:0;height:1px;background:var(--praxis-color-border-default);}
/* The centred column. Pages set its max-width alongside their content column
   so the two share one number. */
.toolbar__inner{display:flex;align-items:center;gap:var(--praxis-space-8);
  width:100%;min-width:0;margin-inline:auto;}
.toolbar__spacer{flex:1;}

.breadcrumb{display:flex;align-items:center;gap:6px;margin-bottom:6px;flex-wrap:wrap;
  font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-secondary);}
.breadcrumb a{color:var(--praxis-color-text-secondary);text-decoration:none;}
.breadcrumb a:hover{color:var(--praxis-color-text-primary);}
.breadcrumb__home{display:inline-flex;align-items:center;color:var(--praxis-color-text-disabled);}
.breadcrumb__sep{display:inline-flex;align-items:center;color:var(--praxis-color-text-disabled);}
.breadcrumb__current{display:inline-flex;align-items:center;gap:6px;color:var(--praxis-color-text-primary);}
.breadcrumb .material-symbols-rounded{font-size:18px;line-height:1;}

.pageheader__titlerow{display:flex;align-items:center;gap:12px;}
/* Neutral, not pink: the pink read as a call to action rather than a type
   marker. A light chip one step off the page with a slate glyph — enough to
   separate from the background without competing with the title. */
.pageheader__icon{width:32px;height:32px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;
  justify-content:center;background:var(--px-surface-2,#f4f6f8);color:var(--praxis-color-neutral-90,#2f4051);
  box-shadow:0 0 0 .5px rgba(16,36,58,.10),0 1px 2px rgba(16,36,58,.06),inset 0 1px 0 rgba(255,255,255,.9);}
body[data-theme="dark"] .pageheader__icon{
  background:var(--px-surface-2);color:#e7ebf1;
  box-shadow:0 0 0 .5px rgba(255,255,255,.08),0 1px 2px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.07);}
.pageheader__icon .material-symbols-rounded{font-size:20px;}
.pageheader__icon .icon{width:20px;height:20px;}
.pageheader__title{margin:0;font-size:var(--praxis-type-size-xl);font-weight:700;line-height:1.25;
  color:var(--praxis-color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pageheader__status{margin-left:auto;display:flex;align-items:center;gap:8px;flex-shrink:0;}

/* ===========================================================================
   praxis-workspace.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   Praxis skin for the Workspace (Concierge home) — brings the home into
   the Mazlan / Groom Lake language and adds a Mazlan-style dark mode, so
   the Workspace and the record page read as one system. Layered on top
   of praxis-chrome-legacy.css (which already shares the --praxis-* tokens and a
   dot-grid page background). Toggled via body[data-theme].
   ===================================================================== */

/* =====================================================================
   App header + nav rail: "floating" light treatment, mirroring the
   record page (record-page.html .appbar / .praxis-navrail). The header
   blends into the page background (no dark bar, no border, no shadow);
   the search becomes a fully-rounded Mazlan-style floating pill; the
   right-side controls read as dark ink on the light header; the nav
   rail loses its background + border and blends into the page. Works
   in both light and dark (the dark-mode token block below recolors it).
   ===================================================================== */

/* ---- App header: blend into the page (solid page-color bg — it's fixed) ---- */
.appbar{
  background:var(--praxis-color-surface-subtle);
  color:var(--praxis-color-text-primary);
  border:0; box-shadow:none;
  /* Span the full viewport width (incl. the scrollbar gutter) so the fixed
     header geometry matches the record page's naturally full-width bar — the
     content scrollbar then sits under the header, and the avatar + centred
     search no longer shift when switching between the two pages. */
  width:100vw; right:auto;
}
/* Brand wordmark — theme-aware (dark wordmark on light header, white in dark) */
/* Match the record page's brand cluster geometry so the wordmark + page
   switcher land in the exact same spot across pages */
.appbar__logo-area{ padding-left:var(--praxis-space-16); gap:var(--praxis-space-16); }
.appbar__brand{ display:inline-flex; align-items:center; text-decoration:none; }
.appbar__logo-img{ display:block; height:26px; width:auto; }
.appbar__logo-img--ondark{ display:none; }
body[data-theme="dark"] .appbar__logo-img--onlight{ display:none; }
body[data-theme="dark"] .appbar__logo-img--ondark{ display:block; }

/* ---- Page switcher (next to the wordmark), mirrors the record page ---- */
.appswitch{ position:relative; }
.appswitch__trigger{
  display:inline-flex; align-items:center; gap:4px; height:32px; padding:0 8px 0 10px;
  border:0; background:transparent; color:inherit; font-family:inherit; font-size:.875rem;
  font-weight:600; border-radius:8px; cursor:pointer;
}
.appswitch__trigger:hover{ background:rgba(16,36,58,.06); }
.appswitch__pop{
  position:absolute; top:calc(100% + 6px); left:0; min-width:200px; z-index:200;
  background:var(--praxis-color-surface-default); border:1px solid var(--praxis-color-border-default);
  border-radius:12px; padding:6px;
  box-shadow:0 0 0 .5px rgba(16,36,58,.06), 0 18px 38px -16px rgba(16,36,58,.22);
}
.appswitch__pop[hidden]{ display:none; }
.appswitch__item{
  display:block; padding:8px 10px; border-radius:8px; font-size:.875rem; font-weight:500;
  color:var(--praxis-color-text-primary); text-decoration:none;
}
.appswitch__item:hover{ background:var(--praxis-color-neutral-05); text-decoration:none; }
.appswitch__item--active{ color:var(--praxis-color-text-link,var(--praxis-color-teal-60)); font-weight:600; }
body[data-theme="dark"] .appswitch__trigger:hover{ background:rgba(255,255,255,.08); }
body[data-theme="dark"] .appswitch__item:hover{ background:rgba(255,255,255,.06); }

/* ---- Search: fully-rounded floating field (Mazlan style) ---- */
.appbar__search{
  border-radius:9999px;
  background:var(--praxis-color-surface-default);
  overflow:visible;
  padding:0 6px 0 4px;
  box-shadow:0 0 0 1px rgba(16,36,58,.08), 0 1px 2px rgba(16,36,58,.05);
}
body[data-theme="dark"] .appbar__search{ box-shadow:0 0 0 1px rgba(255,255,255,.10); }
/* Module selector — transparent floating text with a short centered divider */
.appbar__module-selector{
  background:transparent;
  color:var(--praxis-color-text-secondary);
  border-right:1px solid var(--praxis-color-border-default);
  align-self:center; height:56%;
  min-width:0; padding:0 14px;
}
.appbar__module-selector .material-symbols-rounded{ color:var(--praxis-color-text-secondary); }
/* Input area + input — transparent, primary ink */
.appbar__input-area{ background:transparent; }
.appbar__search-input{ background:transparent; color:var(--praxis-color-text-primary); }
/* Run-search button — transparent floating magnifier */
.appbar__search-go{
  background:transparent;
  color:var(--praxis-color-text-secondary);
  border-radius:9999px;
}
.appbar__search-go:hover{ background:transparent; color:var(--praxis-color-text-primary); }

/* ---- Right-side controls: read as dark ink on the light header ---- */
.appbar__action-btn{
  background:rgba(16,36,58,.06);
  color:var(--praxis-color-text-primary);
  border-color:rgba(16,36,58,.10);
}
.appbar__action-btn:hover{ background:rgba(16,36,58,.10); }
.appbar__icon-btn{ color:var(--praxis-color-text-secondary); }
.appbar__icon-btn:hover{ background:rgba(16,36,58,.06); color:var(--praxis-color-text-primary); }
.appbar__avatar{ border-color:rgba(16,36,58,.12); }
.appbar__avatar--photo{ background-size:cover; background-position:center; background-repeat:no-repeat; color:transparent; overflow:hidden; }
/* Persona picker (styled inline in index.html — needs body-prefix to win) */
body .persona-trigger{
  background:rgba(16,36,58,.06);
  color:var(--praxis-color-text-primary);
}
body .persona-trigger:hover{ background:rgba(16,36,58,.10); }
body .persona-trigger[aria-expanded="true"]{ background:rgba(16,36,58,.14); }
body .persona-trigger__sub{ color:var(--praxis-color-text-secondary); }

/* dark: swap dark-tints for light-tints */
body[data-theme="dark"] .appbar__action-btn{ background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.12); }
/* Mazlan trigger: light neutral pill matching the shared lit-edge button
   convention (nav-rail tools / search pill). The four-dot mark keeps its
   teal→pink signature — its dark-slate dot reads on the light surface. */
#mazlan-trigger{ background:var(--px-surface); color:var(--praxis-color-text-secondary); border:0;
  box-shadow:0 0 0 .5px rgba(16,36,58,.06),0 1px 2px rgba(16,36,58,.05); }
#mazlan-trigger:hover{ filter:none; color:var(--praxis-color-text-primary);
  box-shadow:0 0 0 .5px rgba(16,36,58,.09),0 2px 6px -1px rgba(16,36,58,.14); }
body[data-theme="dark"] #mazlan-trigger{ background:var(--px-surface-2,#232c3b); color:var(--praxis-color-text-secondary); border:0;
  box-shadow:var(--praxis-elevation-card); }
body[data-theme="dark"] #mazlan-trigger:hover{ background:var(--px-surface-2,#232c3b); color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(255,255,255,.12),0 6px 18px -6px rgba(0,0,0,.6),inset 0 .5px 0 rgba(255,255,255,.08); }
body[data-theme="dark"] .appbar__action-btn:hover{ background:rgba(255,255,255,.12); }
body[data-theme="dark"] .appbar__icon-btn:hover{ background:rgba(255,255,255,.10); }
body[data-theme="dark"] .appbar__avatar{ border-color:rgba(255,255,255,.22); }
body[data-theme="dark"] .persona-trigger{ background:rgba(255,255,255,.08); }
body[data-theme="dark"] .persona-trigger:hover{ background:rgba(255,255,255,.12); }
body[data-theme="dark"] .persona-trigger[aria-expanded="true"]{ background:rgba(255,255,255,.16); }

/* Mazlan trigger reads as a neutral control (base .appbar__action-btn) —
   the four-dot Mazlan mark carries the identity */

/* ---- Nav rail: blend into the page — no background, no border ---- */
.praxis-navrail{ background:transparent; border-right:1px solid var(--praxis-color-border-default); }
/* Hide the expand/collapse control for now — the record page's rail has none,
   so this keeps the two consistent. Kept in the DOM (button + JS intact) so it
   can be re-enabled later by removing this rule. The whole cell is hidden to
   avoid leaving its 56px slot empty at the top of the rail. */
.praxis-navrail__cell:has(> .praxis-navrail__toggle){ display:none; }

/* ---- Nav rail buttons: neutral filled containers (a set with Create) ----
   The Create button colors its 40×40 .praxis-navrail__icon box (radius-md); give the
   plain nav buttons' icon boxes the SAME container silhouette but neutral, so
   they read as a matched set of pickable chips rather than floating icons.
   Create keeps its teal; the active item keeps its pink. */
.praxis-navrail__btn:not(.praxis-navrail__btn--create):not(.praxis-navrail__btn--active) .praxis-navrail__icon{
  background:var(--praxis-color-surface-default);
  box-shadow:0 0 0 .5px rgba(16,36,58,.06), 0 1px 2px rgba(16,36,58,.05);
}
.praxis-navrail__btn:not(.praxis-navrail__btn--create):not(.praxis-navrail__btn--active):hover .praxis-navrail__icon{
  background:var(--praxis-color-surface-default);
  box-shadow:0 0 0 .5px rgba(16,36,58,.09), 0 2px 6px -1px rgba(16,36,58,.14);
}
body[data-theme="dark"] .praxis-navrail__btn:not(.praxis-navrail__btn--create):not(.praxis-navrail__btn--active) .praxis-navrail__icon{
  background:rgba(255,255,255,.06);
  box-shadow:0 0 0 .5px rgba(255,255,255,.10);
}
body[data-theme="dark"] .praxis-navrail__btn:not(.praxis-navrail__btn--create):not(.praxis-navrail__btn--active):hover .praxis-navrail__icon{
  background:rgba(255,255,255,.10);
  box-shadow:0 0 0 .5px rgba(255,255,255,.14);
}

/* ---- Cards: Mazlan slate elevation with a lit top edge, 12px geometry ---- */
.card{
  border:0; border-radius:12px;
  box-shadow:0 0 0 .5px rgba(16,36,58,.06), 0 1px 1.5px rgba(16,36,58,.045),
             0 10px 28px -14px rgba(16,36,58,.10), inset 0 .5px 0 rgba(255,255,255,.7);
}

/* ---- Dot grid: align pitch to the record page (18px), and fade the dots to
   the page background at the left + right edges and top by compositing a
   page-color gradient OVER the dots (not masking — masking would hide content). */
.page{
  background-color:var(--praxis-color-surface-subtle);
  background-image:
    linear-gradient(to right, var(--praxis-color-surface-subtle), transparent 7%, transparent 93%, var(--praxis-color-surface-subtle)),
    linear-gradient(to bottom, var(--praxis-color-surface-subtle), transparent 10%),
    radial-gradient(circle, rgba(16,36,58,.11) 1px, transparent 1px);
  background-size:100% 100%, 100% 100%, 18px 18px;
  background-repeat:no-repeat, no-repeat, repeat;
}

/* Theme toggle — inline moon/sun SVGs that swap by [data-theme] (same as the record page) */
.gl-theme-btn{ color:rgba(255,255,255,.85); }
.gl-theme-btn:hover{ color:var(--praxis-color-white); }
.gl-theme-btn .gl-sun{ display:none; }
body[data-theme="dark"] .gl-theme-btn .gl-moon{ display:none; }
body[data-theme="dark"] .gl-theme-btn .gl-sun{ display:block; }
/* Profile menu — shared component, see praxis-profile-menu.css */

/* =====================================================================
   Persona switcher relocated INTO the profile menu ("Viewing as").
   The existing .persona-trigger + .persona-popover flyout keep working;
   here we re-lay it out to sit as a full-width control inside the menu,
   and open its proto-list flyout to the LEFT so it never clips.
   ===================================================================== */
.profile-menu__row--persona{ padding-bottom:2px; }
.persona-picker--in-menu{ position:relative; padding:0 6px 2px; }
/* Trigger becomes a full-width menu control (override the header pill sizing) */
body .persona-picker--in-menu .persona-trigger{
  display:flex; width:100%; height:auto; margin:0; box-sizing:border-box;
  padding:8px 10px; gap:10px; border-radius:8px;
  background:var(--praxis-color-neutral-05); color:var(--praxis-color-text-primary);
}
body .persona-picker--in-menu .persona-trigger:hover{ background:var(--praxis-color-neutral-10); }
body[data-theme="dark"] .persona-picker--in-menu .persona-trigger{ background:rgba(255,255,255,.06); }
body[data-theme="dark"] .persona-picker--in-menu .persona-trigger:hover{ background:rgba(255,255,255,.10); }
.persona-picker--in-menu .persona-trigger__icon{ font-size:20px; color:var(--praxis-color-text-secondary); }
.persona-picker--in-menu .persona-trigger__label{ font-size:var(--praxis-type-size-sm); font-weight:600; color:var(--praxis-color-text-primary); }
.persona-picker--in-menu .persona-trigger__sub{ font-size:12px; font-weight:500; color:var(--praxis-color-text-secondary); }
.persona-picker--in-menu .persona-trigger__text{ flex:1; }
.persona-picker--in-menu .persona-trigger__chev{ margin-left:auto; color:var(--praxis-color-text-secondary); }
/* Flyout opens to the LEFT of the profile popover so it doesn't clip */
.persona-picker--in-menu .persona-popover{
  position:absolute; top:0; right:calc(100% + 12px); left:auto;
  min-width:320px; max-width:340px;
  background:var(--praxis-color-surface-default);
  border:1px solid var(--praxis-color-border-default); border-radius:12px; padding:6px;
  box-shadow:0 0 0 .5px rgba(16,36,58,.06), 0 24px 50px -18px rgba(16,36,58,.30);
}
body[data-theme="dark"] .persona-picker--in-menu .persona-popover{
  background:rgba(26,32,45,.96);
  -webkit-backdrop-filter:saturate(1.3) blur(16px); backdrop-filter:saturate(1.3) blur(16px);
  box-shadow:0 0 0 .5px rgba(255,255,255,.08), 0 24px 50px -20px rgba(0,0,0,.7);
}
@media (max-width: 639px){
  /* Phone: keep the label visible in-menu, and drop the flyout below the trigger */
  .persona-picker--in-menu .persona-trigger__text{ display:flex; }
  .persona-picker--in-menu .persona-popover{
    position:fixed; top:auto; right:var(--praxis-space-12); left:var(--praxis-space-12); min-width:0; max-width:none;
  }
}

/* =====================================================================
   DARK MODE — Mazlan deep-slate palette (token overrides cascade to the
   token-driven Concierge components; plus targeted surface fixes).
   ===================================================================== */
/* Token declarations go on :root, never on body — see praxis-core.css.
   NOTE: this block is a drifted duplicate of the base dark remap in
   praxis-core.css and should not exist. It is preserved verbatim here,
   including the two values that disagree with core (surface-subtle
   #12161f vs #161c27, neutral-05 #12161f vs #10151e), because deleting it
   is a rendering change and this commit is a pure move. Tracked separately. */
:root:has(body[data-theme="dark"]){
  color-scheme:dark;
  --praxis-color-surface-default:#192336;
  --praxis-color-surface-subtle:#12161f;
  --praxis-color-neutral-05:#12161f;
  --praxis-color-neutral-10:#222b39;
  --praxis-color-neutral-20:#3a4556;
  --praxis-color-neutral-30:#3a4556;
  --praxis-color-border-default:rgba(255,255,255,.10);
  --praxis-color-text-primary:#e7ebf1;
  --praxis-color-text-secondary:#9aa7b4;
}
body[data-theme="dark"]{ color:var(--praxis-color-text-primary); }
body[data-theme="dark"] .page{
  background-color:var(--px-page);
  background-image:
    linear-gradient(to right, var(--px-page), transparent 7%, transparent 93%, var(--px-page)),
    linear-gradient(to bottom, var(--px-page), transparent 10%),
    radial-gradient(circle, rgba(255,255,255,.06) 1px, transparent 1px);
  background-size:100% 100%, 100% 100%, 18px 18px;
  background-repeat:no-repeat, no-repeat, repeat;
}
body[data-theme="dark"] .card{
  background:#192336;
  box-shadow:0 0 0 .5px rgba(255,255,255,.08), 0 1px 2px rgba(0,0,0,.4),
             0 16px 40px -18px rgba(0,0,0,.6), inset 0 .5px 0 rgba(255,255,255,.07);
}
body[data-theme="dark"] .praxis-navrail{ background:transparent; border-right:1px solid var(--praxis-color-border-default); }
/* soft cyan accent lift for the primary action in dark, matching Praxis */
body[data-theme="dark"] .btn--primary{ filter:saturate(1.05) brightness(1.05); }

/* ===========================================================================
   praxis-create-new.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS CREATE NEW — the shared "create a new record" component.

   A nav-anchored frosted flyout (opens right of the nav-rail Create button)
   with three views: Shortcuts, All, Templates. Praxis material throughout:
   frosted glass + lit-edge elevation, light header, rounded Find pill, a
   top segmented view-switch, and lit-edge tiles whose icons are tinted by
   the record type's solution.

   Consumed by both the workspace and the record page. Also lightly Praxis-
   ifies the shared shell classes (.cn-overlay / .cn-modal / .cn-head …) that
   the Save-workspace dialog reuses.
   ===================================================================== */

/* Material Symbols base — so the component's icons render wherever it's used
   (the record page doesn't otherwise define this class). Matches praxis-chrome-legacy. */
.material-symbols-rounded{
  font-family:"Material Symbols Rounded"; font-weight:normal; font-style:normal;
  font-size:24px; line-height:1; letter-spacing:normal; text-transform:none;
  display:inline-block; white-space:nowrap; word-wrap:normal; direction:ltr;
  -webkit-font-feature-settings:"liga"; -webkit-font-smoothing:antialiased;
  user-select:none; vertical-align:middle;
}

/* ---- Scrim (also used by the Save-workspace dialog) ---- */
.cn-overlay{
  position:fixed; inset:0; z-index:120;
  display:grid; place-items:center;
  background:rgba(16,36,58,.18);
  padding:var(--praxis-space-24);
  opacity:1;
  transition:opacity .18s ease, display .18s allow-discrete;
}
.cn-overlay[hidden]{ display:none; opacity:0; }
@starting-style{ .cn-overlay:not([hidden]){ opacity:0; } }

/* ---- Standalone scrim — for pages that anchor the flyout directly (e.g. the
       record page uses its own [hidden]-toggled .cn-flyout, not .cn-overlay).
       Sits just below the flyout (z120) to dim the page like the workspace. ---- */
.cn-scrim{
  position:fixed; inset:0; z-index:119;
  background:rgba(16,36,58,.18);
  opacity:1; transition:opacity .18s ease, display .18s allow-discrete;
}
.cn-scrim[hidden]{ display:none; opacity:0; }
@starting-style{ .cn-scrim:not([hidden]){ opacity:0; } }

/* ---- Create New flyout panel — anchored to the nav rail (position:fixed
       ignores the scrim's centering) ---- */
.cn-flyout{
  position:fixed; z-index:120;
  top:calc(var(--praxis-appbar-h) + 8px);
  left:calc(var(--praxis-navrail-width, 56px) + 8px);
  width:468px;
  max-width:calc(100vw - var(--praxis-navrail-width, 56px) - 24px);
  max-height:calc(100vh - var(--praxis-appbar-h) - 24px);
  display:flex; flex-direction:column; overflow:hidden;
  /* Frosted glass so the solid tiles read against it (a solid panel made the
     white/dark tiles blend in). Translucent fill (theme-aware via color-mix)
     so the blur actually shows through; heavy blur for a clean frost. */
  background:color-mix(in srgb, var(--px-surface) 60%, transparent);
  -webkit-backdrop-filter:saturate(1.35) blur(16px); backdrop-filter:saturate(1.35) blur(16px);
  border:0; border-radius:16px;
  box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
  color:var(--praxis-color-text-primary);
  /* Open/close animation + smooth widen when switching to the All view. */
  opacity:1; transform:none; transform-origin:top left;
  transition:opacity .18s ease,
             transform .24s var(--praxis-ease-default, cubic-bezier(.32,.72,0,1)),
             width .26s var(--praxis-ease-default, cubic-bezier(.32,.72,0,1)),
             display .24s allow-discrete;
}
/* Standalone use (record page toggles the flyout's own [hidden], no scrim) */
.cn-flyout[hidden]{ display:none; opacity:0; transform:translateY(-8px) scale(.985); }
@starting-style{
  .cn-flyout:not([hidden]){ opacity:0; transform:translateY(-8px) scale(.985); }
}

/* ---- Wide multi-column view (All tab) — less scrolling ---- */
.cn-flyout--wide{ width:1080px; }
.cn-flyout--wide .cn-content{ column-count:3; column-gap:20px; }
.cn-flyout--wide .cn-group{ break-inside:avoid; margin-top:0; margin-bottom:16px; }
.cn-flyout--wide .cn-grid{ grid-template-columns:1fr; }  /* items stack within each narrower group column */

/* ---- Save-workspace dialog panel — centered Praxis card ---- */
.cn-modal{
  width:768px; max-width:100%; max-height:min(86vh, 820px);
  display:flex; flex-direction:column; overflow:hidden;
  background:var(--px-surface); border:0; border-radius:16px;
  box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
  color:var(--praxis-color-text-primary);
}

/* ---- Light header (shared) ---- */
.cn-head{
  display:flex; align-items:center; justify-content:space-between;
  padding:14px 16px 8px; flex-shrink:0;
}
.cn-head__title{ display:flex; align-items:center; gap:8px; font-size:16px; font-weight:700; color:var(--praxis-color-text-primary); }
.cn-head__title .material-symbols-rounded{ font-size:20px; color:var(--praxis-color-interactive-default); }
.cn-close{
  width:30px; height:30px; display:grid; place-items:center;
  border:0; background:transparent; color:var(--praxis-color-text-secondary);
  border-radius:8px; cursor:pointer;
}
.cn-close .material-symbols-rounded{ font-size:20px; }
.cn-close:hover{ background:var(--px-hover); color:var(--praxis-color-text-primary); }

/* ---- Controls: Find pill + segmented view switch (top) ---- */
.cn-controls{ padding:0 16px 8px; display:flex; flex-direction:column; gap:10px; flex-shrink:0; }
.cn-find{
  display:flex; align-items:center; gap:8px; height:38px; padding:0 12px;
  border-radius:999px; background:var(--px-surface); box-shadow:var(--px-tool-shadow);
}
.cn-find .material-symbols-rounded{ font-size:20px; color:var(--praxis-color-text-secondary); flex-shrink:0; }
.cn-find input{
  flex:1; min-width:0; border:0; outline:0; background:transparent; padding:0;
  font:inherit; font-size:14px; font-weight:600; color:var(--praxis-color-text-primary);
}
.cn-find input::placeholder{ color:var(--praxis-color-text-secondary); font-weight:500; }

.cn-seg{
  display:flex; gap:2px; padding:3px;
  background:var(--px-chip); border-radius:12px; flex-shrink:0;
}
.cn-seg__btn{
  flex:1; height:30px; border:0; background:transparent; border-radius:9px;
  font:inherit; font-size:13px; font-weight:600; color:var(--praxis-color-text-secondary);
  cursor:pointer; transition:background var(--praxis-motion-normal) ease, color var(--praxis-motion-normal) ease;
}
.cn-seg__btn:hover{ color:var(--praxis-color-text-primary); }
/* Selected: the lifted pill and its shadow already carry the state, so the
   label stays plain ink rather than repeating it in the interactive colour —
   which also stopped it reading as a link. This is the reference segmented
   control; .verswitch (praxis-profile-menu.css), .viewswitch (search page) and
   .filter-view-switch (praxis-filters.css) all match it. */
.cn-seg__btn--active{ background:var(--px-surface); color:var(--praxis-color-text-primary); box-shadow:var(--px-tool-shadow); }
.cn-seg[hidden]{ display:none; }

/* ---- Body (scrolls) ---- */
.cn-body{ padding:4px 16px 16px; overflow-y:auto; flex:1; }

/* ---- Solution groups ---- */
.cn-group + .cn-group{ margin-top:16px; }
.cn-group__head{ display:flex; align-items:center; gap:8px; height:32px; }
.cn-group__icon{
  width:24px; height:24px; border-radius:8px; display:grid; place-items:center; flex-shrink:0;
  background:color-mix(in srgb, var(--cn-tone, var(--praxis-color-teal-70)) 14%, transparent);
  color:var(--cn-tone, var(--praxis-color-teal-70));
}
.cn-group__icon .material-symbols-rounded{ font-size:16px; }
.cn-group__title{ font-size:13px; font-weight:700; letter-spacing:.01em; color:var(--praxis-color-text-primary); }

/* Module Selector's section-label group headers applied to Create New
   (solid tone marker + uppercase tracked title in the solution colour).
   Sizes are kept in step with .msel__menu .cn-group__* in
   praxis-module-selector.css — 32px marker, 20px glyph, 32px head — so the two
   menus read as the same component. */
.cn-flyout .cn-group__head{ gap:8px; height:32px; }
.cn-flyout .cn-group__icon{ width:32px; height:32px; border-radius:8px; background:var(--cn-tone); color:var(--praxis-color-white); }
.cn-flyout .cn-group__icon .material-symbols-rounded{ font-size:20px; }
.cn-flyout .cn-group__title{ font-size:11px; font-weight:800; letter-spacing:normal; color:var(--cn-tone); }

/* Solution tones (icons take a brand color per solution) */
.cn-group--pink{   --cn-tone:var(--praxis-color-pink-60); }
.cn-group--teal{   --cn-tone:var(--praxis-color-teal-70); }
.cn-group--blue{   --cn-tone:var(--praxis-color-blue-60); }
.cn-group--orange{ --cn-tone:var(--praxis-color-orange-60); }
.cn-group--purple{ --cn-tone:var(--praxis-color-purple-60); }
.cn-group--green{  --cn-tone:var(--praxis-color-green-60); }

/* ---- Record-type tiles ---- */
.cn-grid{ display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:8px; padding:8px 0 2px; }
.cn-item{
  display:flex; align-items:center; gap:10px; height:46px; padding:0 10px;
  background:var(--px-surface); border:0; border-radius:10px; box-shadow:var(--px-tool-shadow);
  font:inherit; font-size:14px; font-weight:600; color:var(--praxis-color-text-primary);
  text-align:left; cursor:pointer; min-width:0;
  transition:box-shadow var(--praxis-motion-normal) ease;
}
.cn-item:hover{ box-shadow:var(--px-tool-shadow-hover); }
/* Same gap as .tbtn (praxis-admin.css): only ever rendered as <button> on
   Praxis's own pages, so the hover underline every anchor-capable component
   suppresses (.ws-item:hover, .praxis-navrail__link:hover) was never closed
   here either. */
.cn-item:hover,.cn-item:focus-visible{ text-decoration:none; }
.cn-item__icon{
  width:28px; height:28px; border-radius:8px; display:grid; place-items:center; flex-shrink:0;
  background:color-mix(in srgb, var(--cn-tone, var(--praxis-color-teal-70)) 14%, transparent);
  color:var(--cn-tone, var(--praxis-color-teal-70));
}
.cn-item__icon .material-symbols-rounded{ font-size:18px; }
.cn-item__label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ---- Template cards (a record type, partially filled — richer than a tile) ---- */
.cn-tpl-list{ display:flex; flex-direction:column; gap:8px; padding:8px 0 2px; }
.cn-tpl{
  display:flex; align-items:flex-start; gap:10px; width:100%; padding:10px;
  background:var(--px-surface); border:0; border-radius:12px; box-shadow:var(--px-tool-shadow);
  font:inherit; text-align:left; cursor:pointer;
  transition:box-shadow var(--praxis-motion-normal) ease;
}
.cn-tpl:hover{ box-shadow:var(--px-tool-shadow-hover); }
.cn-tpl__icon{
  width:32px; height:32px; border-radius:9px; display:grid; place-items:center; flex-shrink:0;
  background:color-mix(in srgb, var(--cn-tone, var(--praxis-color-teal-70)) 14%, transparent);
  color:var(--cn-tone, var(--praxis-color-teal-70));
}
.cn-tpl__icon .material-symbols-rounded{ font-size:20px; }
.cn-tpl__body{ min-width:0; flex:1; }
.cn-tpl__name{ font-size:14px; font-weight:600; color:var(--praxis-color-text-primary); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cn-tpl__meta{ font-size:12px; color:var(--praxis-color-text-secondary); margin-top:1px; }

.cn-manage-note{ font-size:var(--praxis-type-size-sm); color:var(--praxis-color-text-secondary); margin:8px 0 0; }

/* ---- Footer (Save dialog + manage mode) ---- */
.cn-footer{
  flex-shrink:0; min-height:56px;
  display:flex; align-items:center; justify-content:space-between; gap:var(--praxis-space-16);
  padding:10px 16px; border-top:1px solid var(--praxis-color-border-default);
  font-size:var(--praxis-type-size-sm); color:var(--praxis-color-text-secondary);
}
.cn-footer[hidden]{ display:none; }

/* ---- Manage mode (quick-action picker): searchable checklist ---- */
.cn-flyout--manage .cn-find{ width:100%; }
.cn-list{ display:flex; flex-direction:column; }
.cn-section-label{
  font-size:11px; font-weight:800; letter-spacing:normal;
  color:var(--praxis-color-text-tertiary); padding:14px 0 4px;
}
.cn-row{
  display:flex; align-items:center; gap:var(--praxis-space-12);
  min-height:46px; padding:5px 8px; width:100%;
  border:0; border-radius:10px; background:transparent; cursor:pointer; font:inherit; text-align:left;
  transition:background var(--praxis-motion-normal) ease;
}
.cn-row:hover{ background:var(--px-hover); }
.cn-row__icon{
  width:30px; height:30px; border-radius:8px; display:grid; place-items:center; flex-shrink:0;
  background:var(--px-chip); color:var(--praxis-color-text-secondary);
}
/* Glyph nested in its own span so the container keeps its grid centering
   (a merged .cn-row__icon.material-symbols-rounded lets the font's display win). */
.cn-row__icon .material-symbols-rounded{ font-size:18px; }
.cn-row--selected .cn-row__icon{ background:var(--praxis-color-teal-10); color:var(--praxis-color-teal-80); }
.cn-row__label{ font-size:14px; font-weight:600; color:var(--praxis-color-text-primary); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cn-row__check{ margin-left:auto; font-size: 24px!important; color:var(--praxis-color-border-strong, #c8cfd7); flex-shrink:0; }
.cn-row--selected .cn-row__check{ color:var(--praxis-color-teal-70); }

/* ---- Narrow screens (≤768px): dock the flyout to a full-width bottom sheet;
       the "All" wide view collapses from 3 columns to 1, and the Save dialog
       modal fills the width. ---- */
@media (max-width: 768px){
  .cn-flyout,
  .cn-flyout--wide{
    top:auto; left:0; right:0; bottom:0;
    width:100%; max-width:100%; max-height:88vh;
    border-radius:16px 16px 0 0;
  }
  .cn-flyout--wide .cn-content{ column-count:1; }
  .cn-grid{ grid-template-columns:1fr; }
  .cn-modal{ width:100%; max-width:100%; }
}

/* ===========================================================================
   praxis-module-selector.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS SEARCH MODULE SELECTOR — the search-scope picker in the app header.

   Same catalog as Create New's "All" tab (every module the user can access,
   grouped by solution), presented as a typeable, multi-select filter. Default
   scope is "All". Selecting one or more modules restricts search to those
   record types. Reuses the Create New tile / group / row styling and the
   shared CREATE_CATALOG data.
   ===================================================================== */

/* ---- Trigger — a 240px typeable field on the left of the search pill ---- */
.msel{
  /* relative so the inset divider (::after) anchors here; the body-level menu
     is still positioned from the search pill (.appbar__search) in JS, so this
     does not affect the menu's placement or width */
  position:relative; display:flex; align-items:center; gap:2px;
  width:240px; flex-shrink:0; height:100%; box-sizing:border-box;
  padding:0 6px 0 14px; cursor:pointer;
}
/* Divider between the module selector and the search input — a hairline that
   is inset from the top/bottom edges rather than spanning the full height. */
.msel::after{
  content:""; position:absolute; right:0; top:50%; transform:translateY(-50%);
  width:1px; height:56%; background:var(--praxis-color-border-default);
}
/* The trigger field only displays the current scope; it opens the menu on click
   and is not typeable (filtering lives inside the menu). */
.msel__input{
  flex:1; min-width:0; border:0; outline:0; background:transparent; padding:0;
  font:inherit; font-size:.875rem; font-weight:600; color:var(--praxis-color-text-primary);
  text-overflow:ellipsis; cursor:pointer;
}
.msel__input::placeholder{ color:var(--praxis-color-text-secondary); font-weight:600; }
.msel__caret{ font-size:20px !important; color:var(--praxis-color-text-secondary); flex-shrink:0; pointer-events:none;
  transition:transform var(--praxis-motion-normal) ease; }
.msel.is-open .msel__caret{ transform:rotate(180deg); }
/* Dark: the module label + dropdown arrow read too dark on the header pill;
   lighten to neutral-20 (light palette value — the token remaps dark). */
body[data-theme="dark"] .msel__input{ color:#c8cfd7; }
body[data-theme="dark"] .msel__input::placeholder{ color:#c8cfd7; }
body[data-theme="dark"] .msel__caret{ color:#c8cfd7; }
/* "All modules" is selected by default; its teal-10 chip glares in dark — use a
   translucent-teal chip with bright-teal glyph (the dark teal-chip convention). */

/* ---- Scrim behind the menu — dims the WHOLE page including the app header
       (the menu is rendered at body level, above the scrim, so the header can
       dim under it). Sits above the app bar (z100) and below the menu. ---- */
.msel__scrim{
  position:fixed; inset:0; z-index:125;
  background:rgba(16,36,58,.18);
  opacity:1; transition:opacity .16s ease, display .16s allow-discrete;
}
.msel__scrim[hidden]{ display:none; opacity:0; }
@starting-style{ .msel__scrim:not([hidden]){ opacity:0; } }

/* ---- Menu — frosted panel (Create New flyout material). Lives at body level
       and is positioned from the search pill in JS (left/top/width), so it
       escapes the app bar's stacking context and paints above the scrim while
       the header dims beneath it. ---- */
.msel__menu{
  position:fixed; z-index:130;
  max-height:min(96vh, 1040px); overflow-y:auto;
  padding:24px; display:flex; flex-direction:column;
  /* Match the Create New flyout: more translucent (a hair darker over the page)
     so the solid white tiles read against the surface. */
  background:color-mix(in srgb, var(--px-surface) 60%, transparent);
  -webkit-backdrop-filter:saturate(1.35) blur(16px); backdrop-filter:saturate(1.35) blur(16px);
  border:0; border-radius:14px;
  box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
  color:var(--praxis-color-text-primary);
  /* open/close animation */
  transform-origin:top left;
  transition:opacity .16s ease, transform .2s var(--praxis-ease-default, cubic-bezier(.32,.72,0,1)), display .2s allow-discrete;
}
.msel__menu[hidden]{ display:none; opacity:0; transform:translateY(-6px) scale(.99); }
@starting-style{ .msel__menu:not([hidden]){ opacity:0; transform:translateY(-6px) scale(.99); } }

/* ---- Menu title ---- */
.msel__titlebar{ display:flex; align-items:center; margin:0 0 10px; }
.msel__title{ font-size:16px; font-weight:700; color:var(--praxis-color-text-primary); margin:0; }

/* ---- Filter field — narrows the module list; lives inside the menu, below
       the title (the trigger field is display-only). Reuses the Create New
       search pill (.cn-find); only the spacing below the title is bespoke. ---- */
/* The menu is a flex column with max-height:min(96vh,1040px). A flex item's
   default flex-shrink:1 means the filter gets compressed below its 38px height
   whenever the module list overflows — so on a short viewport the input looked
   squashed, and the amount varied with the window. Pinning the fixed-height
   chrome (title row + filter) means the list absorbs all the overflow and the
   input is always 38px, at any viewport height. */
.msel__filter{ margin:0 0 16px; flex:0 0 auto; }
.msel__titlebar{ flex:0 0 auto; }

/* ---- "All modules" row at the top ---- */

/* ---- Groups flow into three columns (like the Create New All view); the
       "All modules" row spans the full width above them ---- */
.msel__groups{ column-count:3; column-gap:20px; }
.msel__groups .cn-group{ break-inside:avoid; margin:0 0 16px; }

/* Group headers read as section labels, NOT tiles — a solid tone marker + an
   uppercase, tracked title in the solution colour, clearly distinct from the
   pale-tinted, sentence-case tiles below. Left edge still aligns with tiles. */
.msel__menu .cn-group__head{ padding:0 10px; gap:8px; height:32px; margin-bottom:6px; }
.msel__menu .cn-group__icon{ width:32px; height:32px; border-radius:8px; background:var(--cn-tone); color:var(--praxis-color-white); }
.msel__menu .cn-group__icon .material-symbols-rounded{ font-size:20px; }
.msel__menu .cn-group__title{ font-size:11px; font-weight:800; letter-spacing:normal; color:var(--cn-tone); }

/* "All modules" checkbox sits next to the label, not pushed to the far right. */

/* ---- Module tiles: single column within each group (full labels), tinted,
       multi-selectable ---- */
.msel__menu .cn-grid{ grid-template-columns:1fr; }
.msel__menu .cn-item{ position:relative; }
.msel__menu .cn-item.is-sel{ box-shadow:0 0 0 1.5px var(--praxis-color-interactive-default), var(--px-tool-shadow); }
.msel__menu .cn-item__check{ margin-left:auto; color:var(--praxis-color-interactive-default); font-size:18px !important; display:none; }
.msel__menu .cn-item.is-sel .cn-item__check{ display:inline-flex; }

.msel__empty{ font-size:var(--praxis-type-size-sm); color:var(--praxis-color-text-secondary); padding:10px 8px; }

/* =====================================================================
   RESPONSIVE (≤768px md / ≤480px sm) — the trigger shrinks to a compact
   scope chip and the body-level menu becomes a near-full-width sheet with a
   single column of groups. The menu's left/top/width are set inline by JS
   (positionMenu); these rules override with !important to reclaim the width.
   ===================================================================== */
@media (max-width:768px){
  .msel{ width:auto; max-width:132px; padding:0 4px 0 10px; }
  .msel__input{ font-size:.8125rem; }
  .msel__menu{ left:8px !important; right:8px !important; width:auto !important;
    max-width:none; max-height:74vh; padding:16px; }
  .msel__groups{ column-count:1 !important; }
}
@media (max-width:480px){
  .msel{ max-width:96px; }
}

/* Clear — the route back to "All" once modules are picked. Hidden while the
   selection is already empty, since "All" is what an empty selection means. */
.msel__clear{
  margin-left:auto; font:inherit; font-size:13px; font-weight:600;
  color:var(--praxis-color-text-link); background:transparent; border:0;
  padding:4px 8px; border-radius:var(--praxis-radius-sm); cursor:pointer;
}
.msel__clear:hover{ background:var(--px-hover); }
.msel__clear[hidden]{ display:none; }

/* ============================================================================
   PHONE — the module menu becomes a full-width sheet with collapsible groups
   ============================================================================
   At 390px the desktop grid squeezed each option to a couple of characters, so
   the module names — the only information in the menu — were unreadable. The
   options stack one per row at full width, and each group collapses so a long
   catalogue stays navigable.
   ========================================================================= */
@media (max-width:640px){
  .msel__menu{
    left:0 !important; right:0 !important; top:auto !important; bottom:0 !important;
    width:100% !important; max-width:100% !important; max-height:86vh;
    border-radius:16px 16px 0 0; padding:16px;
  }
  /* One option per row, full width, so the whole name is readable. */
  .msel__menu .cn-grid{ grid-template-columns:1fr !important; gap:2px; }
  .msel__menu .cn-item{ width:100%; min-height:44px; }
  .msel__menu .cn-item__label{ white-space:normal; }

  /* Group heads become the collapse control. */
  .msel__menu .cn-group{ margin-bottom:6px; }
  .msel__menu .cn-group__head{
    display:flex; align-items:center; width:100%; height:44px; padding:0 8px;
    border:0; background:transparent; border-radius:10px; cursor:pointer;
    font:inherit; text-align:left; color:var(--praxis-color-text-primary);
  }
  .msel__menu .cn-group__head:hover{ background:var(--px-hover-btn); }
  .msel__menu .cn-group__head::after{
    content:""; margin-left:auto; width:9px; height:9px; flex-shrink:0;
    border-right:2px solid currentColor; border-bottom:2px solid currentColor;
    transform:rotate(45deg); transition:transform 160ms var(--praxis-ease-default);
    color:var(--praxis-color-text-secondary);
  }
  .msel__menu .cn-group.is-collapsed .cn-group__head::after{ transform:rotate(-45deg); }
  .msel__menu .cn-group.is-collapsed .cn-grid{ display:none; }
  .msel__menu .cn-group__title{ font-weight:700; }
}

/* ===========================================================================
   praxis-profile-menu.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS PROFILE MENU — the avatar-triggered menu in the app bar.

   One component for every page. It used to be redeclared per page: ten
   admin pages via praxis-admin.css, the workspace via
   praxis-workspace.css, and index, search and the six record pages each
   with their own inline copy — 24 rule blocks across 10 files, drifting
   in min-width (230 / 236 / 250), radius (12px vs the token) and which
   parts existed at all. The record pages had the Appearance row and the
   version switcher; admin had the nav labels and aria-current state;
   the workspace had the persona switcher. This file is the union, so a
   change lands once.

   Contents differ per page — admin adds nav links, the record pages add
   Appearance, the workspace adds "Viewing as" — but every part is
   described here and a page simply uses the ones it needs.

   SURFACE: the frosted panel shared with the Create New flyout
   (praxis-create-new.css) and the module selector
   (praxis-module-selector.css) — a translucent theme-aware fill over a
   heavy blur, so the solid rows read against it. Previously the menu was
   frosted in dark mode only and solid in light; it is now the same
   material in both, which is what makes it read as the same component as
   those two.

   The base (non-praxis / miramar) rules stay solid, since that variant
   has no frosted-surface language.
   ===================================================================== */

.profile-menu{ position:relative; }

/* ---- Panel ---------------------------------------------------------- */
.profile-menu__pop{
  position:absolute; top:calc(100% + 8px); right:0; z-index:200;
  /* 250px was the widest of the three values in use; the narrower ones
     cramped the Appearance row's segmented control. */
  min-width:250px;
  padding:6px;
  /* The menu's height is whatever its page gives it — admin adds six "Switch
     to" links, the record pages add Appearance, the workspace adds "Viewing
     as" — and the tallest of those runs off the bottom of a laptop viewport,
     putting Sign out somewhere unreachable. Cap it at the room below the app
     bar and scroll the rest: the panel's top edge is the app bar plus 8px, so
     24px leaves a matching 16px gutter underneath. Same cap and same idiom as
     .cn-flyout and the module selector, which scroll the panel itself rather
     than an inner body — there is no sticky header here to keep in place.
     overscroll-behavior stops a flick past the last row from scrolling the
     page behind the menu. */
  max-height:calc(100vh - var(--praxis-appbar-h) - 24px);
  overflow-y:auto; overscroll-behavior:contain;
  background:var(--praxis-color-surface-default);
  border:1px solid var(--praxis-color-border-default);
  border-radius:var(--praxis-radius-md);
  box-shadow:var(--praxis-elevation-4);
}
.profile-menu__pop[hidden]{ display:none; }

/* Praxis: the Create New / module-selector frosted surface, both themes.
   --px-surface is theme-aware, so one color-mix covers light and dark. */
body[data-variant="praxis"] .profile-menu__pop{
  background:color-mix(in srgb, var(--px-surface) 60%, transparent);
  -webkit-backdrop-filter:saturate(1.35) blur(16px); backdrop-filter:saturate(1.35) blur(16px);
  border:0; border-radius:16px;
  box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
}
/* Fallback for engines without backdrop-filter: take the opaque glass
   token instead, so the menu never renders as unreadable transparency. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  body[data-variant="praxis"] .profile-menu__pop{ background:var(--px-glass); }
}

/* ---- Header (name + role) ------------------------------------------- */
.profile-menu__head{
  padding:10px 12px 8px; display:flex; flex-direction:column; gap:2px;
  border-bottom:1px solid var(--praxis-color-border-default); margin-bottom:6px;
}
.profile-menu__name{ font-weight:600; font-size:.875rem; color:var(--praxis-color-text-primary); }
.profile-menu__role{ font-size:.75rem; color:var(--praxis-color-text-secondary); }

/* ---- Rows ----------------------------------------------------------- */
.profile-menu__item{
  display:flex; align-items:center; gap:10px; width:100%; height:38px; padding:0 12px;
  border:0; background:transparent; border-radius:var(--praxis-radius-sm);
  font:inherit; font-size:.875rem; font-weight:500; color:var(--praxis-color-text-primary);
  text-align:left; text-decoration:none; cursor:pointer;
}
a.profile-menu__item{ text-decoration:none; color:inherit; }
.profile-menu__item:hover{ background:var(--px-hover,var(--praxis-color-neutral-05)); text-decoration:none; }
body[data-theme="dark"] .profile-menu__item:hover{ background:rgba(255,255,255,.06); }
/* Both icon systems appear in this menu: Lucide SVGs on the record and
   search pages, Material Symbols on admin and the workspace. */
.profile-menu__item .icon{ width:20px; height:20px; color:var(--praxis-color-text-secondary); fill:none; flex-shrink:0; }
.profile-menu__item .material-symbols-rounded{ font-size:20px; color:var(--praxis-color-text-secondary); }
/* No visual highlight for the current page. The rows are destinations, not a
   nav state, and colouring one of them made the menu read as a navigation
   tree with a selection.

   aria-current="page" is still set on the matching row (praxis-profile-menu.js)
   and deliberately left unstyled: it costs nothing, and it keeps the "you are
   here" fact available to a screen reader without putting the highlight back. */

.profile-menu__navlabel{
  font-size:11px; font-weight:800; letter-spacing:normal;
  color:var(--praxis-color-text-secondary); padding:8px 12px 2px;
}
/* Read-outs, not actions — the app version and build number. Same left edge as
   the rows above so the column stays aligned, but no hover, no pointer and no
   menuitem role: nothing here is clickable. */
.profile-menu__meta{
  padding:4px 12px; font-size:.75rem; font-weight:500;
  color:var(--praxis-color-text-secondary); cursor:default;
}
.profile-menu__sep{ height:1px; background:var(--praxis-color-border-default); margin:6px 0; }

/* ---- Inline control rows (Appearance, Version, Viewing as) ---------- */
.profile-menu__row{ display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 12px; }
.profile-menu__row-label{ font-size:.8125rem; font-weight:600; color:var(--praxis-color-text-secondary); }
.profile-menu__row .verswitch{ margin-right:0; }

/* ---- Segmented control used inside the menu ------------------------- */
.verswitch{
  display:inline-flex; align-items:center; gap:2px; padding:3px;
  background:rgba(16,36,58,.06); border-radius:var(--praxis-radius-full);
}
.verswitch__opt{
  height:28px; padding:0 12px; border:0; background:transparent;
  color:var(--praxis-color-text-secondary); font:inherit; font-size:.8125rem; font-weight:600;
  border-radius:var(--praxis-radius-full); cursor:pointer; white-space:nowrap;
}
.verswitch__opt:hover{ color:var(--praxis-color-text-primary); }
.verswitch__opt--active,.verswitch__opt--active:hover{
  background:var(--praxis-color-surface-default); color:var(--praxis-color-text-primary);
  box-shadow:0 1px 2px rgba(16,36,58,.14);
}
body[data-theme="dark"] .verswitch{ background:rgba(255,255,255,.08); }
body[data-theme="dark"] .verswitch__opt--active{ background:rgba(255,255,255,.16); box-shadow:none; }

/* ===========================================================================
   praxis-toolbar-compact.css  —  Praxis 0.1.15
   ========================================================================== */
/* ============================================================================
   Praxis compact toolbar — tablet form (see praxis-toolbar-compact.js)
   ============================================================================
   Below 1024px the page toolbar keeps only the back button and the primary
   action; everything else moves into a Tools dropdown, the display-mode switch
   into its own icon-only dropdown, and the remaining display controls (sort,
   columns, chart settings) into an Options drawer.
   ========================================================================= */

/* The wrapper sits inside the toolbar, in line with the back button. It's
   empty until the script fills it, so it costs nothing when expanded. */
.tb-compact{ display:flex; align-items:center; gap:8px; }
.tb-compact:empty{ display:none; }
.tb-compact__menu{ position:relative; }
.tb-compact__btn{ gap:8px; }
.tb-compact__caret{ font-size:18px !important; margin-left:-2px; opacity:.7; }

/* Compact mode is driven by measurement, not a media query — the script sets
   this class when the toolbar's contents would no longer fit on one line. */
body.tb-is-compact .toolbar,
body.tb-is-compact .toolbar__inner{ flex-wrap:nowrap; gap:8px; }
body.tb-is-compact .toolbar__spacer{ display:none; }

/* Phone: the bar carries five controls once Display is out of Options, and at
   390px the fifth one ran off the right edge. The space comes back from the
   spacing rather than from a control — nothing here is dropped or hidden.
   The Tools caret goes because it is the one mark on the bar that carries no
   information: the button is already aria-haspopup="menu", and Options next to
   it has never had one. */
@media (max-width:520px){
  body.tb-is-compact .toolbar,
  body.tb-is-compact .toolbar__inner{ gap:6px; }
  .tb-compact{ gap:6px; }
  .tb-compact__btn{ gap:6px; }
  .tb-compact__caret{ display:none; }
  body.tb-is-compact .tbtn{ padding:0 8px; }
  body.tb-is-compact .tbtn--icon{ width:36px; padding:0; }
}
/* The smallest phones this page targets (~360px). The bar fitted here exactly,
   with nothing to spare, so the fifth control has to be paid for.

   It is paid for out of the leading icons on the captioned buttons, not out of
   any caption. A wrench for "Tools" and sliders for "Options" are decoration
   next to the word they sit beside — nobody reads the glyph — while the words
   are the only thing that tells two adjacent dropdowns of settings apart.
   Filters goes with them: it was held back at first as the primary control, but
   one icon among three bare captions reads as an oversight rather than as
   emphasis. Display keeps its icon because it has no caption: there, the icon
   is the state, and it is the only thing distinguishing it from the back
   button. Above this width all three captions keep their icons, so the rule is
   "all or none" at any given size rather than a standing exception. */
@media (max-width:400px){
  .tb-compact > .tb-compact__menu > .tb-compact__btn > .material-symbols-rounded,
  .tb-compact > .tb-compact__btn > .material-symbols-rounded,
  .tb-compact > [data-action="open-filter-drawer"] > .icon,
  .tb-compact > [data-action="add-filter"] > .icon{ display:none; }
}

  /* ---- Tools dropdown ---- */
  .tb-compact__pop{
    position:absolute; top:calc(100% + 6px); left:0; z-index:120;
    min-width:220px; max-width:min(320px, calc(100vw - 32px));
    display:flex; flex-direction:column; gap:2px; padding:6px;
    background:var(--px-surface); border-radius:12px; box-shadow:var(--px-overlay);
  }
  .tb-compact__pop[hidden]{ display:none; }
  /* The tools inside are the page's own buttons, restyled as menu rows so a
     row of pills reads as a list. */
  .tb-compact__pop > *{
    width:100% !important; height:auto !important; min-height:44px;
    justify-content:flex-start !important;
    border-radius:8px !important;
  }
  /* Everything except the primary action flattens to a plain row. Run keeps
     its own background: the blanket transparent fill used to erase its
     gradient but not its white text, leaving it white-on-white at 1.00:1 —
     invisible in light mode. Restyling the text instead would need a new
     accent that passes on white, where the gradient it already carries is
     verified (5.00:1 at the top stop, from the contrast fix in praxis-core).
     Dark is left to its own .tbtn--run soft treatment for the same reason. */
  .tb-compact__pop > *:not(.tbtn--run){
    box-shadow:none !important;
    background:transparent !important;
  }
  .tb-compact__pop > *:not(.tbtn--run):hover{ background:var(--px-hover-btn) !important; }
  /* .tb-menu wrappers hold their own trigger; let it fill the row. */
  .tb-compact__pop .tb-menu > .tbtn{ width:100%; justify-content:flex-start; }

  /* ---- Display-mode dropdown ------------------------------------------
     Its own control on the compact bar rather than the first section of
     Options: choosing a view is the most-repeated action on a results screen,
     and behind Options it was two taps deep, under a heading, below a column
     list. The trigger is the current mode's icon and nothing else — the bar has
     no room for a caption, and the word would only repeat what the icon says.
     The name is on aria-label and the tooltip, and every row in the open menu
     is icon + word, so nothing rests on icon recognition alone. */
  .tb-display__btn{ padding:0 !important; }
  /* Square like the back button beside it, so the bar reads as one row of
     controls rather than a pill with an odd square in it. */
  .tb-display__btn .tb-display__icon{ display:flex; align-items:center; justify-content:center; }
  .tb-display__btn .tb-display__icon > *{ width:20px; height:20px; font-size:20px; }
  .tb-display__btn[aria-expanded="true"]{ background:var(--px-chip); }
  /* Right-aligned: Display sits at the end of the bar next to Options, and a
     left-anchored popover there hangs off the edge of a phone. */
  .tb-display__pop{ left:auto; right:0; min-width:200px; }

  /* The strip becomes a vertical list, matching how it read inside Options.
     Its sliding thumb is absolutely positioned with a width and transform
     computed for the horizontal strip; restacked those numbers are meaningless
     and it renders as a stray pill smeared across the rows. The active row
     carries its own fill here, so the thumb is redundant. */
  .tb-display__pop .viewswitch{
    display:flex; flex-direction:column; gap:2px; width:100%;
    margin:0; padding:0; background:transparent; box-shadow:none;
  }
  .tb-display__pop .viewswitch__thumb{ display:none !important; }
  .tb-display__pop .viewswitch__btn{
    display:flex; align-items:center; justify-content:flex-start; gap:12px;
    width:100%; height:auto; min-height:44px; padding:0 12px;
    border-radius:8px; background:transparent; box-shadow:none;
  }
  .tb-display__pop .viewswitch__btn:hover{ background:var(--px-hover-btn); }
  .tb-display__pop .viewswitch__btn--active{
    background:var(--px-chip); color:var(--praxis-color-pink-60);
  }
  .tb-display__pop .viewswitch__btn .tb-viewlabel{
    font-size:.9375rem; font-weight:600; color:inherit;
  }

  /* ---- Options panel ---------------------------------------------------
     Fourth shape, and this time not an invention: it is the Filters drawer.

     It began as a bottom sheet from under the toolbar to the floor (77% of the
     viewport, 51% of it empty, tab strip clipped on a phone), became an
     anchored dropdown (too small for forty columns), then a flush full-height
     side sheet — square-cornered, edge-to-edge, no scrim. That last one was
     defensible on its own terms and wrong next to its neighbour: Filters, one
     button along on the same bar, opens a rounded card inset 24px from every
     edge over a dimmed page. Two panels opened from the same toolbar, doing the
     same kind of job, looked like they came from different products.

     So the geometry, radius, shadow, inset, scrim and slide are all taken from
     .filter-drawer below 1280px, deliberately by copy rather than by extending
     that class — praxis-filters.css belongs to the filter component and this
     file ships to pages that don't load it. If the drawer's shape changes,
     these numbers have to be changed with it.

     Phone and tablet get the same card at the same inset; min(480px, …) simply
     resolves to the full width minus gutters on a phone, which is what the
     Filters drawer does there too. */
  .tb-options{
    position:fixed; z-index:151;
    top:24px; right:24px; bottom:24px;
    width:min(480px, calc(100% - 48px));
    display:flex; flex-direction:column;
    background:var(--px-surface, var(--praxis-color-surface-default));
    color:var(--praxis-color-text-primary);
    border-radius:20px;
    /* Clips the header band and the scrolling body to the rounded corners. */
    overflow:hidden;
    box-shadow:0 24px 64px rgba(32,42,53,.24), 0 8px 24px rgba(32,42,53,.12);
    transform:translateX(calc(100% + 24px));
    transition:transform 320ms var(--praxis-ease-default);
  }
  .tb-options.is-open{ transform:none; }
  .tb-options[hidden]{ display:none; }
  /* Both shadows are built from light-mode navy and vanish over a dark page.
     Same substitutions praxis-filters.css makes for the drawer and its scrim. */
  body[data-theme="dark"] .tb-options{ box-shadow:var(--px-overlay); }

  /* Same scrim as the Filters drawer, at the same opacity. The panel was
     non-modal by design — the table stayed live behind it — but a card floating
     24px off every edge over an undimmed page reads as detached rather than as
     a layer, and the two panels on this bar should not disagree about whether
     the page behind them is available. Clicking it closes, which the panel
     already supported as tap-away. */
  .tb-options-scrim{
    position:fixed; inset:0; z-index:150;
    background:rgba(32,42,53,.5);
    opacity:0; transition:opacity 220ms ease;
  }
  .tb-options-scrim.is-open{ opacity:1; }
  .tb-options-scrim[hidden]{ display:none; }
  body[data-theme="dark"] .tb-options-scrim{ background:rgba(4,8,16,.62); }

@media (prefers-reduced-motion: reduce){
  .tb-options, .tb-options-scrim{ transition:none; }
}

/* ---- panel header: title, count, close ----
   .filter-drawer__head's proportions: 16px in, 4px under, title at --t-xl/700,
   and no rule beneath it — the hairline belongs to the toolbar row below, which
   is where the search sits here too. */
/* Top-aligned, not centred. Both panels declare padding-top:16px, but the close
   button is 36px against a 30px title line, so centring the row pushed the title
   3px down and the measured gap to 19px. The Filters modal now lands on a true
   16px, and these two are meant to read as the same header. The button keeps its
   36px target and simply hangs 6px lower than the title. */
.tb-options__head{
  flex-shrink:0; display:flex; align-items:flex-start; justify-content:space-between; gap:8px;
  padding:16px 16px 4px;
}
/* Same size and weight as .filter-drawer__head h2, but coloured from
   --praxis-color-text-primary rather than the neutral scale that rule uses.
   praxis-filters.css inverts the neutrals inside .filter-drawer for dark mode;
   this panel is a sibling of that subtree, so it would have taken the light
   value and rendered near-black on navy. */
.tb-options__title{
  margin:0; flex:1; min-width:0;
  font-size:var(--praxis-type-size-xl); font-weight:700;
  color:var(--praxis-color-text-primary);
}
.tb-options__close{
  width:36px; height:36px; flex-shrink:0; display:grid; place-items:center;
  border:0; background:transparent; border-radius:var(--praxis-radius-sm);
  color:var(--praxis-color-text-primary); cursor:pointer;
}
.tb-options__close:hover{ background:var(--px-hover-btn); color:var(--praxis-color-text-primary); }
.tb-options__close:focus-visible{ outline:2px solid var(--praxis-color-teal-50); outline-offset:-2px; }
.tb-options__close .material-symbols-rounded{ font-size:22px; }

/* ---- search ----
   The answer to "there could be many many columns". Appears only once the
   panel holds enough rows to be worth searching (the script decides), so a
   three-item panel doesn't get a search box it doesn't need. */
/* Defined here rather than borrowed: this component ships to four pages and
   .visually-hidden only exists on one of them. */
.tb-options__sr{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
/* Sits where .filter-drawer__toolbar does, and carries the same hairline —
   inset from the edges rather than bled to them, which is what stops the card's
   rounded corners from being cut by a full-width rule. */
.tb-options__search{
  flex-shrink:0; position:relative;
  padding:4px 16px 16px;
}
.tb-options__search::after{
  content:""; position:absolute; left:16px; right:16px; bottom:0; height:1px;
  background:var(--praxis-color-border-default);
}
.tb-options__search[hidden]{ display:none; }
/* .filter-search's metrics — 40px, 12px in, 40px of room for the icon, 600
   weight — on theme-aware colours, for the reason given on the title above. */
.tb-options__search input{
  width:100%; height:40px; padding:0 40px 0 12px;
  border:1px solid var(--praxis-color-border-default);
  border-radius:var(--praxis-radius-md, 8px);
  background:var(--px-surface-2, var(--px-surface));
  color:var(--praxis-color-text-primary);
  font-family:inherit; font-size:var(--praxis-type-size-base); font-weight:600;
  outline:none;
}
.tb-options__search input::placeholder{ color:var(--praxis-color-text-secondary); font-weight:500; }
.tb-options__search input:focus{ border-color:var(--praxis-color-teal-50); box-shadow:0 0 0 2px var(--praxis-color-teal-50); }
.tb-options__search .material-symbols-rounded{
  position:absolute; right:28px; top:calc(50% - 6px); transform:translateY(-50%);
  font-size:20px; color:var(--praxis-color-text-secondary); pointer-events:none;
}
.tb-options__empty{
  padding:18px 16px; margin:0; text-align:center;
  font-size:.8125rem; color:var(--praxis-color-text-secondary);
}
.tb-options__empty[hidden]{ display:none; }

/* No top padding: a sticky heading with top:0 stops at the scrollport edge, so
   any padding above it is a strip the rows scroll through in the open — 6px of
   half a checkbox riding above "COLUMNS". The headings carry their own 8px of
   top padding, so nothing is lost by taking it off the container. */
.tb-options__body{
  flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  padding:0 8px 24px; display:flex; flex-direction:column; gap:2px;
}
.tb-options__panel{ display:flex; flex-direction:column; gap:4px; }
.tb-options__panel[hidden]{ display:none; }
.tb-options__panel + .tb-options__panel{ margin-top:8px; }
/* The search hides rows with the `hidden` attribute, but the layout rules
   below set display on these same elements and would win over the UA's
   [hidden] rule on specificity alone. Restore it explicitly. */
.tb-options__body .viewswitch__btn[hidden],
.tb-options__body .sortmenu__opt[hidden],
.tb-options__body .colmenu__row[hidden],
.tb-options__body .colmenu__group[hidden],
.tb-options__body [data-tb-row][hidden],
.tb-options__body [data-tb-group][hidden]{ display:none !important; }
/* Sticky, because with a long column list the heading you need is the one that
   has just scrolled off. Sections stay expanded rather than collapsing into an
   accordion: NN/g's finding is that accordions cut visibility and add
   interaction cost, and search is the better answer to length. */
.tb-options__sechead{
  position:sticky; top:0; z-index:2; margin:0;
  /* Full-bleed white: the heading sits inside the body's 8px padding, so a
     background only as wide as the text box leaves a gutter down each side for
     rows to scroll through. Pulled out to the panel edges and padded back in,
     the band is solid all the way across. */
  margin-left:-8px; margin-right:-8px;
  padding:8px 16px 6px;
  background:var(--px-surface, var(--praxis-color-surface-default));
  border-bottom:1px solid var(--praxis-color-border-default);
  font-size:.75rem; font-weight:700; letter-spacing:.02em; text-transform:uppercase;
  color:var(--praxis-color-text-secondary);
}
/* Sentence case, weight for emphasis rather than caps. */
.tb-options__label{
  font-size:.8125rem; font-weight:700; color:var(--praxis-color-text-secondary); padding:6px 0 0 8px;
}
/* The page's own "Sort Results By:" caption travels in with the sort menu and
   would sit directly under a heading that already says Sort by. Likewise the
   column list's own filter box — the panel search covers every section, and
   two search fields in one panel is a worse problem than none. */
.tb-options__body .toolbar__sortlabel,
.tb-options__body .colmenu__search{ display:none; }
.tb-options__body .colmenu__grouplabel{
  font-size:.8125rem; font-weight:700; color:var(--praxis-color-text-secondary); padding:6px 8px 2px;
}

/* Display mode: a vertical list of icon + label rather than an icon strip. */
.tb-options__body .viewswitch{
  display:flex; flex-direction:column; gap:2px; width:100%;
  background:transparent; box-shadow:none; padding:0;
}
/* The switcher's sliding indicator is absolutely positioned, and its width and
   transform are computed for the horizontal strip in the toolbar. Restacked as
   a vertical list those numbers are meaningless: it rendered as a stray pale
   pill floating across the rows — most visibly as a smear through the sticky
   DISPLAY MODE heading, which looked like the heading was transparent. The
   active row already carries its own fill here, so the thumb is redundant. */
.tb-options__body .viewswitch__thumb{ display:none !important; }
.tb-options__body .viewswitch__btn{
  display:flex; align-items:center; justify-content:flex-start; gap:12px;
  width:100%; height:auto; min-height:48px; padding:0 12px;
  border-radius:10px; background:transparent; box-shadow:none;
}
.tb-options__body .viewswitch__btn:hover{ background:var(--px-hover-btn); }
.tb-options__body .viewswitch__btn--active{
  background:var(--px-chip); color:var(--praxis-color-pink-60);
}
.tb-options__body .viewswitch__btn .tb-viewlabel{
  font-size:.9375rem; font-weight:600; color:inherit;
}
/* The moved controls were laid out for a horizontal bar; stack them.
   (The "Sort Results By:" caption is hidden above — the section heading says
   it. Styling it here as well would just override that.) */
.tb-options__body .sortmenu,
.tb-options__body .viewswitch{ width:100%; }

/* Sort: a trigger plus its own absolutely-positioned dropdown. Nested inside a
   scrolling popover that dropdown gets clipped by the scroll container, and a
   menu opening a menu is the wrong shape here anyway. Inside Options it
   flattens — the trigger goes and its options become the list, matching how
   Display mode reads two sections above.

   Done in CSS, deliberately: the options keep their own click handlers so
   choosing one still sorts, and #sortPop keeps its `hidden` attribute, so when
   the viewport grows and the control is handed back to the toolbar it returns
   as a closed dropdown rather than a list stuck open. */
.tb-options__body .sortmenu{ position:static; }
.tb-options__body .sortbtn{ display:none; }
.tb-options__body .sortmenu__pop{
  display:flex !important; flex-direction:column; gap:2px;
  /* z-index:auto alongside position:static — see the note on .colmenu__pop in
     search-page.html. A leftover popover z-index can outrank the sticky section
     heading the moment its host becomes a flex item. */
  position:static; z-index:auto; width:100%; min-width:0; inset:auto;
  background:transparent; border:0; border-radius:0; box-shadow:none; padding:0;
}
.tb-options__body .sortmenu__opt{ min-height:44px; height:auto; padding:0 12px; border-radius:10px; font-size:.9375rem; }


.tb-options__body > *{ flex-shrink:0; }

/* A moved-in panel brings its own card chrome — surface, border, shadow, its
   own title and collapse chevron. Inside the drawer that reads as a card in a
   card, and the section label above it already names it. Flatten it. */
.tb-options__body #fieldsPanel,
.tb-options__body #chartPanel,
.tb-options__body #explorePanel{
  background:transparent !important; box-shadow:none !important; border:0 !important;
  border-radius:0 !important; padding:0 !important; width:100% !important;
  max-width:none !important; min-width:0 !important; position:static !important;
}
/* Their own headers duplicate the drawer's section label. */
.tb-options__body :is(#fieldsPanel, #chartPanel, #explorePanel)
  :is(.fields__head, .panel__head, .exp-head, [class$="__head"]):first-child{ display:none; }

/* ===========================================================================
   praxis-controls.css  —  Praxis 0.1.15
   ========================================================================== */
/* ============================================================================
   PRAXIS CONTROLS — the small shared controls that are not the toolbar band.

   A toolbar menu, the 34px icon button and the filter field. All three were
   promoted from the groom-lake prototype on 2026-08-21, where they had been
   hand-copied into up to twelve pages each, in versions that had drifted apart.

   WHY A SHEET OF THEIR OWN AND NOT praxis-admin.css

   That is where .tbtn lives, so it looks like the obvious home — but only six
   of the twenty prototype pages load praxis-admin.css, and it is the largest
   sheet in the system. It also carries the application shell, a bare-element
   box-sizing reset and its own .tbtn, so a record page adding it to pick up a
   dropdown would be restyling its whole toolbar to get one panel. Every page
   that needs these three can afford four kilobytes; none of them can afford
   the admin shell.

   Depends on praxis-tokens.css and praxis-core.css for --px-* and the
   --praxis-* scales, and on nothing else.
   ========================================================================= */

/* ---- Toolbar menu -----------------------------------------------------
   A .tbtn that opens a panel under itself: Group on Report Management, sort
   and column menus on Search, row actions on the record pages, the layout
   picker in the workspace editor.

   Promoted from the prototype 2026-08-21, where twelve pages carried a copy in
   three mutually incompatible versions — 12px radius and no border here, 36px
   items and a hairline there, one calling the rule .tb-dropdown__divider and
   another .tb-dropdown__sep. praxis-toolbar-compact.css and .js already keyed
   off .tb-menu, so the system was reading a class it never defined; that is
   the gap this closes. Both separator names are kept: the alias costs one
   selector and saves editing seven files to rename a hairline.

   Surface follows .px-pop exactly — hairline and elevation-4 by default,
   borderless over --px-overlay under the Praxis variant — so a menu opened from
   the toolbar and one opened from the nav rail are the same object. */
.tb-menu{position:relative;display:inline-flex;}
.tb-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:220px;padding:6px;
  background:var(--praxis-color-surface-default);border:1px solid var(--praxis-color-border-default);
  border-radius:var(--praxis-radius-md);box-shadow:var(--praxis-elevation-4);}
.tb-dropdown[hidden]{display:none;}
body[data-variant="praxis"] .tb-dropdown{border:0;background:var(--px-surface);box-shadow:var(--px-overlay);}
/* Right-aligned when the trigger is at the end of the band and a left-anchored
   panel would hang off the page. */
.tb-dropdown--right{left:auto;right:0;}
/* min-height rather than a fixed height: an item may carry a second line
   (a description, a count, an owner) and must grow rather than clip it. */
.tb-dropdown__item{display:flex;align-items:center;gap:10px;width:100%;min-height:36px;padding:6px 10px;border:0;
  background:transparent;border-radius:var(--praxis-radius-sm);cursor:pointer;text-align:left;
  font-family:inherit;font-size:.875rem;font-weight:500;color:var(--praxis-color-text-primary);}
/* --px-hover's 2.5% wash is tuned for a bare row on --px-surface; a dropdown
   menu item is already on its own tinted menu surface, and the same 2.5% on
   top reads as almost no change. teal-20 is one step past the teal-10
   selection fill (see [aria-checked="true"] below) so hover and selected
   stay visually distinct. blue-10 was considered first — on-palette, but
   it's the system's info tone (--praxis-color-status-info /
   --praxis-tone-info-bg both resolve to blue), so a hovered item would have
   read as an informational callout instead. */
.tb-dropdown__item:hover{background:var(--praxis-color-teal-20);}
.tb-dropdown__item:focus-visible{outline:2px solid var(--praxis-color-border-focus);outline-offset:-2px;}
.tb-dropdown__item .icon,.tb-dropdown__item svg{width:18px;height:18px;flex-shrink:0;color:var(--praxis-color-text-secondary);}
.tb-dropdown__item .material-symbols-rounded{font-size:18px;flex-shrink:0;color:var(--praxis-color-text-secondary);}
.tb-dropdown__item--danger{color:var(--praxis-color-status-danger);}
.tb-dropdown__item--danger .icon,.tb-dropdown__item--danger svg{color:currentColor;}
/* The checked option in a menuitemradio group takes the app's selection accent,
   the same pink the nav rail and the report tree use. */
.tb-dropdown__item[aria-checked="true"] .icon,.tb-dropdown__item[aria-checked="true"] svg{color:var(--praxis-color-pink-60);}
.tb-dropdown__sub{display:block;font-size:.75rem;font-weight:400;color:var(--praxis-color-text-tertiary);}
.tb-dropdown__sec{padding:8px 10px 4px;font-size:.6875rem;font-weight:700;color:var(--praxis-color-text-tertiary);}
.tb-dropdown__divider,.tb-dropdown__sep{height:1px;margin:6px 8px;background:var(--praxis-color-border-default);}
.tb-dropdown__empty{padding:8px 10px;font-size:.8125rem;color:var(--praxis-color-text-tertiary);}

/* ---- Icon button ------------------------------------------------------
   The 34px square that sits in a card header or a panel toolbar — expand all,
   pin, filter toggles, collapse. Not .tbtn--icon: that is the 40px control for
   the page toolbar band, and these two have never been the same size on any
   screen that carries both.

   Promoted from the prototype 2026-08-21 (.rm-iconbtn in three pages,
   .we-iconbtn in a fourth). Radius moves from the copies' 9px to
   --praxis-radius-sm, which is 8px: a design system should not ship a value
   that is off its own scale to preserve one pixel. */
.iconbtn{width:34px;height:34px;flex-shrink:0;display:grid;place-items:center;border:0;
  border-radius:var(--praxis-radius-sm);background:var(--px-surface);color:var(--praxis-color-text-secondary);
  box-shadow:var(--px-tool-shadow);cursor:pointer;
  transition:box-shadow var(--praxis-motion-fast),background var(--praxis-motion-fast),color var(--praxis-motion-fast);}
.iconbtn:hover{box-shadow:var(--px-tool-shadow-hover);color:var(--praxis-color-text-primary);}
.iconbtn:focus-visible{outline:2px solid var(--praxis-color-border-focus);outline-offset:2px;}
.iconbtn .icon,.iconbtn svg{width:18px;height:18px;}
.iconbtn .material-symbols-rounded{font-size:18px;}
/* On = the app's selection accent, so a toggled filter reads the same here as a
   selected row does. */
.iconbtn--on,.iconbtn--on:hover{background:var(--praxis-color-pink-60);color:var(--praxis-color-white);
  box-shadow:0 0 0 .5px rgba(227,0,114,.25);}
.iconbtn:disabled,.iconbtn[aria-disabled="true"]{opacity:.45;cursor:not-allowed;box-shadow:none;}
body[data-theme="dark"] .iconbtn{background:var(--px-surface-2);
  box-shadow:0 0 0 .5px rgba(255,255,255,.08),inset 0 .5px 0 rgba(255,255,255,.06);}

/* ---- Filter field -----------------------------------------------------
   Type-to-filter beside a list: the report tree's Filter, the workspace
   editor's Search reports. Trailing glyph, because the field is a refinement of
   the list under it rather than a search box you begin at — the leading-glyph
   treatment belongs to the app bar and to .cn-find inside a menu.

   Sized to match .iconbtn so a header row of both lines up: same 34px, same
   radius, same tool shadow. Promoted from the prototype 2026-08-21. */
.filterfield{position:relative;display:inline-flex;align-items:center;}
.filterfield input{width:100%;height:34px;padding:0 34px 0 12px;border:0;border-radius:var(--praxis-radius-sm);
  background:var(--px-surface);box-shadow:var(--px-tool-shadow);color:var(--praxis-color-text-primary);
  font:inherit;font-size:.875rem;outline:none;}
.filterfield input::placeholder{color:var(--praxis-color-text-tertiary);}
.filterfield input:focus{box-shadow:0 0 0 2px color-mix(in srgb, var(--praxis-color-interactive-default) 30%, transparent);}
.filterfield .icon,.filterfield svg{position:absolute;right:12px;width:18px;height:18px;
  color:var(--praxis-color-text-tertiary);pointer-events:none;}
.filterfield .material-symbols-rounded{position:absolute;right:12px;font-size:18px;
  color:var(--praxis-color-text-tertiary);pointer-events:none;}
body[data-theme="dark"] .filterfield input{background:var(--px-surface-2);
  box-shadow:0 0 0 .5px rgba(255,255,255,.08),inset 0 .5px 0 rgba(255,255,255,.06);}

/* ===========================================================================
   praxis-quick-rail.css  —  Praxis 0.1.15
   ========================================================================== */
/* ============================================================================
   Praxis quick-filter rail — the compact form of the quick-filter strip
   ============================================================================
   When the toolbar collapses, the five-across card grid in praxis-filters.css
   is hidden and this takes over: a horizontally scrolling row of pills, one
   per designated quick filter, each opening a popover anchored under its pill.

   Gated on body.tb-is-compact, NOT a media query. praxis-toolbar-compact.js
   collapses on `toolbar.scrollWidth > clientWidth || innerWidth <= 1024` — it
   is content-driven with a tablet floor, so a wide toolbar can collapse well
   above 1024px. A max-width query here drifted out of step with it; sharing
   the class keeps the two switches identical by construction.

   Why this exists: the strip used to vanish below 1399px while the filter
   modal still let you designate quick filters — and designating one LOCKS its
   row in the modal (filter-row--locked, controls not rendered). So a
   quick-designated filter became completely unreachable at narrow widths,
   showing a "Used as Quick Filter" badge that pointed at something invisible.

   This file and praxis-quick-rail.js are deliberately separate from
   praxis-filters.*, which is a PORT of the Responsive Search project — a
   re-extract overwrites those two files and would wipe anything added there.
   Only one rule in praxis-filters.css belongs to this feature (the one that
   hides the strip); it is commented there.
   ========================================================================= */

/* Expanded toolbar keeps the card grid; the rail is compact-mode only. */
.qrail{ display:none; }
.qrail-pop{ display:none; }

body.tb-is-compact .qrail{
  display:block;
  /* No negative-margin bleed. It used to pull the rail one gutter past the
     container so it could scroll edge to edge, but that put the box at -16px
     on a 390px screen — the pills ran off the left of the view. The rail
     starts on the content edge and grows to the right only. */
  padding:var(--praxis-space-8) 0;
}
body.tb-is-compact .qrail[hidden]{ display:none !important; }

body.tb-is-compact .qrail__scroller{
  display:flex; gap:var(--praxis-space-8);
  overflow-x:auto; scrollbar-width:none;
  /* One gutter, matching .filter-chips below and the toolbar above, so the
     first pill shares their left edge.

     No scroll-snap here. scroll-snap-align:start snaps a pill to the
     scrollport's border edge, not its padding edge, so the browser settled
     the rail at scrollLeft = padding — swallowing the left inset and sitting
     the first pill flush at 0 while the chips below stayed at one gutter. */
  padding:2px var(--praxis-filters-gutter, 20px);
}
body.tb-is-compact .qrail__scroller::-webkit-scrollbar{ display:none; }

/* Same construction as a toolbar button (.tbtn): rectangular at 10px, the
   --px-tool fill and --px-tool-shadow elevation. Both tokens are theme-scoped,
   so dark comes for free and needs no override here. Applied-filter chips are
   deliberately the opposite — flat and pill-shaped — so the two rows read as
   different kinds of thing. See praxis-filters-local.css. */
body.tb-is-compact .qrail__pill{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:7px;
  height:40px; padding:0 14px;
  border:0; border-radius:10px;
  background:var(--px-tool);
  color:var(--praxis-color-text-primary);
  box-shadow:var(--px-tool-shadow);
  font:inherit; font-size:var(--praxis-type-size-base); font-weight:600;
  white-space:nowrap; cursor:pointer;
  transition:box-shadow var(--praxis-motion-fast, 120ms),
             background var(--praxis-motion-fast, 120ms),
             color var(--praxis-motion-fast, 120ms);
}
body.tb-is-compact .qrail__pill:hover{ box-shadow:var(--px-tool-shadow-hover); }
body.tb-is-compact .qrail__pill:active{ background:var(--px-surface-2); }
body.tb-is-compact .qrail__pill:focus-visible{
  outline:2px solid var(--praxis-color-interactive-default);
  outline-offset:2px;
}
body.tb-is-compact .qrail__pill[aria-expanded="true"]{
  background:var(--px-surface-2);
  box-shadow:var(--px-tool-shadow-hover);
}

body.tb-is-compact .qrail__bolt{ font-size:18px; line-height:1; color:var(--praxis-color-text-tertiary); }
body.tb-is-compact .qrail__caret{ font-size:18px; line-height:1; opacity:.7; margin-left:-2px; }

/* An active pill stays neutral — the count alone carries the state. Several
   of these sit in a row, and accenting them all turns the rail into a solid
   band with no hierarchy left. */
body.tb-is-compact .qrail__count{
  display:inline-grid; place-items:center;
  min-width:20px; height:20px; padding:0 6px;
  border-radius:999px;
  background:var(--praxis-color-neutral-15, rgba(32,42,53,.10));
  color:var(--praxis-color-text-primary);
  font-size:var(--praxis-type-size-xs); font-weight:700;
  font-variant-numeric:tabular-nums;
}

/* praxis-core gives dark toolbar buttons an opaque --px-surface-2 fill, but
   that rule is scoped to .toolbar descendants and the rail sits outside it —
   left alone the pill fell back to --px-tool's translucent white and read a
   shade off. Match the toolbar explicitly. */
body[data-theme="dark"].tb-is-compact .qrail__pill{ background:var(--px-surface-2); }
body[data-theme="dark"].tb-is-compact .qrail__pill:active,
body[data-theme="dark"].tb-is-compact .qrail__pill[aria-expanded="true"]{ background:var(--px-surface); }

body[data-theme="dark"].tb-is-compact .qrail__count{
  background:rgba(255,255,255,.14);
  color:var(--praxis-color-text-primary);
}

/* ---- Popover, anchored under the pill (positioned by the script) ---- */
body.tb-is-compact .qrail-pop{
  position:fixed; z-index:151; top:0; left:0;
  display:flex; flex-direction:column;
  width:max-content; min-width:240px;
  max-width:min(340px, calc(100vw - 16px));
  /* Capped so a long distribution scrolls inside the popover rather than
     running off-screen — this is what makes "See More" safe for an arbitrary
     number of values. */
  max-height:min(60vh, 420px);
  background:var(--px-surface, #fff);
  border:1px solid var(--praxis-color-border-default);
  border-radius:12px;
  box-shadow:var(--px-overlay, 0 8px 32px rgba(0,0,0,.18));
  opacity:0; transform:translateY(-4px);
  transition:opacity 140ms ease,
             transform 140ms ease;
}
body.tb-is-compact .qrail-pop.is-open{ opacity:1; transform:none; }
body.tb-is-compact .qrail-pop[hidden]{ display:none; }

body.tb-is-compact .qrail-pop__head{
  display:flex; align-items:center; gap:8px;
  padding:8px 8px 4px 12px; flex-shrink:0;
}
body.tb-is-compact .qrail-pop__title{
  margin:0; flex:1; min-width:0;
  font-size:var(--praxis-type-size-base); font-weight:700;
  color:var(--praxis-color-text-primary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
body.tb-is-compact .qrail-pop__close{
  display:grid; place-items:center;
  width:32px; height:32px; flex-shrink:0;
  border:0; border-radius:8px; background:transparent;
  color:var(--praxis-color-text-secondary); cursor:pointer;
}
body.tb-is-compact .qrail-pop__close:hover{ background:var(--px-hover, rgba(0,0,0,.04)); }
body.tb-is-compact .qrail-pop__close .material-symbols-rounded{ font-size:20px; }

body.tb-is-compact .qrail-pop__body{
  overflow-y:auto; overscroll-behavior:contain;
  padding:0 8px 8px; min-height:0;
}
/* The .qfilter card is MOVED in here, not cloned, so praxis-filters.js keeps
   driving it (its handlers delegate from document). Strip the card chrome —
   the popover already provides the surface, title and dismiss. */
body.tb-is-compact .qrail-pop__body .qfilter{
  border:0; background:transparent; padding:0;
}
body.tb-is-compact .qrail-pop__body .qfilter__head{ display:none; }
/* Distribution rows are touch targets here. */
body.tb-is-compact .qrail-pop__body .qfilter__row{ min-height:40px; padding:var(--praxis-space-8); }
/* praxis-filters.css resets native button chrome, but scopes that reset to
   .quick-filters and friends — and this card has been moved out of there, so
   every button inside the popover was falling back to the UA default. That is
   what put a `2px outset black` bevel on See More. Same reset, same :where()
   zero specificity so the rules below still win. */
body.tb-is-compact .qrail-pop :where(button){
  border:0; background:transparent; font:inherit; color:inherit; cursor:pointer;
}
body.tb-is-compact .qrail-pop :where(*){ box-sizing:border-box; }

/* See More / See Less: a footer action pinned to the bottom of the scroll area
   so it stays reachable once the list is long enough to scroll. */
body.tb-is-compact .qrail-pop__body .qfilter__more{
  position:sticky; bottom:0; z-index:1;
  display:flex; align-items:center; justify-content:center;
  min-height:40px; width:100%; margin-top:4px; padding:0 8px;
  border:0; border-top:1px solid var(--praxis-color-border-default);
  border-radius:0 0 6px 6px;
  background:var(--px-surface, #fff);
  color:var(--praxis-color-interactive-default);
  font-size:var(--praxis-type-size-xs); font-weight:700; text-align:center;
  cursor:pointer;
  transition:background var(--praxis-motion-fast) ease;
}
body.tb-is-compact .qrail-pop__body .qfilter__more:hover{
  background:var(--px-hover, rgba(0,0,0,.04));
  text-decoration:none;
}
body.tb-is-compact .qrail-pop__body .qfilter__more:focus-visible{
  outline:2px solid var(--praxis-color-interactive-default);
  outline-offset:-2px;
}
body[data-theme="dark"].tb-is-compact .qrail-pop__body .qfilter__more{
  background:var(--px-surface, #192336);
  border-top-color:rgba(255,255,255,.12);
}

@media (prefers-reduced-motion:reduce){
  body.tb-is-compact .qrail-pop{ transition:none; }
}

/* ===========================================================================
   praxis-rfield.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS RECORD FIELDS (.rfield) — shared record-form field system.

   Extracted from record-page.html on 2026-08-12, when a second page
   (record-page-initiate.html) needed the same controls. Previously inline,
   which is the per-page-copy pattern the Aug 4 consolidation pass removed
   everywhere else. Load this instead of copying the block.

   Contents
     1 · .rfield          enclosed control, label above
     2 · .pillset/.pill   picklists as real radios
     3 · .rref            reference field (lookup from a configured list)
     4 · .rtable          in-record table (grid rows + empty state)
     5 · .subsec          sub-section heading inside a section
   ===================================================================== */

/* ============================== 1 · RECORD FORM FIELD ==============================

   The alternative to .field. .field is a read-only display row: label in a
   200px gutter on the left, value on the right, rows separated by inset
   hairlines and nothing enclosed. That reads well for a summary but poorly
   for data entry — there's no visible affordance telling you a value can be
   changed, which matters more here than usual because the same record body
   is editable in one workflow step and frozen in the next.

   So: label above, control in a container. The enclosure carries the state.

   The control started from the enclosed inputs in the filter modal
   (.filter-search-input input / .cf-input in praxis-filters.css) so a text box
   would look the same wherever it appears. It has since moved on: 40px tall,
   no border, --px-field fill, chevron inset 12px. praxis-filters.css still
   carries the older bordered treatment and is a candidate to follow.

   Workflow steps: fields exist per step, and a step can freeze its fields
   once it's passed. .rfield--locked renders that frozen state (no border, no
   fill, value as plain text) so a passed step reads as a record rather than
   a disabled form. Section visibility per step is markup-level: give a
   .section a data-step="initiate" and show/hide by the record's current step.
   ===================================================================== */
/* The fill is the affordance now that there's no border, so it carries a real
   step away from the card surface rather than the near-white it had when a 1px
   edge was doing the work. It lives in praxis-core.css as --px-field, not here,
   because the admin and profile fields (.admin-field) take the same treatment
   and the two must not drift. */

.rfield{display:flex;flex-direction:column;gap:6px;padding:10px 16px;min-width:0;}
/* text-secondary / text-primary, NOT the neutral scale. praxis-core does not
   remap --praxis-color-neutral-90 or -70 for dark — they stay #2a3846 and
   #465c74 — so a control that took them rendered dark-on-dark at 1.17:1,
   invisible. The theme tokens invert properly (11.74:1 in dark). Same trap the
   filter chips hit; worth checking any control that reaches for the raw scale. */
.rfield__label{display:flex;align-items:center;font-size:.8125rem;font-weight:600;
  color:var(--praxis-color-text-secondary);}
.rfield__label .req{display:inline-block;width:.72em;height:.72em;margin-left:5px;
  vertical-align:middle;position:relative;top:-1px;}
/* One enclosure for every control type, so a select, a date and a text box
   share a fill and a baseline. No border: the grey block is the affordance,
   which keeps a column of fields quieter than a column of outlined boxes.
   background-color, not the shorthand — selects layer a chevron image on top
   and the shorthand would wipe it. */
.rfield__control{width:100%;min-width:0;box-sizing:border-box;height:40px;padding:0 12px;
  border:0;border-radius:var(--praxis-radius-md);
  background-color:var(--px-field);
  font:inherit;font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);
  transition:background-color var(--praxis-motion-fast),box-shadow var(--praxis-motion-fast);}
/* Placeholders are content, so they answer to 1.4.3 like any other text.
   --praxis-color-text-disabled measured 2.73:1 light / 2.77:1 dark against this
   fill — well under 4.5. text-secondary is the lightest token that clears it
   (4.91:1 and 5.51:1); the weight drop is what separates a placeholder from a
   real value now, not the colour alone. */
.rfield__control::placeholder{color:var(--praxis-color-text-secondary);font-weight:500;opacity:1;}
.rfield__control:hover:not(:disabled){background-color:var(--px-field-hover);}
/* :not(:disabled) here only to match the hover rule's specificity — without it
   hover is (0,3,0) against focus's (0,2,0) and wins, so clicking into a field
   kept the grey hover fill while the ring said teal. */
/* With no resting border the focused edge has to come from somewhere: an inset
   ring draws it without changing the box, so nothing shifts on focus. */
.rfield__control:focus:not(:disabled){outline:none;
  box-shadow:inset 0 0 0 1px var(--praxis-color-interactive-default),
             0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent);}

/* The native select arrow pins itself to the control's right edge and no
   amount of padding moves it, so it read as touching the side. Own chevron
   instead, inset by the same 12px as the text. Two images rather than
   currentColor, which background-image can't take. */
select.rfield__control{appearance:none;-webkit-appearance:none;padding-right:38px;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%235D6977'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center;background-size:20px 20px;}
body[data-theme="dark"] select.rfield__control{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%239AA7B4'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");}
/* Date and time carry native indicators on the same edge — give them the same
   inset so the trailing icons line up down the column. */
.rfield__control::-webkit-calendar-picker-indicator{margin-right:2px;}
textarea.rfield__control{height:auto;min-height:96px;padding:10px 12px;font-weight:400;
  line-height:1.6;resize:vertical;}

/* Groups of short fields sit side by side rather than each taking a full row. */
.rfield__group{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));}
/* Once frozen, a side-by-side group stacks: three label-above-value columns
   would be the one place static fields stopped being label-beside-value rows. */
.rfield__group:has(.rfield--locked){grid-template-columns:1fr;}
.rfield__group .rfield{padding-inline:16px;}

/* Pills and chips keep their own shapes — they're already the control — but
   take the same label treatment and vertical rhythm as the enclosed ones. */
.rfield--choice .toggle-group,.rfield--choice .chip-area{margin-top:2px;}

/* An auto-filled value the user can still override reads as a normal control;
   the hint carries the "we filled this for you" part so the field doesn't
   silently look like typed input. */
.rfield__hint{font-size:.75rem;font-weight:500;color:var(--praxis-color-text-secondary);}

/* ---- Invalid: a required field the user tried to save or submit without.
   An inset ring rather than a real border, for the same reason focus uses one —
   the box does not change size, so nothing reflows when a form of fields turns
   red at once. 1.5px because a 1px red line on a grey fill is easy to miss.
   Only ever set by the save/submit check, never on load: showing a form as
   broken before it has been touched is its own usability problem. */
.rfield--invalid .rfield__control,
.rfield--invalid .rfield__control:hover:not(:disabled){
  box-shadow:inset 0 0 0 1.5px var(--praxis-color-red-60);}
/* Focus still wins, but keeps the red so the field does not look resolved just
   because the cursor is in it. */
.rfield--invalid .rfield__control:focus:not(:disabled){
  box-shadow:inset 0 0 0 1.5px var(--praxis-color-red-60),
             0 0 0 3px color-mix(in srgb, var(--praxis-color-red-60) 20%, transparent);}
/* A pill group has no box of its own, so the pills carry it. */
.rfield--invalid .pill span{border-color:var(--praxis-color-red-60);}
.rfield--invalid .rfield__label{color:var(--praxis-color-red-60);}

/* ---- The message that explains why nothing saved. role="alert" in markup. */
.form-alert{display:flex;gap:12px;align-items:flex-start;margin:0 0 4px;padding:12px 14px;
  border-radius:var(--praxis-radius-md);background:var(--praxis-color-red-10);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--praxis-color-red-60) 35%, transparent);}
.form-alert[hidden]{display:none;}
body[data-theme="dark"] .form-alert{background:rgba(226,45,56,.14);}
.form-alert__icon{flex-shrink:0;width:20px;height:20px;color:var(--praxis-color-red-60);}
.form-alert__icon svg{width:20px;height:20px;fill:currentColor;display:block;}
.form-alert__body{min-width:0;display:flex;flex-direction:column;gap:2px;}
.form-alert__title{font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);}
.form-alert__detail{font-size:.8125rem;color:var(--praxis-color-text-secondary);}
.form-alert__list{margin:0;padding:0;list-style:none;display:flex;flex-wrap:wrap;gap:4px 10px;}
.form-alert__list button{border:0;background:transparent;padding:0;font:inherit;font-size:.8125rem;
  font-weight:600;color:var(--praxis-color-text-link);text-decoration:underline;cursor:pointer;}
.form-alert__list button:focus-visible{outline:2px solid var(--praxis-color-interactive-default);outline-offset:2px;border-radius:2px;}

/* ====================== FROZEN BY WORKFLOW STEP — the static field ==========
   One representation for a value the user cannot change, wherever it appears:
   label in a fixed gutter on the left, value beside it, a thin inset rule
   between rows. This is the .field row from the record summary card — the
   treatment that already read correctly — generalised, so locked record fields
   and the admin/profile read-outs match it instead of each inventing their own
   label-above-value variant.

   Semantic tokens only. The summary card's .field reaches for --praxis-color-
   neutral-70/-90 and needs two dark-mode overrides to stay legible; taking
   text-secondary/text-primary here means it inverts on its own.
   ===================================================================== */
/* One height for every static row, content centred in it, and the label and
   value sharing a baseline. The three go together: the label and value are set
   at the same size and line-height, so a single line box in each means
   align-items:center centres the pair AND puts their baselines on the same
   line. Aligning baselines while the two texts were 13px and 14px would have
   needed a magic offset that broke the moment either size changed.

   min-height, not height: a wrapped location or a two-line summary has to
   grow. Every single-line row is exactly --px-static-field-h; only content that
   genuinely does not fit exceeds it, which beats clipping it. */
/* 64px, matching the summary card's rows — body[data-variant="praxis"]
   .field:not(.field--vertical) raises those from 48 to 64, so a static row
   built at 48 sat visibly shorter than the ones it was meant to match.
   Named --px-* like the rest of the material layer; a bare --static-field-h
   sat outside every namespace the DS consolidation plan recognises. */
:root{ --px-static-field-h:64px; }
.rfield--locked{
  position:relative;
  flex-direction:row;
  align-items:center;
  gap:12px;
  min-height:var(--px-static-field-h);
  padding:8px 16px;
}
/* Inset, like the summary card's — a rule that runs to the card edge cuts the
   card in half instead of separating two rows. */
/* A divider starts where the label starts. The rows carry 16px of padding, so
   the rule is inset 16px too — at the inherited 24px it began 8px inside the
   label's left edge, which read as a misalignment down the whole card. The
   summary card's own .field::before is corrected to match, below. */
.rfield--locked::before,
.rfield--locked::after{
  content:"";position:absolute;left:16px;right:16px;
  height:1px;background:var(--praxis-color-border-default);
}
.rfield--locked::before{top:0;}
.rfield--locked::after{bottom:0;}
/* Every static row draws both rules, so one standing alone is bounded top and
   bottom. Stacked rows would then paint two 1px lines against each other, so
   consecutive ones overlap by exactly 1px and the two rules land on the same
   pixel. Done with a negative margin rather than by suppressing one rule: the
   rows are not always DOM-adjacent — a .rfield__group of short fields sits
   between them — so a sibling-based :has() test misses those boundaries, which
   is what it did here. */
.rfield--locked + .rfield--locked,
.rfield--locked + .rfield__group,
.rfield__group + .rfield--locked,
.rfield__group + .rfield__group,
.rfield__group .rfield--locked + .rfield--locked{margin-top:-1px;}
/* Same correction for the summary card's rows. Same specificity as the pages'
   own rule, and this file loads after them. */
.field::before,.field__group::before{left:16px;right:16px;}
/* Every static row draws its top rule, including the first in a section: a row
   standing on its own needs a rule above and below it (see the profile page's
   Account card), and suppressing the first one made a lone field look
   unbounded. */
.rfield--locked .rfield__label{
  width:200px;flex-shrink:0;
  /* Same size and line-height as the value, so the two share a baseline
     without an offset. The label stays distinguishable by colour. */
  font-size:.875rem;line-height:1.5;
  align-items:baseline;
  color:var(--praxis-color-text-secondary);
}
/* Labels on static fields terminate in a colon. Done here rather than in the
   markup so it holds for fields added later, and for the ten generated admin
   pages. Ordered before .req so a required frozen field reads "Name:*", the
   same way the summary card's .field__prompt does.
   [data-label-nocolon] opts out the labels that are questions — "Was High
   Energy Present?:" is not an improvement. */
.rfield--locked .rfield__label::after,
.admin-field:has(> .admin-field__value) .admin-field__label::after{
  content:":";order:1;
}
.rfield--locked .rfield__label[data-label-nocolon]::after,
.admin-field:has(> .admin-field__value) .admin-field__label[data-label-nocolon]::after{
  content:none;
}
.rfield--locked .rfield__label .req,
.admin-field:has(> .admin-field__value) .admin-field__label .req{order:2;}
.rfield--locked .rfield__control,
.rfield--locked .rfield__control:hover{
  flex:1;min-width:0;width:auto;height:auto;min-height:0;
  border-color:transparent;background:transparent;padding:0;
  font-size:.875rem;line-height:1.5;
  color:var(--praxis-color-text-primary);box-shadow:none;pointer-events:none;}
.rfield--locked select.rfield__control{appearance:none;-webkit-appearance:none;background-image:none;}
/* A frozen textarea is the one that still looked editable: a filled, bordered,
   resizable box with a scrollbar. As static text it has to size to its content
   instead of holding a fixed 96px well. */
.rfield--locked textarea.rfield__control{
  field-sizing:content;resize:none;overflow:hidden;line-height:1.5;font-weight:600;
}
.rfield--locked .pillset{margin-top:0;flex:1;}
/* The pill shell is 32px tall, which would make the one pill row taller than
   every other static row. Frozen, it is just the chosen value as text. */
.rfield--locked .pill input:checked + span{height:auto;line-height:1.5;padding:0;border:0;background:transparent;}
.rfield--locked .rfield__hint{display:none;}

/* ---- The same row, for the admin and profile read-outs (.admin-field__value).
   Selected with :has() so the ten generated admin pages and the profile page
   need no markup change — the rule finds the read-only rows by the fact that
   they hold a value rather than a control. */
.admin-field:has(> .admin-field__value){
  position:relative;flex-direction:row;align-items:center;gap:12px;
  min-height:var(--px-static-field-h);padding:8px 0;
}
.admin-field:has(> .admin-field__value)::before,
.admin-field:has(> .admin-field__value)::after{
  content:"";position:absolute;left:0;right:0;height:1px;background:var(--praxis-color-border-default);
}
.admin-field:has(> .admin-field__value)::before{top:0;}
.admin-field:has(> .admin-field__value)::after{bottom:0;}
/* No overlap rule for the admin read-outs: on the pages that have them the
   static rows are either side by side in a grid or separated by another field,
   so their rules never meet. Two stacked in one column would want the same 1px
   overlap as the record rows. */
.admin-field:has(> .admin-field__value) .admin-field__label{
  /* Narrower gutter than the record pages': admin lays two fields to a row, so
     a fixed 200px would leave the value squeezed against the column edge. */
  width:min(200px, 45%);flex-shrink:0;
  display:flex;align-items:baseline;
  font-size:.875rem;line-height:1.5;
}
.admin-field:has(> .admin-field__value) .admin-field__value{
  flex:1;min-width:0;height:auto;font-size:.875rem;line-height:1.5;
}

/* ---- The summary card's description. It was a filled, inset-bordered well
   with a drag grip, which is the vocabulary of a textarea — so the one field on
   the card that cannot be edited looked like the only one that could. Now it is
   text in the value column like every other static field. Loaded after each
   record page's own stylesheet, so these win without !important.
   The See All control stays: the description is genuinely truncated. */
.summary__desc,
body[data-variant="praxis"] .summary__desc{
  background:transparent;box-shadow:none;padding:0;min-height:0;cursor:default;
  font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);
}
body[data-variant="praxis"] .summary__desc:focus-within{box-shadow:none;}
.summary__desc-grip{display:none;}
.summary__desc-foot{padding:2px 0 0;justify-content:flex-start;}
.summary__desc-seeall{padding:0;}

/* ============================== 2 · PICKLISTS AS REAL RADIOS ==============================
   The .toggle pills elsewhere are <button aria-checked> with nothing wired to
   them, so they look selectable and aren't. Same silhouette here, built on
   <input type="radio">: selection, keyboard navigation and screen-reader
   semantics all come free, and the group actually holds a value. */
.pillset{display:flex;gap:6px;flex-wrap:wrap;}
.pill{position:relative;display:inline-flex;}
/* Both record pages carry an older, unrelated .pill for status chips
   (.pill--open / --warn / --todo) that sets padding:0 10px and height:26px.
   It matches these radio labels too, which inset each visible pill 10px inside
   its own box — so Yes and No sat 26px apart while the gap said 6px, and the
   label box was 6px shorter than the pill inside it. Reset both, scoped to a
   .pillset so the status chips keep their own padding. */
.pillset .pill{padding:0;height:auto;}
.pill input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer;}
.pill span{display:inline-flex;align-items:center;height:32px;padding:0 16px;
  border:2px solid var(--praxis-color-interactive-default);border-radius:var(--praxis-radius-full);
  background:var(--px-surface);font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);
  white-space:nowrap;transition:background var(--praxis-motion-fast);}
.pill:hover span{background:var(--praxis-color-neutral-10);}
.pill input:checked + span{background:var(--praxis-color-teal-10);}
.pill input:focus-visible + span{outline:2px solid var(--praxis-color-interactive-default);outline-offset:2px;}
body[data-theme="dark"] .pill input:checked + span{background:rgba(41,210,215,.16);}
.rfield--locked .pillset{pointer-events:none;}
.rfield--locked .pill input:not(:checked) + span{display:none;}
.rfield--locked .pill input:checked + span{border-color:transparent;background:transparent;padding-left:0;}

/* ============================== 3 · REFERENCE FIELD ==============================
   A reference points at a row in another table, so free text is the wrong
   affordance: the value has to come from the configured list. The control is
   the same enclosure as .rfield__control with a lookup button on the trailing
   edge; the input filters the list as you type rather than accepting arbitrary
   text, and blur restores the last committed value. Options live in the
   markup (<div class="rref__opt">) so a page can configure its own list. */
.rref{position:relative;display:flex;}
.rref__input{flex:1;min-width:0;padding-right:44px;}
/* 44px wide with a 20px icon centred puts the lookup glyph 12px off the edge —
   the same inset as the select chevron and the field text, so every trailing
   control in a column of fields sits on one vertical line. The 1px offsets
   this had came from the border that is no longer there. */
.rref__btn{position:absolute;right:0;top:0;bottom:0;width:44px;display:flex;
  align-items:center;justify-content:center;border:0;border-radius:0 var(--praxis-radius-md) var(--praxis-radius-md) 0;
  background:transparent;color:var(--praxis-color-text-secondary);cursor:pointer;}
.rref__btn:hover{color:var(--praxis-color-text-primary);background:var(--praxis-color-neutral-10);}
.rref__btn:focus-visible{outline:2px solid var(--praxis-color-interactive-default);outline-offset:-2px;}
.rref__btn svg{width:20px;height:20px;fill:currentColor;}
.rref__menu{position:absolute;z-index:40;top:calc(100% + 4px);left:0;right:0;max-height:260px;
  overflow:auto;padding:6px;border-radius:var(--praxis-radius-md);background:var(--px-surface);
  box-shadow:0 0 0 1px rgba(16,36,58,.10),0 12px 28px -8px rgba(16,36,58,.28);}
body[data-theme="dark"] .rref__menu{box-shadow:0 0 0 1px rgba(255,255,255,.12),0 12px 28px -8px rgba(0,0,0,.6);}
.rref__menu[hidden]{display:none;}
.rref__opt{display:block;width:100%;text-align:left;padding:8px 10px;border:0;border-radius:var(--praxis-radius-sm);
  background:transparent;font:inherit;font-size:.875rem;color:var(--praxis-color-text-primary);cursor:pointer;}
.rref__opt:hover,.rref__opt[data-active]{background:var(--praxis-color-neutral-10);}
body[data-theme="dark"] .rref__opt:hover,
body[data-theme="dark"] .rref__opt[data-active]{background:rgba(255,255,255,.08);}
.rref__opt small{display:block;font-size:.75rem;color:var(--praxis-color-text-secondary);}
.rref__empty{padding:10px;font-size:.8125rem;color:var(--praxis-color-text-secondary);}
.rfield--locked .rref__btn{display:none;}

/* ============================== 4 · IN-RECORD TABLE ==============================
   Rows of related records inside a section (individuals, contractors, assets).
   Its own actions row, and an explicit empty state rather than a bare header —
   an empty table with no message reads as broken. */
/* White, not the grey field fill: a table of rows is content, not a control,
   and the grey read as one large input. Rows then zebra off it so a wide row
   stays trackable across the columns. */
.rtable{margin:4px 16px 0;border:1px solid var(--praxis-color-neutral-20);
  border-radius:var(--praxis-radius-md);overflow:hidden;background:var(--px-surface);}
.rtable__actions{display:flex;align-items:center;gap:var(--praxis-space-8);flex-wrap:wrap;
  padding:8px;border-bottom:1px solid var(--praxis-color-neutral-20);}
.rtable__action{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 10px;
  border:0;border-radius:var(--praxis-radius-md);background:transparent;
  font:inherit;font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-link);cursor:pointer;}
.rtable__action:hover{background:var(--praxis-color-neutral-10);}
.rtable__action:focus-visible{outline:2px solid var(--praxis-color-interactive-default);outline-offset:2px;}
.rtable__action svg{width:18px;height:18px;fill:currentColor;}
.rtable table{width:100%;border-collapse:collapse;}
.rtable th{text-align:left;padding:8px 12px;font-size:.75rem;font-weight:600;
  color:var(--praxis-color-text-secondary);background:var(--praxis-color-neutral-05);
  border-bottom:1px solid var(--praxis-color-neutral-20);white-space:nowrap;}
body[data-theme="dark"] .rtable th{background:rgba(255,255,255,.05);}
.rtable td{padding:10px 12px;font-size:.875rem;color:var(--praxis-color-text-primary);
  border-bottom:1px solid var(--praxis-color-neutral-20);vertical-align:middle;}
.rtable tr:last-child td{border-bottom:0;}
/* Zebra striping. nth-of-type so the count follows the rows as they are added
   and removed, and :not(.rtable__empty) so the "no data" row stays white —
   striping a single placeholder row would look like data. */
.rtable tbody tr:nth-of-type(even):not(.rtable__empty){background:var(--px-surface-2);}
body[data-theme="dark"] .rtable tbody tr:nth-of-type(even):not(.rtable__empty){background:rgba(255,255,255,.04);}
.rtable__empty td{padding:18px 12px;text-align:center;font-size:.875rem;
  color:var(--praxis-color-text-secondary);}
.rtable__rm{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;
  border:0;border-radius:var(--praxis-radius-sm);background:transparent;
  color:var(--praxis-color-text-secondary);cursor:pointer;}
.rtable__rm:hover{background:var(--praxis-color-neutral-10);color:var(--praxis-color-text-primary);}
.rtable__rm:focus-visible{outline:2px solid var(--praxis-color-interactive-default);outline-offset:2px;}
.rtable__rm svg{width:18px;height:18px;fill:currentColor;}
.rtable__cell-actions{width:44px;text-align:right;}

/* A table frozen by workflow step: the rows stay, the ways to change them go.
   Pairs with .rfield--locked so a passed step reads consistently. */
.rtable--locked{box-shadow:none;border-color:var(--praxis-color-neutral-15);}
.rtable--locked .rtable__actions,
.rtable--locked .rtable__rm{display:none;}

/* ============================== 5a · MAZLAN SECTION ACTION ==============================
   A section-level hand-off to Mazlan (Risk Analysis on Incident Review). Pill
   shaped and quiet so it reads as an offer next to the section title rather
   than competing with the toolbar's Submit. The dot mark comes from
   praxis-mazlan.css, so the page must link that too. */
.mazbtn{display:inline-flex;align-items:center;gap:8px;height:34px;padding:0 14px;margin:2px 16px 10px;
  border:0;border-radius:var(--praxis-radius-full);background:var(--px-surface-2);
  font:inherit;font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-primary);cursor:pointer;
  box-shadow:0 0 0 1px rgba(127,140,160,.18);
  transition:background var(--praxis-motion-fast);}
.mazbtn:hover{background:var(--px-chip);}
.mazbtn:focus-visible{outline:2px solid var(--praxis-color-interactive-default);outline-offset:2px;}
body[data-theme="dark"] .mazbtn{box-shadow:0 0 0 1px rgba(255,255,255,.10);}

/* ============================== 4a · SECTION HEADER ==============================
   The collapse control sits to the LEFT of the title: it is the affordance for
   the thing it precedes, and on a page where passed steps arrive collapsed the
   chevrons line up in a column you can run down, instead of being scattered at
   the right edge wherever each title happens to end. Set with order rather than
   by moving markup, so every page that already emits header-then-button gets it
   without an edit. */
/* Order across the header: teal tick, chevron, title, then any note. The tick
   was drawn as .section__title::before, i.e. inside the title, so nothing could
   be placed between the two. It moves to the header as its own flex item and
   the title's copy is switched off — same 3x14 mark, now an item the chevron can
   sit after. Explicit order on every child, because a flex item without one
   defaults to 0 and would jump ahead of the chevron. */
body[data-variant="praxis"] .section__header::before{
  content:"";order:0;flex:0 0 auto;width:3px;height:14px;border-radius:2px;
  background:var(--praxis-color-interactive-default);
}
body[data-variant="praxis"] .section__header .section__title::before{display:none;}
body[data-variant="praxis"] .section__header .section__title{padding-left:0;}
.section__header .chevron-btn{order:1;margin:0 2px 0 6px;}
.section__header .section__title{order:2;margin-right:auto;}

/* Rotates to point at the collapsed body. The pages' own
   .section.is-collapsed rule handles this too; repeated here so pages that only
   link this file still get it. */
.section.is-collapsed .section__header .chevron-btn svg{transform:rotate(-90deg);}
.section__header .chevron-btn svg{transition:transform var(--praxis-motion-fast);}

/* ============================== 5 · SUB-SECTION ==============================
   A titled run of fields inside a section (Action Items under First Report).
   Lighter than .section__title so the nesting is legible without a second
   collapsing control. */
.subsec{margin-top:var(--praxis-space-16);padding-top:var(--praxis-space-8);}
.subsec__title{padding:0 16px 2px;font-size:.9375rem;font-weight:600;
  color:var(--praxis-color-text-primary);}

@media (max-width:640px){
  .rfield__group{grid-template-columns:1fr;}
  .rtable{margin-inline:8px;}
  .rtable th,.rtable td{padding-inline:8px;}
}

/* ===========================================================================
   praxis-filters.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   Praxis Filters — shared filter system

   ORIGIN: v1.5, originally extracted from the Responsive Search project. As of
   2026-08-12 this file is OWNED HERE — do not re-extract over it. It was de-aliased
   onto the canonical token layer, so an upstream re-extraction would reintroduce the
   parallel vocabulary and silently revert the field treatment below.

   The centered filter modal (Standard + Custom in one tree), the quick-filter
   strip and the active-filter chip bar. Pair with praxis-filters.js:

     <link rel="stylesheet" href="praxis-filters.css" />
     <script src="praxis-filters.js"></script>

   Reads the canonical tokens directly (--praxis-* foundation, --px-* materials). The
   old scoping-to-filter-roots trick existed because search-page.html declares the same
   --s/--r/--t names for its Calendar module; with the aliases gone that collision
   cannot happen.
   ===================================================================== */

/* FIELD TREATMENT (2026-08-12): the text inputs here were the last surface still on the
   pre-2026-08-12 look — 1px neutral-20 border on --px-surface-2. They now take the same
   borderless --px-field treatment as .rfield__control / .admin-field__control, with the
   same inset focus ring and an AA-passing placeholder (was neutral-50). See §5A of the
   design-system reference. */

/* The v1.5 adapter block that used to live here is gone (2026-08-12): it declared a
   parallel --s/--r/--t/--d/--color/--shadow/--ease vocabulary and mapped it onto the
   Praxis layer. Every reference now names the canonical token directly, so this sheet
   reads from the same foundation as the rest of the app and there is no second
   vocabulary to keep in sync. */

/* Footer button variants the v1.5 modal adds on top of the host's .btn set.
   Scoped to the modal so they can't leak into a host page's own button scale. */
.filter-drawer .btn--neutral { background: var(--praxis-color-neutral-15); color: var(--praxis-color-neutral-90); }
.filter-drawer .btn--neutral:hover { background: var(--praxis-color-neutral-20); }
.filter-drawer .btn--neutral:active { background: var(--praxis-color-neutral-30); }
.filter-drawer .btn--clear { background: transparent; color: var(--praxis-color-red-60); border: 1px solid var(--praxis-color-red-60); }
.filter-drawer .btn--clear:hover { background: var(--praxis-color-red-10); }
.filter-drawer .btn--clear:active { background: var(--praxis-color-red-10); }

/* =====================================================================
   PRAXIS — footer buttons

   Clear and Reset take the same construction as the app's other buttons: a
   two-stop 180deg gradient, a hairline, a tight shadow, a coloured ambient
   glow and an inset top highlight — the shape of --px-primary-grad /
   --px-primary-shadow, in red for the destructive action and in neutral for
   Reset. In dark both switch to the soft-chip form the primaries use there,
   rather than a bright fill on a dark panel.

   The red stops are darker than a straight red-60 would be, on purpose.
   White on red-60 (#e22d38) is 4.39:1 and on a lighter top stop 3.93:1 —
   under AA. #d9303b → #bb2029 gives 4.73:1 and 6.27:1, so the label holds up
   across the whole gradient. (Worth noting --px-primary-grad's own top stop
   is 3.86:1 with white; this deliberately doesn't copy that.)

   Dark can't reuse --praxis-color-red-60: this file remaps it to #ff8b93 for
   dark because that value is meant to carry text ON a dark surface. As a
   fill it's a light salmon — white on it is 1.9:1 — so in dark it becomes
   the label on a deep red chip instead: 6.5:1.
   ===================================================================== */
body[data-variant="praxis"] .filter-drawer .btn--clear {
  border: 0; color: var(--praxis-color-white, #fff);
  background: linear-gradient(180deg, #d9303b, #bb2029);
  box-shadow: 0 0 0 .5px rgba(16,36,58,.2), 0 1px 2px rgba(16,36,58,.15),
              0 8px 18px -8px rgba(200,36,48,.5), inset 0 .5px 0 rgba(255,255,255,.28);
}
body[data-variant="praxis"] .filter-drawer .btn--clear:hover {
  color: var(--praxis-color-white, #fff); filter: brightness(1.06);
}
body[data-variant="praxis"] .filter-drawer .btn--clear:active { filter: brightness(.97); }

body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--clear {
  color: #ff8b93;
  background: linear-gradient(180deg, rgb(72,26,32) 0%, rgb(54,20,25) 100%);
  box-shadow: 0 0 0 .5px rgba(255,139,147,.30), 0 1px 2px rgba(0,0,0,.35),
              0 4px 10px -8px rgba(255,139,147,.40), inset 0 .5px 0 rgba(255,255,255,.08);
}
body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--clear:hover {
  color: #ffa4ab; filter: brightness(1.10);
}

/* Reset to Defaults — same construction, neutral. It restores displayed
   fields rather than destroying anything, so it carries no colour. */
body[data-variant="praxis"] .filter-drawer .btn--neutral {
  border: 0; color: var(--praxis-color-text-primary);
  background: linear-gradient(180deg, #ffffff, #eef1f5);
  box-shadow: 0 0 0 .5px rgba(16,36,58,.14), 0 1px 2px rgba(16,36,58,.10),
              0 2px 5px -2px rgba(16,36,58,.14);
}
body[data-variant="praxis"] .filter-drawer .btn--neutral:hover {
  background: linear-gradient(180deg, #ffffff, #e7ebf1);
  box-shadow: 0 0 0 .5px rgba(16,36,58,.18), 0 2px 8px -2px rgba(16,36,58,.18);
}
body[data-variant="praxis"] .filter-drawer .btn--neutral:active { filter: brightness(.98); }

body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--neutral {
  color: var(--praxis-color-text-primary);
  background: linear-gradient(180deg, var(--px-surface-2,#232c3b), rgb(28,36,49));
  box-shadow: 0 0 0 .5px rgba(255,255,255,.14), 0 1px 3px rgba(0,0,0,.5),
              inset 0 .5px 0 rgba(255,255,255,.10);
}
body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--neutral:hover {
  color: var(--praxis-color-white);
  background-image: linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  background-color: var(--px-surface-2,#232c3b);
  box-shadow: 0 0 0 .5px rgba(255,255,255,.20), 0 2px 6px rgba(0,0,0,.5), inset 0 .5px 0 rgba(255,255,255,.12);
}

/* Apply matches the toolbar's Run. In light they were already identical — both
   take the shared primary in praxis-core.css. Dark had them apart: that sheet
   gives toolbar primaries the soft chip but scopes it to
   :is(.toolbar, .rp-toolbar, .ws-toolbar, .admin-toolbar), and this footer is
   none of those, so Apply kept the bold cyan CTA.

   Note this reverses a deliberate call recorded in praxis-core.css — that
   in-dialog CTAs sit on their own surface, where the bold treatment still reads
   right. Matching Run was the explicit ask; revert this block to get the bold
   CTA back in dialogs. */
body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--primary {
  background: var(--px-primary-soft);
  color: var(--px-primary-soft-fg);
  box-shadow: var(--px-primary-soft-shadow-sm);
}
body[data-variant="praxis"][data-theme="dark"] .filter-drawer .btn--primary:hover {
  background: var(--px-primary-soft); filter: brightness(1.06);
}
.filter-drawer .btn:disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

/* ---------------------------------------------------------------------
   Ported rules
   --------------------------------------------------------------------- */

/* Zero-specificity resets: the host page has its own global .btn / .icon-btn /
   .chip and box model. :where() keeps these at specificity 0 so the ported rules
   below still win, while stopping host globals from leaking into the panel.

   .filter-controls belongs in these lists and was missing. It is one of the
   component roots — the token bridge above already scopes to it — but its
   buttons were getting no reset, so the Show/Hide Quick Filters toggle
   rendered as a raw UA <button>: buttonface grey fill and a 2px outset
   border. Against the flat page that reads as permanently hovered/pressed,
   and the real :hover (a teal wash) had nothing to contrast against.

   These stay HERE rather than being appended at the end of the file: the
   reset has to sit above the component rules, or `color: inherit` would
   clobber .filter-toggle's accent colour. */
.filter-drawer :where(button), .drawer-scrim :where(button), .select-menu :where(button),
.quick-filters :where(button), .filter-chips :where(button),
.filter-controls :where(button) { border: 0; background: transparent; font: inherit; color: inherit; cursor: pointer; }
.filter-drawer :where(*), .drawer-scrim :where(*), .select-menu :where(*),
.quick-filters :where(*), .filter-chips :where(*),
.filter-controls :where(*) { box-sizing: border-box; }

/* The modal's own button + icon-button scale, so the host's .btn (36px, calendar
   primitive) doesn't shrink the v1.5 footer. */
.filter-drawer .btn { display: inline-flex; align-items: center; gap: var(--praxis-space-8); padding: 0 var(--praxis-space-16); min-height: 40px; border-radius: var(--praxis-radius-md); font-size: var(--praxis-type-size-base); font-weight: 600; font-family: inherit; cursor: pointer; }
.filter-drawer .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: var(--praxis-radius-sm); color: var(--praxis-color-text-primary); }
.filter-drawer .icon-btn:hover { background: var(--px-chip); }
.filter-controls, .quick-filters, .filter-chips { flex-shrink: 0; }
.filter-controls { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--praxis-space-12); padding-bottom: var(--praxis-space-4); }
.filter-controls > .btn[data-action="open-filter-drawer"] { justify-self: start; }
.filter-controls > .filter-toggle { justify-self: center; }
.filter-toggle { display: inline-flex; align-items: center; gap: var(--praxis-space-4); color: var(--praxis-color-teal-60); font-size: var(--praxis-type-size-sm); font-weight: 500; padding: var(--praxis-space-4) var(--praxis-space-8); border-radius: var(--praxis-radius-sm); }
.filter-toggle:hover { background: var(--praxis-color-teal-10); }
.filter-toggle__caret { transition: transform var(--praxis-motion-fast) var(--praxis-ease-default); }
.filter-controls[data-collapsed] .filter-toggle__caret { transform: rotate(180deg); }
.segmented { display: inline-flex; border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); overflow: hidden; background: var(--px-surface); }
.segmented__opt { padding: 0 var(--praxis-space-16); height: 36px; font-size: var(--praxis-type-size-sm); color: var(--praxis-color-text-secondary); border-right: 1px solid var(--praxis-color-neutral-20); font-weight: 500; transition: background var(--praxis-motion-fast) var(--praxis-ease-default); }
.segmented__opt:last-child { border-right: 0px; }
.segmented__opt:hover { background: var(--px-chip); color: var(--praxis-color-text-primary); }
.segmented__opt--active { background: var(--praxis-color-neutral-90); color: rgb(255, 255, 255); }
.segmented__opt--active:hover { background: var(--praxis-color-neutral-80); color: rgb(255, 255, 255); }
.quick-filters { overflow: hidden; transition: max-height var(--praxis-motion-slow) var(--praxis-ease-default), opacity var(--praxis-motion-normal) var(--praxis-ease-default); max-height: 500px; }
.quick-filters[data-collapsed] { max-height: 0px; opacity: 0; }
.quick-filters__scroller { display: grid; grid-template-columns: repeat(5, minmax(0px, 1fr)); gap: var(--praxis-space-12); }
.qfilter { background: var(--px-surface); border: 1px solid var(--praxis-color-border-default); border-radius: var(--praxis-radius-md); padding: var(--praxis-space-12); display: flex; flex-direction: column; gap: var(--praxis-space-8); min-width: 0px; }
.qfilter__head { display: flex; align-items: center; gap: var(--praxis-space-8); }
.qfilter__title { font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-text-primary); flex: 1 1 0%; min-width: 0px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qfilter__controls { display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.qfilter__list { display: flex; flex-direction: column; gap: 2px; }
.qfilter__row { display: flex; align-items: center; gap: var(--praxis-space-8); font-size: var(--praxis-type-size-xs); padding: var(--praxis-space-4); color: var(--praxis-color-text-primary); border-radius: var(--praxis-radius-sm); cursor: pointer; transition: background var(--praxis-motion-fast) var(--praxis-ease-default); }
.qfilter__row:hover { background: var(--praxis-color-neutral-05); }
.qfilter__check {width: 16px; height: 16px; flex-shrink: 0; border: 2px solid var(--praxis-color-neutral-30); border-radius: 4px; background: var(--px-surface-2); color: rgb(255, 255, 255); display: grid; place-items: center; transition: background var(--praxis-motion-fast) var(--praxis-ease-default), border-color var(--praxis-motion-fast) var(--praxis-ease-default); }
.qfilter__check .material-symbols-rounded { font-size: 12px; }
.qfilter__row-label { min-width: 0px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.qfilter__row--selected { color: var(--praxis-color-neutral-100); font-weight: 600; }
.qfilter__row--selected .qfilter__check { background: var(--praxis-color-teal-60); border-color: var(--praxis-color-teal-60); }
.qfilter__more { font-size: var(--praxis-type-size-xs); color: var(--praxis-color-teal-60); font-weight: 600; text-align: center; padding: var(--praxis-space-4) 0; margin-top: auto; }
.qfilter__more:hover { text-decoration: underline; }
.filter-chips { display: flex; flex-wrap: wrap; gap: var(--praxis-space-8); padding: var(--praxis-space-8) 0 var(--praxis-space-12); }
/* High-contrast filter chips. Light mode: light fill + dark text/stroke.
   Dark mode: inverse — dark fill + light text/stroke. */
/* Same fixed-height trap as the status pill. A chip carrying a multi-value
   filter ("Assigned To: is Alexandria Gilbert, Safety Review Team, …") does
   wrap to a second line once the row is narrower than about 900px — but
   height:40px held the outline at one line, so the second line sat outside it.
   min-height lets the outline follow the text. Measured at 834 and 700px:
   clipped before, 52px and intact after. */
.filter-chips .chip { display: inline-flex; align-items: center; gap: var(--praxis-space-8); padding: var(--praxis-space-4) var(--praxis-space-16); min-height: 40px; max-width: 100%; text-align: left; background: var(--px-surface); border: 1px solid var(--praxis-color-neutral-90); border-radius: var(--praxis-radius-full); font-size: var(--praxis-type-size-base); font-weight: 600; font-family: inherit; color: var(--praxis-color-neutral-90); transition: background var(--praxis-motion-fast) var(--praxis-ease-default); }
/* The X never shrinks or wraps away from the label it removes. */
.filter-chips .chip__close { flex-shrink: 0; }
.filter-chips .chip:hover { background: var(--praxis-color-neutral-05); }
body[data-theme="dark"] .filter-chips .chip { background: var(--praxis-color-neutral-90); border-color: rgba(255,255,255,.7); color: #fff; }
body[data-theme="dark"] .filter-chips .chip:hover { background: var(--praxis-color-neutral-80); }
.filter-chips .chip strong { color: inherit; font-weight: 600; margin-left: 2px; }
.filter-chips .chip__close { width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; color: inherit; font-size: 24px !important; }
/* Clear all reads as a destructive tool button rather than an outlined chip:
   soft translucent red over the page, a subtle vertical gradient, a lit top edge
   and the same elevation as the toolbar buttons. */
.filter-chips .chip--clear {
  padding: 0 var(--praxis-space-16);
  color: var(--praxis-color-red-60);
  border: 0;
  background: linear-gradient(180deg, rgba(226, 45, 56, .09), rgba(226, 45, 56, .17));
  box-shadow: var(--px-tool-shadow), inset 0 .5px 0 rgba(255, 255, 255, .6);
  transition: background var(--praxis-motion-fast) var(--praxis-ease-default), box-shadow var(--praxis-motion-fast) var(--praxis-ease-default);
}
.filter-chips .chip--clear:hover {
  background: linear-gradient(180deg, rgba(226, 45, 56, .15), rgba(226, 45, 56, .24));
  box-shadow: var(--px-tool-shadow-hover), inset 0 .5px 0 rgba(255, 255, 255, .6);
}
.filter-chips .chip--clear:active { background: linear-gradient(180deg, rgba(226, 45, 56, .2), rgba(226, 45, 56, .28)); }
/* The bar's generic .chip__close is 24px; Clear all's leading × is a label
   glyph rather than a dismiss target, so it sits a size down. */
.filter-chips .chip--clear .material-symbols-rounded,
.filter-chips .chip--clear svg {
  /* inline-flex, not inline: width/height are ignored on a non-replaced inline
     box, so the glyph kept its 24px em square. Covers the SVG too — the Lucide
     converter swaps these ligatures at runtime. */
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; min-width: 20px;
  font-size: 20px !important; line-height: 1;
}
/* Dark chip contrast is handled by the theme-inverse rule further up (fill
   neutral-90, 70% white border, #fff label — 11.98:1). Worth recording why
   that rule matters beyond looks: praxis-core remaps --praxis-color-neutral-10
   (the old chip fill) to #222b39 but leaves --praxis-color-neutral-90 (the
   label) at #2a3846, so an active chip previously rendered dark-on-dark at
   1.19:1 — invisible, and against the project's hard WCAG 2.2 AA
   requirement. Inverting the fill fixes that at the same time. Don't
   reintroduce a colour-only override here; it would fight that rule. */

/* Dark: the lit edge flips to white-on-dark and the tint carries more weight,
   since a 9% red over a navy surface all but disappears. */
body[data-theme="dark"] .filter-chips .chip--clear {
  color: #ff8b93;
  border-color: rgba(255, 139, 147, .28);
  background: linear-gradient(180deg, rgba(255, 139, 147, .12), rgba(255, 139, 147, .2));
  box-shadow: var(--px-tool-shadow), inset 0 .5px 0 rgba(255, 255, 255, .08);
}
body[data-theme="dark"] .filter-chips .chip--clear:hover {
  background: linear-gradient(180deg, rgba(255, 139, 147, .18), rgba(255, 139, 147, .28));
  box-shadow: var(--px-tool-shadow-hover), inset 0 .5px 0 rgba(255, 255, 255, .08);
}
/* (was a 24px re-declaration here — the size now lives in the block above) */
.drawer-scrim { position: fixed; inset: 0px; background: rgba(32, 42, 53, 0.5); z-index: 80; opacity: 0; transition: opacity var(--praxis-motion-normal) var(--praxis-ease-default); }
.drawer-scrim[data-open] { opacity: 1; }
.filter-drawer { position: fixed; top: var(--praxis-space-24); right: var(--praxis-space-24); bottom: var(--praxis-space-24); width: min(480px, calc(100% - 2 * var(--praxis-space-24))); background: var(--px-surface); z-index: 90; display: flex; flex-direction: column; transform: translateX(calc(100% + var(--praxis-space-24))); transition: transform var(--praxis-motion-slow) var(--praxis-ease-default); box-shadow: rgba(32, 42, 53, 0.24) 0px 24px 64px, rgba(32, 42, 53, 0.12) 0px 8px 24px; border-radius: 20px; overflow: hidden; }
.filter-drawer[hidden] { display: none; }
.filter-drawer[data-open] { transform: none; }
@media (min-width: 1280px) {
  html[data-filter-mode="modal"] .filter-drawer { left: 0px; right: 0px; margin-inline: auto; width: min(960px, -96px + 100vw); height: fit-content; max-height: calc(100vh - 48px); margin-block: auto; transform: translateY(16px); opacity: 0; transition: transform var(--praxis-motion-normal) var(--praxis-ease-default),
                opacity var(--praxis-motion-normal) var(--praxis-ease-default); }
  html[data-filter-mode="modal"] .filter-drawer[data-open] { transform: none; opacity: 1; }
  html[data-filter-mode="modal"] .filter-drawer__resize { display: block; }
}

.filter-drawer__resize { display: none; position: absolute; left: 0px; right: 0px; height: 10px; cursor: ns-resize; z-index: 6; touch-action: none; }
.filter-drawer__resize--top { top: 0px; }
.filter-drawer__resize--bottom { bottom: 0px; }
.filter-drawer__head { display: flex; align-items: center; justify-content: space-between; padding: var(--praxis-space-16) var(--praxis-space-16) var(--praxis-space-4); }
.filter-drawer__head h2 { font-size: var(--praxis-type-size-xl); font-weight: 700; color: var(--praxis-color-neutral-90); }
.filter-drawer__toolbar { position: relative; display: flex; flex-direction: column; align-items: stretch; gap: var(--praxis-space-8); padding: var(--praxis-space-4) var(--praxis-space-16) var(--praxis-space-16); }
.filter-drawer__toolbar::after { content: ""; position: absolute; left: var(--praxis-space-16); right: var(--praxis-space-16); bottom: 0px; height: 1px; background: var(--praxis-color-neutral-20); }
.filter-toolbar-row { display: flex; align-items: center; gap: var(--praxis-space-8); width: 100%; min-width: 0px; flex: 1 1 auto; }
.filter-search-input { position: relative; width: 100%; max-width: 400px; min-width: 0px; height: 40px; }
.filter-toolbar-row .filter-search-input { flex: 1 1 auto; max-width: none; }
/* --px-field (#EEF1F4) is a step off white — legible recessed one level
   inside a --px-surface card, but the toolbar row places this field
   directly on --px-surface-2/--px-page, and the three are close enough (a
   few units per channel) that the field all but disappears. Give it the
   surface it's missing plus the shadow every other toolbar control already
   uses to lift off the band under it, rather than a darker field token. */
.filter-toolbar-row .filter-search-input input {
  background-color: var(--px-surface);
  box-shadow: var(--px-tool-shadow);
}
/* Needed alongside the rule above: an unscoped :hover would lose to the
   base surface override by specificity, and the field would stop reacting
   on hover inside this toolbar-row context. */
.filter-toolbar-row .filter-search-input input:hover {
  background-color: var(--px-surface);
  background-image: linear-gradient(0deg, var(--px-hover-btn), var(--px-hover-btn));
  box-shadow: var(--px-tool-shadow-hover);
}
/* Same reason as :hover above: without restating it here, the unscoped
   :focus ring's box-shadow would lose to this scope's own (0,2,1) box-shadow
   declarations by specificity and the focus ring would disappear. */
.filter-toolbar-row .filter-search-input input:focus {
  background-color: var(--px-surface);
  box-shadow: inset 0 0 0 1px var(--praxis-color-interactive-default),
    0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent);
}
.filter-type-select { flex: 0 0 auto; min-width: 132px; font-size: var(--praxis-type-size-sm); }
.filter-type-select.is-active { border-color: var(--praxis-color-teal-50); background: var(--praxis-color-teal-10); color: var(--praxis-color-teal-80); }
.filter-type-select.is-active .material-symbols-rounded { color: var(--praxis-color-teal-70); }
.filter-search-input input { width: 100%; height: 40px; padding: 0 40px 0 var(--praxis-space-12); border: 0; border-radius: var(--praxis-radius-md); background-color: var(--px-field); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-text-primary); outline: none; }
.filter-search-input input:hover { background-color: var(--px-field-hover); }
.cf-input:hover { background-color: var(--px-field-hover); }
.filter-search-input input::placeholder { color: var(--praxis-color-text-secondary); font-weight: 500; opacity: 1; }
.filter-search-input input:focus { box-shadow: inset 0 0 0 1px var(--praxis-color-interactive-default),
    0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent); }
.filter-search-input .material-symbols-rounded { position: absolute; right: var(--praxis-space-12); top: 50%; transform: translateY(-50%); color: var(--praxis-color-neutral-60); pointer-events: none; font-size: 20px; }
.filter-type-buttons { display: flex; width: 100%; max-width: 400px; border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); height: 40px; flex-shrink: 0; }
.filter-type-btn:first-child { border-radius: calc(var(--praxis-radius-md) - 1px) 0 0 calc(var(--praxis-radius-md) - 1px); }
.filter-type-btn:last-child { border-radius: 0 calc(var(--praxis-radius-md) - 1px) calc(var(--praxis-radius-md) - 1px) 0; }
.filter-type-btn {flex: 1 1 0%; height: 100%; display: inline-flex; align-items: center; justify-content: center; background: var(--px-surface-2); color: var(--praxis-color-neutral-90); border-right: 1px solid var(--praxis-color-neutral-20); transition: background var(--praxis-motion-fast) var(--praxis-ease-default); }
.filter-type-btn:last-child { border-right: 0px; }
.filter-type-btn:hover { background: var(--praxis-color-neutral-10); }
.filter-type-btn[aria-pressed="true"] { background: var(--praxis-color-teal-10); color: var(--praxis-color-teal-80); }
.filter-type-btn .material-symbols-rounded { font-size: 20px; }
.filter-drawer__main { flex: 1 1 0%; min-height: 0px; display: flex; flex-direction: column; }
.filter-drawer__body { flex: 1 1 0%; overflow-y: auto; padding: 0 var(--praxis-space-16) var(--praxis-space-16); display: flex; flex-direction: column; gap: 0px; }
.filter-drawer__body [data-filter-list] { display: flex; flex-direction: column; gap: 0px; }
.filter-row { display: flex; flex-direction: column; gap: 0px; padding: var(--praxis-space-16) 0; border-bottom: 1px solid var(--praxis-color-neutral-20); }
.filter-row[hidden] { display: none; }
.filter-more[hidden] { display: none; }
.filter-drawer__body [data-filter-list] .filter-row:last-child { border-bottom: 0px; }
.segmented--lg .segmented__opt { height: 40px; }
.filter-row__controls .segmented { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.filter-row__controls .segmented__opt { justify-content: center; }
.filter-row__head { display: flex; align-items: center; gap: var(--praxis-space-8); }
/* No enclosure. The glyph used to sit in a filled, fully-rounded chip
   (neutral-05 at 12px radius on a 20px box), which read as a coloured circle —
   near-white in light mode, a distinctly dark disc in dark mode — competing
   with the chevron beside it for the same job. The glyph now stands on its own.
   Box is 20px because that is the size the icons actually render at: the 40px
   here never applied, since praxis-lucide.js replaces the ligature with an
   <svg class="icon icon--20"> whose own rule wins. Stating it explicitly keeps
   any span-rendered glyph (one that never got converted) the same size rather
   than leaving a 40px transparent gap. */
.filter-row__icon { display: grid; place-items: center; width: 20px; height: 20px; color: var(--praxis-color-neutral-90); font-size: 20px; flex-shrink: 0; }
.filter-row__title { font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-neutral-90); margin: 0px; }
.filter-row__toggle { flex: 1 1 0%; display: flex; align-items: center; gap: var(--praxis-space-8); min-width: 0px; border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; padding: 0px; font: inherit; text-align: left; cursor: pointer; }
.filter-row__caret { font-size: 20px; color: var(--praxis-color-neutral-50); flex-shrink: 0; transition: color var(--praxis-motion-fast) var(--praxis-ease-default), transform var(--praxis-motion-normal) var(--praxis-ease-default); }
.filter-row__toggle:hover .filter-row__caret { color: var(--praxis-color-neutral-90); }
.filter-row__toggle[aria-expanded="true"] .filter-row__caret, .filter-more__head[aria-expanded="true"] .filter-row__caret { transform: rotate(90deg); }
.filter-row__fav, .filter-row__quick { display: grid; place-items: center; width: 38px; height: 38px; border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; border-radius: var(--praxis-radius-sm); color: var(--praxis-color-neutral-30); cursor: pointer; flex-shrink: 0; }
.filter-row__fav:hover { background: var(--praxis-color-neutral-10); color: var(--praxis-color-pink-60, #E30072); }
.filter-row__quick:hover { background: var(--praxis-color-neutral-10); color: var(--praxis-color-pink-60, #E30072); }
.filter-row__fav .material-symbols-rounded, .filter-row__quick .material-symbols-rounded { font-size: 23px; }
.filter-row__fav.is-on { color: var(--praxis-color-pink-60, #E30072); }
.filter-row__quick.is-on { color: var(--praxis-color-pink-60, #E30072); }
.filter-row__qf-badge { display: inline-flex; align-items: center; gap: 4px; height: 24px; padding: 0 var(--praxis-space-8); border-radius: var(--praxis-radius-full); background: var(--praxis-color-pink-10, #FDE6F1); color: var(--praxis-color-pink-60, #E30072); font-size: var(--praxis-type-size-xs); font-weight: 700; white-space: nowrap; flex-shrink: 0; }
.filter-row--locked .filter-row__toggle { cursor: default; }
.filter-row--locked .filter-row__caret { visibility: hidden; }
.filter-row--locked .filter-row__title { color: var(--praxis-color-neutral-60); }
.filter-row__chips { display: flex; flex-wrap: wrap; gap: var(--praxis-space-8); margin-top: var(--praxis-space-8); margin-left: calc(20px + var(--praxis-space-8)); }
.filter-row__chips[hidden] { display: none; }
.frow-chip { display: inline-flex; align-items: center; gap: var(--praxis-space-4); padding: 3px 4px 3px var(--praxis-space-12); border-radius: var(--praxis-radius-full); background: var(--praxis-color-neutral-10); border: 1px solid var(--praxis-color-neutral-20); color: var(--praxis-color-neutral-80); font-size: var(--praxis-type-size-sm); font-weight: 600; }
.frow-chip__x { display: grid; place-items: center; width: 22px; height: 22px; border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; border-radius: 50%; color: var(--praxis-color-neutral-50); cursor: pointer; }
.frow-chip__x:hover { background: var(--praxis-color-neutral-20); color: var(--praxis-color-neutral-90); }
.frow-chip__x .material-symbols-rounded { font-size: 16px; }
/* minmax(0, 0fr), not 0fr: an fr track's implicit minimum is min-content, and
   .filter-row__controls carries padding-top:8px, which min-height:0 cannot
   remove — so a collapsed row's track floored at 8px instead of 0. That 8px sat
   below the header inside the row's 16px padding band, leaving the collapsed
   content visibly high in its container (12px above, 21px below). Flooring the
   track at 0 lets it close completely, so the header centres. The open state
   keeps its auto minimum so the controls still size to content. */
.filter-row__body { display: grid; grid-template-rows: minmax(0, 0fr); overflow: hidden; transition: grid-template-rows var(--praxis-motion-normal) var(--praxis-ease-default); }
.filter-row__body.is-open { grid-template-rows: 1fr; }
.filter-row__body > .filter-row__controls { min-height: 0px; overflow: hidden; opacity: 0; transition: opacity var(--praxis-motion-normal) var(--praxis-ease-default); }
.filter-row__body.is-open > .filter-row__controls { opacity: 1; }
.filter-row__controls { display: flex; flex-direction: column; align-items: stretch; gap: var(--praxis-space-8); padding: var(--praxis-space-8) 0 0 calc(20px + var(--praxis-space-8)); }
.filter-row__controls > * { width: 100%; }
.filter-row__value { min-width: 0px; display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.filter-row__value > * { width: 100%; }
.filter-row__value:empty { display: none; }
.filter-date-range { display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.filter-row__body > .filter-row__conditions { min-height: 0px; overflow: hidden; opacity: 0; transition: opacity var(--praxis-motion-normal) var(--praxis-ease-default); }
.filter-row__body.is-open > .filter-row__conditions { opacity: 1; }
.filter-row__conditions { display: flex; flex-direction: column; gap: var(--praxis-space-8); padding: var(--praxis-space-8) 0 0 calc(20px + var(--praxis-space-8)); }
.filter-cond { display: grid; grid-template-columns: minmax(120px, 0.8fr) minmax(150px, 1fr) auto; gap: var(--praxis-space-8); align-items: start; }
.filter-cond + .filter-cond { border-top: 1px dashed var(--praxis-color-neutral-20); padding-top: var(--praxis-space-8); }
.filter-cond .cf-value { min-width: 0px; }
.filter-row__add-cond { align-self: flex-start; display: inline-flex; align-items: center; gap: 4px; margin-top: 2px; height: 32px; padding: 0 var(--praxis-space-12) 0 var(--praxis-space-8); border: 1px dashed var(--praxis-color-neutral-30); border-radius: var(--praxis-radius-sm); background: transparent; color: var(--praxis-color-teal-70); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-sm); font-weight: 600; cursor: pointer; transition: background var(--praxis-motion-fast) var(--praxis-ease-default), border-color var(--praxis-motion-fast) var(--praxis-ease-default); }
.filter-row__add-cond:hover { background: var(--praxis-color-teal-10); border-color: var(--praxis-color-teal-40); }
.filter-row__add-cond .material-symbols-rounded { font-size: 18px; }
@media (max-width: 639px) {
  .filter-cond { grid-template-columns: 1fr auto; }
  .filter-cond .cf-value { grid-column: 1 / -1; }
}

.filter-more { padding: var(--praxis-space-16) 0; }
.filter-more__head { width: 100%; display: flex; align-items: center; gap: var(--praxis-space-8); border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; padding: 0px; font: inherit; text-align: left; cursor: pointer; }
.filter-more__count { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0px; border-radius: var(--praxis-radius-full); background: var(--praxis-color-neutral-10); color: var(--praxis-color-neutral-70); font-size: var(--praxis-type-size-xs); font-weight: 700; flex-shrink: 0; }
.filter-more__body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--praxis-motion-slow) var(--praxis-ease-default); }
.filter-more.is-open .filter-more__body { grid-template-rows: 1fr; }
.filter-more__list { min-height: 0px; overflow: hidden; }
.filter-more__list .filter-row:first-child { margin-top: var(--praxis-space-8); border-top: 1px solid var(--praxis-color-neutral-20); }
.filter-more__list .filter-row:last-child { border-bottom: 0px; }
.filter-more__empty { padding: var(--praxis-space-12) 0 0; color: var(--praxis-color-neutral-50); font-size: var(--praxis-type-size-sm); }
.filter-select, .filter-date { display: flex; align-items: center; justify-content: space-between; gap: var(--praxis-space-8); height: 40px; padding: 0 var(--praxis-space-12); background: var(--px-surface-2); border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-neutral-90); text-align: left; cursor: pointer; min-width: 0px; }
.filter-select:hover, .filter-date:hover { background: var(--praxis-color-neutral-05); border-color: var(--praxis-color-neutral-30); }
.filter-select:focus-visible, .filter-date:focus-visible { outline: 2px solid var(--praxis-color-teal-50); outline-offset: 2px; }
.filter-select > span:first-child, .filter-date > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-select .material-symbols-rounded, .filter-date .material-symbols-rounded { color: var(--praxis-color-neutral-60); font-size: 20px; flex-shrink: 0; }
.filter-search { position: relative; height: 40px; min-width: 0px; }
.filter-search input { width: 100%; height: 40px; padding: 0 40px 0 var(--praxis-space-12); border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); background: var(--px-surface-2); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-neutral-90); outline: none; }
.filter-search input::placeholder { color: var(--praxis-color-neutral-50); font-weight: 500; }
.filter-search input:focus { border-color: var(--praxis-color-teal-50); box-shadow: 0 0 0 2px var(--praxis-color-teal-50); }
.filter-search .material-symbols-rounded { position: absolute; right: var(--praxis-space-12); top: 50%; transform: translateY(-50%); color: var(--praxis-color-neutral-60); pointer-events: none; font-size: 20px; }
.select-menu { background: var(--px-surface); border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); box-shadow: var(--px-overlay); padding: var(--praxis-space-4); max-height: 320px; overflow-y: auto; z-index: 200; }
.select-menu__opt { display: flex; align-items: center; width: 100%; padding: var(--praxis-space-8) var(--praxis-space-12); background: none; border: 0px; text-align: left; font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-base); font-weight: 600; color: var(--praxis-color-neutral-90); border-radius: var(--praxis-radius-sm); cursor: pointer; white-space: nowrap; }
.select-menu__opt:hover { background: var(--praxis-color-neutral-10); }
.select-menu__opt--active { background: var(--praxis-color-teal-10); color: var(--praxis-color-teal-80); }
.filter-date { position: relative; }
.filter-date__input { position: absolute; inset: 0px; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
.onoff { display: inline-flex; align-items: center; gap: var(--praxis-space-8); cursor: pointer; justify-self: start; }
.onoff__label { font-size: var(--praxis-type-size-sm); font-weight: 600; color: var(--praxis-color-neutral-60); }
.onoff:has(.switch:checked) .onoff__label--on { color: var(--praxis-color-neutral-90); }
.onoff:not(:has(.switch:checked)) .onoff__label--off { color: var(--praxis-color-neutral-90); }
.filter-drawer__foot { display: flex; justify-content: flex-end; gap: var(--praxis-space-8); padding: var(--praxis-space-16); background: var(--praxis-color-neutral-10); border-top: 1px solid var(--praxis-color-neutral-20); box-shadow: rgba(32, 42, 53, 0.06) 0px -8px 16px, rgba(32, 42, 53, 0.04) 0px -2px 4px; position: relative; z-index: 1; }
.filter-drawer__foot .btn { flex: 1 1 auto; max-width: 150px; justify-content: center; white-space: nowrap; }
.filter-drawer__foot [data-restore-defaults] { margin-right: auto; flex: 0 0 auto; max-width: none; }
.filter-drawer__foot [data-restore-defaults][hidden] { display: none; }
.filter-drawer__foot [data-cf-clear-btn][hidden] { display: none; }
.filter-drawer__foot [data-action="close-filter-drawer"][hidden] { display: none; }
@media (max-width: 1399px) {
  .filter-controls { grid-template-columns: 1fr; }
  .filter-controls > .btn[data-action="open-filter-drawer"] { justify-self: start; }
}

/* LOCAL DELETION: `.filter-controls > .filter-toggle, .quick-filters` used to
   be hidden here from 1399px down. That handover now keys off
   body.tb-is-compact and lives in praxis-filters-local.css, so it survives a
   re-extract of this ported file. */

@media (max-width: 639px) {
  .filter-controls { grid-template-columns: 1fr; gap: var(--praxis-space-8); padding-bottom: var(--praxis-space-8); }
  .filter-controls > .btn[data-action="add-filter"], .filter-controls > .filter-toggle { justify-self: stretch; }
  .segmented { flex-shrink: 0; }
  .segmented__opt { padding: 0 var(--praxis-space-12); height: 32px; font-size: var(--praxis-type-size-xs); }
  .quick-filters { display: none; }
  .filter-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: var(--praxis-space-8) 0; }
  .filter-chips::-webkit-scrollbar { display: none; }
  /* This is a scroll strip, not a wrapping row: chips keep their natural width
     and you swipe. max-width:100% from the base rule would make a long chip
     wrap to two lines instead of extending into the scroller, so it's lifted
     here — the wrapping behaviour is only wanted where the row actually wraps. */
  .filter-chips .chip { flex-shrink: 0; max-width: none; }
}

.quick-filters[hidden] { display: none !important; }
.filter-controls .filter-toggle[hidden] { display: none !important; }
.select-menu--multi { padding: 0px; display: flex; flex-direction: column; max-height: 340px; overflow: hidden; }
.select-menu__search { display: flex; align-items: center; gap: var(--praxis-space-8); padding: var(--praxis-space-8) var(--praxis-space-12); border-bottom: 1px solid var(--praxis-color-neutral-15); flex-shrink: 0; }
.select-menu__search .material-symbols-rounded { font-size: 18px; color: var(--praxis-color-neutral-50); }
.select-menu__search input { border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; outline: none; flex: 1 1 0%; min-width: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-sm); background: transparent; color: var(--praxis-color-neutral-90); }
.select-menu__search input::placeholder { color: var(--praxis-color-neutral-50); }
.select-menu__list { overflow-y: auto; padding: var(--praxis-space-4); }
.select-menu--multi .select-menu__opt { display: flex; align-items: center; gap: var(--praxis-space-8); width: 100%; text-align: left; }
.select-menu__check {width: 18px; height: 18px; border: 2px solid var(--praxis-color-neutral-30); border-radius: 4px; background: var(--px-surface-2); color: rgb(255, 255, 255); display: grid; place-items: center; flex-shrink: 0; transition: background var(--praxis-motion-fast) var(--praxis-ease-default), border-color var(--praxis-motion-fast) var(--praxis-ease-default); }
.select-menu__check .material-symbols-rounded { font-size: 14px; }
.select-menu__opt.is-checked .select-menu__check { background: var(--praxis-color-teal-60); border-color: var(--praxis-color-teal-60); }
.select-menu__empty { padding: var(--praxis-space-12); text-align: center; color: var(--praxis-color-neutral-50); font-size: var(--praxis-type-size-sm); }
.select-menu--tree { min-width: 260px; }
.select-menu__group-row { display: flex; align-items: center; }
.select-menu__group-row .select-menu__opt { flex: 1 1 0%; min-width: 0px; }
.select-menu__twist { width: 24px; height: 24px; flex-shrink: 0; display: grid; place-items: center; border-radius: var(--praxis-radius-sm); color: var(--praxis-color-neutral-60); transition: background var(--praxis-motion-fast) var(--praxis-ease-default); }
.select-menu__twist:hover { background: var(--praxis-color-neutral-10); }
.select-menu__twist--blank { pointer-events: none; }
.select-menu__twist .material-symbols-rounded { font-size: 18px; transition: transform var(--praxis-motion-fast) var(--praxis-ease-default); }
.select-menu__group.is-expanded .select-menu__twist .material-symbols-rounded { transform: rotate(90deg); }
.select-menu__kids { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--praxis-motion-normal) var(--praxis-ease-default); }
.select-menu__kids > div { overflow: hidden; min-width: 0px; }
.select-menu__group.is-expanded .select-menu__kids { grid-template-rows: 1fr; }
.select-menu__opt--child { padding-left: 46px; }
.select-menu__opt.is-mixed .select-menu__check { background: var(--praxis-color-teal-60); border-color: var(--praxis-color-teal-60); }
@media (min-width: 1280px) {
  .filter-row, .filter-more { padding: var(--praxis-space-12) 0; }
  .filter-row__controls { display: grid; grid-template-columns: minmax(0px, 0.6fr) minmax(0px, 1fr) minmax(0px, 0.4fr); align-items: start; }
  html[data-filter-mode="modal"] .filter-drawer__toolbar { flex-direction: row; align-items: center; }
}

.filter-type-btn { position: relative; }
.filter-type-btn::after { content: attr(data-tip); position: absolute; top: calc(100% + 6px); left: 50%; translate: -50%; z-index: 5; padding: 4px 8px; border-radius: var(--praxis-radius-sm, 6px); background: var(--praxis-color-neutral-90); color: rgb(255, 255, 255); font-size: var(--praxis-type-size-xs); font-weight: 600; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity var(--praxis-motion-fast) var(--praxis-ease-default) .25s; }
.filter-type-btn:hover::after, .filter-type-btn:focus-visible::after { opacity: 1; }
.filter-view-switch { display: inline-flex; align-self: stretch; margin-left: auto; gap: 2px; padding: 3px; border: 1px solid var(--praxis-color-neutral-20); border-radius: var(--praxis-radius-md); background: var(--praxis-color-neutral-05, #f3f4f6); flex-shrink: 0; }
.filter-view-switch__opt { display: inline-flex; align-items: center; justify-content: center; gap: var(--praxis-space-8); height: 34px; padding: 0 var(--praxis-space-16); border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; border-radius: calc(var(--praxis-radius-md) - 3px); background: transparent; color: var(--praxis-color-neutral-70); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-sm); font-weight: 600; cursor: pointer; transition: background var(--praxis-motion-fast) var(--praxis-ease-default), color var(--praxis-motion-fast) var(--praxis-ease-default); }
.filter-view-switch__opt:hover { color: var(--praxis-color-neutral-90); }
.filter-view-switch__opt.is-active { background: var(--praxis-color-teal-60); color: rgb(255, 255, 255); box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 2px; }
.filter-view-switch__opt.is-active:hover { color: rgb(255, 255, 255); }
.filter-view-switch__opt .material-symbols-rounded { font-size: 18px; }

/* =====================================================================
   PRAXIS — Standard / Custom takes the workspace segmented switch:
   .segswitch in index.html, the control the Assignments and Recently Created
   Items cards use (.asg__tabs / .rci__range are only size modifiers on it).
   Values are copied from there so the two read as one component.

   An earlier pass here matched the Create New flyout's .cn-seg instead. That
   was the wrong reference: .cn-seg is a flat chip track with a single flat
   shadow, while the workspace switch is a RECESSED groove — inset shadow plus
   hairline — with the selected option raised on a ring and two drop shadows.
   Same silhouette, visibly different material.

   Geometry is untouched (34px options, 16px padding, 18px icons); only the
   surface treatment moves.

   The selected label is plain ink: the lifted pill and its shadow already carry
   the state, so colour doesn't need to repeat it, and ink keeps the label from
   reading as a link. .cn-seg was moved to match, so this is now one treatment
   across all four segmented controls in the system.

   Specificity: further down this file there is a dark-mode colour correction
   for the old teal fill — body[data-theme="dark"] .filter-view-switch__opt
   .is-active, which sets --px-primary-fg (#08313a, near-black teal, chosen to
   sit on a saturated teal button). A [data-variant] prefix does NOT beat it:
   both compute to (0,3,1), so the later one wins and the label came out
   #08313a on the #192336 pill — 1.13:1, invisible. The [data-theme] variants
   below take it to (0,4,1)/(0,5,1) so this treatment holds in dark too.
   ===================================================================== */
body[data-variant="praxis"] .filter-view-switch {
  border: 0; padding: 3px; background: var(--px-chip); border-radius: 11px;
  box-shadow: inset 0 1px 2px rgba(16,36,58,.10), inset 0 0 0 .5px rgba(16,36,58,.06);
}
body[data-variant="praxis"] .filter-view-switch__opt { color: var(--praxis-color-text-secondary); border-radius: 8px; }
body[data-variant="praxis"] .filter-view-switch__opt:hover { color: var(--praxis-color-text-primary); }
body[data-variant="praxis"] .filter-view-switch__opt.is-active,
body[data-variant="praxis"] .filter-view-switch__opt.is-active:hover {
  background: var(--px-surface);
  color: var(--praxis-color-text-primary);
  box-shadow: 0 0 0 .5px rgba(16,36,58,.14), 0 1px 2px rgba(16,36,58,.10), 0 2px 5px -2px rgba(16,36,58,.14);
}
/* Dark: groove darkens, raised option takes the elevated surface + lit edge. */
body[data-variant="praxis"][data-theme="dark"] .filter-view-switch {
  background: rgba(0,0,0,.22);
  box-shadow: inset 0 1px 2px rgba(0,0,0,.45), inset 0 0 0 .5px rgba(255,255,255,.05);
}
body[data-variant="praxis"][data-theme="dark"] .filter-view-switch__opt.is-active,
body[data-variant="praxis"][data-theme="dark"] .filter-view-switch__opt.is-active:hover {
  background: var(--px-surface-2);
  color: var(--praxis-color-white);
  box-shadow: 0 0 0 .5px rgba(255,255,255,.14), 0 1px 3px rgba(0,0,0,.5), inset 0 .5px 0 rgba(255,255,255,.10);
}
.custom-builder { position: absolute; inset: 0px; z-index: 3; background: var(--px-surface, var(--praxis-color-white)); display: flex; flex-direction: column; }
.custom-builder[hidden] { display: none; }
.custom-builder__head { position: relative; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--praxis-space-16); padding: var(--praxis-space-12) var(--praxis-space-16); }
.custom-builder__head::after { content: ""; position: absolute; left: var(--praxis-space-16); right: var(--praxis-space-16); bottom: 0px; height: 1px; background: var(--praxis-color-neutral-20); }
.custom-builder__head h3 { font-size: var(--praxis-type-size-base); font-weight: 700; color: var(--praxis-color-neutral-90); }
.custom-builder__head p { font-size: var(--praxis-type-size-xs); color: var(--praxis-color-neutral-60); margin-top: 2px; max-width: 520px; }
.cf-example-btn { border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; color: var(--praxis-color-teal-70); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-sm); font-weight: 700; cursor: pointer; white-space: nowrap; padding: var(--praxis-space-4) 0; }
.cf-example-btn:hover { text-decoration: underline; }
.custom-builder__tree { flex: 1 1 0%; overflow-y: auto; padding: var(--praxis-space-12) var(--praxis-space-16); }
.cf-group { border: 1px solid var(--praxis-color-neutral-15); border-radius: var(--praxis-radius-md); background: var(--praxis-color-neutral-05); padding: var(--praxis-space-8) var(--praxis-space-12) var(--praxis-space-12); }
.cf-group--d0 { background: rgb(255, 255, 255); }
.cf-group .cf-group { margin-top: 0px; }
.cf-group__bar { display: flex; align-items: center; gap: var(--praxis-space-8); padding-bottom: var(--praxis-space-8); }
.cf-group__hint { font-size: var(--praxis-type-size-xs); font-weight: 700; letter-spacing:normal; color: var(--praxis-color-neutral-40); flex: 1 1 0%; }
.cf-group__children { display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.cf-empty { font-size: var(--praxis-type-size-sm); color: var(--praxis-color-neutral-50); padding: var(--praxis-space-8); }
.cf-join { align-self: flex-start; display: inline-flex; gap: 2px; padding: 2px; margin-left: var(--praxis-space-12); background: var(--praxis-color-neutral-10); border-radius: var(--praxis-radius-full); }
.cf-join button { border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: 11px; font-weight: 800; letter-spacing: 0.04em; padding: 3px 12px; border-radius: var(--praxis-radius-full); color: var(--praxis-color-neutral-50); cursor: pointer; transition: background var(--praxis-motion-fast) var(--praxis-ease-default), color var(--praxis-motion-fast) var(--praxis-ease-default); }
.cf-join button:hover { color: var(--praxis-color-neutral-80); }
.cf-join .cf-join__and.is-active { background: var(--praxis-color-teal-70); color: rgb(255, 255, 255); }
.cf-join .cf-join__or.is-active { background: rgb(176, 23, 92); color: rgb(255, 255, 255); }
.cf-cond { display: grid; grid-template-columns: auto minmax(150px, 1fr) minmax(130px, 0.9fr) minmax(150px, 1fr) auto; gap: var(--praxis-space-8); align-items: start; padding: var(--praxis-space-8) 0; }
.cf-cond, .cf-group { position: relative; }
.cf-cond__lead { display: inline-flex; align-items: center; gap: 2px; padding-top: 9px; }
.cf-drag { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 24px; color: var(--praxis-color-neutral-40); cursor: grab; }
.cf-drag:hover { color: var(--praxis-color-neutral-70); }
.cf-drag:active { cursor: grabbing; }
.cf-drag .material-symbols-rounded { font-size: 18px; }
.cf-select { display: inline-flex; align-items: center; }
.cf-select input { width: 15px; height: 15px; cursor: pointer; accent-color: var(--praxis-color-teal-60); margin: 0px; }
.cf-cond.is-selected { outline: 2px solid var(--praxis-color-teal-40); outline-offset: 3px; border-radius: var(--praxis-radius-sm); }
.cf-group__bar .cf-drag { height: 22px; }
.cf-dragging { opacity: 0.4; }
.cf-drop-before::before, .cf-drop-after::after { content: ""; position: absolute; left: 0px; right: 0px; height: 2px; background: var(--praxis-color-teal-60); border-radius: 2px; z-index: 2; }
.cf-drop-before::before { top: -5px; }
.cf-drop-after::after { bottom: -5px; }
.cf-group-selected { color: var(--praxis-color-teal-70) !important; border-color: var(--praxis-color-teal-50) !important; }
.cf-group-selected:hover { background: var(--praxis-color-teal-10) !important; }
.cf-pick { min-width: 0px; }
.cf-value { min-width: 0px; display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.cf-value:empty { display: none; }
.cf-input { height: 40px; padding: 0 var(--praxis-space-12); border: 0; border-radius: var(--praxis-radius-md); background-color: var(--px-field); font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-sm); color: var(--praxis-color-text-primary); outline: none; width: 100%; }
.cf-input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--praxis-color-interactive-default),
    0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent); }
@media (max-width: 1279px) {
  .filter-select, .filter-date, .filter-search, .filter-search input, .cf-input { height: 44px; }
}

.cf-date-range { display: flex; flex-direction: column; gap: var(--praxis-space-8); }
.cf-remove { display: grid; place-items: center; width: 32px; height: 32px; border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; background: transparent; border-radius: var(--praxis-radius-full); color: var(--praxis-color-neutral-40); cursor: pointer; flex-shrink: 0; }
.cf-remove:hover { background: var(--praxis-color-neutral-10); color: rgb(192, 36, 74); }
.cf-remove .material-symbols-rounded { font-size: 18px; }
.cf-group__add { display: flex; gap: var(--praxis-space-8); padding-top: var(--praxis-space-8); }
.cf-group__add button { display: inline-flex; align-items: center; gap: 4px; border: 1px dashed var(--praxis-color-neutral-30); background: transparent; border-radius: var(--praxis-radius-full); padding: 5px 12px; font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--praxis-type-size-xs); font-weight: 700; color: var(--praxis-color-neutral-70); cursor: pointer; }
.cf-group__add button:hover { border-color: var(--praxis-color-teal-50); color: var(--praxis-color-teal-70); background: var(--praxis-color-teal-10); }
.cf-group__add .material-symbols-rounded { font-size: 16px; }
.filter-expr { flex-shrink: 0; border-top: 1px solid var(--praxis-color-neutral-20); padding: var(--praxis-space-8) var(--praxis-space-16); background: var(--praxis-color-neutral-05); max-height: 110px; overflow-y: auto; }
.filter-expr[hidden] { display: none; }
.filter-expr__label { font-size: 10px; font-weight: 700; letter-spacing:normal; color: var(--praxis-color-neutral-50); }
.filter-expr__body { font-size: var(--praxis-type-size-sm); line-height: 1.7; color: var(--praxis-color-neutral-90); margin-top: 2px; }
/* The expression readout's parts, as emitted by formatExpr(). Each is its own
   element so a consumer can give it a shape — see the note in praxis-filters.js.
   .cfx-field replaced a bare <b> in 0.1.12; the weight here is what keeps that
   appearance for anyone not restyling it. */
.cfx-field { font-weight: 700; }
/* Spacing between the parts is a margin, not the literal spaces formatExpr used
   to emit: a space glyph is whatever width the font makes it, which cannot be
   lined up with anything, and it collapses unpredictably once a part becomes an
   inline-flex box. 2px a side reproduces the ~4px gap the spaces gave at the
   readout's size. A consumer painting the parts as chips will want more — set
   margin-inline on these same classes. */
.cfx-field, .cfx-opword, .cfx-val, .cfx-bool, .cfx-incomplete { margin-inline: 2px; }
.cfx-field:first-child { margin-left: 0; }
.cfx-bool { font-weight: 800; font-size: var(--praxis-type-size-xs); padding: 1px 7px; border-radius: var(--praxis-radius-full); }
.cfx-bool--and { background: var(--praxis-color-teal-10); color: var(--praxis-color-teal-80); }
.cfx-bool--or { background: rgb(251, 227, 232); color: rgb(176, 23, 92); }
.cfx-paren { font-weight: 800; color: rgb(232, 163, 61); }
.cfx-val { color: var(--praxis-color-teal-80); font-weight: 600; }
.cfx-opword { color: var(--praxis-color-neutral-60); }
.cfx-incomplete { color: var(--praxis-color-neutral-40); font-style: italic; }
/* A condition whose value is not chosen yet. It rendered as a plain .cfx-val
   before 0.1.12 — teal and semibold, i.e. indistinguishable from a value the
   user had actually picked, and reading as a value literally named "…". It takes
   the incomplete treatment instead. */
.cfx-val--pending { color: var(--praxis-color-neutral-40); font-weight: 400; font-style: italic; }
.custom-builder__actions { display: flex; justify-content: flex-end; gap: var(--praxis-space-8); padding: var(--praxis-space-12) var(--praxis-space-16); border-top: 1px solid var(--praxis-color-neutral-15); }
.custom-builder__actions .btn { max-width: 220px; justify-content: center; }
.custom-builder__actions [data-action="cf-clear"] { margin-right: auto; }
.custom-summary { border: 1px solid var(--praxis-color-teal-30); border-radius: var(--praxis-radius-md); background: var(--praxis-color-teal-10); padding: var(--praxis-space-12); margin: var(--praxis-space-12) 0; }
.custom-summary[hidden] { display: none; }
.custom-summary__head { display: flex; align-items: center; gap: var(--praxis-space-8); }
.custom-summary__head .material-symbols-rounded { font-size: 19px; color: var(--praxis-color-teal-70); }
.custom-summary__head h3 { font-size: var(--praxis-type-size-base); font-weight: 700; color: var(--praxis-color-neutral-90); flex: 1 1 0%; }
.custom-summary__count { font-size: var(--praxis-type-size-xs); font-weight: 700; color: var(--praxis-color-teal-80); background: rgb(255, 255, 255); border: 1px solid var(--praxis-color-teal-30); border-radius: var(--praxis-radius-full); padding: 2px 10px; }
.custom-summary__expr { background: rgb(255, 255, 255); border: 1px solid var(--praxis-color-teal-20); border-radius: var(--praxis-radius-sm); padding: var(--praxis-space-8) var(--praxis-space-12); margin-top: var(--praxis-space-8); font-size: var(--praxis-type-size-sm); line-height: 1.7; color: var(--praxis-color-neutral-90); }
.custom-summary__actions { display: flex; gap: var(--praxis-space-8); margin-top: var(--praxis-space-8); }
.custom-summary__actions .btn { padding: 6px 16px; }
.filter-chips .chip--custom { border-color: var(--praxis-color-teal-50); background: var(--praxis-color-teal-10); color: var(--praxis-color-teal-80); }
.filter-chips .chip--custom .material-symbols-rounded:first-child { font-size: 16px; }
@media (max-width: 1279px) {
  .cf-cond { grid-template-columns: auto 1fr auto; }
  .cf-cond .cf-pick[data-role="cf-op"], .cf-cond .cf-value { grid-column: 2; }
}

.filter-drawer:has(.custom-builder:not([hidden])) .filter-drawer__body { display: none; }
.filter-drawer:has(.custom-builder:not([hidden])) .filter-toolbar-row { display: none; }
.custom-builder:not([hidden]) { position: static; flex: 1 1 0%; min-height: 0px; }
@media (min-width: 1280px) {
  html[data-filter-mode="modal"] .filter-drawer:has(.custom-builder:not([hidden])) { height: fit-content; max-height: calc(-48px + 100vh); }
}

.select-menu { animation: motion-menu-in var(--praxis-motion-normal) var(--praxis-ease-default); transform-origin: left top; }
.quick-filters:not([hidden]) { animation: motion-rise-in var(--praxis-motion-slow) var(--praxis-ease-default); }
.quick-filters__scroller .qfilter { animation: motion-rise-in var(--praxis-motion-slow) var(--praxis-ease-default) backwards; }
.quick-filters__scroller .qfilter:nth-child(2) { animation-delay: 40ms; }
.quick-filters__scroller .qfilter:nth-child(3) { animation-delay: 80ms; }
.quick-filters__scroller .qfilter:nth-child(4) { animation-delay: 120ms; }
.quick-filters__scroller .qfilter:nth-child(5) { animation-delay: 160ms; }
.filter-chips .chip { animation: motion-fade-in var(--praxis-motion-normal) var(--praxis-ease-default); }
.custom-builder:not([hidden]), .custom-summary:not([hidden]) { animation: motion-rise-in var(--praxis-motion-normal) var(--praxis-ease-default); }
.filter-row__chips:not([hidden]), .filter-drawer__foot [data-restore-defaults]:not([hidden]) { animation: motion-fade-in var(--praxis-motion-normal) var(--praxis-ease-default); }
.cf-enter { animation: motion-rise-in var(--praxis-motion-normal) var(--praxis-ease-default); }
.cf-enter--controls .cf-pick[data-role="cf-op"], .cf-enter--controls .cf-value > * { animation: motion-rise-in var(--praxis-motion-normal) var(--praxis-ease-default); }
.cf-exit { animation: motion-shrink-out 140ms var(--praxis-ease-default) forwards; pointer-events: none; }
.chip, .filter-type-btn, .filter-view-switch__opt, .cf-example-btn, .filter-toggle, .qfilter__check, .select-menu__check { transition: background var(--praxis-motion-fast) var(--praxis-ease-default),
    background-color var(--praxis-motion-fast) var(--praxis-ease-default),
    border-color var(--praxis-motion-fast) var(--praxis-ease-default),
    color var(--praxis-motion-fast) var(--praxis-ease-default),
    box-shadow var(--praxis-motion-fast) var(--praxis-ease-default),
    opacity var(--praxis-motion-fast) var(--praxis-ease-default),
    transform var(--praxis-motion-fast) var(--praxis-ease-default); }
.chip:active, .filter-type-btn:active, .filter-view-switch__opt:active, .cf-example-btn:active, .filter-toggle:active { transform: scale(0.97); }
.qfilter__more, .select-menu__opt, .filter-more__head { transition: background var(--praxis-motion-fast) var(--praxis-ease-default),
    border-color var(--praxis-motion-fast) var(--praxis-ease-default),
    border-bottom-color var(--praxis-motion-fast) var(--praxis-ease-default),
    color var(--praxis-motion-fast) var(--praxis-ease-default); }
.filter-search-input input { transition: background-color var(--praxis-motion-fast) var(--praxis-ease-default),
              box-shadow var(--praxis-motion-fast) var(--praxis-ease-default); }

/* ---------------------------------------------------------------------
   Checkbox faces: show the tick only when actually checked.

   Upstream relied on the tick being white-on-white and only becoming visible
   once the box filled teal. That breaks the moment the box sits on a dark
   surface (Praxis dark theme), where a white tick reads as permanently
   checked. Drive visibility explicitly off the state class instead — which
   also survives praxis-lucide swapping the glyph for an SVG.
   --------------------------------------------------------------------- */
.qfilter__check > *,
.select-menu__check > * { opacity: 0; transition: opacity var(--praxis-motion-fast) var(--praxis-ease-default); }
.qfilter__row--selected .qfilter__check > *,
.select-menu__opt.is-checked .select-menu__check > *,
.select-menu__opt.is-mixed .select-menu__check > * { opacity: 1; }

/* ---------------------------------------------------------------------
   Quick-filter card spacing

   The source stylesheet leaned on its host page's global `ul, ol { margin:0;
   padding:0; list-style:none }` reset. search-page.html doesn't load
   praxis-chrome-legacy.css, so the card's <ul> kept UA defaults — a 40px inline
   start padding that pushed every checkbox 44px right of the card title, plus
   14px block margins that made the vertical rhythm lumpy. Reset it here so the
   component doesn't depend on the host having a reset at all.
   --------------------------------------------------------------------- */
.qfilter__list { margin: 0; padding: 0; list-style: none; }

/* Rows: content aligns flush with the card title, while the hover background
   still bleeds into the card padding so the target doesn't look cramped. */
.qfilter__row { padding: var(--praxis-space-4); margin-inline: calc(var(--praxis-space-4) * -1); }

/* Value labels at 14px, matching the card title */
.qfilter__row { font-size: var(--praxis-type-size-base); }

/* "See More" keeps its own even spacing; the cards are equal-height grid items,
   so it stays bottom-anchored across the row rather than floating mid-card. */
.qfilter__more { padding: var(--praxis-space-4); margin-inline: calc(var(--praxis-space-4) * -1); text-align: left; }

/* ---------------------------------------------------------------------
   Page gutter — the strip and the chip bar line up with whatever inset the
   host uses for its page header / toolbar / results. Defaults to flush so the
   component drops into any container; the host opts in.
   --------------------------------------------------------------------- */
.quick-filters,
.filter-chips { padding-inline: var(--praxis-filters-gutter, 0); }

/* ---------------------------------------------------------------------
   Quick-filter card head

   Same class of leak as the <ul> above: .qfilter__title is an <h3> and the
   source relied on the host's global heading reset. Without it the UA's
   `margin: 1em 0` added 14px above AND below the title — pushing it 27px down
   from the card top and opening a 22px gulf before the first value, which is
   what was forcing the cards tall. The close button likewise inherited the
   host's 36px .icon-btn instead of the intended 20px --xs size.
   --------------------------------------------------------------------- */
.qfilter__title { margin: 0; }
.qfilter__close { width: 20px; height: 20px; flex-shrink: 0; padding: 0; }
.qfilter__close .material-symbols-rounded,
.qfilter__close svg { width: 16px; height: 16px; font-size: 16px; }

/* Head sits tight to the top; values follow close behind it */
.qfilter { padding: var(--praxis-space-12) var(--praxis-space-12) var(--praxis-space-12) var(--praxis-space-16); gap: var(--praxis-space-4); }
.qfilter__head { align-items: flex-start; }

/* Rows realign to the card's roomier left padding */
.qfilter__row,
.qfilter__more { margin-inline: calc(var(--praxis-space-4) * -1); }

/* =====================================================================
   ACCORDION MOTION + HIT TARGET

   1. Easing. The whole component runs on --praxis-ease-default,
      cubic-bezier(.32,.72,0,1). That curve is extremely front-loaded — it
      reaches ~77% progress in the first quarter of its duration, then
      crawls to rest. On a hover colour it's imperceptible, but driving
      grid-template-rows 0fr→1fr it reads as a springy snap-and-settle:
      the row flies open, overshoots the eye, then eases in. Swap the
      opening rows (and the caret that tracks them) onto an even
      decelerate. Nothing else in the panel changes — the drawer slide,
      hovers and press states keep the house curve.

      Note this is a perceptual bounce, not a literal one: neither curve in
      the system overshoots mathematically (no y control point exceeds 1).
      Duration is unchanged at --d-normal; only the distribution moves.

   2. Hit target. .filter-row__toggle only spans caret + icon + title, so
      the row's 16px padding band, the header gaps and the quick-filter
      badge were all dead space — a click that visibly landed "on the row"
      did nothing. praxis-filters.js now forwards those to the button; this
      block supplies the matching affordance.
   ===================================================================== */

.filter-controls,
.quick-filters,
.filter-chips,
.filter-drawer,
.filter-menu,
.drawer-scrim,
.select-menu {
  /* Even decelerate — no snap at the head of the curve. */
  --ease-accordion: cubic-bezier(.4, 0, .2, 1);
}

.filter-row__body { transition: grid-template-rows var(--praxis-motion-normal) var(--ease-accordion); }
.filter-row__body > .filter-row__controls,
.filter-row__body > .filter-row__conditions { transition: opacity var(--praxis-motion-normal) var(--ease-accordion); }
.filter-row__caret { transition: color var(--praxis-motion-fast) var(--praxis-ease-default), transform var(--praxis-motion-normal) var(--ease-accordion); }
/* The "More Filters" roll-up is the same interaction one level up, so it
   tracks the rows rather than keeping the springier curve. */
.filter-more__body { transition: grid-template-rows var(--praxis-motion-slow) var(--ease-accordion); }

/* =====================================================================
   QUICK-FILTER BOLT — filled when on

   .is-on only changed the glyph's colour, so a pinned quick filter read as
   "pink outline" against an "unset grey outline" — a hue change carrying
   the whole state, which is weak at 23px and invisible to anyone who
   can't separate the two hues. Fill the glyph instead, so the state also
   reads as weight.

   praxis-lucide.js rewrites the Material ligature `bolt` into a Lucide
   `zap` <svg>, which is stroke-built (fill="none" stroke="currentColor"),
   so this is a fill change rather than the Material FILL axis. The font
   glyph is covered too, in case the Lucide pass hasn't run or is absent —
   the page loads the FILL 0..1 axis, so 'FILL' 1 is available there.

   Both use currentColor, so the fill follows whatever pink the theme has
   already resolved — #E30072 in light, the brightened #FF7AB8 in dark —
   and the treatment is identical in both. */
.filter-row__quick.is-on svg,
.filter-row__quick.is-on svg * { fill: currentColor; }
.filter-row__quick.is-on .material-symbols-rounded { font-variation-settings: 'FILL' 1; }

/* ---- The press bounce: the global scale-down on wide controls -------
   praxis-core.css gives every control a press affordance: hover lifts,
   press scales down over 150ms. It splits by footprint — .95 for "small
   controls: icon buttons and compact pills", .97 for larger surfaces,
   because (its words) "Larger surfaces travel less, or the shrink reads
   as a jump."

   The split keys off class, though, and the bucket for .95 is a bare
   `button` selector. Several controls here are full-width rows that carry
   no .btn-family class, so they land in the SMALL bucket and take the
   largest scale of anything on the page:

     .filter-more__head    928px wide  → shrinks ~46px on press
     .filter-row__toggle   836px wide  → shrinks ~42px on press
     .filter-select        491px wide  → shrinks ~25px on press
     .filter-date          491px wide  → shrinks ~25px on press

   On the accordion headers that's the visible fault: press Status or
   Priority and the chevron and title lurch inward, spring back over
   150ms on the house curve, and only then does the row open.

   praxis-core writes those rules with :where() at specificity 0 so a
   component can opt out — "A component that wants different press
   behaviour — or none — overrides it with any class rule." That invitation
   undersells what's needed, though: :where() is 0, but the rest of the
   selector is `:active:not(:disabled):not([aria-disabled="true"])`, and
   :not() contributes its argument's specificity. The core rule is
   therefore (0,3,0), and a plain `.filter-row__toggle:active` — (0,2,0) —
   loses to it even when declared later. Verified in-engine, not assumed.

   Hence the descendant + :not() below: (0,4,0), which beats it outright
   rather than depending on load order. These are rows and fields, not
   buttons; they already signal press through hover and the caret.
   Genuinely small controls in the panel (the bolt, bookmark, close and
   twist icons) keep the affordance. */
.filter-drawer .filter-row__toggle:active:not(:disabled),
.filter-drawer .filter-more__head:active:not(:disabled),
.filter-drawer .filter-select:active:not(:disabled),
.filter-drawer .filter-date:active:not(:disabled) { transform: none; }

/* ---- The panel resizing under the accordion -------------------------
   Easing was only half the story. In modal mode the panel is
   `height: fit-content` AND vertically centred (`margin-block: auto`), so
   every row that expands grows the dialog — and a centred box grows BOTH
   ways. Measured on a 980px viewport, opening one row grew the panel 42px:
   the top edge rose 21px while the bottom dropped 21px, over the same
   180ms the row was easing open. The whole dialog breathes outward from
   its centre on every click. That movement is what reads as a bounce, and
   no easing change can fix it — initModalResize() already documents the
   behaviour ("The modal is centered, so it grows both ways").

   So take content out of the height equation: give the panel a stable
   height and let .filter-drawer__body (already overflow-y:auto) absorb the
   growth by scrolling. Rows then open in place against a dialog that never
   moves.

   An explicit height, not max-height, now drives the sheet. The drag
   handles keep working unchanged — initModalResize() writes that same
   property, and its `grow * 2` still holds because a centred box of fixed
   height moves each edge by half the delta. The default tracks the height
   the panel naturally settled at, so its resting size is unchanged. */
@media (min-width: 1280px) {
  html[data-filter-mode="modal"] .filter-drawer,
  html[data-filter-mode="modal"] .filter-drawer:has(.custom-builder:not([hidden])) {
    /* var(), not a bare 720px: initModalResize() writes --filter-modal-h on the
       drawer's inline style on every pointermove, and until 0.1.12 nothing read
       it — a grep for the property across the package returned exactly one hit,
       the write. So the drag handles bound, fired and computed correctly while
       the panel never moved. The variable was orphaned by the stable-height
       change documented above, which replaced this rule's max-height with an
       explicit height and hardcoded the 720px the resting size needs. 720px is
       now the fallback, so that resting size is unchanged. The handler already
       clamps to [360, innerHeight - 48]; the min() here is the same limit from
       the CSS side. */
    height: min(var(--filter-modal-h, 720px), calc(100vh - 48px));
    max-height: calc(100vh - 48px);
  }
}

/* The whole header row is now a hit target, so it takes the pointer and
   drives the caret's hover state — previously both were scoped to the
   inner button. Locked rows (pinned as quick filters) don't expand, so
   they keep the default cursor and no hover response. */
.filter-row__head { cursor: pointer; }
.filter-row__head:hover .filter-row__caret { color: var(--praxis-color-neutral-90); }
.filter-row--locked .filter-row__head { cursor: default; }
.filter-row--locked .filter-row__head:hover .filter-row__caret { color: var(--praxis-color-neutral-50); }

/* =====================================================================
   DARK THEME

   The ported v1.5 stylesheet paints with RAW SCALE tokens
   (--praxis-color-neutral-90, --praxis-color-teal-80, …) rather than the
   semantic text/surface tokens, because its source only ever ran in light.
   praxis-core's dark theme remaps just the low end of that scale —
   neutral-05/10/15/20, the surface steps — and leaves neutral-30..100 and
   the whole teal/red/pink tint range at their light values. So in dark:

     • every `color: var(--praxis-color-neutral-90)` painted #2a3846 (dark
       navy) onto a #192336 modal — 1.2:1, illegible. That hit the modal
       title, every filter-row title, all inputs, selects and menu options.
     • the teal-10 "selected" tints (#e8f3f3, near-white) glared as light
       blocks on the dark panel.
     • three panels hardcode rgb(255,255,255) and rendered pure white.

   Rather than rewrite 500 lines of ported rules (and risk regressing
   light), remap the scale itself inside the filter roots — same scope as
   the bridge block at the top of this file, so it cannot leak into a host
   page. That corrects the bulk of the component in one pass; the sections
   below then fix only the rules where a token is used as a FILL behind
   light ink, which a straight scale flip would invert the wrong way.

   Values follow the Mazlan dark DNA already in praxis-core: #e7ebf1 ink,
   the #29D2D7/#5CE0E5 cyan accent in place of light-mode teal, and dark
   ink (--px-primary-fg) on any bright cyan fill. All text pairings here
   clear WCAG 2.2 AA (4.5:1) against the surface they sit on, and control
   borders clear 3:1 for 1.4.11.
   ===================================================================== */

body[data-theme="dark"] :is(.filter-controls,
.quick-filters,
.filter-chips,
.filter-drawer,
.filter-menu,
.drawer-scrim,
.select-menu) {
  /* Ink. The scale is ordered light→dark in the source, so it inverts:
     the high steps become the brightest ink. */
  --praxis-color-neutral-100:#f2f5f9;
  --praxis-color-neutral-90:#e7ebf1;   /* primary ink — titles, inputs, options */
  --praxis-color-neutral-80:#cfd8e3;
  --praxis-color-neutral-70:#b7c3d0;
  --praxis-color-neutral-60:#9aa7b4;   /* secondary ink, trailing icons */
  --praxis-color-neutral-50:#8b98a6;   /* placeholders, empty states — 5.4:1 */
  --praxis-color-neutral-40:#7e8c9b;   /* faint hints, drag handles — 4.6:1 */
  /* Borders and "off" icon controls. Translucent white so it rides whatever
     surface it lands on (modal, recessed group, footer) at a consistent
     weight; .42 lands ~3.4:1 for the checkbox borders and the unset
     favourite/quick-filter icons, clearing 1.4.11's 3:1 for controls. */
  --praxis-color-neutral-30:rgba(255,255,255,.42);

  /* Accent. Light-mode teal is far too dark to read on the panel, so the
     family shifts to the cyan praxis-core already uses for dark accents.
     The low steps stay translucent — a flat tint would occlude the surface
     tier it sits on and flatten the modal's elevation. teal-30/40 are
     referenced by .custom-summary and .cf-cond.is-selected but were never
     defined at any level, so those declarations were dropping as invalid;
     defining them here also repairs that in dark. */
  --praxis-color-teal-10:rgba(41,210,215,.14);
  --praxis-color-teal-20:rgba(41,210,215,.22);
  --praxis-color-teal-30:rgba(41,210,215,.32);
  --praxis-color-teal-40:rgba(41,210,215,.42);
  --praxis-color-teal-50:#29D2D7;
  --praxis-color-teal-60:#29D2D7;
  --praxis-color-teal-70:#5CE0E5;
  --praxis-color-teal-80:#7FEAEE;      /* ink on the cyan tints — 8.2:1 */

  /* Destructive + pink, same treatment. #ff8b93 matches the Clear-all
     override already established above. pink-10 was only ever reachable
     through an inline #FDE6F1 fallback, which flared white on dark. */
  --praxis-color-red-10:rgba(255,139,147,.16);
  --praxis-color-red-50:#ff8b93;
  --praxis-color-red-60:#ff8b93;
  --praxis-color-pink-10:rgba(255,122,184,.18);
  --praxis-color-pink-60:#FF7AB8;
}

/* ---- Fills that carry light ink -------------------------------------
   These use a scale token as a BACKGROUND, so the inversion above would
   have put near-white ink on a near-white fill. Each takes the dark-CTA
   pattern from praxis-core instead: bright cyan fill, dark ink. */
body[data-theme="dark"] .segmented__opt--active {
  background: var(--praxis-color-teal-60); color: var(--px-primary-fg, #08313a);
}
body[data-theme="dark"] .segmented__opt--active:hover {
  background: #5CE0E5; color: var(--px-primary-fg, #08313a);
}
body[data-theme="dark"] .filter-view-switch__opt.is-active,
body[data-theme="dark"] .filter-view-switch__opt.is-active:hover {
  color: var(--px-primary-fg, #08313a);
}
/* Checkbox ticks: white-on-cyan is ~1.9:1. The faces fill with teal-60,
   so the glyph takes the same dark ink the CTAs use. */
body[data-theme="dark"] .qfilter__check,
body[data-theme="dark"] .select-menu__check { color: var(--px-primary-fg, #08313a); }

/* AND/OR join pills read as one control, so both take a bright fill with
   dark ink — the hardcoded deep magenta on OR would otherwise pair a
   white-ink pill against a dark-ink one. */
body[data-theme="dark"] .cf-join .cf-join__and.is-active {
  background: var(--praxis-color-teal-60); color: var(--px-primary-fg, #08313a);
}
body[data-theme="dark"] .cf-join .cf-join__or.is-active {
  background: #FF7AB8; color: #3d0020;
}

/* Tooltip: neutral-90 was its fill, which the inversion turns near-white.
   Takes the page's own dark tooltip/snackbar slate. */
body[data-theme="dark"] .filter-type-btn::after {
  background: #2b3648; color: var(--praxis-color-text-primary, #e7ebf1);
}

/* ---- Hardcoded light panels ----------------------------------------- */
/* The depth-0 group is the builder's outermost card: white in light, so it
   reads as the panel surface rather than the recessed well its nested
   children sit in. Same intent in dark, one tier up from neutral-05. */
body[data-theme="dark"] .cf-group--d0 { background: var(--px-surface); }
body[data-theme="dark"] .custom-summary__expr { background: rgba(0,0,0,.22); }
body[data-theme="dark"] .custom-summary__count {
  background: rgba(0,0,0,.22); color: var(--praxis-color-teal-80);
}
/* Expression chips: the OR variant hardcodes a pale pink fill + deep
   magenta ink. Mirror the AND chip's tint-plus-bright-ink construction. */
body[data-theme="dark"] .cfx-bool--or {
  background: rgba(255,122,184,.18); color: #FF7AB8;
}
body[data-theme="dark"] .cf-remove:hover { color: #ff8b93; }

/* Custom-filter chips carry the cyan accent, but the bar-wide dark rule
   above (.filter-chips .chip) outranks .chip--custom and was flattening
   them to plain ink. Re-assert with the theme prefix so it wins. */
body[data-theme="dark"] .filter-chips .chip--custom { color: var(--praxis-color-teal-80); }

/* ---- Materials ------------------------------------------------------
   The modal's shadow, the footer's lip shadow and the scrim are all built
   from light-mode navy (rgba(32,42,53,…)), which is invisible over a dark
   page — the modal lost its separation from the content behind it. Take
   the Praxis dark overlay material, and deepen the scrim so the panel
   still reads as modal. */
body[data-theme="dark"] .filter-drawer { box-shadow: var(--px-overlay); }
body[data-theme="dark"] .drawer-scrim { background: rgba(4,8,16,.62); }
body[data-theme="dark"] .filter-drawer__foot {
  box-shadow: 0 -8px 16px rgba(0,0,0,.28), 0 -2px 4px rgba(0,0,0,.20);
}

/* Row hover used neutral-05, which inverts DARKER than the card it sits on
   — a hover that reads as a hole. Use the theme's own hover wash. */
body[data-theme="dark"] .qfilter__row:hover { background: var(--px-hover); }

/* .cf-input never declares a background — in light the UA default is white,
   which passes for the same field colour its sibling .filter-select paints
   with --px-surface-2. Under color-scheme:dark the UA default is a neutral
   grey instead, so the value inputs sat warm and light against the navy
   selects beside them. Name the surface rather than inherit it. */
body[data-theme="dark"] .cf-input { background: var(--px-surface-2); }

/* Search focus fell back to a near-white border after the inversion; the
   cyan focus border matches the other inputs in the panel. */
body[data-theme="dark"] .filter-search-input input:focus {
  border-color: var(--praxis-color-teal-50);
}

/* ===========================================================================
   praxis-mazlan.css  —  Praxis 0.1.15
   ========================================================================== */
/* ============================================================================
   Praxis Mazlan drawer — shared component styles
   ============================================================================
   Extracted verbatim from index.html. Right-side overlay anchored below the
   app header, plus its scrim. Load alongside praxis-mazlan.js. */

  /* ============================================================
     Mazlan drawer — right-side overlay anchored below the app header
     ============================================================ */
  .mazlan-scrim {
    position: fixed;
    /* Full-viewport, including the app header. The scrim used to start below
       the header, which left the top bar undimmed and reading as still-live
       while the drawer was open. The drawer itself stays anchored below the
       header — only the dimming extends over it. */
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(32, 42, 53, 0.25);
    /* Above the app bar: most pages put it at 30, but the Contextual Awareness
       workspace's is fixed at 100 (praxis-chrome-legacy.css), so the scrim has to
       clear that to dim it. Still below Create New (119) and the module
       selector (125), which are never open at the same time. */
    z-index: 105;
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    opacity: 0;
    transition: opacity var(--praxis-motion-drawer) var(--praxis-ease-spring-soft);
  }
  .mazlan-scrim[hidden] { display: none; }
  .mazlan-scrim.mazlan-scrim--open { opacity: 1; }

  .mazlan-drawer {
    /* Floats with margin from edges so all four 24px corners are visible.
       Anchored below the top bar; calc keeps it inset from top/right/bottom. */
    position: fixed;
    top: calc(var(--praxis-appbar-h) + 16px);
    right: 16px;
    bottom: 16px;
    width: min(484px, calc(100vw - 32px));
    background: var(--praxis-color-surface-default);
    border: 0;
    border-radius: 24px;
    box-shadow: -8px 16px 40px rgba(32, 42, 53, 0.18), 0 4px 12px rgba(32, 42, 53, 0.06);
    z-index: 106;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transform: translateX(calc(100% + 16px));
    transition: transform var(--praxis-motion-drawer) var(--praxis-ease-spring);
  }
  .mazlan-drawer[hidden] { display: none; }
  .mazlan-drawer.mazlan-drawer--open { transform: translateX(0); }

  /* ===================================================================
     DARK THEME — match the external Mazlan v3 prototype
     (ideagen-workspace.vercel.app/mazlan-chatbot-v3.html):
       drawer body = navy #0e1324, menu + composer = slate, no borders.
     Surfaces that make up the drawer shell (head / bottom / footer) all
     read as one navy sheet; only the composer + menu lift to slate.
     =================================================================== */
  body[data-theme="dark"] .mazlan-drawer,
  body[data-theme="dark"] .mazlan-drawer__head,
  body[data-theme="dark"] .mazlan-drawer__bottom,
  body[data-theme="dark"] .mazlan-drawer__footer {
    background: #0e1324;
  }
  body[data-theme="dark"] .mazlan-menu,
  body[data-theme="dark"] .mazlan-menu__head {
    background: #24303c;
  }

  /* Sliding menu sub-drawer — slides in from the left over the chat surface */
  .mazlan-menu {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    background: #EDF0F2;
    z-index: 50;
    display: flex;
    flex-direction: column;
    border-radius: 24px;
    transform: translateX(-100%);
    transition: transform 250ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
    overflow: hidden;
  }
  .mazlan-drawer--menu-open .mazlan-menu {
    transform: translateX(0);
    box-shadow: 8px 0 24px rgba(0,0,0,0.1), 2px 0 8px rgba(0,0,0,0.06);
  }
  .mazlan-menu__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 16px;
    background: #EDF0F2;
    flex-shrink: 0;
  }
  .mazlan-menu__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
  }
  .mazlan-menu__brand-label {
    font-family: 'Gilroy', sans-serif;
    font-size: 18px;
    font-weight: 500;
    color: var(--praxis-color-neutral-80);
    letter-spacing: 0.02em;
  }
  .mazlan-menu__divider {
    height: 1px;
    background: rgba(55, 72, 90, 0.1);
    margin: 0 8px;
    flex-shrink: 0;
  }
  .mazlan-menu__nav {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px;
    flex-shrink: 0;
  }
  .mazlan-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    font-weight: 600;
    color: var(--praxis-color-neutral-80);
    cursor: pointer;
    text-align: left;
    letter-spacing: 0.02em;
    transition: background 0.2s ease-out;
    width: 100%;
  }
  .mazlan-menu__item:hover { background: rgba(55, 72, 90, 0.08); }
  .mazlan-menu__item:active { background: rgba(55, 72, 90, 0.12); }
  .mazlan-menu__item--active { background: rgba(55, 72, 90, 0.10); }
  .mazlan-menu__item .material-symbols-rounded {
    flex-shrink: 0;
    font-size: 20px;
    color: var(--praxis-color-neutral-80);
  }
  .mazlan-menu__new-cube {
    width: 20px;
    height: 20px;
    border-radius: 5px;
    background: var(--praxis-color-pink-60, #E30072);
    display: grid;
    place-items: center;
    flex-shrink: 0;
  }
  .mazlan-menu__new-cube .material-symbols-rounded {
    color: var(--praxis-color-white) !important;
    font-size: 16px !important;
  }
  .mazlan-menu__shortcut {
    margin-left: auto;
    font-size: 12px;
    font-weight: 500;
    color: rgba(55, 72, 90, 0.4);
    flex-shrink: 0;
    opacity: 0;
    transition: opacity 0.15s ease;
  }
  .mazlan-menu__item:hover .mazlan-menu__shortcut { opacity: 1; }

  .mazlan-menu__body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 8px;
  }
  .mazlan-menu__section + .mazlan-menu__section { margin-top: 16px; }
  .mazlan-menu__section-head {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 8px 10px;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-family: 'Gilroy', sans-serif;
    font-size: 12px;
    font-weight: 600;
    color: rgba(55, 72, 90, 0.62);
    letter-spacing:normal;
  }
  .mazlan-menu__section-head:hover { color: var(--praxis-color-neutral-80); }
  .mazlan-menu__section-head .material-symbols-rounded {
    font-size: 14px;
    flex-shrink: 0;
  }
  .mazlan-menu__chev { transition: transform 0.2s ease; }
  .mazlan-menu__section-head[aria-expanded="true"] .mazlan-menu__chev { transform: rotate(90deg); }
  .mazlan-menu__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px 0;
  }
  .mazlan-menu__chat {
    padding: 8px 10px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    font-family: 'Gilroy', sans-serif;
    font-size: 13px;
    font-weight: 500;
    color: var(--praxis-color-neutral-80);
    cursor: pointer;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background 0.15s;
  }
  .mazlan-menu__chat:hover { background: rgba(55, 72, 90, 0.08); }

  /* Dark: light ink on the slate menu (base colors are dark ink for the
     light #EDF0F2 menu, which reads as washed-out on the dark slate). */
  body[data-theme="dark"] .mazlan-menu__brand-label { color: #e4e4e7; }
  body[data-theme="dark"] .mazlan-menu__item { color: #e4e4e7; }
  body[data-theme="dark"] .mazlan-menu__item .material-symbols-rounded { color: #cbd2d9; }
  body[data-theme="dark"] .mazlan-menu__item:hover { background: rgba(255, 255, 255, 0.06); }
  body[data-theme="dark"] .mazlan-menu__item:active { background: rgba(255, 255, 255, 0.12); }
  body[data-theme="dark"] .mazlan-menu__item--active { background: rgba(255, 255, 255, 0.10); }
  body[data-theme="dark"] .mazlan-menu__shortcut { color: rgba(255, 255, 255, 0.4); }
  body[data-theme="dark"] .mazlan-menu__divider { background: rgba(255, 255, 255, 0.08); }
  body[data-theme="dark"] .mazlan-menu__section-head { color: #9ca3af; }
  body[data-theme="dark"] .mazlan-menu__section-head:hover { color: #e4e4e7; }
  body[data-theme="dark"] .mazlan-menu__chat { color: #cbd2d9; }
  body[data-theme="dark"] .mazlan-menu__chat:hover { background: rgba(255, 255, 255, 0.06); }

  /* Header — v3 light-mode clean variant: white background, 16px padding,
     no gradient hairline, no borders on the icon buttons. Three slots:
     menu button (top-left), Mazlan brand (center-left), action icons (right). */
  .mazlan-drawer__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 16px;
    background: var(--praxis-color-surface-default);
    border-radius: 24px 24px 0 0;
    flex-shrink: 0;
    position: relative;
  }
  .mazlan-drawer__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
  }
  .mazlan-drawer__title {
    margin: 0;
    font-family: 'Gilroy', sans-serif;
    font-size: 18px;
    font-weight: 500;
    color: var(--praxis-color-neutral-80);
    letter-spacing: 0.02em;
  }
  /* Icon-only header buttons — no border, no fill, just hover bg */
  .mazlan-drawer__head-btn,
  .mazlan-drawer__menu-btn {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    flex-shrink: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: #9ca3af;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background 0.15s, color 0.15s;
    padding: 0;
  }
  .mazlan-drawer__head-btn:hover,
  .mazlan-drawer__menu-btn:hover {
    background: #F0F2F4;
    color: #2F4051;
  }
  /* Dark: there was no dark hover at all, so the light #F0F2F4 applied and the
     button flashed near-white. Matches the Mazlan prototype's dark header
     buttons — a translucent white wash, not a fill. */
  body[data-theme="dark"] .mazlan-drawer__head-btn:hover,
  body[data-theme="dark"] .mazlan-drawer__menu-btn:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #e4e4e7;
  }
  .mazlan-drawer__head-btn .material-symbols-rounded,
  .mazlan-drawer__menu-btn .material-symbols-rounded { font-size: 18px; }
  .mazlan-drawer__head-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
  .mazlan-drawer__head-spacer { flex: 1; min-width: 0; }
  .mazlan-drawer__brand { display: flex; align-items: center; gap: var(--praxis-space-12); min-width: 0; }
  .mazlan-drawer__brand-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .mazlan-drawer__title {
    margin: 0;
    font-size: var(--praxis-type-size-md);
    font-weight: 700;
    color: var(--praxis-color-text-primary);
  }
  .mazlan-drawer__sub {
    margin: 0;
    font-size: var(--praxis-type-size-xs);
    color: var(--praxis-color-text-tertiary);
  }
  /* (Header button styling is defined later in the file under the
     v3 light-mode clean variant — no borders, no fills, icon-only.) */

  .mazlan-drawer__body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--praxis-space-24);
    display: flex;
    flex-direction: column;
    position: relative;
  }
  /* Dot pattern background — pure CSS radial-gradient repeated on a 22px
     grid. A second radial-gradient overlay fades the pattern toward the
     edges (replacement for the mask approach, which wasn't rendering
     consistently). */
  .mazlan-drawer__dots {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
      /* Edge fade overlay — same color as drawer bg, transparent in the
         center, opaque at the edges. Centered on the greeting (50%/50%)
         with a tighter visible ellipse (smaller halo). */
      radial-gradient(ellipse 50% 47% at 50% 26%, transparent 0%, transparent 27%, var(--praxis-color-surface-default) 90%),
      /* Repeating dot pattern — 1.25px dots at 0.4 alpha */
      radial-gradient(circle at 2px 2px, rgba(77, 101, 127, 0.4) 1.25px, transparent 1.5px);
    background-size: 100% 100%, 20px 20px;
    background-position: 0 0, 0 0;
  }
  /* Dark: the edge-fade must resolve to the navy body colour, not
     surface-default (#192336) — otherwise the dot region reads as a lighter
     band and seams against the navy header/footer. */
  body[data-theme="dark"] .mazlan-drawer__dots {
    background-image:
      radial-gradient(ellipse 50% 47% at 50% 26%, transparent 0%, transparent 27%, #0e1324 90%),
      radial-gradient(circle at 2px 2px, rgba(77, 101, 127, 0.4) 1.25px, transparent 1.5px);
  }
  /* Lift body content above the dot pattern. Excludes the dots themselves
     so the > * rule doesn't override their position: absolute (both rules
     have the same specificity and the descendant selector lands later in
     source order, which was making the dot layer collapse). */
  .mazlan-drawer__body > *:not(.mazlan-drawer__dots) { position: relative; z-index: 1; }

  /* Prompts + input overlay the bottom of the drawer so the welcome block can
     centre over the drawer's full height (greeting lands at the vertical
     centre) rather than only the space above them. */
  .mazlan-drawer__bottom {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 2;
    background: var(--praxis-color-surface-default);
  }
  /* Conversation messages clear the overlaid bottom group. */
  .mazlan-thread { padding-bottom: 172px; }

  /* Welcome landing — centered Mazlan logo + greeting, the v3 default state */
  .mazlan-welcome {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    text-align: center;
    /* Greeting sits near the top of the drawer (~48px below the header),
       not vertically centred — matches the prototype's upper-weighted layout. */
    padding: 48px 24px 24px;
    flex: 1;
    min-height: 240px;
  }
  .mazlan-welcome[aria-hidden="true"] { display: none; }
  /* Scoped with .mazlan-drawer so the welcome mark's size is authoritative on
     every page — otherwise a page's generic `.mazlan-mark {width:20px}` (search/
     record inline, admin) or concierge's `.mazlan-mark--xl {40px}` (workspace)
     would win by cascade order and the logo would differ per page. */
  .mazlan-drawer .mazlan-welcome__logo {
    width: 40px;
    height: 40px;
    margin-bottom: 16px;
  }
  /* The welcome mark breathes — each dot swells slightly on its own beat so
     the resting state reads as "listening" rather than parked. Amplitude is
     deliberately small (8%) to stay calm in a regulated-industry context. */
  .mazlan-welcome__logo > span {
    animation: mazlan-breathe 3.2s ease-in-out infinite;
  }
  .mazlan-welcome__logo > span:nth-child(2) { animation-delay: 0.4s; }
  .mazlan-welcome__logo > span:nth-child(3) { animation-delay: 0.8s; }
  .mazlan-welcome__logo > span:nth-child(4) { animation-delay: 1.2s; }
  @keyframes mazlan-breathe {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.08); }
  }
  @media (prefers-reduced-motion: reduce) {
    .mazlan-welcome__logo > span { animation: none; }
  }
  .mazlan-welcome__greeting {
    margin: 0;
    font-family: 'Gilroy', sans-serif;
    font-size: 24px;
    font-weight: 500;
    color: var(--praxis-color-text-primary);
    line-height: 1.35;
    max-width: 340px;
  }
  body[data-theme="dark"] .mazlan-welcome__greeting { color: #e4e4e7; }

  /* Suggestion prompts — styled to read as siblings to the input below:
     same border/shadow vocabulary, pill shape. Stacked vertically, each
     pill carries a small leading icon + prompt text. */
  .mazlan-suggestions {
    padding: 0 var(--praxis-space-24);
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--praxis-color-surface-default);
  }
  body[data-theme="dark"] .mazlan-suggestions { background: transparent; }
  /* The class rule above sets display:flex, which overrides the [hidden]
     attribute's UA display:none — so the grid must explicitly re-hide here
     when the conversation starts. */
  .mazlan-suggestions[hidden] { display: none; }
  /* Header row above the cards — prototype's "A few ways I can help" label +
     shuffle + "See all assistants" link (.welcome-view-all). */
  .mazlan-suggestions__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 2px 0;
  }
  .mazlan-suggestions__label {
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    font-weight: 500;
    color: var(--muted, #6b7280);
  }
  body[data-theme="dark"] .mazlan-suggestions__label { color: #9ca3af; }
  .mazlan-suggestions__shuffle {
    display: grid;
    place-items: center;
    width: 24px; height: 24px;
    border: 0; border-radius: 6px;
    background: none;
    color: #9ca3af;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, transform 0.45s ease;
  }
  .mazlan-suggestions__shuffle:hover { color: #e4e4e7; background: rgba(255,255,255,0.06); }
  .mazlan-suggestions__shuffle .material-symbols-rounded { font-size: 15px; }
  .mazlan-suggestions__shuffle.is-spinning { transform: rotate(180deg); }
  .mazlan-suggestions__seeall {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    font-weight: 600;
    color: var(--praxis-color-interactive-default, var(--praxis-color-teal-60));
    background: none;
    border: 0;
    cursor: pointer;
    transition: gap 0.15s, color 0.15s;
  }
  .mazlan-suggestions__seeall .material-symbols-rounded { font-size: 16px; }
  .mazlan-suggestions__seeall:hover { gap: 6px; }
  body[data-theme="dark"] .mazlan-suggestions__seeall { color: #29D2D7; }
  body[data-theme="dark"] .mazlan-suggestions__seeall:hover { color: #5CE0E5; }
  .mazlan-suggestions__grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-items: stretch;
  }
  /* Match the prototype: show three curated cards, Shuffle re-rolls the set. */
  #mazlan-suggestions-grid > :nth-child(n + 4) { display: none; }
  @media (max-height: 780px) {
    #mazlan-suggestions-grid > :nth-child(n + 3) { display: none; }
  }
  .mazlan-sugg {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    max-width: 100%;
    text-align: left;
    padding: 12px;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 16px;
    background: var(--praxis-color-surface-default);
    box-shadow: 0 2px 16px -4px rgba(0, 0, 0, 0.06);
    cursor: pointer;
    transition: box-shadow 0.2s ease-out, transform 0.15s ease;
    min-width: 0;
  }
  .mazlan-sugg:hover { box-shadow: 0 2px 16px -4px rgba(0, 0, 0, 0.10); }
  /* Dark: black-alpha border/shadow vanish on the dark surface, so the cards
     read flat. Use an elevated slate chip with a light edge + real drop shadow
     so they read as floating cards. */
  /* Dark: match the prototype's .explore-card resting treatment exactly. */
  body[data-theme="dark"] .mazlan-sugg {
    background: rgba(36, 48, 64, 0.55);
    border-color: rgba(255, 255, 255, 0.09);
    box-shadow: 0 1px 2px rgba(0,0,0,0.25), 0 7px 20px -10px rgba(0,0,0,0.42);
  }
  body[data-theme="dark"] .mazlan-sugg:hover {
    border-color: rgba(255, 255, 255, 0.14);
    box-shadow: 0 2px 6px rgba(0,0,0,0.3), 0 10px 26px -10px rgba(0,0,0,0.5);
  }
  .mazlan-sugg:active { transform: scale(0.99); }
  /* Icon chip — prototype .explore-card-thumb: 48px rounded tile holding a
     tinted glyph (our per-prompt icons stand in for its illustrations). */
  .mazlan-sugg__icon {
    width: 36px; height: 36px;
    border-radius: 8px;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    background: rgba(16, 36, 58, 0.05);
    color: var(--praxis-color-text-tertiary);
  }
  body[data-theme="dark"] .mazlan-sugg__icon { background: #1F2A3C; }
  .mazlan-sugg__icon .material-symbols-rounded { font-size: 20px; }
  /* Category accent on the leading icon */
  .mazlan-sugg__icon--teal   { color: var(--praxis-color-interactive-default); }
  .mazlan-sugg__icon--pink   { color: var(--praxis-color-pink-60, #e30072); }
  .mazlan-sugg__icon--blue   { color: #0069cf; }
  .mazlan-sugg__icon--orange { color: #d4880f; }
  .mazlan-sugg__icon--green  { color: #0f8a52; }
  .mazlan-sugg__icon--purple { color: #805ad5; }
  .mazlan-sugg__title {
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    font-weight: 500;
    color: var(--praxis-color-text-primary);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }
  body[data-theme="dark"] .mazlan-sugg__title { color: #e4e4e7; }

  /* Conversation thread */
  .mazlan-thread { display: flex; flex-direction: column; gap: 18px; }
  .mazlan-thread:empty { display: none; }
  .mazlan-msg { display: flex; flex-direction: column; }
  /* User turn — slate bubble, white text, no label (prototype .msg-user). */
  .mazlan-msg--user { align-self: flex-end; align-items: flex-end; max-width: 85%; }
  .mazlan-msg__bubble {
    padding: 12px 16px;
    border-radius: 16px;
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.5;
    background: #ECEFF3;
    color: var(--praxis-color-text-primary);
  }
  body[data-theme="dark"] .mazlan-msg__bubble { background: #24303c; color: #e4e4e7; }
  /* Bot turn — full width, container-less response text. */
  .mazlan-msg--mazlan { align-self: stretch; max-width: 100%; }

  /* Reasoning — collapsible activity header + timeline (prototype .reasoning). */
  .mazlan-reasoning { margin-top: 2px; }
  .mazlan-reasoning__header {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 4px 0; border: 0; background: none; cursor: pointer;
    font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500;
    color: #6B7785; text-align: left;
  }
  .mazlan-reasoning__header:hover { color: var(--praxis-color-text-primary); }
  body[data-theme="dark"] .mazlan-reasoning__header { color: #9ca3af; }
  body[data-theme="dark"] .mazlan-reasoning__header:hover { color: #e4e4e7; }
  .mazlan-reasoning__logo { width: 20px; height: 20px; flex-shrink: 0; }
  .mazlan-reasoning__logo .mazlan-mark { width: 20px; height: 20px; }
  .mazlan-reasoning__chev { font-size: 18px; flex-shrink: 0; transition: transform 0.25s ease; }
  .mazlan-reasoning--open .mazlan-reasoning__chev { transform: rotate(90deg); }
  .mazlan-reasoning__summary { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mazlan-reasoning__timer { margin-left: auto; flex-shrink: 0; font-size: 12px; opacity: 0.55; font-variant-numeric: tabular-nums; }
  .mazlan-reasoning__header:hover .mazlan-reasoning__timer,
  .mazlan-reasoning--open .mazlan-reasoning__timer { opacity: 1; }
  .mazlan-reasoning__timeline {
    display: grid; grid-template-rows: 0fr; opacity: 0; margin-top: 0;
    transition: grid-template-rows 0.25s ease, opacity 0.25s ease, margin-top 0.25s ease;
  }
  .mazlan-reasoning--open .mazlan-reasoning__timeline { grid-template-rows: 1fr; opacity: 1; margin-top: 8px; }
  .mazlan-reasoning__timeline > div { overflow: hidden; position: relative; padding-left: 22px; }
  .mazlan-reasoning__timeline > div::before {
    content: ''; position: absolute; left: 5px; top: 6px; bottom: 10px; width: 2px;
    background: rgba(16, 36, 58, 0.12);
  }
  body[data-theme="dark"] .mazlan-reasoning__timeline > div::before { background: rgba(255, 255, 255, 0.10); }
  .mazlan-reasoning__step { position: relative; display: flex; align-items: baseline; gap: 8px; padding: 6px 0; }
  .mazlan-reasoning__dot {
    position: absolute; left: -21px; top: 7px; width: 8px; height: 8px; border-radius: 50%;
    background: var(--praxis-color-interactive-default);
    box-shadow: 0 0 0 3px var(--praxis-color-surface-default);
  }
  body[data-theme="dark"] .mazlan-reasoning__dot { box-shadow: 0 0 0 3px #0e1324; }
  .mazlan-reasoning__step-text { flex: 1; font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500; line-height: 1.5; color: var(--praxis-color-text-primary); }
  body[data-theme="dark"] .mazlan-reasoning__step-text { color: #d1d5db; }
  .mazlan-reasoning__dur { flex-shrink: 0; font-size: 11px; color: var(--praxis-color-text-tertiary); font-variant-numeric: tabular-nums; }

  /* Container-less response text (prototype .msg-bot-text). */
  .mazlan-msg__text {
    margin-top: 6px;
    font-family: 'Gilroy', sans-serif;
    font-size: 14px;
    line-height: 1.6;
    color: var(--praxis-color-text-primary);
  }
  body[data-theme="dark"] .mazlan-msg__text { color: #d1d5db; }
  .mazlan-msg__text strong { font-weight: 600; color: var(--praxis-color-text-primary); }
  body[data-theme="dark"] .mazlan-msg__text strong { color: #f4f4f5; }

  /* Inline agentic actions (Start audit-prep, etc.) */
  .mazlan-msg__actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
  .mazlan-msg__action {
    display: inline-flex; align-items: center; gap: 4px; height: 30px; padding: 0 12px;
    background: none; color: var(--praxis-color-interactive-default);
    border: 1px solid var(--praxis-color-interactive-default); border-radius: 999px;
    font-size: var(--praxis-type-size-sm); font-weight: 600; cursor: pointer;
  }
  body[data-theme="dark"] .mazlan-msg__action { color: #29D2D7; border-color: rgba(41,210,215,.5); }
  .mazlan-msg__action .material-symbols-rounded { font-size: 14px; }

  /* Sources — collapsible, avatar stack + expandable list (prototype .msg-sources). */
  .mazlan-sources { padding-top: 16px; }
  .mazlan-sources__toggle {
    display: flex; align-items: center; gap: 6px; width: 100%; padding: 0;
    border: 0; background: none; cursor: pointer;
    font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500; color: #6B7785;
  }
  body[data-theme="dark"] .mazlan-sources__toggle { color: #9ca3af; }
  body[data-theme="dark"] .mazlan-sources__toggle:hover { color: #e4e4e7; }
  .mazlan-sources__chev { font-size: 18px; color: var(--praxis-color-text-tertiary); transition: transform 0.2s ease; }
  .mazlan-sources--open .mazlan-sources__chev { transform: rotate(90deg); }
  .mazlan-sources__avatars { display: flex; align-items: center; flex-shrink: 0; }
  .mazlan-source__avatar {
    width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
    border: 2px solid var(--praxis-color-surface-default);
    background: #EDF0F2; color: var(--praxis-color-neutral-80);
    display: flex; align-items: center; justify-content: center;
    font-family: 'Gilroy', sans-serif; font-size: 9px; font-weight: 700;
  }
  body[data-theme="dark"] .mazlan-source__avatar { border-color: #0e1324; background: #24303c; color: #cbd2d9; }
  .mazlan-sources__avatars .mazlan-source__avatar + .mazlan-source__avatar { margin-left: -8px; }
  .mazlan-sources__label { margin-left: 2px; }
  .mazlan-sources__list {
    display: grid; grid-template-rows: 0fr; opacity: 0;
    transition: grid-template-rows 0.25s ease, opacity 0.25s ease;
  }
  .mazlan-sources--open .mazlan-sources__list { grid-template-rows: 1fr; opacity: 1; }
  .mazlan-sources__list > div { overflow: hidden; }
  .mazlan-source__item {
    display: flex; align-items: flex-start; gap: 8px; width: 100%; padding: 12px;
    border: 0; background: none; border-radius: 12px; cursor: pointer; text-align: left;
    margin-top: 6px;
  }
  .mazlan-source__item:hover { background: rgba(16, 36, 58, 0.05); }
  body[data-theme="dark"] .mazlan-source__item:hover { background: rgba(255, 255, 255, 0.05); }
  .mazlan-source__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .mazlan-source__title { font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500; color: var(--praxis-color-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  body[data-theme="dark"] .mazlan-source__title { color: #d1d5db; }
  .mazlan-source__origin { font-size: 12px; color: var(--praxis-color-text-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mazlan-source__arrow { font-size: 16px; color: var(--praxis-color-text-tertiary); flex-shrink: 0; }

  /* Follow-up pills (Stage 3 — prototype .followup-chip). */
  .mazlan-followups { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin-top: 14px; }
  .mazlan-followup {
    display: inline-flex; align-items: center; max-width: 100%; text-align: left;
    padding: 10px 14px; border: 0; border-radius: 999px; cursor: pointer;
    font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500; line-height: 1.4;
    background: rgba(16, 36, 58, 0.05); color: var(--praxis-color-text-primary);
    transition: background 0.15s;
  }
  .mazlan-followup span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mazlan-followup:hover { background: rgba(16, 36, 58, 0.09); }
  body[data-theme="dark"] .mazlan-followup { background: rgba(255, 255, 255, 0.06); color: #d1d5db; }
  body[data-theme="dark"] .mazlan-followup:hover { background: rgba(255, 255, 255, 0.12); }

  /* Response tools — copy / thumbs up / thumbs down (prototype .msg-actions). */
  .mazlan-msg__tools { display: flex; align-items: center; gap: 2px; margin-top: 12px; }
  .mazlan-msg__tool {
    display: grid; place-items: center; width: 30px; height: 30px;
    border: 0; border-radius: 8px; background: none; cursor: pointer;
    color: var(--praxis-color-text-tertiary); transition: background 0.15s, color 0.15s;
  }
  .mazlan-msg__tool:hover { background: rgba(16, 36, 58, 0.06); color: var(--praxis-color-text-primary); }
  body[data-theme="dark"] .mazlan-msg__tool { color: #71717a; }
  body[data-theme="dark"] .mazlan-msg__tool:hover { background: rgba(255, 255, 255, 0.08); color: #e4e4e7; }
  .mazlan-msg__tool .material-symbols-rounded { font-size: 17px; }

  /* In-chat: the dot-grid welcome canvas is retired once a chat begins. */
  .mazlan-drawer--in-chat .mazlan-drawer__dots { display: none; }

  /* Stage 4 — top fade: the conversation fades out as it scrolls under the
     header (prototype .header-fade, #0e1324@~70%). A sticky ::before pinned
     to the top of the scroll viewport; negative margins let content flow
     under it instead of being pushed down. */
  /* Drop the body's top padding in-chat so the fade sits flush against the
     header bottom (the padding otherwise pushes it down); the thread carries
     the top breathing room instead. */
  .mazlan-drawer--in-chat .mazlan-drawer__body { padding-top: 0; }
  .mazlan-drawer--in-chat .mazlan-drawer__body::before {
    content: '';
    position: sticky;
    top: 0;
    z-index: 4;
    display: block;
    height: 40px;
    /* full-bleed over the body's 24px side padding; -40px bottom keeps it an
       overlay (no layout push). Sits at y=0 now the top padding is gone. */
    margin: 0 calc(var(--praxis-space-24) * -1) -40px;
    background: linear-gradient(to bottom, var(--praxis-color-surface-default) 55%, transparent);
    pointer-events: none;
  }
  body[data-theme="dark"] .mazlan-drawer--in-chat .mazlan-drawer__body::before {
    background: linear-gradient(to bottom, #0e1324 55%, transparent);
  }
  /* Push the first turn clear of the flush fade so nothing is dimmed at rest —
     the fade only bites once the conversation scrolls up under the header. */
  .mazlan-drawer--in-chat .mazlan-thread { padding-top: 44px; }

  /* ===================================================================
     STAGE 5 — in-chat header: summary title, ⋮ options menu, content panel
     =================================================================== */
  /* Chat-only header controls appear once a conversation starts. */
  .mazlan-chat-only { display: none; }
  .mazlan-drawer--in-chat .mazlan-chat-only { display: flex; }
  /* Conversation summary (fills the header's spacer slot). */
  .mazlan-drawer__title--convo {
    flex: 1; min-width: 0;
    font-family: 'Gilroy', sans-serif; font-size: 14px; font-weight: 600;
    color: var(--praxis-color-text-primary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: 8px;
  }
  body[data-theme="dark"] .mazlan-drawer__title--convo { color: #e4e4e7; }
  .mazlan-drawer__title--editing {
    outline: none; cursor: text; overflow-x: auto; text-overflow: clip;
    border-radius: 6px; box-shadow: 0 0 0 2px rgba(41, 210, 215, 0.45);
    padding: 2px 6px; margin: -2px 8px -2px -6px;
  }
  /* ⋮ options dropdown */
  .mazlan-more-wrap { position: relative; }
  .mazlan-more {
    position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
    min-width: 190px; padding: 6px;
    background: var(--praxis-color-surface-default); border-radius: 12px;
    box-shadow: 0 0 0 .5px rgba(16,36,58,.10), 0 12px 32px -8px rgba(16,36,58,.28);
  }
  body[data-theme="dark"] .mazlan-more {
    background: #24303c;
    box-shadow: 0 0 0 .5px rgba(255,255,255,.10), 0 16px 40px -12px rgba(0,0,0,.7);
  }
  .mazlan-more[hidden] { display: none; }
  .mazlan-more__item {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 9px 10px; border: 0; border-radius: 8px; background: none; cursor: pointer; text-align: left;
    font-family: 'Gilroy', sans-serif; font-size: 13px; font-weight: 500; color: var(--praxis-color-text-primary);
  }
  body[data-theme="dark"] .mazlan-more__item { color: #e4e4e7; }
  .mazlan-more__item:hover { background: rgba(16,36,58,.06); }
  body[data-theme="dark"] .mazlan-more__item:hover { background: rgba(255,255,255,.06); }
  .mazlan-more__item .material-symbols-rounded { font-size: 17px; color: var(--praxis-color-text-tertiary); }
  body[data-theme="dark"] .mazlan-more__item .material-symbols-rounded { color: #9ca3af; }
  .mazlan-more__item--danger,
  .mazlan-more__item--danger .material-symbols-rounded { color: #dc2626; }
  body[data-theme="dark"] .mazlan-more__item--danger,
  body[data-theme="dark"] .mazlan-more__item--danger .material-symbols-rounded { color: #f87171; }
  .mazlan-more__divider { height: 1px; margin: 6px 4px; background: rgba(16,36,58,.08); }
  body[data-theme="dark"] .mazlan-more__divider { background: rgba(255,255,255,.08); }

  /* Content panel — slides in from the right over the chat surface. */
  .mazlan-content {
    position: absolute; inset: 0; z-index: 55;
    display: flex; flex-direction: column; overflow: hidden;
    background: var(--praxis-color-surface-default); border-radius: 24px;
    transform: translateX(100%);
    transition: transform 250ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  }
  body[data-theme="dark"] .mazlan-content { background: #0e1324; }
  .mazlan-drawer--content-open .mazlan-content { transform: translateX(0); }
  .mazlan-content__head { display: flex; align-items: center; gap: 8px; padding: 16px; flex-shrink: 0; }
  .mazlan-content__title { flex: 1; font-family: 'Gilroy', sans-serif; font-size: 14px; font-weight: 600; color: var(--praxis-color-text-primary); }
  body[data-theme="dark"] .mazlan-content__title { color: #e4e4e7; }
  .mazlan-content__body { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 12px 16px; display: flex; flex-direction: column; gap: 6px; }
  .mazlan-content__item {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 12px; border: 0; border-radius: 12px; background: none; cursor: pointer; text-align: left;
  }
  .mazlan-content__item:hover { background: rgba(16,36,58,.05); }
  body[data-theme="dark"] .mazlan-content__item:hover { background: rgba(255,255,255,.05); }
  .mazlan-content__icon {
    width: 36px; height: 36px; border-radius: 8px; flex-shrink: 0;
    display: grid; place-items: center; background: rgba(16,36,58,.05); color: var(--praxis-color-interactive-default);
  }
  body[data-theme="dark"] .mazlan-content__icon { background: #1F2A3C; color: #5CE0E5; }
  .mazlan-content__icon .material-symbols-rounded { font-size: 20px; }
  .mazlan-content__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .mazlan-content__item-title { font-family: 'Gilroy', sans-serif; font-size: 14px; font-weight: 500; color: var(--praxis-color-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  body[data-theme="dark"] .mazlan-content__item-title { color: #e4e4e7; }
  .mazlan-content__meta { font-size: 12px; color: var(--praxis-color-text-tertiary); }

  /* Typing indicator while Mazlan is "thinking" */
  .mazlan-typing {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: var(--praxis-space-12);
    background: var(--praxis-color-surface-subtle);
    border: 1px solid var(--praxis-color-border-subtle);
    border-radius: 14px;
    border-bottom-left-radius: 4px;
  }
  .mazlan-typing__dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--praxis-color-pink-60, #e30072);
    animation: mazlanTypingPulse 1.2s ease-in-out infinite;
  }
  .mazlan-typing__dot:nth-child(2) { animation-delay: 0.15s; }
  .mazlan-typing__dot:nth-child(3) { animation-delay: 0.3s; }
  @keyframes mazlanTypingPulse {
    0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
    30% { opacity: 1; transform: translateY(-2px); }
  }

  /* Footer chat input — taller, with toolbar (Mazlan mark + action icons + send).
     Vocabulary mirrors Mazlan Builder + Ideagen Workspace + the custom widget
     wizard: textarea on top, bottom-row toolbar with attach / settings /
     voice-to-text / voice input on the left and a circular send button on
     the right. */
  .mazlan-drawer__footer {
    padding: var(--praxis-space-8) var(--praxis-space-24) var(--praxis-space-24);
    background: var(--praxis-color-surface-default);
    flex-shrink: 0;
  }
  /* Composer edge — copies the prototype's .input-box trick: the border is
     NOT a real border (transparent) but a box-shadow stack. A faint 0.5px
     spread ring wraps all four edges, and an inset top-inner highlight adds a
     brighter line along the TOP only — so the "border" reads bright at the top
     and fades down the sides. Constant across every state (no focus ring). */
  .mazlan-drawer__input {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: var(--praxis-space-16);
    background: var(--praxis-color-surface-default);
    border: 1px solid transparent;
    border-radius: 20px;
    box-shadow:
      0 0 0 0.5px rgba(16, 36, 58, 0.06),
      0 2px 4px rgba(16, 36, 58, 0.05),
      0 10px 28px -12px rgba(16, 36, 58, 0.17),
      inset 0 0.7px 0 rgba(255, 255, 255, 0.7);
  }
  /* Dark: flat slate fill on the navy body; same top-down fade edge. */
  body[data-theme="dark"] .mazlan-drawer__input {
    background: #202b39;
    box-shadow:
      0 0 0 0.5px rgba(255, 255, 255, 0.08),
      0 2px 4px rgba(0, 0, 0, 0.28),
      0 12px 30px -12px rgba(0, 0, 0, 0.52),
      inset 0 0.7px 0 rgba(255, 255, 255, 0.12);
  }
  /* Textarea — v3 typography vocabulary. No leading icon (the brand mark
     is the dark header above). */
  .mazlan-drawer__textarea {
    width: 100%;
    border: 0;
    outline: none;
    resize: none;
    font: inherit;
    font-size: 16px;
    font-weight: 500;
    color: var(--praxis-color-text-primary);
    line-height: 1.75;
    background: transparent;
    padding-left: 8px;
    margin-bottom: 2px;
    min-height: 28px;
    max-height: 200px;
    overflow-y: auto;
  }
  .mazlan-drawer__textarea::placeholder { color: #767676; }

  /* Toolbar at the bottom of the input — left group (attach, settings),
     right group (voice-to-text, voice input, send). */
  .mazlan-drawer__toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--praxis-space-8);
  }
  .mazlan-drawer__tools-left,
  .mazlan-drawer__tools-right {
    display: flex;
    align-items: center;
    gap: var(--praxis-space-4);
    min-width: 0;
  }
  /* Action icons — v3 sizing: 44×44 circular ghost buttons */
  .mazlan-tool-btn {
    width: 44px; height: 44px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    /* --praxis-color-text-tertiary isn't defined on every page (search/record/
       admin don't load concierge), which left the tool icons a different tint
       per page. Use --praxis-color-text-secondary, defined identically app-wide. */
    color: var(--praxis-color-text-secondary);
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background 0.15s, color 0.15s;
    flex-shrink: 0;
  }
  .mazlan-tool-btn:hover {
    background: #f3f4f6;
    color: var(--praxis-color-text-primary);
  }
  .mazlan-tool-btn:focus-visible {
    outline: 2px solid var(--praxis-color-interactive-default);
    outline-offset: 2px;
  }
  .mazlan-tool-btn .material-symbols-rounded { font-size: 20px; }

  /* Primary button — square 44×44 with 12px radius. Used for both dictate
     (default) and send (swapped in when textarea has content). */
  .mazlan-drawer__primary {
    flex-shrink: 0;
    width: 44px; height: 44px;
    border: 0;
    border-radius: 12px;
    background: var(--praxis-color-interactive-default);
    color: white;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background 0.15s, transform 0.1s;
  }
  .mazlan-drawer__primary:hover { background: var(--praxis-color-interactive-hover, #167077); }
  .mazlan-drawer__primary:active { transform: scale(0.93); }
  .mazlan-drawer__primary .material-symbols-rounded { font-size: 20px; }
  /* Dark: --praxis-color-interactive-default brightens to cyan, and a white glyph
     on it glares. Dark ink instead — the same --px-primary-fg the other CTAs
     use on the bright cyan in dark. */
  body[data-theme="dark"] .mazlan-drawer__primary { color: var(--px-primary-fg, #08313a); }

  /* Scope picker — pill that names the search scope (workspace / site / network) */
  .mazlan-scope-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: #6b7785;
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s;
    flex-shrink: 0;
  }
  .mazlan-scope-btn:hover { background: #f3f4f6; color: var(--praxis-color-text-primary); }

  /* + button dropdown menu (Take photo / Attach / Record) */
  .mazlan-plus-wrap { position: relative; }
  .mazlan-dropdown {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 0;
    background: var(--praxis-color-surface-default);
    border-radius: 14px;
    padding: 6px;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.04);
    z-index: 60;
    min-width: 220px;
  }
  .mazlan-dropdown[hidden] { display: none; }
  .mazlan-dropdown__item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    cursor: pointer;
    color: var(--praxis-color-text-primary);
    font: inherit;
    text-align: left;
    transition: background 0.15s;
  }
  .mazlan-dropdown__item:hover { background: rgba(0, 0, 0, 0.04); }
  .mazlan-dropdown__icon {
    color: var(--praxis-color-text-tertiary);
    flex-shrink: 0;
    font-size: 20px !important;
  }
  .mazlan-dropdown__title {
    font-size: 14px;
    font-weight: 500;
  }
  .mazlan-drawer__send:disabled {
    background: var(--praxis-color-border-default);
    cursor: not-allowed;
    opacity: 0.6;
    transform: none;
  }
  .mazlan-drawer__send .material-symbols-rounded { font-size: 20px; }

  /* Narrow: full-width drawer on small screens */
  @media (max-width: 600px) {
    .mazlan-drawer { width: 100vw; }
    .mazlan-suggestions__grid { grid-template-columns: 1fr; }
  }

  /* Respect reduced motion */
  @media (prefers-reduced-motion: reduce) {
    .mazlan-drawer { transition: none; }
    .mazlan-scrim { transition: none; }
    .mazlan-typing__dot { animation: none; opacity: 0.6; }
  }

/* ===========================================================================
   praxis-admin.css  —  Praxis 0.1.15
   ========================================================================== */
/* =====================================================================
   PRAXIS ADMIN — shared stylesheet for the Administration section.

   The admin section is a set of standalone pages (admin-users.html,
   admin-notifications.html, …). Each page carries the same shell
   (appbar + icon nav-rail + a labeled admin side-nav + page header +
   toolbar) and pulls ALL of its visual treatment from this one file so
   the ten screens read as a single, consistent section.

   Built in the Praxis / Mazlan design language (see praxis-core.css):
   --px-* layered slate surfaces w/ lit edges, 8/12/16 geometry, teal
   accent, light + dark via body[data-theme]. This file self-contains the
   base EHSQ palette + shell layout so an admin page only needs to link:
     praxis-core · praxis-appbar · praxis-navrail · praxis-pageheader ·
     praxis-create-new · praxis-module-selector · praxis-admin (this).
   ===================================================================== */

/* ============================================================ FONTS */
/* ==================================================== DS TOKENS (base
   EHSQ palette — identical vocabulary to record/search/workspace) */
/* Foundation tokens (--praxis-*) moved to praxis-tokens.css, the single
   source. This file had its own 67-token copy, which meant admin pages
   rendered a different radius scale and text-secondary than the rest of
   the app depending on load order. Link praxis-tokens.css before this. */
:root{

  --appbar-h:64px; --navrail-w:56px; --adminnav-w:260px;
  /* Was a dedicated amber (#fbe6c2 / rgba(245,196,90,.20)) — a few units per
     channel from --praxis-tone-warning-bg (--praxis-color-orange-10,
     #fdf2e6), so a selected row and a warning-toned row read as the same
     colour. teal-10 is already this system's selection fill (see the
     dropdown's aria-checked state); aliasing it here instead of carrying a
     second, near-identical selection colour resolves in both themes without
     a separate dark declaration. */
  --admin-row-selected:var(--praxis-color-teal-10);
}

/* ================================================================ BASE */
*{box-sizing:border-box;}
html,body{height:100%;}
body{margin:0;font-family:var(--praxis-type-font-sans);color:var(--praxis-color-text-primary);
  font-size:var(--praxis-type-size-base);line-height:1.5;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
button{font-family:inherit;cursor:pointer;color:inherit;}
a{color:var(--praxis-color-text-link);text-decoration:none;}
a:hover{text-decoration:underline;}
svg{display:block;}
.icon{width:22px;height:22px;fill:currentColor;flex-shrink:0;}
.icon--20{width:20px;height:20px;}.icon--18{width:18px;height:18px;}.icon--16{width:16px;height:16px;}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);}
.app{display:flex;flex-direction:column;height:100vh;overflow:hidden;}

/* ============================================================= APP BAR
   Container positioning only — fill/brand/search/right cluster come from
   praxis-appbar.css. */
.appbar{position:relative;height:var(--appbar-h);flex-shrink:0;display:flex;align-items:center;z-index:30;}
/* Page switcher (next to the logo) */
.appswitch{position:relative;}
.appswitch__trigger{display:inline-flex;align-items:center;gap:4px;height:32px;padding:0 8px 0 10px;border:0;background:transparent;color:inherit;font-family:inherit;font-size:.875rem;font-weight:600;border-radius:var(--praxis-radius-md);cursor:pointer;}
.appswitch__trigger:hover{background:rgba(127,127,127,.14);}
.appswitch__pop{position:absolute;top:calc(100% + 6px);left:0;min-width:200px;z-index:200;background:var(--praxis-color-surface-default);border:1px solid var(--praxis-color-border-default);border-radius:var(--praxis-radius-md);padding:6px;box-shadow:var(--praxis-elevation-4);}
.appswitch__pop[hidden]{display:none;}
.appswitch__item{display:block;padding:8px 10px;border-radius:var(--praxis-radius-sm);font-size:.875rem;font-weight:500;color:var(--praxis-color-text-primary);text-decoration:none;}
.appswitch__item:hover{background:var(--praxis-color-neutral-05);text-decoration:none;}
.appswitch__item--active{color:var(--praxis-color-text-link);font-weight:600;}
body[data-theme="dark"] .appswitch__item:hover{background:rgba(255,255,255,.06);}

/* =====================================================================
   Toggle switch (shared: profile appearance, admin toggles, filter On/Off)

   TWO MARKUP FORMS, ONE DEFINITION. Praxis shipped two contradictory ideas of
   what `.switch` means, and until 2026-08-18 only one of them was styled:

     WRAPPER form — canonical. `.switch` is the wrapper; the input is inside it.
       <span class="switch">
         <input type="checkbox">
         <span class="switch__track"></span><span class="switch__thumb"></span>
       </span>

     SIBLING form — what praxis-filters.js emits. `.switch` is ON the input and
     the track is its next sibling.
       <input type="checkbox" class="switch">
       <span class="switch__track"><span class="switch__thumb"></span></span>

   The sibling form rendered as a BARE BROWSER CHECKBOX between two labels: no
   sheet defined .switch__track or .switch__thumb, and `class="switch"` on the
   input also opts it out of the Praxis checkbox rules in praxis-core.css, which
   are keyed `input[type="checkbox"]:not(.switch):not(.switch *)`.

   WHY THE WRAPPER IS CANONICAL AND THE SIBLING FORM IS SUPPORTED ANYWAY
   The wrapper form works and real pages use it, and praxis-core.css is already
   committed to it — the `:not(.switch *)` clause in that exclusion only means
   anything if `.switch` can have descendants. So the wrapper wins.
   But praxis-filters.css reads `.onoff:has(.switch:checked)`, which only matches
   with the class on the input, so its sheet and its script agree with each other.
   Rewriting either would mean touching the JS and that `:has()` selector to fix a
   problem that CSS alone can close. So both forms are defined here, from one set
   of values, and they render identically.

   PART NAMES: `.switch__track` / `.switch__thumb` are canonical. The original
   `.track` / `.thumb` still work and are kept because consumers use them, but
   they are two of the most collision-prone class names it is possible to put in a
   shared stylesheet, so new markup should not use them.
   ===================================================================== */
.switch{position:relative;display:inline-flex;align-items:center;width:34px;height:20px;flex-shrink:0;}
.switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer;}
.switch .track,.switch .switch__track{position:absolute;inset:0;border-radius:999px;background:var(--praxis-color-neutral-30);transition:background var(--praxis-motion-fast);}
.switch .thumb,.switch .switch__thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--praxis-color-white);box-shadow:0 1px 2px rgba(16,36,58,.35);transition:transform var(--praxis-motion-fast);pointer-events:none;}
.switch input:checked + .track,.switch input:checked + .switch__track{background:var(--praxis-color-teal-60);}
.switch input:checked ~ .thumb,.switch input:checked ~ .switch__thumb{transform:translateX(14px);}
.switch input:focus-visible + .track,.switch input:focus-visible + .switch__track{outline:2px solid var(--praxis-color-border-focus);outline-offset:2px;}

/* Sibling form. `input.switch` matches ONLY this form — in the wrapper form the
   input carries no class — so these rules cannot reach the markup above.
   The input is collapsed rather than absolutely positioned because its parent
   (.onoff, a <label>) is not a positioned ancestor; the label makes the whole row
   clickable, which is the better target anyway. */
input.switch{width:0;height:0;opacity:0;margin:0;padding:0;border:0;flex:none;}
input.switch + .switch__track{position:relative;display:inline-block;width:34px;height:20px;flex-shrink:0;border-radius:999px;background:var(--praxis-color-neutral-30);transition:background var(--praxis-motion-fast);cursor:pointer;}
input.switch + .switch__track .switch__thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--praxis-color-white);box-shadow:0 1px 2px rgba(16,36,58,.35);transition:transform var(--praxis-motion-fast);pointer-events:none;}
input.switch:checked + .switch__track{background:var(--praxis-color-teal-60);}
input.switch:checked + .switch__track .switch__thumb{transform:translateX(14px);}
input.switch:focus-visible + .switch__track{outline:2px solid var(--praxis-color-border-focus);outline-offset:2px;}
/* Profile menu — shared component, see praxis-profile-menu.css */

/* ============================================================== LAYOUT */
.main{flex:1;display:flex;min-height:0;}
/* Nav-rail CONTAINER (contents styled by praxis-navrail.css) */
.praxis-navrail{display:flex;flex-direction:column;justify-content:space-between;width:var(--navrail-w);flex-shrink:0;
  background:var(--praxis-color-surface-default);border-right:1px solid var(--praxis-color-neutral-20);box-sizing:border-box;z-index:20;}
/* --ph-pad-x sits here rather than on .pageheader so the toolbar band and
   .admin-body / .rm-body inherit the same inset and everything below the
   header lines up against one number. */
.content{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;--ph-pad-x:24px;}

/* Shared nav-rail popover material (Create New / Dashboards flyouts) */
.px-pop{position:absolute;z-index:200;background:var(--praxis-color-surface-default);border:1px solid var(--praxis-color-border-default);border-radius:12px;padding:6px;box-shadow:var(--praxis-elevation-4);}
.px-pop[hidden]{display:none;}
body[data-variant="praxis"] .px-pop{border:0;box-shadow:var(--px-overlay);}

/* ============================================================= TOOLBAR
   The band itself — height, inset, page fade and closing hairline — is the
   shared component in praxis-pageheader.css. Only the controls are local. */
.tbtn{display:inline-flex;align-items:center;gap:7px;height:40px;padding:0 14px;border:0;border-radius:10px;background:var(--px-tool);color:var(--praxis-color-text-primary);font-size:.875rem;font-weight:600;box-shadow:var(--px-tool-shadow);transition:box-shadow var(--praxis-motion-fast),background var(--praxis-motion-fast),color var(--praxis-motion-fast);}
.tbtn .icon{color:var(--praxis-color-text-secondary);}
.tbtn .material-symbols-rounded{font-size:20px;color:var(--praxis-color-text-secondary);}
.tbtn:hover{box-shadow:var(--px-tool-shadow-hover);color:var(--praxis-color-text-primary);}
/* .ws-item:hover and .praxis-navrail__link:hover both suppress the hover
   underline because Praxis anticipates either being rendered as <a href>
   (see .tbtn[aria-disabled="true"] below, added for the same reason) — this
   one didn't, so a .tbtn rendered as a link underlined on hover while every
   other anchor-capable component in the system did not. */
.tbtn:hover,.tbtn:focus-visible{text-decoration:none;}
.tbtn:active{background:var(--px-surface-2);}
.tbtn--icon{width:40px;padding:0;justify-content:center;}
.tbtn--primary{background:var(--px-primary-grad);color:var(--praxis-color-white);box-shadow:var(--px-primary-shadow);padding:0 18px;}
.tbtn--primary .icon,.tbtn--primary .material-symbols-rounded{color:var(--praxis-color-white);}
.tbtn--primary:hover{filter:brightness(1.05);color:var(--praxis-color-white);box-shadow:var(--px-primary-shadow);}
/* ghost variant — flat labelled action (used by Search Logs / Activity) */
.tbtn--ghost{background:transparent;box-shadow:none;}
/* Disabled had no APPEARANCE, only suppressed behaviour. praxis-core.css excludes
   :disabled and [aria-disabled="true"] from the hover wash and the press
   transform, so a disabled button stopped responding while looking identical to an
   enabled one at rest — indistinguishable in a screenshot, and worse than useless
   to a user deciding whether to click. Added 2026-08-18.

   Opacity rather than a flat grey so it works on every variant at once, including
   .tbtn--primary's gradient, which no single colour could stand in for.
   [aria-disabled] is covered too: a link styled as a button cannot take :disabled. */
.tbtn:disabled,.tbtn[aria-disabled="true"],
.admin-ghostbtn:disabled,.admin-ghostbtn[aria-disabled="true"]{
  opacity:.45;cursor:not-allowed;box-shadow:none;
}
.tbtn--ghost:hover{background:var(--px-hover);box-shadow:none;}

/* ===================================================================
   ADMIN SIDE-NAV — the labeled panel that lets the user jump between
   admin screens (replaces the old tile launcher). Sits between the icon
   rail and the page content.
   =================================================================== */
.adminnav{width:var(--adminnav-w);flex-shrink:0;display:flex;flex-direction:column;min-height:0;
  /* Solid page fill so the fixed dot-grid does not show through this nav panel at all. */
  background:var(--px-page);
  border-right:1px solid var(--praxis-color-border-default);z-index:15;}
.adminnav__head{padding:16px 18px 8px;}
.adminnav__title{margin:0;font-size: 1rem;font-weight:700;color:var(--praxis-color-text-primary);line-height:1.2;}
.adminnav__sub{display:block;margin-top:2px;font-size:.75rem;color:var(--praxis-color-text-secondary);}
.adminnav__filter{position:relative;margin:2px 14px 6px;}
.adminnav__filter .material-symbols-rounded{position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:18px;color:var(--praxis-color-text-secondary);pointer-events:none;}
.adminnav__filter input{width:100%;height:36px;padding:0 12px 0 34px;border:0;border-radius:var(--praxis-radius-md);background:var(--px-chip);color:var(--praxis-color-text-primary);font:inherit;font-size:.8125rem;}
.adminnav__filter input::placeholder{color:var(--praxis-color-text-secondary);}
.adminnav__filter input:focus{outline:2px solid var(--praxis-color-border-focus);outline-offset:1px;}
.adminnav__scroll{flex:1;overflow-y:auto;padding:12px 10px 18px;}
.adminnav__group{padding:14px 10px 4px;font-size:.6875rem;font-weight:800;letter-spacing:normal;color:var(--praxis-color-text-secondary);}
.adminnav__item{position:relative;display:flex;align-items:center;gap:12px;min-height:40px;padding:0 12px;border-radius:10px;
  color:var(--praxis-color-text-secondary);text-decoration:none;font-size:.875rem;font-weight:600;line-height:1.2;
  transition:background var(--praxis-motion-fast),color var(--praxis-motion-fast);}
.adminnav__item:hover{background:var(--px-hover);color:var(--praxis-color-text-primary);text-decoration:none;}
.adminnav__item:focus-visible{outline:2px solid var(--praxis-color-border-focus);outline-offset:-2px;}
.adminnav__item .material-symbols-rounded{font-size:20px;flex-shrink:0;}
.adminnav__item--active{background:var(--praxis-color-teal-10);color:var(--praxis-color-teal-80);}
.adminnav__item--active:hover{background:var(--praxis-color-teal-10);color:var(--praxis-color-teal-80);}
.adminnav__item--active::before{content:"";position:absolute;left:4px;top:9px;bottom:9px;width:3px;border-radius:2px;background:var(--praxis-color-teal-60);}
body[data-theme="dark"] .adminnav__item--active{background:rgba(41,210,215,.14);color:#5CE0E5;}
body[data-theme="dark"] .adminnav__item--active::before{background:#29D2D7;}
.adminnav__badge{margin-left:auto;font-size: 0.6875rem;font-weight:800;letter-spacing:.05em;padding:2px 6px;border-radius:5px;background:var(--praxis-color-pink-50);color:var(--praxis-color-white);}

/* ===================================================================
   ADMIN BODY — the scrolling content region below the toolbar.
   =================================================================== */
.admin-body{flex:1;min-height:0;overflow-y:auto;padding:var(--px-toolbar-gutter,16px) var(--ph-pad-x,24px) 32px;
  display:flex;flex-direction:column;gap:18px;}
/* two-column split (Users, Reporting Authorities, Components) */
.admin-cols{display:grid;gap:18px;align-items:start;}
.admin-cols--2{grid-template-columns:1fr 1fr;}
.admin-cols--aside{grid-template-columns:minmax(280px,340px) 1fr;}  /* narrow left + wide right */
.admin-cols--main{grid-template-columns:1fr minmax(320px,420px);}  /* wide left + narrow right */

/* ---- Card ---- */
.admin-card{background:var(--px-surface);border-radius:var(--praxis-radius-md);box-shadow:var(--praxis-card);padding:20px 22px;}
.admin-card--flush{padding:16px 18px;}
.admin-card__title{margin:0 0 16px;font-size:1rem;font-weight:700;color:var(--praxis-color-text-primary);
  display:flex;align-items:center;gap:8px;}
.admin-card__hint{margin-left:6px;width:16px;height:16px;border-radius:50%;background:var(--praxis-color-neutral-15);
  color:var(--praxis-color-text-secondary);font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;}
.admin-subhead{margin:6px 0 14px;font-size: 0.875rem;font-weight:700;color:var(--praxis-color-text-primary);}

/* ---- Fields (flat label + underlined control) ---- */
.admin-grid{display:grid;gap:18px 34px;}
.admin-grid--2{grid-template-columns:repeat(2,minmax(0,1fr));}
.admin-grid--3{grid-template-columns:repeat(3,minmax(0,1fr));}
.admin-grid--4{grid-template-columns:repeat(4,minmax(0,1fr));}
.admin-grid--6{grid-template-columns:repeat(6,minmax(0,1fr));}
.admin-field{position:relative;display:flex;flex-direction:column;gap:5px;min-width:0;}
.admin-field__label{font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-secondary);}
.admin-field__label .req{color:var(--praxis-color-red-60);margin-left:2px;}
/* ---- Fields: the same treatment as the record fields (.rfield__control in
   praxis-rfield.css) — no border, --px-field fill carrying the affordance,
   chevron and trailing icons inset 12px. Was an underline on a transparent
   background, which read as a different control system to the record pages'.
   Height stays 34px rather than the record pages' 40: admin screens run
   several fields to a row inside fixed grids, and the shared thing is the
   treatment, not the density. ---- */
.admin-field__control{height:34px;width:100%;border:0;border-radius:var(--praxis-radius-md);
  background-color:var(--px-field);font:inherit;font-size:.875rem;
  color:var(--praxis-color-text-primary);padding:0 12px;
  transition:background-color var(--praxis-motion-fast),box-shadow var(--praxis-motion-fast);}
/* text-disabled measured 2.73:1 against this fill — under 1.4.3. text-secondary
   is the lightest token that clears 4.5:1; weight does the rest of the work of
   separating a placeholder from a real value. */
.admin-field__control::placeholder{color:var(--praxis-color-text-secondary);font-weight:500;opacity:1;}
.admin-field__control:hover:not(:disabled){background-color:var(--px-field-hover);}
/* No resting border, so focus draws an inset ring — inside the box, so nothing
   reflows in a grid of fields when one takes focus. */
.admin-field__control:focus{outline:none;
  box-shadow:inset 0 0 0 1px var(--praxis-color-interactive-default),
             0 0 0 3px color-mix(in srgb, var(--praxis-color-interactive-default) 22%, transparent);}
/* Chevron drawn by CSS on every select, so it cannot go missing. The old
   .admin-field__caret span was only on some selects, leaving the rest with no
   arrow at all under appearance:none — and praxis-lucide.js rewrites
   material-symbols spans into SVGs it sizes via an injected
   `svg.material-symbols-rounded` rule, which outranks a plain class selector,
   so the span could not be reliably hidden from here either. Removed from
   build-admin.py and profile.html instead. */
select.admin-field__control{appearance:none;-webkit-appearance:none;padding-right:34px;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%235D6977'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center;background-size:20px 20px;}
body[data-theme="dark"] select.admin-field__control{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%239AA7B4'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");}
/* Date, time and datetime-local carry native indicators on the same edge. */
.admin-field__control::-webkit-calendar-picker-indicator{margin-right:2px;}
/* A read-only value is not a field, so it takes no fill and no underline —
   an underlined value next to unbordered inputs read as the editable one. */
.admin-field__value{height:34px;display:flex;align-items:center;font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);}
/* inline label + toggle/value row */
/* Label and control sit together. This was space-between with a flex:1 label,
   which pinned the toggle to the far right of whatever the row happened to be
   — across a full-width card that left a label and its switch a metre apart
   with nothing in between, and no way to tell which label a switch belonged to
   in a stack of them. */
.admin-field--row{flex-direction:row;align-items:center;justify-content:flex-start;gap:12px;}
.admin-field--row .admin-field__label{flex:0 1 auto;}

/* ---- Tabs (underline strip) ---- */
.admin-tabs{display:flex;gap:2px;flex-wrap:wrap;border-bottom:1px solid var(--praxis-color-border-default);margin-bottom:18px;}
.admin-tab{appearance:none;border:0;background:transparent;font:inherit;font-size:.8125rem;font-weight:700;letter-spacing:.02em;color:var(--praxis-color-text-secondary);padding:10px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;}
.admin-tab:hover{color:var(--praxis-color-text-primary);}
.admin-tab--active{color:var(--praxis-color-teal-70);border-bottom-color:var(--praxis-color-teal-60);}
body[data-theme="dark"] .admin-tab--active{color:#5CE0E5;border-bottom-color:#29D2D7;}

/* ---- Data table ---- */
.admin-tabletools{display:flex;align-items:center;gap:6px;padding:0 0 12px;flex-wrap:wrap;}
.admin-ghostbtn{display:inline-flex;align-items:center;gap:8px;height:34px;padding:0 12px;border:0;background:transparent;
  color:var(--praxis-color-text-secondary);font:inherit;font-size:.8125rem;font-weight:700;border-radius:8px;}
.admin-ghostbtn:hover{background:var(--px-hover);color:var(--praxis-color-text-primary);}
.admin-ghostbtn .material-symbols-rounded{font-size:18px;}
.admin-ghostbtn--accent{color:var(--praxis-color-teal-70);}
.admin-ghostbtn--accent .material-symbols-rounded{color:var(--praxis-color-teal-60);}
.admin-table-wrap{border:1px solid var(--praxis-color-border-default);border-radius:var(--praxis-radius-sm);overflow:hidden;background:var(--px-surface);}
.admin-table-scroll{max-height:440px;overflow:auto;}
.admin-table{width:100%;border-collapse:collapse;font-size:.875rem;}
.admin-table thead th{position:sticky;top:0;z-index:1;text-align:left;font-weight:700;font-size:.75rem;letter-spacing:.02em;
  color:var(--praxis-color-text-secondary);padding:11px 14px;background:var(--px-surface-2);
  border-bottom:1px solid var(--praxis-color-border-default);white-space:nowrap;}
.admin-table thead th .req{color:var(--praxis-color-red-60);}
.admin-table tbody td{padding:12px 14px;border-bottom:1px solid var(--praxis-color-border-default);
  color:var(--praxis-color-text-primary);vertical-align:top;}
.admin-table tbody tr:last-child td{border-bottom:0;}
.admin-table tbody tr:hover{background:var(--px-hover);}
.admin-table__row--selected,.admin-table__row--selected:hover{background:var(--admin-row-selected);}
/* The rule above is (0,1,0)/(0,2,0) — the zebra stripe below is (0,2,2) in
   light — so a selected row on an even index rendered the stripe, not the
   selection, and a selected row looked identical to an unselected one on
   every other row. Prefixing the wrap ancestor reaches (0,3,2), which beats
   the light zebra rule unconditionally regardless of sheet order. */
.admin-table-wrap .admin-table tbody tr.admin-table__row--selected,
.admin-table-wrap .admin-table tbody tr.admin-table__row--selected:hover{
  background:var(--admin-row-selected);
}
/* The dark zebra rule is a type-selector heavier — body[data-theme="dark"]
   .admin-table tbody tr:nth-child(even) is (0,3,3) — so the (0,3,2) rule
   above still loses on an even row in dark mode specifically (verified in a
   browser: an even selected row rendered the zebra tint, not the selection
   colour). Same fix as dark-mode row hover: add body[data-theme="dark"] to
   the wrap-ancestor selector to reach (0,4,3), which beats it
   unconditionally. */
body[data-theme="dark"] .admin-table-wrap .admin-table tbody tr.admin-table__row--selected,
body[data-theme="dark"] .admin-table-wrap .admin-table tbody tr.admin-table__row--selected:hover{
  background:var(--admin-row-selected);
}
.admin-table__empty td{padding:22px 14px;text-align:center;color:var(--praxis-color-text-secondary);}
.admin-table__num{text-align:right;font-variant-numeric:tabular-nums;}
.admin-link{color:var(--praxis-color-text-link);font-weight:600;cursor:pointer;}
.admin-rowx{border:0;background:transparent;color:var(--praxis-color-text-secondary);width:30px;height:30px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;}
.admin-rowx:hover{background:var(--px-hover);color:var(--praxis-color-red-60);}
.admin-rowx .material-symbols-rounded{font-size:18px;}
.admin-check{width:16px;height:16px;accent-color:var(--praxis-color-teal-60);cursor:pointer;}
/* status pills (Users grid, etc.)

   WCAG 1.4.3 — the label is 12px/700, which is not large text (large starts at
   18.66px bold), so it needs 4.5:1. Two of the three variants were built from
   palette rungs rather than the audited --praxis-tone-* pairs and measured
   short: --ok was green-60 on green-10 at 3.81:1 and --lock was red-60 on
   red-10 at 3.88:1. Both now take the tone pair, which is one rung darker on
   the ink and the same tint behind it — 4.99:1 and 5.08:1. --off was already
   4.89:1 and is left where it is rather than moved for symmetry.

   --info and --warning were added 2026-08-25: the reference site needed an
   amber and a blue pill and both were missing, so its own pages were reaching
   for .admin-pill--warning and getting the bare shape with no colour. */
.admin-pill{display:inline-flex;align-items:center;height:22px;padding:0 9px;border-radius:999px;font-size:.75rem;font-weight:700;white-space:nowrap;}
.admin-pill--ok{background:var(--praxis-tone-success-bg);color:var(--praxis-tone-success-fg);}
.admin-pill--off{background:var(--praxis-color-neutral-10);color:var(--praxis-color-text-secondary);}
.admin-pill--lock{background:var(--praxis-tone-danger-bg);color:var(--praxis-tone-danger-fg);}
.admin-pill--info{background:var(--praxis-tone-info-bg);color:var(--praxis-tone-info-fg);}
.admin-pill--warning{background:var(--praxis-tone-warning-bg);color:var(--praxis-tone-warning-fg);}
body[data-theme="dark"] .admin-pill--ok{background:rgba(9,139,83,.16);color:#4cd08a;}
body[data-theme="dark"] .admin-pill--off{background:rgba(255,255,255,.08);color:var(--praxis-color-text-secondary);}
body[data-theme="dark"] .admin-pill--lock{background:rgba(226,45,56,.18);color:#f28b91;}
/* The two overrides above now restate what the tone tokens already resolve to
   in dark, and they are kept rather than deleted: praxis-core.css remaps the
   tone pairs under body[data-variant="praxis"][data-theme="dark"], so on a dark
   page that is NOT the Praxis variant the pill would keep its light tint. Same
   reason --info and --warning get none: nothing outside the Praxis variant uses
   them yet. */
/* card header with a right-aligned count/meta */
.admin-card__meta{margin-left:auto;font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-secondary);}
.admin-table-scroll--tall{max-height:560px;}

/* ---- Tinted panel (Details / setting groups / logon options) ---- */
.admin-panel{background:var(--px-surface-2);border-radius:var(--praxis-radius-md);padding:18px 20px;}
.admin-panel__title{margin:0 0 14px;font-size: 0.875rem;font-weight:700;color:var(--praxis-color-text-primary);}

/* ---- Preferences: settings card grid ---- */
.admin-settings-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;}
.admin-setting-card{background:var(--px-surface-2);border-radius:var(--praxis-radius-md);padding:16px 18px;display:flex;flex-direction:column;}
.admin-setting-card__title{margin:0 0 6px;font-size: 0.875rem;font-weight:700;color:var(--praxis-color-text-primary);}
.admin-setting-row{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:38px;
  padding:6px 0;border-bottom:1px solid var(--praxis-color-border-default);}
.admin-setting-row:last-child{border-bottom:0;}
.admin-setting-row__label{font-size:.8125rem;font-weight:600;color:var(--praxis-color-text-secondary);}
.admin-setting-row__value{font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);text-align:right;}
.admin-setting-row__value--muted{color:var(--praxis-color-text-disabled);font-weight:500;}

/* ---- Performance: drill-down stat buttons ---- */
.admin-drill{display:flex;flex-wrap:wrap;align-items:flex-start;gap:24px;}
.admin-drill__date{min-width:120px;}
.admin-drill__date .admin-field__label{margin-bottom:4px;}
.admin-drill__stats{flex:1;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:16px;}
.admin-stat{display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;}
/* Light tool button, not a filled slate slab: these are navigation into a
   report, not primary actions, and a column of dark blocks dominated the page. */
.admin-stat__btn{width:100%;height:40px;min-height:40px;border:0;border-radius:10px;padding:0 14px;
  background:var(--px-surface);color:var(--praxis-color-text-primary);
  font:inherit;font-weight:600;font-size: 0.875rem;line-height:1.2;
  box-shadow:var(--px-tool-shadow);
  display:flex;align-items:center;justify-content:center;text-align:center;
  transition:background var(--praxis-motion-fast),box-shadow var(--praxis-motion-fast),transform 150ms var(--praxis-ease-default);}
.admin-stat__btn:hover{background:var(--px-surface);box-shadow:var(--px-tool-shadow-hover);}
.admin-stat__btn:active{transform:scale(.97);}
body[data-theme="dark"] .admin-stat__btn{background:var(--px-surface-2);color:var(--praxis-color-text-primary);box-shadow:var(--px-tool-shadow);}
.admin-stat__metric{font-size:.8125rem;color:var(--praxis-color-text-secondary);}
.admin-stat__metric b{color:var(--praxis-color-text-primary);font-weight:700;}

/* ---- Info banner (Components) ---- */
.admin-banner{display:flex;gap:12px;align-items:flex-start;background:var(--praxis-color-blue-10);
  border-radius:var(--praxis-radius-md);padding:14px 16px;color:var(--praxis-color-text-primary);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--praxis-color-blue-60) 22%, transparent);}
.admin-banner .material-symbols-rounded{color:var(--praxis-color-blue-60);font-size:22px;flex-shrink:0;}
.admin-banner__body{flex:1;min-width:0;}
.admin-banner__title{font-weight:700;margin-bottom:2px;}
.admin-banner__text{font-size:.875rem;color:var(--praxis-color-text-secondary);line-height:1.5;}
.admin-banner__close{border:0;background:transparent;color:var(--praxis-color-text-secondary);width:30px;height:30px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
.admin-banner__close:hover{background:rgba(16,36,58,.06);color:var(--praxis-color-text-primary);}
body[data-theme="dark"] .admin-banner{background:rgba(71,102,235,.14);}

/* ---- Components: menus list + preview ---- */
.admin-menulist{display:flex;flex-direction:column;}
.admin-menulist__label{font-size:.8125rem;font-weight:700;color:var(--praxis-color-text-secondary);padding:4px 4px 8px;}
.admin-menurow{display:flex;align-items:center;gap:10px;padding:12px 6px;border-bottom:1px solid var(--praxis-color-border-default);}
.admin-menurow__name{flex:1;font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);}
.admin-menurow__act{border:0;background:transparent;color:var(--praxis-color-text-secondary);width:30px;height:30px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;}
.admin-menurow__act:hover{background:var(--px-hover);color:var(--praxis-color-text-primary);}
.admin-menurow__act .material-symbols-rounded{font-size:18px;}
.admin-addmenu{display:inline-flex;align-items:center;gap:10px;margin-top:14px;border:0;background:transparent;color:var(--praxis-color-teal-70);font:inherit;font-size:.875rem;font-weight:700;padding:6px 4px;}
.admin-addmenu .material-symbols-rounded{color:var(--praxis-color-teal-60);font-size:22px;}
.admin-preview__label{font-size:.8125rem;font-weight:700;color:var(--praxis-color-text-secondary);margin-bottom:12px;}
.admin-cngroup{margin-bottom:18px;}
.admin-cngroup__title{margin:0 0 8px;font-size: 1rem;font-weight:700;color:var(--praxis-color-text-primary);}
.admin-cngrid{display:grid;grid-template-columns:1fr 1fr;gap:10px;background:var(--px-surface-2);border-radius:10px;padding:12px;}
.admin-cnbtn{display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--px-surface);border-radius:8px;
  padding:11px 14px;box-shadow:var(--px-tool-shadow);font-size:.875rem;font-weight:600;color:var(--praxis-color-text-primary);}
.admin-cnbtn .material-symbols-rounded{color:var(--praxis-color-teal-60);font-size:20px;}

/* ---- Search Logs: helper note + color legend ---- */
.admin-note{font-size:.875rem;color:var(--praxis-color-text-secondary);line-height:1.6;font-style:italic;}
.admin-note .lg-tm{color:var(--praxis-color-blue-60);font-style:normal;font-weight:700;}
.admin-note .lg-jv{color:var(--praxis-color-green-60);font-style:normal;font-weight:700;}
.admin-note .lg-cs{color:var(--praxis-color-pink-60);font-style:normal;font-weight:700;}
.admin-note .lg-err{color:var(--praxis-color-red-60);font-style:normal;font-weight:800;}

/* ===================================================================
   GLOBAL CHROME OVERLAYS — the shared app-header / nav-rail popovers that
   work on every page: nav-rail popover rows (px-menu), the Dashboards
   popout (ws-pop), search suggestions (ss-pop), and the Mazlan drawer
   (mz-*). Create New (cn-*) and the module selector (msel-*) get their CSS
   from praxis-create-new.css / praxis-module-selector.css. Ported from the
   record page so behaviour + look match app-wide.
   =================================================================== */

/* Mazlan four-dot brand mark (app-header pill + drawer greeting) */
.mazlan-mark{position:relative;display:inline-block;width:20px;height:20px;flex-shrink:0;vertical-align:middle;}
.mazlan-mark--sm{width:16px;height:16px;}
.mazlan-mark>span{position:absolute;width:25%;height:25%;border-radius:50%;display:block;}
.mazlan-mark>span:nth-child(1){left:12.5%;top:12.5%;background:#2F4051;}
.mazlan-mark>span:nth-child(2){right:12.5%;top:12.5%;background:#E30072;}
.mazlan-mark>span:nth-child(3){left:12.5%;bottom:12.5%;background:#5CC9D0;}
.mazlan-mark>span:nth-child(4){right:12.5%;bottom:12.5%;background:var(--praxis-color-teal-60);}
body[data-theme="dark"] .mazlan-mark>span:nth-child(1){background:#d1d5db;}

/* generic overlay rows (used by the Dashboards popout) */
.px-menu__head{padding:8px 10px 4px;font-size:.6875rem;font-weight:700;letter-spacing:normal;color:var(--praxis-color-text-secondary);}
.px-menu__item{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:transparent;
  border-radius:8px;cursor:pointer;text-align:left;font-family:inherit;font-size:.875rem;font-weight:500;
  color:var(--praxis-color-text-primary);text-decoration:none;}
.px-menu__item:hover{background:var(--px-hover);text-decoration:none;}
.px-menu__sep{height:1px;background:var(--praxis-color-border-default);margin:6px 4px;}

/* ---- Dashboards / workspaces popout ---- */
.ws-pop{top:0;left:calc(100% + 8px);width:300px;max-height:70vh;overflow-y:auto;}
body[data-variant="praxis"] .ws-pop{
  background:color-mix(in srgb, var(--px-surface) 60%, transparent);
  -webkit-backdrop-filter:saturate(1.35) blur(16px);backdrop-filter:saturate(1.35) blur(16px);
  box-shadow:var(--px-overlay), inset 0 .5px 0 var(--px-edge);
  border-radius:14px;padding:10px;transform-origin:top left;}
body[data-variant="praxis"] .ws-pop .px-menu__head{
  font-size:16px;font-weight:700;letter-spacing:0;text-transform:none;color:var(--praxis-color-text-primary);padding:6px 8px 12px;}
.ws-item{display:flex;align-items:center;gap:12px;width:100%;padding:8px 10px;border:0;background:transparent;
  border-radius:10px;cursor:pointer;text-align:left;font-family:inherit;color:var(--praxis-color-text-primary);text-decoration:none;}
.ws-item:hover{background:var(--px-hover);text-decoration:none;}
.ws-item:focus-visible{outline:2px solid var(--praxis-color-teal-50);outline-offset:-2px;}
.ws-item__icon{width:34px;height:34px;flex-shrink:0;border-radius:9px;display:flex;align-items:center;justify-content:center;
  background:var(--praxis-color-neutral-10);color:var(--praxis-color-text-secondary);}
.ws-item__icon .icon{width:19px;height:19px;}
.ws-item--home .ws-item__icon{background:var(--praxis-color-teal-10);color:var(--praxis-color-teal-80);}
.ws-item--new .ws-item__icon{background:transparent;color:var(--praxis-color-interactive-default);}
body[data-variant="praxis"] .ws-pop .ws-item:not(.ws-item--home):not(.ws-item--new) .ws-item__icon{
  background:var(--px-surface);box-shadow:var(--px-tool-shadow);color:var(--praxis-color-text-secondary);}
.ws-item__text{display:flex;flex-direction:column;min-width:0;flex:1;}
.ws-item__name{font-size:.875rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ws-item__sub{font-size:.75rem;color:var(--praxis-color-text-secondary);}

/* ---- Search suggestions ---- */
.ss-pop{position:absolute;top:calc(100% + 8px);left:0;right:0;z-index:210;padding:6px;
  background:var(--praxis-color-surface-default);border:1px solid var(--praxis-color-border-default);border-radius:12px;
  box-shadow:var(--praxis-elevation-4);max-height:60vh;overflow-y:auto;}
.ss-pop[hidden]{display:none;}
body[data-variant="praxis"] .ss-pop{border:0;box-shadow:var(--px-overlay);}
body[data-variant="praxis"][data-theme="dark"] .ss-pop{background:rgba(26,32,45,.94);
  -webkit-backdrop-filter:saturate(1.3) blur(16px);backdrop-filter:saturate(1.3) blur(16px);}
.ss-sec__head{padding:8px 10px 4px;font-size:.6875rem;font-weight:700;letter-spacing:normal;color:var(--praxis-color-text-secondary);}
.ss-row{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:transparent;
  border-radius:8px;cursor:pointer;text-align:left;font-family:inherit;font-size:.875rem;font-weight:500;color:var(--praxis-color-text-primary);}
.ss-row:hover{background:var(--px-hover);}
.ss-row .icon{width:18px;height:18px;color:var(--praxis-color-text-secondary);flex-shrink:0;}
.ss-row mark{background:transparent;color:var(--praxis-color-interactive-default);font-weight:700;}
.ss-row[hidden]{display:none;}

/* ===================================================================
   RESPONSIVE — mirrors the app-wide 1024 / 768 / 480 cascade.
   =================================================================== */
@media (max-width:1024px){
  .admin-settings-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
  .admin-drill__stats{grid-template-columns:repeat(3,minmax(0,1fr));}
  .admin-cols--2,.admin-cols--aside,.admin-cols--main{grid-template-columns:1fr;}
  .admin-grid--4,.admin-grid--6{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:768px){
  :root{--adminnav-w:224px;}
  .admin-grid--2,.admin-grid--3{grid-template-columns:1fr;}
  .admin-settings-grid{grid-template-columns:1fr;}
  .admin-drill__stats{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:640px){
  /* Collapse the labeled side-nav to an icon-only rail on small screens */
  .adminnav{width:60px;}
  .adminnav__head,.adminnav__filter,.adminnav__group span,.adminnav__item span:not(.material-symbols-rounded),.adminnav__badge{display:none;}
  .adminnav__group{padding:10px 0 2px;text-align:center;}
  .adminnav__item{justify-content:center;padding:0;}
  .adminnav__scroll{padding:6px 8px;}
}
@media (max-width:480px){
  .admin-grid--4,.admin-grid--6,.admin-drill__stats{grid-template-columns:1fr;}
  .appswitch{display:none;}
}


/* ---------------------------------------------------------------------
   Admin tables — same treatment as the search table: the header carries no
   fill (the rule and the weight do the work) and the body is zebra striped.
   --------------------------------------------------------------------- */
.admin-table thead th{ background:var(--px-surface); }
.admin-table tbody tr:nth-child(even){ background:var(--px-hover-btn); }
body[data-theme="dark"] .admin-table thead th{ background:var(--px-surface); }
body[data-theme="dark"] .admin-table tbody tr:nth-child(even){ background:rgba(255,255,255,.035); }
/* Row hover never showed on an even row in dark mode, at any colour. The rule
   above is (0,3,3) — one type-selector heavier than the base
   `.admin-table tbody tr:hover` (0,2,2) can ever reach, dark mode or not — so
   hover always lost on an even row regardless of what a consumer set it to.
   This rule ties the zebra rule's own (0,3,3) and wins the tie by coming
   later in this same sheet, the same fix already applied above for the
   nth-child/selected-row collision, just against hover instead of selection.
   --px-hover-btn (10% white in dark) rather than a dark remap of whatever
   colour hover uses in light: it is the token every other interactive
   surface in this theme already hovers to, so nothing new needs defining.
   Found building a real product on this checkout — see
   CHANGELOG.md's Beakon-reskin findings. */
body[data-theme="dark"] .admin-table tbody tr:hover{ background:var(--px-hover-btn); }


/* ---- Workspace flyout ------------------------------------------------- */
.ws-pop{ max-height:min(70vh,560px); overflow-y:auto; }
.ws-item__badge{
  margin-left:auto; flex-shrink:0; padding:3px 9px; border-radius:999px;
  font-size:.6875rem; font-weight:700;
  background:var(--praxis-color-teal-10); color:var(--praxis-color-teal-80);
}
body[data-theme="dark"] .ws-item__badge{ background:rgba(41,210,215,.16); color:var(--praxis-color-text-link); }
/* The current workspace read as a white block in dark — --praxis-color-teal-10
   is a light tint that isn't remapped. Pink, matching how selection is marked
   elsewhere in the app. */
body[data-theme="dark"] .ws-item--home .ws-item__icon{
  background:rgba(227,0,114,.18); color:#ff5aa8;
}
body[data-theme="dark"] .ws-item--home{ background:rgba(227,0,114,.10); }
body[data-theme="dark"] .ws-item--home .ws-item__badge{
  background:rgba(227,0,114,.18); color:#ff5aa8;
}
