/* ====================================================================
   BudiHub shared theme — Design-Language Spec, Phase 0 (2026-08-14)
   Linked from every page (app.html, worker-app.html, store-owner.html,
   admin.html, BudiHub's own index.html) instead of each page carrying
   its own duplicated <style> token block. Mirrors the existing shared-JS
   pattern (shell-core.js) — one file, not a convention everyone has to
   remember to repeat.

   Token values below are a full remap of the real, audited --budi-*
   token set already in use across all five pages (16 names found via
   grep before this file was written) onto the new "FOX Green" palette —
   not a parallel token system. Only tokens the approved spec gave new
   values for were changed (accent, cta [new], bg, text, success/danger/
   warning/neutral [new], and dark-mode card-bg). Every other existing
   token (anchor, border, muted, panel-bg, tag-bg, status-bg, notice-bg,
   badge-bg, danger-bg, success-bg, disabled-border, disabled-text) kept
   its current value — the spec didn't respecify these, so changing them
   would be inventing colors nobody reviewed. The *-bg tint tokens in
   dark mode in particular were tuned against the OLD dark background
   (#1a1a1a) and haven't been re-checked against the new, much darker
   #0B0F0E — worth a visual pass once Phase 1 (Admin) is actually being
   built and can be looked at for real, not something to guess at now.
   ==================================================================== */

/* Design system formalization (2026-09-01) — typeface, type scale, spacing scale. Inter,
   loaded once here rather than per-page, since every page already links this one file.
   Chosen because it's built specifically for UI legibility at small sizes and covers the
   full 400-800 weight range this app already leans on for its big numbers (kpi-value,
   hero-metric-value, tier-tile-value) — same reasoning most modern app UIs (Stripe, Linear,
   GitHub, Notion) standardized on it for. Real fallback stack after it, not just "sans-serif". */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=JetBrains+Mono:wght@700&display=swap');

:root {
  /* Type scale — fills the gaps around sizes already established and left untouched (13px
     kpi-label, 20px tile-label "general fonts", 22px kpi-value, 34px hero-metric-value). */
  --budi-text-2xs: 12px;
  --budi-text-xs: 13px;
  --budi-text-sm: 15px;
  --budi-text-base: 17px;
  --budi-text-lg: 20px;
  --budi-text-xl: 22px;
  --budi-text-2xl: 26px;
  --budi-text-3xl: 34px;
  /* Weight ramp — 800 (extrabold) stays reserved for hero/KPI numbers only, so it keeps
     meaning "the one big number on this screen" instead of being diluted everywhere. */
  --budi-weight-regular: 400;
  --budi-weight-semibold: 600;
  --budi-weight-bold: 700;
  --budi-weight-extrabold: 800;
  /* Spacing scale — a 4px-based ladder for new/updated components to pull from going forward.
     Not a retrofit of every existing hardcoded padding/margin/gap in the app (that's a large,
     separate undertaking, not something to rewrite wholesale in one pass) — this just gives
     new work a real system to use instead of another one-off pixel value. */
  --budi-space-1: 4px;
  --budi-space-2: 8px;
  --budi-space-3: 12px;
  --budi-space-4: 16px;
  --budi-space-6: 24px;
  --budi-space-8: 32px;
  /* New tokens (2026-09-01), verified directly from the real budihub.app marketing site's own
     CSS (extracted from the site's own zip, not guessed) — added as available tokens only,
     nothing existing was changed to use them yet, so nothing looks different until a future
     component deliberately reaches for one. --budi-border/--budi-accent etc. below were already
     an exact hex match to the site's own --border/--green/--charcoal/--near-black/--muted, which
     confirms the app and the marketing site were already well-aligned before this pass. No
     dark-mode variants defined for these four yet — that's premature until something actually
     uses one in a dark-sensitive way; add the pairing then, not speculatively now. */
  --budi-amber: #F8C34C;
  --budi-amber-dark: #D99B1F;
  --budi-green-dark: #0F7C37;
  --budi-green-pale: #F0FDF4;
  --budi-accent: #16A34A;
  --budi-anchor: #2C2C2A;
  --budi-cta: #111827;
  --budi-bg: #F8F9F7;
  --budi-text: #1F2937;
  --budi-card-bg: #ffffff;
  --budi-border: #cccccc;
  --budi-muted: #666666;
  --budi-panel-bg: #f7f7f5;
  --budi-tag-bg: #eeeeee;
  --budi-status-bg: #e0ffe0;
  --budi-notice-bg: #fff3cd;
  --budi-badge-bg: #fff8e0;
  --budi-danger-bg: #f8d7da;
  --budi-success-bg: #d7f5e9;
  --budi-disabled-border: #cccccc;
  --budi-disabled-text: #999999;
  --budi-success: #16A34A;
  --budi-danger: #DC2626;
  --budi-warning: #F59E0B;
  --budi-neutral: #6B7280;
  /* Phase 10B consistency pass (2026-08-14) — text-on-tinted-badge colors, separate from
     --budi-warning itself (used elsewhere as a plain accent, e.g. progress-bar-fill.warn,
     where the un-tinted #F59E0B already reads fine). --budi-warning at full brightness on
     top of --budi-badge-bg's pale cream measured ~2.1:1 in light mode — these darker values
     match the already-correct .pill-tier-gold precedent. Bronze/gold tier-pill text needs
     its own pair for the same reason (fixed hex against a background that itself changes
     per theme). */
  --budi-warning-text: #B45309;
  --budi-tier-bronze-text: #92400E;
  --budi-tier-gold-text: #B45309;
  /* User-requested (2026-08-15) — Worker Home's tier tile needs the same "own color per
     tier" treatment .pill-tier-* already established elsewhere, extended with a silver
     token (the pill version hardcoded #6B7280 for both themes since it was only ever a
     small badge; the tile is bigger/bolder, so it gets a real light/dark pair like bronze
     and gold already have). */
  --budi-tier-silver-text: #4B5563;
}

:root[data-theme="dark"] {
  --budi-anchor: #e8e8e6;
  --budi-bg: #0B0F0E;
  --budi-text: #F3F4F6;
  --budi-card-bg: #14181A;
  --budi-border: #3d3d3a;
  --budi-muted: #a0a09c;
  --budi-panel-bg: #242422;
  --budi-tag-bg: #3d3d3a;
  --budi-status-bg: #1e3a2a;
  /* Dark-mode audit (2026-09-06) — was #4a3a10, a straight proportional darken of the light
     theme's pale yellow (#fff3cd). Red/green hues stay recognizable when darkened this way
     (see --budi-danger-bg/--budi-success-bg below), but yellow collapses into a muddy brown at
     low lightness — a real color-perception effect, not a copy-paste miss. Lightened and
     shifted a touch warmer/more amber so it reads as "dark gold" rather than "mud," still
     clearly distinct from --budi-card-bg/--budi-panel-bg around it. */
  --budi-notice-bg: #5c451c;
  --budi-badge-bg: #5c451c;
  --budi-danger-bg: #4a1f24;
  --budi-success-bg: #163d2c;
  --budi-disabled-border: #3d3d3a;
  --budi-disabled-text: #777773;
  /* --budi-accent/--budi-cta/--budi-success/--budi-danger/--budi-warning/
     --budi-neutral are deliberately NOT redefined here — validated in the
     reviewed dark-mode mockup as holding contrast unadjusted, same hex
     in both themes. */
  /* Phase 10B — the three text-on-badge tokens above DO need dark-mode redefinition, unlike
     the plain semantic colors: their light-mode values were darkened specifically to read on
     a pale cream badge, which reads as murky/low-contrast once that same badge background
     goes dark-brown (--budi-badge-bg/--budi-tag-bg both darken in this block). Lightened
     instead, back toward --budi-warning's own full-brightness value, which already measured
     fine against the dark badge backgrounds. */
  --budi-warning-text: #F59E0B;
  --budi-tier-bronze-text: #D97706;
  --budi-tier-gold-text: #F59E0B;
  --budi-tier-silver-text: #9CA3AF;
}

/* Per-app accent override REMOVED (2026-09-01) — user's explicit call as part of formalizing
   one uniform design system: BudiCart/BudiEats no longer get their own accent color, every
   vertical now shares the single suite-wide --budi-accent green. Semantic colors (success/
   danger/warning) were never touched by this override anyway, so nothing else changes.
   Removed: the light-mode #3E9B4F/#E8622C --budi-accent + --vertical-accent overrides for
   #view-budicart/#view-budieats/.vertical-budicart/.vertical-budieats, and the dark-mode
   BudiCart-specific green tint on #view-budicart's card-bg/border. store-owner.html's own
   matching vendor-theme-budicart/vendor-theme-budieats rules were removed the same way. */

/* Vertical header (wordmark + tagline) — was duplicated identically in
   app.html and worker-app.html; centralized here instead. */
.vertical-header { margin: 14px 0 6px; }
.vertical-wordmark { font-family: 'Segoe UI Rounded', ui-rounded, 'SF Pro Rounded', system-ui, sans-serif; font-size: 30px; font-weight: 800; margin: 0; color: var(--vertical-accent, var(--budi-accent)); }
.vertical-tagline { font-size: 14px; color: var(--budi-muted); margin: 2px 0 0; font-style: italic; }

/* Real brand logo files (added 2026-08-19), replacing the text-drawn wordmarks above wherever
   a .brand-logo img sits inside .vertical-wordmark / .wordmark. */
.brand-logo { height: 44px; width: auto; display: block; }
.wordmark .brand-logo, header .brand-logo { height: 52px; }

/* ====================================================================
   Spacing and shape
   ==================================================================== */

.card {
  border: 0.5px solid var(--budi-border);
  border-radius: 16px;
  padding: 20px;
  margin: 12px 0;
  background: var(--budi-card-bg);
}
.card h3 { margin: 0 0 6px 0; }
:root[data-theme="dark"] .card { border: none; }

/* UI polish pass (2026-08-30) — button.secondary existed only in worker-app.html/
   store-owner.html's own local <style> blocks; app.html and admin.html never had it, so a
   Cancel/Decline button coded with class="secondary" in those two apps silently fell back to
   the plain solid-accent button rule — identical to the primary action right next to it.
   Centralizing here (removed from both local copies) makes it available consistently in all
   four live shells. button.danger is new: same outlined shape, for actions that actually
   remove/revoke something rather than just declining or backing out — a genuine second tier
   of visual hierarchy that never existed before this pass (Approve/Reject and Remove/Delete
   buttons across the suite were audited and swept to use these; see the sweep's own commit
   for the full list of call sites). */
button.secondary { background: none; border: 1px solid var(--budi-border); color: var(--budi-text); }
button.danger { background: none; border: 1px solid var(--budi-danger); color: var(--budi-danger); }
button.danger:hover { background: var(--budi-danger-bg); }

/* Interactive states, formalized (2026-09-01) — before this, the ENTIRE suite's only hover
   feedback was a blanket "button:hover { opacity:0.9; }" duplicated in every page's own inline
   <style> (removed there in favor of this one centralized rule — those pages load this file
   first, so this now wins on source order once the duplicate opacity rule is gone), applied
   identically to every button regardless of type. There was no :active (pressed) feedback and,
   more importantly, NO :focus indicator anywhere in the suite — a real accessibility gap for
   keyboard users, since nothing showed which element currently had focus.
   Upgraded same day to match the real lift+shadow pattern verified from budihub.app's own CSS
   (translateY + a soft, color-matched box-shadow) rather than the flatter brightness-filter
   this started as — a plain button gets a green-tinted glow (matching the site's .btn-primary),
   a .kpi-stat-block tile lifts further with a neutral dark shadow (matching the site's
   .app-card), and outlined .secondary/.danger buttons just lift without a colored glow (closer
   to the site's .btn-outline, which inverts instead of glowing — full invert wasn't adopted
   since these two classes already had their own established hover backgrounds from the earlier
   UI polish pass, this only adds the matching lift motion on top of those). :focus-visible (not
   bare :focus) shows the ring only for keyboard navigation, not every mouse click — the modern
   standard that avoids the old "ugly ring on every click" complaint that used to lead sites to
   disable focus outlines entirely (which is effectively the state this app was in). */
button:not(:disabled) { transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; }
button:not(:disabled):hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(22, 163, 74, 0.28); }
button:not(:disabled):active { transform: translateY(0); box-shadow: none; }
button.secondary:not(:disabled):hover, button.danger:not(:disabled):hover { box-shadow: none; }
button.kpi-stat-block:not(:disabled):hover { transform: translateY(-4px); box-shadow: 0 16px 32px rgba(17, 24, 39, 0.08); border-color: var(--budi-accent); cursor: pointer; }
button.kpi-stat-block:not(:disabled):active { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(17, 24, 39, 0.08); }
button:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
outline: 2px solid var(--budi-accent);
outline-offset: 2px;
}

/* UI polish pass (2026-08-30) — these five base rules were byte-identical across all 4 live
   shells' own <style> blocks (input/textarea/select, .sub-form + its inputs, .row) EXCEPT
   store-owner.html was missing ".row button" entirely — a real, visible bug: every list row
   in the Store app (31 places: staff list, saved cards, etc.) rendered its trailing button as
   a full-width block instead of the small inline button every other app shows, purely because
   that one rule never got copied over when the other three files' local copies drifted apart.
   Centralizing here removes the duplication (same reasoning as .status-tag/.shell-loading
   above) and fixes store-owner.html's gap for free — no per-file copy left to fall out of
   sync again. */
input, textarea, select { display: block; width: 100%; margin: 8px 0; padding: 8px; box-sizing: border-box; background: var(--budi-card-bg); color: var(--budi-text); border: 1px solid var(--budi-border); }
/* UI polish pass (2026-08-30) — real bug found on audit: the base rule above applies to
   EVERY input including checkbox/radio, which have no business being width:100%/display:block.
   34 call sites across the app already knew to override this manually (inconsistently: some
   'width:auto;display:inline-block', some 'display:inline;width:auto', etc.) — but 19 others
   never did, including every single Terms-of-Service/consent checkbox in every onboarding flow
   (Worker, Customer, Store, Admin, the facial-recognition and unbadged-liability consent
   screens, event opt-in) and the errand-timing ASAP/Scheduled radios. Those were rendering as
   giant full-width, block-level boxes instead of a normal small checkbox. Fixing centrally
   here makes every existing manual override redundant-but-harmless and fixes the 19 broken
   ones without touching each call site. */
input[type="checkbox"], input[type="radio"] { display: inline-block; width: auto; margin: 4px 6px 4px 0; padding: 0; }
.sub-form { margin-top: 10px; border-top: 1px solid var(--budi-border); padding-top: 10px; }
.sub-form input, .sub-form select, .sub-form textarea { display: inline-block; width: 45%; margin-right: 2%; }
.row { border-top: 1px solid var(--budi-border); padding: 8px 0; font-size: 13px; }
.row button { display: inline; padding: 4px 10px; margin-left: 8px; }
/* UI polish pass (2026-08-30) — 6th identically-duplicated rule found across all 4 shells.
   Also visually aligned it toward .insight-callout's look (4px->10px radius, tighter padding
   bump) rather than leaving it a flatter, more cramped box — a code comment elsewhere in this
   file already calls .notice "legacy" (superseded by .insight-callout for tip/warning-style
   messages), but 16 live call sites across Admin/Customer/Worker still use it for plain
   single-line notes and multi-line data-dump panels that don't fit .insight-callout's
   icon+flex structure. Rather than rewrite all 16 (real risk of breaking embedded dynamic
   logic for no visual gain), this keeps .notice as the simpler no-icon variant of the same
   family, just no longer visually clashing with it. */
.notice { background: var(--budi-notice-bg); color: var(--budi-text); padding: 10px 12px; border-radius: 10px; font-size: 12px; margin-top: 6px; }

/* Any grid/stack of cards should use this instead of relying on each
   card's own margin, so the 12px gap is consistent whether cards stack
   vertically or sit side by side. */
.card-stack { display: flex; flex-direction: column; gap: 12px; }
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }

/* Quick Jobs (Prompt 32-33, 2026-09-23) — a compact browse grid, roughly half the width of a
   full .card per tile (user feedback: the old single-column .card list read as too wide), 2+
   columns on anything wider than a phone instead of always stacking full-width. Titles use the
   suite's own "general fonts" tile convention (.tile-label — bold 20px, same class every other
   tile grid in the suite uses) and .centered's exact centering rule, for uniformity with
   Activity/Rentals/every other tile grid rather than inventing separate styling here. */
.quick-jobs-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 12px 0; }
.quick-job-card { border: 0.5px solid var(--budi-border); border-radius: 16px; padding: 14px; background: var(--budi-card-bg); display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; }
:root[data-theme="dark"] .quick-job-card { border: none; }
.quick-job-card h4.tile-label { margin: 0; }
.quick-job-card .quick-job-tagline { font-size: 12px; color: var(--budi-muted); margin: 0; flex-grow: 1; }
.quick-job-card .quick-job-meta { font-size: 12px; font-weight: 600; }
.quick-job-card button { font-size: 13px; padding: 8px 10px; margin: 0; width: 100%; }

/* User-requested (2026-08-27) — the "100% of tips" line under the Earnings vertical tile grid,
   styled to visibly stand out from ordinary body text (its own high-contrast badge/banner) for
   a clean screenshot/video capture. One shared line for all 4 tiles, not per-tile. Fixed black
   background + brand-green body text + amber "Tips" (matching the amber dot over the "i" in
   the budiHub wordmark) — deliberately hardcoded, not theme-token-driven, so this graphic looks
   identical in every recording regardless of the viewer's light/dark setting. */
/* Reusable "brand callout" banner (2026-08-27) — black bg, fox green (#16A34A) body text,
   amber (#F59E0B) highlight span matching the dot over the "i" in the budiHub wordmark.
   First used on the Earnings tab's tips line, now also the Shopper Bonus footnote — same
   treatment, deliberately hardcoded (not theme-token-driven) so it looks identical in every
   recording regardless of the viewer's light/dark setting. */
.brand-callout { margin-top: 10px; padding: 14px 16px; background: #000000; color: #16A34A; border-radius: 8px; font-size: 19px; font-weight: 800; line-height: 1.45; }
.brand-callout .brand-callout-amber { color: #F59E0B; }

/* ====================================================================
   Reusable component patterns — build once, apply per-surface in
   Phase 1 (Admin) / Phase 2 (BudiEats) / Phase 3 (BudiCart) and beyond.
   Nothing on this page wires these into a real screen yet — that's
   deliberately later-phase work, this is the shared layer they'll draw
   from.
   ==================================================================== */

/* 1. Hero metric card — the ONE most-important number per screen. Dark
   background in both themes, but a DIFFERENT dark token per theme per
   the spec: --budi-cta on an otherwise-light page (so it visually pops),
   --budi-card-bg on an already-dark page (so it doesn't look identical
   to a plain dark card, just distinctly composed instead). */
.hero-metric-card {
  background: var(--budi-cta);
  color: #F3F4F6;
  border-radius: 16px;
  padding: 20px;
  margin: 12px 0;
}
:root[data-theme="dark"] .hero-metric-card { background: var(--budi-card-bg); }
.hero-metric-card .hero-metric-label { font-size: 13px; color: #9CA3AF; margin: 0 0 4px; }
.hero-metric-card .hero-metric-value { font-size: 34px; font-weight: 800; margin: 0; font-variant-numeric: tabular-nums; }
.hero-metric-card .hero-metric-delta { display: inline-block; font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: 10px; margin-top: 6px; }
.hero-metric-card .hero-metric-delta.up { background: rgba(22,163,74,0.2); color: #4ADE80; }
.hero-metric-card .hero-metric-delta.down { background: rgba(220,38,38,0.2); color: #F87171; }
.hero-metric-card .hero-metric-caption { font-size: 12px; color: #9CA3AF; margin-top: 4px; }

/* 2. KPI stat block — secondary numbers, 2-4 side by side. Use .card-grid
   as the wrapper. */
.kpi-stat-block { background: var(--budi-card-bg); border: 0.5px solid var(--budi-border); border-radius: 16px; padding: 16px; }
:root[data-theme="dark"] .kpi-stat-block { border: none; }
/* Customer-app UI redesign (2026-08-31 round 2) — a clickable .kpi-stat-block used as a
   drill-through tile (BudiBid's Activity/Earnings and Activity's own 4-tile grid) has no
   "back" control now — both tile bars stay visible permanently, so this active/selected state
   is how a person can tell which tile they're currently looking the content of. */
button.kpi-stat-block.active { border: 2px solid var(--budi-accent); background: #A8A29E; }
button.kpi-stat-block.active .kpi-label,
button.kpi-stat-block.active .kpi-value { color: #fff; }
.kpi-stat-block .kpi-label { font-size: 12px; color: var(--budi-muted); margin: 0 0 4px; }
.kpi-stat-block .kpi-value { font-size: 22px; font-weight: 800; margin: 0; color: var(--budi-text); font-variant-numeric: tabular-nums; }
.kpi-stat-block .kpi-delta { font-size: 11px; font-weight: 700; margin-top: 2px; }
.kpi-stat-block .kpi-delta.up { color: var(--budi-success); }
.kpi-stat-block .kpi-delta.down { color: var(--budi-danger); }

/* 3. Horizontal progress bar. Set the fill width inline per instance
   (style="width: 62%") and add .warn or .danger for context — plain
   .progress-bar-fill defaults to the healthy/on-track color. */
.progress-bar-row { display: flex; justify-content: space-between; font-size: 12px; color: var(--budi-muted); margin-bottom: 4px; }
.progress-bar-track { background: var(--budi-tag-bg); border-radius: 8px; height: 8px; overflow: hidden; }
.progress-bar-fill { height: 100%; border-radius: 8px; background: var(--budi-success); }
.progress-bar-fill.warn { background: var(--budi-warning); }
.progress-bar-fill.danger { background: var(--budi-danger); }

/* 4. Semicircular gauge — RESERVED for Platinum-eligibility progress and
   BudiPro's monthly-budget-used ratio only (see spec). Pure-CSS conic-
   gradient semicircle: set --gauge-pct (0-100) inline per instance, e.g.
   style="--gauge-pct: 62". Not yet visually verified in a real browser
   (standing limitation all session, no browser tool available) — worth
   a first-look check once Phase 1 actually renders one for real. */
.gauge-semicircle {
  --gauge-pct: 0;
  width: 160px; height: 80px;
  position: relative;
  margin: 0 auto;
  overflow: hidden;
}
.gauge-semicircle::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 160px; height: 160px;
  border-radius: 50%;
  background: conic-gradient(from 270deg, var(--budi-accent) calc(var(--gauge-pct) * 1.8deg), var(--budi-tag-bg) 0);
  /* Design-Language Spec Phase 5 — explicit correction: this read as a solid filled pie
     slice, not a "bar" gauge. Masking out the center turns the same conic-gradient into a
     genuine thick ring/donut-style arc (like a real progress bar bent into a semicircle)
     instead of a solid half-disc. Stroke width is the one tunable number here (18px against
     an 80px radius) — no exact px was specified, chosen for visual weight at this size. */
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 18px), #000 calc(100% - 18px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 18px), #000 calc(100% - 18px));
}
.gauge-semicircle .gauge-value { position: absolute; bottom: 0; left: 0; right: 0; text-align: center; font-size: 20px; font-weight: 800; color: var(--budi-text); }
.gauge-semicircle .gauge-target { position: absolute; bottom: -18px; left: 0; right: 0; text-align: center; font-size: 11px; color: var(--budi-muted); }

/* 5. Segmented category bar + legend — companion/summary view to the
   Prompt N pie charts, not a replacement for them. */
.segmented-bar { display: flex; height: 14px; border-radius: 7px; overflow: hidden; }
.segmented-bar-segment { height: 100%; }
.segmented-bar-legend { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; font-size: 12px; }
.segmented-bar-legend-item { display: flex; align-items: center; gap: 5px; color: var(--budi-text); }
.segmented-bar-legend-swatch { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }

/* 6. Colored initial-avatar log row. Pair with avatarInitialsHtml() in
   shell-core.js, which hashes a name to one of these palette colors the
   same way BudiPro's chart category colors are hashed (getCategoryColor)
   — reusing the existing deterministic-color technique rather than a
   second one. */
.avatar-log-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-top: 1px solid var(--budi-border); font-size: 13px; }
.avatar-log-row:first-child { border-top: none; }
.avatar-initials { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; color: #fff; }
.avatar-log-row .avatar-log-main { flex: 1; min-width: 0; }
.avatar-log-row .avatar-log-title { color: var(--budi-text); }
.avatar-log-row .avatar-log-date { color: var(--budi-muted); font-size: 11px; }
.avatar-log-row .avatar-log-amount { font-weight: 700; text-align: right; }
.avatar-log-row .avatar-log-amount.positive { color: var(--budi-success); }
.avatar-log-row .avatar-log-amount.negative { color: var(--budi-danger); }
.avatar-log-row .avatar-log-amount.neutral { color: var(--budi-neutral); }

/* 7. Tier upgrade prompt — Silver/Gold, non-gamified. No progress bar,
   no job count; a confident paid-upgrade card, distinct from pattern 4. */
.tier-upgrade-card { background: var(--budi-card-bg); border: 0.5px solid var(--budi-border); border-radius: 16px; padding: 20px; }
:root[data-theme="dark"] .tier-upgrade-card { border: none; }
.tier-upgrade-card .tier-badge { display: inline-block; background: var(--budi-badge-bg); color: var(--budi-text); font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 10px; margin-bottom: 8px; }
.tier-upgrade-card .tier-upgrade-line { font-size: 14px; color: var(--budi-text); margin-bottom: 12px; }
.tier-upgrade-card button { background: var(--budi-accent); }

/* User-requested (2026-08-15) — Worker Home's tier tile, paired beside the hero-metric-card
   (see .hero-row) instead of the old separate "Your Tier" card + tier-upgrade-card, which
   this replaces both of. Same tinted-background-plus-colored-text language as .pill-tier-*
   below, scaled up to a full clickable tile rather than a small badge — keeps the tier color
   language consistent suite-wide instead of inventing a second, louder palette just for this
   one component. */
.hero-row { display: flex; gap: 12px; align-items: stretch; margin: 12px 0; }
.hero-row .hero-metric-card { flex: 1.6 1 0; margin: 0; }
.tier-tile { flex: 1 1 0; border: none; border-radius: 16px; padding: 16px; text-align: left; cursor: pointer; display: flex; flex-direction: column; justify-content: center; }
.tier-tile-label { font-size: 12px; opacity: 0.75; margin: 0 0 4px; color: inherit; }
.tier-tile-value { font-size: 20px; font-weight: 800; margin: 0; color: inherit; font-variant-numeric: tabular-nums; }
.tier-tile-caption { font-size: 11px; opacity: 0.85; margin-top: 4px; color: inherit; }
.tier-tile.tier-bronze { background: var(--budi-tag-bg); color: var(--budi-tier-bronze-text); }
.tier-tile.tier-silver { background: var(--budi-tag-bg); color: var(--budi-tier-silver-text); }
.tier-tile.tier-gold { background: var(--budi-badge-bg); color: var(--budi-tier-gold-text); }
.tier-tile.tier-platinum { background: var(--budi-success-bg); color: var(--budi-success); }

/* 8. Tip/insight callout — small, non-blocking suggestion. */
.insight-callout { display: flex; gap: 10px; align-items: flex-start; background: var(--budi-notice-bg); border-radius: 10px; padding: 10px 12px; font-size: 13px; color: var(--budi-text); }
.insight-callout .insight-icon { flex-shrink: 0; }
.insight-callout a { color: var(--budi-accent); font-weight: 600; }
/* Design-Language Spec Phase 6 — safety-monitoring callouts (BudiRide check-in prompts,
   help-requested alerts) are urgent, not a passive tip, so they need a visually distinct
   variant rather than sharing the same low-key tint as a spending suggestion. */
.insight-callout.danger { background: var(--budi-danger-bg); border-left: 3px solid var(--budi-danger); }
.insight-callout.danger .insight-icon { color: var(--budi-danger); }
/* Suite-wide sweep 2026-08-14 — a positive/confirmed-good counterpart to .danger (e.g. the
   safety badge active notice), replacing the legacy .notice.success variant. */
.insight-callout.success { background: var(--budi-success-bg); }
.insight-callout.success .insight-icon { color: var(--budi-success); }
/* Design-Language Spec Phase 9 Part B — the third severity this component's own spec always
   named (--budi-danger/--budi-warning/--budi-notice-bg) but only .danger/.success ever got
   built. A left-border accent (not just a background tint) distinguishes "needs attention
   but isn't broken" (warning) from "actually failed" (danger) at a glance. */
.insight-callout.warning { background: var(--budi-notice-bg); border-left: 3px solid var(--budi-warning); }

/* Contextual point-of-action tips (2026-08-15) — a dismissible first-visit variant of the
   same .insight-callout, so a screen-specific hint reads as the same visual language as every
   other explainer in the suite rather than a new, competing UI element. */
.tip-callout .tip-dismiss { flex-shrink: 0; background: transparent; border: 1px solid var(--budi-border); color: var(--budi-muted); font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 999px; cursor: pointer; }
.tip-callout .tip-dismiss:hover { background: var(--budi-panel-bg); color: var(--budi-text); }

/* Design-Language Spec Phase 6 — the unified ride-confirmation block (driver photo,
   vehicle, plate, PIN) needed real visual weight of its own instead of sitting inside a
   bare .sub-form divider, so it reads as the one thing to check before getting in the
   car. Nested inside the ride's own outer .card, so this is a tinted inset panel rather
   than a second bordered card-in-a-card. */
.ride-confirm-card { display: flex; align-items: center; gap: 12px; background: var(--budi-panel-bg); border-radius: 12px; padding: 12px; margin-top: 10px; }
.ride-confirm-card .ride-confirm-title { font-weight: 700; margin-bottom: 2px; }
.ride-confirm-pin { font-family: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace; font-size: 18px; font-weight: 700; letter-spacing: 3px; color: var(--budi-accent); }

/* Phase 10B follow-up (2026-08-15) — this base rule was identically duplicated across all 9
   HTML pages' own <style> blocks (found during the Phase 10B audit); centralized here as the
   single source of truth for the 4 live pages that link this file (app.html, worker-app.html,
   admin.html, store-owner.html). The 5 legacy/unlinked pages (budicart.html, budiride.html,
   budipro.html, index.html, worker-profile.html) keep their own local copy — they don't link
   this file at all, so removing theirs would break status tags there, not deduplicate them. */
.status-tag { display: inline-block; background: var(--budi-tag-bg); color: var(--budi-text); padding: 2px 8px; border-radius: 10px; font-size: 12px; }

/* Design-Language Spec Phase 6 — ride status states each get their own color treatment
   instead of sharing one flat neutral .status-tag for every state. REQUESTED (waiting for
   a driver) intentionally keeps the plain default — nothing to react to yet. */
.status-tag.ride-status-DRIVER_EN_ROUTE { background: var(--budi-badge-bg); color: var(--budi-warning-text); }
.status-tag.ride-status-IN_PROGRESS { background: var(--budi-success-bg); color: var(--budi-success); }
.status-tag.ride-status-COMPLETED { color: var(--budi-muted); }
.status-tag.ride-status-CANCELED { background: var(--budi-danger-bg); color: var(--budi-danger); }

/* Design-Language Spec Phase 8 Part A — the two BudiBid address-privacy states (city-only vs.
   full address revealed) each get their own distinct visual treatment rather than the reader
   having to notice which string got interpolated into an otherwise-identical line of text. */
.status-tag.address-hidden { background: var(--budi-badge-bg); color: var(--budi-muted); }
.status-tag.address-revealed { background: var(--budi-success-bg); color: var(--budi-success); }

/* 9. Listing card — browsing lists of discrete items (BudiEats dishes,
   BudiBid errand listings, etc.). */
.listing-card { background: var(--budi-card-bg); border: 0.5px solid var(--budi-border); border-radius: 16px; padding: 12px; }
/* Real bug, user-reported: BudiEats' "Popular Dishes" tile rendered huge on a narrow screen
   while BudiCart's own "Popular Items" tile looked normal. Root cause was data-count-driven,
   not per-vertical: .card-grid's `repeat(auto-fit, minmax(140px, 1fr))` is shared with the
   KPI-stat-block component, which WANTS its 2-4 blocks to stretch and fill the row — but
   auto-fit collapses empty tracks, so a listing grid with only one or two items (BudiEats
   simply had fewer trending dishes than BudiCart at the time) stretches that lone card to
   the FULL row width instead of staying a normal card size. Scoped to `.card-grid
   .listing-card` specifically (not the bare .listing-card class, which BudiBid's plain
   stacked bid-list cards also use outside of any grid and need to stay full-width) so this
   fixes every current and future .card-grid-based listing across the suite — BudiCart and
   BudiEats popular items, catalog browsing — without touching .card-grid's own stretch
   behavior, which the KPI blocks still rely on. */
.card-grid .listing-card { max-width: 200px; }
:root[data-theme="dark"] .listing-card { border: none; }
.listing-card .listing-image { width: 100%; aspect-ratio: 1.6; border-radius: 12px; background: var(--budi-panel-bg); object-fit: cover; }
.listing-card .listing-title { font-weight: 700; color: var(--budi-text); margin: 8px 0 4px; }
.listing-card .listing-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.listing-card .listing-tag-pill { font-size: 11px; background: var(--budi-tag-bg); color: var(--budi-text); padding: 2px 8px; border-radius: 10px; }
.listing-card .listing-stat { font-weight: 700; color: var(--budi-text); }
/* Design-Language Spec Phase 3 — out-of-stock items visually gray out rather than
   disappearing (real inventory is tracked, so customers should see why something isn't
   addable), and every listing card gets a quick "add" action (component 9's own spec,
   named for both BudiEats' popular dishes in Phase 2 and BudiCart's item browsing here). */
.listing-card.out-of-stock { opacity: 0.5; filter: grayscale(60%); }
.listing-quick-add { padding: 4px 12px; font-size: 12px; border-radius: 14px; }

/* 10. Loading state — Design-Language Spec Phase 9 Part B. .shell-loading/.shell-error were
   previously duplicated identically in app.html and worker-app.html's own <style> blocks;
   consolidated here as the single source of truth. .skeleton-card/.skeleton-row are new: gray
   placeholder shapes matching the actual card layouts they stand in for, so a loading list
   reads as "content is coming" rather than "something is broken" — used in place of
   .shell-loading's plain text specifically on the card-heavy list screens (Jobs feed, Home,
   order/ride histories, Admin tables), not applied blanket to every single tab (a settings
   form's loading state doesn't benefit from mimicking a card shape that isn't there). */
.shell-loading { padding: 24px 0; text-align: center; color: var(--budi-muted); font-size: 13px; }
.shell-error { display: none; background: var(--budi-danger-bg); color: var(--budi-text); padding: 8px; border-radius: 4px; font-size: 12px; margin: 8px 0; }
@keyframes skeleton-shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }
.skeleton-card, .skeleton-row .skeleton-line { background: linear-gradient(90deg, var(--budi-panel-bg) 25%, var(--budi-border) 37%, var(--budi-panel-bg) 63%); background-size: 400px 100%; animation: skeleton-shimmer 1.4s ease infinite; border-radius: 8px; }
.skeleton-card { height: 96px; margin-bottom: 10px; }
.skeleton-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.skeleton-row .skeleton-avatar { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0; background: linear-gradient(90deg, var(--budi-panel-bg) 25%, var(--budi-border) 37%, var(--budi-panel-bg) 63%); background-size: 400px 100%; animation: skeleton-shimmer 1.4s ease infinite; }
.skeleton-row .skeleton-line { height: 14px; flex: 1; }
@media (prefers-reduced-motion: reduce) {
.skeleton-card, .skeleton-row .skeleton-line, .skeleton-row .skeleton-avatar { animation: none; }
/* 2026-09-01 — the new lift-on-hover button/tile motion respects this too; the shadow/color
   feedback still applies (not a motion effect), just without the translateY movement. */
button:not(:disabled), button:not(:disabled):hover, button:not(:disabled):active, button.kpi-stat-block:not(:disabled):hover, button.kpi-stat-block:not(:disabled):active { transition: box-shadow 0.15s ease, background 0.15s ease; transform: none; }
}

/* 11. Error card — Design-Language Spec Phase 9 Part B. Reuses component 8's insight-callout
   shape in .danger/.warning coloring rather than inventing a fourth callout-shaped component;
   the difference from a plain .insight-callout.danger is the required action button this one
   always carries (retry/contact-support/go-back) — an error state must never be a dead end. */
.error-card-action { margin-top: 6px; }

/* 12. Shared icon sprite (Design-Language Spec Phase 10 Part A) — see /icons.svg for the
   symbol definitions and shell-core.js's loadIconSprite() for how it gets injected. Three
   fixed sizes only, matching the spacing/typography scale the rest of this spec already
   establishes: 16px for inline/dense contexts (inside an insight-callout, next to a status
   pill), 20px for standard UI elements (Admin sidebar), 24px for primary nav (bottom nav).
   color: currentColor by default so an icon picks up whatever text color surrounds it
   (a danger callout's icon goes red for free, a status-tag's icon matches its own pill
   color) — never hardcode a fill/stroke color on an individual <use>. */
.icon-16, .icon-20, .icon-24 { display: inline-block; vertical-align: -0.15em; flex-shrink: 0; color: currentColor; }
.icon-16 { width: 16px; height: 16px; }
.icon-20 { width: 20px; height: 20px; }
.icon-24 { width: 24px; height: 24px; }

/* Design-Language Spec Phase 3 — fulfillment-mode two-option toggle (personal-shopper vs.
   store-fulfilled), replacing a native <select> only shown when a store genuinely supports
   both modes for an order. */
.fulfillment-toggle { display: flex; gap: 6px; margin: 8px 0 2px; }
.fulfillment-toggle button { flex: 1; background: var(--budi-tag-bg); color: var(--budi-text); border-radius: 20px; padding: 8px 10px; font-size: 12.5px; }
.fulfillment-toggle button.active { background: var(--budi-accent); color: #fff; font-weight: 600; }

/* ====================================================================
   Admin Phase 1 (2026-08-14) — sidebar nav, pills, pagination, and a
   "featured" KPI variant. Admin-specific because Admin is the one
   surface that's genuinely desktop-primary rather than mobile-card-
   first — this pattern isn't meant to generalize to Worker/Customer.
   ==================================================================== */

.admin-layout { display: flex; align-items: flex-start; gap: 28px; }
.admin-sidebar { width: 200px; flex-shrink: 0; position: sticky; top: 20px; }
/* Design-Language Spec Phase 10 Part A — icons added at .icon-20 (standard UI element size),
   both nav lists switched to flex so the icon and label sit on one line rather than the icon
   floating above stacked text the way primary bottom-nav (.icon-24) does. */
.admin-sidebar-dashboard-link { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: none; color: var(--budi-text); padding: 9px 10px; border-radius: 8px; font-size: 14px; font-weight: 700; cursor: pointer; margin-bottom: 18px; }
.admin-sidebar-dashboard-link:hover { background: var(--budi-tag-bg); }
.admin-sidebar-dashboard-link.active { background: var(--budi-accent); color: #fff; }
.admin-sidebar-group { margin-bottom: 12px; }
/* User request (2026-08-15) — sidebar category groups (People/Commerce/System) became
   clickable "bubbles": a pill-shaped button that expands/collapses its own items, rather
   than always showing all 14 nav items flat. Whichever group contains the currently-active
   page auto-expands (see admin.html's showAdminPage()); collapsed is the default otherwise,
   and more than one group can be open at once — this isn't an exclusive accordion. */
.admin-sidebar-group-label { display: flex; align-items: center; justify-content: space-between; width: 100%; text-align: left; background: var(--budi-tag-bg); border: none; border-radius: 999px; padding: 7px 12px; margin: 0 0 6px; cursor: pointer; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--budi-muted); font-weight: 700; }
.admin-sidebar-group-label:hover { background: var(--budi-border); }
.admin-sidebar-group-chevron { display: inline-block; transition: transform 0.15s ease; font-size: 10px; line-height: 1; }
.admin-sidebar-group.collapsed .admin-sidebar-group-chevron { transform: rotate(-90deg); }
.admin-sidebar-group.collapsed .admin-sidebar-group-items { display: none; }
.admin-sidebar-group-items button { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: none; color: var(--budi-text); padding: 8px 10px; border-radius: 8px; font-size: 13.5px; cursor: pointer; }
.admin-sidebar-group-items button:hover { background: var(--budi-tag-bg); }
.admin-sidebar-group-items button.active { background: var(--budi-accent); color: #fff; font-weight: 600; }
.admin-main { flex: 1; min-width: 0; }

/* ====================================================================
   Prompt 23 (Part B, 2026-08-27) — real text-size scaling. Replaces the old
   "Large Text"/"Simple Mode" toggles (Prompt 13, #28/#80/#29/#78), removed
   entirely — confirmed broken. The old large-text mode set `font-size: 120%`
   on :root, which only rescales rem/em-sized text; nearly every component
   in this codebase sizes text in hardcoded px (font-size:12px inline styles
   throughout), so that override silently did nothing for most of the app —
   the actual root cause of "doesn't work properly." `zoom` scales the whole
   rendered page uniformly (fonts, padding, borders, layout — regardless of
   what unit a given rule happens to use), the same mechanism a browser's own
   page-zoom uses, which is why it's the one property that reliably reaches
   every px-sized element here too. --budi-font-scale is set on <html> itself
   by applyFontScale() in shell-core.js (1.0-2.0, i.e. 100%-200%). ==================================================================== */
:root { --budi-font-scale: 1; }
html { zoom: var(--budi-font-scale); }

/* Voice input (Part B) — one mic button injected after every plain text
   input/textarea by shell-core.js's voice-input observer. Deliberately
   plain and inline, matching this app's existing "input directly followed
   by its own button" convention (see e.g. the address-search fields)
   rather than an absolutely-positioned overlay icon. */
.voice-input-btn {
  font-size: 15px;
  padding: 4px 8px;
  line-height: 1;
  vertical-align: middle;
  margin-left: 4px;
}
.voice-input-btn.voice-input-listening {
  background: var(--budi-danger);
  color: #fff;
  animation: voice-input-pulse 1s ease-in-out infinite;
}
@keyframes voice-input-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Pill status/tier indicators — carry color + meaning in one glance, chosen over a
   dot-plus-label pattern. Reused for both status ("Active"/"Past due") and tier
   (Bronze/Silver/Gold/Platinum) so both read consistently across every Admin table. */
.pill { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.pill-success { background: var(--budi-success-bg); color: var(--budi-success); }
.pill-danger { background: var(--budi-danger-bg); color: var(--budi-danger); }
.pill-warning { background: var(--budi-badge-bg); color: var(--budi-warning-text); }
.pill-neutral { background: var(--budi-tag-bg); color: var(--budi-neutral); }
.pill-tier-bronze { background: var(--budi-tag-bg); color: var(--budi-tier-bronze-text); }
.pill-tier-silver { background: var(--budi-tag-bg); color: #6B7280; }
.pill-tier-gold { background: var(--budi-badge-bg); color: var(--budi-tier-gold-text); }
.pill-tier-platinum { background: var(--budi-success-bg); color: var(--budi-success); }

/* Real numbered pagination — page numbers + ellipsis for skipped ranges + a rows-per-page
   control. Admin tables will have far more rows at scale than any other surface, so a
   plain "see all" link isn't enough here the way it might be elsewhere in the suite. */
.pagination { display: flex; align-items: center; justify-content: center; gap: 4px; margin: 16px 0; flex-wrap: wrap; }
.pagination button { padding: 6px 11px; font-size: 12px; background: var(--budi-card-bg); border: 1px solid var(--budi-border); color: var(--budi-text); border-radius: 6px; cursor: pointer; }
.pagination button.active-page { background: var(--budi-accent); color: #fff; border-color: var(--budi-accent); }
.pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
.pagination-ellipsis { padding: 6px 4px; color: var(--budi-muted); font-size: 12px; }
.pagination-size-select { width: auto; display: inline-block; margin: 0 0 0 8px; padding: 6px; font-size: 12px; }

/* Featured KPI card — the one Dashboard metric that needs action, deliberately tinted with
   the existing notice/callout token rather than a new color, and deliberately NOT the
   hero-metric-card pattern (that's reserved for one dominant number; these three sit as
   equal-weight KPI blocks with one drawing the eye via background alone). */
.kpi-stat-block.featured { background: var(--budi-notice-bg); }
.kpi-stat-block .kpi-comparison { font-size: 11px; color: var(--budi-muted); margin-top: 4px; }

/* Centered variant — for compact label+value-only breakdown cards (Users by Role, Errands
   by Status, etc.) where there's no delta/comparison line to anchor left-aligned body text;
   left-aligned reads as lost/stranded in these shorter cards. The top KPI row (which DOES
   carry a delta + comparison caption, i.e. real body text) stays left-aligned/unchanged —
   this is scoped to .centered only, not a blanket change to the shared component. */
/* Bug fixed 2026-09-01 — .centered only ever set horizontal centering (text-align). Vertical
   centering was never actually implemented: a .card-grid is CSS Grid, whose default
   align-items:stretch makes every tile in a row match the tallest sibling's height, so any
   tile shorter than that (nearly all of them, since content is just an icon + 1-2 short lines)
   had its content sitting wherever normal block flow happened to leave it — reported live as
   "text more toward the bottom than centered" on the Payouts tiles, but this is the shared
   class used by all 36+ drill-tile call sites suite-wide, so the same fix applies everywhere
   at once rather than patching each screen's tiles individually (again). */
.kpi-stat-block.centered { text-align: center; display: flex; flex-direction: column; align-items: center; justify-content: center; }

/* "General fonts" — the user's own named convention (2026-08-31), coined from the Customer
   App's Wallet/Budi Bucks identity-header tiles: bold, 20px labels on every clickable
   drill-through tile suite-wide, not just those two. Deliberately a SEPARATE class from
   .kpi-label itself rather than changing that base class's default — .kpi-label is also used
   for ordinary (non-tile) KPI stat displays throughout Admin/Store/etc., which should keep
   their own smaller/lighter look; this only applies where a .kpi-label sits inside a
   clickable navigation tile. Whenever asked to give a tile "general fonts," add this class
   alongside .kpi-label.
   Bug fixed 2026-09-01: this rule never actually won its font-size against
   .kpi-stat-block .kpi-label (line ~252) — that selector has higher specificity (two classes)
   than .tile-label alone (one class), so font-size:12px silently kept winning regardless of
   source order ever since this class was introduced. font-weight:700 was unaffected (the
   competing rule doesn't set it), which is why only the size looked wrong, not the boldness.
   The second, more specific rule below is the real fix — .tile-label itself is left as the
   fallback for the "not a .kpi-label" case the comment above already covers. */
.tile-label { font-weight: 700; font-size: 20px; }
.kpi-stat-block .kpi-label.tile-label { font-size: 20px; }

/* Onboarding progress bar (2026-09-13) — shared across Worker/Customer/Store first-run
   onboarding overlays, so all three "feel like you're working toward something" the same
   way. Percent-of-steps-complete, computed and set by updateOnboardProgress() in
   shell-core.js; the markup itself is just a track + fill + label, no per-shell variant. */
.onboard-progress { margin: 0 0 var(--budi-space-4, 16px); }
.onboard-progress-track { background: var(--budi-tag-bg); border-radius: 999px; height: 8px; overflow: hidden; }
.onboard-progress-fill { height: 100%; background: var(--budi-accent); border-radius: 999px; transition: width 0.3s ease; width: 0%; }
.onboard-progress-label { font-size: 11px; color: var(--budi-muted); margin-top: 4px; text-align: right; }

/* ====================================================================
   Native-app shell (2026-09-09) — "feels like a web portal, not a Play
   Store app" pass.

   Everything below is about removing *browser* tells from a page that
   is now shipping inside a Capacitor WebView. None of it changes layout
   or color; it changes the things a user reads subconsciously as "this
   is a website someone wrapped" — the blue flash when you tap, the
   rubber-band overscroll at the top of a list, text turning blue-
   selected when you press-and-hold a button, the 300ms dead zone before
   a tap registers, content sliding under the status bar and the Android
   gesture pill.

   Paired with the viewport meta tag added to all five entry points in
   the same change. That tag is the load-bearing half: without it an
   Android WebView lays the page out at ~980px CSS and scales the result
   down, so `body { max-width: 720px; margin: 0 auto }` rendered as a
   small centered document floating in dead margins. That single missing
   line is most of why this read as a web portal.
   ==================================================================== */

html {
  /* Kills browser pull-to-refresh and the rubber-band bounce past the end
     of a scroll. Native Android apps have neither unless they ask for it. */
  overscroll-behavior: none;
  /* Stop the WebView from auto-inflating font sizes on "text-heavy" blocks,
     which silently breaks the type scale on some Android builds. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  overscroll-behavior: none;
  /* No blue/gray flash rectangle on tap — the single loudest "this is a
     web page" signal on Android. Real feedback comes from :active states,
     which the design system already defines. */
  -webkit-tap-highlight-color: transparent;
}

/* Press-and-hold on a button or nav item should not start a text selection
   or pop the copy/share callout. Content the user may legitimately want to
   copy — reference numbers (BB-/BR-/BC-), addresses, PINs, form values — is
   explicitly opted back in below, so this is not a blanket lockout. */
button, .tab-bar, #bottomNav, .kpi-label, .tile-label, .status-tag, nav, label {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

input, textarea, select, [contenteditable],
p, td, .ride-confirm-pin, .small-result, .reference-number, code {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* Removes the ~300ms double-tap-to-zoom wait before a tap fires. This is the
   difference between a tap that feels instant and one that feels like a web
   page thinking about it. */
button, a, .tab-bar button, #bottomNav button, [role="button"], input, select, textarea {
  touch-action: manipulation;
}

/* Scrollbars are a desktop affordance; Android overlays its own transient one. */
::-webkit-scrollbar { width: 0; height: 0; }
* { scrollbar-width: none; }

/* --- Safe areas -----------------------------------------------------
   Requires viewport-fit=cover in the viewport meta, or every env() below
   resolves to 0 and these are harmless no-ops (which is the correct
   behavior on desktop/admin). Without them, content runs under the status
   bar at the top and under the Android gesture pill at the bottom. */
body {
  padding-top: calc(var(--budi-space-5, 20px) + env(safe-area-inset-top));
  padding-left: calc(var(--budi-space-5, 20px) + env(safe-area-inset-left));
  padding-right: calc(var(--budi-space-5, 20px) + env(safe-area-inset-right));
}

#bottomNav {
  /* The bar's background must extend *through* the gesture-pill area, so the
     inset goes on padding, not margin — otherwise you get a strip of page
     background below the nav. */
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

#bottomNav button {
  /* Android's minimum touch target is 48dp; Material bottom nav is 56dp+.
     These were ~36px tall, which reads (and misses) like a web toolbar. */
  min-height: 56px;
  justify-content: center;
  font-size: 12px;
}

/* Body bottom padding has to clear the nav *plus* the gesture pill, or the
   last row of every list sits permanently behind the nav bar. */
body:has(#bottomNav) {
  padding-bottom: calc(90px + env(safe-area-inset-bottom));
}

/* Header chrome now holds an icon + a label rather than an emoji + a label, so the button
   needs to be a flex row — emoji sat on the text baseline for free, an <svg> does not. */
#themeToggle, #profileToggleBtn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* Android exposes a system-wide dark mode and the theme now follows it, so the in-header
   toggle is a duplicate control in the native build only — it stays on the web, where there
   is no OS-level switch the page is already honoring. Delete this rule to bring it back. */
.budi-native #themeToggle { display: none; }
