/* ============================================================================
   Cajella — application stylesheet

   Organised as: tokens → base → layout → navigation → typography → buttons →
   forms → feedback → badges → tables → empty states → cards & detail → tabs →
   modal → wizard → auth → responsive.

   The locked visual system is the carbon-fibre tactical HUD described in
   `skills/cajella-hud/SKILL.md`: dark carbon ground, olive drab and muted gold,
   sharp angular panels with thin 1px edges, critical red reserved for real
   urgency. Implementation contract: `docs/plans/2026-08-21-cajella-hud-v1.md`.

   Three rules govern edits here (Decision Log: "Cajella shared visual system",
   and 2026-08-21 "Cajella wears the tactical HUD"):

   1. Colour, spacing, radius and shadow come from the `--cj-*` tokens below.
      A raw hex in a rule is a bug unless it is a one-off translucent overlay.
   2. A wrong rule is corrected where it is declared. Overrides appended to the
      bottom of the file to out-specify an earlier rule are what produced the
      two competing button systems this file replaces.
   3. No selector is deleted. `CssClassCoverageTest` requires every class a
      rendered page emits to have a rule, and `PrimitiveRenderTest` pins
      thirty-four primitive class names whether or not a template still uses
      them. Retheming edits a rule in place; it never removes one.
   ========================================================================== */

:root {
    /* Accents. Olive drab is the system colour — nav, primary action, ready.
       Muted gold carries money, counts and links. Nothing else is a brand
       colour; the HUD skill admits no third hue. */
    --cj-accent: #6B7A3A;
    --cj-accent-hover: #8A9A4A;
    --cj-accent-dark: #C4A35A;
    --cj-gold-dim: #8A7340;

    /* Surfaces. Three steps of near-black: page, panel, inset. The rail is a
       fourth, one step darker than the page, so the chrome sits *under* the
       content rather than floating over it. */
    --cj-bg: #0B0C0A;
    --cj-surface: #121410;
    --cj-hover: #171910;

    /* The panel hierarchy, as four named steps rather than three colours used
       from memory. `--cj-panel-face` is what a panel fills with;
       `--cj-panel-inset-face` is a region nested inside one; and the selected
       tint is olive at 12% over whichever of those it lands on, so a chosen row
       reads as the same surface lit rather than as a different colour. */
    --cj-panel-face: var(--cj-surface);
    --cj-panel-inset-face: var(--cj-hover);
    --cj-panel-selected-face: #6B7A3A1f;

    /* The carbon weave. A diagonal 1px thread at 5% over the page ground —
       enough to read as woven, not enough to read as texture. Applied once, on
       `body::before`, so no panel pays for it twice. */
    --cj-carbon: repeating-linear-gradient(
        45deg,
        #ffffff0d 0 1px,
        transparent 1px 5px
    );

    /* Sidebar */
    --cj-sidebar: #0E0F0C;
    --cj-sidebar-hover: #1B1D14;
    --cj-sidebar-border: #2A2C24;

    /* Text */
    --cj-text: #E6E2D4;
    --cj-muted: #9A9686;
    --cj-on-dark: #E6E2D4;
    --cj-on-dark-muted: #9A9686;

    /* Ink on an olive or gold fill. `--cj-surface` used to serve this purpose
       as well as being the card background: on paper those were the same
       colour, and on carbon they are opposites. Splitting them is what stops a
       primary button from rendering pale-on-olive at 3:1. */
    --cj-on-accent: #0B0C0A;

    /* Borders. A HUD panel is a 1px olive-gold edge, not a shadow. */
    --cj-border: #2A2C24;
    --cj-border-subtle: #1E2018;
    --cj-border-control: #34372B;
    --cj-border-hover: var(--cj-gold-dim);

    /* The panel edge, and the four states that are allowed to change it.
       `--cj-panel-edge` is one step brighter than `--cj-border` because it is
       the only line a panel has: with the chamfer the edge *is* the panel's
       silhouette, and at #2A2C24 against the page ground a 1px silhouette read
       as a smudge rather than as a drawn line.

       Everything else here is state. A panel wears the neutral edge unless it
       has something to say — the HUD skill reserves gold for meaning, and a
       screen where every panel is accented has told the owner nothing. The
       superseded treatment was a gold top hairline on every panel; it is now the
       emphasis variant, applied deliberately. */
    --cj-panel-edge: #33372A;
    --cj-panel-edge-inset: #262A20;
    --cj-panel-edge-emphasis: var(--cj-gold-dim);
    --cj-panel-edge-warning: var(--cj-warning-border);
    --cj-panel-edge-critical: var(--cj-danger-border);
    --cj-panel-edge-selected: var(--cj-accent-hover);

    /* Focus. Gold reads against every carbon surface in the application, so
       one ring serves both the page and the rail. */
    --cj-focus: var(--cj-accent-dark);
    --cj-focus-on-dark: var(--cj-accent-dark);

    /* Status. Olive is ready, gold is caution, red is genuine failure. The
       `-bg` tints are the same hue at low alpha over carbon rather than the
       pastel washes a light theme uses. */
    --cj-success: #7E9C4A;
    --cj-success-bg: #5D7A3A26;
    --cj-success-border: #5D7A3A;
    --cj-warning: #C4A35A;
    --cj-warning-bg: #C4A35A1f;
    --cj-warning-border: var(--cj-gold-dim);
    --cj-danger: #D2664B;
    --cj-danger-hover: #B54A32;
    --cj-danger-bg: #B54A3226;
    --cj-danger-border: #B54A32;
    --cj-info: #9A9686;
    --cj-info-bg: #E6E2D414;
    --cj-info-border: #34372B;
    --cj-neutral: #9A9686;
    --cj-neutral-bg: #E6E2D40f;

    /* Elevation. Flat HUD cards with 1px edges — the skill rejects heavy
       glassmorphism, so these are depth cues, not drop shadows. */
    --cj-shadow: 0 1px 0 #00000059;
    --cj-shadow-lg: 0 6px 20px #00000073;
    --cj-shadow-modal: 0 24px 70px #000000b3;

    /* Radii. Sharp angular panels: effectively square, with 1–2px to stop the
       corners looking accidental. Panels no longer use a radius at all — they
       are cut rather than rounded (see the chamfer below) — and these remain the
       corner treatment for controls, chips and small chrome. */
    --cj-radius-xs: 0;
    --cj-radius-sm: 1px;
    --cj-radius-lg: 2px;
    --cj-radius-pill: 2px;
    /* Approved 2026-09: the shared button shape (below) moved from a cut
       corner to a conventional rounded one — the chamfer was reading as a
       clipping bug on a filled, bordered control, not as Cajella's angular
       identity, which is untouched everywhere else (panels, cards, badges,
       chips, form fields all keep their cut). `.pagination-page` and
       `.row-overflow summary` move with it, because both are declared
       "the same family as the buttons" where the cut used to live — see the
       comment above `.form-control` below. This is deliberately a new,
       larger radius rather than a reuse of `--cj-radius-sm`/`-lg`: those are
       barely-there roundings tuned to still read as square, and "modestly
       rounded" was the whole point of the correction. */
    --cj-radius-control: 6px;

    /* The chamfer. This is the shape of Cajella: a panel's corners are cut at
       45°, not rounded, and the 1px edge follows the cut. One size for panels,
       one for controls and chips, one for the small framed icon container — a
       9px cut on a 22px badge would eat the badge.

       Sizes are deliberately small. The cut is meant to read as machined, and a
       cut large enough to notice on its own is a decoration. */
    --cj-chamfer: 9px;
    --cj-chamfer-sm: 5px;
    --cj-chamfer-xs: 3px;
    /* The one genuinely round thing in Cajella: an identity chip. A person's
       initials in a square read as a label; in a circle they read as a person,
       which is the whole job of the control in the header. Nothing else may use
       this — the system is angular, and `--cj-radius-pill` above is 2px on
       purpose. */
    --cj-radius-round: 50%;

    /* Display face — condensed military sans for headers, nav group labels and
       widget labels, uppercase and tracked. Deliberately a system stack: an
       ERP that a shop runs on a bench machine must not depend on fetching a
       font. Table bodies and form controls never use it; the HUD skill forbids
       stencil in a table body. */
    --cj-font-display: 'Oswald', 'Roboto Condensed', 'Arial Narrow',
        'Helvetica Neue Condensed', 'Liberation Sans Narrow', ui-sans-serif, sans-serif;

    /* Icons. One family, one delivery mechanism.
       The geometry is Lucide's — outlined, 24px box, 2px stroke, ISC-licensed —
       inlined as SVG masks. Inlined rather than fetched for the same reason the
       HUD mark beside the wordmark is drawn in CSS: the rail must render
       identically on a bench machine with no network. Masks rather than an
       `<img>` because a mask takes its colour from `background-color`, so a
       glyph is tinted by the token layer like everything else and an active row
       can accent its icon without shipping a second file. Masks rather than an
       inline `<svg>` in Blade because a pseudo-element is decorative to
       assistive technology without anyone having to remember `aria-hidden` — the
       label beside it already says where the row goes.
       Add a glyph by adding a token here and a `[data-icon]` line beside
       `.nav-item::before`; `ApplicationShellTest` fails on a destination whose
       icon name has no token. */
    --cj-icon-layout-dashboard: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Crect width='7' height='9' x='3' y='3' rx='1' /%3E%3Crect width='7' height='5' x='14' y='3' rx='1' /%3E%3Crect width='7' height='9' x='14' y='12' rx='1' /%3E%3Crect width='7' height='5' x='3' y='16' rx='1' /%3E%3C/svg%3E");
    --cj-icon-layers: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z' /%3E%3Cpath d='M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12' /%3E%3Cpath d='M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17' /%3E%3C/svg%3E");
    --cj-icon-blocks: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2' /%3E%3Crect x='14' y='2' width='8' height='8' rx='1' /%3E%3C/svg%3E");
    --cj-icon-package: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z' /%3E%3Cpath d='M12 22V12' /%3E%3Cpolyline points='3.29 7 12 12 20.71 7' /%3E%3Cpath d='m7.5 4.27 9 5.15' /%3E%3C/svg%3E");
    --cj-icon-box: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z' /%3E%3Cpath d='m3.3 7 8.7 5 8.7-5' /%3E%3Cpath d='M12 22V12' /%3E%3C/svg%3E");
    --cj-icon-wrench: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z' /%3E%3C/svg%3E");
    --cj-icon-factory: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M12 16h.01' /%3E%3Cpath d='M16 16h.01' /%3E%3Cpath d='M3 19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8.5a.5.5 0 0 0-.769-.422l-4.462 2.844A.5.5 0 0 1 15 10.5v-2a.5.5 0 0 0-.769-.422L9.77 10.922A.5.5 0 0 1 9 10.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z' /%3E%3Cpath d='M8 16h.01' /%3E%3C/svg%3E");
    --cj-icon-trash-2: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10 11v6' /%3E%3Cpath d='M14 11v6' /%3E%3Cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6' /%3E%3Cpath d='M3 6h18' /%3E%3Cpath d='M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' /%3E%3C/svg%3E");
    --cj-icon-settings: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' /%3E%3Ccircle cx='12' cy='12' r='3' /%3E%3C/svg%3E");
    --cj-icon-circle-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M8 12h8' /%3E%3Cpath d='M12 8v8' /%3E%3C/svg%3E");
    --cj-icon-bell: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10.268 21a2 2 0 0 0 3.464 0' /%3E%3Cpath d='M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326' /%3E%3C/svg%3E");
    --cj-icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m6 9 6 6 6-6' /%3E%3C/svg%3E");

    /* Added by the panel pass. The severity, trend, time and money glyphs the
       shared panel, metric, badge and attention primitives need — same family,
       same 24px box, same 2px stroke, same delivery. A glyph is added here and
       bound below beside `.nav-item::before`; nothing draws an icon any other
       way. */
    --cj-icon-chevron-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m9 18 6-6-6-6' /%3E%3C/svg%3E");
    --cj-icon-triangle-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3' /%3E%3Cpath d='M12 9v4' /%3E%3Cpath d='M12 17h.01' /%3E%3C/svg%3E");
    --cj-icon-circle-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cline x1='12' x2='12' y1='8' y2='12' /%3E%3Cline x1='12' x2='12.01' y1='16' y2='16' /%3E%3C/svg%3E");
    --cj-icon-circle-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='m9 12 2 2 4-4' /%3E%3C/svg%3E");
    --cj-icon-clock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M12 6v6l4 2' /%3E%3C/svg%3E");
    --cj-icon-trending-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M16 7h6v6' /%3E%3Cpath d='m22 7-8.5 8.5-5-5L2 17' /%3E%3C/svg%3E");
    --cj-icon-trending-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M16 17h6v-6' /%3E%3Cpath d='m22 17-8.5-8.5-5 5L2 7' /%3E%3C/svg%3E");
    --cj-icon-coins: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M13.744 17.736a6 6 0 1 1-7.48-7.48' /%3E%3Cpath d='M15 6h1v4' /%3E%3Cpath d='m6.134 14.768.866-.5 2 3.464' /%3E%3Ccircle cx='16' cy='8' r='6' /%3E%3C/svg%3E");
    --cj-icon-clipboard-list: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1' /%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2' /%3E%3Cpath d='M12 11h4' /%3E%3Cpath d='M12 16h4' /%3E%3Cpath d='M8 11h.01' /%3E%3Cpath d='M8 16h.01' /%3E%3C/svg%3E");
    --cj-icon-boxes: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z' /%3E%3Cpath d='m7 16.5-4.74-2.85' /%3E%3Cpath d='m7 16.5 5-3' /%3E%3Cpath d='M7 16.5v5.17' /%3E%3Cpath d='M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z' /%3E%3Cpath d='m17 16.5-5-3' /%3E%3Cpath d='m17 16.5 4.74-2.85' /%3E%3Cpath d='M17 16.5v5.17' /%3E%3Cpath d='M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z' /%3E%3Cpath d='M12 8 7.26 5.15' /%3E%3Cpath d='m12 8 4.74-2.85' /%3E%3Cpath d='M12 13.5V8' /%3E%3C/svg%3E");
    --cj-icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M12 16v-4' /%3E%3Cpath d='M12 8h.01' /%3E%3C/svg%3E");
    /* Business Settings names two things the set had no glyph for: the company
       itself, and a percentage. Same family, same 24px Lucide grid, same
       two-pixel round stroke as the twenty-three above. */
    --cj-icon-store: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7' /%3E%3Cpath d='M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8' /%3E%3Cpath d='M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4' /%3E%3Cpath d='M2 7h20' /%3E%3Cpath d='M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7' /%3E%3C/svg%3E");
    --cj-icon-calendar: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Crect width='18' height='18' x='3' y='4' rx='2' ry='2' /%3E%3Cline x1='16' x2='16' y1='2' y2='6' /%3E%3Cline x1='8' x2='8' y1='2' y2='6' /%3E%3Cline x1='3' x2='21' y1='10' y2='10' /%3E%3C/svg%3E");
    --cj-icon-percent: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cline x1='19' x2='5' y1='5' y2='19' /%3E%3Ccircle cx='6.5' cy='6.5' r='2.5' /%3E%3Ccircle cx='17.5' cy='17.5' r='2.5' /%3E%3C/svg%3E");
    --cj-icon-users: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' /%3E%3Ccircle cx='9' cy='7' r='4' /%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87' /%3E%3Cpath d='M16 3.13a4 4 0 0 1 0 7.75' /%3E%3C/svg%3E");
    /* Approved 2026-09: the canonical Cajella mark (`public/images/cajella-mark.svg`),
       the same artwork the public marketing site renders, as a mask. Only the
       two fill paths are carried over — the cream vein stroke is redundant to
       an alpha mask, since it sits entirely inside the already-opaque leaf
       fill. One icon delivery mechanism, no exception for the brand mark: no
       network fetch, no rasterizing, no second image dropped into the shell. */
    --cj-brand-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120'%3E%3Cpath fill='%23000' d='M31 15 H101 L90 35 H48 L35 48 V76 L48 89 H91 L102 109 H31 L12 90 V34 Z'/%3E%3Cpath fill='%23000' d='M52 78 C55 60 68 49 87 46 C85 64 74 77 56 82 C54 82 53 80 52 78 Z'/%3E%3C/svg%3E");

    /* The application header's height, and the sidebar brand block's. One token
       because the two must match: their bottom hairlines are meant to read as a
       single line across the shell, and two independent figures is how that line
       ends up with a step in it. */
    --cj-topbar-h: 76px;

    /* Spacing */
    --cj-space-1: 4px;
    --cj-space-2: 8px;
    --cj-space-3: 12px;
    --cj-space-4: 16px;
    --cj-space-5: 24px;
    --cj-space-6: 32px;

    /* Density. The reference consoles are information-dense without being
       cramped, and the previous panel padding — a flat 24px on every side of
       every card — was the single biggest reason Cajella read as roomier and
       vaguer than them. These are the numbers a panel, its header, a metric tile
       and a grid actually use, named once so that "panel padding" is a decision
       and not a habit.

       They are also what the full-bleed panel headers negate: a header band
       reaches the panel edge by cancelling `--cj-panel-pad-*`, so the two must
       come from the same place or the band stops short of the edge by however
       much they disagree. */
    --cj-panel-pad-y: 18px;
    --cj-panel-pad-x: 20px;
    --cj-panel-head-pad-y: 9px;
    --cj-metric-pad-y: 14px;
    --cj-metric-pad-x: 16px;
    --cj-grid-gap: 12px;
    --cj-icon-gap: 9px;
    --cj-control-height: 34px;
    --cj-row-height: 40px;

    /* Type scale. The gap between the page title and a section title used to be
       2.6rem → 1.3rem, which left nothing to carry the middle of a page. */
    --cj-text-xs: .72rem;
    --cj-text-sm: .84rem;
    --cj-text-base: .95rem;
    --cj-text-lg: 1.15rem;
    --cj-text-xl: 1.35rem;
    --cj-text-2xl: 1.9rem;

    /* The seven levels the interface actually distinguishes, from the page's
       command header down to the smallest piece of metadata. Sizes above carry
       most of it; these three are the ones that were being guessed at each site
       of use — a metric's figure, a metric's figure in a dense grid, and the
       tracked caps of a panel label.

       Tracking is a token for the same reason a colour is: a panel label at
       .18em beside a badge at .1em beside a tile label at .2em is three
       systems, and the difference is invisible until they sit in one row. */
    --cj-text-metric: 2.15rem;
    --cj-text-metric-sm: 1.55rem;
    --cj-text-micro: .66rem;
    --cj-track-label: .18em;
}

/* ---------------------------------------------------------------- base ---- */

* { box-sizing: border-box; }

/* `color-scheme: dark` is what tells the browser to draw its *own* furniture
   dark: a date picker's calendar glyph, a select's arrow, a textarea's resize
   handle and the scrollbars. Without it those are drawn for a light page and are
   the only light things on the screen. */
html {
    color-scheme: dark;
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
    color: var(--cj-text);
    background: var(--cj-bg);
}

/* A radio or a checkbox is olive, wherever it is. `.form-choice input` said so
   already; the choices inside a fieldset did not, and rendered in the browser's
   own blue. */
input[type='radio'],
input[type='checkbox'] { accent-color: var(--cj-accent); }

body { margin: 0; line-height: 1.5; }

/* The weave. Fixed and non-interactive, so it does not scroll away from a long
   page and cannot intercept a click. `z-index: 0` with the shell above it keeps
   it behind content without giving every panel a stacking context. */
body::before {
    content: '';
    position: fixed;
    z-index: 0;
    inset: 0;
    background-image: var(--cj-carbon);
    pointer-events: none;
}
body > * { position: relative; z-index: 1; }

a { color: var(--cj-accent-dark); font-weight: 650; text-decoration: none; }
a:hover { color: var(--cj-warning); text-decoration: underline; }

:focus-visible { outline: 3px solid var(--cj-focus); outline-offset: 2px; }

/* A keyboard user should be able to reach the page without walking the whole
   sidebar first. Visible only while focused. */
.skip-link {
    position: absolute;
    z-index: 20;
    top: var(--cj-space-2);
    left: var(--cj-space-2);
    padding: 10px 15px;
    border: 1px solid var(--cj-accent-dark);
    border-radius: var(--cj-radius-sm);
    background: var(--cj-surface);
    color: var(--cj-text);
    font-weight: 750;
    transform: translateY(-200%);
    transition: transform .15s;
}
.skip-link:focus { transform: translateY(0); }

/* -------------------------------------------------------------- layout ---- */

.app-shell { display: flex; min-height: 100vh; }
.app-main { min-width: 0; flex: 1; }

/* The application header: the page's title on the left, controls on the right.
   It was a 58px strip carrying a .96rem caps line, which read as a breadcrumb
   above the page rather than as the page's own header — and because every module
   index also printed its name in the body, an owner read COMPONENTS twice. The
   band is the title now (`.topbar h1`), at the reference console's height and
   type scale, and it is the only title on the screen.

   `--cj-topbar-h` is shared with `.sidebar-brand`, which is the whole point of
   having it: the brand block and this band are the same height, so the hairline
   under each is one line running the full width of the shell. */
.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cj-space-5);
    min-height: var(--cj-topbar-h);
    padding: 14px 4vw;
    border-bottom: 1px solid var(--cj-border);
    background: var(--cj-sidebar);
    box-shadow: inset 0 -1px 0 #8A734033;
}
.topbar .eyebrow { margin: 0; }

/* Which module the page belongs to, and — when the page is a record inside that
   module rather than the module itself — what that record is. The identity
   truncates rather than wraps: the header is one row at every width, and the
   controls to its right must stay reachable before the identity stays complete.
   No flanking rules and no centring: the title starts at the content column's
   left edge, which is where a title of unpredictable length can start safely. */
.topbar-identity {
    display: flex;
    min-width: 0;
    overflow: hidden;
}
.topbar h1 {
    display: flex;
    align-items: baseline;
    min-width: 0;
    gap: var(--cj-space-3);
    margin: 0;
    font-family: var(--cj-font-display);
    font-size: clamp(1.15rem, 1.9vw, 1.6rem);
    font-weight: 600;
    letter-spacing: .12em;
    line-height: 1.1;
    text-transform: uppercase;
}
.topbar-module,
.topbar-detail {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Gold for the module, olive for the record — the reference's two-tone title.
   The record is set smaller than the module rather than at the same size: the
   reference's second segment is a fixed two-word qualifier, and Cajella's is a
   Product or Material name that has no length limit. */
.topbar-module { flex: 0 1 auto; color: var(--cj-accent-dark); }
.topbar-detail {
    flex: 1 1 auto;
    color: var(--cj-accent-hover);
    font-size: .74em;
    letter-spacing: .1em;
}
.topbar-separator { flex: 0 0 auto; color: var(--cj-gold-dim); font-size: .6em; }

/* The controls, behind a hairline of their own so they read as chrome rather
   than as the end of the title. The hairline runs the height of the band. */
.topbar-actions {
    display: flex;
    align-items: center;
    align-self: stretch;
    flex: 0 0 auto;
    gap: var(--cj-space-1);
    padding-left: var(--cj-space-5);
    border-left: 1px solid var(--cj-border);
}

/* The superseded status strip. The shell used to state "Signed in as …" and a
   system lamp here; the person is now the user control to the right, and "the
   application is running" was never news to anyone reading the page it was on.
   The rule stays because the stylesheet corrects in place and deletes nothing
   (see this file's header), and `.topbar-lamp` is still the rail's lamp. */
.topbar-status { display: flex; align-items: center; gap: var(--cj-space-2); }
.topbar-lamp {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: var(--cj-radius-pill);
    background: var(--cj-success);
    box-shadow: 0 0 6px #7E9C4A99;
}

/* ------------------------------------------------ header controls ---- */

/* The notification bell and the user menu. Both are the same shape: a real
   <button> that owns `aria-expanded`, and a panel positioned against the group
   around them. Compact, square, outlined — a header control is chrome, so it is
   quiet until it is hovered, focused or open. */
.header-control-group { position: relative; display: flex; }

.header-control {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    gap: var(--cj-space-2);
    padding: 6px 10px;
    border: 1px solid transparent;
    border-radius: var(--cj-radius-sm);
    background: transparent;
    color: var(--cj-muted);
    font: inherit;
    line-height: 1;
    cursor: pointer;
}
.header-control:hover {
    border-color: var(--cj-border-control);
    background: var(--cj-sidebar-hover);
    color: var(--cj-text);
}
/* Open is a state of the control, not only of the panel, so the button still
   reads as pressed when the pointer leaves it. */
.header-control[aria-expanded='true'] {
    border-color: var(--cj-gold-dim);
    background: var(--cj-sidebar-hover);
    color: var(--cj-text);
}
/* A control whose whole content is a glyph is square and carries its meaning in
   `aria-label` instead. */
.header-control-icon-only {
    position: relative;
    justify-content: center;
    min-width: 40px;
    padding: 6px;
}

/* Unread notifications, when there are any. Cajella has no notification system
   at this commit, so `x-notification-bell` is passed no count and this never
   renders — it exists so that connecting one later is a controller change and
   not a design decision. */
.notification-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    min-width: 16px;
    padding: 0 4px;
    border-radius: var(--cj-radius-sm);
    background: var(--cj-danger-hover);
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: .6rem;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}

/* The signed-in person. A hairline separates them from the bell, the name
   truncates rather than pushing the control off the row, and the initials chip
   is the one round thing in the application (see `--cj-radius-round`). */
.user-menu { padding-left: var(--cj-space-1); border-left: 1px solid var(--cj-border-subtle); }
.user-control { max-width: 15rem; }
/* Tracked caps, stated rather than inherited. The shared `button` rule further
   down gives every bare <button> the display treatment, so this used to be caps
   by accident; the header wears caps on purpose and the declaration should say
   so. Tracking is the rail's .06em rather than the button rule's .1em, because a
   person's name is longer than a button's verb. */
.user-control-name {
    min-width: 0;
    overflow: hidden;
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: .86rem;
    font-weight: 650;
    letter-spacing: .06em;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
}
.user-control-chevron { flex: 0 0 14px; width: 14px; height: 14px; mask-size: 14px 14px; -webkit-mask-size: 14px 14px; }
.user-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 28px;
    width: 28px;
    height: 28px;
    border: 1px solid var(--cj-gold-dim);
    border-radius: var(--cj-radius-round);
    background: var(--cj-bg);
    color: var(--cj-accent-dark);
    font-family: var(--cj-font-display);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .04em;
}

/* The panel. Closed panels carry the `hidden` attribute rather than a class, so
   they are out of the tab order and the accessibility tree whether or not
   `menu.js` has run — see that file. */
/* An overlay panel: the shared shape at the smaller cut, and one of the two
   places besides the modal that keeps a shadow, because it genuinely is floating
   over the page it belongs to. */
.header-menu {
    --cj-panel-cut: var(--cj-chamfer-sm);
    --cj-panel-edge-color: var(--cj-panel-edge-emphasis);
    position: absolute;
    z-index: 8;
    top: calc(100% + 6px);
    right: 0;
    min-width: 14rem;
    padding: var(--cj-space-2);
    box-shadow: var(--cj-shadow-lg);
}
.header-menu-wide { min-width: 17rem; }
.header-menu-title {
    margin: 0 0 var(--cj-space-2);
    padding: 2px 8px 8px;
    border-bottom: 1px solid var(--cj-border-subtle);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .64rem;
    font-weight: 600;
    letter-spacing: .22em;
    text-transform: uppercase;
}
.header-menu-empty { margin: 0; padding: 2px 8px 6px; color: var(--cj-muted); font-size: var(--cj-text-sm); }
.header-menu-identity { padding: 2px 8px 10px; border-bottom: 1px solid var(--cj-border-subtle); }
.header-menu-identity strong { display: block; color: var(--cj-text); font-size: var(--cj-text-sm); }
.header-menu-identity small {
    display: block;
    overflow: hidden;
    color: var(--cj-muted);
    font-size: var(--cj-text-xs);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.header-menu-form { margin: 0; }
.header-menu-item {
    display: block;
    width: 100%;
    margin-top: 2px;
    padding: 9px 8px;
    border: 0;
    border-left: 2px solid transparent;
    border-radius: var(--cj-radius-sm);
    background: transparent;
    color: var(--cj-on-dark);
    font: inherit;
    font-size: var(--cj-text-sm);
    font-weight: 650;
    letter-spacing: normal;
    text-align: left;
    /* Sign out is a <button> and Business settings is an <a>, so without this
       the shared `button` rule's display treatment made one of the two items in
       the same menu shout and the other not. Both are destinations; neither
       shouts. */
    text-transform: none;
    cursor: pointer;
}
.header-menu-item:hover {
    border-left-color: var(--cj-gold-dim);
    background: var(--cj-hover);
    color: var(--cj-text);
    text-decoration: none;
}
/* Signing out is the destructive-ish one of the two, and it is still not the
   loudest thing in the menu. */
.header-menu-item-danger { color: var(--cj-danger); }
.header-menu-item-danger:hover { border-left-color: var(--cj-danger-border); background: var(--cj-danger-bg); color: var(--cj-danger); }

/* Tighter again (was 42px, then 32px). The page's title is in the header band
   now, so what sits at the top of this box is the lede or the first panel, and
   the reference console starts its first panel close under the band. */
.page-content { max-width: 1240px; margin: 0 auto; padding: 24px 4vw 56px; }

/* A page whose content is a wide table rather than prose opts out of the
   reading measure with `@section('page-class','page-wide')`. The Materials list
   is the case that asked for it: eight columns plus an Actions cell of five
   controls did not fit inside 1240px less 4vw of gutter on a 1440px screen, and
   the table scrolled sideways while a third of the window sat empty beside it.
   Confined to the desktop range above the shell's own 900px breakpoint, so the
   narrow-viewport padding further down still applies untouched — this widens
   one page, not Cajella. */
@media (min-width: 901px) {
    .page-content.page-wide { max-width: none; padding-right: 24px; padding-left: 24px; }
}

/* ---------------------------------------------------------- navigation ---- */

/* The rail. Around 240px per the HUD skill's implementation note — 256px since
   the rows gained an icon column. Sixteen pixels, not the fifty a conventional
   ERP rail would take, and measured rather than guessed: at 248px the longest
   label in the map, PACKAGING MATERIALS, wrapped to a second line and that one
   row stood 15px taller than every other. There is a gold hairline on the
   inside edge so the chrome is separated from the page by a line rather than by
   a change of brightness alone. */
.app-sidebar {
    display: flex;
    flex-direction: column;
    width: 256px;
    flex: 0 0 256px;
    /* No top padding: the brand block below sets its own height from
       `--cj-topbar-h` so that it finishes level with the application header. */
    padding: 0 14px 16px;
    border-right: 1px solid var(--cj-border);
    background: var(--cj-sidebar);
    color: var(--cj-text);
    box-shadow: inset -1px 0 0 #8A734026;
}
/* The focus ring must be visible against the dark rail, not against paper. */
.app-sidebar :focus-visible { outline-color: var(--cj-focus-on-dark); }

/* Mark plus wordmark, top-left. The mark is drawn from the border tokens
   rather than shipped as an asset: two olive bars and a gold corner, which is
   the HUD's own vocabulary and costs no request. */
/* A grid, not a flex row. The wordmark and its sub-label are two lines, and as
   a flex row they sat side by side and pushed "Operations" off the edge of the
   rail. Two columns put the mark beside the wordmark; the sub-label is placed
   into the second column of the second row so it aligns under the wordmark
   rather than under the mark. */
/* It is `--cj-topbar-h` tall and full-bleed to the rail's edges, with the same
   border and the same gold inset the application header carries. The two
   hairlines then meet at the rail's right edge and read as one line across the
   shell, which is what the reference console does and what this rail did not:
   the brand block finished at 76px and the header at 58px, so the chrome had a
   step in it eighteen pixels wide. */
.sidebar-brand {
    display: grid;
    box-sizing: border-box;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    align-content: center;
    min-height: var(--cj-topbar-h);
    margin: 0 -14px;
    column-gap: 10px;
    padding: 0 24px;
    border-bottom: 1px solid var(--cj-border);
    box-shadow: inset 0 -1px 0 #8A734033;
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    letter-spacing: .16em;
    font-weight: 700;
    font-size: 1.12rem;
    text-transform: uppercase;
}
/* Approved 2026-09: the canonical Cajella mark (`--cj-brand-mark`) replaces
   the CSS-drawn rotated-square placeholder this pseudo-element used to draw.
   Auto-placed into the same first grid column the placeholder occupied,
   sized to what the placeholder was; `currentColor` so the mark recolors with
   the wordmark on hover instead of carrying its own hardcoded color. Same
   mask mechanism every other glyph in the shell uses, so no `<svg>` or
   `<img>` is added to the rendered page. */
.sidebar-brand::before {
    content: '';
    display: block;
    width: 14px;
    height: 14px;
    background-color: currentColor;
    -webkit-mask: var(--cj-brand-mark) center / contain no-repeat;
    mask: var(--cj-brand-mark) center / contain no-repeat;
}
.sidebar-brand small {
    grid-column: 2;
    display: block;
    color: var(--cj-muted);
    font-size: .6rem;
    font-weight: 500;
    letter-spacing: .28em;
}
.sidebar-brand:hover { color: var(--cj-accent-dark); text-decoration: none; }

.nav-group-label {
    margin: 18px 12px 6px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--cj-border-subtle);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .64rem;
    font-weight: 600;
    letter-spacing: .22em;
}

/* A nav row is `[icon column] [label]` on a flat strip, with room on the left
   for the active bar so nothing shifts sideways when the bar appears.
   The label is tracked caps in the display face, which is what the rest of the
   HUD's headers wear; it is *rendered* in caps rather than written that way, so
   the navigation map keeps one sentence-case label per destination and the rail
   never renames a screen. */
.nav-item {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 12px;
    border-left: 3px solid transparent;
    border-radius: var(--cj-radius-sm);
    color: var(--cj-on-dark-muted);
    font-family: var(--cj-font-display);
    font-size: .8rem;
    font-weight: 600;
    letter-spacing: .06em;
    line-height: 1.3;
    text-transform: uppercase;
}

/* The icon column, and the one place a glyph is drawn.
   `flex: 0 0 18px` is what guarantees the alignment: every label starts exactly
   18px + one gap from the row's text edge whatever glyph sits there, so no icon
   can nudge the row below it out of line. The mask is chosen by the row's own
   `data-icon`, one line each, below. */
.nav-item::before,
.header-icon {
    display: block;
    flex: 0 0 18px;
    width: 18px;
    height: 18px;
    background-color: currentColor;
    -webkit-mask: var(--cj-glyph, none) center / 18px 18px no-repeat;
    mask: var(--cj-glyph, none) center / 18px 18px no-repeat;
}
.nav-item::before { content: ''; }

[data-icon='layout-dashboard'] { --cj-glyph: var(--cj-icon-layout-dashboard); }
[data-icon='layers'] { --cj-glyph: var(--cj-icon-layers); }
[data-icon='blocks'] { --cj-glyph: var(--cj-icon-blocks); }
[data-icon='package'] { --cj-glyph: var(--cj-icon-package); }
[data-icon='box'] { --cj-glyph: var(--cj-icon-box); }
[data-icon='wrench'] { --cj-glyph: var(--cj-icon-wrench); }
[data-icon='factory'] { --cj-glyph: var(--cj-icon-factory); }
[data-icon='trash-2'] { --cj-glyph: var(--cj-icon-trash-2); }
[data-icon='settings'] { --cj-glyph: var(--cj-icon-settings); }
[data-icon='circle-plus'] { --cj-glyph: var(--cj-icon-circle-plus); }
[data-icon='bell'] { --cj-glyph: var(--cj-icon-bell); }
[data-icon='chevron-down'] { --cj-glyph: var(--cj-icon-chevron-down); }
[data-icon='chevron-right'] { --cj-glyph: var(--cj-icon-chevron-right); }
[data-icon='triangle-alert'] { --cj-glyph: var(--cj-icon-triangle-alert); }
[data-icon='circle-alert'] { --cj-glyph: var(--cj-icon-circle-alert); }
[data-icon='circle-check'] { --cj-glyph: var(--cj-icon-circle-check); }
[data-icon='clock'] { --cj-glyph: var(--cj-icon-clock); }
[data-icon='trending-up'] { --cj-glyph: var(--cj-icon-trending-up); }
[data-icon='trending-down'] { --cj-glyph: var(--cj-icon-trending-down); }
[data-icon='coins'] { --cj-glyph: var(--cj-icon-coins); }
[data-icon='clipboard-list'] { --cj-glyph: var(--cj-icon-clipboard-list); }
[data-icon='boxes'] { --cj-glyph: var(--cj-icon-boxes); }
[data-icon='info'] { --cj-glyph: var(--cj-icon-info); }
[data-icon='store'] { --cj-glyph: var(--cj-icon-store); }
[data-icon='calendar'] { --cj-glyph: var(--cj-icon-calendar); }
[data-icon='percent'] { --cj-glyph: var(--cj-icon-percent); }
[data-icon='users'] { --cj-glyph: var(--cj-icon-users); }

.nav-item:hover {
    border-left-color: var(--cj-gold-dim);
    background: var(--cj-sidebar-hover);
    color: var(--cj-text);
    text-decoration: none;
}
/* Active is the olive left bar *and* an olive fill *and* a gold icon — the skill
   offers either of the first two; using all three means the current module is
   legible at a glance, legible to someone who cannot separate olive from carbon
   by hue alone, and legible in the icon column as well as the label. The 1px
   inset gold edge is the panel treatment the rest of the HUD uses, at the one
   scale where a real border would move the row. */
.nav-item.active {
    border-left-color: var(--cj-accent-hover);
    background: #6B7A3A3d;
    box-shadow: inset 0 0 0 1px #8A734033;
    color: var(--cj-text);
    font-weight: 700;
    text-decoration: none;
}
.nav-item.active::before { background-color: var(--cj-accent-dark); }

/* The brand block above is now full-bleed with a rule of its own, so whichever
   of these two follows it supplies the air the rail's old top padding used to. */
.business-identity {
    margin: 18px 0 16px;
    padding: 10px 12px;
    border: 1px solid var(--cj-sidebar-border);
    border-left: 2px solid var(--cj-gold-dim);
    border-radius: var(--cj-radius-sm);
    background: var(--cj-bg);
    color: var(--cj-text);
}
.business-identity small,
.business-switcher label {
    display: block;
    margin-bottom: 3px;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .62rem;
    font-weight: 600;
    letter-spacing: .2em;
    text-transform: uppercase;
}

.business-switcher { margin: 18px 0 16px; padding: 0 2px; }
.business-switcher label { margin-bottom: 6px; font-size: .62rem; }
.business-switcher select {
    width: 100%;
    padding: 8px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-sm);
    background: var(--cj-bg);
    color: var(--cj-text);
    font: inherit;
}

/* The superseded rail status line. It read "System ready" beside a green lamp,
   which claimed Cajella was monitoring a system it has no health check, worker
   or queue for; an indicator that reports invented state is worse than no
   indicator, so the rail now ends after the navigation and nothing replaced it.

   The rule stays because this stylesheet corrects in place and deletes no
   selector (see the file header), and because a rail-level status line is a
   plausible thing to want back on the day there is something true to put in it.
   `margin-top: auto` is what pinned it to the foot of the rail without a fixed
   height. */
.sidebar-status {
    display: flex;
    align-items: center;
    gap: var(--cj-space-2);
    margin: auto 12px 0;
    padding-top: 14px;
    border-top: 1px solid var(--cj-border-subtle);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .62rem;
    letter-spacing: .18em;
    text-transform: uppercase;
}

/* Sign out is a routine action, not the loudest control in the rail — which is
   also why it is no longer *in* the rail: it is the last item of the user menu
   in the header, beside the person it signs out. These three rules are kept
   because this stylesheet corrects in place and deletes nothing (see the file
   header), and because a rail-level sign-out is a plausible thing to want back. */
.sidebar-signout { margin: 16px 12px 0; }
.sidebar-signout .button {
    width: 100%;
    border-color: var(--cj-border-control);
    background: transparent;
    color: var(--cj-muted);
}
.sidebar-signout .button:hover {
    border-color: var(--cj-gold-dim);
    background: var(--cj-sidebar-hover);
    color: var(--cj-text);
}

/* The drawer toggle and its backdrop. Both are hidden until the drawer
   breakpoint; see the responsive section. */
.sidebar-toggle-button { display: none; }
.sidebar-backdrop { display: none; }
.sidebar-close { display: none; }

/* ---------------------------------------------------------- typography ---- */

.eyebrow {
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-xs);
    font-weight: 600;
    letter-spacing: .2em;
    text-transform: uppercase;
}

/* The page's lede and its actions — no longer its heading.

   This was a command header: a 1.9rem uppercase `<h1>` on a gold rule. The
   heading moved into the application header band (`.topbar h1`), because the
   page name was being rendered in both places and an owner opening Components
   read COMPONENTS in the band and COMPONENTS again right beneath it. What is
   left is one line of prose and the page's controls, so the rule goes with the
   heading — the band above already draws one, and two rules twenty pixels apart
   is a box around nothing. `.page-header h1` is gone with it: the component
   emits no heading, so a rule for one could only be reached by reintroducing
   the defect. */
.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--cj-space-5);
    margin-bottom: var(--cj-space-5);
}
.page-subtitle { margin: 0; color: var(--cj-muted); }

/* Section titles inside panels share the display face, one step down. Applied
   by element rather than by class so a card written before the HUD landed still
   gets the treatment. */
.card > h2,
.detail-section-header h2,
.form-section > h2,
.modal-header h2 {
    font-family: var(--cj-font-display);
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
}

/* `x-page-header` emits exactly `<div class="page-actions">`, so the spacing
   between actions has to come from here. It previously existed only inside the
   mobile media query, which left desktop with no `gap` at all: the templates
   are minified, so there was not even inter-tag whitespace between two buttons
   and they rendered touching. */
.page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--cj-space-2);
}

/* -------------------------------------------------------------- panels ---- */

/* One panel shape, for every panel in Cajella.
 *
 * A Cajella panel is square-sided with a small 45° cut at each corner, a 1px
 * edge that follows the cut, a flat dark face, and no shadow. That shape is the
 * thing a person recognises before they read a word on the screen, so it is
 * declared once, here, over the list of classes that are panels — not per page,
 * and not per module.
 *
 * Why two pseudo-elements rather than `border` + `clip-path`:
 *
 *   A `border` cannot follow a clipped corner. Clip a bordered box and the
 *   straight runs of border survive while the diagonal comes out unlined — the
 *   edge appears to stop short of the corner, which reads as a rendering fault
 *   rather than as a cut. So the edge is drawn as a *shape*: `::before` is the
 *   panel's silhouette filled with the edge colour, and `::after` is the same
 *   silhouette inset by 1px filled with the face colour. What is left showing
 *   between them is a 1px line along every side including the two diagonals.
 *
 * Why `isolation: isolate` and negative z-index rather than a wrapper element:
 *
 *   Both pseudo-elements have to paint *behind the panel's content*. Inside a
 *   stacking context, negative-z-index descendants paint after the element's own
 *   background and before its in-flow content, which is exactly the layer a
 *   panel background belongs in — and it needs no extra element. That matters
 *   more than it sounds: this treatment lands on markup that already exists and
 *   several of those panels are pinned byte for byte by tests
 *   (`<section class="card detail-section">`, `<header class="detail-section-header">`,
 *   `<a class="hud-widget" href="…">`). None of them can gain a wrapper.
 *
 * Why `clip-path` is NOT on the panel itself:
 *
 *   A panel contains absolutely positioned children. `.row-overflow-menu` opens
 *   inside a table inside a `.card`; `.notification-badge` sits on a control.
 *   `clip-path` clips descendants, so a clipped panel would cut its own menus
 *   off at the edge. Only the two pseudo-elements are clipped, and they contain
 *   nothing.
 *
 * Three custom properties parameterise the whole thing, so a variant — inset,
 * emphasised, warning, critical, selected, compact — is a token change and never
 * a second implementation of the shape. */
.panel,
.card,
.section-card,
.detail-section,
.form-section,
.metric-card,
.hud-widget,
.money-strip,
.attention-rail,
.product-identity,
.wizard-content,
.wizard-variant-card,
.header-menu,
.row-overflow-menu,
.modal-dialog {
    --cj-panel-cut: var(--cj-chamfer);
    --cj-panel-edge-color: var(--cj-panel-edge);
    --cj-panel-surface: var(--cj-panel-face);
    position: relative;
    isolation: isolate;
    border: 0;
    border-radius: 0;
    background: transparent;
}

/* `:is()` rather than the list written out twice more: the list is one fact and
   should have one home. Specificity is unchanged — `:is()` takes that of its
   most specific argument, and every argument here is a single class. */
:is(.panel, .card, .section-card, .detail-section, .form-section, .metric-card,
    .hud-widget, .money-strip, .attention-rail, .product-identity, .wizard-content,
    .wizard-variant-card, .header-menu, .row-overflow-menu, .modal-dialog)::before,
:is(.panel, .card, .section-card, .detail-section, .form-section, .metric-card,
    .hud-widget, .money-strip, .attention-rail, .product-identity, .wizard-content,
    .wizard-variant-card, .header-menu, .row-overflow-menu, .modal-dialog)::after {
    content: '';
    position: absolute;
    pointer-events: none;
}

/* The edge: the silhouette, in the edge colour. */
:is(.panel, .card, .section-card, .detail-section, .form-section, .metric-card,
    .hud-widget, .money-strip, .attention-rail, .product-identity, .wizard-content,
    .wizard-variant-card, .header-menu, .row-overflow-menu, .modal-dialog)::before {
    z-index: -2;
    inset: 0;
    background: var(--cj-panel-edge-color);
    clip-path: polygon(
        var(--cj-panel-cut) 0,
        calc(100% - var(--cj-panel-cut)) 0,
        100% var(--cj-panel-cut),
        100% calc(100% - var(--cj-panel-cut)),
        calc(100% - var(--cj-panel-cut)) 100%,
        var(--cj-panel-cut) 100%,
        0 calc(100% - var(--cj-panel-cut)),
        0 var(--cj-panel-cut)
    );
}

/* The face: the same silhouette, inset by the width of the edge. Its own cut is
   1px smaller, which is what keeps the diagonal the same 1px as the sides
   instead of the 1.41px a same-size cut would leave. */
:is(.panel, .card, .section-card, .detail-section, .form-section, .metric-card,
    .hud-widget, .money-strip, .attention-rail, .product-identity, .wizard-content,
    .wizard-variant-card, .header-menu, .row-overflow-menu, .modal-dialog)::after {
    z-index: -1;
    inset: 1px;
    background: var(--cj-panel-surface);
    clip-path: polygon(
        calc(var(--cj-panel-cut) - 1px) 0,
        calc(100% - var(--cj-panel-cut) + 1px) 0,
        100% calc(var(--cj-panel-cut) - 1px),
        100% calc(100% - var(--cj-panel-cut) + 1px),
        calc(100% - var(--cj-panel-cut) + 1px) 100%,
        calc(var(--cj-panel-cut) - 1px) 100%,
        0 calc(100% - var(--cj-panel-cut) + 1px),
        0 calc(var(--cj-panel-cut) - 1px)
    );
}

/* The base panel, for anything built after this pass. `.card` remains the name
   the application's existing markup uses and the two are the same object.
   A panel holds no padding itself: the header band and the body each carry
   their own, which is what lets the band reach the panel's edge. */
.panel { padding: 0; }

/* The geometry above needs `position: relative` on every panel, because a
   panel's edge and face are two absolutely positioned pseudo-elements that need
   a containing block. Two of those panels are popovers that must float above the
   page instead, and both declare `position: absolute` themselves —
   `.row-overflow-menu` further down this file, where the declaration survives,
   and `.header-menu` five hundred lines *above* it, where a later rule of equal
   specificity silently took it away.
 *
 * The consequence was invisible at desktop width and not at all invisible on a
 * phone: both header popovers were laid out in flow inside the header row, so
 * opening the user menu at 390px stretched that row to 353px inside a 390px
 * viewport and the whole page scrolled sideways. Restated here — immediately
 * after the geometry section rather than inside it, because `PanelLanguageTest`
 * counts each panel's appearances in that section and a sixth is a panel with a
 * duplicated edge as far as it can tell. `ApplicationShellTest` pins this one. */
.header-menu { position: absolute; }

/* ---- panel surfaces ---- */

/* A region nested inside a panel. One step lighter, a quieter edge and a smaller
   cut, so it reads as *inside* rather than as a second panel that happens to
   overlap the first. */
.panel-inset {
    --cj-panel-cut: var(--cj-chamfer-sm);
    --cj-panel-edge-color: var(--cj-panel-edge-inset);
    --cj-panel-surface: var(--cj-panel-inset-face);
}

/* ---- panel states ---- */

/* Four states, and each one moves the *edge* — never the face. A panel that
   tints its whole surface for a warning is a panel nobody can read, and a
   dashboard of them is a dashboard nobody looks at twice. */
.panel-emphasis { --cj-panel-edge-color: var(--cj-panel-edge-emphasis); }
.panel-warning { --cj-panel-edge-color: var(--cj-panel-edge-warning); }
.panel-critical { --cj-panel-edge-color: var(--cj-panel-edge-critical); }
.panel-selected {
    --cj-panel-edge-color: var(--cj-panel-edge-selected);
    --cj-panel-surface: var(--cj-panel-selected-face);
}

/* ---- panel headers ---- */

/* `[icon] TITLE ──────────────── optional action`
 *
 * Compact, tracked caps, a small semantic glyph, a hairline under it, and the
 * band reaches both edges of the panel. Reaching the edge is the point: a
 * heading floating inside the panel's padding with a short rule under it is the
 * SaaS card header this pass is replacing. It is what visually joins a header to
 * the table or the form below it.
 *
 * `.panel-head` is for panels built with `x-panel`. The three headers the
 * application already renders — a detail section's, a form section's and a
 * modal's — are given the same treatment where they are declared, rather than
 * being asked to change their markup. */
.panel-head {
    display: flex;
    align-items: center;
    gap: var(--cj-icon-gap);
    min-height: 36px;
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
}
.panel-title {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: .76rem;
    font-weight: 600;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}
.panel-actions { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: var(--cj-space-2); }
.panel-body { padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x); }
/* A body whose whole content is a table or a list hands the gutters to it, and
   keeps back the one pixel the panel's own edge is drawn in. */
.panel-body-flush { padding: 1px; }

/* ---- icons in panels ---- */

/* Three treatments, one family, one delivery mechanism — the shell pass's
   `--cj-icon-*` mask selected by `data-icon` and tinted by `currentColor`. The
   glyph is drawn on the element for a plain or status icon and on `::before` for
   a framed one, because a framed icon's own box carries the frame.
 *
 * Deliberately a separate rule from `.nav-item::before, .header-icon`: that one
 * pins an 18px column because the rail's alignment depends on it, and these are
 * sized to the text beside them. */
.icon,
.panel-icon,
.metric-trend {
    display: block;
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    background-color: currentColor;
    -webkit-mask: var(--cj-glyph, none) center / contain no-repeat;
    mask: var(--cj-glyph, none) center / contain no-repeat;
}
/* A panel's own glyph is gold-dim: present, quiet, and the same on every panel
   so the header band reads as one system. */
.panel-icon { color: var(--cj-gold-dim); }

/* The framed icon — the outlined container the reference's metric tiles use.
   Same cut as everything else, at the smallest size. */
.icon-framed {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border: 1px solid var(--cj-panel-edge);
    background: var(--cj-panel-inset-face);
    color: var(--cj-accent-dark);
    clip-path: polygon(
        var(--cj-chamfer-xs) 0,
        calc(100% - var(--cj-chamfer-xs)) 0,
        100% var(--cj-chamfer-xs),
        100% calc(100% - var(--cj-chamfer-xs)),
        calc(100% - var(--cj-chamfer-xs)) 100%,
        var(--cj-chamfer-xs) 100%,
        0 calc(100% - var(--cj-chamfer-xs)),
        0 var(--cj-chamfer-xs)
    );
}
.icon-framed::before {
    content: '';
    display: block;
    width: 15px;
    height: 15px;
    background-color: currentColor;
    -webkit-mask: var(--cj-glyph, none) center / contain no-repeat;
    mask: var(--cj-glyph, none) center / contain no-repeat;
}

/* A status glyph takes its meaning from the text beside it and its colour from
   the state around it. It is never the only thing saying what the state is —
   `.status-badge` and the attention rows both carry the word as well. */
.icon-ok { color: var(--cj-success); }
.icon-warning { color: var(--cj-warning); }
.icon-critical { color: var(--cj-danger); }
.icon-neutral { color: var(--cj-muted); }

/* ---- the cut on the smaller controls ---- */

/* A field, a choice and a chip are the same family as the panels, so they
   carry the same corner. `button, .button` and `.status-badge` declare their
   own — they need different sizes — and the two icon frames declare theirs,
   which accounts for every cut left in the stylesheet. A page number and the
   row-overflow trigger *were* here too until the button correction below:
   both are declared part of the button family (their own rules still say so
   — `.pagination-page`, `.row-overflow summary`), so both moved to
   `--cj-radius-control` with it rather than being left as the one cut corner
   next to every other now-rounded control. */
.form-control,
.form-choice,
.option-value-chip {
    clip-path: polygon(
        var(--cj-chamfer-sm) 0,
        calc(100% - var(--cj-chamfer-sm)) 0,
        100% var(--cj-chamfer-sm),
        100% calc(100% - var(--cj-chamfer-sm)),
        calc(100% - var(--cj-chamfer-sm)) 100%,
        var(--cj-chamfer-sm) 100%,
        0 calc(100% - var(--cj-chamfer-sm)),
        0 var(--cj-chamfer-sm)
    );
}

/* ---------------------------------------------------------- metric cards ---- */

/* The metric treatment, shared by the navigational tile the HUD pass introduced
   (`.hud-widget`, which is an `<a>` and opens the page it counts) and the plain
   readout this pass adds (`.metric-card`). They are the same object; one of them
   is a link. Keeping them one rule is what stops the dashboard and the interior
   pages growing two different ideas of what a figure looks like.
 *
 * The grid is `[icon] [label]` on the first row and the figure across both on
 * the second, so the number starts at the panel's own text edge rather than
 * being indented under the icon. Nothing is centred: the reference tiles are
 * left-aligned and read down a column, and centring a stat card is the habit
 * that makes six of them look like six unrelated things. */
.metric-card,
.hud-widget {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-content: start;
    align-items: center;
    gap: var(--cj-space-1) var(--cj-icon-gap);
    padding: var(--cj-metric-pad-y) var(--cj-metric-pad-x);
    color: var(--cj-text);
    text-decoration: none;
}
.metric-card > .icon-framed,
.hud-widget > .icon-framed { grid-row: 1; grid-column: 1; }

/* Small, tracked, dim: a label is what the figure is *of*, and it should never
   compete with the figure. */
.metric-label,
.hud-widget-label {
    grid-column: 2;
    align-self: center;
    min-width: 0;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-micro);
    font-weight: 600;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}

/* The figure is the dominant element on the tile. Tabular, so a column of them
   lines up on the same stems; gold, because on this HUD a number is money or a
   count and both are gold. */
.metric-figure {
    display: flex;
    grid-column: 1 / -1;
    align-items: baseline;
    gap: 5px;
    min-width: 0;
}
.metric-figure .metric-value { display: inline; }
.metric-unit {
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
}

/* The comparison or context line, and the trend glyph that can lead it. Both are
   optional; a metric with nothing to compare against says nothing rather than
   saying "0%". */
/* `min-width: 0` on both of these and on the label: a grid item's automatic
   minimum is its content, so a long note refused to wrap and widened the tile
   instead. */
.metric-context {
    display: flex;
    grid-column: 1 / -1;
    min-width: 0;
    align-items: center;
    gap: 6px;
    color: var(--cj-muted);
    font-size: .76rem;
}
.metric-trend { width: 13px; height: 13px; }
.metric-trend-up { color: var(--cj-success); }
.metric-trend-down { color: var(--cj-danger); }
.metric-trend-flat { color: var(--cj-muted); }

/* Metric states. Each moves the edge, the framed icon and the figure — three
   quiet signals rather than one loud tinted card. `-neutral` is the default and
   changes nothing, so it can be passed unconditionally by a caller that computes
   its state.
 *
 * `-positive` deliberately does not paint the panel olive: "this bucket is
 * clear" is the ordinary state of most tiles most days, and a screen where the
 * ordinary state is decorated has spent its emphasis on nothing. */
.metric-neutral,
.metric-positive { --cj-panel-edge-color: var(--cj-panel-edge); }
.metric-positive .metric-value { color: var(--cj-success); }
.metric-positive > .icon-framed { color: var(--cj-success); }
.metric-warning { --cj-panel-edge-color: var(--cj-panel-edge-warning); }
.metric-warning .metric-value { color: var(--cj-warning); }
.metric-warning > .icon-framed { color: var(--cj-warning); border-color: var(--cj-warning-border); }
.metric-critical { --cj-panel-edge-color: var(--cj-panel-edge-critical); }
.metric-critical .metric-value { color: var(--cj-danger); }
.metric-critical > .icon-framed { color: var(--cj-danger); border-color: var(--cj-danger-border); }

/* ------------------------------------------------------------- buttons ---- */

/* One system. `.button` carries the shape and the default (primary) skin, so a
   bare `class="button"` is still a primary action; `.button-*` modifiers change
   only what differs. Nothing here re-declares the whole property set. */
/* Olive fill or gold/olive outline — the HUD skill admits no consumer blue and
   no rounded pill. Labels are tracked uppercase, which is what makes a squared
   control read as a HUD control rather than as an unstyled one. */
/* Approved 2026-09: a conventional rounded rectangle, not the panel's cut
   corner. The cut read as a rendering bug on a filled, bordered, text-bearing
   control in a way it never did on a panel (nothing sat close enough to the
   corner to make the diagonal legible as a decision rather than as clipping),
   and the product owner flagged the secondary-button case in particular. The
   border is a complete rectangle now, at `--cj-radius-control` on every side,
   with nothing clipped away — see the focus-ring note further down for what
   this simplified. `.header-control` still opts out of sharing this rule
   outright below; it carries the notification badge and has its own square
   chrome by design, unrelated to this correction. */
button,
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: var(--cj-control-height);
    border: 1px solid var(--cj-accent-hover);
    border-radius: var(--cj-radius-control);
    padding: 7px 14px;
    background: var(--cj-accent);
    color: var(--cj-on-accent);
    font: inherit;
    font-family: var(--cj-font-display);
    font-weight: 600;
    font-size: .84rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    cursor: pointer;
    text-decoration: none;
    transition: background .15s, border-color .15s, color .15s, opacity .15s;
}

/* An icon inside a button. Decorative: the label beside it is the action. */
.button-icon {
    flex: 0 0 14px;
    width: 14px;
    height: 14px;
    background-color: currentColor;
    -webkit-mask: var(--cj-glyph, none) center / contain no-repeat;
    mask: var(--cj-glyph, none) center / contain no-repeat;
}
button:hover,
.button:hover { background: var(--cj-accent-hover); color: var(--cj-on-accent); text-decoration: none; }

.button-primary { border-color: var(--cj-accent-hover); background: var(--cj-accent); color: var(--cj-on-accent); }
.button-primary:hover { background: var(--cj-accent-hover); }

/* The gold outline. A secondary action is the one the owner reaches for as
   often as the primary, so it is a full-strength edge on carbon, not a greyed
   box. */
.button-secondary {
    border-color: var(--cj-gold-dim);
    background: transparent;
    color: var(--cj-accent-dark);
}
.button-secondary:hover {
    border-color: var(--cj-accent-dark);
    background: #C4A35A1a;
    color: var(--cj-accent-dark);
}

.button-subtle {
    border-color: transparent;
    background: transparent;
    color: var(--cj-muted);
    font-weight: 600;
}
.button-subtle:hover { background: var(--cj-hover); color: var(--cj-text); }

.button-danger { border-color: var(--cj-danger-hover); background: var(--cj-danger-hover); color: var(--cj-text); }
.button-danger:hover { border-color: var(--cj-danger); background: var(--cj-danger); color: var(--cj-on-accent); }

/* The compact table action: a real control, sized for a dense row. Same
   radius as every other button — unlike the retired chamfer, a rounded
   corner does not need a smaller variant to keep from eating the corner of a
   short control. */
.button-small {
    min-height: 28px;
    padding: 3px 9px;
    font-size: var(--cj-text-xs);
}

/* The controls that are not shaped like buttons, stated here rather than left to
   be discovered.
 *
 * `.header-control` carries `.notification-badge` as an absolutely positioned
 * child and `clip-path` clips descendants, so a cut control would clip its own
 * badge; header chrome is square on purpose anyway. The other three are
 * borderless transparent hit areas — there is no shape to cut, and declaring the
 * cut on them would only mean four more places to keep in step. */
.header-control,
.row-action,
.chip-remove,
.modal-close { clip-path: none; }

/* A cut control un-cuts itself while it is focused.
 *
 * A focus ring is painted outside the border box and `clip-path` clips
 * anything outside the shape, so a chamfered control silently ate its own
 * focus ring — browser verification caught the Add material button focusing
 * with no visible indicator at all. Drawing the ring inside the shape instead
 * is not an option on the filled primary: gold on olive measures 2.1:1, below
 * the 3:1 WCAG 2.2 SC 1.4.11 asks of a focus indicator, where the same ring
 * on the page ground measures 8.8:1.
 *
 * So the corner comes back square for exactly as long as the control is
 * focused, and the ring is whole. The ring is the more important of the two.
 *
 * `button`, `.button`, `.pagination-page` and `.row-overflow summary` are
 * gone from this rule as of the 2026-09 button-shape correction: none of the
 * three carries a `clip-path` any more (they moved to a real `border-radius`
 * instead, `--cj-radius-control`), so there is nothing left on them for a
 * focus ring to be clipped by — the global `:focus-visible` rule now draws a
 * complete, unclipped ring on all three the same way it always did on plain
 * unstyled elements. Only the controls that keep the cut corner still need
 * the un-cut-on-focus treatment.
 *
 * `:focus-within` on the last two because the ring belongs to a control
 * *inside* them — a radio inside a choice, the remove button inside an
 * option chip — and a parent's clip path clips its descendants as well as
 * itself. */
.form-control:focus-visible,
.form-choice:focus-within,
.option-value-chip:focus-within { clip-path: none; }

/* Destructive, but not louder than the action beside it. Removing a BOM line
   sat in the same orange as "Continue to Production"; a row of eight identical
   primaries tells the owner nothing about which one is safe. */
.button-danger-subtle { background: transparent; color: var(--cj-danger); font-weight: 650; }
.button-danger-subtle:hover { background: var(--cj-danger-bg); color: var(--cj-danger); }

button:disabled,
.button[aria-disabled=true] { opacity: .55; cursor: not-allowed; }

/* A text-weight action inside a table row. Explicitly returned to the reading
   sans in its natural case: the HUD skill forbids the stencil face in a table
   body, and `.row-action` is worn by `<button>` elements that would otherwise
   inherit the display treatment from the shared button rule above. */
.row-action {
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    padding: 5px 8px;
    border: 0;
    background: transparent;
    color: var(--cj-accent-dark);
    font-family: inherit;
    font-size: .82rem;
    font-weight: 650;
    letter-spacing: normal;
    text-transform: none;
    cursor: pointer;
    text-decoration: none;
}
.row-action:hover { background: transparent; color: var(--cj-warning); text-decoration: underline; }
.row-action-danger { color: var(--cj-danger); }
.row-action-danger:hover { color: var(--cj-danger); }

/* The remove control on an option-value chip. Comfortable to tap, and it says
   what it removes rather than presenting a bare glyph. */
.chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    min-height: 36px;
    padding: 0;
    border: 0;
    border-radius: var(--cj-radius-xs);
    background: transparent;
    color: var(--cj-danger);
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
}
.chip-remove:hover { background: var(--cj-danger-bg); color: var(--cj-danger); }

/* An option value, with the control that removes it. */
.option-value-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--cj-space-1);
    padding: 2px 2px 2px 12px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-pill);
    background: var(--cj-hover);
    color: var(--cj-text);
    font-size: var(--cj-text-sm);
    font-weight: 650;
}
.option-value-chip form { display: inline; margin: 0; }

/* --------------------------------------------------------------- forms ---- */

.form-shell { max-width: 760px; }

/* A grouped set of choices. The browser's default fieldset is a 2px inset ridge
   in its own grey — the one piece of furniture in the application that belonged
   to no system — so it takes the panel language too: the inset surface, a thin
   edge and a tracked-caps legend. */
fieldset {
    margin: 0 0 var(--cj-space-4);
    padding: var(--cj-space-3) var(--cj-space-4);
    border: 1px solid var(--cj-panel-edge-inset);
    border-radius: 0;
    background: var(--cj-panel-inset-face);
}
fieldset > legend {
    padding: 0 var(--cj-space-2);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-micro);
    font-weight: 600;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}

.form-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--cj-space-4); }
/* A field label sits below a panel title in the hierarchy, so it is smaller and
   lighter than it was: at .84rem/750 it was heavier than the panel header above
   it, which inverted the two. */
.form-field label,
.form-label { font-size: .82rem; font-weight: 700; }

/* The required marker. Previously written as `.required` in two templates with
   no rule anywhere, so it rendered as unstyled body text. */
.required { color: var(--cj-danger); font-weight: 750; }

.form-control {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-sm);
    background: var(--cj-surface);
    color: var(--cj-text);
    font: inherit;
}
.form-control:focus { border-color: var(--cj-accent); }
.form-control:focus-visible { outline: 3px solid var(--cj-focus); outline-offset: 2px; }

/* A value Cajella worked out, sitting where the form's other answers sit.
   Aligned like `.form-control` so the Opening inventory group reads as one
   column of fields, and deliberately not bordered like one: it is an <output>,
   not somewhere to type. The figure carries the emphasis instead. */
.form-derived {
    display: block;
    padding: 10px 0;
    color: var(--cj-accent-dark);
    font-variant-numeric: tabular-nums;
    font-size: 1.05rem;
    font-weight: 750;
}

.form-help { color: var(--cj-muted); font-size: .82rem; }
.form-error { color: var(--cj-danger); font-size: .82rem; }
.has-errors .form-control { border-color: var(--cj-danger); }

.form-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
    padding-top: var(--cj-space-4);
    margin-top: var(--cj-space-2);
    border-top: 1px solid var(--cj-border-subtle);
}
/* A form section owns its own footer, so a footer inside one does not draw a
   second rule directly under the section's own border. */
.form-section > .form-actions { border-top: 0; margin-top: 0; }

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

/* A grouped form section is a panel, and its `<h2>` is that panel's header —
   the same full-bleed band, tracked caps and hairline a detail section wears, so
   Business Settings and the Material and Component forms are made of the same
   object as the rest of the application. The markup is untouched: the tests pin
   these headings as a bare `<h2>Identity</h2>`, and the treatment is the
   stylesheet's job anyway. */
.form-section {
    grid-column: 1 / -1;
    padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x);
}
.form-section + .form-section { margin-top: 0; }
.form-section > h2 {
    margin:
        calc(var(--cj-panel-pad-y) * -1)
        calc(var(--cj-panel-pad-x) * -1)
        var(--cj-panel-pad-y);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
    font-size: .8rem;
    letter-spacing: var(--cj-track-label);
    line-height: 1.6;
}
.form-section > h2 + .form-help { display: block; margin: calc(var(--cj-space-3) * -1) 0 var(--cj-space-4); }
.form-section > .form-field:last-child { margin-bottom: 0; }

/* ------------------------------------------------- business settings ---- */

/* Business Settings' composition, and only its composition. The reference
   console's settings screen is a two-column grid of cards, each one a framed
   glyph beside a title, a rule under both, a line of description and then its
   content — so that is the shape here.
 *
 * What the reference is NOT copied on: its six cards are Shop Profile, Users &
 * Roles, Notifications, Integrations, Billing and Data & Backup, and Cajella
 * has none of those six. It has four real groups of settings, and a card for a
 * feature that does not exist is worse than a missing card, because the owner
 * believes they have something to configure. Its footer status bar and its
 * per-card counts and dates are left out for the same reason: nothing in the
 * application knows them.
 *
 * No new primitive. A settings card is a `.form-section` — the same panel the
 * Material and Component forms use — with its full-page `grid-column` released
 * so two of them sit side by side. Everything else it inherits. */
/* Approved 2026-09 (audit hardening; reconfirmed with the width
   normalization pass below): a compound selector, not a same-specificity
   rule that only wins because it happens to be declared after `.form-shell`
   in this file. `.form-shell.settings-form` outranks bare `.form-shell`
   regardless of source order, so the full-width intent survives even if this
   stylesheet is ever reordered or split. The Blade class list is unchanged —
   `business/settings.blade.php` already carries both classes.
   This override is not an obsolete width workaround made redundant by the
   page going `page-wide` — it is the reason the settings-grid can use that
   wider canvas at all. `.form-shell` still means "cap at 760px" everywhere
   else it appears (Customer, Channel, Event, Machine... every simple form in
   the app); without this override the settings-grid's two columns would be
   squeezed into it exactly as before, regardless of how wide the outer page
   is. */
.form-shell.settings-form { max-width: none; }
.settings-grid { gap: var(--cj-space-5); align-items: start; }
/* A card is as tall as what is in it, which is where this departs from the
   reference: its six cards hold a title and two lines each, so equal heights
   cost it nothing. Identity holds one field and Costing display holds two
   selects and three paragraphs, so stretching the row left 150px of empty
   bordered card under the business name — which reads as a missing field rather
   than as alignment. A ragged bottom edge is the honest version. */
.settings-grid > .form-section {
    grid-column: auto;
    align-self: start;
    padding: var(--cj-space-5);
}
/* The card header: glyph, title, and the rule the `.form-section` band already
   draws. One step up in size from a form section's heading, because here the
   card title is the most prominent thing on the card. */
.settings-grid > .form-section > h2 {
    display: flex;
    align-items: center;
    gap: var(--cj-space-3);
    margin:
        calc(var(--cj-space-5) * -1)
        calc(var(--cj-space-5) * -1)
        var(--cj-space-4);
    padding: var(--cj-space-3) var(--cj-space-5);
    font-size: .92rem;
}
/* Larger than a metric tile's frame — the reference's settings glyph is the
   biggest thing on the card after the title. */
.settings-grid .icon-framed { width: 34px; height: 34px; }
.settings-grid .icon-framed::before { width: 19px; height: 19px; }
.settings-grid > .form-section > h2 + .form-help { margin: calc(var(--cj-space-2) * -1) 0 var(--cj-space-4); }

/* A choice control inside a fieldset — sized to match `.form-control` and the
   button primitives it sits beside. */
.form-choice {
    display: inline-flex;
    align-self: flex-start;
    align-items: center;
    gap: var(--cj-space-2);
    min-width: 160px;
    margin: 0 var(--cj-space-2) 6px 0;
    padding: 9px 13px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-sm);
    background: var(--cj-surface);
    color: var(--cj-text);
    font: inherit;
    font-size: .92rem;
    font-weight: 650;
    cursor: pointer;
    transition: background .15s, border-color .15s;
}
.form-choice:hover { border-color: var(--cj-border-hover); background: var(--cj-hover); }
.form-choice input { width: 16px; height: 16px; margin: 0; accent-color: var(--cj-accent); cursor: pointer; }
.form-choice:has(input:checked) { border-color: var(--cj-accent-hover); background: #6B7A3A2e; }
.form-choice:focus-within { outline: 3px solid var(--cj-focus); outline-offset: 2px; }
.has-errors .form-choice { border-color: var(--cj-danger); }

/* The filter row is a strip of compact controls, so its labels are the tracked
   caps the rest of the compact chrome wears rather than body-weight text: seven
   bold sentence-case labels above seven fields was the loudest row on a list
   page and none of it was the data. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--cj-space-3); padding: var(--cj-space-3) 0; }
.filter-bar .form-field { margin-bottom: 0; gap: var(--cj-space-1); }
.filter-bar .form-field label,
.filter-bar .form-label {
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-micro);
    font-weight: 600;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}
.filter-actions { display: flex; align-items: flex-end; gap: var(--cj-space-2); }

/* A list is easier to scan when a row is one line tall. 14px of cell padding
   on an eleven-column table spends 300px on whitespace alone. */
.data-table td { padding: 11px 12px; }
.data-table th { padding: 10px 12px; }

/* The column carrying the record's name. Without a floor it is squeezed by the
   columns beside it and a long name wraps to seven lines, making one row as
   tall as four. The table scrolls instead, which §10 prefers when the columns
   genuinely matter. */
.data-table .cell-name { min-width: 220px; }

/* ------------------------------------------------------------ feedback ---- */

/* A HUD notice is a panel with a coloured left edge — the severity is carried
   by the bar, so the message itself stays readable rather than being tinted to
   the edge of legibility. */
.alert {
    margin-bottom: 20px;
    padding: 12px 16px;
    border: 1px solid;
    border-left-width: 3px;
    border-radius: var(--cj-radius-sm);
}
.alert-success { border-color: var(--cj-success-border); background: var(--cj-success-bg); color: var(--cj-success); }
.alert-info { border-color: var(--cj-info-border); background: var(--cj-info-bg); color: var(--cj-text); }
.alert-warning { border-color: var(--cj-warning-border); background: var(--cj-warning-bg); color: var(--cj-warning); }
.alert-error { border-color: var(--cj-danger-border); background: var(--cj-danger-bg); color: var(--cj-danger); }

/* -------------------------------------------------------------- badges ---- */

/* Status reads by meaning, not by decoration: olive is ready, gold is caution,
   red is failure. Squared and bordered so a pill is still distinguishable in a
   dense table from the text beside it, and tracked uppercase so the word is
   scannable at 0.72rem. */
/* Compact, squared and cut like everything else — a chip, not a pill. The
   previous 2px radius was already square-ish; what made these read as pills was
   their height and padding, so both come down and the corner is cut instead. */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-height: 20px;
    padding: 1px 7px;
    border: 1px solid var(--cj-border-control);
    border-radius: 0;
    background: var(--cj-neutral-bg);
    color: var(--cj-neutral);
    font-family: var(--cj-font-display);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    white-space: nowrap;
    clip-path: polygon(
        var(--cj-chamfer-xs) 0,
        calc(100% - var(--cj-chamfer-xs)) 0,
        100% var(--cj-chamfer-xs),
        100% calc(100% - var(--cj-chamfer-xs)),
        calc(100% - var(--cj-chamfer-xs)) 100%,
        var(--cj-chamfer-xs) 100%,
        0 calc(100% - var(--cj-chamfer-xs)),
        0 var(--cj-chamfer-xs)
    );
}

/* An optional glyph inside a chip. The word is always present, so the glyph is
   decorative and the state is never carried by colour or shape alone. */
.status-badge-icon {
    flex: 0 0 11px;
    width: 11px;
    height: 11px;
    background-color: currentColor;
    -webkit-mask: var(--cj-glyph, none) center / contain no-repeat;
    mask: var(--cj-glyph, none) center / contain no-repeat;
}

/* The palette, by meaning rather than by word.
 *
 * The three tokens the application already emits — active/complete, warning/
 * draft, inactive, danger, info — keep their exact rules. The rest are the
 * vocabulary the later passes need (ok, low, critical, planned, in progress,
 * completed, overdue) mapped onto the same four meanings, so that a page which
 * starts saying OVERDUE does not also start inventing a colour for it. Every one
 * of them is grouped with the rule it shares, which is why there are four rules
 * here and not eleven. */
.status-active,
.status-complete,
.status-ok,
.status-completed { border-color: var(--cj-success-border); background: var(--cj-success-bg); color: var(--cj-success); }
.status-warning,
.status-draft,
.status-low,
.status-progress { border-color: var(--cj-warning-border); background: var(--cj-warning-bg); color: var(--cj-warning); }
.status-inactive,
.status-neutral { border-color: var(--cj-border-control); background: var(--cj-neutral-bg); color: var(--cj-neutral); }
.status-danger,
.status-critical,
.status-overdue { border-color: var(--cj-danger-border); background: var(--cj-danger-bg); color: var(--cj-danger); }
.status-info,
.status-planned { border-color: var(--cj-info-border); background: var(--cj-info-bg); color: var(--cj-text); }

/* -------------------------------------------------------------- tables ---- */

.table-responsive { overflow-x: auto; }

/* Dense and scannable. The header row is the only part of a table that takes
   the display face — the body stays in the reading sans, which the HUD skill
   requires of a table body. */
.data-table { width: 100%; border-collapse: collapse; font-size: .89rem; }
/* The header row is a typographic band, not a filled one. The fill it used to
   carry is what stopped a table sitting inside a panel: at the top of a flush
   table it painted a solid strip across the panel's own 1px edge and over both
   of its cut corners, which is exactly the "floating SaaS element in a card"
   look the panel pass exists to remove. Caps, tracking and a gold hairline
   separate the header from the rows without a block of colour. */
.data-table th {
    padding: 12px 14px;
    border-bottom: 1px solid var(--cj-gold-dim);
    background: transparent;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-xs);
    font-weight: 600;
    letter-spacing: .16em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}
.data-table td { padding: 14px; border-bottom: 1px solid var(--cj-border-subtle); vertical-align: middle; }

/* A sortable header's link fills its cell. As bare inline text it was a 14px
   tall tap target, well under anything usable on a phone. */
.data-table th a {
    display: block;
    padding: 12px 14px;
    margin: -12px -14px;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
}
.data-table th a:hover { color: var(--cj-text); text-decoration: none; }

/* A link inside a body cell gets a usable hit area, and the layout does not
   notice.
   A record's name in a table cell was a 16px-tall tap target — the height of its
   own text — against the 32px floor every other control in the application
   meets, which three browser verifications recorded as a finding. The header rule
   above solves the same problem with padding and a cancelling negative margin,
   and that technique does NOT transfer to a body cell: an inline-block's margin
   box still participates in line layout, and the first attempt measured rows at
   69px against 44px. Table density is not something to spend on a tap target.
   So the hit area is a pseudo-element. Pseudo-elements are out of flow, so row
   height, column widths and density are identical to before, and the overlay
   reaches 14px above and below the text — 44px in all — without reaching sideways
   into the next column. Buttons are excluded: they clear the floor already. */
.data-table td a:not(.button) { position: relative; }
.data-table td a:not(.button)::after {
    content: '';
    position: absolute;
    inset: -14px 0;
}
.data-table tbody tr:hover { background: #6B7A3A1a; }
.data-table td small { display: block; color: var(--cj-muted); font-size: .78rem; }

/* A figure column. Right-aligned and tabular so digits line up down the column
   and a total sits under its addends. Gold, because on this HUD a number is
   money or a count and both are gold. */
.data-table th.numeric,
.data-table td.numeric,
.numeric { text-align: right; font-variant-numeric: tabular-nums; }
.data-table td.numeric { color: var(--cj-accent-dark); }

.table-total-label { text-align: right; }

/* An actions cell keeps its controls on one row and stays vertically centred.
   It was `display:flex`, which both broke `vertical-align` and let five
   controls wrap into four lines, making every row of the Materials list about
   200px tall. */
.data-table td.actions { white-space: nowrap; }
.data-table td.actions form { display: inline; margin: 0; }
.row-actions { display: inline-flex; align-items: center; gap: var(--cj-space-1); }
.row-actions form { display: inline; margin: 0; }

/* A toolbar above a table is that panel's header band, so it is the same
   treatment as every other panel header: transparent, compact, hairline under
   it. Filled, it painted over the panel edge and the cut corners the same way
   the table header row did. */
.table-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--cj-space-3);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
    background: transparent;
}
.toolbar-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

.row-overflow { position: relative; display: inline-block; }
.row-overflow summary {
    padding: 4px 9px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-control);
    background: var(--cj-hover);
    color: var(--cj-muted);
    cursor: pointer;
    font-weight: 800;
    list-style: none;
}
.row-overflow summary::-webkit-details-marker { display: none; }
.row-overflow[open] summary { border-color: var(--cj-border-hover); background: var(--cj-bg); color: var(--cj-accent-dark); }
.row-overflow-menu {
    --cj-panel-cut: var(--cj-chamfer-sm);
    --cj-panel-edge-color: var(--cj-panel-edge-emphasis);
    --cj-panel-surface: var(--cj-bg);
    position: absolute;
    z-index: 3;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-width: 170px;
    padding: 6px;
    box-shadow: var(--cj-shadow-lg);
}
.row-overflow-menu form { margin: 0; }
.row-overflow-menu .row-action { display: flex; align-items: center; width: 100%; min-height: 40px; text-align: left; }
.row-overflow summary { display: inline-flex; align-items: center; min-height: 34px; }

.color-swatch {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-right: 6px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-xs);
    vertical-align: middle;
}

/* ---------------------------------------------------------- pagination ---- */

/* The one paginator, rendered by resources/views/pagination/cajella.blade.php
   for every ->links() call in the application.

   It replaces Laravel's default Tailwind view, whose utilities this stylesheet
   does not define: `sm:hidden` and `hidden` are what switch that view's two
   responsive halves, so with no rule behind them both halves rendered at once
   and the numbered links stacked with no flex container — 2,272px of pagination
   at 1440 and 826px at 390. Previous and Next reuse .button .button-small
   .button-secondary rather than restating a control, so they inherit the hover,
   focus and disabled treatment the rest of the application already has. */

.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--cj-space-3);
    padding: var(--cj-space-4) 0 0;
}

.pagination-summary { margin: 0; color: var(--cj-muted); font-size: var(--cj-text-sm); }

.pagination-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--cj-space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Previous and Next keep a little air between themselves and the numbers. */
.pagination-step { margin: 0 var(--cj-space-2); }

/* A disabled step is a span, not an anchor, so it is not tabbable and cannot be
   followed. This stops the shared .button:hover accent from firing on it — the
   control has to look inert as well as be inert — without touching the button
   system itself. */
.pagination .button[aria-disabled=true],
.pagination .button[aria-disabled=true]:hover {
    border-color: var(--cj-border);
    background: transparent;
    color: var(--cj-muted);
}

.pagination-page-item { display: inline-flex; }

.pagination-page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    min-height: 34px;
    padding: 0 var(--cj-space-2);
    border: 1px solid transparent;
    border-radius: var(--cj-radius-control);
    color: var(--cj-text);
    font-size: var(--cj-text-sm);
    font-weight: 600;
    text-decoration: none;
}
a.pagination-page:hover { border-color: var(--cj-border-hover); background: var(--cj-hover); color: var(--cj-accent-dark); text-decoration: none; }

/* The current page is told apart three ways, so colour is never the only one:
   it is heavier, it is a filled bordered box rather than plain text, and it is
   not a link — there is nothing to hover and nothing to follow. */
.pagination-page.is-current {
    border-color: var(--cj-accent-hover);
    background: var(--cj-accent);
    color: var(--cj-on-accent);
    font-weight: 750;
    cursor: default;
}

.pagination-gap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    min-height: 34px;
    color: var(--cj-muted);
}

/* The position in words stands in for the window on a phone, so it is absent
   wherever the window is present. `is-always` is the simple paginator, which has
   no window at any width. */
.pagination-position {
    display: none;
    color: var(--cj-muted);
    font-size: var(--cj-text-sm);
}
.pagination-position.is-always { display: inline-flex; margin: 0 var(--cj-space-2); }

/* --------------------------------------------------------- empty state ---- */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 150px;
    padding: var(--cj-space-5);
    color: var(--cj-muted);
    text-align: center;
}
.empty-state strong { color: var(--cj-text); font-size: var(--cj-text-lg); }
.empty-state span { max-width: 52ch; }
.empty-state-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--cj-space-2); }
.empty-state-actions form { margin: 0; }

/* An unknown value. Distinct from a figure that is genuinely zero — see
   `x-money` and Decision Log: "Unset money is not zero". */
.value-unset { color: var(--cj-muted); }

/* ----------------------------------------------------- cards and detail ---- */

/* A carbon panel: the shape, the edge and the face all come from the panel
   geometry above, so all that is left here is what a card is *for* — its
   padding.
 *
 * The gold top hairline every card used to wear is gone. It was applied to every
 * panel on every page, which made it decoration rather than emphasis; emphasis
 * is `.panel-emphasis` now, and a row of panels is separated by the grid gap and
 * by their own cut corners rather than by a stripe. The drop shadow is gone for
 * the same reason the skill rejects glassmorphism, and because a rectangular
 * shadow under a chamfered panel draws the corners the cut just removed. */
.card,
.section-card {
    padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x);
    box-shadow: none;
}

/* A region nested inside a panel is the inset surface, not a second panel and
   not a borderless void. It used to be the latter: `border: 0` left a nested
   card with no edge at all, so a form section inside a card had no boundary and
   its heading rule was the only thing separating it from the section above. */
.card .card,
.card .form-section,
.card .detail-section {
    --cj-panel-cut: var(--cj-chamfer-sm);
    --cj-panel-edge-color: var(--cj-panel-edge-inset);
    --cj-panel-surface: var(--cj-panel-inset-face);
    box-shadow: none;
}

/* A table fills its panel.
 *
 * The header row, the rows and the panel edge then line up, which is what makes
 * the table read as the panel's content rather than as a floating element that
 * happens to be inside one. It is done with negative margins against the panel's
 * own padding tokens, so it needs no markup change: both `x-table-shell` and
 * `x-data-table` emit `.table-responsive` as the card's direct child.
 *
 * The 1px the margin holds back is the panel's edge. A table that reached
 * `inset: 0` would paint its header row and its hover tint over the very line
 * that draws the panel, and over the cut corners with it — which is also why the
 * header row below is a typographic band with a hairline rather than a filled
 * one. */
.card > .table-responsive {
    margin: calc((var(--cj-panel-pad-y) - 1px) * -1) calc((var(--cj-panel-pad-x) - 1px) * -1);
}
/* The toolbar above a flush table is that table's header band, so it carries the
   panel-header padding rather than the toolbar's own. */
.card > .table-responsive > .table-toolbar {
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
}

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

.detail-section {
    padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x);
    box-shadow: none;
}

/* The heading, its supporting line and the section's actions. The subtitle used
   to sit in the same flex row as the heading, so supporting copy floated to the
   far right of a wide card and read as unrelated text. It belongs under the
   heading it supports; only the actions stay on the right. */
/* This is a panel header, so it is treated as one: the band reaches both edges
   of the panel by cancelling the panel's horizontal padding, it sits tight to
   the top edge, and a hairline joins it to the body below. Before this it was a
   heading inside the padding with a short rule under it — the SaaS card header
   the panel pass is replacing — and at `--cj-text-xl` it was also the second
   largest thing on a detail page after the page's own command header. */
.detail-section-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--cj-space-4);
    margin:
        calc(var(--cj-panel-pad-y) * -1)
        calc(var(--cj-panel-pad-x) * -1)
        var(--cj-panel-pad-y);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
}
.detail-section-heading { min-width: 0; }
.detail-section-header h2 {
    margin: 0;
    font-size: .8rem;
    letter-spacing: var(--cj-track-label);
    line-height: 1.6;
}
.detail-section-subtitle { margin: var(--cj-space-1) 0 0; color: var(--cj-muted); font-size: .88rem; }
.detail-section-actions { display: flex; flex-wrap: wrap; gap: var(--cj-space-2); }

.detail-section-body { min-width: 0; }

.detail-rows { display: grid; grid-template-columns: auto 1fr; gap: var(--cj-space-3) 20px; margin: 0; }
.detail-row { display: contents; }
.detail-row dt { color: var(--cj-muted); font-size: .88rem; }
/* A detail value is a value, not a reason for the page to scroll sideways.
   `.detail-rows` is an `auto 1fr` grid and `.detail-row` is `display: contents`,
   so the value is a direct grid item — and a grid item's default `min-width` is
   `auto`, which refuses to shrink below its content. One long unbreakable string
   (a supplier's purchase URL is the usual one) therefore widened the grid past
   its card and took the whole page with it: Task 3B browser verification
   measured 58px of horizontal page overflow at 390px and 27px at 1024px on the
   Component detail page. `min-width: 0` lets the column shrink, and
   `overflow-wrap` gives the string somewhere to break. */
.detail-row dd { margin: 0; min-width: 0; overflow-wrap: anywhere; font-weight: 650; }

/* The metric grid, shared by the tiles and by the figure groups on the detail
   pages. Same floor and same gap as `.hud-widgets`, because a row of figures is
   a row of figures wherever it appears. */
.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--cj-grid-gap); }

/* The figure is the punch. Gold, tabular, and in the display face so a column
   of them lines up on the same stems. */
.metric-value {
    display: block;
    color: var(--cj-accent-dark);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-metric-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: .03em;
    line-height: 1.15;
}

/* The identity band above a product's tabs. A panel like any other; the olive
   left rule it used to carry is superseded by the shape — a 3px stripe on one
   side of an otherwise 1px edge was the old way of making a panel distinctive,
   and the cut corners do that now for every panel at once. */
.product-identity {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cj-space-3) var(--cj-space-6);
    margin-bottom: var(--cj-space-4);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    box-shadow: none;
}
.product-identity-item { display: flex; flex-direction: column; gap: var(--cj-space-1); }
.product-identity-item small {
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .18em;
    text-transform: uppercase;
}
.product-identity-item strong { color: var(--cj-text); font-size: 1.02rem; }

/* ------------------------------------------------- HUD: count widgets ---- */

/* The three patterns the HUD skill names as components: a count widget that
   navigates, a money figure, and an attention rail. They are shared, not
   dashboard-only — the interior pages use the same vocabulary above their one
   primary table, which is what stops the application growing a second visual
   system for the same job.

   `auto-fit` with a 168px floor gives six tiles across a 1440px page, three at
   tablet and one on a phone, with no breakpoint of their own. */
.hud-widgets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
    gap: var(--cj-grid-gap);
    margin-bottom: var(--cj-space-5);
}

/* A tile is a link. The whole tile, not a link inside it: the skill says a
   widget opens its destination, and a 168x104 target is easier to hit than a
   caption.
 *
 * Everything about how it *looks* is now the shared metric treatment in the
   panels section — the shape, the edge, the face, the grid, the icon, the label
   and the figure. What is left here is what a tile does that a plain metric card
   does not: it is a fixed minimum height so a row of them is even whatever
   wording each carries, and it responds to a pointer.
 *
 * `::after` was the rotated-square corner tick. It is the panel face now, and
   the tick is superseded rather than moved: the chamfered corner *is* the corner
   treatment, and a reticule mark over the top of it was two marks for one job.
   `overflow: hidden`, which existed to contain that tick, went with it — a
   panel that hides its overflow also hides the row-action menus inside it. */
.hud-widget {
    min-height: 100px;
    font-weight: 400;
    transition: color .15s;
}
.hud-widget:hover {
    --cj-panel-edge-color: var(--cj-panel-edge-emphasis);
    --cj-panel-surface: var(--cj-panel-inset-face);
    color: var(--cj-text);
    text-decoration: none;
}
.hud-widget:hover .hud-widget-value { color: var(--cj-warning); }
.hud-widget:hover > .icon-framed { color: var(--cj-accent-dark); border-color: var(--cj-gold-dim); }

/* The number is the visual punch the skill asks for, and on a tile it is the
   largest text in the application after a page's own command header. */
.hud-widget-value {
    grid-column: 1 / -1;
    color: var(--cj-accent-dark);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-metric);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: .02em;
    line-height: 1;
    transition: color .15s;
}
.hud-widget-note { grid-column: 1 / -1; min-width: 0; color: var(--cj-muted); font-size: .74rem; overflow-wrap: anywhere; }

/* Severity on a tile, through the shared metric states: the edge, the framed
   icon and the figure move, and the face never does.
 *
 * `-ok` is deliberately quiet. It used to draw an olive left rule, which meant
   that on an orderly shop every tile on the dashboard was decorated — and a
   screen where the ordinary state is decorated has spent its emphasis on
   nothing. Gold and red appear only when the count behind them is non-zero,
   which is the rule that keeps them worth noticing. */
.hud-widget-ok { --cj-panel-edge-color: var(--cj-panel-edge); }
.hud-widget-warn { --cj-panel-edge-color: var(--cj-panel-edge-warning); }
.hud-widget-warn .hud-widget-value { color: var(--cj-warning); }
.hud-widget-warn > .icon-framed { color: var(--cj-warning); border-color: var(--cj-warning-border); }
.hud-widget-critical { --cj-panel-edge-color: var(--cj-panel-edge-critical); }
.hud-widget-critical .hud-widget-value { color: var(--cj-danger); }
.hud-widget-critical > .icon-framed { color: var(--cj-danger); border-color: var(--cj-danger-border); }
.hud-widget-critical:hover .hud-widget-value { color: var(--cj-danger); }
.hud-widget-critical:hover > .icon-framed { color: var(--cj-danger); border-color: var(--cj-danger-border); }

/* A readout rather than a link. Machines, Packaging Materials and Write-offs
   have no query-string filters, so a tile on those pages has nowhere to send
   the owner that they are not already looking at. It states the figure and
   stops there — the alternative is a widget that links to the page it is on,
   which teaches that tiles do nothing. It is a `<div>`, not an `<a>`, so it is
   also not in the tab order. */
.hud-widget-static { cursor: default; }
.hud-widget-static:hover { border-color: var(--cj-border); background: var(--cj-surface); }
.hud-widget-static:hover .hud-widget-value { color: var(--cj-accent-dark); }

/* ---------------------------------------------------------- HUD: money ---- */

/* The few figures a shop owner checks first, on one band rather than in cards
   of their own. Gold, tabular, with dividers between them. */
.money-strip {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cj-space-4) var(--cj-space-6);
    margin-bottom: var(--cj-space-5);
    padding: var(--cj-space-4) var(--cj-panel-pad-x);
}
.money-figure {
    display: flex;
    flex: 1 1 180px;
    flex-direction: column;
    gap: var(--cj-space-1);
    padding-left: var(--cj-space-4);
    border-left: 1px solid var(--cj-panel-edge-inset);
}
.money-figure:first-child { padding-left: 0; border-left: 0; }
.money-label {
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .2em;
    text-transform: uppercase;
}
.money-value {
    color: var(--cj-accent-dark);
    font-family: var(--cj-font-display);
    font-size: 1.55rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
/* The total of the figures beside it, marked as the one to read first. */
.money-figure-total .money-value { font-size: 1.85rem; }
.money-figure-total .money-label { color: var(--cj-accent-dark); }

/* ------------------------------------------------ HUD: attention rail ---- */

/* Severity, one line of why, and a jump. Scarce by construction: a row exists
   only when its count is non-zero, so an empty rail is a shop with nothing
   waiting rather than a panel of zeroes. */
/* The rail is a panel whose header is a header band: it holds no padding of its
   own, and the heading and the list carry theirs, so the hairline under the
   heading runs edge to edge and the rows reach both edges too. That is the
   difference between a panel with a title in it and a panel with a header. */
.attention-rail { padding: 0; }
.attention-heading {
    display: flex;
    align-items: center;
    gap: var(--cj-icon-gap);
    min-height: 36px;
    margin: 0;
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}
/* The header's glyph, drawn rather than written: the heading text already says
   what the panel is, so the mark is decorative and a pseudo-element keeps it out
   of the accessibility tree without anyone having to remember `aria-hidden`.
   It also means the two pages that render a rail — the brief and the Materials
   list — gain it without either template changing. */
.attention-heading::before {
    content: '';
    flex: 0 0 14px;
    width: 14px;
    height: 14px;
    background-color: var(--cj-gold-dim);
    -webkit-mask: var(--cj-icon-triangle-alert) center / contain no-repeat;
    mask: var(--cj-icon-triangle-alert) center / contain no-repeat;
}
/* 1px of side padding so a row's severity rule sits *inside* the panel's edge
   rather than painting over it. */
.attention-list { margin: 0; padding: var(--cj-space-1) 1px; list-style: none; }

/* An attention row: severity, what happened, optionally the value or the
   context, and a jump.
 *
 * A row supports more than the two shapes rendered today — `.attention-severity`
 * for the chip, `.attention-detail` and `.attention-context` for a second line,
 * `.attention-value` for a figure or a time — because the later Materials and
 * Production passes need those and a second row primitive for the same job is
 * how an application ends up with two. The two shapes in use (count + why + jump
 * on the brief, why + jump on the Materials list) are unchanged.
 *
 * Severity is a left rule *and* a glyph *and* the colour of the leading figure.
 * Never colour alone: every row states its condition in words. */
.attention-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--cj-space-2) var(--cj-space-3);
    min-height: var(--cj-row-height);
    padding: var(--cj-space-2) var(--cj-panel-pad-x);
    border-left: 2px solid var(--cj-border-control);
    color: var(--cj-text);
    font-weight: 400;
    text-decoration: none;
}
/* The severity glyph. Neutral by default; each severity names its own below. */
.attention-item::before {
    content: '';
    flex: 0 0 15px;
    width: 15px;
    height: 15px;
    background-color: var(--cj-muted);
    -webkit-mask: var(--cj-icon-info) center / contain no-repeat;
    mask: var(--cj-icon-info) center / contain no-repeat;
}
.attention-item + .attention-item { border-top: 1px solid var(--cj-border-subtle); }
.attention-item:hover { background: var(--cj-hover); color: var(--cj-text); text-decoration: none; }

/* The count leads the row, because it is what decides whether the row is worth
   reading. */
.attention-count {
    flex: 0 0 auto;
    min-width: 2.2ch;
    color: var(--cj-accent-dark);
    font-family: var(--cj-font-display);
    font-size: 1.15rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
}
/* The severity chip, for the rows that lead with a word rather than a figure. */
.attention-severity { flex: 0 0 auto; }
/* Message over context, as one block, so a two-line row still has one left
   edge and the value and the jump stay on the row's own baseline. */
.attention-detail { display: flex; flex: 1 1 12rem; flex-direction: column; gap: 2px; min-width: 0; }
.attention-why { flex: 1 1 auto; min-width: 0; font-size: .88rem; }
.attention-detail .attention-why { flex: 0 0 auto; }
.attention-context { color: var(--cj-muted); font-size: .78rem; overflow-wrap: anywhere; }
.attention-value {
    flex: 0 0 auto;
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: .84rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
}
.attention-jump {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    flex: 0 0 auto;
    /* Whatever the row wraps, the jump stays on the right of the last line: in
       a narrow rail the message takes a line of its own, and a jump left
       against it read as a third piece of body text. */
    margin-left: auto;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .66rem;
    letter-spacing: .16em;
    text-transform: uppercase;
}
/* The chevron the reference rows end on. Drawn, decorative, and it follows the
   colour of the label it belongs to. */
.attention-jump::after {
    content: '';
    width: 12px;
    height: 12px;
    background-color: currentColor;
    -webkit-mask: var(--cj-icon-chevron-right) center / contain no-repeat;
    mask: var(--cj-icon-chevron-right) center / contain no-repeat;
}
.attention-item:hover .attention-jump { color: var(--cj-accent-dark); }

.attention-item-critical { border-left-color: var(--cj-danger); }
.attention-item-critical::before { background-color: var(--cj-danger); -webkit-mask-image: var(--cj-icon-circle-alert); mask-image: var(--cj-icon-circle-alert); }
.attention-item-critical .attention-count { color: var(--cj-danger); }
.attention-item-warning { border-left-color: var(--cj-warning-border); }
.attention-item-warning::before { background-color: var(--cj-warning); -webkit-mask-image: var(--cj-icon-triangle-alert); mask-image: var(--cj-icon-triangle-alert); }
.attention-item-warning .attention-count { color: var(--cj-warning); }
.attention-item-info { border-left-color: var(--cj-accent); }
.attention-item-info::before { background-color: var(--cj-accent-hover); }

.attention-empty {
    display: flex;
    align-items: center;
    gap: var(--cj-space-2);
    margin: 0;
    padding: var(--cj-space-3) var(--cj-panel-pad-x);
    color: var(--cj-muted);
    font-size: .88rem;
}
.attention-empty::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: var(--cj-radius-pill);
    background: var(--cj-success);
}

/* ------------------------------------------------ HUD: command brief ---- */

/* The dashboard's two columns: the brief on the left, the rail on the right.
   A single grid rather than nested flex, so the rail's height is the row's
   height and the two columns end level. */
.brief-grid { display: grid; grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr); gap: var(--cj-space-5); align-items: start; }
.brief-main { min-width: 0; }
.brief-rail { min-width: 0; }

/* The brief holds six tiles beside a rail, so it needs a wider floor than the
   shared 168px: at that floor the column fitted four across and the last two
   wrapped onto a short second row, which read as two tiles that had fallen off
   the first. A 200px floor gives three and three at desktop and two and two and
   two once the rail takes its share, and it does that arithmetically rather than
   with a breakpoint per width. */
.brief-main .hud-widgets { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* ---------------------------------------------------------------- tabs ---- */

/* Approved 2026-09: Product Detail moved to the wide page treatment
   specifically because seven tabs (an eighth on a Product with versions'
   own sub-navigation would not fit) were producing a horizontal scrollbar at
   ordinary desktop widths — the row was wider than the 1240px shell had room
   for. Width alone does not finish the job: rendering the real seven labels
   (measured, not guessed — "Bill of Materials" is the long one) against the
   256px rail at 1024px, one of this fix's own required verification widths,
   still overflowed by ~50px after a first, smaller trim (space-4 to
   space-3, .12em to .1em). `.detail-tab`'s padding is now `--cj-space-1`
   and its letter-spacing `.06em` — the same tracking `.user-control-name`
   already uses at this exact .86rem, so the value is reused from an
   existing label at the same size, not invented for this rule. Verified:
   the row now fits with no scrollbar at 1440/1024/768px; at 390px it still
   scrolls, which is the explicitly allowed mobile behaviour below. */
.detail-tabs {
    display: flex;
    gap: var(--cj-space-1);
    margin-bottom: var(--cj-space-5);
    padding: 0 2px;
    border-bottom: 1px solid var(--cj-border);
    overflow-x: auto;
    scrollbar-width: thin;
}
.detail-tab {
    flex: 0 0 auto;
    margin-bottom: -1px;
    padding: 10px var(--cj-space-1);
    border: 1px solid transparent;
    border-bottom: 2px solid transparent;
    border-radius: var(--cj-radius-sm) var(--cj-radius-sm) 0 0;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: .86rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    white-space: nowrap;
}
.detail-tab:hover { background: var(--cj-hover); color: var(--cj-text); text-decoration: none; }
.detail-tab-current {
    border-color: var(--cj-border);
    border-bottom-color: var(--cj-accent-hover);
    background: var(--cj-surface);
    color: var(--cj-accent-dark);
}
.detail-tab-current:hover { background: var(--cj-surface); color: var(--cj-accent-dark); }

/* --------------------------------------------------------------- modal ---- */

/* A dialog opened with showModal() lives in the top layer, so the shade behind
   it is ::backdrop, not an element. A tall modal keeps its header and footer
   and scrolls its body, otherwise the submit button sits outside the viewport. */
/* A modal is a panel that genuinely floats, so it is the one panel that keeps a
   shadow — and it takes the emphasis edge, because it is the only thing on the
   screen the owner is being asked to deal with. */
.modal-dialog {
    --cj-panel-edge-color: var(--cj-panel-edge-emphasis);
    width: 90%;
    max-width: 580px;
    color: var(--cj-text);
    box-shadow: var(--cj-shadow-modal);
}
dialog.modal-dialog::backdrop { background: #05060499; }
dialog.modal-dialog[open] {
    display: flex;
    flex-direction: column;
    max-height: min(90vh, 100% - 2rem);
    padding: 0;
    overflow: hidden;
}
dialog.modal-dialog[open] .modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
dialog.modal-dialog[open] .modal-header,
dialog.modal-dialog[open] .modal-footer { flex: 0 0 auto; }

/* The same header band as every other panel: compact, tracked caps, hairline. */
.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cj-space-4);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
}
.modal-header h2 { margin: 0; font-size: .84rem; letter-spacing: var(--cj-track-label); line-height: 1.6; }
.modal-body { padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x); }
.modal-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding: var(--cj-space-3) var(--cj-panel-pad-x);
    border-top: 1px solid var(--cj-panel-edge);
    background: var(--cj-panel-inset-face);
}
.modal-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 2px 10px;
    border: 0;
    border-radius: var(--cj-radius-sm);
    background: transparent;
    color: var(--cj-muted);
    font-size: 1.4rem;
    line-height: 1;
}
.modal-close:hover { background: var(--cj-neutral-bg); color: var(--cj-text); }

/* -------------------------------------------------------------- wizard ---- */

/* Product setup. These rules were a <style> block inside x-wizard-layout with a
   hardcoded palette — the step markers were blue and green, which are not
   Cajella colours, making the wizard's most prominent element the one thing on
   screen that did not belong to the product. */
.wizard-container { max-width: 1240px; margin: 0 auto; }
.wizard-header { margin-bottom: var(--cj-space-6); }
.wizard-header h1 {
    margin: 0 0 var(--cj-space-5);
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: clamp(1.6rem, 2.2vw, var(--cj-text-2xl));
    font-weight: 600;
    letter-spacing: .1em;
    line-height: 1.15;
    text-transform: uppercase;
}

.wizard-progress { display: flex; position: relative; gap: var(--cj-space-2); padding-bottom: var(--cj-space-5); }
.wizard-progress::before {
    content: '';
    position: absolute;
    z-index: 0;
    top: 19px;
    right: 0;
    left: 0;
    height: 2px;
    background: var(--cj-border);
}
.wizard-step { flex: 1; position: relative; z-index: 1; text-align: center; }
/* A squared step marker rather than a circle: the HUD is angular, and a
   36px square reads as a stage on a progress rail as clearly as a disc does. */
.wizard-step .step-number {
    display: inline-block;
    width: 38px;
    height: 38px;
    line-height: 36px;
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-pill);
    background: var(--cj-surface);
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-weight: 600;
    margin-bottom: var(--cj-space-2);
}
.wizard-step .step-name {
    display: block;
    color: var(--cj-muted);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-sm);
    letter-spacing: .1em;
    text-transform: uppercase;
}
.wizard-step.complete .step-number {
    border-color: var(--cj-success-border);
    background: var(--cj-success-bg);
    color: var(--cj-success);
}
.wizard-step.current .step-number {
    border-color: var(--cj-accent-hover);
    background: var(--cj-accent);
    color: var(--cj-on-accent);
}
.wizard-step.current .step-name { color: var(--cj-accent-dark); font-weight: 700; }
.wizard-step-link { display: block; padding: 2px; color: inherit; text-decoration: none; cursor: pointer; }
.wizard-step-link:hover { text-decoration: none; }
.wizard-step-link:hover .step-number { border-color: var(--cj-border-hover); background: var(--cj-hover); }

.wizard-content {
    padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x);
    box-shadow: none;
}
.wizard-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: var(--cj-space-5);
    padding-top: var(--cj-space-5);
    border-top: 1px solid var(--cj-border-subtle);
}
/* A variant inside the wizard's own panel: the inset surface, so it reads as a
   region of the step rather than as a panel floating on one. */
.wizard-variant-card {
    --cj-panel-cut: var(--cj-chamfer-sm);
    --cj-panel-edge-color: var(--cj-panel-edge-inset);
    --cj-panel-surface: var(--cj-panel-inset-face);
    margin-bottom: var(--cj-space-4);
    padding: var(--cj-space-4);
}
.wizard-variant-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--cj-space-3);
    margin-bottom: var(--cj-space-4);
}
.wizard-variant-header h3 { margin: 0; font-size: var(--cj-text-lg); }
.wizard-variant-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--cj-space-2); }
.wizard-variant-actions form { margin: 0; }
.wizard-intro { margin: 0 0 var(--cj-space-4); color: var(--cj-muted); }
/* The step's own title is the step panel's header band. */
.wizard-content > h2:first-child {
    margin:
        calc(var(--cj-panel-pad-y) * -1)
        calc(var(--cj-panel-pad-x) * -1)
        var(--cj-panel-pad-y);
    padding: var(--cj-panel-head-pad-y) var(--cj-panel-pad-x);
    border-bottom: 1px solid var(--cj-panel-edge);
    font-size: .8rem;
    letter-spacing: var(--cj-track-label);
    line-height: 1.6;
}
.form-control-small { width: auto; padding: 6px 10px; font-size: var(--cj-text-sm); }
.wizard-variant-header small { display: block; margin-top: 2px; color: var(--cj-muted); font-weight: 400; }

/* ---------------------------------------------------------------- auth ---- */

.auth-page { min-height: 100vh; padding: 8vh 20px; background: var(--cj-bg); }
.auth-shell { max-width: 460px; margin: 0 auto; }
/* The same grid as `.sidebar-brand`, so the mark, the wordmark and the
   sub-label stack the same way on both surfaces — one system, not two. Centred
   as a block rather than by text alignment, because a grid's columns are sized
   to their content. */
.auth-brand {
    display: grid;
    grid-template-columns: auto auto;
    align-items: center;
    justify-content: center;
    column-gap: 10px;
    width: fit-content;
    margin: 0 auto 20px;
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: .2em;
    text-transform: uppercase;
}
/* The same mark the rail wears, so the sign-in screen belongs to the same
   application rather than being a plain form on a dark page. */
/* Approved 2026-09: same correction as `.sidebar-brand::before` — the
   canonical mark via `--cj-brand-mark`, in place of the rotated-square
   placeholder, using the same mask mechanism every glyph in the shell uses. */
.auth-brand::before {
    content: '';
    display: block;
    width: 14px;
    height: 14px;
    background-color: currentColor;
    -webkit-mask: var(--cj-brand-mark) center / contain no-repeat;
    mask: var(--cj-brand-mark) center / contain no-repeat;
}
.auth-brand:hover { color: var(--cj-accent-dark); text-decoration: none; }
.auth-brand small { grid-column: 2; display: block; color: var(--cj-muted); font-size: .6rem; font-weight: 500; letter-spacing: .28em; }
.auth-card { padding: 36px; }
.auth-card h1 {
    margin: 0 0 var(--cj-space-2);
    color: var(--cj-text);
    font-family: var(--cj-font-display);
    font-size: var(--cj-text-2xl);
    font-weight: 600;
    letter-spacing: .08em;
    line-height: 1.15;
    text-transform: uppercase;
}
.auth-card .intro { margin: 0 0 22px; color: var(--cj-muted); }
.auth-card .form-actions { justify-content: stretch; border-top: 0; }
.auth-card .form-actions .button { width: 100%; }
.auth-footnote { margin-top: 20px; color: var(--cj-muted); text-align: center; }
/* Comfortable to tap, not a 19px line of text. */
.auth-footnote a,
.auth-card .intro a { display: inline-block; padding: 10px 4px; }
.auth-tagline { color: var(--cj-muted); text-align: center; }

/* Present for assistive technology, absent visually — an actions column needs
   a header that a screen reader can announce but that would only add noise to
   the visible header row. */
/* Deliberately NOT position:absolute. An absolutely positioned descendant is
   laid out against its nearest positioned ancestor, not against the scroll
   container it sits in — so inside a horizontally scrolling table it escaped
   the container entirely and pushed the page 243px wide on its own. Kept in
   flow at 1px instead, which costs nothing and cannot escape. */
.visually-hidden {
    display: inline-block;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

[hidden] { display: none !important; }

/* ------------------------------------------------------- reduced motion ---- */

/* Everything that moves or fades in Cajella does so to acknowledge a pointer or
   to slide the navigation drawer, and none of it carries information. Someone
   who has asked their system for less motion gets the end state immediately.

   `!important` because the durations it overrides are declared on the controls
   themselves, and this is a user preference rather than another opinion about
   the design. Only the durations are touched: the drawer's `visibility` delays
   still sequence correctly, so the navigation opens and closes exactly as it
   does otherwise — instantly. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}

/* ---------------------------------------------------------- responsive ---- */

/* The rail is only worth its width when there is room left for the content
   beside it. At tablet portrait there is not: a 216px rail against a 768px
   viewport left ~552px of page, so the drawer now starts at 900px rather than
   640px and the in-between band no longer gets a desktop layout it cannot
   afford. */
@media (max-width: 900px) {
    .sidebar-toggle-button {
        display: inline-flex;
        position: fixed;
        z-index: 6;
        top: 12px;
        left: 12px;
        min-height: 44px;
        padding: 9px 14px;
        border: 1px solid var(--cj-gold-dim);
        border-radius: var(--cj-radius-sm);
        background: var(--cj-sidebar);
        color: var(--cj-text);
    }
    .sidebar-toggle-button:hover { background: var(--cj-sidebar-hover); color: var(--cj-accent-dark); }

    /* `visibility: hidden` and not merely `left: -288px`.
       An off-canvas drawer positioned outside the viewport is still *visible* to
       the browser, so its twelve navigation links stay in the tab order: a
       keyboard user at this width tabbed through twelve off-screen destinations
       before reaching the page, which two consecutive browser verifications
       recorded as a finding. Visibility removes a subtree from the tab order and
       from the accessibility tree, needs no JavaScript, and therefore holds
       whether or not `nav.js` has run.

       The two `visibility` transitions are asymmetric on purpose. Opening is
       `0s` with no delay, because `nav.js` moves focus into the drawer in the
       same tick and focus is refused on a subtree that is still hidden — the
       first attempt delayed both directions and silently broke focus-on-open.
       Closing is delayed by the length of the slide, so the drawer stays visible
       while it travels out rather than blinking away. */
    .app-sidebar {
        position: fixed;
        z-index: 5;
        top: 0;
        bottom: 0;
        left: -288px;
        width: 288px;
        flex-basis: 288px;
        padding-top: 64px;
        overflow-y: auto;
        visibility: hidden;
        transition: left .2s ease, visibility 0s linear .2s;
    }
    .app-shell.nav-open .app-sidebar {
        left: 0;
        visibility: visible;
        transition: left .2s ease, visibility 0s linear 0s;
    }

    /* A tap outside the drawer closes it, and nothing behind it is clickable
       while it is open. */
    .app-shell.nav-open .sidebar-backdrop {
        display: block;
        position: fixed;
        z-index: 4;
        inset: 0;
        background: #050604b3;
        border: 0;
    }
    .sidebar-close {
        display: inline-flex;
        position: absolute;
        top: 14px;
        right: 14px;
        min-width: 44px;
        min-height: 44px;
        padding: 6px 10px;
        border: 1px solid var(--cj-sidebar-border);
        border-radius: var(--cj-radius-sm);
        background: transparent;
        color: var(--cj-on-dark);
        font-size: 1.3rem;
        line-height: 1;
    }
    .sidebar-close:hover { background: var(--cj-sidebar-hover); color: var(--cj-text); }

    /* The header stays one row. The drawer toggle is fixed at the top-left of
       the viewport, which is inside this band, so the row is indented past it
       rather than pushed below it — stacking the header cost 64px of vertical
       space on the width that has least of it, and put the user menu and the
       bell under the page identity instead of beside it. */
    .topbar { gap: var(--cj-space-2); padding: 12px 16px 12px 76px; }
    .page-content { padding: 22px 18px 56px; }
    .detail-grid { grid-template-columns: 1fr; }
    .wizard-progress { gap: var(--cj-space-1); }

    /* The command brief loses its second column before its tiles lose their
       row: the attention rail is content, not chrome, so it stacks under the
       brief rather than being squeezed to an unreadable width. */
    .brief-grid { grid-template-columns: 1fr; }

    /* A header control is a thumb target here, not a pointer target. */
    .header-control { min-height: 44px; }
    .header-control-icon-only { min-width: 44px; }
    .topbar-actions { padding-left: var(--cj-space-2); }
    .user-control { max-width: 11rem; }

    /* Every table-row and detail-row action is a thumb target here too — the
       audit's own finding: `.button-small` (28px) and `.row-action` (34px)
       were the one place in Cajella still under the 44px floor every other
       control gets on a phone (`.header-control`, `.pagination-step` above).
       Desktop keeps the compact sizing on purpose — a dense table is exactly
       where that density earns its keep with a pointer — so this is scoped to
       the same breakpoint as the rest of the thumb-target correction rather
       than raised globally. */
    .button-small,
    .row-action { min-height: 44px; }
}

@media (max-width: 640px) {
    /* Density and geometry come down together, in one place, because they are one
       decision: a 20px gutter and a 9px cut are proportionate to a 1240px page
       and not to a 390px one. Every panel, header band, metric tile and negated
       margin in the stylesheet reads these, so a phone is a token change rather
       than a second layout. */
    :root {
        --cj-panel-pad-y: 14px;
        --cj-panel-pad-x: 14px;
        --cj-panel-head-pad-y: 8px;
        --cj-metric-pad-x: 14px;
        --cj-chamfer: 7px;
        --cj-text-metric: 1.95rem;
        /* The header band comes down with everything else. 76px of chrome is
           proportionate to a 900px window; on a 390px one it is the tallest
           thing on the screen before any content has been read. The sidebar
           brand block reads the same token, so the two stay level. */
        --cj-topbar-h: 64px;
    }

    /* The title keeps the band's full width here: the record segment is hidden
       forty lines below, so what is left is the module on one line. */
    .topbar { padding: 10px 14px 10px 72px; }
    .topbar h1 { font-size: 1.05rem; gap: var(--cj-space-2); letter-spacing: .1em; }

    /* A direct-child link rule with 6px of vertical padding used to sit here — a
       partial, in-flow attempt at the same tap-target problem the pseudo-element
       overlay above now solves properly. It bought 12px of target and cost 12px
       of row height at the width where density matters most, and having both
       mechanisms would mean two rules for one concern. Removed with the overlay
       landing. */
    .page-header { align-items: flex-start; flex-direction: column; }
    .page-header .page-actions { width: 100%; }
    .page-header .page-actions .button { flex: 1 1 auto; }

    /* A numbered page window does not fit a phone, and squeezing it on is how
       Laravel's default paginator ended up 826px tall here. The window goes and
       the position it stands for is stated in words instead — the same trade the
       wizard progress rail makes with its step labels two rules below. Previous
       and Next stay, and take the full row between them so the tap targets are
       the width of the thumb rather than of the label. */
    .pagination { justify-content: stretch; }
    .pagination-summary { display: none; }
    .pagination-list { flex-wrap: nowrap; justify-content: space-between; width: 100%; gap: var(--cj-space-2); }
    .pagination-page-item { display: none; }
    .pagination-position { display: inline-flex; margin: 0; }
    .pagination-step { min-height: 44px; margin: 0; }

    .form-grid { grid-template-columns: 1fr; }
    .form-section { padding: 18px; }
    /* One card per row, at the same density as every other panel on a phone.
       Written here rather than left to `.form-section` above because
       `.settings-grid > .form-section` out-specifies it. */
    .settings-grid { gap: var(--cj-space-4); }
    .settings-grid > .form-section { padding: 18px; }
    .settings-grid > .form-section > h2 {
        margin: -18px -18px var(--cj-space-4);
        padding: var(--cj-space-3) 18px;
        font-size: .84rem;
    }
    .settings-grid .icon-framed { width: 28px; height: 28px; }
    .settings-grid .icon-framed::before { width: 16px; height: 16px; }
    .form-choice { display: flex; margin-right: 0; }
    .form-actions { justify-content: stretch; }
    .form-actions .button { flex: 1 1 auto; }

    .metric-grid { grid-template-columns: 1fr; }

    /* One tile per row on a phone, and the money band becomes a stack: three
       gold figures side by side at 390px is three truncated figures. The
       dividers move with them, from left edges to top edges. */
    .hud-widgets,
    .brief-main .hud-widgets { grid-template-columns: 1fr; }
    .hud-widget { min-height: 0; }
    /* `flex: 1 1 180px` is a *main axis* basis, and the main axis here is
       vertical: stacked, each figure was 180px tall and the band was 560px of
       mostly nothing. */
    .money-strip { flex-direction: column; gap: var(--cj-space-3); }
    .money-figure { flex: 0 0 auto; padding-left: 0; padding-top: var(--cj-space-3); border-left: 0; border-top: 1px solid var(--cj-panel-edge-inset); }
    .money-figure:first-child { padding-top: 0; border-top: 0; }
    .attention-item { flex-wrap: wrap; }

    /* Below 900px the row is expected to scroll on a genuinely narrow phone
       (this tier also covers 768/900px tablets, where it usually still fits) —
       intentional per-page scroll, not the desktop bug above. The visual
       scrollbar chrome is hidden on both scrollbar-width (Firefox) and the
       WebKit/Blink pseudo-element below; `overflow-x: auto` keeps touch and
       trackpad scrolling working exactly as before, and the row's own
       horizontal padding (`.detail-tabs`'s `padding: 0 2px`) keeps the first/
       last tab from touching the viewport edge while scrolling. */
    .detail-tabs { gap: 2px; margin-bottom: 18px; scrollbar-width: none; }
    .detail-tabs::-webkit-scrollbar { display: none; height: 0; }
    .detail-tab { padding: 10px 12px; font-size: .88rem; }
    .product-identity { gap: var(--cj-space-3) 20px; padding: 14px 16px; }

    /* A card header is a heading, a subtitle and a row of actions side by side,
       which on a phone becomes three columns narrow enough to break "Bill of
       materials" over three lines. They stack instead. */
    .detail-section-header { flex-direction: column; align-items: stretch; gap: 10px; }
    .detail-section-actions { flex-direction: column; }
    .detail-section-actions .button { width: 100%; }

    /* Six steps do not fit across a phone. The markers stay, the labels drop to
       the current step only, which is the one the owner needs to read. */
    .wizard-progress { flex-wrap: wrap; justify-content: center; gap: var(--cj-space-2); padding-bottom: var(--cj-space-4); }
    .wizard-progress::before { display: none; }
    .wizard-step { flex: 0 0 auto; }
    .wizard-step .step-name { display: none; }
    .wizard-step.current .step-name {
        display: block;
        position: absolute;
        top: 44px;
        left: 50%;
        width: max-content;
        max-width: 60vw;
        transform: translateX(-50%);
    }
    .wizard-content { padding: 18px; }
    .wizard-actions { flex-direction: column-reverse; align-items: stretch; }
    .wizard-variant-card { padding: 16px; }

    /* What the header gives up, in the order it gives it up.
       Navigation access and page identity come first, then the user menu, then
       the bell — so the record's name goes before the module's, the person's name
       goes before their avatar, and every control stays a control. The panel is
       pinned to the viewport edge rather than to its button, because a 17rem menu
       hanging off a 44px button at 360px would otherwise overflow the page. */
    .topbar-detail,
    .topbar-separator { display: none; }
    .user-control { max-width: none; }
    .user-control-name { display: none; }
    .header-menu,
    .header-menu-wide { right: -8px; min-width: min(17rem, calc(100vw - 32px)); }

    .auth-page { padding: 28px 16px; }
    .auth-card { padding: 24px; }
}

/* The narrowest tier the shell is verified at.

   Between the drawer toggle on the left and the bell and avatar on the right, a
   390px band leaves about 158px for the title — and PACKAGING MATERIALS is 195px
   even a step down in size and tracking, so it truncated to PACKAGING MATERI…
   Shrinking it far enough to fit on one line would take it below the size of the
   labels in the rail, so it wraps to two instead. The record segment is already
   hidden at this width, so what wraps is only ever the module's own name, and
   two lines of it still sit inside the band's 64px without growing it. */
@media (max-width: 430px) {
    .topbar h1 { font-size: .92rem; letter-spacing: .06em; line-height: 1.05; }
    /* `overflow: hidden` is what draws the ellipsis at wider widths, and here it
       would crop the second line instead. The band's height is a *minimum*, so
       releasing the clip lets it grow by a line on a viewport narrow enough to
       need a third one rather than cutting the title in half. */
    .topbar-identity { overflow: visible; }
    .topbar-module {
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
    }
}

/* Searchable select (combobox) — public/js/searchable-select.js
   Progressive enhancement over a native <select data-searchable-select>: the
   select itself stays in the DOM (hidden) as the real form control, and this
   is the visible text input + listbox laid over it. Tokens match .form-control
   so an enhanced field is visually indistinguishable from a plain one until
   focused. */
.searchable-select { position: relative; }
.searchable-select-input { padding-right: 34px; }
.searchable-select-clear {
    position: absolute;
    top: 50%;
    right: 6px;
    transform: translateY(-50%);
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--cj-radius-sm);
    background: transparent;
    color: var(--cj-muted);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}
.searchable-select-clear:hover { background: var(--cj-hover); color: var(--cj-text); }
.searchable-select-clear:focus-visible { outline: 3px solid var(--cj-focus); outline-offset: 2px; }
.searchable-select-listbox {
    position: absolute;
    z-index: 40;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 280px;
    overflow-y: auto;
    margin: 0;
    padding: 4px;
    list-style: none;
    background: var(--cj-panel-face);
    border: 1px solid var(--cj-border-control);
    border-radius: var(--cj-radius-sm);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.searchable-select-group {
    padding: 6px 10px 4px;
    color: var(--cj-muted);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}
.searchable-select-option {
    padding: 8px 10px;
    border-radius: var(--cj-radius-sm);
    cursor: pointer;
}
.searchable-select-option:hover,
.searchable-select-option.is-active { background: var(--cj-hover); }
.searchable-select-option[aria-selected="true"] { color: var(--cj-accent-dark); font-weight: 700; }
.searchable-select-empty { padding: 8px 10px; color: var(--cj-muted); font-size: .85rem; }

/* ------------------------------------------------- EV-7B storefront ---- */

/* The public Event storefront's own shell — a standalone page, not the
   authenticated app's sidebar/topbar shell, so it needs only a page ground
   and a readable max-width column. Mobile-first: the grid below is
   `auto-fit`, so it is already a single column at phone widths with no
   media query needed, and gains columns as room allows. */
.storefront-body { background: var(--cj-bg); }
.storefront-shell { max-width: 720px; margin: 0 auto; padding: var(--cj-space-5) var(--cj-space-4) var(--cj-space-6); }

.storefront-header { margin-bottom: var(--cj-space-5); }
.storefront-eyebrow {
    margin: 0 0 var(--cj-space-1);
    color: var(--cj-accent-dark);
    font-size: var(--cj-text-xs);
    font-weight: 700;
    letter-spacing: var(--cj-track-label);
    text-transform: uppercase;
}
.storefront-title {
    margin: 0;
    font-family: var(--cj-font-display, inherit);
    font-size: var(--cj-text-2xl);
}
.storefront-meta { margin: var(--cj-space-1) 0 0; color: var(--cj-muted); }

.storefront-section { margin-bottom: var(--cj-space-5); }
.storefront-section-title { margin: 0 0 var(--cj-space-1); font-size: var(--cj-text-lg); }
.storefront-section-subtitle { margin: 0 0 var(--cj-space-3); color: var(--cj-muted); font-size: var(--cj-text-sm); }

.storefront-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--cj-grid-gap); }

.storefront-card {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--cj-space-3);
    padding: var(--cj-panel-pad-y) var(--cj-panel-pad-x);
    background: var(--cj-panel-face);
    border: 1px solid var(--cj-panel-edge);
    border-radius: var(--cj-radius-sm);
}
.storefront-card-name { margin: 0; font-size: var(--cj-text-base); }
.storefront-card-variant { margin: 2px 0 0; color: var(--cj-muted); font-size: var(--cj-text-sm); }
.storefront-card-price { margin: var(--cj-space-2) 0 0; color: var(--cj-accent-dark); font-weight: 700; font-size: var(--cj-text-lg); }
.storefront-card-stock { margin: var(--cj-space-1) 0 0; color: var(--cj-muted); font-size: var(--cj-text-sm); }
.storefront-card-qty { display: flex; align-items: center; gap: var(--cj-space-2); }
.storefront-card-qty label { color: var(--cj-muted); font-size: var(--cj-text-sm); }
.storefront-qty-input { width: 5.5rem; }

/* EV-7C — the order form beneath the catalog grids: reuses `.form-field`/
   `.form-control`/`.form-error` (defined once, above) rather than a second
   set of storefront-only field styles. */
.storefront-submit { margin-top: var(--cj-space-2); }

/* EV-7D — confirmation page and owner-panel QR/copy controls. Reuses the
   EV-7B storefront rules above (`.storefront-header`, `.storefront-section`,
   `.storefront-card-variant`, `.data-table`, `.detail-rows`) rather than a
   second parallel set; the two additions below are genuinely new shapes:
   the QR image container (the `<svg>` inside it is server-rendered by
   `App\Support\Storefront\QrCodeSvg` via the `<x-storefront-qr>` Blade
   component, not client-side JS) and the copyable-URL row on the
   owner-facing Event show page. */
.storefront-qr {
    display: inline-block;
    width: 160px;
    max-width: 100%;
    margin-top: var(--cj-space-2);
}
.storefront-qr svg { display: block; width: 100%; height: auto; }
#storefront-url-value {
    display: inline-block;
    word-break: break-all;
    margin-right: var(--cj-space-2);
}
