/* RTL readiness note (multi-store plan §10, Stage 5, 2026-08-05): margin/padding/
   border-left|right and text-align left|right below were swept to logical
   properties (inline-start/inline-end) so they follow `dir` instead of a fixed
   screen side. `left:`/`right:` used for POSITION:ABSOLUTE/FIXED placement were
   deliberately left untouched — those are genuinely screen-relative (dropdown
   anchors, fixed panels, badge corners) and flipping them blind, without visual
   RTL verification, risks breaking layouts worse than leaving them LTR-only. */

/*
 * Shared BlinkVenue design system — "amber" theme. Single source of truth for
 * color tokens, typography, and reusable component classes (buttons, cards,
 * tables, nav, forms, badges) so individual templates stop reinventing the
 * same CSS in inline <style> blocks. Served from /css/** (permitted in
 * SecurityConfig).
 */
@import url('https://fonts.googleapis.com/css2?family=Anton&family=JetBrains+Mono:wght@500;600&display=swap');

:root {
    --font-display: 'Anton', sans-serif;
    --font-mono: 'JetBrains Mono', monospace;
    --surface-1: #fdfaf5;
    --surface-2: #ffffff;
    --page-plane: #f5efe2;
    --text-primary: #1c1410;
    --text-secondary: #6b5c47;
    --text-muted: #9c8a6e;
    --border: rgba(120, 83, 24, 0.16);
    --accent: #b45309;
    --accent-tint: #fef3c7;
    --accent-soft: #f59e0b;
    --cta-bg: #92400e;
    --cta-text: #fffaf0;
    --success: #15803d;
    --success-tint: #dcfce7;
    --danger: #b3261e;
    --danger-tint: #fbe6e6;
    --shadow-sm: 0 1px 2px rgba(120, 83, 24, .06);
    --shadow-md: 0 4px 14px rgba(120, 83, 24, .10);
    --radius: 12px;
    --radius-sm: 8px;

    /* Duotone button/surface language — ambient glow (bare RGB triplet, used
       inside rgba()) and per-variant gradient stops. Tokenised, not hardcoded
       in the rules, so the blue theme below re-maps every fill in one place. */
    --cta-glow: 120, 83, 24;
    --cta-grad-1: #b45309;
    --cta-grad-2: #92400e;
    --cta-grad-3: #7a350c;
    --accent-grad-1: #f59e0b;
    --accent-grad-2: #b45309;
    --accent-on-grad: #2b1600;
    --success-grad-1: #22c55e;
    --success-grad-2: #15803d;
    --danger-grad-1: #e05a52;
    --danger-grad-2: #b3261e;

    /* Aggregator/channel chip hues. Tokenised (rather than hardcoded in the
       .badge-channel-* rules below) because those rules previously carried one
       fixed light palette plus a scattered set of per-theme overrides, so a new
       theme silently inherited amber-era tints. One block per theme now covers
       every channel; a future theme adds one block instead of five rules. */
    --channel-swiggy-bg: #fdece0;   --channel-swiggy-fg: #c2440c;
    --channel-zomato-bg: #fde4e4;   --channel-zomato-fg: #b3261e;
    --channel-whatsapp-bg: #dcfce7; --channel-whatsapp-fg: #15803d;
    --channel-ondc-bg: #e0edff;     --channel-ondc-fg: #3366aa;
    /* Violet: the one hue that collides with none of the other five channels
       and none of this theme's accent. UrbanPiper had no rule at all, so it
       fell back to bare .badge — accent tint — and was indistinguishable from
       a DIRECT order on screen. */
    --channel-urbanpiper-bg: #ede9fe; --channel-urbanpiper-fg: #6d28d9;
    --kot-online-bg: #eef4ff;

    /* Dashboard module-tile name badges (§3.3) — one fixed hue per module,
       reused cyclically past 8 modules. Validated categorical palette (see
       the dataviz skill's references/palette.md). These are LIGHT-PLANE hues:
       amber, dark and light-pleasant all keep a light content surface, but
       darker-blue does not — it overrides all eight with lightness-stepped
       values (same rule as --chart-* below, which got that treatment the day
       the theme landed while the badges were missed: violet computed 1.65:1
       on the navy plane). Any new theme with a dark plane must re-step both. */
    --badge-blue: #2a78d6;
    --badge-green: #008300;
    --badge-magenta: #e87ba4;
    --badge-yellow: #eda100;
    --badge-aqua: #1baf7a;
    --badge-orange: #eb6834;
    --badge-violet: #4a3aa7;
    --badge-red: #e34948;

    /* The card "sheen": a white top-light every card/tile wears (gradient wash
       + 1px inset highlight). 50% white is right on a light plane and a grey
       film on a dark one, so it is a token — darker-blue drops it to 5%/8%.
       Never write the gradient or the inset colour literally in a component
       rule again; that is how 16 copies of this drifted out of theme reach. */
    --card-sheen: linear-gradient(180deg, rgba(255, 255, 255, .5), transparent 60px);
    --card-sheen-line: rgba(255, 255, 255, .5);

    /* Floor-plan tile states that have no palette hue of their own (violet for
       RESERVED, mid-blue for CHECK_PRESENTED) — light-plane pastels here,
       lightness-separated equivalents in darker-blue (see the ONDC chip). */
    --tile-check-bg: #e0edff;
    --tile-check-border: #6699e0;
    --tile-check-fg: #3366aa;
    --tile-reserved-bg: #ede9fe;
    --tile-reserved-border: #8b5cf6;
    --tile-reserved-fg: #6d28d9;

    /* Night-shift chip on the staff rota. */
    --rota-night-bg: rgba(37, 99, 235, 0.16);
    --rota-night-fg: #2563eb;

    /* Modal/drawer scrim — the amber theme's warm brown; dark planes override
       to plain black so the scrim doesn't tint the page sepia. */
    --overlay-scrim: rgba(28, 20, 16, .45);

    /* Report chart series slots — the same validated categorical palette as the
       badges, but their own tokens because darker-blue needs dark-stepped values
       (validated against its #14224a surface) while the badges stay put. Fixed
       slot order: blue, orange, aqua, yellow, magenta, green, violet, red —
       assigned per entity, never cycled past 8 (fold the tail into "Other"). */
    --chart-1: #2a78d6;
    --chart-2: #eb6834;
    --chart-3: #1baf7a;
    --chart-4: #eda100;
    --chart-5: #e87ba4;
    --chart-6: #008300;
    --chart-7: #4a3aa7;
    --chart-8: #e34948;

    /* Chrome (sidebar rail + top header) — always dark, independent of the
       light content area. Amber theme: dark orange/brown chrome. */
    --chrome-bg: #7a350c;
    --chrome-bg-hover: rgba(255, 255, 255, 0.08);
    --chrome-active-bg: rgba(255, 255, 255, 0.16);
    --chrome-text: #fdf1e2;
    --chrome-text-secondary: rgba(253, 241, 226, 0.78);
    --chrome-text-muted: rgba(253, 241, 226, 0.55);
    --chrome-border: rgba(255, 255, 255, 0.14);
    --chrome-accent: #fbbf24;

    /* Auth surfaces (login / signup / forgot / reset / invite / platform login).
       An auth page has no sidebar and no header, so it is effectively ALL chrome:
       these tokens track each theme's chrome character, not its content plane.
       Tokenised for the same reason the channel chips were — the auth shell used
       to hardcode one navy/cyan palette, so it looked identical no matter which
       theme the user had chosen, while the auth pages that did NOT use the shell
       fell through to the bare amber default. Amber: warm plane, amber accent. */
    --auth-plane: #2a1206;
    --auth-dot: rgba(255, 255, 255, .05);
    --auth-glow: rgba(251, 191, 36, .16);
    --auth-card-bg: rgba(255, 255, 255, .05);
    --auth-card-border: rgba(255, 255, 255, .14);
    --auth-card-shadow: 0 30px 70px rgba(0, 0, 0, .5);
    --auth-text: #fff7ec;
    --auth-text-secondary: rgba(253, 241, 226, .68);
    --auth-text-muted: rgba(253, 241, 226, .5);
    --auth-text-faint: rgba(253, 241, 226, .35);
    --auth-field-bg: rgba(255, 255, 255, .05);
    --auth-field-border: rgba(255, 255, 255, .16);
    --auth-accent: #fbbf24;
    --auth-accent-hover: #fcd34d;
    --auth-accent-glow: rgba(251, 191, 36, .35);
    --auth-on-accent: #2b1600;
    --auth-success: #6ee7a8;
    --auth-success-bg: rgba(110, 231, 168, .14);
    --auth-danger: #fca5a5;
    --auth-danger-bg: rgba(248, 113, 113, .14);
    --auth-info: #fcd34d;
    --auth-info-bg: rgba(251, 191, 36, .12);
    /* Native widget hint for the auth plane only — the amber theme's CONTENT
       area stays light, so this cannot live on the theme block itself. */
    --auth-color-scheme: dark;
}

/* Dark theme — chrome stays dark navy, but the content area between the
   sidebar/header flips to a light blue plane (mirrors the amber theme's
   dark-orange-chrome/light-content layout, just in blue). */
:root[data-theme="dark"] {
    --surface-1: #eef4fc;
    --surface-2: #ffffff;
    --page-plane: #e4edfa;
    --text-primary: #101828;
    --text-secondary: #45536b;
    --text-muted: #7c8aa3;
    --border: rgba(37, 99, 235, 0.14);
    --accent: #2563eb;
    --accent-tint: #dbeafe;
    --accent-soft: #3b82f6;
    --cta-bg: #1d4ed8;
    --cta-text: #f5f9ff;
    --success: #15803d;
    --success-tint: #dcfce7;
    --danger: #b3261e;
    --danger-tint: #fbe6e6;
    --shadow-sm: 0 1px 2px rgba(30, 64, 175, .08);
    --shadow-md: 0 4px 14px rgba(30, 64, 175, .12);

    /* Duotone re-mapping for the blue theme — same roles, blue hues. */
    --cta-glow: 37, 99, 235;
    --cta-grad-1: #2563eb;
    --cta-grad-2: #1d4ed8;
    --cta-grad-3: #1e40af;
    --accent-grad-1: #60a5fa;
    --accent-grad-2: #2563eb;
    --accent-on-grad: #eff6ff;
    --success-grad-1: #22c55e;
    --success-grad-2: #15803d;
    --danger-grad-1: #e05a52;
    --danger-grad-2: #b3261e;

    --chrome-bg: #0f1b33;
    --chrome-bg-hover: rgba(255, 255, 255, 0.06);
    --chrome-active-bg: rgba(255, 255, 255, 0.10);
    --chrome-text: #e7ecf7;
    --chrome-text-secondary: #a9b6d1;
    --chrome-text-muted: #6f7ea3;
    --chrome-border: rgba(148, 178, 235, 0.16);
    --chrome-accent: #60a5fa;

    /* Channel chips: unchanged values, just moved off the old
       `:root[data-theme="dark"] .badge-channel-*` overrides onto the tokens. */
    --channel-swiggy-bg: #4a2a12;   --channel-swiggy-fg: #f0a06a;
    --channel-zomato-bg: #451c1c;   --channel-zomato-fg: #e88a8a;
    --channel-whatsapp-bg: #163a24; --channel-whatsapp-fg: #7fe0a0;
    --channel-ondc-bg: #16233f;     --channel-ondc-fg: #8fb8f0;
    --channel-urbanpiper-bg: #2e1f4d; --channel-urbanpiper-fg: #c4b5fd;
    --kot-online-bg: #dbeafe;

    /* Auth surfaces — this theme's chrome is dark navy, so the auth plane is too. */
    --auth-plane: #0f1b33;
    --auth-glow: rgba(96, 165, 250, .18);
    --auth-text: #ffffff;
    --auth-text-secondary: rgba(231, 236, 247, .7);
    --auth-text-muted: rgba(231, 236, 247, .52);
    --auth-text-faint: rgba(231, 236, 247, .35);
    --auth-accent: #60a5fa;
    --auth-accent-hover: #93c5fd;
    --auth-accent-glow: rgba(96, 165, 250, .35);
    --auth-on-accent: #06132b;
    --auth-info: #93c5fd;
    --auth-info-bg: rgba(96, 165, 250, .14);
}

/* Darker Blue theme (new) — genuine dark content plane, not just a dark chrome.
   Pulled from the marketing site's navy/cyan palette (blinkvenue-website/assets/style.css)
   so the app and website finally share a real dark look. Selectable from the app
   navigation toggle; does not replace or alter the "dark" (light-blue) block above. */
:root[data-theme="darker-blue"] {
    /* Tells the browser this is a dark page so every NATIVE widget follows:
       select option lists, date/time picker popups and their calendar icons,
       checkboxes, scrollbars — all of which otherwise keep light-mode UA
       chrome (e.g. a near-invisible dark calendar icon on a dark input). */
    color-scheme: dark;
    --surface-1: #14224a;
    --surface-2: #1a2b5c;
    --page-plane: #0f1a3d;
    --text-primary: #f3f5ff;
    --text-secondary: rgba(243, 245, 255, 0.72);
    /* 0.5 measured 4.28:1 on --surface-2 for the small text this carries
       (table headers, status pills); 0.62 clears AA at 6.29:1. */
    --text-muted: rgba(243, 245, 255, 0.62);
    /* 0.12 computed 1.45:1 against --surface-1 — form-field and table edges
       were effectively invisible on the dark plane. */
    --border: rgba(255, 255, 255, 0.20);
    --accent: #2dd4ff;
    --accent-tint: rgba(45, 212, 255, 0.14);
    --accent-soft: #67e8f9;
    --cta-bg: #0ea5e9;
    --cta-text: #041018;
    --success: #34d399;
    --success-tint: rgba(52, 211, 153, 0.16);
    --danger: #e05a52;
    --danger-tint: rgba(224, 90, 82, 0.16);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-md: 0 18px 44px rgba(0, 0, 0, .45);

    --cta-glow: 45, 212, 255;
    --cta-grad-1: #2dd4ff;
    --cta-grad-2: #0ea5e9;
    --cta-grad-3: #0369a1;
    --accent-grad-1: #67e8f9;
    --accent-grad-2: #2dd4ff;
    --accent-on-grad: #041018;
    --success-grad-1: #34d399;
    --success-grad-2: #059669;
    --danger-grad-1: #e05a52;
    --danger-grad-2: #b3261e;

    /* Dark-stepped chart series — same hue order, re-stepped for the dark
       surface (all 8 clear 3:1 contrast there; the light set does not). */
    --chart-1: #3987e5;
    --chart-2: #d95926;
    --chart-3: #199e70;
    --chart-4: #c98500;
    --chart-5: #d55181;
    /* Was #008300, copied verbatim from the light set — the one slot that
       didn't clear the 3:1 this block's own comment claims (3.13:1). */
    --chart-6: #2bb34d;
    --chart-7: #9085e9;
    --chart-8: #e66767;

    /* Badge word-colours, lightness-stepped for the navy plane. The :root set
       is light-plane hues; on #14224a their .badge-* recipe (18% tint behind
       the hue as TEXT) bottomed out at 1.65:1 (violet). Every value here reads
       ≥5:1 on its own tint. Same hue order as :root so module identities hold. */
    --badge-blue: #7db4f5;
    --badge-green: #5fd48b;
    --badge-magenta: #f0a3c0;
    --badge-yellow: #f5c04d;
    --badge-aqua: #4fd6a8;
    --badge-orange: #ff9a63;
    --badge-violet: #b3a6f5;
    --badge-red: #ff8f8c;

    /* The 50%-white card sheen that every light plane wears computes to a grey
       film over navy — the single biggest reason this theme read unfinished. */
    --card-sheen: linear-gradient(180deg, rgba(255, 255, 255, .05), transparent 60px);
    --card-sheen-line: rgba(255, 255, 255, .08);

    /* Floor-plan tiles: same lightness-separation rule as --channel-ondc-bg
       (blue on navy cannot separate by hue). RESERVED reuses the urbanpiper
       violet pair, CHECK the ONDC blue pair, so the floor and the channel
       chips speak one dark-plane dialect. */
    --tile-check-bg: #2f5391;
    --tile-check-border: #6699e0;
    --tile-check-fg: #cfe2ff;
    --tile-reserved-bg: #3a2a5e;
    --tile-reserved-border: #8b5cf6;
    --tile-reserved-fg: #d3c4ff;

    /* #2563eb text was 2.99:1 on this plane; lift the fg, soften the wash. */
    --rota-night-bg: rgba(125, 180, 245, .18);
    --rota-night-fg: #7db4f5;

    --overlay-scrim: rgba(0, 0, 0, .6);

    --chrome-bg: #0f1a3d;
    --chrome-bg-hover: rgba(255, 255, 255, 0.06);
    --chrome-active-bg: rgba(255, 255, 255, 0.10);
    --chrome-text: #eaf1ff;
    --chrome-text-secondary: rgba(234, 241, 255, 0.78);
    --chrome-text-muted: rgba(234, 241, 255, 0.55);
    --chrome-border: rgba(255, 255, 255, 0.12);
    --chrome-accent: #2dd4ff;

    /* Channel chips: this theme had NO channel treatment at all, so every chip
       fell back to the light amber-era tints and rendered as a light sticker on
       the navy plane. ONDC is the one chip that cannot separate from this plane
       by hue (blue on navy), so it separates by LIGHTNESS instead — sitting
       above --surface-2 (#1a2b5c) where the other four sit below it. */
    --channel-swiggy-bg: #3d2412;   --channel-swiggy-fg: #ffb782;
    --channel-zomato-bg: #3d1a1a;   --channel-zomato-fg: #ff9c9c;
    --channel-whatsapp-bg: #10321f; --channel-whatsapp-fg: #6ee7a8;
    --channel-ondc-bg: #2f5391;     --channel-ondc-fg: #cfe2ff;
    --channel-urbanpiper-bg: #3a2a5e; --channel-urbanpiper-fg: #d3c4ff;
    --kot-online-bg: #16233f;

    /* Auth surfaces — these are the exact values the auth shell used to hardcode
       (deep navy, one cyan accent, matching the marketing site). Keeping them
       verbatim here is what makes the tokenisation a no-op for this theme. */
    --auth-plane: #070b1a;
    --auth-glow: rgba(45, 212, 255, .16);
    --auth-text: #ffffff;
    --auth-text-secondary: rgba(243, 245, 255, .68);
    --auth-text-muted: rgba(243, 245, 255, .5);
    --auth-text-faint: rgba(243, 245, 255, .35);
    --auth-accent: #2dd4ff;
    --auth-accent-hover: #57e0ff;
    --auth-accent-glow: rgba(45, 212, 255, .35);
    --auth-on-accent: #041018;
    --auth-success: #34d399;
    --auth-success-bg: rgba(52, 211, 153, .14);
    --auth-danger: #fca5a5;
    --auth-danger-bg: rgba(248, 113, 113, .14);
    --auth-info: #2dd4ff;
    --auth-info-bg: rgba(45, 212, 255, .14);
}

/* Light Pleasant theme (new) — very mild lavender, a light content plane distinct
   from both amber (warm/earthy) and the "dark" block above (cool/corporate blue).
   Selectable from the app navigation toggle alongside Darker Blue. */
:root[data-theme="light-pleasant"] {
    --surface-1: #eaeaef;
    --surface-2: #efefef;
    --page-plane: #e2deea;
    --text-primary: #2a2440;
    --text-secondary: #5b5378;
    --text-muted: #9a91b8;
    --border: rgba(58, 47, 95, 0.14);
    --accent: #8b5cf6;
    --accent-tint: #ede9fe;
    --accent-soft: #a78bfa;
    --cta-bg: #6d28d9;
    --cta-text: #f5f3ff;
    --success: #16a34a;
    --success-tint: #dcfce7;
    --danger: #b3261e;
    --danger-tint: #fbe6e6;
    --shadow-sm: 0 1px 2px rgba(76, 55, 137, .06);
    --shadow-md: 0 4px 14px rgba(76, 55, 137, .10);

    --cta-glow: 124, 58, 237;
    --cta-grad-1: #a78bfa;
    --cta-grad-2: #7c3aed;
    --cta-grad-3: #5b21b6;
    --accent-grad-1: #c4b5fd;
    --accent-grad-2: #8b5cf6;
    --accent-on-grad: #2e1065;
    --success-grad-1: #22c55e;
    --success-grad-2: #16a34a;
    --danger-grad-1: #e05a52;
    --danger-grad-2: #b3261e;

    --chrome-bg: #d7cfeb;
    --chrome-bg-hover: rgba(255, 255, 255, 0.08);
    --chrome-active-bg: rgba(255, 255, 255, 0.16);
    --chrome-text: #211b38;
    --chrome-text-secondary: rgba(33, 27, 56, 0.78);
    --chrome-text-muted: rgba(33, 27, 56, 0.55);
    --chrome-border: rgba(255, 255, 255, 0.14);
    --chrome-accent: #6d28d9;

    /* Channel chips: also had no treatment. The amber-era tints read acceptably
       on this light lavender plane, so these are a mild re-tint for consistency
       rather than a fix — the difference is deliberately subtle. */
    --channel-swiggy-bg: #f6e6dc;   --channel-swiggy-fg: #a8380a;
    --channel-zomato-bg: #f5dede;   --channel-zomato-fg: #96201a;
    --channel-whatsapp-bg: #dbeee1; --channel-whatsapp-fg: #146c35;
    --channel-ondc-bg: #dfe6f5;     --channel-ondc-fg: #2d5691;
    /* Teal here, not violet: this theme's own accent IS violet, so a violet
       UrbanPiper chip would collide with DIRECT — the exact confusion being
       fixed. The token indirection is what makes a per-theme hue choice possible. */
    --channel-urbanpiper-bg: #d5eceb; --channel-urbanpiper-fg: #0f5f5c;
    --kot-online-bg: #e8e2f7;

    /* Auth surfaces — the ONE theme whose chrome is light, so its auth plane is
       light too. A navy login page handing off to a lavender app is the same
       inconsistency this token set exists to remove, just in the other direction.
       --auth-color-scheme flips back to light so native widgets follow. */
    --auth-plane: #e2deea;
    --auth-dot: rgba(58, 47, 95, .07);
    --auth-glow: rgba(139, 92, 246, .2);
    --auth-card-bg: rgba(255, 255, 255, .72);
    --auth-card-border: rgba(58, 47, 95, .14);
    --auth-card-shadow: 0 24px 60px rgba(76, 55, 137, .18);
    --auth-text: #2a2440;
    --auth-text-secondary: #5b5378;
    --auth-text-muted: #7a719c;
    --auth-text-faint: #9a91b8;
    --auth-field-bg: rgba(255, 255, 255, .85);
    --auth-field-border: rgba(58, 47, 95, .18);
    --auth-accent: #6d28d9;
    --auth-accent-hover: #7c3aed;
    --auth-accent-glow: rgba(109, 40, 217, .28);
    --auth-on-accent: #f5f3ff;
    --auth-success: #15803d;
    --auth-success-bg: rgba(21, 128, 61, .12);
    --auth-danger: #b3261e;
    --auth-danger-bg: rgba(179, 38, 30, .1);
    --auth-info: #6d28d9;
    --auth-info-bg: rgba(109, 40, 217, .1);
    --auth-color-scheme: light;
}

* {
    box-sizing: border-box;
}

html, body {
    height: 100%;
}

body {
    font-family: 'Inter', system-ui, -apple-system, "Segoe UI", sans-serif;
    background: var(--page-plane);
    color: var(--text-primary);
    margin: 0;
}

/* No brand watermark. One existed here as a body::before pseudo-element, but its
   negative offsets ("right -60px bottom -60px") pushed the icon past the viewport
   corner, so pages showed a stray misaligned sliver rather than a mark. Centering
   it instead hid it completely behind the login/signup card and bisected it between
   the two panels of wide pages like Business Profile. Removed 2026-08-11 by user
   decision — the per-theme --page-plane background carries the branding alone. */

a {
    color: var(--accent);
}

/* ---------- Layout shells ---------- */

/* Same dark "chrome" treatment as .chrome-header (Dashboard/POS) — every
   page's top bar reads as one consistent BlinkVenue chrome, not a light bar
   sitting oddly next to the always-dark sidebar rail. */
.app-header {
    min-height: 80px;
    background: var(--chrome-bg);
    border-bottom: 1px solid var(--chrome-border);
    padding: 1rem 2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: .75rem;
    box-sizing: border-box;
}

.app-header .brand {
    font-family: var(--font-display);
    font-size: 1.1rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--chrome-accent);
}

/* Tenant-wide online-ordering on/off switch, dropped into every header's
   .header-actions next to the theme toggle (fragments/sidebar.html ::
   onlineOrderingToggle) - a single click flips the guest storefront on/off. */
.online-toggle-form {
    display: inline-flex;
}

.online-toggle-pill {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .4rem .85rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .01em;
    cursor: pointer;
    border: 1px solid var(--chrome-border);
    background: var(--chrome-bg-hover);
    color: var(--chrome-text-secondary);
    transition: background .12s ease, color .12s ease, border-color .12s ease;
}

.online-toggle-pill::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.online-toggle-pill.on {
    color: #4ade80;
    border-color: rgba(74, 222, 128, .4);
    background: rgba(74, 222, 128, .12);
}

.online-toggle-pill.on:hover {
    background: rgba(74, 222, 128, .2);
}

.online-toggle-pill.off {
    color: #f87171;
    border-color: rgba(248, 113, 113, .4);
    background: rgba(248, 113, 113, .12);
}

.online-toggle-pill.off:hover {
    background: rgba(248, 113, 113, .2);
}

/* Shared Active/Online/In-store status pills - used on category and product
   tiles (grid + list view) so a dish's or category's availability reads the
   same way as the header's Online Ordering pill. .status-pill is the
   read-only variant (no edit permission); .status-pill-btn is the clickable
   form-submit version that actually toggles the flag. */
.status-pills { display: flex; gap: .4rem; flex-wrap: wrap; }
.status-pill, .status-pill-btn {
    display: inline-flex; align-items: center; gap: .3rem;
    font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .03em;
    padding: .3rem .6rem; border-radius: 999px;
    background: var(--border); color: var(--text-muted);
}
.status-pill.on, .status-pill-btn.on { background: var(--success-tint); color: var(--success); }
.status-pill.off, .status-pill-btn.off { background: rgba(220,38,38,.12); color: var(--danger); }
.status-pill .dot, .status-pill-btn .dot { width: .4rem; height: .4rem; border-radius: 999px; background: currentColor; flex-shrink: 0; }
.status-pill-btn { border: 1px solid transparent; cursor: pointer; font-family: inherit; transition: filter .12s ease; }
.status-pill-btn.on { border-color: rgba(22,163,74,.35); }
.status-pill-btn.off { border-color: rgba(220,38,38,.3); }
.status-pill-btn:hover { filter: brightness(0.95); }
.status-pill-form { display: inline-flex; }

.header-actions {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
}

.app-main {
    width: 100%;
    max-width: 960px;
    margin: 2.5rem auto;
    padding: 0 1.25rem;
    box-sizing: border-box;
}

.app-main.narrow {
    max-width: 620px;
}

.app-main.wide {
    max-width: 1180px;
}

.app-main.full {
    max-width: none;
    margin: 1.5rem 0;
    padding: 0 2rem;
}

.centered-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.5rem 1rem;
}

.centered-card {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2rem;
    width: 100%;
    max-width: 480px;
    box-shadow: var(--shadow-md);
}

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

.page-title {
    font-size: 1.5rem;
    font-weight: 800;
    margin: 0 0 .3rem;
    letter-spacing: -.01em;
}

.page-subtitle {
    color: var(--text-secondary);
    margin: 0 0 1.75rem;
    font-size: .95rem;
}

/* Small muted helper/caption text — was being redefined ad hoc in several
   templates' own <style> blocks (settings-business.html, order-detail.html);
   promoted here so new pages don't have to redeclare it (or silently render
   unstyled when they forget to). */
.hint {
    font-size: .78rem;
    color: var(--text-muted);
    margin-top: .25rem;
}

/* Standard module-page header: title and its primary action share one row
   (no subtitle) — the consistent shape every module page's top-of-page
   uses, with any secondary toolbar buttons in their own row underneath. */
.page-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: .75rem;
    margin-bottom: 1.5rem;
}

/* A toolbar-style page-head - several buttons/filters packed left instead of
   one title left / one action right. Was a `style="justify-content:flex-start;"`
   D3 duplicate in 4 templates. */
.page-head--start {
    justify-content: flex-start;
}

/* .page-title's own default bottom margin (below) is for when it stands alone;
   inside .page-head it shares a flex row with buttons/filters, spaced by the
   row's own gap - the .3rem default plus every page's own inline margin
   override was D3 duplication in ~32 templates, all overriding to the same
   effective zero. */
.page-head .page-title {
    margin: 0;
}

/* Groups the title with its inline primary action (e.g. "+ New floor plan")
   as ONE flex child of .page-head. Without this, a page-head with a button
   between the title and a trailing page-nav/view-toggle has THREE+ direct
   children under justify-content:space-between, which spreads the button
   into the empty middle of the row instead of sitting next to the title it
   names — worst when the trailing view-toggle is conditionally absent (an
   empty list), leaving pure N-way space-between with no rescuing auto-margin. */
.page-head-title-group {
    display: flex;
    align-items: center;
    gap: .75rem;
}

/* The first trailing sibling after .page-head-title-group (page-nav, or
   view-toggle when there's no page-nav) claims all the row's free space for
   itself, packing everything before it — title + button — tightly on the
   left and pushing itself (and anything after it, via the ordinary .page-head
   gap) to the far right as one cluster. */
.page-head-title-group + .page-nav,
.page-head-title-group + .view-toggle {
    margin-left: auto;
}

/* ---------- Nav links ---------- */

.nav-link {
    font-size: .85rem;
    font-weight: 600;
    color: var(--text-secondary);
    text-decoration: none;
    padding: .3rem 0;
}

.nav-link:hover {
    color: var(--accent);
}

.nav-link.active {
    color: var(--accent);
}

/* .app-header is the dark "chrome" treatment (matches the sidebar rail and
   .chrome-header). .nav-link's own rule above is the light-surface default it
   uses at page level (inside .page-nav); this override is what it needs when it
   sits on the dark chrome instead. Both placements are live — page-specific
   links moved out of the header to .page-nav, but the chrome still styles its
   own Dashboard link with these rules. */
.app-header .nav-link,
.chrome-header .nav-link {
    color: var(--chrome-text-secondary);
}

.app-header .nav-link:hover,
.app-header .nav-link.active,
.chrome-header .nav-link:hover,
.chrome-header .nav-link.active {
    color: var(--chrome-accent);
}

/* Page-level secondary navigation: the "Back to …" link and the sibling-page
   links ("Products", "Orders") that a page offers.

   These used to be passed up into the shared chrome header via its `links`
   fragment parameter, which put page-specific navigation inside app-level
   chrome — the same strip as the store switcher and Log out. That read as
   global navigation that changed under you from page to page. They belong to
   the page, so they live in the page's own title row, alongside the module
   name and its action buttons, rather than on a separate line of their own. */
.page-nav {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem;
}

/* Deliberately no margin-inline-start:auto to push these right. These rows already end
   with a .view-toggle that pins itself right with an auto margin, and a second
   auto margin splits the free space between the two — dropping the links in the
   middle of an otherwise empty row, looking misplaced rather than aligned.
   Worse, that toggle is th:if'd on the list being non-empty, so the links would
   move depending on whether the page had data. Sitting directly after the
   row's action buttons is the one position that holds still. */

.back-link {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .85rem;
    font-weight: 700;
    color: var(--text-secondary);
    text-decoration: none;
    padding: .4rem .8rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-1);
}

.back-link::before {
    content: "←";
}

.back-link:hover {
    color: var(--accent);
    border-color: var(--accent-soft);
    background: var(--accent-tint);
}

/* Centred "Already have an account? Log in" trailer under a card or form.
   signup.html and login.html override this with their own dark-navy variant in
   a local <style> block; defining the themed default here is what the standard
   app pages (platform-reset-credentials, storefront-confirmation) were missing. */
.footer-link {
    text-align: center;
    margin-top: 1.25rem;
    font-size: .9rem;
    color: var(--text-secondary);
}

.footer-link a {
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
}

.footer-link a:hover {
    text-decoration: underline;
}

.lang-switcher {
    display: flex;
    gap: .25rem;
}

.lang-switcher a {
    font-size: .78rem;
    font-weight: 700;
    text-decoration: none;
    color: var(--text-secondary);
    padding: .25rem .55rem;
    border-radius: 999px;
}

/* Same dark-chrome scoping as .app-header .nav-link above — .back-link and
   .lang-switcher are reused on light page bodies too (e.g. an inline "Edit"
   toggle, the login/signup card), so only override them inside the header. */
.app-header .back-link,
.chrome-header .back-link {
    color: var(--chrome-text-secondary);
    border-color: var(--chrome-border);
    background: var(--chrome-bg-hover);
}

.app-header .back-link:hover,
.chrome-header .back-link:hover {
    color: var(--chrome-accent);
    border-color: var(--chrome-border);
    background: var(--chrome-active-bg);
}

.app-header .lang-switcher a {
    color: var(--chrome-text-secondary);
}

.app-header .lang-switcher a.active {
    color: var(--chrome-accent);
    background: var(--chrome-active-bg);
}

.lang-switcher a.active {
    color: var(--accent);
    background: var(--accent-tint);
}

/* Single-control locale switcher (login/signup/app shell) - replaces the old
   row-of-links pattern above with one <select> now that 6 locales don't fit
   comfortably as separate links. Reuses .lang-switcher's positioning; the
   select itself gets its own pill styling since a native <select> can't take
   the anchor-link treatment above. */
.lang-switcher-select {
    font-size: .78rem;
    font-weight: 700;
    color: var(--text-secondary);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: .25rem .6rem;
    cursor: pointer;
}

.app-header .lang-switcher-select,
.chrome-header .lang-switcher-select {
    color: var(--chrome-text-secondary);
    border-color: var(--chrome-border);
    background: var(--chrome-bg-hover);
}

/* ---------- Cards ---------- */

.card {
    background: var(--surface-2);
    /* Duotone surface: faint top-edge light so every card (incl. KDS .kot-card
       and .online-orders-card, which extend .card) reads as a lit object. */
    background-image: var(--card-sheen);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem;
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-md);
}

.card-grid {
    width: 100%;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
    box-sizing: border-box;
}

.card .label {
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    margin-bottom: .4rem;
    font-weight: 700;
}

.card .value {
    font-size: 1.15rem;
    font-weight: 700;
    /* Long unbroken values -- email addresses above all -- have no natural break
       opportunity, so with the default `word-break: normal` they simply overflow
       the card: measured 282px of text in a 200px box on the staff roster, spilling
       past the card edge with no ellipsis and nothing to tell the reader the address
       was cut. Wrapping keeps the whole value readable, which is what matters for an
       address someone has to type or check; an ellipsis would hide part of it. */
    overflow-wrap: break-word;
}

/* A card's leading name/title value (guest, staff member, tenant...) sits a
   touch smaller than a numeric .value so longer text wraps less aggressively.
   Was a `style="font-size:1.05rem"` D3 duplicate in 13 templates. */
.value--name {
    font-size: 1.05rem;
}

/* Orders grid card: the order code/id is a plain (often long, numeric-only for
   online channels) identifier, not the visual anchor of the card - keep it
   noticeably smaller than a real headline value. */
.order-code-value {
    font-size: .95rem;
}

/* Orders grid card table field: the Hall/Floor (or channel) name stays at the
   inherited .value size; the table label / raw order id next to it is
   secondary information, so it's shown smaller. */
.table-value-secondary {
    font-size: .8em;
    font-weight: 600;
    color: var(--text-secondary);
    margin-inline-start: .3em;
}

/* Orders grid card: Type/Status/Total kept on one row (instead of each on its
   own label+value pair) so the card reads more compactly. Each field is
   label+value on a single line (not stacked), and fields size to their own
   content (flex: 0 0 auto) rather than splitting the row evenly - an equal
   3-way split was narrower than "Dine-in"/"1180.00" at the card's min-width,
   so the text wrapped and the row collapsed back into stacked-looking lines.
   flex-wrap lets the whole group wrap onto a second line together if the
   card really is too narrow, instead of squeezing each field individually. */
.card-inline-row {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem 1.1rem;
    margin-top: .2rem;
}

.card-inline-field {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    gap: .35rem;
    white-space: nowrap;
}

.card-inline-field .label,
.card-inline-field .value {
    margin: 0;
}

.card-inline-field .value {
    font-size: .92rem;
}

.order-lines {
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: .3rem;
}

.order-line-item {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    font-size: .82rem;
    color: var(--text-secondary);
}

.order-line-item .qty {
    font-family: var(--font-mono);
    color: var(--text-muted);
    flex-shrink: 0;
}

/* ---------- Dashboard module launcher ---------- */

.module-launcher {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
    margin-top: 1.5rem;
    box-sizing: border-box;
}

.module-launcher-label {
    display: block;
    font-size: .72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
    margin-bottom: .65rem;
}

.module-tiles {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}

.module-tile {
    position: relative;
    flex: 0 0 225px;
    min-height: 215px;
    padding: 1.5rem 1.25rem;
    background: var(--surface-2);
    background-image: var(--card-sheen);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-sm);
    font-size: 1rem;
    font-weight: 700;
    transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease, border-color .14s ease;
    box-sizing: border-box;
}

/* Visual + click-target content of a tile. A plain <span> for the
   aria-disabled placeholder tiles (Online, AI, Agents); an <a> for real,
   built modules. Layout lives here (not on .module-tile) so the small
   upload-icon button below can sit as a sibling instead of nesting
   interactive content inside the link. */
.module-tile-link, .module-tile > span.module-tile-link {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: space-between;
    gap: .6rem;
    width: 100%;
    height: 100%;
    color: var(--text-primary);
    text-decoration: none;
    cursor: pointer;
}

.module-tile-link svg {
    width: 40px;
    height: 40px;
    color: var(--accent);
}

/* Module-name label as a highlighter-style badge — a bright tint of the
   module's assigned hue behind bold dark text, like a marked-up tag, rather
   than plain text. --tile-badge-color is set per-tile via a modifier class
   below; color-mix keeps the tint/border derived from one hue instead of
   hand-picked light/dark pairs. */
.module-tile-name {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding: .3rem .7rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 800;
    letter-spacing: .01em;
    color: var(--text-primary);
    background: color-mix(in srgb, var(--tile-badge-color) 30%, var(--surface-1));
    border: 1px solid color-mix(in srgb, var(--tile-badge-color) 55%, transparent);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
}

.module-tile-name--blue { --tile-badge-color: var(--badge-blue); }
.module-tile-name--green { --tile-badge-color: var(--badge-green); }
.module-tile-name--magenta { --tile-badge-color: var(--badge-magenta); }
.module-tile-name--yellow { --tile-badge-color: var(--badge-yellow); }
.module-tile-name--aqua { --tile-badge-color: var(--badge-aqua); }
.module-tile-name--orange { --tile-badge-color: var(--badge-orange); }
.module-tile-name--violet { --tile-badge-color: var(--badge-violet); }
.module-tile-name--red { --tile-badge-color: var(--badge-red); }

/* A tile with a custom uploaded image fills the entire card edge-to-edge
   (not a small 40px icon) — the module-name badge overlays the bottom-left
   corner on a gradient scrim, like a photo card, instead of sitting in
   normal flex flow. */
.module-tile:has(.module-tile-icon-img) {
    padding: 0;
    overflow: hidden;
}

.module-tile:has(.module-tile-icon-img) .module-tile-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(to top, rgba(0, 0, 0, .72) 0%, rgba(0, 0, 0, 0) 60%);
}

.module-tile:has(.module-tile-icon-img) .module-tile-link .module-tile-name {
    position: absolute;
    left: .9rem;
    bottom: .9rem;
    z-index: 2;
}

.module-tile-icon-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

.module-tile:hover {
    transform: translateY(-3px);
    box-shadow: inset 0 1px 0 var(--card-sheen-line),
                0 12px 30px rgba(var(--cta-glow), .18);
    border-color: var(--accent-soft);
}

.module-tile[aria-disabled="true"] {
    opacity: .45;
    cursor: not-allowed;
    pointer-events: none;
}

/* Upload/reset controls float in the tile's top-left corner as siblings of
   .module-tile-link, not nested inside it — avoids invalid interactive-in-
   interactive HTML, same sibling-overlay pattern as .dish-card-edit. Sits
   above the full-bleed image/gradient/label (z-index 0-2) when one is set. */
.module-tile-upload {
    position: absolute;
    top: .65rem;
    left: .65rem;
    display: flex;
    gap: .3rem;
    z-index: 3;
}

/* Fixed dark-chip-on-any-background instead of theme surface/text tokens -
   this control overlays the tile's own content (icon, uploaded photo, or a
   colored badge), so a theme-relative color could land low-contrast or
   near-invisible against whatever's directly under it. A constant dark
   translucent chip + white icon stays readable regardless. */
.module-tile-upload-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(17, 17, 17, .6);
    border: 1px solid rgba(255, 255, 255, .4);
    color: #fff;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
}

.module-tile-upload-btn:hover {
    background: rgba(17, 17, 17, .8);
    border-color: rgba(255, 255, 255, .7);
}

.module-tile-upload-btn svg {
    width: 14px;
    height: 14px;
}

.module-tile-upload-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
}

.module-tile-upload-reset {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(17, 17, 17, .6);
    border: 1px solid rgba(255, 255, 255, .4);
    color: #fff;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    font-size: .8rem;
    line-height: 1;
}

.module-tile-upload-reset:hover {
    background: rgba(220, 38, 38, .85);
    border-color: rgba(220, 38, 38, .3);
}

.summary-card {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem;
    margin-bottom: 1.5rem;
}

.summary-row {
    display: flex;
    justify-content: space-between;
    padding: .4rem 0;
    font-size: .9rem;
    border-bottom: 1px solid var(--border);
}

.summary-row:last-child {
    border-bottom: none;
}

.summary-row .label {
    color: var(--text-muted);
    font-weight: 600;
}

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

/* Duotone language: two-hue gradient fills (tokens above), a specular sheen
   overlaid via ::after, coloured ambient shadows, and a physical hover-lift /
   press-down. The sheen sits at z-index:-1 inside the button's own stacking
   context (isolation:isolate) so it tints the fill without covering the label. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    padding: .65rem 1.2rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: none;
    color: var(--text-secondary);
    font-size: .9rem;
    font-weight: 700;
    cursor: pointer;
    text-decoration: none;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease,
                background .14s ease, border-color .14s ease, filter .14s ease;
}

.btn:hover {
    transform: translateY(-1px);
    background: var(--accent-tint);
    color: var(--accent);
}

.btn:active {
    transform: translateY(1px);
}

.btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Filled variants — duotone gradient fill + specular sheen + coloured glow. */
.btn-primary,
.btn-accent,
.btn-success,
.btn-danger {
    border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3),
                0 2px 6px rgba(var(--cta-glow), .22),
                0 10px 26px rgba(var(--cta-glow), .26);
}

.btn-primary::after,
.btn-accent::after,
.btn-success::after,
.btn-danger::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(180deg, rgba(255, 255, 255, .26) 0%,
                rgba(255, 255, 255, .05) 45%, rgba(0, 0, 0, .10) 100%);
}

.btn-primary:hover,
.btn-accent:hover,
.btn-success:hover,
.btn-danger:hover {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .36),
                0 14px 34px rgba(var(--cta-glow), .34);
}

.btn-primary:active,
.btn-accent:active,
.btn-success:active,
.btn-danger:active {
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, .28);
}

.btn-primary {
    background: linear-gradient(135deg, var(--cta-grad-1) 0%,
                var(--cta-grad-2) 55%, var(--cta-grad-3) 100%);
    color: var(--cta-text);
}

.btn-primary:hover {
    background: linear-gradient(135deg, var(--cta-grad-1) 0%,
                var(--cta-grad-2) 55%, var(--cta-grad-3) 100%);
    color: var(--cta-text);
}

.btn-accent {
    background: linear-gradient(135deg, var(--accent-grad-1) 0%, var(--accent-grad-2) 100%);
    color: var(--accent-on-grad);
}

.btn-accent:hover {
    background: linear-gradient(135deg, var(--accent-grad-1) 0%, var(--accent-grad-2) 100%);
    color: var(--accent-on-grad);
}

.btn-success {
    background: linear-gradient(135deg, var(--success-grad-1) 0%, var(--success-grad-2) 100%);
    color: #fff;
}

.btn-success:hover {
    background: linear-gradient(135deg, var(--success-grad-1) 0%, var(--success-grad-2) 100%);
    color: #fff;
}

.btn-danger {
    background: linear-gradient(135deg, var(--danger-grad-1) 0%, var(--danger-grad-2) 100%);
    color: #fff;
}

.btn-danger:hover {
    background: linear-gradient(135deg, var(--danger-grad-1) 0%, var(--danger-grad-2) 100%);
    color: #fff;
}

.btn-secondary {
    background: linear-gradient(180deg, var(--surface-2), var(--page-plane));
    color: var(--text-secondary);
    border-color: var(--border);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 3px rgba(0, 0, 0, .07);
}

.btn-secondary:hover {
    background: linear-gradient(180deg, var(--surface-2), var(--page-plane));
    color: var(--accent);
    border-color: var(--accent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 3px 8px rgba(0, 0, 0, .09);
}

/* Explicit ghost alias — same look as a bare .btn; kept for template clarity. */
.btn-ghost {
    background: none;
    box-shadow: none;
    color: var(--text-secondary);
    border-color: var(--border);
}

.btn-ghost:hover {
    background: var(--accent-tint);
    color: var(--accent);
    box-shadow: none;
}

.btn-sm {
    padding: .35rem .75rem;
    font-size: .8rem;
    border-radius: 6px;
}

.btn-lg {
    padding: .9rem 1.7rem;
    font-size: 1rem;
}

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: .5;
    cursor: not-allowed;
    pointer-events: none;
    transform: none;
}

a.action-link, button.action-link {
    font-size: .8rem;
    font-weight: 700;
    color: var(--accent);
    text-decoration: none;
    padding: .3rem .7rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    display: inline-block;
    background: var(--surface-1);
    cursor: pointer;
    font-family: inherit;
    line-height: 1.4;
}

a.action-link:hover, button.action-link:hover {
    background: var(--accent-tint);
}

.action-link.danger {
    color: var(--danger);
    border-color: rgba(220,38,38,.3);
}

.action-link.danger:hover {
    background: rgba(220,38,38,.12);
}

/* Action clusters in a table cell or on a card. `.row-actions` is by far the
   more common spelling in the templates (12 files) and was never defined here,
   so those clusters rendered with no flex and no gap — both names are supported
   rather than churning every call site. */
.table-row-actions,
.row-actions {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}

/* Card-view variant: separates the action row from the card content above it
   (a table-cell .row-actions wants no such gap, since it's already boxed by
   the cell). Was a `style="margin-top:.75rem;"` D3 duplicate in 12 templates. */
.row-actions--spaced {
    margin-top: .75rem;
}

.table-row-actions form.inline,
.row-actions form.inline {
    display: inline-flex;
}

/* Keeps the file picker from stretching the Actions column on wide tables. */
.po-invoice-upload {
    display: flex;
    align-items: center;
    gap: .3rem;
}

.po-invoice-upload input[type="file"] {
    max-width: 9rem;
}

/* ---------- Forms ---------- */

.form-field {
    margin-bottom: 1.1rem;
}

.form-field label {
    display: block;
    font-size: .82rem;
    font-weight: 700;
    margin-bottom: .35rem;
    color: var(--text-secondary);
}

.form-field input,
.form-field select,
.form-field textarea {
    width: 100%;
    padding: .6rem .75rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-1);
    font-size: .9rem;
    font-family: inherit;
    color: var(--text-primary);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .06);
}

.form-field input.field-invalid {
    border-color: var(--danger);
    background: var(--danger-tint);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
    outline: 2px solid var(--accent-soft);
    outline-offset: 1px;
}

.form-row {
    display: flex;
    gap: 1rem;
}

.form-row .form-field {
    flex: 1;
}

/* ---------- Inline form controls ---------- */

/*
 * Compact controls that live in a toolbar, a table row or a side panel rather
 * than in a stacked `.form-field`. Form controls are styled *only* through
 * `.form-field input|select|textarea` above, so these matched no rule at all
 * and rendered as raw browser widgets beside properly styled fields.
 *
 * `.field-inline` is the class to reach for on new markup. The container
 * selectors beside it cover the panels that already exist, which fixes ~40
 * call sites without touching a single template.
 *
 * Checkboxes, radios, file and hidden inputs are deliberately excluded: they
 * are intentionally native here, and the `.form-field` treatment (notably
 * `width: 100%`) actively breaks a checkbox — which is why settings-tax.html
 * carried a hand-written `style="width:auto"` patch.
 */
.field-inline,
:is(.wastage-panel, .upload-panel, .import-panel, .bulk-price-panel, .search-box,
    .stat-box, .recipe-filter-bar, .recipe-import-row, .new-batch-form,
    .orders-date-range-form, .invoice-review-row, .ci-subrecipe-fields,
    .ci-inventory-fields, .qty-input-row,
    form.inline, .data-table td)
  :is(input, select, textarea):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]) {
    padding: .45rem .6rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-1);
    font-size: .85rem;
    font-family: inherit;
    color: var(--text-primary);
    max-width: 100%;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .06);
}

.field-inline:focus,
:is(.wastage-panel, .upload-panel, .import-panel, .bulk-price-panel, .search-box,
    .stat-box, .recipe-filter-bar, .recipe-import-row, .new-batch-form,
    .orders-date-range-form, .invoice-review-row, .ci-subrecipe-fields,
    .ci-inventory-fields, .qty-input-row,
    form.inline, .data-table td)
  :is(input, select, textarea):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):focus {
    outline: 2px solid var(--accent-soft);
    outline-offset: 1px;
}

/* Sizing hints for the three controls in a recipe-ingredient row. They were
   referenced by preparation-recipes.html but never defined, so all three sat
   at equal auto width and the cramped row wrapped unpredictably. */
.ci-existing-item {
    min-width: 12rem;
}

.ci-new-item-name {
    min-width: 10rem;
}

.ci-unit {
    min-width: 5rem;
}

/* Secondary/annotation text. Used in several templates (recipe filter counts,
   inline hints) but previously undefined, so it rendered at full body colour
   and read as primary content. */
.text-muted {
    color: var(--text-muted);
    font-size: .82rem;
}

fieldset {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem 1.25rem;
    margin: 0 0 1.5rem;
    background: var(--surface-2);
}

legend {
    font-weight: 700;
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    padding: 0 .4rem;
}

.module-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: .5rem;
    margin-top: .5rem;
}

.module-grid label {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .85rem;
    font-weight: 500;
    color: var(--text-primary);
}

.actions {
    display: flex;
    gap: .75rem;
    margin-top: 1.5rem;
    flex-wrap: wrap;
}

/* ---------- Tables ---------- */

table.data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

table.data-table th,
table.data-table td {
    text-align: start;
    padding: .75rem 1rem;
    font-size: .88rem;
    border-bottom: 1px solid var(--border);
}

/* The uppercase micro-caps sat on the same .75rem vertical padding as body
   rows, so the head crowded the first row. Slightly more room and a real 2px
   rule underneath separate head from body without changing row density. */
table.data-table th {
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    background: var(--surface-1);
    font-weight: 700;
    padding-top: .85rem;
    padding-bottom: .85rem;
    border-bottom: 2px solid var(--border);
}

table.data-table tr:last-child td {
    border-bottom: none;
}

table.data-table tbody tr:hover {
    background: var(--accent-tint);
}

/* ---------- Generic Grid/List toggle for record-listing pages ---------- */

.list-toggle {
    width: 100%;
    box-sizing: border-box;
}

.list-toggle .view-toggle {
    margin-inline-start: auto;
    margin-bottom: 1rem;
}

.list-toggle.mode-list .card-grid {
    display: none;
}

.list-toggle.mode-grid table.data-table {
    display: none;
}

.empty-state {
    padding: 3rem 1.25rem;
    text-align: center;
    color: var(--text-muted);
    background: var(--surface-2);
    border: 1px dashed var(--border);
    border-radius: var(--radius);
}

/* Promotes the headline onto its own line. Without this the title and the
   explanatory sentence run together as one muted block of text. Templates that
   use a bare .empty-state with no <strong> are unaffected. */
.empty-state strong {
    display: block;
    color: var(--text-secondary);
    font-size: .95rem;
    margin-bottom: .3rem;
}

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

/* Standard Indian menu veg/non-veg mark: a square outline with a filled dot,
   green for veg, maroon for non-veg. Shared component (was duplicated
   page-local in menu-products.html only; the POS New Order screen needs the
   same mark on its product cards). */
.veg-mark {
    width: 12px;
    height: 12px;
    border: 1.5px solid var(--success);
    border-radius: 2px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-inline-end: .4rem;
    flex-shrink: 0;
}

.veg-mark::after {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--success);
}

.veg-mark.nonveg {
    border-color: var(--danger);
}

.veg-mark.nonveg::after {
    background: var(--danger);
}

.badge {
    display: inline-block;
    background: var(--accent-tint);
    /* Duotone wash — sits on top of whatever background the colour modifiers set. */
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .4), transparent);
    color: var(--accent);
    font-size: .72rem;
    font-weight: 800;
    padding: .2rem .6rem;
    border-radius: 999px;
    letter-spacing: .03em;
    text-transform: uppercase;
    white-space: nowrap;
}

.badge-muted {
    background: var(--border);
    color: var(--text-muted);
}

/* Status-colored badge variants, built from the same --badge-* hue tokens the
   dashboard module-tiles use (§3.3) — tint background + solid text, same
   formula as .kot-timer.stale. Used where a list has more states than a
   plain accent-colored .badge can distinguish at a glance (dev-plan item 5:
   Exclusion List, Reservations). */
.badge-red { background: color-mix(in srgb, var(--badge-red) 18%, var(--surface-1)); color: var(--badge-red); }

/* Super Admin chrome badge (fragments/platform-shell.html) — sits on the dark
   .app-header, so it keys off chrome tokens instead of the content palette. */
.platform-badge {
    background: var(--chrome-active-bg);
    color: var(--chrome-accent);
    border: 1px solid var(--chrome-border);
}
.badge-green { background: color-mix(in srgb, var(--badge-green) 18%, var(--surface-1)); color: var(--badge-green); }
.badge-blue { background: color-mix(in srgb, var(--badge-blue) 18%, var(--surface-1)); color: var(--badge-blue); }
.badge-yellow { background: color-mix(in srgb, var(--badge-yellow) 22%, var(--surface-1)); color: var(--badge-yellow); }
.badge-aqua { background: color-mix(in srgb, var(--badge-aqua) 18%, var(--surface-1)); color: var(--badge-aqua); }

.alert {
    padding: .65rem .9rem;
    border-radius: var(--radius-sm);
    font-size: .88rem;
    margin-bottom: 1.25rem;
    position: relative;
}

.alert-dismissible {
    padding-inline-end: 2.2rem;
}

.alert-close {
    position: absolute;
    top: .4rem;
    right: .55rem;
    background: none;
    border: none;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    color: inherit;
    opacity: .65;
    padding: .2rem;
}

.alert-close:hover {
    opacity: 1;
}

.alert-message {
    display: block;
    overflow-wrap: anywhere;
    margin-bottom: .5rem;
}

.alert-error {
    background: var(--danger-tint);
    color: var(--danger);
}

.alert-success {
    background: var(--success-tint);
    color: var(--success);
}

.alert-info {
    background: var(--accent-tint);
    color: var(--text-primary);
}

.note {
    background: var(--accent-tint);
    color: var(--text-primary);
    border-radius: var(--radius-sm);
    padding: .75rem 1rem;
    font-size: .85rem;
    margin-bottom: 1.25rem;
}

/* ---------- App shell with icon rail (fragments/sidebar.html) ---------- */

.app-shell {
    display: flex;
    min-height: 100vh;
}

.sidebar-rail {
    width: 104px;
    flex-shrink: 0;
    background: var(--chrome-bg);
    border-inline-end: 1px solid var(--chrome-border);
    display: flex;
    flex-direction: column;
    align-items: center;
}

.sidebar-logo {
    height: 80px;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border-bottom: 1px solid var(--chrome-border);
    flex-shrink: 0;
}

/* The real BlinkVenue logo, linked to /dashboard. Uses the dedicated
   icon-only asset (blinkvenue-icon.png, square with transparent corners)
   rather than cropping a slice out of the full wordmark lockup — simpler
   and doesn't break if the lockup image's proportions ever change. */
.sidebar-logo .mark {
    display: block;
    width: 44px;
    height: 44px;
    background-image: url('/images/blinkvenue-icon.png');
    background-repeat: no-repeat;
    background-size: 44px 44px;
    background-position: center;
    border-radius: 8px;
}

.sidebar-logo .mark:hover {
    background-color: color-mix(in srgb, var(--chrome-accent) 18%, transparent);
}

.sidebar-groups {
    flex: 1;
    width: 100%;
    overflow-y: auto;
    padding: .85rem 0;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}

.sidebar-footer {
    width: 100%;
    flex-shrink: 0;
    padding: .75rem 0;
    display: flex;
    justify-content: center;
    border-top: 1px solid var(--chrome-border);
}

.sidebar-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .3rem;
}

.sidebar-group-label {
    font-size: .6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--chrome-text-muted);
    opacity: .85;
    margin-bottom: .15rem;
}

.sidebar-link {
    width: 64px;
    padding: .5rem 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .3rem;
    border-radius: var(--radius-sm);
    color: var(--chrome-text-secondary);
    text-decoration: none;
    background: none;
    border: none;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}

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

.sidebar-link.active {
    background: var(--chrome-active-bg);
    color: var(--chrome-accent);
}

.sidebar-link svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.sidebar-link .sidebar-label {
    font-size: .6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -.01em;
}

.sidebar-link[aria-disabled="true"] {
    opacity: .35;
    pointer-events: none;
    cursor: not-allowed;
}

.shell-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    max-height: 100vh;
    overflow-y: auto;
}

/* ---------- Chrome header (brand + tenant/site + lang pill + user chip) ---------- */

.chrome-header {
    height: 80px;
    flex-shrink: 0;
    padding: 0 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--chrome-bg);
    border-bottom: 1px solid var(--chrome-border);
}

.chrome-header .chrome-left {
    display: flex;
    align-items: baseline;
    gap: 1rem;
    min-width: 0;
}

/* A read-only fact inside a form — used where a field used to be a control but the
   value is now decided server-side (the active store on create forms). Styled to
   read as information rather than as a disabled input, which would imply it could
   be enabled. */
.field-static-label {
    display: block;
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.field-static-value {
    display: block;
    margin-top: .15rem;
    font-weight: 600;
    color: var(--text-primary);
}

/* Store + channel filter bar on Reports. Sits directly under the tab strip and
   applies to whichever tab is open, so it reads as scoping the whole page rather
   than belonging to one report. Wraps on narrow screens instead of forcing the
   page to scroll sideways. */
.report-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: 0 0 1rem;
}

.report-filter-label {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    white-space: nowrap;
}

/* Only rendered while a filter is active — the way back to the whole business. */
.report-filter-clear {
    font-size: .8rem;
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    margin-inline-start: .25rem;
}

.report-filter-clear:hover,
.report-filter-clear:focus-visible {
    text-decoration: underline;
}

/* Disclosure on the Day-end tab when the figures cover only the default store
   because no active store is bound. Deliberately styled as a caution rather
   than a neutral caption — reading one store's till as the whole business's is
   the mistake it exists to prevent. */
.report-site-fallback {
    margin-top: .3rem;
    color: var(--danger);
    font-weight: 600;
}

/* Active-store control in the chrome header (§3.16). Renders only for users who
   can reach more than one store, so on a single-store tenant these rules never
   match anything. align-items:center rather than inheriting .chrome-left's
   baseline, which would hang the select off the brand's text baseline. */
.chrome-header .store-switcher {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0;
}

.chrome-header .store-switcher-label {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    white-space: nowrap;
}

.chrome-header .store-switcher select {
    max-width: 12rem;
}

.chrome-header .chrome-brand {
    font-family: var(--font-display);
    font-size: 1.1rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--chrome-accent);
    white-space: nowrap;
}

.chrome-header .chrome-divider {
    width: 1px;
    height: 1rem;
    background: var(--chrome-border);
    flex-shrink: 0;
}

.chrome-header .chrome-tenant {
    font-size: .88rem;
    font-weight: 700;
    color: var(--chrome-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.chrome-header .chrome-site {
    font-family: var(--font-mono);
    font-size: .72rem;
    color: var(--chrome-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.chrome-header .chrome-right {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-shrink: 0;
}

.pill-group {
    display: flex;
    background: var(--chrome-bg-hover);
    padding: .15rem;
    border-radius: 999px;
    gap: .1rem;
}

.pill-group a {
    padding: .3rem .65rem;
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--chrome-text-muted);
    text-decoration: none;
    border-radius: 999px;
}

.pill-group a.active {
    background: var(--chrome-active-bg);
    color: var(--chrome-text);
    box-shadow: var(--shadow-sm);
}

.user-chip {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: 0 .9rem;
    border-inline-start: 1px solid var(--chrome-border);
    border-inline-end: 1px solid var(--chrome-border);
}

.user-chip .user-chip-text {
    text-align: center;
    line-height: 1.2;
}

.user-chip .user-chip-name {
    font-size: 1rem;
    font-weight: 800;
    color: var(--chrome-accent);
    margin: 0;
}

.user-chip .user-chip-role {
    font-size: .65rem;
    color: var(--chrome-text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
    margin: 0;
}

/* The active store's short_code / UrbanPiper location_ref_id — a third, quieter
   line under the tenant name+code, monospace so the exact value is unambiguous
   to copy into an aggregator's outlet-mapping config. */
.user-chip .user-chip-locref {
    font-size: .6rem;
    font-family: var(--font-mono);
    color: var(--chrome-text-muted);
    margin: 0;
}

/* Log out sits on its own after the tenant chip — themed CTA gradient via
   .btn-primary, so it reads amber on the amber theme and blue on the blue one. */
.chrome-header .chrome-logout,
.app-header .chrome-logout {
    margin: 0;
}

.chrome-search {
    position: relative;
    width: 220px;
}

.chrome-search input {
    width: 100%;
    padding: .45rem .75rem .45rem 2.1rem;
    border-radius: 999px;
    border: 1px solid var(--chrome-border);
    background: var(--chrome-bg-hover);
    font-size: .8rem;
    font-family: inherit;
    color: var(--chrome-text);
}

.chrome-search input::placeholder {
    color: var(--chrome-text-muted);
}

.chrome-search input:focus {
    outline: 2px solid var(--chrome-accent);
    outline-offset: 1px;
}

.chrome-search::before {
    content: "🔍";
    position: absolute;
    left: .75rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: .75rem;
    opacity: .55;
}

.icon-btn {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    border: 1px solid var(--chrome-border);
    background: var(--chrome-bg-hover);
    color: var(--chrome-text-secondary);
    cursor: pointer;
    position: relative;
}

.icon-btn:hover {
    background: var(--chrome-active-bg);
    color: var(--chrome-accent);
}

.icon-btn svg {
    width: 17px;
    height: 17px;
}

.icon-btn .icon-moon {
    display: none;
}

:root[data-theme="dark"] .icon-btn .icon-sun,
:root[data-theme="darker-blue"] .icon-btn .icon-sun {
    display: none;
}

:root[data-theme="dark"] .icon-btn .icon-moon,
:root[data-theme="darker-blue"] .icon-btn .icon-moon {
    display: block;
}

.icon-btn[aria-disabled="true"] {
    opacity: .4;
    cursor: not-allowed;
    pointer-events: none;
}

.icon-btn .notif-dot {
    position: absolute;
    top: 5px;
    right: 6px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--danger);
}

/* ---------- Responsive app chrome (phone/small-tablet) ---------- */

/*
 * Below 720px the rail goes off-canvas behind a hamburger and the chrome header
 * compacts. Above it, nothing here applies — tablet-landscape and desktop keep
 * the layout they already have.
 *
 * Scoped to #appRail — the TENANT rail in fragments/sidebar.html — and not to
 * `.sidebar-rail`, which fragments/platform-shell.html also uses. The platform
 * console renders `topbar`, not `chromeHeader`, so it has no hamburger: matching
 * the class would slide the Super Admin nav off-canvas with nothing able to
 * bring it back. Giving the platform shell the same treatment is a separate
 * change, and until then it keeps its existing fixed rail at every width.
 *
 * Measured on a 390px phone before this existed: the rail held a fixed
 * 104px (27% of the screen, leaving 286px of content) and .chrome-header was an
 * 80px non-wrapping flex bar carrying brand + store switcher + online toggle +
 * page links + language + theme + update chip + a 3-line user chip + logout,
 * which overlapped each other.
 *
 * The four interactive controls are RELOCATED into the drawer by chrome-nav.js,
 * not hidden — a phone user still needs store switching, the online toggle,
 * theme and language. `flex-wrap` below is the no-JS fallback: if the script
 * never runs, the header wraps to a second row (ugly, still usable) rather than
 * overlapping itself or losing controls entirely.
 */

.nav-toggle { display: none; }

@media (max-width: 720px) {

    /* Rail becomes an off-canvas drawer. */
    #appRail,
    #platformRail {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        z-index: 60;
        /* Deliberately the same 104px as desktop rather than a narrower drawer:
           off-canvas costs no content width, so shrinking it buys nothing and
           starts truncating the labels and the relocated language select. */
        transform: translateX(-100%);
        transition: transform .22s cubic-bezier(.2, .8, .3, 1);
        box-shadow: var(--shadow-md);
    }

    /* `translateX` is screen-relative, not writing-mode relative, so RTL has to
       hide off the opposite edge explicitly (multi-store plan §10, Stage 5). */
    [dir="rtl"] #appRail,
    [dir="rtl"] #platformRail { transform: translateX(100%); }

    :root.nav-open #appRail,
    :root.nav-open #platformRail,
    [dir="rtl"] :root.nav-open #appRail,
    [dir="rtl"] :root.nav-open #platformRail { transform: none; }

    :root.nav-open body::after {
        content: "";
        position: fixed;
        inset: 0;
        z-index: 55;
        background: rgba(0, 0, 0, .45);
    }

    :root.nav-open body { overflow: hidden; }

    .nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        flex: 0 0 auto;
        border-radius: var(--radius-sm);
        border: 1px solid var(--chrome-border);
        background: none;
        color: var(--chrome-text);
        cursor: pointer;
        padding: 0;
    }

    .nav-toggle:hover { background: var(--chrome-bg-hover); }

    .nav-toggle:focus-visible {
        outline: 2px solid var(--chrome-accent);
        outline-offset: 2px;
    }

    /* Compact header. height:auto + wrap is what keeps the no-JS state legible. */
    .chrome-header,
    .app-header {
        height: auto;
        min-height: 56px;
        flex-wrap: wrap;
        padding: .35rem .7rem;
        gap: .45rem;
    }

    .chrome-header .chrome-left { gap: .5rem; align-items: center; min-width: 0; }
    .chrome-header .chrome-right { gap: .35rem; min-width: 0; }

    /* Text-only chrome that a phone header has no room for. These are labels,
       not controls — nothing becomes unreachable by hiding them. */
    .chrome-header .chrome-divider,
    .chrome-header .user-chip-role,
    .chrome-header .user-chip-locref,
    .chrome-header .nav-link,
    .chrome-header .store-static-label,
    /* Platform topbar: the admin's name rides a .nav-link span. Identity is
       still on screen via the console's own pages; the name is the first thing
       to go when 390px has to fit a hamburger, a brand and a logout button. */
    .app-header .nav-link { display: none; }

    /* The Super Admin console keeps its "Platform" badge — on a phone it is the
       only thing distinguishing this shell from the tenant app. */
    .app-header .brand { font-size: .95rem; white-space: nowrap; min-width: 0; }
    .app-header .header-actions { gap: .35rem; min-width: 0; flex-wrap: wrap; }
    .app-header .btn { padding: .32rem .62rem; font-size: .78rem; }

    .chrome-header .chrome-brand { font-size: .95rem; white-space: nowrap; }

    .chrome-header .user-chip-name {
        max-width: 10ch;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-size: .8rem;
    }

    .chrome-header .chrome-logout .btn { padding: .32rem .62rem; font-size: .78rem; }

    /* Drawer slot: the controls chrome-nav.js parks here. */
    .rail-chrome-slot {
        width: 100%;
        flex-shrink: 0;
        padding: .7rem .5rem;
        border-top: 1px solid var(--chrome-border);
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: .55rem;
    }

    .rail-chrome-slot .store-switcher { display: flex; flex-direction: column; gap: .25rem; }
    .rail-chrome-slot .store-switcher-label,
    .rail-chrome-slot .lang-switcher-select,
    .rail-chrome-slot select { width: 100%; }
    .rail-chrome-slot .theme-toggle,
    .rail-chrome-slot .online-toggle-pill { width: 100%; }
}

/* ---------- POS ordering screen (order-detail.html) ---------- */

.pos-layout {
    display: grid;
    grid-template-columns: 1fr 380px;
    gap: 1.5rem;
    align-items: start;
}

@media (max-width: 980px) {
    .pos-layout {
        grid-template-columns: 1fr;
    }

    /* Below the breakpoint the columns stack, so any panel sticking to the
       same top offset would overlap - fall back to normal page scroll, and
       let the menu column drop its two-row span. */
    .pos-menu-column,
    .cart-panel,
    .checkout-panel {
        position: static;
        max-height: none;
        overflow-y: visible;
    }

    .pos-layout--open .pos-menu-column {
        grid-column: 1;
        grid-row: auto;
        position: static;
        max-height: none;
        overflow-y: visible;
    }

    .pos-layout--open .cart-panel,
    .pos-layout--open .checkout-panel {
        grid-column: 1;
    }
}

.pos-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .4rem;
    padding: .6rem 0;
    grid-column: 1 / -1;
}

.pos-toolbar-btn {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: .74rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .5rem .85rem;
    border-radius: 999px;
    text-decoration: none;
    cursor: pointer;
}

.pos-toolbar-btn:hover {
    background: var(--accent-tint);
    color: var(--accent);
    border-color: var(--accent-soft);
}

.pos-toolbar-btn.primary {
    background: var(--cta-bg);
    color: var(--cta-text);
    border-color: var(--cta-bg);
}

.pos-toolbar-btn.danger {
    color: var(--danger);
    border-color: var(--danger-tint);
}

.pos-toolbar-btn.danger:hover {
    background: var(--danger-tint);
}

.pos-toolbar-btn[aria-disabled="true"],
.pos-toolbar-btn:disabled {
    opacity: .4;
    cursor: not-allowed;
    pointer-events: none;
}

.pos-toolbar-icon {
    font-weight: 900;
}

.pos-toolbar-form {
    display: contents;
}

.pos-toolbar-popover {
    position: relative;
}

.pos-toolbar-popover > summary {
    list-style: none;
}

.pos-toolbar-popover > summary::-webkit-details-marker {
    display: none;
}

.pos-toolbar-popover[open] > summary {
    background: var(--accent-tint);
    color: var(--accent);
    border-color: var(--accent-soft);
}

.pos-popover-panel {
    position: absolute;
    top: calc(100% + .4rem);
    left: 0;
    z-index: 20;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md, 0 8px 24px rgba(0,0,0,.15));
    padding: .9rem;
    width: 240px;
    display: flex;
    flex-direction: column;
    gap: .6rem;
}

.pos-popover-hint {
    margin: 0;
    font-size: .78rem;
    color: var(--text-muted);
}

.menu-heading {
    display: flex;
    align-items: center;
    /* flex-start + the h2's margin-right:auto, not space-between: with the
       search capped at a max-width, space-between dumped the leftover into
       uneven gaps around the veg chips. This way every control sits at exactly
       one `gap` from its neighbour and only the title-to-controls gap flexes. */
    justify-content: flex-start;
    gap: 1rem;
    margin-bottom: .85rem;
}

.menu-heading h2 {
    margin-right: auto;
}

.menu-heading h2 {
    font-family: var(--font-display);
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    margin: 0;
    color: var(--text-primary);
    flex: none;
}

.view-toggle {
    display: flex;
    width: fit-content;
    flex: none;
    background: var(--surface-1);
    padding: .15rem;
    border-radius: 999px;
    gap: .1rem;
    border: 1px solid var(--border);
}

.view-toggle button {
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border: none;
    background: none;
    color: transparent;
    padding: 0;
    border-radius: 999px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
}

.view-toggle button::before {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 15px;
    height: 15px;
    background-color: var(--text-muted);
    -webkit-mask: var(--view-icon) center / contain no-repeat;
    mask: var(--view-icon) center / contain no-repeat;
}

.view-toggle button[data-view="grid"] {
    --view-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7'/%3E%3Crect x='14' y='3' width='7' height='7'/%3E%3Crect x='3' y='14' width='7' height='7'/%3E%3Crect x='14' y='14' width='7' height='7'/%3E%3C/svg%3E");
}

.view-toggle button[data-view="list"] {
    --view-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' y1='6' x2='20' y2='6'/%3E%3Cline x1='4' y1='12' x2='20' y2='12'/%3E%3Cline x1='4' y1='18' x2='20' y2='18'/%3E%3C/svg%3E");
}

.view-toggle button.active {
    background: var(--surface-2);
    box-shadow: var(--shadow-sm);
}

.view-toggle button.active::before {
    background-color: var(--accent);
}

.pos-topbar {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: .9rem 1.25rem;
    margin-bottom: 1.25rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    grid-column: 1 / -1;
}

.pos-topbar-meta {
    display: flex;
    align-items: baseline;
    gap: 1.1rem;
    flex-wrap: wrap;
}

.pos-topbar-meta .order-ref {
    font-family: var(--font-mono);
    font-weight: 600;
    font-size: 1rem;
}

.pos-topbar-meta .label {
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    font-weight: 700;
    margin-inline-end: .3rem;
}

.order-type-badge {
    background: var(--accent-tint);
    color: var(--accent);
    font-size: .7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: .3rem .7rem;
    border-radius: 999px;
}

.sale-mode-group {
    display: inline-flex;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: .2rem;
    gap: .15rem;
}

.sale-mode-form {
    display: contents;
}

.sale-mode-pill {
    font-family: inherit;
    border: none;
    background: none;
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-muted);
    padding: .3rem .7rem;
    border-radius: 999px;
    cursor: pointer;
}

.sale-mode-pill:hover:not(.active):not(:disabled) {
    background: var(--accent-tint);
    color: var(--accent);
}

.sale-mode-pill.active {
    background: var(--cta-bg);
    color: var(--cta-text);
    cursor: default;
}

.sale-mode-pill:disabled {
    cursor: not-allowed;
}

.shift-chip {
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    font-size: .78rem;
    color: var(--text-secondary);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: .3rem .8rem;
}

.shift-chip-empty {
    cursor: pointer;
    color: var(--danger);
    border-color: var(--danger-tint);
    font-weight: 700;
}

.pos-search {
    position: relative;
    margin-bottom: 1rem;
}

.pos-search input {
    width: 100%;
    padding: .65rem .9rem .65rem 2.4rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    font-size: .88rem;
    font-family: inherit;
    color: var(--text-primary);
}

.pos-search input:focus {
    outline: 2px solid var(--accent-soft);
    outline-offset: 1px;
}

.pos-search::before {
    content: "🔍";
    position: absolute;
    left: .9rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: .85rem;
    opacity: .55;
}

.menu-search {
    position: relative;
    margin-bottom: 1rem;
}

.menu-search input {
    width: 100%;
    /* padding-left carries !important because the shared control rule at
       :is(.recipe-filter-bar, .search-box, …) input has ~(0,5,1) specificity
       from its :not() chain and silently flattens this to .6rem wherever a
       .menu-search sits inside one of those containers — which put the 🔍
       icon ON TOP of the text on /preparation/recipes. menu-products.html
       fights the identical conflict with a page-local !important; this fixes
       it once at the component. */
    padding: .55rem .9rem .55rem 2.3rem;
    padding-left: 2.3rem !important;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    font-size: .85rem;
    font-family: inherit;
    color: var(--text-primary);
}

/* Compact variant sitting inline in .menu-heading, between the "MENU" title
   and the Grid/List toggle, instead of the default full-width block above
   the category chips. */
.menu-search--inline {
    flex: 1 1 auto;
    max-width: 340px;
    margin-bottom: 0;
}

.menu-search--inline input {
    padding: .35rem .75rem .35rem 2rem;
    /* Same shared-control-rule conflict as .menu-search input above. */
    padding-left: 2rem !important;
    font-size: .8rem;
}

.menu-search--inline::before {
    left: .7rem;
    font-size: .7rem;
}

/* Two independent on/off chips (not a single-select segment like .view-toggle) -
   Veg and Non-veg each toggle their own .active state; clicking an already-active
   one clears it back to "no filter". Colour matches .veg-mark's green/maroon
   convention so the filter reads as an extension of the per-dish mark. */
.veg-toggle-group {
    display: flex;
    gap: .4rem;
    flex: none;
}

.veg-toggle-chip {
    display: inline-flex;
    align-items: center;
    border: 1.5px solid var(--success);
    background: var(--surface-2);
    color: var(--success);
    font-size: .72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .4rem .8rem;
    border-radius: 999px;
    cursor: pointer;
}

.veg-toggle-chip:hover {
    background: color-mix(in srgb, var(--success) 12%, var(--surface-2));
}

.veg-toggle-chip.active {
    background: var(--success);
    color: #fff;
}

.veg-toggle-chip.nonveg {
    border-color: var(--danger);
    color: var(--danger);
}

.veg-toggle-chip.nonveg:hover {
    background: color-mix(in srgb, var(--danger) 12%, var(--surface-2));
}

.veg-toggle-chip.nonveg.active {
    background: var(--danger);
    color: #fff;
}

.menu-search input::placeholder {
    color: var(--text-muted);
}

.menu-search input:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.menu-search::before {
    content: "🔍";
    position: absolute;
    left: .9rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: .8rem;
    opacity: .55;
}

.category-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: 1.25rem;
}

.category-chip {
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: .78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .45rem 1rem;
    border-radius: 999px;
    cursor: pointer;
}

.category-chip:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.category-chip.active {
    background: var(--cta-bg);
    border-color: var(--cta-bg);
    color: var(--cta-text);
}

.product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 1rem;
}

.product-card {
    /* Anchors .product-card-note-btn, which is absolutely positioned over the media. */
    position: relative;
    background: var(--surface-2);
    background-image: var(--card-sheen);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-sm);
    display: flex;
    flex-direction: column;
    transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease;
}

.product-card:hover {
    transform: translateY(-2px);
    box-shadow: inset 0 1px 0 var(--card-sheen-line),
                0 10px 24px rgba(var(--cta-glow), .16);
}

.product-card-media {
    position: relative;
    height: 140px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    background: linear-gradient(135deg, var(--accent-tint), var(--page-plane));
}

.product-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.product-card-media-code {
    position: absolute;
    top: .5rem;
    left: .5rem;
    background: rgba(28, 20, 16, .72);
    color: #fff;
    font-family: var(--font-mono);
    font-size: .68rem;
    font-weight: 700;
    padding: .15rem .5rem;
    border-radius: 999px;
}

.product-card-media-price {
    position: absolute;
    bottom: .5rem;
    right: .5rem;
    background: var(--cta-bg);
    color: var(--cta-text);
    font-family: var(--font-mono);
    font-size: .72rem;
    font-weight: 700;
    padding: .25rem .6rem;
    border-radius: 999px;
    box-shadow: var(--shadow-sm);
}

.product-card-footer .product-card-price {
    display: none;
}

.product-card-body {
    padding: .75rem .85rem .9rem;
    display: flex;
    flex-direction: column;
    gap: .3rem;
    flex: 1;
}

.product-card-code {
    font-family: var(--font-mono);
    font-size: .68rem;
    color: var(--text-muted);
    font-weight: 600;
}

.product-card-category {
    font-size: .62rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    font-weight: 700;
}

.product-card-name {
    font-size: .88rem;
    font-weight: 700;
    line-height: 1.25;
}

.product-card-footer {
    margin-top: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.product-card-price {
    font-family: var(--font-mono);
    font-weight: 600;
    font-size: .85rem;
}

.product-card form {
    margin: 0;
}

.product-card-add {
    position: relative;
    background: var(--accent-tint);
    color: var(--accent);
    border: none;
    border-radius: 999px;
    font-size: .72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .45rem .8rem;
    cursor: pointer;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.product-card-add:hover {
    background: linear-gradient(135deg, var(--cta-grad-1) 0%, var(--cta-grad-2) 55%, var(--cta-grad-3) 100%);
    color: var(--cta-text);
}

.product-card-add-badge {
    position: absolute;
    top: -.5rem;
    right: -.4rem;
    min-width: 1.3rem;
    height: 1.3rem;
    padding: 0 .3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cta-bg);
    color: var(--cta-text);
    border: 2px solid var(--surface-2);
    border-radius: 999px;
    font-size: .68rem;
    font-weight: 800;
    box-shadow: var(--shadow-sm);
}

.product-grid.list-view {
    grid-template-columns: 1fr;
}

.list-view .product-card {
    flex-direction: row;
    align-items: center;
}

.list-view .product-card-media {
    height: 64px;
    width: 64px;
    flex-shrink: 0;
    font-size: 1.6rem;
}

.list-view .product-card-media-code,
.list-view .product-card-media-price {
    display: none;
}

.list-view .product-card-footer .product-card-price {
    display: inline-block;
}

.list-view .product-card-footer {
    display: flex;
    align-items: center;
    gap: .5rem;
}

.list-view .product-card-add {
    width: auto;
}

.list-view .product-card-body {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    padding: .6rem .85rem;
    gap: .6rem 1rem;
}

.list-view .product-card-category {
    order: 1;
    flex-basis: 100px;
}

.list-view .product-card-name {
    order: 2;
    flex: 1;
    min-width: 120px;
}

.list-view .product-card form {
    order: 4;
    flex-shrink: 0;
}

.list-view .product-card-footer {
    margin-top: 0;
}

.cart-panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    /* Discount/Customer/Coupon/Payment now live inside this panel (below the
       cart lines/totals/actions) - their combined height easily exceeds the
       viewport, so the panel itself scrolls rather than spilling out past
       its sticky position. .cart-lines keeps its own inner scroll for the
       line-item list specifically. */
    overflow-y: auto;
    /* auto (the default) lets wheel input chain into the page once this
       panel hits its own top/bottom edge, so scrolling past the edge just
       continues onto .shell-main instead of getting stuck. */
    overscroll-behavior: auto;
}

/* ---- Status-scoped POS layouts (see the comment on .pos-layout in
   order-detail.html). The OPEN ordering screen and the CLOSED review screen
   share one template; each mode's placement rules are scoped to its modifier
   so a change to one can never restructure the other. ---- */

/* OPEN (POS ordering): menu grid is a sticky, own-scroll LEFT column; the
   cart and the checkout sections stack in the 380px RIGHT column and flow
   with the page. */
.pos-layout--open .pos-menu-column {
    grid-column: 1;
    grid-row: span 2;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
}

.pos-layout--open .cart-panel,
.pos-layout--open .checkout-panel {
    grid-column: 2;
    position: static;
    max-height: none;
    overflow-y: visible;
}

/* CLOSED/VOIDED (review): no menu column - the cart panel takes the wide
   first column and the checkout panel the 380px second, same row; both keep
   the sticky/own-scroll defaults from their base rules below. */

/* Right-hand column of the order row: Payments history + Payment form +
   Discount/Customer/Coupon, beside (same grid row as) the Current-order
   panel. Mirrors .cart-panel's sticky + bounded-height + own-scroll setup. */
.checkout-panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
    padding-top: 1.25rem;
}

/* Bottom full-width strip replacing the product grid's slot when there's
   nothing to pick from (CLOSED/VOIDED order) - dashed outline marks it as an
   inert placeholder, not an actionable panel. Sits after .pos-layout at the
   end of the page. */
.pos-empty-menu-note {
    margin-top: 1.5rem;
    border: 1px dashed var(--text-muted);
    border-radius: var(--radius);
    background: var(--surface-2);
    text-align: center;
    color: var(--text-muted);
    padding: 1.4rem 1rem;
}

.pos-empty-menu-note-title {
    font-size: .78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    margin-bottom: .3rem;
}

/* Compact payment-history rows for the checkout panel (the old full-width
   data-table didn't fit a 380px column). */
.payment-history-row {
    border-bottom: 1px solid var(--border);
    padding: .6rem 0;
}

.payment-history-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.payment-history-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    font-size: .88rem;
}

.payment-history-tender {
    font-weight: 700;
}

.payment-history-status {
    color: var(--success);
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .03em;
}

/* The amount was the only one of the three spans with no rule, so it rendered
   at base weight with proportional digits while its siblings were styled. */
.payment-history-amount {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.payment-history-row .refund-form {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-top: .5rem;
}

.payment-history-row .refund-form input[type="number"] {
    width: 90px;
    flex: 0 0 auto;
}

.payment-history-row .refund-form input[type="text"] {
    flex: 1 1 100px;
    min-width: 0;
}

/* Manager staff code + PIN, added when the tenant requires refund authorisation.
   Fixed-basis rather than the text field's `flex: 1 1 100px`, so turning refund
   approval on does not squeeze the reason field down to its min-width. */
.payment-history-row .refund-form input[type="password"] {
    width: 90px;
    flex: 0 0 auto;
}

.checkout-panel .pos-section {
    margin: 0 1.25rem 1.25rem;
}

.checkout-panel .pos-section .form-row {
    flex-wrap: wrap;
}

.checkout-panel .pos-section .form-row .form-field {
    flex: 1 1 calc(50% - .5rem);
    min-width: 110px;
}

.cart-panel .pos-section {
    margin: 0 1.25rem 1.25rem;
}

.cart-panel .pos-section .form-row {
    flex-wrap: wrap;
}

.cart-panel .pos-section .form-row .form-field {
    flex: 1 1 calc(50% - .5rem);
    min-width: 110px;
}

.cart-panel .pos-section .form-row .form-field-full {
    flex-basis: 100%;
}

.cart-header {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

.cart-header .cart-title {
    font-weight: 800;
    font-size: 1rem;
}

.cart-header .cart-subtitle {
    font-size: .78rem;
    color: var(--text-muted);
    margin-top: .2rem;
}

.item-keypad {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

.item-keypad-display {
    width: 100%;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 1.1rem;
    padding: .5rem;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-primary);
    margin-bottom: .6rem;
}

.item-keypad-display.item-keypad-error {
    border-color: var(--danger);
    color: var(--danger);
}

.item-keypad-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .4rem;
}

.keypad-key {
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: .95rem;
    padding: .55rem 0;
    border-radius: 8px;
    cursor: pointer;
}

.keypad-key:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.keypad-clear {
    color: var(--danger);
}

.keypad-enter {
    background: linear-gradient(135deg, var(--cta-grad-1) 0%, var(--cta-grad-2) 55%, var(--cta-grad-3) 100%);
    color: var(--cta-text);
    border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 2px 8px rgba(var(--cta-glow), .25);
    grid-column: span 1;
}

.cart-columns-head {
    display: grid;
    grid-template-columns: 2.5rem 1fr 3rem 3.5rem;
    gap: .5rem;
    padding: .4rem 1.25rem 0;
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    font-weight: 700;
}

.cart-lines {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: auto;
    padding: .5rem 1.25rem;
}

.cart-line {
    display: flex;
    align-items: center;
    gap: .7rem;
    padding: .75rem 0;
    border-bottom: 1px solid var(--border);
}

.cart-line:last-child {
    border-bottom: none;
}

.cart-line-media {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    background: var(--accent-tint);
    flex-shrink: 0;
}

.cart-line-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 8px;
}

.cart-line-body {
    flex: 1;
    min-width: 0;
}

.cart-line-code {
    font-family: var(--font-mono);
    font-size: .65rem;
    color: var(--text-muted);
}

.cart-line-name {
    font-size: .85rem;
    font-weight: 700;
}

/* Modifier summary under a storefront cart line — was undefined, so it read at
   the same weight/size as the item name it belongs to. */
.modifiers-note {
    font-size: .75rem;
    color: var(--text-muted);
    margin-top: .15rem;
}

.cart-line-price {
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
}

/* Read-only quantity cell on closed/voided orders - its own flex column so the
   value lines up under the QTY header instead of dangling below the item name
   (the OPEN screen keeps the +/- stepper inside the line body instead). */
.cart-line-qty {
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: .85rem;
    width: 3rem;
    flex-shrink: 0;
    text-align: center;
}

.cart-line-total {
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: .85rem;
    min-width: 60px;
    text-align: end;
}

.qty-stepper {
    display: flex;
    align-items: center;
    gap: .4rem;
}

.qty-stepper form {
    margin: 0;
}

.qty-stepper button {
    width: 22px;
    height: 22px;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-secondary);
    font-weight: 800;
    cursor: pointer;
    line-height: 1;
}

.qty-stepper button:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.qty-stepper .qty-value {
    font-family: var(--font-mono);
    font-size: .82rem;
    font-weight: 600;
    min-width: 1.2rem;
    text-align: center;
}

.cart-remove {
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: .9rem;
    padding: .2rem;
}

.cart-remove:hover {
    color: var(--danger);
}

.cart-totals {
    padding: 1rem 1.25rem;
    border-top: 1px solid var(--border);
}

.cart-totals .summary-row {
    font-size: .82rem;
}

.cart-totals .summary-row.total {
    font-size: 1rem;
    font-weight: 800;
    border-top: 1px solid var(--border);
    margin-top: .4rem;
    padding-top: .6rem;
}

.cart-totals .summary-row span:last-child {
    font-family: var(--font-mono);
}

.cart-actions {
    padding: 1rem 1.25rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: .6rem;
}

.cart-actions .btn {
    width: 100%;
}

/* Native <dialog> elements do NOT inherit the page's colors — the UA default
   is a white background with black text. On light themes that happens to
   match, but on darker-blue the theme's near-white label/text tokens land on
   that white UA background and every field name in every popup disappears.
   Every dialog base class must therefore state its surface explicitly. */
.app-modal-dialog,
.confirm-order-dialog,
.receipt-dialog,
.payment-success-dialog,
.new-order-banner-dialog {
    background: var(--surface-2);
    color: var(--text-primary);
}

.app-modal-dialog {
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 1.8rem;
    width: 100%;
    max-width: 504px;
    max-height: 90vh;
    overflow-y: auto;
    font-size: 1.05rem;
}

/* Shared by the in-place access-denied popup (fragments/sidebar :: rail) and
   the standalone module-access-denied.html fallback page. */
.access-denied-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--danger-tint);
    color: var(--danger);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    font-weight: 800;
    margin: 0 auto 1rem;
}

.app-modal-dialog::backdrop {
    background: var(--overlay-scrim);
}

.app-modal-dialog h2 {
    margin: 0 0 1rem;
    font-size: 1.4rem;
    font-weight: 800;
}

.app-modal-dialog .form-field {
    margin-bottom: 1rem;
}

/* The default 504px dialog is too narrow for two side-by-side fields once
   one of them is a rich-date/datetime trigger showing a full label
   ("Jul 25, 2026, 5:00 PM") - stack them instead of cramming into columns. */
.app-modal-dialog .form-row {
    flex-direction: column;
    gap: 1rem;
}

.app-modal-actions {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin-top: .5rem;
}

.app-modal-actions .btn {
    width: 100%;
}

/* Wider variant for review grids (e.g. invoice-scan line review) that don't fit the default 420px form-modal width. */
.app-modal-dialog--wide {
    max-width: 720px;
    max-height: 85vh;
    overflow-y: auto;
}

.app-modal-dialog--wide .app-modal-actions {
    flex-direction: row;
    justify-content: flex-end;
}

.app-modal-dialog--wide .app-modal-actions .btn {
    width: auto;
}

.invoice-review-row {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr auto;
    gap: .5rem;
    align-items: center;
    padding: .5rem 0;
    border-bottom: 1px solid var(--border);
}

.invoice-review-row:last-of-type {
    border-bottom: none;
}

.invoice-review-header {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr auto;
    gap: .5rem;
    font-size: .75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-muted);
    padding-bottom: .4rem;
    border-bottom: 1px solid var(--border);
}

.edit-staff-danger-zone {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.edit-staff-danger-zone form {
    margin: 0;
}

.confirm-order-dialog {
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 1.5rem;
    width: 100%;
    max-width: 420px;
}

.confirm-order-dialog::backdrop {
    background: var(--overlay-scrim);
}

.confirm-order-dialog h2 {
    margin: 0 0 .3rem;
    font-size: 1.15rem;
    font-weight: 800;
}

.confirm-order-list {
    list-style: none;
    margin: 1rem 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.confirm-order-list li {
    padding: .5rem .75rem;
    background: var(--surface-1);
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: .9rem;
}

.confirm-order-actions {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.confirm-order-actions form,
.confirm-order-actions .btn {
    width: 100%;
}

.tender-quick-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .5rem;
}

.tender-quick-btn {
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-secondary);
    font-weight: 800;
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .6rem 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.tender-quick-btn:hover {
    background: var(--accent-tint);
    color: var(--accent);
    border-color: var(--accent-soft);
}

.tender-quick-btn.active {
    background: var(--accent);
    color: var(--surface-1);
    border-color: var(--accent);
}

.tender-selected-hint {
    margin: .25rem 0 0;
    font-size: .8rem;
    color: var(--text-muted);
}

.pos-action-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .5rem;
}

.pos-action-row .btn,
.pos-action-row form {
    width: 100%;
    margin: 0;
}

.pos-action-row .btn {
    text-align: center;
}

.change-due-row {
    margin: .6rem 0 0;
    font-size: .9rem;
    color: var(--text-secondary);
}

.change-due-row strong {
    font-family: var(--font-mono);
    color: var(--success, var(--accent));
}

.pos-section {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.1rem 1.25rem;
    margin-bottom: 1.25rem;
}

.offline-queue-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .65rem 1rem;
    margin-bottom: .9rem;
    background: color-mix(in srgb, var(--badge-yellow) 18%, var(--surface-1));
    color: var(--badge-yellow);
    border: 1px solid color-mix(in srgb, var(--badge-yellow) 35%, transparent);
    border-radius: var(--radius);
    font-size: .85rem;
    font-weight: 600;
}

.offline-queue-banner button {
    flex: none;
    background: none;
    border: 1px solid currentColor;
    color: inherit;
    border-radius: var(--radius-sm);
    padding: .3rem .7rem;
    font-size: .8rem;
    font-weight: 600;
    cursor: pointer;
}

.cart-line.sync-pending {
    opacity: .7;
}

.sync-pending-badge {
    display: inline-block;
    font-size: .68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: .12rem .5rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--badge-yellow) 18%, var(--surface-1));
    color: var(--badge-yellow);
    margin-left: .4rem;
}

.upi-qr-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .5rem;
    padding: 1rem;
    margin-bottom: 1rem;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-align: center;
}

.upi-qr-panel[hidden] {
    display: none;
}

.upi-qr-image {
    width: 160px;
    height: 160px;
}

.payment-submit-row {
    margin-top: .9rem;
}

.payment-submit-row .btn {
    width: 100%;
    margin: 0;
}

.receipt-dialog {
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 0;
    width: 100%;
    max-width: 460px;
    max-height: 88vh;
    overflow: hidden;
}

.receipt-dialog::backdrop {
    background: var(--overlay-scrim);
}

.receipt-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

.receipt-dialog-head h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 800;
}

.receipt-dialog-actions {
    display: flex;
    gap: .5rem;
}

.receipt-dialog-frame {
    width: 100%;
    height: 70vh;
    border: none;
    display: block;
}

.payment-success-dialog {
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 2rem 1.75rem;
    width: 100%;
    max-width: 360px;
    text-align: center;
}

.payment-success-dialog::backdrop {
    background: var(--overlay-scrim);
}

.payment-success-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto .9rem;
    border-radius: 50%;
    background: var(--success-tint);
    color: var(--success);
    font-size: 1.6rem;
    font-weight: 800;
    display: flex;
    align-items: center;
    justify-content: center;
}

.payment-success-dialog h2 {
    margin: 0 0 .4rem;
    font-size: 1.15rem;
    font-weight: 800;
    color: var(--success);
}

.payment-success-dialog p {
    margin: 0 0 1.25rem;
    color: var(--text-secondary);
    font-size: .9rem;
}

.payment-success-dialog .btn {
    width: 100%;
    background: linear-gradient(135deg, var(--success-grad-1) 0%, var(--success-grad-2) 100%);
    color: #fff;
    border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3),
                0 2px 6px rgba(21, 128, 61, .22),
                0 10px 26px rgba(21, 128, 61, .26);
}

.payment-success-dialog .btn:hover {
    filter: brightness(1.06);
}

.new-order-banner-dialog {
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 0;
    width: 100%;
    max-width: 340px;
    overflow: hidden;
}

.new-order-banner-dialog::backdrop {
    background: var(--overlay-scrim);
}

.new-order-banner-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .85rem 1.1rem;
    background: linear-gradient(135deg, var(--accent-grad-1) 0%, var(--accent-grad-2) 100%);
    color: #fff;
}

.new-order-banner-title {
    font-size: 1rem;
    font-weight: 800;
}

.new-order-banner-close {
    appearance: none;
    border: none;
    background: transparent;
    color: #fff;
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 .1rem;
}

.new-order-banner-close:hover {
    opacity: .8;
}

.new-order-banner-body {
    padding: 1rem 1.1rem 1.1rem;
}

.new-order-banner-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .4rem 0;
}

.new-order-banner-row + .new-order-banner-row {
    border-top: 1px dashed var(--border);
}

.new-order-banner-field-label {
    font-size: .78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-muted);
}

.new-order-banner-order-value,
.new-order-banner-table-value {
    font-size: .95rem;
    font-weight: 700;
    color: var(--text-primary);
}

.pos-section-title {
    font-size: .78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    margin: 0 0 .9rem;
}

/* Order feedback capture sits in a full-width .pos-section, but a rating select
   and a short comment box read badly stretched across the whole page — cap the
   form so the controls keep a form-like measure. */
.feedback-form {
    max-width: 520px;
}

.pos-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.pos-section-header .pos-section-title {
    margin: 0 0 .9rem;
}

.customer-clear-btn {
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-secondary);
    width: 1.6rem;
    height: 1.6rem;
    line-height: 1;
    border-radius: 50%;
    cursor: pointer;
    font-size: .95rem;
    margin-bottom: .5rem;
}

.customer-clear-btn:hover {
    background: var(--danger-tint);
    color: var(--danger);
    border-color: var(--danger);
}

.customer-search-field {
    position: relative;
    margin-bottom: .75rem;
}

.customer-search-field input {
    width: 100%;
}

.customer-search-results {
    position: absolute;
    top: calc(100% + .3rem);
    left: 0;
    right: 0;
    z-index: 20;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    max-height: 220px;
    overflow-y: auto;
}

.customer-search-result {
    display: flex;
    flex-direction: column;
    width: 100%;
    text-align: start;
    padding: .5rem .7rem;
    border: none;
    border-bottom: 1px solid var(--border);
    background: none;
    cursor: pointer;
    font-family: inherit;
}

.customer-search-result:last-child {
    border-bottom: none;
}

.customer-search-result:hover {
    background: var(--accent-tint);
}

.customer-search-result-name {
    font-weight: 700;
    font-size: .85rem;
    color: var(--text-primary);
}

.customer-search-result-phone {
    font-size: .75rem;
    color: var(--text-muted);
}

.customer-search-empty {
    padding: .6rem .7rem;
    font-size: .8rem;
    color: var(--text-muted);
}

/* ---------- Themed suggestion dropdown (search-suggest.js) ----------
   Replaces native <datalist> popups, which the BROWSER draws: they follow the
   OS dark/light setting, ignore all four app themes, and cannot be styled at
   all — on /preparation/recipes that was a dark grey panel over an amber page.
   Same visual family as .customer-search-results above, token-only so every
   theme inherits with zero per-theme rules (per the CLAUDE.md token rule). */

.search-suggest {
    position: relative;
    display: block;
}

/* For inline control rows (the recipe ingredient editor) where a block
   wrapper would break the select+input+select line. */
.search-suggest--inline {
    display: inline-block;
    vertical-align: middle;
    max-width: 100%;
}

.search-suggest-menu {
    position: absolute;
    top: calc(100% + .3rem);
    left: 0;
    right: 0;
    z-index: 20;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    max-height: 240px;
    overflow-y: auto;
}

.search-suggest-item {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .8rem;
    width: 100%;
    text-align: start;
    padding: .5rem .8rem;
    border: none;
    border-bottom: 1px solid var(--border);
    background: none;
    cursor: pointer;
    font-family: inherit;
    font-size: .85rem;
    color: var(--text-primary);
}

.search-suggest-item:last-child {
    border-bottom: none;
}

/* Load-bearing, not belt-and-braces: the display:flex above OUTRANKS the UA
   stylesheet's [hidden] { display: none } (equal specificity, later source
   order), so without this rule filtered-out items kept rendering while their
   hidden attribute said otherwise — caught on the live page, invisible to
   jsdom, which computes no styles. */
.search-suggest-menu[hidden],
.search-suggest-item[hidden] {
    display: none;
}

.search-suggest-item:hover,
.search-suggest-item.is-active {
    background: var(--accent-tint);
}

.search-suggest-label {
    /* Also the JS hook search-suggest.js rebuilds the <mark> emphasis into. */
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.search-suggest-item mark {
    background: none;
    color: var(--accent);
    font-weight: 700;
}

.search-suggest-code {
    font-size: .75rem;
    color: var(--text-muted);
    white-space: nowrap;
}

/* ---------- Floor/table visual grid (floor-tables.html) ---------- */

.table-search-row {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    flex-wrap: wrap;
    margin-bottom: .85rem;
}

.table-search {
    flex: 0 0 300px;
    margin-bottom: 0;
}

.section-pills {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: 1.25rem;
}

.section-pills .category-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
}

.section-pill-count {
    background: rgba(0, 0, 0, .12);
    color: inherit;
    font-family: var(--font-mono);
    font-size: .68rem;
    font-weight: 700;
    padding: .05rem .4rem;
    border-radius: 999px;
}

.category-chip.active .section-pill-count {
    background: rgba(255, 255, 255, .25);
}

.stats-strip {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .75rem;
    margin-bottom: 1.25rem;
}

.stat-tile {
    position: relative;
    background: var(--surface-2);
    background-image: var(--card-sheen);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: .75rem 1rem;
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-sm);
    display: flex;
    flex-direction: column;
    gap: .2rem;
}

.stat-tile-icon {
    position: absolute;
    top: .7rem;
    right: .75rem;
    font-size: .9rem;
    line-height: 1;
    opacity: .85;
}

.stat-tile-label {
    font-size: .66rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    font-weight: 700;
    padding-inline-end: 1rem;
}

.stat-tile-value {
    font-family: var(--font-mono);
    font-size: 1.2rem;
    font-weight: 700;
}

.stat-tile-subtitle {
    font-size: .68rem;
    color: var(--text-muted);
}

.floor-layout {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 1.5rem;
    align-items: start;
}

/* Independent scroll regions - without these, a wheel event over the floor
   plan or the table-details panel scrolls the whole page instead of just
   that column, same fix as the POS screen's .pos-menu-column / .cart-panel
   pair. */
.floor-grid-col, .table-details-panel {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
}

@media (max-width: 1080px) {
    .floor-layout {
        grid-template-columns: 1fr;
    }

    .floor-grid-col, .table-details-panel {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

.table-details-panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.25rem;
}

.table-details-empty {
    color: var(--text-muted);
    font-size: .85rem;
}

.table-details-body h2 {
    font-family: var(--font-display);
    font-weight: 400;
    margin: 0 0 .75rem;
}

.table-details-row {
    display: flex;
    justify-content: space-between;
    padding: .4rem 0;
    border-bottom: 1px solid var(--border);
    font-size: .85rem;
}

.table-details-row span:first-child {
    color: var(--text-muted);
    font-weight: 700;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.table-details-body .btn {
    margin-top: 1rem;
    width: 100%;
}

/* Edit + Delete share one row at equal widths — the delete button lives inside
   a form, so the form (not the button) is the flex item that splits the row. */
.table-details-body .row-actions {
    flex-wrap: nowrap;
}

.table-details-body .row-actions .btn,
.table-details-body .row-actions form.inline {
    flex: 1 1 0;
    min-width: 0;
    margin-top: 0;
}

.table-details-body .row-actions form.inline .btn {
    width: 100%;
    margin-top: 0;
}

.table-details-section-label {
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 800;
    color: var(--text-muted);
    margin-top: 1.1rem;
    display: block;
}

.td-action-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .5rem;
    margin-top: .5rem;
}

.td-action-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .3rem;
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-secondary);
    font-weight: 700;
    font-size: .75rem;
    padding: .7rem .5rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
    width: 100%;
    list-style: none;
}

.td-action-btn::-webkit-details-marker {
    display: none;
}

.td-action-btn:hover {
    background: var(--accent-tint);
    color: var(--accent);
    border-color: var(--accent-soft);
}

.td-action-btn.primary {
    background: var(--cta-bg);
    color: var(--cta-text);
    border-color: var(--cta-bg);
}

.td-action-icon {
    font-size: 1.1rem;
    line-height: 1;
}

.td-action-popover {
    position: relative;
}

.td-action-popover[open] > .td-action-btn {
    background: var(--accent-tint);
    color: var(--accent);
    border-color: var(--accent-soft);
}

.td-popover-panel {
    position: absolute;
    top: calc(100% + .4rem);
    left: 0;
    right: 0;
    z-index: 20;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: .9rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.td-popover-panel select {
    width: 100%;
    padding: .5rem .6rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-1);
    font-family: inherit;
    font-size: .82rem;
    color: var(--text-primary);
}

.td-popover-panel .table-details-section-label {
    margin-top: 0;
}

.td-popover-panel .btn {
    margin-top: 0;
}

.party-size-row {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: .3rem;
    margin-top: .4rem;
}

.party-size-btn {
    border: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-secondary);
    font-weight: 700;
    padding: .4rem 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.party-size-btn.active {
    background: var(--cta-bg);
    color: var(--cta-text);
    border-color: var(--cta-bg);
}

#tdRunningActions select {
    width: 100%;
    margin-top: .3rem;
    margin-bottom: .5rem;
    padding: .5rem .6rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-1);
    font-family: inherit;
    font-size: .82rem;
    color: var(--text-primary);
}

.split-lines-list {
    display: flex;
    flex-direction: column;
    gap: .3rem;
    margin: .4rem 0;
    font-size: .8rem;
}

.split-lines-list label {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-weight: 400;
}

.split-lines-list input[type="checkbox"] {
    width: auto;
}

.split-lines-empty {
    color: var(--text-muted);
    font-size: .78rem;
    margin: 0;
}

.table-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    margin-bottom: 1.5rem;
}

.table-legend-item {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .82rem;
    color: var(--text-secondary);
}

.table-legend-swatch {
    width: 14px;
    height: 14px;
    border-radius: 4px;
}

.table-section {
    margin-bottom: 1.75rem;
}

.table-section-name {
    font-size: .78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    margin: 0 0 .75rem;
}

.table-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 1.2rem;
}

.table-tile {
    position: relative;
    border: 2px solid var(--border);
    border-radius: var(--radius);
    /* Top padding is deliberately taller than the side/bottom padding (not just
       a uniform 1.2x scale) - it's the only thing that keeps .table-tile-label
       clear of .table-tile-status, which floats absolutely in the top-right
       corner rather than sitting in normal flow. */
    padding: 1.9rem 1rem 1.2rem;
    text-align: center;
    background: var(--surface-2);
    background-image: var(--card-sheen);
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-sm);
    cursor: pointer;
    transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease;
}

.table-tile:hover {
    transform: translateY(-2px);
    box-shadow: inset 0 1px 0 var(--card-sheen-line),
                0 10px 24px rgba(var(--cta-glow), .16);
}

.table-tile.selected {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.table-tile-dots {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: .24rem;
    margin: .48rem 0;
}

.table-tile-dot {
    width: 8.4px;
    height: 8.4px;
    border-radius: 999px;
    background: currentColor;
    opacity: .55;
}

.table-tile-amount {
    font-family: var(--font-mono);
    font-size: .94rem;
    font-weight: 700;
}

.table-tile-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: .36rem;
    gap: .48rem;
}

.table-tile-elapsed {
    font-size: .82rem;
    color: var(--text-muted);
    white-space: nowrap;
}

.table-tile-label {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 1.38rem;
    letter-spacing: .02em;
}

.table-tile-capacity {
    font-size: .84rem;
    color: var(--text-muted);
    margin-top: .24rem;
}

.table-tile-status {
    position: absolute;
    top: .72rem;
    right: .72rem;
    background: var(--surface-2);
    font-size: .7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .24rem .6rem;
    border-radius: 999px;
    box-shadow: var(--shadow-sm);
}

.table-tile-icons {
    display: flex;
    justify-content: center;
    gap: .48rem;
    margin-top: .72rem;
}

.table-tile-icons .icon-btn {
    width: 34px;
    height: 34px;
    background: var(--surface-1);
    border-color: var(--border);
    color: var(--text-secondary);
}

.table-tile-icons .icon-btn svg {
    width: 17px;
    height: 17px;
}

.table-tile.status-AVAILABLE { background: var(--success-tint); border-color: var(--success); color: var(--success); }
.table-tile.status-AVAILABLE .table-tile-status { color: var(--success); }

.table-tile.status-SEATED,
.table-tile.status-ORDERED { background: var(--accent-tint); border-color: var(--accent-soft); color: var(--accent); }
.table-tile.status-SEATED .table-tile-status,
.table-tile.status-ORDERED .table-tile-status { color: var(--accent); }

.table-tile.status-CHECK_PRESENTED { background: var(--tile-check-bg); border-color: var(--tile-check-border); color: var(--tile-check-fg); }
.table-tile.status-CHECK_PRESENTED .table-tile-status { color: var(--tile-check-fg); }

.table-tile.status-NEEDS_CLEANING { background: var(--danger-tint); border-color: var(--danger); color: var(--danger); }
.table-tile.status-NEEDS_CLEANING .table-tile-status { color: var(--danger); }

/* Held for a booking — a violet of its own so "reserved" never reads as
   available-green or occupied-amber at a glance across the floor. Tokenised
   2026-08-12: the old literal pastels ("both themes want the same pair")
   predate darker-blue, where #ede9fe glowed like a white sticker on navy. */
.table-tile.status-RESERVED { background: var(--tile-reserved-bg); border-color: var(--tile-reserved-border); color: var(--tile-reserved-fg); }
.table-tile.status-RESERVED .table-tile-status { color: var(--tile-reserved-fg); }

/* The upcoming-booking line, shown on any table with a sitting due — including
   one that is still AVAILABLE, hours before its hold window opens. */
.table-tile-reservation {
    margin-top: .3rem;
    font-size: .74rem;
    font-weight: 600;
    color: #6d28d9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.table-tile form {
    margin: .5rem 0 0;
}

/* ---------- KDS board (kds-tickets.html) ---------- */

.kds-layout {
    display: grid;
    grid-template-columns: 1fr 360px;
    gap: 1.5rem;
    align-items: start;
}

@media (max-width: 1080px) {
    .kds-layout {
        grid-template-columns: 1fr;
    }
}

.kds-stats {
    margin-bottom: 1.25rem;
}

.kds-stats .value {
    font-size: 1.5rem;
    font-weight: 800;
}

/* Hero stat tile (reports KPI rows) — the one headline figure, larger than
   its neighbours; five unrelated measures share the row, so hierarchy comes
   from size, not from forcing them onto one axis. */
.kds-stats .stat-hero .value {
    font-size: 2.05rem;
}

.kds-stats .sub {
    font-size: .72rem;
    color: var(--text-muted);
    margin-top: .25rem;
}

.kds-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem;
    margin-bottom: 1.25rem;
}

.kds-toolbar .btn[aria-disabled="true"],
.page-head .btn[aria-disabled="true"] {
    opacity: .5;
    cursor: not-allowed;
    pointer-events: none;
}

.kds-toolbar .btn:disabled,
.page-head .btn:disabled {
    opacity: .5;
    cursor: not-allowed;
}

#soundAlertsToggle.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.kds-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem 1rem;
    margin-bottom: 1.25rem;
}

.kds-filter-group {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
}

.kds-single-station-label {
    font-size: .76rem;
    font-weight: 700;
    color: var(--text-muted);
    padding: .35rem .8rem;
    border: 1px dashed var(--border);
    border-radius: 999px;
}

.kds-chip {
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: .76rem;
    font-weight: 700;
    padding: .35rem .8rem;
    border-radius: 999px;
    cursor: pointer;
}

.kds-chip:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.kds-chip.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.kds-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 1rem;
}

.kot-card {
    border-inline-start: 4px solid var(--accent-soft);
    cursor: pointer;
}

.kot-card.status-READY { border-left-color: var(--success); }
.kot-card.status-SERVED { border-left-color: var(--text-muted); opacity: .7; }
.kot-card.recalled { border-left-color: var(--danger); }

/* Themed via --kot-online-bg: previously only darker-blue had an override, so
   "dark" and light-pleasant both showed the amber-era light blue. */
.kot-card.channel-online {
    background: var(--kot-online-bg);
}

.kot-card.selected {
    box-shadow: var(--shadow-md);
    border-color: var(--accent-soft);
}

.kot-card-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: .3rem;
}

.kot-card-title {
    font-weight: 800;
    font-size: .95rem;
}

.kot-timer {
    font-family: var(--font-mono);
    font-size: .78rem;
    font-weight: 700;
    padding: .1rem .5rem;
    border-radius: 999px;
    background: var(--accent-tint);
    color: var(--accent);
    white-space: nowrap;
}

.kot-timer.stale {
    background: var(--danger-tint);
    color: var(--danger);
}

/* Brief attention-grabbing pulse the instant a ticket first crosses its SLA (dev-plan item 8). */
.kot-card.overrun-pulse {
    animation: kot-overrun-pulse 1.2s ease-out 3;
}

@keyframes kot-overrun-pulse {
    0%, 100% { box-shadow: none; }
    50% { box-shadow: 0 0 0 3px var(--danger); }
}

.kot-card-meta {
    font-size: .75rem;
    color: var(--text-muted);
    margin-bottom: .55rem;
}

.kot-status-pill {
    display: inline-block;
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 700;
    padding: .12rem .55rem;
    border-radius: 999px;
    margin-bottom: .6rem;
    background: var(--accent-tint);
    color: var(--accent);
}

.kot-status-pill.status-READY { background: var(--success-tint); color: var(--success); }
.kot-status-pill.status-SERVED { background: var(--border); color: var(--text-muted); }
.kot-status-pill.status-RECALLED { background: var(--danger-tint); color: var(--danger); }

.kot-lines {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    margin-bottom: .7rem;
}

.kot-line {
    display: flex;
    flex-direction: column;
    gap: .3rem;
}

/* Checkbox + name + station badge, top-aligned rather than centered: with
   align-items:center a wrapped two-line item name left the badge floating
   mid-way down the row, reading as if it belonged to the wrap rather than
   the item. flex-start pins it level with the name's first line instead. */
.kot-line-head {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    font-size: .82rem;
}

.kot-line-check {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    border: 1.5px solid var(--border);
    background: var(--surface-1);
    cursor: pointer;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.kot-line-check.checked {
    background: var(--success);
    border-color: var(--success);
    color: #fff;
    font-size: .7rem;
    font-weight: 800;
}

.kot-line-name {
    flex: 1;
}

.kot-line.done .kot-line-name {
    text-decoration: line-through;
    color: var(--text-muted);
}

.kot-line-station {
    font-size: .65rem;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--text-muted);
    background: var(--surface-1);
    border: 1px solid var(--border);
    padding: .05rem .4rem;
    border-radius: 999px;
    white-space: nowrap;
}

/* Per-line modifiers and status in the KDS ticket detail. All three classes
   were referenced by kds-tickets.html but defined nowhere, so the modifier
   chips rendered as an unseparated run of plain text. Chip styling mirrors
   .kot-line-station above. */
.ticket-modifiers {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem;
    margin: .3rem 0;
}

/* On the compact tile only (not the detail panel, which has no checkbox to
   align past): indent modifiers under the item name rather than the
   checkbox, so they read as belonging to the name above them. */
.kot-line .ticket-modifiers {
    margin: 0 0 0 calc(18px + .5rem);
}

.mod-chip {
    font-size: .65rem;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--surface-1);
    border: 1px solid var(--border);
    padding: .05rem .4rem;
    border-radius: 999px;
    white-space: nowrap;
}

/* The customer's own note on a kitchen ticket. Deliberately not a .mod-chip:
   chips read as a list of equal-weight options, and this is the line an allergy
   arrives on. Full contrast, its own row, bold. */
.ticket-special-request {
    font-size: .72rem;
    font-weight: 700;
    color: var(--danger);
    background: var(--danger-tint);
    border-radius: var(--radius-sm);
    padding: .2rem .4rem;
    margin: .3rem 0;
}

.ticket-status {
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-muted);
    margin-bottom: .35rem;
}

/* ---------- KDS detail panel ---------- */

.kot-panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.25rem;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
}

/* Board/list column - independent scroll region so a wheel event over the
   ticket board (KDS) or item list (Inventory) doesn't scroll the whole page,
   same fix as the POS screen's .pos-menu-column / .cart-panel pair. */
.kds-board-column {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
}

@media (max-width: 1080px) {
    .kds-board-column, .kot-panel {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

.kot-panel-title {
    font-weight: 800;
    font-size: 1.05rem;
    margin-bottom: .2rem;
}

.kot-panel-subtitle {
    font-size: .78rem;
    color: var(--text-muted);
    margin-bottom: 1.1rem;
}

.kot-panel-station-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: .55rem 0;
    border-bottom: 1px solid var(--border);
    font-size: .85rem;
}

.kot-panel-station-row .count {
    font-family: var(--font-mono);
    font-weight: 700;
    color: var(--text-muted);
}

.kot-panel-note {
    background: var(--accent-tint);
    border-radius: var(--radius-sm);
    padding: .9rem 1rem;
    margin-top: 1.1rem;
    font-size: .8rem;
}

.kot-panel-note ul {
    margin: .5rem 0 0;
    padding-inline-start: 1.1rem;
}

.kot-panel-note li { margin-bottom: .3rem; }

.kot-panel-alert {
    background: var(--danger-tint);
    border-radius: var(--radius-sm);
    padding: .9rem 1rem;
    margin-top: 1.1rem;
    font-size: .8rem;
    color: var(--danger);
}

.kot-panel-alert .action-link { margin-top: .6rem; display: inline-block; }

.kot-detail { display: none; }
.kot-detail.open { display: block; }

.kot-stage-stepper {
    display: flex;
    justify-content: space-between;
    margin: 1rem 0 1.2rem;
}

.kot-stage {
    flex: 1;
    text-align: center;
    font-size: .68rem;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--text-muted);
    position: relative;
    padding-top: 1.4rem;
}

.kot-stage::before {
    content: "";
    position: absolute;
    top: .2rem;
    left: 50%;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--border);
    transform: translateX(-50%);
}

.kot-stage.done::before { background: var(--success); }
.kot-stage.current { color: var(--accent); }
.kot-stage.current::before { background: var(--accent); }

.kot-detail-line {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .7rem .8rem;
    margin-bottom: .6rem;
}

.kot-detail-line-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: .3rem;
}

.kot-detail-actions {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin-top: 1rem;
}

.kot-detail-actions .btn { width: 100%; }

/* ---------- Low-stock banner (inventory-items.html) ---------- */

.low-stock-banner {
    background: var(--accent-tint);
    color: var(--text-primary);
    border: 1px solid var(--accent-soft);
    border-radius: var(--radius);
    padding: .8rem 1.1rem;
    margin-bottom: 1.25rem;
    display: flex;
    align-items: center;
    gap: .6rem;
    font-size: .88rem;
    font-weight: 600;
}

/* ---------- Refund inline form (order-detail.html payments history) ---------- */

.refund-form {
    display: flex;
    align-items: center;
    gap: .35rem;
    flex-wrap: wrap;
}

.refund-form input[type="number"] {
    width: 90px;
    padding: .3rem .5rem;
    font-size: .78rem;
}

.refund-form input[type="text"] {
    width: 120px;
    padding: .3rem .5rem;
    font-size: .78rem;
}

/* The manager-PIN field the POS refund authorisation adds (POS Feature Plan §3.3).
   This row selects controls by input TYPE rather than by a container class, so a
   password input matched no rule at all and rendered as a raw browser widget beside
   its styled siblings — the exact failure the UI Consistency Plan was written for. */
.refund-form input[type="password"] {
    width: 90px;
    padding: .3rem .5rem;
    font-size: .78rem;
}

/* ---------- Generic toolbar row (reused by Staff & Rota, mirrors .kds-toolbar) ---------- */

.toolbar-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem;
    margin-bottom: 1.25rem;
}

/* ---------- Staff & Rota (staffops-roster.html) ---------- */

.staff-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 1.25rem;
    align-items: start;
}

.staff-board {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 1rem;
}

.staff-card {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
}

.staff-card.status-late {
    border-color: var(--danger);
}

.staff-avatar {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--accent-tint);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: .85rem;
}

.staff-card-body {
    min-width: 0;
    flex: 1;
}

.staff-card-name {
    font-weight: 700;
    font-size: .95rem;
}

.staff-card-role {
    font-size: .78rem;
    color: var(--text-muted);
}

.staff-card-meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: .5rem;
    font-size: .78rem;
    color: var(--text-muted);
}

.staff-card-sales {
    margin-top: .35rem;
    font-size: .76rem;
    color: var(--text-secondary);
}

.staff-card-sales strong {
    font-family: var(--font-mono);
    color: var(--text-primary);
}

.staff-status-pill {
    display: inline-block;
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .2rem .55rem;
    border-radius: 999px;
    background: var(--border);
    color: var(--text-muted);
}

.staff-status-pill.clockedIn { background: var(--success-tint); color: var(--success); }
.staff-status-pill.late { background: var(--danger-tint); color: var(--danger); }
.staff-status-pill.upcoming { background: var(--accent-tint); color: var(--accent); }

.rota-grid-wrap {
    overflow-x: auto;
}

.rota-grid {
    width: 100%;
    border-collapse: collapse;
}

.rota-grid th, .rota-grid td {
    padding: .5rem .6rem;
    text-align: center;
    border-bottom: 1px solid var(--border);
    font-size: .82rem;
}

.rota-grid th:first-child, .rota-grid td:first-child {
    text-align: start;
    font-weight: 700;
    white-space: nowrap;
}

.rota-tag {
    display: inline-block;
    width: 100%;
    padding: .3rem 0;
    border-radius: var(--radius-sm);
    font-size: .72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
}

.rota-tag.rota-day { background: var(--accent-tint); color: var(--accent); }
.rota-tag.rota-night { background: var(--rota-night-bg); color: var(--rota-night-fg); }
.rota-tag.rota-off { background: var(--border); color: var(--text-muted); }

.my-status-panel {
    display: flex;
    flex-direction: column;
    gap: .9rem;
}

.my-status-panel .form-actions-stack {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

/* Independent scroll regions - without these, a wheel event over the staff
   board/rota or the shift-status panel scrolls the whole page instead of
   just that column, same fix as the POS screen's .pos-menu-column /
   .cart-panel pair. */
.staff-list-column, .my-status-panel {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: auto;
}

@media (max-width: 900px) {
    .staff-layout {
        grid-template-columns: 1fr;
    }

    .staff-list-column, .my-status-panel {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

/* Inventory (inventory-items.html) — on-hand/par stock gauge + stock-health pill. */
.stock-bar {
    width: 100%;
    min-width: 90px;
    height: 6px;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
    margin-top: .35rem;
}

.stock-bar-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--success);
}

.stock-bar-fill.low { background: var(--accent); }
.stock-bar-fill.reorder { background: var(--danger); }

.stock-bar-label {
    font-size: .78rem;
    font-weight: 700;
    white-space: nowrap;
}

.inventory-status-pill {
    display: inline-block;
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .03em;
    padding: .2rem .55rem;
    border-radius: 999px;
    background: var(--border);
    color: var(--text-muted);
}

.inventory-status-pill.healthy { background: var(--success-tint); color: var(--success); }
.inventory-status-pill.low { background: var(--accent-tint); color: var(--accent); }
.inventory-status-pill.reorder { background: var(--danger-tint); color: var(--danger); }

.item-expiry-tag {
    font-size: .7rem;
    color: var(--text-muted);
}

/* ---------- Reporting (reports.html) — stacked mix bars, trend sparkline, insights list ---------- */
/* Stacked part-to-whole bar (channel + payment mix) — one track, segments
   colored per entity via the --chart-N slots, 2px surface gaps so adjacent
   fills never touch. Three palette hues sit below 3:1 contrast on the light
   surfaces, so the legend beside it always carries labels and exact values —
   the bar is never the only encoding. */
.stacked-bar {
    display: flex;
    width: 100%;
    height: 22px;
    gap: 2px;
    margin-bottom: .8rem;
}

.stacked-bar-seg {
    min-width: 3px;
    border-radius: 3px;
}

.stacked-bar-seg:first-child { border-start-start-radius: 6px; border-end-start-radius: 6px; }
.stacked-bar-seg:last-child { border-start-end-radius: 6px; border-end-end-radius: 6px; }

.channel-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    flex: 1 1 220px;
}

.channel-legend li {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .85rem;
}

.channel-dot {
    width: .65rem;
    height: .65rem;
    border-radius: 50%;
    flex: 0 0 auto;
}

.channel-legend-label {
    font-weight: 700;
    flex: 1 1 auto;
}

.channel-legend-value {
    color: var(--text-muted);
    font-size: .78rem;
    white-space: nowrap;
}

.trend-chart {
    width: 100%;
    height: auto;
    /* Matches the SVG viewBox (600x180) so text labels render undistorted —
       the old preserveAspectRatio="none" stretched glyphs with the card. */
    aspect-ratio: 600 / 180;
}

/* Selective direct labels: the peak and the endpoint only, in ink (text
   tokens), never the series color, and never a value on every point. */
.trend-chart text {
    fill: var(--text-secondary);
    font-size: 11px;
    font-weight: 700;
    font-family: inherit;
}

.trend-chart .trend-axis-label {
    fill: var(--text-muted);
    font-weight: 600;
}

.trend-chart polyline {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.trend-chart circle {
    fill: var(--accent);
}

.insights-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .55rem;
}

.insights-list li {
    font-size: .88rem;
    padding-inline-start: 1.1rem;
    position: relative;
}

.insights-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: .5em;
    width: .4rem;
    height: .4rem;
    border-radius: 50%;
    background: var(--accent);
}

/* ---------- Reporting (reports.html) — tabs, hourly footfall, ranked bar lists, checklist ---------- */
.report-tabs {
    display: flex;
    gap: .4rem;
    flex-wrap: wrap;
    margin-bottom: 1.1rem;
}

.report-tab {
    padding: .5rem 1rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-muted);
    font-weight: 700;
    font-size: .82rem;
    text-decoration: none;
    white-space: nowrap;
}

.report-tab.active {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

.report-tab-export {
    margin-inline-start: auto;
}

.footfall-chart {
    display: flex;
    align-items: flex-end;
    gap: .25rem;
    height: 120px;
}

.footfall-bar {
    flex: 1;
    background: var(--border);
    border-radius: 3px 3px 0 0;
    position: relative;
    min-height: 2px;
}

.footfall-bar.has-data {
    background: var(--accent);
}

.footfall-bar:hover .footfall-tooltip {
    display: block;
}

.footfall-tooltip {
    display: none;
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-bottom: .3rem;
    /* Was var(--text) — a variable that exists nowhere, so the tooltip had
       rendered with a transparent background since it shipped. */
    background: var(--text-primary);
    color: var(--surface-1);
    font-size: .68rem;
    font-weight: 700;
    padding: .15rem .4rem;
    border-radius: 4px;
    white-space: nowrap;
    pointer-events: none;
}

/* The one always-visible footfall label — the peak hour's count. Everything
   else stays hover-only; a number on every column would be noise. */
.footfall-peak-label {
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-bottom: .2rem;
    font-size: .66rem;
    font-weight: 800;
    color: var(--text-secondary);
    white-space: nowrap;
    pointer-events: none;
}

.footfall-labels {
    display: flex;
    gap: .25rem;
    margin-top: .4rem;
}

.footfall-labels span {
    flex: 1;
    text-align: center;
    font-size: .62rem;
    color: var(--text-muted);
}

.ranked-bar-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .75rem;
}

.ranked-bar-head {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    font-size: .85rem;
    margin-bottom: .3rem;
}

.ranked-bar-head .name {
    font-weight: 700;
}

.ranked-bar-head .value {
    color: var(--text-muted);
    font-size: .78rem;
    white-space: nowrap;
}

.checklist-status {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .6rem;
}

.checklist-status li {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .85rem;
}

.checklist-dot {
    width: .6rem;
    height: .6rem;
    border-radius: 50%;
    flex: 0 0 auto;
}

.checklist-dot.ok {
    background: var(--success);
}

.checklist-dot.pending {
    background: var(--danger);
}

/* ---------- Online Orders board (online-orders.html) ---------- */

.online-orders-channels {
    margin-bottom: 1.25rem;
}

.online-orders-channel-card {
    border-top: 3px solid transparent;
}

.online-orders-channel-card.channel-SWIGGY { border-top-color: #c2440c; }
.online-orders-channel-card.channel-ZOMATO { border-top-color: #b3261e; }
.online-orders-channel-card.channel-DIRECT { border-top-color: var(--accent); }
.online-orders-channel-card.channel-WHATSAPP { border-top-color: #15803d; }
.online-orders-channel-card.channel-ONDC { border-top-color: #3366aa; }

.online-orders-channel-card .value {
    font-size: 1.5rem;
    font-weight: 800;
}

.online-orders-channel-card .sub {
    font-size: .72rem;
    color: var(--text-muted);
    margin-top: .25rem;
}

.online-orders-board-column {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-x: auto;
    overscroll-behavior: auto;
}

@media (max-width: 1080px) {
    .online-orders-board-column {
        /* Copied from .kds-board-column's overflow-y rule without adjusting for
           the axis switch — overflow-x: visible let the READY/PICKED/DELIVERED
           columns spill past the viewport edge with no scrollbar to reach them,
           since (unlike KDS's single vertical ticket list) the page itself was
           never meant to scroll horizontally. Keep the horizontal scroll region
           active at every width instead. */
        position: static;
        max-height: none;
        overflow-x: auto;
    }
}

.online-orders-board {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
}

.online-orders-column {
    flex: 0 0 220px;
    display: flex;
    flex-direction: column;
    gap: .7rem;
}

.online-orders-column-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: .78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    padding: 0 .1rem;
}

.online-orders-column-head .count {
    font-family: var(--font-mono);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0 .5rem;
}

.online-orders-column-body {
    display: flex;
    flex-direction: column;
    gap: .7rem;
}

.online-orders-card {
    position: relative;
    min-height: 8.5rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    border-inline-start: 4px solid var(--accent-soft);
    cursor: pointer;
}

.online-orders-card.status-READY { border-left-color: var(--success); }
.online-orders-card.status-PICKED { border-left-color: var(--accent); }
.online-orders-card.status-DELIVERED { border-left-color: var(--text-muted); opacity: .75; }
.online-orders-card.selected { box-shadow: var(--shadow-md); border-color: var(--accent-soft); }

/* Rider-assignment form inside the READY-order detail panel. */
.online-orders-assign-rider {
    margin-top: .6rem;
}

.online-orders-card-link {
    position: absolute;
    inset: 0;
}

.online-orders-card-foot {
    display: flex;
    justify-content: space-between;
    font-size: .78rem;
    color: var(--text-muted);
    margin-top: .5rem;
}

/* Every channel hue resolves through the --channel-* tokens defined per theme,
   so all four themes are covered by construction. DIRECT stays on the accent
   pair — it is the tenant's own channel and deliberately tracks the theme. */
.badge-channel-SWIGGY { background: var(--channel-swiggy-bg); color: var(--channel-swiggy-fg); }
.badge-channel-ZOMATO { background: var(--channel-zomato-bg); color: var(--channel-zomato-fg); }
.badge-channel-DIRECT { background: var(--accent-tint); color: var(--accent); }
.badge-channel-WHATSAPP { background: var(--channel-whatsapp-bg); color: var(--channel-whatsapp-fg); }
.badge-channel-ONDC { background: var(--channel-ondc-bg); color: var(--channel-ondc-fg); }
.badge-channel-URBANPIPER { background: var(--channel-urbanpiper-bg); color: var(--channel-urbanpiper-fg); }

.online-orders-detail-fact-list {
    margin: 1rem 0;
}

.online-orders-detail-fact {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: .5rem;
    padding: .4rem 0;
    border-bottom: 1px solid var(--border);
    font-size: .84rem;
}

.online-orders-detail-fact:last-child { border-bottom: none; }

.online-orders-detail-items {
    margin: .6rem 0 1rem;
}

.online-orders-detail-item {
    display: flex;
    justify-content: space-between;
    font-size: .84rem;
    padding: .3rem 0;
}

.online-orders-payout {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .8rem 1rem;
    margin-bottom: 1rem;
}

.online-orders-payout-row {
    display: flex;
    justify-content: space-between;
    font-size: .84rem;
    padding: .25rem 0;
}

.online-orders-checklist {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin-bottom: 1rem;
}

.online-orders-checklist-confirm {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .84rem;
    cursor: pointer;
}

.online-orders-decision-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .5rem;
}

.online-orders-decision-actions form {
    display: flex;
    gap: .4rem;
    align-items: center;
}

.online-orders-decision-actions input[type="text"] {
    width: 140px;
}

.online-orders-contact-mask {
    margin-bottom: 1rem;
}

.online-orders-payout-row.negative { color: var(--danger); }

.online-orders-payout-row.total {
    font-weight: 800;
    font-size: .92rem;
    border-top: 1px solid var(--border);
    margin-top: .3rem;
    padding-top: .5rem;
}

/* ---------- Rich date-picker (js/date-picker.js) ----------
   Wraps every native <input type="date"> in a colorful custom calendar
   dropdown - the native input still holds the real value/name (kept in the
   DOM, just visually hidden) so form submission, th:field binding, and
   server-side parsing are all untouched; only the on-page presentation
   changes. Weekend/today/selected use distinct hues from the dashboard
   tile badge palette (--badge-*) rather than one flat accent, for genuine
   "rich" color rather than a single-tint restyle. */
.rich-date-wrap {
    position: relative;
    display: inline-flex;
}

.rich-date-native {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
    border: 0;
    padding: 0;
}

.rich-date-trigger {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .45rem .75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-primary);
    font-family: inherit;
    font-size: .85rem;
    font-weight: 600;
    cursor: pointer;
}

.rich-date-trigger::before {
    content: "📅";
    font-size: .9em;
}

.rich-date-trigger--empty {
    color: var(--text-muted);
    font-weight: 500;
}

.rich-date-trigger:hover {
    border-color: var(--accent-soft);
    background: var(--accent-tint);
}

.rich-date-panel {
    position: absolute;
    top: calc(100% + .4rem);
    left: 0;
    z-index: 50;
    width: 266px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    overflow: hidden;
}

.rich-date-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .6rem .75rem;
    background: linear-gradient(135deg, var(--accent), var(--accent-soft));
    color: #fff;
}

.rich-date-month-label {
    font-weight: 800;
    font-size: .85rem;
    letter-spacing: .01em;
}

.rich-date-nav {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, .18);
    color: #fff;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.rich-date-nav:hover {
    background: rgba(255, 255, 255, .32);
}

.rich-date-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    padding: .6rem .5rem 0;
}

.rich-date-weekday {
    text-align: center;
    font-size: .68rem;
    font-weight: 800;
    text-transform: uppercase;
    color: var(--text-muted);
}

.rich-date-weekday--sun { color: var(--badge-red); }
.rich-date-weekday--sat { color: var(--badge-blue); }

.rich-date-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: .15rem;
    padding: .4rem .5rem .6rem;
}

.rich-date-day {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-primary);
    font-size: .8rem;
    font-weight: 600;
    cursor: pointer;
}

.rich-date-day--blank {
    cursor: default;
    pointer-events: none;
}

.rich-date-day--sun:not(.rich-date-day--selected) { color: var(--badge-red); }
.rich-date-day--sat:not(.rich-date-day--selected) { color: var(--badge-blue); }

.rich-date-day:hover:not(.rich-date-day--blank):not(.rich-date-day--selected) {
    background: var(--accent-tint);
    color: var(--accent);
}

.rich-date-day--today {
    box-shadow: inset 0 0 0 2px var(--badge-orange);
    font-weight: 800;
}

.rich-date-day--selected {
    background: linear-gradient(135deg, var(--accent-grad-1) 0%, var(--accent-grad-2) 100%);
    color: var(--accent-on-grad);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 2px 8px rgba(var(--cta-glow), .25);
}

/* Past dates on a minNow picker (reservations) - visibly inert, not just
   unclickable, so it reads as "not offered" rather than "broken". */
.rich-date-day--disabled {
    color: var(--text-muted);
    opacity: .35;
    cursor: not-allowed;
}

.rich-date-day--disabled:hover {
    background: transparent;
    color: var(--text-muted);
}

.rich-date-nav:disabled {
    opacity: .3;
    cursor: not-allowed;
}

.rich-date-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .55rem .75rem;
    border-top: 1px solid var(--border);
}

.rich-date-today-btn {
    border: 0;
    border-radius: 999px;
    padding: .3rem .7rem;
    background: var(--success-tint);
    color: var(--success);
    font-size: .72rem;
    font-weight: 800;
    cursor: pointer;
}

.rich-date-clear-btn {
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-size: .72rem;
    font-weight: 700;
    cursor: pointer;
    text-decoration: underline;
}

.rich-date-done-btn {
    background: var(--accent-tint);
    color: var(--accent);
}

.rich-date-time-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .5rem .75rem;
    border-top: 1px solid var(--border);
}

.rich-date-time-label {
    font-size: .72rem;
    font-weight: 800;
    text-transform: uppercase;
    color: var(--text-muted);
}

.rich-date-time-input {
    padding: .3rem .5rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-primary);
    font-family: inherit;
    font-size: .82rem;
    font-weight: 600;
}

.rich-date-time-hint {
    font-size: .68rem;
    color: var(--text-muted);
    flex-shrink: 0;
}

/* Inside a .form-field, the trigger should fill the field's width like the
   other styled inputs around it rather than shrink to its label text. */
.form-field .rich-date-wrap {
    display: flex;
    width: 100%;
}

.form-field .rich-date-trigger {
    width: 100%;
    justify-content: flex-start;
}

.form-field .rich-date-panel {
    left: 0;
    right: auto;
}

/* ---------- Live-alert toasts (top-right corner) ---------- */

.notification-toast-stack {
    position: fixed;
    top: 1rem;
    right: 1rem;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: .7rem;
    max-width: 384px;
}

.notification-toast {
    position: relative;
    padding: .9rem 2.4rem .9rem 1.2rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-inline-start: 4px solid var(--accent);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    animation: toast-slide-in .18s ease;
}

.notification-toast.leaving {
    opacity: 0;
    transform: translateX(8px);
    transition: opacity .2s ease, transform .2s ease;
}

@keyframes toast-slide-in {
    from { opacity: 0; transform: translateX(16px); }
    to { opacity: 1; transform: translateX(0); }
}

.notification-toast-title {
    margin: 0 0 .2rem;
    font-size: .85rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}

.notification-toast-message {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
}

.notification-toast-close {
    position: absolute;
    top: .4rem;
    right: .5rem;
    border: none;
    background: none;
    font-size: 1.2rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
}

.notification-toast-close:hover {
    color: var(--text-primary);
}

/* ---------- Duotone surface gloss — remaining panel containers ----------
   Same top-edge light + inset highlight the .card/.module-tile family uses.
   Kept as one grouped rule at the end of the file so the box-shadow wins the
   cascade over each component's own shadow-sm. Floating overlays are listed
   separately below WITHOUT a box-shadow override so they keep their own
   (larger) elevation shadows. */
.centered-card,
.summary-card,
.cart-panel,
.checkout-panel,
.pos-section,
.table-details-panel,
.kot-panel,
.online-orders-payout {
    background-image: var(--card-sheen);
    box-shadow: inset 0 1px 0 var(--card-sheen-line), var(--shadow-md);
}

.td-popover-panel,
.pos-popover-panel,
.rich-date-panel,
.notification-toast {
    background-image: var(--card-sheen);
}

/* ---------- Duotone control language — pills, chips, tabs, toolbar buttons ----------
   The secondary-button treatment for every small control that is not a .btn:
   rest = surface + top-light + inset highlight + lift on hover; the selected/
   primary state = the same duotone CTA gradient the filled buttons use. Grouped
   here at the end of the file so these override each control's own older
   rest/active rules at equal specificity. */
.pos-toolbar-btn,
.category-chip,
.kds-chip,
.report-tab,
.report-tab-export,
.td-action-btn,
.party-size-btn,
.tender-quick-btn,
.product-card-add,
a.action-link,
button.action-link,
.kds-toolbar .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent),
.page-head .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent) {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .6), rgba(255, 255, 255, 0) 70%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 3px rgba(0, 0, 0, .07);
    transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease,
                background .14s ease, border-color .14s ease, color .14s ease, filter .14s ease;
}

.pos-toolbar-btn:hover,
.category-chip:hover,
.kds-chip:hover,
.report-tab:hover,
.report-tab-export:hover,
.td-action-btn:hover,
.party-size-btn:hover,
.tender-quick-btn:hover,
.product-card-add:hover,
a.action-link:hover,
button.action-link:hover,
.kds-toolbar .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent):hover,
.page-head .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent):hover {
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 3px 8px rgba(0, 0, 0, .09);
}

/* Toolbar .btn's sit directly on the page plane, where the ghost base is too
   faint — give them a solid surface so they read as buttons (same treatment
   as .pos-toolbar-btn). Hover still tints via the base .btn:hover rule. */
.kds-toolbar .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent),
.page-head .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent) {
    background-color: var(--surface-2);
    color: var(--text-primary);
}

.kds-toolbar .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent):hover,
.page-head .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger):not(.btn-success):not(.btn-accent):hover {
    background-color: var(--accent-tint);
    color: var(--accent);
}

.pos-toolbar-btn[aria-disabled="true"],
.pos-toolbar-btn:disabled,
.kds-toolbar .btn[aria-disabled="true"],
.kds-toolbar .btn:disabled,
.page-head .btn[aria-disabled="true"],
.page-head .btn:disabled {
    transform: none;
    box-shadow: none;
}

/* Selected / primary state — duotone CTA gradient with glow. */
.pos-toolbar-btn.primary,
.sale-mode-pill.active,
.category-chip.active,
.kds-chip.active,
.report-tab.active,
.tender-quick-btn.active,
.party-size-btn.active,
.td-action-btn.primary,
.view-toggle button.active {
    background: linear-gradient(135deg, var(--cta-grad-1) 0%, var(--cta-grad-2) 55%, var(--cta-grad-3) 100%);
    color: var(--cta-text);
    border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 2px 8px rgba(var(--cta-glow), .25);
}

.category-chip.active .section-pill-count {
    color: var(--cta-text);
}

/* The icon-only grid/list toggle hides its label via transparent text and
   paints a masked ::before icon instead — restore that after the grouped
   active rule above, and flip the icon to light on the gradient fill. */
.view-toggle button.active {
    color: transparent;
}

.view-toggle button.active::before {
    background-color: var(--cta-text);
}

#soundAlertsToggle.active {
    background: linear-gradient(135deg, var(--accent-grad-1) 0%, var(--accent-grad-2) 100%);
    color: var(--accent-on-grad);
    border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 2px 8px rgba(var(--cta-glow), .25);
}

/* Indicator pills (read-only status) — just the badge-style light wash. */
.kot-status-pill,
.inventory-status-pill,
.status-pill-btn,
.user-chip,
.shift-chip {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .4), transparent);
}

/* Language pill-group on the content plane (Business Profile page-head) — the
   chrome-token version above is only readable on the dark header bar. */
.page-head .pill-group {
    background: var(--surface-2);
    border: 1px solid var(--border);
}

.page-head .pill-group a {
    color: var(--text-muted);
}

.page-head .pill-group a.active {
    background-image: linear-gradient(135deg, var(--accent-grad-1), var(--accent-grad-2));
    color: var(--accent-on-grad);
    box-shadow: 0 1px 4px rgba(var(--cta-glow), .35);
}

/* KDS voice-alert popup — created by kds-board.js when a new order is announced;
   removed again when the speech finishes (or on click / fallback timeout), so it
   never participates in normal page layout. */
.kds-voice-popup-stack {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    align-items: flex-end;
}

.kds-voice-popup {
    width: 300px;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-inline-start: 3px solid var(--accent);
    border-radius: 10px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, .18);
    padding: .85rem 1rem;
    cursor: pointer;
}

.kds-voice-popup-title {
    font-weight: 600;
    color: var(--accent);
    margin-bottom: .35rem;
}

.kds-voice-popup-order {
    border-top: 1px solid var(--border);
    padding-top: .5rem;
    margin-top: .5rem;
}

.kds-voice-popup-order:first-of-type {
    border-top: 0;
    margin-top: 0;
    padding-top: 0;
}

.kds-voice-popup-head {
    font-weight: 600;
    color: var(--text-primary);
    font-size: .9rem;
}

.kds-voice-popup-item {
    color: var(--text-secondary);
    font-size: .85rem;
    padding-inline-start: .5rem;
}


/* Login store picker (multi-store plan §6.1) — one question, chrome-less on
   purpose: no operating context exists until the answer. */
.store-picker-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: var(--surface-0, var(--surface-1));
}

.store-picker-card {
    max-width: 26rem;
    width: 100%;
    text-align: center;
}

.store-picker-options {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    margin-top: 1.25rem;
}

.store-picker-option {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
}

.store-picker-name {
    font-weight: 600;
}

.store-picker-city {
    font-size: .75rem;
    opacity: .75;
}

/* Single-store tenants: the chrome states the store instead of offering a
   choice that does not exist (§6.2 — no affordance implying otherwise). */
.store-static-label {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: .8rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
}

/* ============================================================
   Auth pages — E3 consolidation 2026-08-05, themed 2026-08-10.
   The "glass" language every auth page carried as ~80 duplicated
   page-local lines (matching the marketing site): dotted texture,
   one accent, frosted glass card.

   Every value below is a --auth-* token (defined once per theme at the
   top of this file), NOT a hardcoded colour and NOT a per-theme rule
   override. It used to be hardcoded navy/cyan, which meant the auth
   shell ignored the user's theme entirely — while forgot-password and
   friends, which used the plain .centered-card instead of this shell,
   fell through to the bare amber default. Same journey, two palettes.
   A new theme now adds one --auth-* block and inherits all of this.

   Used by: login, signup, forgot-password, reset-password,
   invite-accept, must-change-password, backoffice-login, and (card
   only, over its photo backdrop) platform-login.
   Page-specific remainder stays local: .login-card/.signup-card widths,
   signup's fieldset/hint/locale-note extras.
   ============================================================ */
.auth-page {
    position: relative;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3rem;
    /* The auth plane is independent of the theme's CONTENT plane (amber's
       content is light, its auth plane is a dark warm brown), so the
       colour-scheme hint has to ride here rather than on the theme block. */
    color-scheme: var(--auth-color-scheme, dark);
    background: var(--auth-plane);
    background-image: radial-gradient(circle, var(--auth-dot) 1px, transparent 1px);
    background-size: 26px 26px;
    overflow: hidden;
}

.auth-page::before {
    content: "";
    position: absolute;
    top: -20%;
    right: -10%;
    width: 55vw;
    height: 55vw;
    min-width: 420px;
    min-height: 420px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--auth-glow), transparent 70%);
    pointer-events: none;
}

/* Width (and any vertical margin) is the page's own decision:
   .login-card caps at 420px, .signup-card at 620px. */
.auth-card {
    position: relative;
    z-index: 2;
    width: 100%;
    background: var(--auth-card-bg);
    backdrop-filter: blur(20px) saturate(160%);
    border: 1px solid var(--auth-card-border);
    border-radius: 22px;
    padding: 2.2rem;
    box-shadow: var(--auth-card-shadow);
    color: var(--auth-text);
}

.auth-brand {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1.25rem;
    font-weight: 800;
    color: var(--auth-text);
}

.auth-brand img { height: 26px; width: auto; }

.auth-page .page-title { color: var(--auth-text); }
.auth-page .page-subtitle { color: var(--auth-text-secondary); }
.auth-page .lang-switcher a { color: var(--auth-text-muted); }
.auth-page .lang-switcher a.active { color: var(--auth-on-accent); background: var(--auth-accent); }
.auth-page .form-field label { color: var(--auth-text-secondary); }
.auth-page .form-field input,
.auth-page .form-field select,
.auth-page .lang-switcher-select {
    background: var(--auth-field-bg);
    border-color: var(--auth-field-border);
    color: var(--auth-text);
}
.auth-page .form-field input::placeholder { color: var(--auth-text-faint); }
/* Option lists paint on the UA's own plane rather than the card's, so without an
   explicit pair they go light-on-light (or dark-on-dark) the moment the theme
   flips. color-scheme above fixes the popup chrome; this fixes the text. */
.auth-page select option { color: var(--auth-text); background: var(--auth-plane); }
.auth-page .form-field input:focus,
.auth-page .form-field select:focus,
.auth-page .lang-switcher-select:focus { outline-color: var(--auth-accent); border-color: var(--auth-accent); }
.auth-page .btn-primary {
    background: var(--auth-accent);
    color: var(--auth-on-accent);
    box-shadow: 0 10px 26px var(--auth-accent-glow);
}
.auth-page .btn-primary:hover { background: var(--auth-accent-hover); }
.auth-page .alert-error { background: var(--auth-danger-bg); color: var(--auth-danger); }
.auth-page .alert-success { background: var(--auth-success-bg); color: var(--auth-success); }
.auth-page .alert-info { background: var(--auth-info-bg); color: var(--auth-info); }
.auth-page .alert .alert-message { color: inherit; }
.auth-page .footer-link { color: var(--auth-text-muted); }
.auth-page .footer-link a { color: var(--auth-accent); }
.auth-page .badge { background: var(--auth-info-bg); color: var(--auth-info); border-color: var(--auth-card-border); }

/* ---------- Theme + language control row (fragments/auth-shell.html) ----------
   The auth journey's equivalent of the header theme toggle: the rail that owns
   that toggle is not rendered on any auth page, so without this an unauthenticated
   visitor had no way to pick a theme and the pages had no way to show one.
   Writes the SAME localStorage('blinkvenue-theme') key the rail and the Business
   Profile theme tile use, so the choice carries into the app after login.
   Four swatches rather than the header's two-way toggle, because this is the one
   surface where all four themes are reachable and none is "current" yet. */
.auth-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.auth-theme-picker {
    display: flex;
    align-items: center;
    gap: .4rem;
}

.auth-theme-dot {
    width: 22px;
    height: 22px;
    padding: 0;
    border-radius: 50%;
    cursor: pointer;
    border: 1px solid var(--auth-card-border);
    background-color: transparent;
    transition: box-shadow .15s ease, transform .15s ease;
}

.auth-theme-dot:hover { transform: scale(1.12); }

/* Double ring: the inner one is the plane colour, so the accent ring reads as a
   halo on every theme instead of merging into a light card. */
.auth-theme-dot[aria-pressed="true"] {
    box-shadow: 0 0 0 2px var(--auth-plane), 0 0 0 4px var(--auth-accent);
}

/* Each swatch shows its own theme's accent over its own plane — deliberately
   fixed values, not tokens: a swatch must look like the theme it SELECTS,
   not like the theme currently applied. */
.auth-theme-dot--amber { background-image: linear-gradient(135deg, #f59e0b 50%, #7a350c 50%); }
.auth-theme-dot--dark { background-image: linear-gradient(135deg, #60a5fa 50%, #0f1b33 50%); }
.auth-theme-dot--darker-blue { background-image: linear-gradient(135deg, #2dd4ff 50%, #0f1a3d 50%); }
.auth-theme-dot--light-pleasant { background-image: linear-gradient(135deg, #a78bfa 50%, #d7cfeb 50%); }

.auth-controls .lang-switcher { margin: 0; }
.auth-controls .lang-switcher-select { font-size: .85rem; }

@media (max-width: 720px) {
    .auth-page { padding: 1.5rem; }
}

/* ---------- Item customisation sheet (fragments/item-sheet.html) ----------
   One component for all three ordering surfaces: the POS order screen, the
   table-QR guest menu and the online storefront. Bottom sheet under 820px
   (thumb reach on a phone), centred modal above it (a POS terminal has a
   mouse). Tokens only — it inherits all four themes with no per-theme rules.

   Replaces the inline per-card checkbox list these screens used to carry:
   in a 337-dish grid that pushed the Add button below the fold on every card
   that happened to have options. */

.item-sheet {
    border: 0;
    padding: 0;
    margin: 0;
    background: transparent;
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    overflow: hidden;
}

.item-sheet::backdrop {
    background: var(--overlay-scrim);
}

.item-sheet[open] {
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.item-sheet-panel {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 18px 18px 0 0;
    box-shadow: var(--shadow-md);
    width: 100%;
    max-width: 460px;
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: item-sheet-rise .22s ease-out;
}

@keyframes item-sheet-rise {
    from { transform: translateY(14%); opacity: .6; }
    to { transform: none; opacity: 1; }
}

@media (min-width: 820px) {
    .item-sheet[open] { align-items: center; }
    .item-sheet-panel { border-radius: var(--radius); max-height: 82vh; }
}

/* Drag affordance — only meaningful in the bottom-sheet form. */
.item-sheet-grab {
    width: 2.4rem;
    height: .22rem;
    border-radius: 99px;
    background: var(--border);
    margin: .5rem auto .1rem;
    flex: none;
}

@media (min-width: 820px) {
    .item-sheet-grab { display: none; }
}

.item-sheet-head {
    display: flex;
    gap: .7rem;
    align-items: flex-start;
    padding: .6rem .95rem .7rem;
    border-bottom: 1px solid var(--border);
    flex: none;
}

.item-sheet-thumb {
    width: 2.6rem;
    height: 2.6rem;
    border-radius: var(--radius-sm);
    background: linear-gradient(135deg, var(--accent-grad-1), var(--accent-grad-2));
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    flex: none;
}

.item-sheet-heading {
    min-width: 0;
}

.item-sheet-title {
    font-weight: 700;
    font-size: .98rem;
    display: flex;
    align-items: center;
    gap: .35rem;
    color: var(--text-primary);
}

.item-sheet-meta {
    font-size: .72rem;
    color: var(--text-muted);
}

.item-sheet-close {
    margin-left: auto;
    background: none;
    border: 0;
    font-size: 1.15rem;
    color: var(--text-muted);
    cursor: pointer;
    line-height: 1;
    padding: 0 .15rem;
    flex: none;
}

.item-sheet-body {
    overflow-y: auto;
    padding: .35rem .95rem .8rem;
    flex: 1;
}

.item-sheet-group {
    padding: .7rem 0;
    border-bottom: 1px solid var(--border);
}

.item-sheet-group:last-of-type {
    border-bottom: 0;
}

.item-sheet-group-head {
    display: flex;
    align-items: baseline;
    gap: .45rem;
    margin-bottom: .35rem;
}

.item-sheet-group-name {
    font-weight: 700;
    font-size: .82rem;
    color: var(--text-primary);
}

.item-sheet-rule {
    margin-left: auto;
    font-size: .66rem;
    color: var(--text-muted);
}

.item-sheet-tag {
    font-size: .6rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    padding: .08rem .38rem;
    border-radius: 99px;
    font-weight: 700;
}

.item-sheet-tag.is-required {
    background: var(--danger);
    color: var(--cta-text);
}

.item-sheet-tag.is-optional {
    background: var(--surface-1);
    color: var(--text-muted);
    border: 1px solid var(--border);
}

.item-sheet-option {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .28rem .3rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: .82rem;
    color: var(--text-primary);
}

.item-sheet-option:hover {
    background: var(--surface-1);
}

.item-sheet-option input {
    accent-color: var(--accent);
    width: 1rem;
    height: 1rem;
    flex: none;
}

/* Marks an option the sheet pre-ticked from this guest's own order history. */
.item-sheet-suggested {
    font-size: .58rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 700;
    background: var(--accent-tint);
    color: var(--accent);
    padding: .05rem .35rem;
    border-radius: 99px;
}

.item-sheet-delta {
    margin-left: auto;
    color: var(--accent);
    font-size: .78rem;
    font-weight: 600;
}

.item-sheet-option.is-capped {
    opacity: .42;
    cursor: not-allowed;
}

/* Collapsed optional group — long-tail preference sets stay out of the way. */
details.item-sheet-group > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .45rem;
    font-weight: 700;
    font-size: .82rem;
    color: var(--text-primary);
}

details.item-sheet-group > summary::-webkit-details-marker {
    display: none;
}

details.item-sheet-group > summary::before {
    content: "\25B8";
    color: var(--text-muted);
    font-size: .7rem;
    transition: transform .15s;
}

details.item-sheet-group[open] > summary::before {
    transform: rotate(90deg);
}

.item-sheet-special {
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border);
}

.item-sheet-special > label {
    display: block;
    font-weight: 700;
    font-size: .82rem;
    margin-bottom: .3rem;
    color: var(--text-primary);
}

.item-sheet-special textarea {
    width: 100%;
    font: inherit;
    font-size: .82rem;
    padding: .45rem .55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-1);
    color: var(--text-primary);
    resize: vertical;
    min-height: 2.6rem;
}

.item-sheet-special textarea:focus {
    outline: 2px solid var(--accent-soft);
    outline-offset: -1px;
    background: var(--surface-2);
}

.item-sheet-hint {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    font-size: .66rem;
    color: var(--text-muted);
    margin-top: .25rem;
}

.item-sheet-blocked {
    font-size: .68rem;
    color: var(--danger);
    padding: .4rem .95rem .5rem;
    background: var(--surface-1);
    /* The body above scrolls, so without a rule this bar butts straight up
       against a half-clipped line of text and reads as overlapping it. */
    border-top: 1px solid var(--border);
    flex: none;
}

.item-sheet-foot {
    border-top: 1px solid var(--border);
    padding: .65rem .95rem;
    display: flex;
    gap: .6rem;
    align-items: center;
    background: var(--surface-1);
    flex: none;
}

.item-sheet-stepper {
    display: flex;
    align-items: center;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface-2);
    flex: none;
}

.item-sheet-stepper button {
    border: 0;
    background: none;
    font: inherit;
    font-size: 1rem;
    font-weight: 700;
    color: var(--accent);
    width: 2rem;
    height: 2.1rem;
    cursor: pointer;
}

.item-sheet-qty {
    min-width: 1.6rem;
    text-align: center;
    font-weight: 700;
    font-size: .88rem;
    color: var(--text-primary);
}

.item-sheet-cta {
    flex: 1;
    border: 0;
    border-radius: var(--radius-sm);
    height: 2.4rem;
    font: inherit;
    font-size: .88rem;
    font-weight: 700;
    cursor: pointer;
    background: linear-gradient(135deg, var(--accent-grad-1), var(--accent-grad-2));
    color: var(--accent-on-grad);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
}

.item-sheet-cta:disabled {
    background: var(--border);
    color: var(--text-muted);
    cursor: not-allowed;
}

/* The "note only" affordance on a card. Deliberately small and secondary: the
   primary + Add stays one tap for a dish with no options, which is what keeps
   a 337-card grid fast to work through. */
.product-card-note-btn {
    position: absolute;
    right: .45rem;
    top: .45rem;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    border: 0;
    background: var(--surface-2);
    color: var(--accent);
    cursor: pointer;
    font-size: .75rem;
    line-height: 1;
    box-shadow: var(--shadow-sm);
    z-index: 1;
}

.product-card-note-btn.has-note {
    background: var(--accent);
    color: var(--cta-text);
}

.product-card-options-hint {
    font-size: .68rem;
    color: var(--text-muted);
    margin: .15rem 0 .3rem;
}

/* The customer's own note, wherever a line is displayed back to them or to the
   kitchen. Italic + accent so it reads as a request, not as a modifier name. */
.special-request-note {
    font-size: .7rem;
    color: var(--accent);
    font-style: italic;
}


/* ---------- .toggle-row — a labelled on/off switch ----------
   Lived page-locally in menu-products.html until the modifier-group editor
   needed the same control. Promoted rather than copied: a second private copy
   is how two screens end up with two slightly different switches. The
   page-local block in menu-products.html is deleted, not shadowed. */

.toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .45rem 0;
    font-size: .88rem;
    border-bottom: 1px solid var(--border);
}

.toggle-row:last-of-type {
    border-bottom: none;
}

/* A plain browser checkbox reads as unfinished next to the rest of a styled
   form - restyle every toggle-row checkbox as a small pill switch instead. */
.toggle-row input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    width: 38px;
    height: 21px;
    min-width: 38px;
    border-radius: 999px;
    background: var(--border);
    border: none;
    cursor: pointer;
    transition: background .15s ease;
}

.toggle-row input[type="checkbox"]::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
    transition: transform .15s ease;
}

.toggle-row input[type="checkbox"]:checked {
    background: var(--accent);
}

.toggle-row input[type="checkbox"]:checked::after {
    transform: translateX(17px);
}

/* Amend the customer's note on a cart line that has not gone to the kitchen.
   Its own rule rather than .field-inline because the control has to sit inside a
   dense cart row: a full-width stacked field would push the price and the
   qty/remove controls out of line on every single row that carries a note. */
.special-request-edit {
    display: flex;
    gap: .3rem;
    align-items: center;
    margin: .2rem 0 .1rem;
}

.special-request-edit input[type="text"] {
    flex: 1;
    min-width: 0;
    font: inherit;
    font-size: .72rem;
    padding: .2rem .4rem;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-1);
    color: var(--text-primary);
}

.special-request-edit input[type="text"]:focus {
    outline: 2px solid var(--accent-soft);
    outline-offset: -1px;
    border-style: solid;
    background: var(--surface-2);
}

.special-request-edit button {
    flex: none;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--accent);
    font: inherit;
    font-size: .68rem;
    font-weight: 700;
    padding: .2rem .5rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.special-request-edit button:hover {
    background: var(--accent-tint);
}

/* === Super Admin console: tenant tiles + module picker (platform-tenants,
   platform-signup-review, platform-tenant-modules). Token-only colors so all
   four themes inherit their own palette — no per-theme overrides needed. === */

/* Max 4 tiles per row, stepping down with the viewport. Overrides card-grid's
   auto-fill inside this container only. */
.tenant-tile-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1rem;
}

@media (max-width: 1400px) {
    .tenant-tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 1040px) {
    .tenant-tile-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
    .tenant-tile-grid { grid-template-columns: minmax(0, 1fr); }
}

.tenant-tile {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: .75rem;
    min-width: 0;
}

.tenant-tile-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    min-width: 0;
}

.tenant-tile-name {
    font-weight: 700;
    font-size: .95rem;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tenant-tile-head .status-pill { flex-shrink: 0; }

.tenant-tile-meta {
    display: flex;
    flex-direction: column;
    gap: .3rem;
    font-size: .8rem;
}

.tenant-tile-meta-row {
    display: flex;
    justify-content: space-between;
    gap: .75rem;
    min-width: 0;
}

.tenant-tile-meta-row > span:first-child { color: var(--text-muted); flex-shrink: 0; }

.tenant-tile-meta-row > span:last-child {
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Uniform two-column button footer: every action is a same-sized real button,
   centred text, Delete full-width on its own row so it is hard to fat-finger. */
.tenant-tile-actions {
    margin-top: auto;
    border-top: 1px solid var(--border);
    padding-top: .75rem;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .45rem;
}

.tenant-tile-actions > form { display: contents; }

.tenant-tile-actions .btn {
    width: 100%;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-inline: .4rem;
}

/* The list view triggers the same reset dialog from a <button> — strip the
   browser button chrome so it reads exactly like its sibling action links. */
button.action-link {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    cursor: pointer;
}

/* One aligned footer row for review/edit screens: secondary action left,
   primary action right, identical heights. */
.actions-split {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: .75rem;
    margin-top: 1.25rem;
}

/* The one-time password reveal inside result dialogs. */
.password-reveal {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .75rem 1rem;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: .02em;
    word-break: break-all;
}

/* --- Module picker (shared fragment) --- */
.module-picker { display: flex; flex-direction: column; gap: .6rem; }

.module-picker-master {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .65rem .9rem;
    cursor: pointer;
}

.module-picker-master-title { font-weight: 700; font-size: .9rem; color: var(--text-primary); }

.module-picker-master-count { color: var(--text-muted); font-size: .8rem; margin-inline-start: .4rem; }

/* One module per row, not a grid — a 3-column grid squeezed each label into
   ~174px, which wrapped module names onto two lines and pushed the switch
   into the middle of the wrap rather than beside the name. Full-width rows
   fit name + code on one line for all 21 modules with room to spare. */
.module-picker-grid {
    display: flex;
    flex-direction: column;
    gap: .4rem;
}

/* Named .module-picker-tile*, not .module-tile* — the dashboard's own launcher
   tiles (fragments referenced from dashboard.html/user-groups.html) already own
   .module-tile/.module-tile-name for a totally different big-square-card design
   (min-height: 215px among other things). Reusing that name here is what made
   every row in this picker inherit that 215px minimum with no visible content
   to fill it. Different class family, not just a different selector, so this
   can't recur if the dashboard tile's own styling changes later. */
.module-picker-tile {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .55rem .9rem;
    cursor: pointer;
    min-width: 0;
}

.module-picker-tile:has(input:checked),
.module-picker-master:has(input:checked) {
    border-color: var(--accent);
}

.module-picker-tile-text { display: flex; align-items: baseline; gap: .6rem; min-width: 0; flex: 1; }

.module-picker-tile-name { font-size: .85rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; }

.module-picker-tile-code {
    font-size: .68rem;
    color: var(--text-muted);
    letter-spacing: .03em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The switch: the real checkbox stays (submitted, focusable) but is visually
   replaced by the pill. :focus-visible ring kept for keyboard users. */
.module-picker input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.module-switch {
    flex-shrink: 0;
    width: 36px;
    height: 20px;
    border-radius: 999px;
    background: var(--border);
    position: relative;
    transition: background .15s ease;
}

.module-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    inset-inline-start: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--surface-2);
    transition: transform .15s ease;
}

.module-picker input[type="checkbox"]:checked + .module-switch { background: var(--accent); }

.module-picker input[type="checkbox"]:checked + .module-switch::after { transform: translateX(16px); }

.module-picker input[type="checkbox"]:focus-visible + .module-switch {
    outline: 2px solid var(--accent-soft);
    outline-offset: 2px;
}

.module-picker-master-text { min-width: 0; }
