/* STRAND — Design System
   Premium, minimal, fast. Inspired by the clarity of Linear/Notion/Apple —
   not their visuals, their restraint. Warm neutral palette (salon-appropriate,
   not clinical-tech-blue), generous whitespace, one accent color. */

:root {
  --bg: #FAF9F7;
  --surface: #FFFFFF;
  --surface-raised: #FFFFFF;
  --border: #ECE8E2;
  --border-strong: #DDD6CC;
  --ink: #1C1B19;
  --ink-soft: #57534A;
  --ink-faint: #8C877D;
  /* "Home" brand identity — sunshine yellow + coral. Replaces the earlier
     violet/magenta rebrand (kept for a while, but read too cool/tech for a
     hairstylist tool). This is the one hue pair that's the app's own
     signature, separate from every section's "color cabinet" override below —
     it's what you see on Today, onboarding, and the logo mark.
     --accent-deep is the dark end of every accent gradient. */
  --accent: #FFA800;
  --accent-deep: #B8790A;
  --accent-light: #FFD666;
  --accent-soft: #FFF3D6;
  --accent-ink: #1C1B19;
  /* Coral pop as the second gradient stop so primary surfaces (buttons,
     hero, brand mark) read as a living duotone gradient instead of a single
     flat hue. */
  --accent-pop: #FF6F5E;
  --glow-accent: 0 0 0 1px rgba(255,168,0,0.18), 0 10px 30px rgba(255,168,0,0.30), 0 2px 8px rgba(255,111,94,0.18);
  /* Raw R,G,B triples of the two colors above — kept in sync by hand
     whenever --accent/--accent-pop change (per page, see the "color
     cabinet" block below) so the ambient background blobs can use
     rgba(var(--accent-rgb), alpha), which plain hex custom properties
     don't support. */
  --accent-rgb: 255,168,0;
  --accent-pop-rgb: 255,111,94;
  --success: #3D7A5C;
  --success-soft: #E4F0E9;
  --warn: #B8863A;
  --warn-soft: #F6EBD9;
  /* Secondary palette — deliberately drawn from real hair-color families
     (copper/auburn, golden blonde, burgundy/wine fashion color, chestnut
     brunette) instead of generic dashboard accent colors, so the "alive"
     secondary palette actually means something to a colorist at a glance.
     Note: the --teal token name is legacy (kept so every call site that
     already references kpi-teal/avatar-t/etc. doesn't need renaming) but
     its value is now a warm chestnut/mahogany brunette, not teal. */
  --plum: #7A3B5E;         /* burgundy / wine fashion color */
  --plum-soft: #F3E4EC;
  --teal: #6E4A2E;         /* chestnut / mahogany brunette */
  --teal-soft: #EFE3D6;
  --gold: #B8863A;         /* golden blonde */
  --gold-soft: #F6EBD9;
  --ink-2: #2E2A26;
  /* 2026 pass: bigger, softer radii and richer layered shadows — flat cards
     with 12px corners read as a 2019 admin dashboard. Floating, generously
     rounded surfaces with real depth is the current baseline for a modern
     native-feeling app (this is what "looks like an app in 2026" means in
     CSS terms: elevation + roundness + a spring easing curve, not new hue). */
  --radius-sm: 11px;
  --radius: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;
  --shadow-sm: 0 1px 2px rgba(28,27,25,0.05), 0 1px 1px rgba(28,27,25,0.04);
  --shadow: 0 8px 24px rgba(28,27,25,0.08), 0 2px 6px rgba(28,27,25,0.05);
  --shadow-lg: 0 24px 56px rgba(28,27,25,0.16), 0 4px 14px rgba(28,27,25,0.08);
  --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-serif: "Fraunces", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
}

/* ---- "Color cabinet" per-page theming --------------------------------
   Direct stylist feedback: the app read as flat and one-color. A colorist's
   whole professional world is organized by color family (the wall of tubes
   behind every chair), so instead of one accent color everywhere, each top-
   level section of the app gets its own vivid, distinct hue. JS sets
   data-page on <body> on every navigation (see pageKeyForPath/resolveRoute
   in strand.js); everything downstream is free — --accent/--accent-pop/etc.
   already drive ~80 rules across this file (buttons, the page-title
   gradient, active nav/tab state, focus rings, card accents...), so
   overriding just these few variables per page recolors the whole section
   consistently instead of hand-recoloring dozens of components. Today/
   Onboarding keeps the original violet/magenta brand pair defined in :root
   above (no override needed) since that's the app's "home" identity. */
body[data-page="overview"] {
  --accent: #FF6A3D; --accent-deep: #C2410C; --accent-light: #FFB088; --accent-soft: #FFEDE0; --accent-pop: #FFC24A;
  --glow-accent: 0 0 0 1px rgba(255,106,61,0.18), 0 10px 30px rgba(255,106,61,0.30), 0 2px 8px rgba(255,194,74,0.18);
  --accent-rgb: 255,106,61; --accent-pop-rgb: 255,194,74;
}
body[data-page="clients"] {
  --accent: #EC4899; --accent-deep: #9D174D; --accent-light: #F472B6; --accent-soft: #FCE7F3; --accent-pop: #A855F7;
  --glow-accent: 0 0 0 1px rgba(236,72,153,0.18), 0 10px 30px rgba(236,72,153,0.30), 0 2px 8px rgba(168,85,247,0.18);
  --accent-rgb: 236,72,153; --accent-pop-rgb: 168,85,247;
}
body[data-page="formulas"] {
  --accent: #F5A623; --accent-deep: #B45309; --accent-light: #FBC969; --accent-soft: #FEF3D9; --accent-pop: #E8734A;
  --glow-accent: 0 0 0 1px rgba(245,166,35,0.18), 0 10px 30px rgba(245,166,35,0.30), 0 2px 8px rgba(232,115,74,0.18);
  --accent-rgb: 245,166,35; --accent-pop-rgb: 232,115,74;
}
body[data-page="cabinet"] {
  --accent: #14B8A6; --accent-deep: #0F766E; --accent-light: #5EEAD4; --accent-soft: #DFF7F1; --accent-pop: #34D399;
  --glow-accent: 0 0 0 1px rgba(20,184,166,0.18), 0 10px 30px rgba(20,184,166,0.30), 0 2px 8px rgba(52,211,153,0.18);
  --accent-rgb: 20,184,166; --accent-pop-rgb: 52,211,153;
}
body[data-page="settings"] {
  --accent: #3B82F6; --accent-deep: #1E40AF; --accent-light: #93C5FD; --accent-soft: #E4ECFF; --accent-pop: #6366F1;
  --glow-accent: 0 0 0 1px rgba(59,130,246,0.18), 0 10px 30px rgba(59,130,246,0.30), 0 2px 8px rgba(99,102,241,0.18);
  --accent-rgb: 59,130,246; --accent-pop-rgb: 99,102,241;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  /* A richer, multi-point gradient mesh instead of two faint radial washes —
     this is the "canvas" the floating sidebar/cards sit on top of, which is
     what makes them read as floating panels rather than a flat page. */
  background:
    /* Subtle film-grain layer (2026 glassmorphism reads as flat/dated
       without texture behind it — a faint noise layer is what separates
       "frosted glass over something real" from "white card, blur filter") */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E"),
    radial-gradient(1200px 750px at 108% -10%, rgba(var(--accent-rgb),0.18), transparent 55%),
    radial-gradient(950px 680px at -10% 6%, rgba(var(--accent-pop-rgb),0.13), transparent 55%),
    radial-gradient(850px 750px at 28% 118%, rgba(122,59,94,0.09), transparent 60%),
    radial-gradient(750px 550px at 100% 100%, rgba(184,134,58,0.09), transparent 55%),
    var(--bg);
  transition: background 0.5s ease;
  background-attachment: fixed;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 14px;
  line-height: 1.5;
  position: relative;
  overflow-x: hidden;
}
/* Futuristic ambient glow — two large, blurred, slowly-drifting color
   blobs pulled from real hair-color families (copper/auburn + golden
   blonde on one side, burgundy + chestnut brunette on the other) instead
   of generic purple/blue "AI app" gradients. Fixed behind everything so
   panels read as floating glass over a living backdrop, the same visual
   language as the reference screens, but on-brand for a colorist's tool. */
body::before, body::after {
  content: "";
  position: fixed;
  border-radius: 50%;
  filter: blur(90px);
  z-index: 0;
  pointer-events: none;
  opacity: 0.6;
  will-change: transform;
}
body::before {
  width: 620px; height: 620px;
  top: -200px; right: -140px;
  background: radial-gradient(circle, rgba(var(--accent-rgb),0.52), rgba(var(--accent-pop-rgb),0.24) 55%, transparent 75%);
  animation: orb-drift-a 24s ease-in-out infinite;
}
body::after {
  width: 560px; height: 560px;
  bottom: -180px; left: -120px;
  background: radial-gradient(circle, rgba(var(--accent-pop-rgb),0.32), rgba(var(--accent-rgb),0.20) 55%, transparent 75%);
  animation: orb-drift-b 28s ease-in-out infinite;
}
@keyframes orb-drift-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-46px, 42px) scale(1.08); }
}
@keyframes orb-drift-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(36px, -34px) scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  body::before, body::after { animation: none; }
}
#app { position: relative; z-index: 1; }

/* Anchor tags are used throughout as real, native links (nav, list rows,
   cards, buttons) — reset their default styling so they read as the
   component they're wearing, not as browser-default blue underlined text. */
a { color: var(--accent); text-decoration: none; }
a.card, a.list-row, a.btn, a.nav-link, a.kpi-card, a.back-link {
  color: inherit;
  text-decoration: none;
}

.icon svg { width: 100%; height: 100%; display: block; }

#app { display: flex; flex-direction: column; min-height: 100vh; }
#loading-shell { padding: 44px; color: var(--ink-faint); font-size: 13px; }
/* The sidebar used to butt up against the page edge as a flat bordered
   column — floating it on the gradient canvas with its own radius/shadow
   is most of what separates a "dashboard from 2019" look from a native
   2026 app shell. */
.app-row { display: flex; flex: 1; min-height: 0; align-items: stretch; gap: 16px; padding: 16px 16px 0 16px; }

/* ---- v2: Cabinet DNA, structured formula items, notes consent UI ---- */
.stock-note {
  margin-top: 8px; font-size: 12px; padding: 6px 10px; border-radius: var(--radius-sm);
  background: var(--bg); color: var(--ink-soft); border: 1px solid var(--border);
}
.stock-note.stock-exact { background: var(--success-soft); color: #275A40; border-color: transparent; }
.stock-note.stock-similar { background: var(--warn-soft); color: #8A5F1F; border-color: transparent; }
.stock-note.stock-none { background: var(--accent-soft); color: var(--accent); border-color: transparent; }

.item-row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.item-row select.item-product { flex: 1; }
.item-row input.item-other-name { flex: 1; }
.item-row input.item-amount { width: 100px; }

.star-pick { font-size: 22px; color: var(--border-strong); cursor: pointer; user-select: none; }
.star-pick.on { color: var(--warn); }

.shade-row { display: flex; gap: 8px; align-items: center; padding: 4px 0; }

.filter-active { opacity: 0.55; }
.filter-active.on { opacity: 1; background: var(--accent-soft); color: var(--accent); }

.verify-note {
  padding: 6px 10px; border-radius: var(--radius-sm); background: var(--bg);
  color: var(--ink-faint); border: 1px solid var(--border);
  font-weight: 600; display: flex; align-items: center; gap: 6px;
}
.verify-note.verified { background: var(--success-soft); color: #275A40; border-color: transparent; }
/* Unverified/generalized suggestions are the higher-risk case (see
   HAIRSTYLIST_REALITY_CHECK.md objection 3) — this needs to read as a
   caution badge at a glance, not blend in as plain muted text. */
.verify-note.unverified { background: var(--warn-soft); color: #8A5F1F; border-color: transparent; }

.photo-feedback {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 10px; font-size: 12px; line-height: 1.5;
}

.voice-row { margin: 6px 0 12px 0; }
.voice-btn.voice-listening { background: var(--warn-soft); color: #8A5F1F; border-color: transparent; }
.voice-summary {
  background: var(--accent-soft); border: 1px solid var(--accent); border-radius: var(--radius-sm);
  padding: 8px 10px; font-size: 12.5px; line-height: 1.5; margin-bottom: 10px;
}
.photo-confirm-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border);
}
.photo-confirm-row:last-child { border-bottom: none; }

.lang-switcher { padding: 12px 4px 4px 4px; border-top: 1px solid rgba(255,255,255,0.08); margin-top: 8px; }
.lang-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: rgba(228,223,245,0.4); margin-bottom: 8px; }
.lang-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.lang-pill {
  font-family: var(--font); font-size: 11px; font-weight: 650; letter-spacing: 0.02em;
  padding: 5px 9px; border-radius: 7px; border: 1px solid rgba(255,255,255,0.12);
  background: rgba(255,255,255,0.05); color: rgba(228,223,245,0.7); cursor: pointer;
  transition: border-color 0.15s var(--ease-spring), color 0.15s var(--ease-spring), background 0.15s var(--ease-spring);
}
.lang-pill:hover { border-color: var(--accent-light); color: #fff; background: rgba(124,77,255,0.25); }
.lang-pill.active { background: linear-gradient(120deg, var(--accent) 0%, var(--accent-pop) 100%); border-color: transparent; color: #fff; }

.persistence-banner {
  background: var(--warn-soft); color: #7A5720; padding: 10px 24px;
  font-size: 12.5px; font-weight: 600; text-align: center;
  border-bottom: 1px solid var(--border-strong);
}
.demo-banner {
  background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff;
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.demo-banner button {
  background: rgba(255,255,255,0.2); color: #fff; border: 1px solid rgba(255,255,255,0.4);
  border-radius: 999px; padding: 3px 12px; font-size: 11.5px; font-weight: 650; cursor: pointer;
  transition: background 0.15s var(--ease-spring);
}
.demo-banner button:hover { background: rgba(255,255,255,0.32); }

.profile-picker {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.profile-picker-card {
  width: 100%; max-width: 380px; text-align: center;
  background: rgba(255,255,255,0.78);
  backdrop-filter: blur(24px) saturate(1.4);
  -webkit-backdrop-filter: blur(24px) saturate(1.4);
  border: 1px solid rgba(255,255,255,0.6); border-radius: 24px;
  padding: 32px 28px; box-shadow: 0 20px 60px rgba(74,50,168,0.16);
}
.profile-picker-title { font-family: "Fraunces", serif; font-size: 22px; font-weight: 600; }
.profile-picker-divider {
  display: flex; align-items: center; gap: 10px; margin: 18px 0;
  color: var(--ink-faint); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em;
}
.profile-picker-divider::before, .profile-picker-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--border-strong);
}

/* ---------- Layout ---------- */
.sidebar {
  width: 232px;
  flex-shrink: 0;
  /* Dark "control rail" instead of a light glass panel that just blends
     into the light canvas behind it — pairing a deep, near-black violet
     rail with the light content area is the actual contrast move that
     makes a sidebar read as a distinct, considered surface (this is the
     Linear/Vercel/Arc pattern) instead of "content, but with a border." */
  background:
    radial-gradient(140% 90% at 0% 0%, rgba(124,77,255,0.5), transparent 60%),
    radial-gradient(120% 80% at 100% 100%, rgba(226,74,166,0.28), transparent 55%),
    linear-gradient(165deg, #1B1330 0%, #120D22 60%, #0E0A1A 100%);
  background-blend-mode: normal;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 56px rgba(14,10,26,0.45), 0 2px 8px rgba(14,10,26,0.3);
  padding: 20px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-self: flex-start;
  position: sticky;
  top: 16px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  color: #E4DFF5;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px 24px 10px;
  border-radius: var(--radius-sm);
}
.brand:hover .brand-mark { transform: scale(1.06) rotate(-3deg); }
.brand-mark {
  width: 32px; height: 32px; border-radius: 10px;
  background: linear-gradient(155deg, var(--accent), var(--accent-pop)); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-serif); font-weight: 600; font-size: 16px;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.16) inset, 0 0 18px rgba(124,77,255,0.55), 0 4px 10px rgba(124,77,255,0.4);
  transition: transform 0.2s var(--ease-spring);
  animation: brand-mark-pulse 3.4s ease-in-out infinite;
}
@keyframes brand-mark-pulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.16) inset, 0 0 18px rgba(124,77,255,0.55), 0 4px 10px rgba(124,77,255,0.4); }
  50% { box-shadow: 0 0 0 1px rgba(255,255,255,0.22) inset, 0 0 26px rgba(226,74,166,0.6), 0 4px 14px rgba(124,77,255,0.5); }
}
@media (prefers-reduced-motion: reduce) { .brand-mark { animation: none; } }
.brand-name { font-weight: 650; font-size: 16px; letter-spacing: -0.01em; color: #fff; }
.brand-sub { font-size: 10px; color: rgba(228,223,245,0.5); text-transform: uppercase; letter-spacing: 0.08em; margin-top: -1px; }

#nav-links { display: flex; flex-direction: column; gap: 3px; }
.nav-link {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px 8px 7px; border-radius: var(--radius-pill);
  color: rgba(228,223,245,0.68); font-size: 13.5px; font-weight: 550;
  cursor: pointer;
  position: relative;
  transition: background 0.18s var(--ease-spring), color 0.18s var(--ease-spring), transform 0.15s var(--ease-spring);
  /* Each item eases + fades into place on sidebar mount instead of just
     appearing — cheap, and it's the first thing a user's eye lands on
     every single session. */
  animation: nav-item-in 0.45s var(--ease-spring) backwards;
}
.nav-link:nth-child(1) { animation-delay: 0.02s; }
.nav-link:nth-child(2) { animation-delay: 0.06s; }
.nav-link:nth-child(3) { animation-delay: 0.10s; }
.nav-link:nth-child(4) { animation-delay: 0.14s; }
.nav-link:nth-child(5) { animation-delay: 0.18s; }
.nav-link:nth-child(6) { animation-delay: 0.22s; }
.nav-link:nth-child(7) { animation-delay: 0.26s; }
@keyframes nav-item-in { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: translateX(0); } }
@media (prefers-reduced-motion: reduce) { .nav-link { animation: none; } }
.nav-link:hover { background: rgba(255,255,255,0.07); color: #fff; transform: translateX(2px); }
.nav-link:active { transform: scale(0.97); }
.nav-link.active {
  background: linear-gradient(120deg, var(--accent) 0%, var(--accent-pop) 100%);
  color: #fff;
  box-shadow: 0 4px 18px rgba(124,77,255,0.55), 0 0 0 1px rgba(255,255,255,0.08) inset;
}
/* A small glowing dot on the active item's right edge — the kind of tiny
   "current location" beacon native apps use (think a menu-bar status
   light), instead of relying on the background fill alone to say
   "you are here." */
.nav-link.active::after {
  content: ""; position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  width: 6px; height: 6px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 8px 2px rgba(255,255,255,0.75);
  animation: nav-beacon 1.8s ease-in-out infinite;
}
@keyframes nav-beacon { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
/* Icon chip — each nav icon sits in its own small rounded tile instead of
   floating bare next to the label, and each section gets its own accent
   tint (via the data-icon attribute set in JS) instead of every icon
   being the same muted violet — a colored icon system is one of the
   fastest ways to make a nav rail feel considered rather than generated. */
.nav-link .icon {
  opacity: 0.9; flex-shrink: 0;
  width: 28px !important; height: 28px !important;
  border-radius: 9px;
  background: rgba(255,255,255,0.06);
  color: rgba(228,223,245,0.85);
  display: flex; align-items: center; justify-content: center;
  transition: background 0.18s var(--ease-spring), transform 0.25s var(--ease-spring), color 0.18s var(--ease-spring);
}
.nav-link .icon svg { width: 15px; height: 15px; }
.nav-link[data-icon="mascot"] .icon { color: #C9B6FF; }
.nav-link[data-icon="logistics"] .icon { color: #7FE0C6; }
.nav-link[data-icon="clients"] .icon { color: #F0A8D0; }
.nav-link[data-icon="formulas"] .icon { color: #E7B872; }
.nav-link[data-icon="library"] .icon { color: #8FC6F0; }
.nav-link[data-icon="cabinet"] .icon { color: #B8E08A; }
.nav-link[data-icon="settings"] .icon { color: #C9C4DC; }
.nav-link:hover .icon { background: rgba(255,255,255,0.12); transform: scale(1.1) rotate(-5deg); }
.nav-link.active .icon { opacity: 1; background: rgba(255,255,255,0.24); color: #fff; transform: scale(1.05); }
.nav-spacer { flex: 1; }
.nav-foot { padding: 10px; font-size: 10.5px; color: rgba(228,223,245,0.4); letter-spacing: 0.01em; }

.back-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-faint);
  margin-bottom: 14px; cursor: pointer;
}
.back-link:hover { color: var(--accent); }

/* Route transition — every navigation (sidebar click, hero pill, back link)
   fades + slides the new page's content in instead of a hard content swap.
   Re-triggered via JS by toggling this class on #main after each route
   resolves (see animateRouteChange). This one change does more for "feels
   like a real app" than any single card style, because it's felt on every
   single navigation, not just admired once on the dashboard. */
.route-enter { animation: route-fade-in 0.4s var(--ease-spring); }
@keyframes route-fade-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .route-enter { animation: none; }
}

.main {
  flex: 1;
  min-width: 0; /* flex children default to min-width:auto, which lets their
    content's intrinsic width force the whole column (and page) to overflow
    horizontally instead of shrinking/wrapping — this is the actual fix,
    the list-row wrap rules below help but don't work without this. */
  padding: 8px 24px 80px 8px;
  max-width: 1080px;
}

.page-header { margin-bottom: 24px; }
.page-title {
  font-family: var(--font-serif); font-size: 32px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 4px 0;
  /* Gradient headline — a small, cheap detail that reads as "designed in
     2026" on every single page since page-title is used everywhere. Bumped
     from 27px because a headline this treatment needs real size to land;
     at body-copy scale a gradient just looks like a rendering glitch. */
  background: linear-gradient(100deg, var(--ink) 30%, var(--accent) 70%, var(--accent-pop) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.page-sub { color: var(--ink-soft); font-size: 13.5px; margin: 0; }
.page-header-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; row-gap: 10px; }

/* ---------- Hero band (Dashboard only) ----------
   The dashboard used to open with the same flat page-header every other
   page uses, which is exactly why it read as "boring" — nothing signaled
   this was the one screen a stylist opens dozens of times a day. A single
   dark, warm-gradient band with a soft dot texture and one glow accent
   gives it presence without introducing a new loud color. */
.hero-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  padding: 30px 30px 28px 30px;
  margin-bottom: 20px;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-deep) 60%, #2E1E63 130%);
  box-shadow: 0 20px 48px rgba(74,50,168,0.32), var(--shadow);
}
.hero-band::before {
  content: "";
  position: absolute; inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.08) 1px, transparent 1px);
  background-size: 15px 15px;
  opacity: 0.6;
  pointer-events: none;
}
.hero-band::after {
  content: "";
  position: absolute; top: -70px; right: -50px;
  width: 260px; height: 260px; border-radius: 50%;
  background: radial-gradient(circle, rgba(184,134,58,0.4), rgba(109,79,224,0.18) 55%, transparent 72%);
  pointer-events: none;
  filter: blur(4px);
}
.hero-mark {
  position: relative;
  width: 40px; height: 40px; border-radius: 13px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 14px; color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--gold));
  box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset, 0 6px 18px rgba(184,134,58,0.4);
  animation: hero-mark-glow 3.2s ease-in-out infinite;
}
@keyframes hero-mark-glow {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset, 0 6px 18px rgba(184,134,58,0.4); }
  50% { box-shadow: 0 0 0 1px rgba(255,255,255,0.2) inset, 0 6px 26px rgba(184,134,58,0.65); }
}
.hero-greeting {
  position: relative;
  font-family: var(--font-serif); font-size: 27px; font-weight: 600;
  letter-spacing: -0.01em; color: #fff; margin: 0 0 6px 0;
}
.hero-sub { position: relative; color: rgba(255,255,255,0.76); font-size: 13.5px; margin: 0 0 18px 0; max-width: 480px; }
/* Persistent command bar inside the hero — this is what makes the assistant
   the spine of the app instead of a side feature you have to remember
   exists: it's the first interactive thing on the screen a stylist opens
   dozens of times a day, not a floating button in a corner. */
.hero-ask-row {
  position: relative; display: flex; align-items: center; gap: 10px;
  background: rgba(255,255,255,0.14);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  border: 1px solid rgba(255,255,255,0.24);
  border-radius: var(--radius-pill);
  padding: 6px 8px 6px 16px;
  margin-bottom: 14px;
  max-width: 520px;
  color: rgba(255,255,255,0.85);
  transition: background 0.2s var(--ease-spring), border-color 0.2s var(--ease-spring);
}
.hero-ask-row:focus-within { background: rgba(255,255,255,0.2); border-color: rgba(255,255,255,0.4); }
.hero-ask-row .icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.85; }
.hero-ask-row input {
  flex: 1; background: transparent; border: none; box-shadow: none; padding: 6px 0;
  color: #fff; font-size: 13.5px;
}
.hero-ask-row input::placeholder { color: rgba(255,255,255,0.62); }
.hero-ask-row input:focus { box-shadow: none; }
.hero-ask-send {
  width: 32px; height: 32px; border-radius: 50%; border: none; flex-shrink: 0;
  background: linear-gradient(135deg, #fff, rgba(255,255,255,0.8));
  color: var(--accent-deep); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.15s var(--ease-spring);
}
.hero-ask-send:hover { transform: scale(1.08); }
.hero-ask-send:active { transform: scale(0.94); }

/* ---------- Today ask-bar (invisible-AI redesign) ----------
   Replaces the old full-screen chat-agent Home. One calm, single-line row
   on a plain card — no gradient, no avatar, no "how can I help" prompt.
   Submitting or speaking into it opens the assistant panel with the
   question already sent; nothing about this row itself looks like a
   chatbot product. */
.today-ask-card { padding: 10px 14px; margin-bottom: 14px; }
.today-ask-row { display: flex; align-items: center; gap: 10px; }
.today-ask-row .icon { width: 15px; height: 15px; flex-shrink: 0; color: var(--ink-soft); }
.today-ask-row input {
  flex: 1; background: transparent; border: none; box-shadow: none; padding: 6px 0;
  color: var(--ink); font-size: 13.5px;
}
.today-ask-row input::placeholder { color: var(--ink-soft); }
.today-ask-row input:focus { box-shadow: none; }
.today-ask-send {
  width: 28px; height: 28px; border-radius: 50%; border: none; flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.15s var(--ease-spring), background 0.15s var(--ease-spring);
}
.today-ask-send:hover { transform: scale(1.08); background: var(--accent); color: #fff; }
.today-ask-send:active { transform: scale(0.94); }

/* Today Career Record strip — replaced the old New client/Add product/
   Schedule quick-action tiles (real-stylist feedback: flat, admin-y, not
   worth opening the app for). This is the same real numbers as the Career
   Record card in Settings — tenure, formulas, clients — surfaced daily
   instead of buried in settings, with a count-up animation on load. Fills
   the same "tall phone, dead gap before the tab bar" space with something
   that grows and feels earned instead of a row of buttons. */
.career-strip { transition: transform 0.18s var(--ease-spring), box-shadow 0.18s var(--ease-spring); }
.career-strip:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.career-strip:active { transform: scale(0.98); }
.career-strip-stat { font-size: 20px; font-weight: 700; color: var(--ink); }

/* Daily streak pill — small and warm, not a loud gamification badge. Sits
   next to the "N appointments today" line on Today. */
/* Confetti burst for celebrate() — see strand.js. Each dot starts pinned at
   the toast's position (opacity 0) then animates to its randomized
   --dx/--dy offset while fading, via a class toggle (not @keyframes) so the
   random per-dot distance/angle computed in JS drives the transition. */
.confetti-dot { opacity: 0; transform: translate(0, 0) scale(0.6); transition: transform 0.6s cubic-bezier(0.16,1,0.3,1), opacity 0.6s ease; }
.confetti-dot-go { opacity: 1; transform: translate(var(--dx), var(--dy)) scale(1); }
.celebrate-toast { animation: celebrate-pop 0.35s var(--ease-spring); }
@keyframes celebrate-pop {
  0% { transform: translateX(-50%) scale(0.85); opacity: 0; }
  60% { transform: translateX(-50%) scale(1.04); opacity: 1; }
  100% { transform: translateX(-50%) scale(1); }
}

.today-insight-card { transition: transform 0.18s var(--ease-spring), box-shadow 0.18s var(--ease-spring); }
.today-insight-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.today-insight-card:active { transform: scale(0.98); }
.today-insight-icon {
  width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--accent-soft), transparent);
  color: var(--accent-deep);
}
.streak-pill {
  display: inline-flex; align-items: center; gap: 2px;
  font-size: 12.5px; font-weight: 700;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: linear-gradient(120deg, var(--accent-soft), transparent);
  color: var(--accent-deep);
  border: 1px solid rgba(var(--accent-rgb),0.25);
}

/* Landing glow for the full Career Record card in Settings when you arrive
   via the Today strip's link — confirms the click actually took you
   somewhere real instead of just dumping you at the top of Settings. */
#career-record-card { transition: box-shadow 0.4s ease, transform 0.4s ease; }
#career-record-card.career-record-landed {
  box-shadow: var(--glow-accent);
  transform: scale(1.012);
}

/* ---------- Career Record milestone modal ----------
   A rare, earned moment (see maybeCelebrateCareerMilestone) — deserves more
   presence than a routine confirm dialog, so it gets the accent glow and a
   spring pop-in rather than the plain modal fade. Fires at most once per
   threshold, ever, so this can afford to feel a little bigger than usual. */
.career-milestone-title {
  display: flex; align-items: center; gap: 10px; font-size: 20px;
  animation: career-milestone-pop 0.5s var(--ease-spring);
}
.career-milestone-title .icon { width: 22px; height: 22px; filter: drop-shadow(0 0 10px rgba(124,77,255,0.45)); }
.career-milestone-body { animation: career-milestone-fade 0.6s var(--ease-spring) 0.08s backwards; }
@keyframes career-milestone-pop {
  0% { opacity: 0; transform: scale(0.85) translateY(6px); }
  60% { opacity: 1; transform: scale(1.03) translateY(0); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes career-milestone-fade {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
.modal:has(.career-milestone-title) { box-shadow: var(--glow-accent); }

.hero-ask-mic {
  width: 30px; height: 30px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.3); flex-shrink: 0;
  background: rgba(255,255,255,0.12); color: #fff; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.18s var(--ease-spring), transform 0.15s var(--ease-spring), box-shadow 0.18s var(--ease-spring);
}
.hero-ask-mic:hover { background: rgba(255,255,255,0.22); transform: scale(1.05); }
.hero-ask-mic.listening {
  background: linear-gradient(135deg, #fff, rgba(255,255,255,0.85)); color: var(--accent-deep);
  animation: hero-mic-pulse 1.4s ease-in-out infinite;
}
@keyframes hero-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,255,255,0.5); }
  50% { box-shadow: 0 0 0 8px rgba(255,255,255,0); }
}
.hero-actions { position: relative; display: flex; flex-wrap: wrap; gap: 8px; }
.hero-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  padding: 8px 14px; border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.1);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fff; cursor: pointer;
  transition: background 0.18s var(--ease-spring), transform 0.15s var(--ease-spring), border-color 0.18s var(--ease-spring);
}
.hero-pill:hover { background: rgba(255,255,255,0.2); border-color: rgba(255,255,255,0.32); transform: translateY(-1px); }
.hero-pill:active { transform: scale(0.97); }
.hero-pill .icon { width: 13px; height: 13px; opacity: 0.92; }

/* ---------- Home: chat-first AI agent screen ----------
   Home no longer opens with a greeting/stat hero — it opens with VYLT
   itself, mirroring the "AI Agent — how can I help you today?" reference:
   a centered glowing mascot, a short prompt, a few one-tap suggestion
   chips, and an inline conversation that grows in place as the stylist
   talks to it. All the old greeting/KPI/agenda content lives on the
   Overview tab now — see .kpi-card etc. below, unchanged. */
.agent-home {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  max-width: 620px; margin: 0 auto;
  padding: 36px 12px 8px 12px;
  min-height: calc(100vh - 140px);
}
.agent-home-avatar {
  width: 260px; height: 260px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.9), transparent 60%), linear-gradient(135deg, var(--accent-light), var(--accent-deep) 60%, var(--gold));
  box-shadow: 0 0 0 1px rgba(255,255,255,0.5) inset, 0 16px 40px rgba(109,79,224,0.35);
  color: #fff; margin-bottom: 20px;
  animation: agent-avatar-glow 3.4s ease-in-out infinite;
  position: relative;
}
/* A soft pulsing halo behind the avatar — the same trick every 2026-era
   "AI orb" reference uses to signal "this is alive/listening", without
   needing actual video or a Lottie file. */
.agent-home-avatar::before {
  content: "";
  position: absolute; inset: -14px; border-radius: 50%;
  background: radial-gradient(circle, rgba(109,79,224,0.25), transparent 70%);
  animation: agent-avatar-halo 3.4s ease-in-out infinite;
  z-index: -1;
}
@keyframes agent-avatar-halo {
  0%, 100% { transform: scale(0.94); opacity: 0.7; }
  50% { transform: scale(1.12); opacity: 1; }
}
@keyframes agent-avatar-glow {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.5) inset, 0 16px 40px rgba(109,79,224,0.35); }
  50% { box-shadow: 0 0 0 1px rgba(255,255,255,0.6) inset, 0 20px 54px rgba(109,79,224,0.5); }
}
/* ---------- VYLT mascot animation ----------
   The mascot itself is never static: a gentle float+wiggle on the whole
   drop, a slow color shimmer, and a periodic blink on the eyes. Blink is
   global (works at every size the mascot appears); float/wiggle/shimmer
   are scoped to the large Home avatar only, so tiny 13-18px instances
   elsewhere (chat bubbles, insight card) stay calm and legible. */
.vylt-eye {
  transform-box: fill-box; transform-origin: center;
  animation: vylt-blink 4.5s ease-in-out infinite;
}
.vylt-eyes .vylt-eye:last-child { animation-delay: 0.05s; }
@keyframes vylt-blink {
  0%, 90%, 100% { transform: scaleY(1); }
  95% { transform: scaleY(0.12); }
}
/* Sized as a percentage of the avatar circle (not a fixed px passed from
   JS) so the glyph scales correctly whether the avatar is 260px on desktop
   or the smaller mobile override below — the icon() helper's inline px
   style is intentionally overridden here. */
.agent-home-avatar .icon { width: 62% !important; height: 62% !important; }
.agent-home-avatar .vylt-mascot {
  animation: vylt-float 4.2s ease-in-out infinite, vylt-shimmer 5.5s ease-in-out infinite;
}
@keyframes vylt-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(-6px) rotate(-4deg); }
  50% { transform: translateY(-9px) rotate(0deg); }
  75% { transform: translateY(-6px) rotate(4deg); }
}
@keyframes vylt-shimmer {
  0%, 100% { filter: hue-rotate(0deg) saturate(1) brightness(1); }
  50% { filter: hue-rotate(18deg) saturate(1.25) brightness(1.08); }
}

/* ---------- First-five-minutes "wow" moments ----------
   Three small, honest delight beats added on top of already-real features:
   1) the onboarding recall-proof card (slide-up + mascot pulse + quote
      fade-in), 2) the Today-screen day-one card (count-up stats), and
      3) the Clients-list first-client nudge (soft pulse on the mascot
      glyph). None of these fabricate data or claim anything not already
      true of the saved record. */
.recall-proof-card { animation: recall-proof-in 0.42s var(--ease-spring); }
@keyframes recall-proof-in {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.recall-proof-mascot { display: inline-flex; animation: recall-proof-mascot-pulse 0.6s ease-out 0.15s both; }
@keyframes recall-proof-mascot-pulse {
  0% { transform: scale(0.75); }
  55% { transform: scale(1.25); }
  100% { transform: scale(1); }
}
.recall-proof-quote { color: var(--ink-soft); animation: recall-proof-quote-in 0.85s ease-out 0.25s both; }
@keyframes recall-proof-quote-in {
  0%, 45% { color: var(--ink-soft); }
  100% { color: var(--ink); }
}

.day-one-card { animation: recall-proof-in 0.42s var(--ease-spring); }
.day-one-mascot { display: inline-flex; animation: recall-proof-mascot-pulse 0.6s ease-out 0.1s both; }
.day-one-stat { font-size: 19px; font-weight: 700; }

.first-client-nudge { animation: recall-proof-in 0.4s var(--ease-spring) 0.1s both; }
.first-client-nudge-mascot { display: inline-flex; animation: agent-avatar-halo 3.4s ease-in-out infinite; }

.agent-home-title { font-family: var(--font-serif); font-size: 24px; font-weight: 650; letter-spacing: -0.01em; color: var(--ink); margin: 0 0 4px 0; }
.agent-home-sub { color: var(--ink-soft); font-size: 14.5px; margin: 0 0 20px 0; }
.agent-home-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 22px; }
.chip-suggestion {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink);
  padding: 9px 15px; border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: transform 0.15s var(--ease-spring), border-color 0.18s var(--ease-spring), box-shadow 0.18s var(--ease-spring);
}
.chip-suggestion .icon { width: 13px; height: 13px; color: var(--accent); }
.chip-suggestion:hover { transform: translateY(-1px); border-color: var(--accent-light); box-shadow: var(--shadow-sm); }
.chip-suggestion:active { transform: scale(0.97); }
.agent-home-chat {
  width: 100%; display: flex; flex-direction: column; gap: 10px;
  margin-bottom: 16px; text-align: left;
  max-height: 50vh; overflow-y: auto;
}
.agent-home-chat:empty { margin-bottom: 0; }
.agent-home-form.hero-ask-row {
  width: 100%; max-width: 100%;
  background: rgba(255,255,255,0.72);
  backdrop-filter: blur(20px) saturate(1.35);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  border: 1px solid var(--border);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
  margin-bottom: 0;
  margin-top: auto;
}
.agent-home-form.hero-ask-row:focus-within { background: rgba(255,255,255,0.85); border-color: var(--accent-light); }
.agent-home-form.hero-ask-row .icon { color: var(--accent); opacity: 1; }
.agent-home-form.hero-ask-row input { color: var(--ink); }
.agent-home-form.hero-ask-row input::placeholder { color: var(--ink-soft); }
.agent-home-form .hero-ask-mic { border-color: var(--border); background: var(--accent-soft); color: var(--accent-deep); }
.agent-home-form .hero-ask-mic:hover { background: var(--accent-soft); transform: scale(1.05); }
.agent-home-form .hero-ask-mic.listening { background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff; }
.agent-home-form .hero-ask-send { background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff; }
@media (max-width: 760px) {
  .agent-home { padding-top: 22px; min-height: calc(100vh - 210px); }
  .agent-home-avatar { width: 190px; height: 190px; }
}
@media (max-width: 400px) {
  .agent-home-avatar { width: 160px; height: 160px; }
}

/* ---------- Daily insight card (Dashboard) ----------
   The one thing VYLT thinks you most need to know right now, computed off
   the same numbers as the rest of the app (never invented) and given its
   own distinct surface so it reads as "your AI noticed this" rather than
   blending into the KPI grid below it. */
.insight-card {
  display: flex; align-items: center; gap: 12px;
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 16px;
  background: rgba(255,255,255,0.68);
  backdrop-filter: blur(20px) saturate(1.35);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  border: 1px solid rgba(255,255,255,0.55);
  box-shadow: var(--shadow-sm);
}
.insight-icon {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--accent), var(--plum)); color: #fff;
}
.insight-warn .insight-icon { background: linear-gradient(135deg, var(--warn), #8A5F1F); }
.insight-success .insight-icon { background: linear-gradient(135deg, var(--success), #275A40); }
.insight-text { flex: 1; font-size: 13px; color: var(--ink); line-height: 1.45; }
.insight-cta { flex-shrink: 0; }

/* ---------- Mini calendar (Dashboard) ---------- */
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.cal-month { font-family: var(--font-serif); font-weight: 650; font-size: 15px; }
.cal-nav {
  width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--ink-soft); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s var(--ease-spring), transform 0.15s var(--ease-spring);
}
.cal-nav:hover { background: var(--accent-soft); color: var(--accent); }
.cal-nav:active { transform: scale(0.92); }
.cal-nav .icon { transform: rotate(-90deg); width: 12px; height: 12px; }
.cal-nav-next .icon { transform: rotate(90deg); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-weekdays { margin-bottom: 4px; }
.cal-weekday { text-align: center; font-size: 10.5px; font-weight: 650; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 0; }
.cal-day {
  aspect-ratio: 1; border-radius: 10px; border: none; background: transparent;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  font-size: 12.5px; color: var(--ink); cursor: pointer; position: relative;
  transition: background 0.15s var(--ease-spring), transform 0.15s var(--ease-spring), color 0.15s var(--ease-spring);
}
.cal-day:hover { background: var(--accent-soft); color: var(--accent); }
.cal-day:active { transform: scale(0.94); }
.cal-day-empty { cursor: default; pointer-events: none; }
.cal-day-today { font-weight: 700; box-shadow: inset 0 0 0 1.5px var(--accent); color: var(--accent); }
.cal-day-selected {
  background: linear-gradient(135deg, var(--accent), var(--accent-deep));
  color: #fff; font-weight: 650;
  box-shadow: 0 4px 12px rgba(109,79,224,0.35);
}
.cal-day-selected:hover { background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff; }
.cal-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--accent); }
.cal-day-selected .cal-dot, .cal-day-today .cal-dot { background: currentColor; }

/* ---------- Cards ---------- */
.card {
  /* Glass with a gradient border, not a flat grey hairline — this is the
     single detail that makes a frosted card look "designed" instead of
     "default." The two-layer background trick paints the glass fill in
     the padding-box and a duotone gradient in the border-box beneath it,
     so the border itself is never a flat color. */
  background:
    linear-gradient(rgba(255,255,255,0.7), rgba(255,255,255,0.7)) padding-box,
    linear-gradient(135deg, rgba(124,77,255,0.45), rgba(226,74,166,0.28) 55%, rgba(124,77,255,0.15)) border-box;
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 20px 22px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.25s var(--ease-spring), transform 0.25s var(--ease-spring);
}
.card + .card { margin-top: 12px; }
.card:hover { box-shadow: var(--shadow); }
.card-title {
  font-size: 12.5px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink-faint); margin: 0 0 12px 0;
  display: flex; align-items: center; gap: 7px;
}
.card-title::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }

a.card:hover { box-shadow: var(--glow-accent); border-color: rgba(124,77,255,0.35); transform: translateY(-4px) scale(1.005); }
a.card:active { transform: translateY(-1px) scale(0.99); }

/* KPI cards that jump to a section further down the page instead of a
   dedicated route are real <button> elements (for a11y + no href-to-
   nowhere), reset to look and behave exactly like the <a>/<div> variants. */
button.kpi-jump {
  font-family: inherit; text-align: left; color: inherit;
  width: 100%; cursor: pointer;
}
button.kpi-jump:hover { box-shadow: var(--shadow-lg); border-color: var(--border-strong); transform: translateY(-3px); }
button.kpi-jump:active { transform: translateY(-1px); }

.kpi-card { display: block; border-left: 3px solid var(--border); transition: border-color 0.2s var(--ease-spring), box-shadow 0.25s var(--ease-spring), transform 0.3s var(--ease-spring); }
/* A little playful wobble instead of a straight lift — 2026 apps use one
   asymmetric micro-interaction per surface type so hovering feels alive,
   not just "elevated." Kept tiny (1.5deg) so it reads as bouncy, not silly. */
.kpi-card:hover { transform: translateY(-4px) rotate(-0.6deg) scale(1.015); }
.kpi-card:hover .kpi { color: var(--accent); }
.kpi { transition: color 0.15s var(--ease-spring); font-family: var(--font-serif); }
/* Semantic accent variants — give each KPI a distinct identity instead of
   every number looking the same, without introducing new "loud" colors. */
.kpi-card.kpi-accent { border-left-color: var(--accent); }
.kpi-card.kpi-teal { border-left-color: var(--teal); }
.kpi-card.kpi-teal .kpi { color: var(--teal); }
.kpi-card.kpi-plum { border-left-color: var(--plum); }
.kpi-card.kpi-plum .kpi { color: var(--plum); }
.kpi-card.kpi-gold { border-left-color: var(--gold); }
.kpi-card.kpi-gold .kpi { color: var(--gold); }
/* Colored glow on hover, matched to each variant — small detail, but it's
   the difference between "cards that happen to have a stripe" and cards
   that feel like they belong to a distinct, considered section. */
.kpi-card.kpi-teal:hover { box-shadow: 0 8px 20px rgba(110,74,46,0.20); }
.kpi-card.kpi-plum:hover { box-shadow: 0 8px 20px rgba(122,59,94,0.20); }
.kpi-card.kpi-gold:hover { box-shadow: 0 8px 20px rgba(184,134,58,0.20); }
.kpi-card.kpi-accent:hover { box-shadow: 0 8px 20px rgba(109,79,224,0.20); }

.grid { display: grid; gap: 12px; }
.grid-2 { grid-template-columns: 1fr 1fr; }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* Bento grid (Overview KPIs): a 4-column grid where one tile is declared
   as a 2x2 hero and the rest fall in around it via CSS grid's own
   auto-placement — no manual row/col numbers needed on the small tiles,
   so adding/removing a stat tile in JS never requires touching this CSS.
   This is the asymmetric "one big number, several small ones" bento look
   instead of a uniform stat-card wall. */
.grid-bento {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: minmax(96px, auto);
}
.grid-bento .bento-hero { grid-column: span 2; grid-row: span 2; display: flex; flex-direction: column; justify-content: center; }
.bento-hero-kpi { font-size: 46px !important; }
@media (max-width: 980px) and (min-width: 761px) {
  .grid-bento { grid-template-columns: repeat(2, 1fr); }
  .grid-bento .bento-hero { grid-column: span 2; grid-row: span 1; }
  .bento-hero-kpi { font-size: 34px !important; }
}
@media (max-width: 760px) {
  .grid-bento { grid-template-columns: repeat(2, 1fr); }
  .grid-bento .bento-hero { grid-column: span 2; grid-row: span 1; }
  .bento-hero-kpi { font-size: 34px !important; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 13px; font-weight: 600;
  padding: 9px 16px; border-radius: var(--radius-pill); border: 1px solid transparent;
  cursor: pointer; transition: transform 0.15s var(--ease-spring), background 0.18s var(--ease-spring), box-shadow 0.2s var(--ease-spring);
  white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: scale(0.97) translateY(0); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.btn[disabled]:hover { background: inherit; transform: none; }
.btn:focus-visible, .nav-link:focus-visible, .tab:focus-visible, .pill:focus-visible,
.lang-pill:focus-visible, a.card:focus-visible, .list-row:focus-visible, .kpi-card:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.btn-primary {
  position: relative; overflow: hidden;
  background: linear-gradient(120deg, var(--accent) 0%, var(--accent-pop) 100%);
  background-size: 160% 160%;
  color: #fff;
  box-shadow: 0 3px 10px rgba(124,77,255,0.35);
  transition: transform 0.15s var(--ease-spring), background-position 0.4s var(--ease-spring), box-shadow 0.2s var(--ease-spring);
}
.btn-primary:hover { background-position: 100% 0%; box-shadow: var(--glow-accent); transform: translateY(-2px) scale(1.015); }
.btn-primary:active { transform: scale(0.96) translateY(0); }
/* Playful light-sweep on hover — a thin diagonal highlight travels across
   the button, the classic "premium button" micro-interaction. Runs once
   per hover via animation, not infinitely, so it stays a delight rather
   than a distraction. */
.btn-primary::after {
  content: ""; position: absolute; top: 0; left: -60%; width: 40%; height: 100%;
  background: linear-gradient(115deg, transparent, rgba(255,255,255,0.45), transparent);
  transform: skewX(-20deg);
  opacity: 0; pointer-events: none;
}
.btn-primary:hover::after { opacity: 1; animation: btn-sweep 0.9s ease forwards; }
@keyframes btn-sweep { from { left: -60%; } to { left: 130%; } }
@media (prefers-reduced-motion: reduce) { .btn-primary::after { display: none; } }
.btn-secondary { background: rgba(255,255,255,0.7); backdrop-filter: blur(8px); border-color: var(--border-strong); color: var(--ink); }
.btn-secondary:hover { background: var(--surface); border-color: var(--ink-faint); box-shadow: var(--shadow-sm); }
.btn-ghost { background: transparent; color: var(--ink-soft); }
.btn-ghost:hover { background: var(--bg); color: var(--ink); }
.btn-sm { padding: 6px 12px; font-size: 12px; }
.btn-danger { background: transparent; color: #B04B3A; }
.btn-danger:hover { background: #FBEAE6; }
.btn .icon { width: 13px; height: 13px; }

/* ---------- Forms ---------- */
label { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-soft); margin-bottom: 5px; }
input, select, textarea {
  width: 100%; font-family: inherit; font-size: 13.5px;
  padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border-strong);
  background: #fff; color: var(--ink); outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* Checkboxes/radios shouldn't inherit the width:100% meant for text inputs —
   without this, every checkbox row silently stretches full-width and its
   label wraps oddly, a bug that kept recurring one component at a time. */
input[type="checkbox"], input[type="radio"] { width: auto; }
textarea { resize: vertical; min-height: 64px; }
.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 12px; }
.field-row > .field { flex: 1; }
.checkbox-row { display: flex; align-items: center; gap: 8px; }
.checkbox-row input { width: auto; }

/* ---------- Chips / badges ---------- */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  background: var(--bg); color: var(--ink-soft); border: 1px solid var(--border);
}
.chip-warm { background: var(--warn-soft); color: #8A5F1F; border-color: transparent; }
.chip-cool { background: #E4EEF4; color: #2B5F79; border-color: transparent; }
.chip-neutral { background: var(--border); color: var(--ink-soft); border-color: transparent; }
.chip-accent { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.chip-success { background: var(--success-soft); color: var(--success); border-color: transparent; }

/* ---------- Lists ---------- */
.list-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  cursor: pointer; border: 1px solid transparent;
  transition: background 0.18s var(--ease-spring), border-color 0.18s var(--ease-spring), transform 0.18s var(--ease-spring);
}
.list-row:hover { background: rgba(255,255,255,0.7); border-color: var(--border); transform: translateX(3px); }
.list-row:active { background: var(--accent-soft); transform: translateX(3px) scale(0.99); }
/* Used when the assistant jumps the stylist straight to one specific
   agenda row (e.g. "take me to Taylor's appointment") — a brief glow so
   it's obvious which row it means, not just a silent scroll. */
.list-row-flash { animation: list-row-flash 1.8s var(--ease-spring); }
@keyframes list-row-flash {
  0% { background: var(--accent-soft); border-color: var(--accent); box-shadow: var(--glow-accent); }
  70% { background: var(--accent-soft); border-color: var(--accent); }
  100% { background: transparent; border-color: transparent; box-shadow: none; }
}
.avatar {
  width: 36px; height: 36px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-weight: 650; font-size: 13px; flex-shrink: 0;
}
/* Stable per-client color rotation (see avatarClass() in strand.js) — a
   client list of identical rust circles reads as a placeholder state, not
   a finished product. This is the same restrained secondary palette used
   everywhere else, just applied per-row instead of per-page. */
.avatar.avatar-a { background: var(--accent-soft); color: var(--accent); }
.avatar.avatar-t { background: var(--teal-soft); color: var(--teal); }
.avatar.avatar-p { background: var(--plum-soft); color: var(--plum); }
.avatar.avatar-g { background: var(--gold-soft); color: var(--gold); }

/* Client detail header profile photo — the initials avatar doubles as an
   upload button (real-stylist feedback: wanted a way to put an actual
   client photo on their profile). Hovering/tapping surfaces a small camera
   badge; the file input itself stays visually hidden and spans the whole
   circle so the tap target is generous, not just a tiny icon. */
.avatar-upload-wrap { position: relative; display: block; border-radius: 50%; cursor: pointer; flex-shrink: 0; }
.avatar-photo { border-radius: 50%; object-fit: cover; display: block; }
.avatar-upload-badge {
  position: absolute; bottom: -2px; right: -2px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px #fff;
}
.list-row-main { flex: 1; min-width: 0; }
.list-row-title { font-weight: 600; font-size: 13.5px; }
.list-row-sub { color: var(--ink-faint); font-size: 12px; margin-top: 1px; }
.list-row-meta { color: var(--ink-faint); font-size: 12px; text-align: right; flex-shrink: 0; }

/* ---------- Tabs ----------
   Client profile has 6 tabs (Overview/Formulas/Appointments/Gallery/Notes/
   Hair Passport). At phone widths that's more than fits — without scroll
   the row silently clips and Hair Passport (plus part of Notes) becomes
   unreachable, no visual hint anything's cut off. Let it scroll
   horizontally with its own momentum instead of clipping. */
.tabs {
  display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  padding: 9px 12px; font-size: 13px; font-weight: 600; color: var(--ink-faint);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color 0.12s ease, border-color 0.12s ease;
  white-space: nowrap; flex-shrink: 0;
}
.tab:hover { color: var(--ink); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); border-bottom-width: 2.5px; }

/* ---------- Empty state ---------- */
.empty {
  text-align: center; padding: 48px 20px; color: var(--ink-faint);
}
.empty-icon {
  width: 48px; height: 48px; margin: 0 auto 14px auto;
  border-radius: 16px; background: linear-gradient(135deg, var(--accent-soft), var(--teal-soft)); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-sm);
}
.empty-icon .icon { width: 20px; height: 20px; }
.empty-title { font-weight: 650; color: var(--ink-soft); margin-bottom: 4px; font-size: 14px; }

/* ---------- Brief ---------- */
.brief-box {
  background: linear-gradient(180deg, #FFF 0%, #FCF8F5 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px 24px;
  white-space: pre-wrap;
  font-size: 13.5px;
  line-height: 1.7;
}
.brief-tier { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-faint); margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); }
.dot-ai { background: var(--accent); }

/* ---------- Suggestion cards ---------- */
.suggestion {
  border: 1px solid rgba(255,255,255,0.55); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px;
  background: rgba(255,255,255,0.68);
  backdrop-filter: blur(20px) saturate(1.35);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  transition: box-shadow 0.25s var(--ease-spring), transform 0.25s var(--ease-spring);
}
.suggestion:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
.suggestion-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.suggestion-code { font-family: ui-monospace, SFMono-Regular, monospace; font-weight: 700; font-size: 13.5px; background: var(--bg); padding: 2px 8px; border-radius: 6px; }
.suggestion-title { font-weight: 650; font-size: 13.5px; }
.suggestion-reasoning { color: var(--ink-soft); font-size: 12.5px; line-height: 1.55; }

/* ---------- Modal ---------- */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(28,27,25,0.32);
  display: flex; align-items: flex-start; justify-content: center; padding: 6vh 20px;
  z-index: 50; backdrop-filter: blur(2px);
}
.modal {
  background: rgba(255,255,255,0.86);
  backdrop-filter: blur(28px) saturate(1.4);
  -webkit-backdrop-filter: blur(28px) saturate(1.4);
  border: 1px solid rgba(255,255,255,0.6);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  width: 100%; max-width: 560px; padding: 24px 26px; max-height: 88vh; overflow-y: auto;
  animation: modal-in 0.28s var(--ease-spring);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-title { font-family: var(--font-serif); font-size: 20px; font-weight: 650; margin: 0 0 16px 0; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* ---------- Monetization: plan compare + Pro badge ---------- */
.plan-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
.plan-tile {
  border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 16px;
  background: rgba(255,255,255,0.6);
}
.plan-tile-pro {
  border: 1.5px solid var(--accent);
  background: linear-gradient(165deg, var(--accent-soft), rgba(255,255,255,0.7));
  box-shadow: 0 8px 20px rgba(109,79,224,0.18);
}
.plan-tile-name { font-weight: 700; font-size: 13.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-soft); }
.plan-tile-price { font-family: var(--font-serif); font-size: 26px; font-weight: 650; margin: 4px 0 10px 0; }
.plan-tile-price span { font-size: 13px; font-weight: 500; color: var(--ink-faint); }
.plan-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; font-size: 12.5px; color: var(--ink-soft); }
.plan-features li { padding-left: 18px; position: relative; }
.plan-features li::before { content: "✓"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
.pro-badge {
  display: inline-block; font-size: 9.5px; font-weight: 800; letter-spacing: 0.05em;
  padding: 2px 6px; border-radius: 999px; vertical-align: middle;
  background: linear-gradient(135deg, var(--accent), var(--plum)); color: #fff;
}
@media (max-width: 480px) {
  .plan-compare { grid-template-columns: 1fr; }
}

/* ---------- Misc ---------- */
.muted { color: var(--ink-faint); }
.hstack { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 8px; }
.spread { display: flex; align-items: center; justify-content: space-between; }
/* Plan card header (Settings): title + description on the left, an
   Upgrade/Subscribe button on the right. At mid-range widths (tablet
   layout — sidebar still showing but less room than full desktop) the
   description text is long enough that the button had nowhere to go and
   got clipped off the right edge of the card instead of just wrapping.
   Letting it wrap to its own line fixes that at every width instead of
   only past one specific breakpoint. */
.plan-header-row { flex-wrap: wrap; gap: 10px; }
.plan-header-row > div:first-child { flex: 1 1 240px; min-width: 0; }
.divider { height: 1px; background: var(--border); margin: 16px 0; }
.kpi { font-size: 30px; font-weight: 700; letter-spacing: -0.02em; }
.kpi-label { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.04em; margin-top: 2px; }
.search-input { max-width: 340px; }
.onboard-shell { max-width: 640px; margin: 40px auto; }
.step-dots { display: flex; gap: 6px; margin-bottom: 24px; }
.step-dot { width: 24px; height: 4px; border-radius: 2px; background: var(--border-strong); }
.step-dot.active { background: var(--accent); }

/* ---------- Settings tab strip ---------- */
.settings-tabs {
  display: flex; gap: 4px; margin: 18px 0 20px; border-bottom: 1px solid var(--border);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.settings-tab-btn {
  padding: 9px 14px; border: none; background: none; cursor: pointer;
  font-size: 13px; font-weight: 650; color: var(--muted-fg, #6b6478);
  border-bottom: 2px solid transparent; white-space: nowrap; flex: none;
  transition: color 0.15s var(--ease-spring), border-color 0.15s var(--ease-spring);
}
.settings-tab-btn:hover { color: var(--ink-soft); }
.settings-tab-btn.active { color: var(--accent-deep); border-bottom-color: var(--accent); }
.settings-tab-panel[hidden] { display: none; }

.pill-select { display: flex; flex-wrap: wrap; gap: 8px; }
.pill {
  padding: 7px 13px; border-radius: 999px; border: 1px solid var(--border-strong);
  font-size: 12.5px; font-weight: 600; cursor: pointer; background: rgba(255,255,255,0.65); color: var(--ink-soft);
  backdrop-filter: blur(10px);
  transition: background 0.18s var(--ease-spring), color 0.18s var(--ease-spring), border-color 0.18s var(--ease-spring), transform 0.15s var(--ease-spring), box-shadow 0.18s var(--ease-spring);
}
.pill:hover { border-color: var(--ink-faint); transform: translateY(-1px); }
.pill:active { transform: scale(0.96); }
.pill.selected { background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff; border-color: transparent; box-shadow: 0 4px 12px rgba(109,79,224,0.32); }

/* ---------- Cabinet / Inventory product grid ---------- */
.product-grid-brand { margin-bottom: 22px; }
.product-grid-brand-title { font-size: 13px; font-weight: 700; color: var(--ink-soft); margin-bottom: 10px; letter-spacing: 0.01em; }
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.product-card {
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid rgba(255,255,255,0.55); border-radius: 18px; padding: 14px;
  display: flex; flex-direction: column; gap: 10px; position: relative; overflow: hidden;
  transition: box-shadow 0.2s var(--ease-spring), transform 0.2s var(--ease-spring), border-color 0.2s var(--ease-spring);
}
.product-card:hover { box-shadow: 0 10px 26px rgba(74,50,168,0.14); border-color: var(--accent-light); transform: translateY(-2px); }
.product-card.low-stock { border-color: rgba(200,140,40,0.5); }
.product-card.out-of-stock { border-color: rgba(200,60,60,0.55); }
.product-swatch {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.08);
}
.product-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.product-card-chips { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.product-card-name { font-size: 13.5px; font-weight: 650; line-height: 1.25; }
.product-card-meta { font-size: 11px; color: var(--ink-faint); }
.product-card-opened-note { font-size: 10.5px; color: var(--ink-faint); margin-top: 2px; }
.product-card-low-chip {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--warn-soft); color: #8A5F1F; padding: 3px 8px; border-radius: 999px; align-self: flex-start;
}
.product-card-out-chip {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  background: rgba(200,60,60,0.14); color: #B23A3A; padding: 3px 8px; border-radius: 999px; align-self: flex-start;
}
.product-card-expiry-chip {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 3px 8px; border-radius: 999px; align-self: flex-start;
}
.product-card-expiry-chip.aging { background: rgba(200,140,40,0.14); color: #8A5F1F; }
.product-card-expiry-chip.expired { background: rgba(200,60,60,0.14); color: #B23A3A; }
.product-card-qty-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-top: auto; }
.product-card-qty { display: flex; align-items: center; gap: 6px; }
.product-card-qty span { min-width: 44px; text-align: center; font-weight: 700; font-size: 13px; }
.product-card-actions { display: flex; gap: 6px; }
.needs-attention .product-grid-brand-title { color: #B23A3A; }

/* ---------- Cabinet value snapshot + toolbar ---------- */
.cabinet-stat-row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; }
.cabinet-stat-card {
  flex: 1; min-width: 180px;
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid rgba(255,255,255,0.55); border-radius: 16px; padding: 14px 16px;
}
.cabinet-stat-label { font-size: 11.5px; font-weight: 650; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.03em; }
.cabinet-stat-value { font-family: var(--font-serif); font-size: 24px; font-weight: 650; margin-top: 4px; }
.cabinet-stat-note { font-size: 10.5px; color: var(--ink-faint); margin-top: 4px; }
.cabinet-toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.cabinet-search {
  flex: 1; min-width: 200px; display: flex; align-items: center; gap: 8px;
  background: rgba(255,255,255,0.7); border: 1px solid var(--border-strong); border-radius: 12px; padding: 8px 12px;
  color: var(--ink-faint);
}
.cabinet-search .icon { width: 14px; height: 14px; flex: none; }
.cabinet-search input { border: none; background: transparent; outline: none; font-size: 13px; color: var(--ink); flex: 1; }
.input-select {
  border: 1px solid var(--border-strong); border-radius: 12px; padding: 8px 12px; font-size: 13px;
  background: rgba(255,255,255,0.7); color: var(--ink); cursor: pointer;
}
@media (max-width: 480px) {
  .cabinet-toolbar { flex-direction: column; }
  .cabinet-toolbar .input-select { width: 100%; }
}
.chart-group-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.chart-group-header .product-grid-brand-title { margin-bottom: 0; }
.chart-official-link {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 650;
  color: var(--accent-deep); text-decoration: none; white-space: nowrap;
  background: rgba(109,79,224,0.08); border: 1px solid rgba(109,79,224,0.18);
  border-radius: 999px; padding: 5px 11px; transition: background 0.15s var(--ease-spring), transform 0.15s var(--ease-spring);
}
.chart-official-link:hover { background: rgba(109,79,224,0.16); transform: translateY(-1px); }
.chart-disclaimer {
  display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-soft);
  background: rgba(109,79,224,0.08); border: 1px solid rgba(109,79,224,0.16);
  border-radius: 14px; padding: 10px 14px; margin-bottom: 18px;
}
.color-chart-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.chart-swatch-card {
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid rgba(255,255,255,0.55); border-radius: 16px; padding: 10px;
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  transition: box-shadow 0.2s var(--ease-spring), transform 0.2s var(--ease-spring), border-color 0.2s var(--ease-spring);
}
.chart-swatch-card:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(74,50,168,0.14); }
.chart-swatch-color { width: 100%; aspect-ratio: 1; border-radius: 12px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }
/* Left intentionally blank/white — a level+warmth-approximated hue used to
   render here and real-stylist feedback flagged it as looking like a real
   color match when it wasn't. What's trustworthy is the decoded code text
   below (.chart-swatch-desc), not a guessed swatch color. */
.chart-swatch-blank { background: #fff; }
.chart-swatch-code { font-size: 12px; font-weight: 700; }
.chart-swatch-desc { font-size: 10px; color: var(--ink-soft); line-height: 1.25; }
.chart-swatch-label { font-size: 10.5px; color: var(--ink-faint); }
.chart-swatch-card.chart-out-stock { opacity: 0.55; }
.chart-swatch-card.chart-not-owned { opacity: 0.32; }
@media (max-width: 480px) {
  .color-chart-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
  .product-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ---------- Global voice/chat assistant ---------- */
.assistant-fab {
  position: fixed; bottom: 24px; right: 24px; z-index: 45;
  width: 56px; height: 56px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--plum));
  color: #fff; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 24px rgba(122,59,94,0.35), 0 2px 8px rgba(109,79,224,0.3);
  transition: transform 0.2s var(--ease-spring), box-shadow 0.2s var(--ease-spring);
  animation: assistant-pulse 2.8s ease-in-out infinite;
}
.assistant-fab:hover { transform: scale(1.08) translateY(-2px); }
.assistant-fab:active { transform: scale(0.96); }
.assistant-fab .icon { width: 24px; height: 24px; }
@keyframes assistant-pulse {
  0%, 100% { box-shadow: 0 8px 24px rgba(122,59,94,0.35), 0 0 0 0 rgba(122,59,94,0.28); }
  50% { box-shadow: 0 8px 24px rgba(122,59,94,0.35), 0 0 0 10px rgba(122,59,94,0); }
}
/* Agent-style header — a glowing gradient avatar + live status dot, the
   same visual language as a "Meet [AI Agent]" onboarding/chat screen,
   scaled down to fit a modal header instead of a full hero. */
.agent-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.agent-avatar {
  width: 44px; height: 44px; border-radius: 14px; flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), var(--gold));
  color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.25) inset, 0 6px 18px rgba(184,134,58,0.4);
  animation: hero-mark-glow 3.2s ease-in-out infinite;
}
.agent-name { font-family: var(--font-serif); font-weight: 650; font-size: 16.5px; }
.agent-status { font-size: 11.5px; color: var(--ink-faint); display: flex; align-items: center; gap: 6px; margin-top: 1px; }
.agent-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }

.assistant-list {
  max-height: 340px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
  padding: 4px 2px 10px 2px; margin-bottom: 4px;
}
.assistant-msg { display: flex; align-items: flex-end; gap: 6px; }
.assistant-msg-user { justify-content: flex-end; }
.assistant-msg-assistant { justify-content: flex-start; }
.assistant-msg-avatar {
  width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), var(--plum));
  color: #fff; display: flex; align-items: center; justify-content: center;
}
.assistant-bubble {
  max-width: 78%; padding: 9px 13px; border-radius: 16px; font-size: 13px; line-height: 1.5;
  white-space: pre-wrap;
}
.assistant-msg-user .assistant-bubble { background: linear-gradient(135deg, var(--accent), var(--accent-deep)); color: #fff; border-bottom-right-radius: 5px; }
.assistant-msg-assistant .assistant-bubble { background: var(--bg); color: var(--ink); border: 1px solid var(--border); border-bottom-left-radius: 5px; position: relative; padding-right: 30px; }
.assistant-speak-btn {
  position: absolute; right: 6px; bottom: 6px; width: 20px; height: 20px;
  border: none; border-radius: 50%; background: transparent; color: var(--muted, #8A8398);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  padding: 0; transition: background 0.15s, color 0.15s;
}
.assistant-speak-btn:hover { background: var(--border); color: var(--ink); }
.assistant-speak-btn[data-speaking="1"] { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); animation: assistantSpeakPulse 1.1s ease-in-out infinite; }
@keyframes assistantSpeakPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

/* Pill-shaped "Ask anything…" input bar with a circular gradient send
   button — matches the AI-chat reference screens instead of a plain
   text input + rectangular button. */
.assistant-input-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill); padding: 5px 5px 5px 16px;
  transition: border-color 0.15s var(--ease-spring), box-shadow 0.15s var(--ease-spring);
}
.assistant-input-row:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.assistant-input-row input {
  border: none; background: transparent; padding: 7px 0; box-shadow: none !important;
}
.assistant-send {
  width: 36px; height: 36px; border-radius: 50%; border: none; flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), var(--gold));
  color: #fff; display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: transform 0.15s var(--ease-spring), box-shadow 0.2s var(--ease-spring);
  box-shadow: 0 3px 10px rgba(109,79,224,0.32);
}
.assistant-send:hover { transform: scale(1.07); box-shadow: 0 5px 14px rgba(109,79,224,0.4); }
.assistant-send:active { transform: scale(0.94); }

/* ---------- Phone bottom tab bar ----------
   Hidden on desktop/tablet — the sidebar handles navigation there. Below
   the phone breakpoint it's the opposite: this app is meant to live on a
   stylist's phone, so navigation should look like a native iOS/Android
   tab bar (floating glass, icon-only, thumb reach), not a shrunk sidebar.
   Redesign: was a flat dark charcoal slab (read as "boring settings bar").
   Now a proper frosted-glass iOS pill — light, translucent, saturated
   blur — that tints itself with whatever page you're on via --accent-rgb,
   so the "color cabinet" per-page theming (see body[data-page] in :root)
   reaches all the way down into the nav chrome instead of stopping at
   page backgrounds. */
.bottom-nav {
  display: none;
  position: fixed; left: 12px; right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 44;
  background: linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0.5)),
    radial-gradient(120% 180% at 50% 120%, rgba(var(--accent-rgb),0.16), transparent 70%);
  backdrop-filter: blur(26px) saturate(2.1);
  -webkit-backdrop-filter: blur(26px) saturate(2.1);
  border: 1px solid rgba(255,255,255,0.65);
  border-radius: var(--radius-pill);
  box-shadow: 0 10px 34px rgba(28,27,25,0.14), 0 2px 8px rgba(var(--accent-rgb),0.12), inset 0 1px 0 rgba(255,255,255,0.7);
  padding: 8px 10px;
  align-items: center; justify-content: space-around;
  transition: box-shadow 0.4s ease, background 0.4s ease;
}
.bottom-nav .nav-link.bottom-nav-link {
  padding: 10px; border-radius: 50%;
  color: var(--ink-soft);
  display: flex; align-items: center; justify-content: center;
  transition: background 0.2s var(--ease-spring), color 0.2s var(--ease-spring), transform 0.2s var(--ease-spring);
}
.bottom-nav .nav-link.bottom-nav-link:hover { background: rgba(var(--accent-rgb),0.12); color: var(--accent-deep); }
.bottom-nav .nav-link.bottom-nav-link.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-pop));
  color: #fff;
  box-shadow: 0 4px 14px rgba(var(--accent-rgb),0.45), 0 0 0 1px rgba(255,255,255,0.25) inset;
  transform: translateY(-1px) scale(1.04);
}
/* .nav-link .icon (above) is styled for the dark sidebar rail — near-white
   icon color on a barely-there dark chip. Reused here on the light frosted
   glass bottom bar, that near-white icon reads as invisible. Override with
   a color that actually shows up against glass, and drop the sidebar's
   dark-chip background so it doesn't look like a smudge behind the icon. */
.bottom-nav .nav-link .icon {
  width: 24px !important; height: 24px !important;
  background: transparent;
  color: var(--ink-soft);
  opacity: 1;
}
.bottom-nav .nav-link .icon svg { width: 21px; height: 21px; }
.bottom-nav .nav-link:hover .icon { background: transparent; transform: none; }
.bottom-nav .nav-link.active .icon { color: #fff; background: transparent; opacity: 1; }

@media (max-width: 760px) {
  /* Bigger, not smaller, on mobile — this is the fastest voice-capture
     entry point in the app (see HAIRSTYLIST_REALITY_CHECK.md item 9) and
     needs to be an easy one-thumb hit through gloves, not a precision tap. */
  .assistant-fab { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); right: 16px; width: 64px; height: 64px; }
  .assistant-fab .icon { width: 26px; height: 26px; }
  .bottom-nav { display: flex; }
  .sidebar { display: none; }
  .app-row { padding-top: 8px; }
  .main { padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }
  /* Reorder-soon card (Cabinet): "Copy shopping list" used to sit right
     under the floating assistant FAB (same fixed bottom-right corner) on
     narrow screens, clipping the button's text and part of its tap target.
     Stack title above button here instead of side-by-side so it never
     lands in the FAB's zone. */
  .reorder-soon-header { flex-direction: column; align-items: flex-start; gap: 8px; }
  .reorder-soon-header .btn { align-self: stretch; }
  /* Today screen: the ask bar is the last card in the page, so on shorter
     phone screens (e.g. 667px tall) it landed below the fold behind the
     bottom nav — you had to scroll to find it even though it's the main
     "still need something?" control. Pin it just above the bottom nav
     instead so it's always visible without scrolling, and give the page
     enough bottom padding that the Career Record card above it never gets
     hidden underneath. */
  .today-ask-card {
    position: fixed; left: 12px; right: 12px;
    bottom: calc(76px + env(safe-area-inset-bottom, 0px));
    margin: 0 !important; z-index: 40;
    box-shadow: 0 10px 30px rgba(74,50,168,0.16);
  }
  body:has(.today-ask-card) .main { padding-bottom: calc(160px + env(safe-area-inset-bottom, 0px)); }
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px; }

/* ---------- Tablet: keep the sidebar, tighten the grid ---------- */
@media (max-width: 980px) and (min-width: 761px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .main { padding: 12px 24px 60px 12px; }
}

/* ---------- Small screens: sidebar is fully hidden here (see .bottom-nav
   above) — a stylist's phone gets a native-style bottom tab bar instead of
   a shrunk-down desktop rail. These rules just handle the content grid. --- */
@media (max-width: 760px) {
  .app-row { padding: 8px 8px 0 8px; gap: 8px; }
  .grid-3 { grid-template-columns: 1fr; }
  .grid-4 { grid-template-columns: 1fr 1fr; }
  .grid-2 { grid-template-columns: 1fr; }
  .field-row { flex-direction: column; gap: 0; }
  /* Rows that pack a title + chip + stepper + button (inventory, reorder
     panel) don't fit on one line at narrow widths — let them wrap instead
     of forcing the whole page to scroll horizontally. */
  .list-row { flex-wrap: wrap; row-gap: 8px; }
  .list-row-main { flex-basis: 100%; }
  .spread { flex-wrap: wrap; row-gap: 8px; }
}

/* ---------- First-ever-open cold splash screen ---------- */
/* Full-viewport, dark, flowing color-swirl in the app's own fashion-color
   palette (accent/plum/teal/gold — the same names used for hairProfile
   theming elsewhere) — meant to read like a real app's animated cold open,
   not a static "please sign in" wall. Shown exactly once per device, then
   fades into the existing Welcome Gate. See renderSplashScreen() in
   js/strand.js. */
.vylt-splash {
  position: fixed; inset: 0; z-index: 9999;
  background: radial-gradient(circle at 30% 20%, #241C2E 0%, #17121C 55%, #0F0C12 100%);
  overflow: hidden; display: flex; align-items: center; justify-content: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  animation: vyltSplashIn 0.5s var(--ease-spring, ease-out) both;
}
.vylt-splash-exit { animation: vyltSplashOut 0.24s ease-in both; }
@keyframes vyltSplashIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes vyltSplashOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(1.03); } }

.vylt-splash-blobs { position: absolute; inset: 0; filter: blur(60px); mix-blend-mode: screen; }
.vylt-splash-blob { position: absolute; border-radius: 50%; opacity: 0.75; }
.vylt-splash-blob.b1 { width: 46vmax; height: 46vmax; left: -14vmax; top: -12vmax; background: radial-gradient(circle, var(--accent-pop, #FF6F5E), transparent 70%); animation: vyltBlobDrift1 16s ease-in-out infinite; }
.vylt-splash-blob.b2 { width: 40vmax; height: 40vmax; right: -12vmax; top: -8vmax; background: radial-gradient(circle, var(--plum, #7A3B5E), transparent 70%); animation: vyltBlobDrift2 19s ease-in-out infinite; }
.vylt-splash-blob.b3 { width: 42vmax; height: 42vmax; left: -8vmax; bottom: -16vmax; background: radial-gradient(circle, var(--gold, #B8863A), transparent 70%); animation: vyltBlobDrift3 21s ease-in-out infinite; }
.vylt-splash-blob.b4 { width: 38vmax; height: 38vmax; right: -10vmax; bottom: -14vmax; background: radial-gradient(circle, var(--accent, #FFA800), transparent 70%); animation: vyltBlobDrift4 17s ease-in-out infinite; }
@keyframes vyltBlobDrift1 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(6vmax,5vmax) scale(1.12); } }
@keyframes vyltBlobDrift2 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-5vmax,6vmax) scale(1.08); } }
@keyframes vyltBlobDrift3 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(5vmax,-5vmax) scale(1.1); } }
@keyframes vyltBlobDrift4 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-6vmax,-4vmax) scale(1.14); } }

.vylt-splash-sheen {
  position: absolute; inset: -20%; pointer-events: none;
  background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,0.10) 48%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0.10) 52%, transparent 60%);
  animation: vyltSheenSweep 3.4s ease-in-out infinite;
}
@keyframes vyltSheenSweep { 0% { transform: translateX(-30%); } 100% { transform: translateX(30%); } }

.vylt-splash-content {
  position: relative; z-index: 1; text-align: center; color: #fff; padding: 0 24px;
  animation: vyltContentIn 0.7s 0.15s var(--ease-spring, ease-out) both;
}
@keyframes vyltContentIn { from { opacity: 0; transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
.vylt-splash-mark { display: flex; justify-content: center; margin-bottom: 14px; filter: drop-shadow(0 12px 30px rgba(0,0,0,0.35)); }
.vylt-splash-mark svg { animation: vyltMarkFloat 3.2s ease-in-out infinite; }
@keyframes vyltMarkFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
.vylt-splash-word { font-family: var(--font-serif); font-size: 42px; font-weight: 650; letter-spacing: 0.5px; }
.vylt-splash-tag { margin: 8px 0 34px; font-size: 14.5px; color: rgba(255,255,255,0.72); }
.vylt-splash-cta {
  display: inline-flex; align-items: center; gap: 8px; padding: 11px 22px; border-radius: var(--radius-pill);
  border: 1px solid rgba(255,255,255,0.28); background: rgba(255,255,255,0.08); backdrop-filter: blur(6px);
  font-size: 13px; font-weight: 600; letter-spacing: 0.3px; animation: vyltCtaPulse 1.8s ease-in-out infinite;
}
@keyframes vyltCtaPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }

@media (prefers-reduced-motion: reduce) {
  .vylt-splash, .vylt-splash-content, .vylt-splash-mark svg,
  .vylt-splash-blob, .vylt-splash-sheen, .vylt-splash-cta { animation: none !important; }
}

/* =============================================================
   NATIVE APP (Capacitor iOS/Android shell) — .is-native-app only.
   Every rule below is scoped under this class, set by strand.js only
   when window.Capacitor.isNativePlatform() is true. The plain web app
   (loaded in any desktop or mobile browser) never gets this class, so
   none of this touches the existing web layout the founder's wife
   depends on. See strand.js for the matching JS-side branches
   (renderDashboardNative, the onboarding step-1 field cut, the client
   Overview accordion pass, the Save Formula progressive-disclosure
   pass, and the Cabinet toolbar collapse).
   ============================================================= */

/* Shallow-nav check: the bottom tab bar is the only top-level nav in
   native, regardless of the device's actual width — a native iPad app
   window can be wider than the 760px mobile breakpoint the web version
   uses to decide sidebar-vs-bottom-bar, but it's still a phone-shaped
   native shell, not a desktop browser tab. Force the phone nav on and
   the desktop sidebar rail off unconditionally so the bottom bar can
   never end up buried under (or replaced by) sidebar chrome. */
.is-native-app .bottom-nav { display: flex !important; }
.is-native-app .sidebar { display: none !important; }
.is-native-app .app-row { padding-top: 8px; }
.is-native-app .main { padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }

/* Bigger tap targets app-wide in native — thumb-driven, no mouse pointer
   precision to rely on. */
.is-native-app .btn { min-height: 44px; }
.is-native-app .btn-sm { min-height: 38px; }
.is-native-app input, .is-native-app select, .is-native-app textarea { min-height: 44px; font-size: 16px; }
.is-native-app .field { margin-bottom: 16px; }

/* Generic "see more" collapse pattern, reused by Today's secondary
   section, Save Formula's More Details, and Cabinet's search/filter
   toggle — one hidden block behind one ghost button, expand on tap. */
.is-native-app .native-secondary-collapsed,
.is-native-app .native-more-fields.native-collapsed,
.is-native-app #inv-toolbar.native-collapsed {
  display: none;
}
.is-native-app .native-secondary-open,
.is-native-app .native-more-fields.native-open,
.is-native-app #inv-toolbar.native-open {
  display: block;
  animation: nativeRevealIn 0.22s var(--ease-spring, ease-out) both;
}
@keyframes nativeRevealIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.native-see-more { font-weight: 600; }

/* Today (native): hero glance card gets a bit more presence since it's
   now the entire above-the-fold screen instead of one card among five. */
.is-native-app .native-hero-card { padding: 18px 16px; }

/* Client profile (native): dense multi-panel Overview collapses into a
   tap-to-open accordion — header always visible (title + primary action),
   body hidden until tapped. */
.is-native-app .native-accordion-header { cursor: pointer; }
.is-native-app .native-accordion-card { overflow: hidden; transition: padding 0.2s ease; }
.is-native-app .native-accordion-card.native-accordion-closed { padding-bottom: 16px; }
.is-native-app .native-accordion-card.native-accordion-closed > *:not(.native-accordion-header) { display: none; }
.is-native-app .native-accordion-card.native-accordion-open > .native-accordion-header::after {
  content: "︿"; float: right; color: var(--ink-soft); font-size: 13px;
}
.is-native-app .native-accordion-card.native-accordion-closed > .native-accordion-header::after {
  content: "﹀"; float: right; color: var(--ink-soft); font-size: 13px;
}

/* Save Formula (native): single column (drop the side-by-side Formula
   Intelligence preview column — it's a "while you type" helper, not
   something that needs equal billing on a narrow screen), and pin Save
   to the bottom thumb zone instead of it scrolling away after a long
   form. */
.is-native-app .native-formula-grid { display: block; }
.is-native-app .native-fi-collapsed { margin-top: 14px; }
.is-native-app .native-sticky-save {
  position: sticky; bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  z-index: 5; box-shadow: 0 -6px 18px rgba(28,27,25,0.14);
}

/* Cabinet (native): fewer visible details per product card, bigger
   touch targets on the qty/action row, list-style instead of a dense
   info-packed grid card. */
.is-native-app .native-compact-list .product-card-meta,
.is-native-app .native-compact-list .product-card-opened-note {
  display: none;
}
.is-native-app .native-compact-list .product-card-qty-row button,
.is-native-app .native-compact-list .product-card-actions button {
  min-height: 40px; min-width: 40px;
}
