/* ═══════════════════════════════════════════════════════════════════════
   ANGEL HUB — Shared Design System
   Used by all modules (Hub, KB, Chat, Inventory, Audits)
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── DARK THEME (default) ─── */
:root, [data-theme="dark"] {
  --bg-base: #07090d;
  --bg-surface: #0e131c;
  --bg-elevated: #161d2a;
  --bg-hover: #1a2230;
  --border: #1f2838;
  --border-strong: #2c384e;
  --text-primary: #e8ecf4;
  --text-secondary: #8a97b0;
  --text-muted: #5b6779;
  --shadow-lg: 0 24px 60px -20px rgba(0,0,0,0.8);
  /* Dark mode separates cards with borders, not shadows — keep none so the
     dark look is unchanged. Light mode overrides these to lift cards. */
  --shadow-card: none;
  --shadow-card-hover: none;
  --float-bg: rgba(14,19,28,0.7);
  --float-bg-hover: rgba(22,29,42,0.85);
  --float-border: rgba(255,255,255,0.06);
  --float-border-hover: rgba(255,255,255,0.12);
  --float-shadow: 0 4px 14px rgba(0,0,0,0.3);
  --float-shadow-hover: 0 6px 18px rgba(0,0,0,0.4);
  --bg-gradient-1: rgba(0,216,230,0.08);
  --bg-gradient-2: rgba(45,212,164,0.04);
  --drawer-gradient: rgba(0,216,230,0.06);
  --header-bg: rgba(7,9,13,0.85);
  /* Compat aliases — the shared cost-entry widget was authored against these
     names with hardcoded dark fallbacks, so it rendered dark-on-dark (invisible
     text) in light mode. Map them to canonical tokens so the widget themes
     correctly in BOTH modes. */
  --bg-card: var(--bg-surface);
  --bg-input: var(--bg-elevated);
  --border-input: var(--border-strong);
  --border-subtle: var(--border);
}

[data-theme="light"] {
  /* ── Warm paper (2026-07-01) ──────────────────────────────────────
     The cool "slate" ramp that lived here read cold and clinical — a
     developer-tool register that felt a touch depressing for a warm
     hospitality product. Neutrals now carry a whisper of WARMTH instead
     of cool blue, so the app feels human, light and awake — without
     going back to the old dated "greige". Cards stay pure white and lift
     off a warm off-white canvas (Notion-style); the top bar is
     re-whitened in css/11 so it reads as a clean header rather than part
     of the page. Text is a warm near-black; shadows warmed to match.
     Dark mode is untouched — every value here is scoped to light. */
  --bg-base: #f7f6f3;        /* warm off-white page (canvas) */
  --bg-surface: #ffffff;     /* pure white cards / panels + top bar */
  --bg-elevated: #f1efea;    /* warm raised fills, on-card chips, tracks */
  --bg-hover: #ebe8e1;       /* warm hover state */
  --border: #eceae3;         /* warm hairline borders */
  --border-strong: #d9d5cc;  /* inputs, dividers, crisp control edges */
  --text-primary: #232019;   /* warm near-black — confident, high-contrast */
  --text-secondary: #57534d; /* warm grey-600 */
  --text-muted: #77726a;     /* warm grey-500 — still AA on white */
  --shadow-lg: 0 24px 60px -18px rgba(45,38,25,0.18);
  /* Soft warm lift so cards float off the page instead of sitting flat
     in one muddy plane — the depth that was missing in light mode. */
  --shadow-card: 0 1px 2px rgba(45,38,25,0.04), 0 4px 12px -4px rgba(45,38,25,0.10);
  --shadow-card-hover: 0 2px 6px rgba(45,38,25,0.07), 0 12px 26px -6px rgba(45,38,25,0.14);
  --float-bg: rgba(255,255,255,0.92);
  --float-bg-hover: rgba(255,255,255,1);
  --float-border: rgba(45,38,25,0.08);
  --float-border-hover: rgba(45,38,25,0.16);
  --float-shadow: 0 4px 14px rgba(45,38,25,0.08);
  --float-shadow-hover: 0 8px 22px rgba(45,38,25,0.14);
  --bg-gradient-1: rgba(0,0,0,0);
  --bg-gradient-2: rgba(0,0,0,0);
  --drawer-gradient: rgba(0,0,0,0);
  --header-bg: rgba(247,246,243,0.85);
  /* Compat aliases (see dark block) — re-declared here so the cost-entry
     widget picks up light-mode surfaces/borders. */
  --bg-card: var(--bg-surface);
  --bg-input: var(--bg-elevated);
  --border-input: var(--border-strong);
  --border-subtle: var(--border);
}

/* ─── ACCENT COLOURS ─── */
[data-accent="cyan"]   { --accent: #00d8e6; --accent-soft: rgba(0,216,230,0.12); --accent-glow: rgba(0,216,230,0.35); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }
[data-accent="teal"]   { --accent: #2dd4a4; --accent-soft: rgba(45,212,164,0.12); --accent-glow: rgba(45,212,164,0.35); --success: #00d8e6; --success-soft: rgba(0,216,230,0.12); }
[data-accent="purple"] { --accent: #a78bfa; --accent-soft: rgba(167,139,250,0.12); --accent-glow: rgba(167,139,250,0.35); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }
[data-accent="amber"]  { --accent: #f5a623; --accent-soft: rgba(245,166,35,0.12); --accent-glow: rgba(245,166,35,0.35); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }
[data-accent="rose"]   { --accent: #f472b6; --accent-soft: rgba(244,114,182,0.12); --accent-glow: rgba(244,114,182,0.35); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }
[data-accent="sky"]    { --accent: #38bdf8; --accent-soft: rgba(56,189,248,0.12); --accent-glow: rgba(56,189,248,0.35); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }
[data-accent="slate"]  { --accent: #94a3b8; --accent-soft: rgba(148,163,184,0.14); --accent-glow: rgba(148,163,184,0.20); --success: #2dd4a4; --success-soft: rgba(45,212,164,0.12); }

/* Light-mode accents — bumped to a more vivid 500-shade, soft tints bumped
   to 14% so colored highlights actually read on white. Still passes AA. */
[data-theme="light"][data-accent="cyan"]   { --accent: #06b6d4; --accent-soft: rgba(6,182,212,0.14); --accent-glow: rgba(6,182,212,0.30); --success: #059669; --success-soft: rgba(5,150,105,0.14); }
[data-theme="light"][data-accent="teal"]   { --accent: #10b981; --accent-soft: rgba(16,185,129,0.14); --accent-glow: rgba(16,185,129,0.30); --success: #06b6d4; --success-soft: rgba(6,182,212,0.14); }
[data-theme="light"][data-accent="purple"] { --accent: #8b5cf6; --accent-soft: rgba(139,92,246,0.14); --accent-glow: rgba(139,92,246,0.30); --success: #059669; --success-soft: rgba(5,150,105,0.14); }
[data-theme="light"][data-accent="amber"]  { --accent: #f59e0b; --accent-soft: rgba(245,158,11,0.14); --accent-glow: rgba(245,158,11,0.30); --success: #059669; --success-soft: rgba(5,150,105,0.14); }
[data-theme="light"][data-accent="rose"]   { --accent: #ec4899; --accent-soft: rgba(236,72,153,0.14); --accent-glow: rgba(236,72,153,0.30); --success: #059669; --success-soft: rgba(5,150,105,0.14); }
[data-theme="light"][data-accent="sky"]    { --accent: #0ea5e9; --accent-soft: rgba(14,165,233,0.14); --accent-glow: rgba(14,165,233,0.30); --success: #059669; --success-soft: rgba(5,150,105,0.14); }
[data-theme="light"][data-accent="slate"]  { --accent: #64748b; --accent-soft: rgba(100,116,139,0.12); --accent-glow: rgba(100,116,139,0.20); --success: #059669; --success-soft: rgba(5,150,105,0.14); }

/* ─── COMMON ─── */
:root {
  --warning: #f5a623; --warning-soft: rgba(245,166,35,0.12);
  --danger: #ff5670;  --danger-soft: rgba(255,86,112,0.12);
  --purple: #a78bfa;  --purple-soft: rgba(167,139,250,0.12);
  --display: 'Bricolage Grotesque', system-ui, sans-serif;
  --body: 'Manrope', system-ui, sans-serif;
  --drawer-width: 300px;
  --keyboard-height: 0px;

  /* ─── SAFE-AREA TOP for FULL-SCREEN overlay top bars ───────────────────
     The PWA runs status-bar-style: black-translucent (index.html), so web
     content lays out from the PHYSICAL top (y=0) UNDER the status bar — a
     full-screen overlay's top bar must pad itself clear by the safe-area
     inset. iOS can STUBBORNLY report env(safe-area-inset-top) as 0 on a
     standalone-PWA cold launch (not just briefly), and a small floor
     (10–16px) then leaves the bar — its close/back/⋯ icons and chips —
     stranded behind the clock / notch / Dynamic Island. Floor at 54px so the
     bar ALWAYS clears the status bar even at 0-inset; on real notch /
     Dynamic-Island phones env+11 (≈58–70px) exceeds the floor so they are
     unchanged. Proven on-device by the chat thread header (css/15:6524).
     Use this for EVERY full-screen overlay top bar (reader, viewer, detail
     takeover) — never a bare max(14px, env(safe-area-inset-top)). Centred /
     bottom-sheet modal heads do NOT use it (they never reach the notch). */
  --safe-top: max(54px, calc(env(safe-area-inset-top) + 11px));

  /* KB entity icon colours (folder = amber, document = slate) — single source for
     the folder/doc avatar backgrounds used across KB links, Projects + Documents.
     Same value in both themes (fixed brand colours). */
  --kb-folder: #f59e0b;
  --kb-doc: #64748b;

  /* ─── RAG STATUS (pass / warn / fail) — ONE fixed set (added 2026-07-01) ───
     Ends the audit/checklist drift between #16a34a & #22c55e, #dc2626 & #ef4444
     and four ambers. The bare token is the readable TEXT/icon colour — brightened
     here for the DARK surface, since a light-mode red (#dc2626) goes muddy on
     near-black; `*-fill` is the saturated hue for left-bars, dots + chip tints
     (theme-stable). Chip bg/border are color-mix() on the `*-fill`. Fixed values,
     NOT accent-swapped, so RAG meaning never rides the accent scheme. */
  --rag-pass: #4ade80;  --rag-pass-fill: #22c55e;
  --rag-warn: #fbbf24;  --rag-warn-fill: #f59e0b;
  /* 4-band scales (the safety 5×5 risk matrix): High sits between warn and fail. */
  --rag-high: #fb923c;  --rag-high-fill: #f97316;
  --rag-fail: #f87171;  --rag-fail-fill: #ef4444;

  /* ─── DESIGN TOKENS (Phase 1 — added 2026-05-15) ───
     Sharper, more rectangular shape language than before.
     Edit these tokens to retune globally. */

  /* Border radius — sharper than original (was 8-18px on most cards) */
  --radius-xs: 2px;          /* hairlines, dividers */
  --radius-sm: 4px;          /* tags, small badges */
  --radius-md: 8px;          /* cards, buttons, inputs (most things) */
  --radius-lg: 10px;         /* large cards, modals */
  --radius-pill: 999px;      /* pill buttons, chips, tabs */
  --radius-full: 50%;        /* avatars, dots */

  /* Spacing scale — 4px base */
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;
  --space-12: 48px; --space-16: 64px;

  /* Type scale — calibrated to existing dominant sizes
     (was 22 ad-hoc values incl. half-pixels) */
  --text-xs: 11px;     /* small labels, captions — KEPT (legibility floor) */
  --text-sm: 12px;     /* meta, secondary — KEPT (legibility floor) */
  --text-base: 13px;   /* DEFAULT body in cards — KEPT (dense-card readability) */
  --text-md: 14px;     /* primary body — KEPT */
  --text-lg: 14px;     /* html root size — tightened 15→14 (2026-06-16 density pass) */
  --text-xl: 16px;     /* prominent body — KEPT 16 (iOS input anti-zoom floor; do NOT lower) */
  --text-2xl: 17px;    /* small headings — 18→17 (density pass) */
  --text-3xl: 22px;    /* section titles — 24→22 (density pass) */
  --text-4xl: 26px;    /* page titles — 28→26 (density pass) */
}
[data-theme="light"] {
  --warning: #d97706; --warning-soft: rgba(217,119,6,0.10);
  --danger: #dc2626;  --danger-soft: rgba(220,38,38,0.10);
  --purple: #7c3aed;  --purple-soft: rgba(124,58,237,0.10);
  /* RAG text darkens on white for contrast; the fill hue stays the same. */
  --rag-pass: #16a34a;  --rag-pass-fill: #22c55e;
  --rag-warn: #d97706;  --rag-warn-fill: #f59e0b;
  --rag-high: #ea580c;  --rag-high-fill: #f97316;
  --rag-fail: #dc2626;  --rag-fail-fill: #ef4444;
}

/* ── Light-mode card depth ────────────────────────────────────────────
   Lifts the highest-traffic "first impression" surfaces — the home module
   grid and the dashboard KPI tiles — off the page with a soft cool shadow,
   so light mode reads layered and modern instead of flat. Scoped to
   [data-theme="light"]; dark mode keeps --shadow-card:none (no-op there).
   Easy to extend to list cards (.entry-card, .callout-card, …) later. */
[data-theme="light"] .module-card,
[data-theme="light"] .callout-kpi-card,
[data-theme="light"] .cl-kpi-card {
  box-shadow: var(--shadow-card);
}
[data-theme="light"] .module-card:hover,
[data-theme="light"] .callout-kpi-card:hover,
[data-theme="light"] .cl-kpi-card:hover {
  box-shadow: var(--shadow-card-hover);
}

/* List-row cards across the core modules — give them the same soft resting
   lift so light-mode lists read as floating cards (Trello/Linear feel) rather
   than flat rows. The :hover selectors carry higher specificity than each
   card's own hover rule, so the stronger lift always wins on hover without
   clobbering their border-colour transition. Light mode only. */
[data-theme="light"] .callout-card,
[data-theme="light"] .cl-card,
[data-theme="light"] .tasks-card,
[data-theme="light"] .asset-card,
[data-theme="light"] .order-card,
[data-theme="light"] .audit-tpl-card,
[data-theme="light"] .audit-sub-card,
[data-theme="light"] .rooms-card,
[data-theme="light"] .svc-card,
[data-theme="light"] .inv-card,
[data-theme="light"] .ctr-card,
[data-theme="light"] .entry-card {
  box-shadow: var(--shadow-card);
}
[data-theme="light"] .callout-card:hover,
[data-theme="light"] .cl-card:hover,
[data-theme="light"] .tasks-card:hover,
[data-theme="light"] .asset-card:hover,
[data-theme="light"] .order-card:hover,
[data-theme="light"] .audit-tpl-card:hover,
[data-theme="light"] .audit-sub-card:hover,
[data-theme="light"] .rooms-card:hover,
[data-theme="light"] .svc-card:hover,
[data-theme="light"] .inv-card:hover,
[data-theme="light"] .ctr-card:hover,
[data-theme="light"] .entry-card:hover {
  box-shadow: var(--shadow-card-hover);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* Visually-hidden but readable by screen readers — for live-announce regions and
   off-screen labels. Shared a11y utility; reuse it, don't fork. */
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
html, body {
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--body);
  font-size: var(--text-lg);
  line-height: 1.5;
  height: 100%;
  -webkit-font-smoothing: antialiased;
  /* Body never scrolls — .app-shell is the scroll container so the
     sticky app-bar stays anchored while content bounces on overscroll. */
  overflow: hidden;
  transition: background 0.3s ease, color 0.3s ease;
}

/* ─── Desktop UI scale ─────────────────────────────────────────────────
   On a wide desktop browser the px-based UI reads slightly oversized —
   you were manually browser-zooming out one notch (~90%) to make it sit
   right. Bake that in: scale the whole document ~10% smaller so fonts,
   spacing, icons and cards shrink together. A px-based design system can't
   do this via root font-size (only text would move), so use `zoom` — and
   `zoom`, not transform:scale, keeps fixed overlays, hit-testing and
   getBoundingClientRect coherent. Applied at :root so body-level modals /
   sheets / the impersonation banner scale WITH the shell, never split-scale
   (they're appended to <body>, outside .app-shell — which itself can't host
   the zoom because its transform would leave those modals at 100%).
   Tunable via --app-zoom (future Settings "Desktop density" toggle).
   Scoped to real desktops only: min-width 1024px (comfortably ABOVE the
   768px nav-rail breakpoint, so zoom never flips layout near a threshold)
   AND pointer:fine (phones + touch tablets stay 1:1; mobile is already
   width-tuned). The keyboard vars (--vvh/--vvtop) are gated behind
   body.kb-open, which a desktop never triggers, so they stay inert here. */
@media (min-width: 1024px) and (pointer: fine) {
  :root { zoom: var(--app-zoom, 0.88); }
  /* The photo-annotation EDITOR must run at 1:1. Under CSS zoom some browsers
     desync pointer clientX from getBoundingClientRect(), so freehand/arrow
     drawing starts offset from the cursor. Cancel the page zoom on its overlay
     so its subtree is net 1.0 — pointer coords then map exactly to the canvas,
     in every browser. (The read-only annotation SVG shown elsewhere is
     position-corrected in JS instead, since it renders inline in the zoomed
     page.) --app-unzoom MUST equal 1 / --app-zoom — keep the two in sync. */
  .kb-annot-overlay { zoom: var(--app-unzoom, 1.13636); }
}

/* Themed scrollbar — webkit (Chrome, Safari, Edge). Applies app-wide
   so the default white system bar doesn't clash with dark mode. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong, var(--border));
  border-radius: var(--radius-sm);
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
  background-clip: content-box;
}
/* iOS auto-zoom prevention — maximum-scale=1 in viewport handles zoom.
   Individual inputs that need 16px are set specifically below. */
@media (max-width: 1024px) {
  /* These were below 16px — iOS zooms on focus for anything under 16px.
     Setting exactly 16px prevents zoom without the cursor misalignment
     that !important causes when overriding layout-specific font sizes. */
  .entry-tag-input,
  .kb-search-input,
  .kb-modal-tag-input,
  .kb-modal-input,
  .kb-modal-textarea,
  .add-input,
  .kb-edit-photo-caption,
  .kb-instr-edit-photo-caption,
  .kb-doc-block-caption,
  .kb-instr-edit-step-label { font-size: var(--text-xl); }
}
button { font-family: inherit; cursor: pointer; border: none; background: transparent; color: inherit; touch-action: manipulation; -webkit-tap-highlight-color: transparent; user-select: none; }
a { color: inherit; text-decoration: none; }
body.locked {
  overflow: hidden;
}
html:has(body.locked) { overflow: hidden; }
/* When a modal locks the body, also freeze the shell scroll container. */
body.locked .app-shell { overflow: hidden; }

/* ─── iOS auto-zoom prevention ─────────────────────────────────────
   iOS Safari (and PWAs added to home screen) zoom in automatically when
   the user taps any input/textarea whose font-size is less than 16px.
   The zoom does NOT auto-restore when typing finishes, requiring manual
   pinch-out. Applying 16px to form fields ON MOBILE stops the zoom from
   ever happening.
   
   Scoped to ≤1024px because desktop has no auto-zoom problem and the
   16px floor was making form fields look outsized vs the rest of the
   desktop UI. !important on these because individual component classes
   have higher specificity and would otherwise override this, breaking
   the fix on touch devices.
   contenteditable elements do NOT use !important so internal headings
   (h3 inside the doc editor) can still be larger. */
@media (max-width: 1024px) {
  input,
  textarea,
  select {
    font-size: var(--text-xl) !important;
  }
  [contenteditable="true"] {
    font-size: max(16px, 1em);
  }
}

/* ─── SCROLLBARS (transparent, theme-aware) ─── */
/* Firefox */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
/* WebKit / Chromium / Safari */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
  background: transparent;
}
::-webkit-scrollbar-track {
  background: transparent;
  border: none;
}
::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
  background-clip: padding-box;
  border: 2px solid transparent;
}
::-webkit-scrollbar-corner { background: transparent; }
/* Hide scrollbar buttons (top/bottom arrows) */
::-webkit-scrollbar-button { display: none; }

/* ── Shared "no dead taps" pending spinner (ui/async-tap.js → AngelHubUI.guardTap).
   The helper swaps a button's label for <span class="ah-spinner"> while an async
   tap is in flight and adds .ah-pending. currentColor keeps the spinner in the
   button's OWN text colour, so it contrasts on any button (primary/ghost/danger)
   without knowing its style. The one app-wide spinner primitive — every other
   spinner today is component-local (kb-modal-spin, tasksSpin, …). ── */
.ah-pending { pointer-events: none; cursor: wait; }
.ah-spinner {
  display: inline-block;
  width: 1em; height: 1em;
  vertical-align: -0.15em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-full);
  animation: ah-spin 0.6s linear infinite;
}
@keyframes ah-spin { to { transform: rotate(360deg); } }

