/* ==========================================================================
   GreenerClean 綠適居 — app styles
   Mobile-first. On wide screens the app is laid inside a phone device frame.

   Token architecture
   ------------------
   1. PRIMITIVES  raw ramps. Never referenced by a component.
   2. SEMANTICS   what every component actually uses. Re-pointed per theme.
   3. LEGACY      the old green/mint variable names kept as aliases so the
                  inline styles in app.js keep resolving.

   Theme switching is driven by data-theme on <html>, set by the inline
   bootstrap in index.html before first paint. That keeps the dark values in
   exactly one place instead of duplicating them for prefers-color-scheme.
   ========================================================================== */

/* ============================== 1. PRIMITIVES ============================ */
:root {
  /* brand green ramp */
  --p-green-50:  #ecfdf5;
  --p-green-100: #d1fae5;
  --p-green-200: #a7f3d0;
  --p-green-300: #6ee7b7;
  --p-green-400: #34d399;
  --p-green-500: #10b981;
  --p-green-600: #059669;
  --p-green-700: #047857;
  --p-green-800: #065f46;
  --p-green-900: #064e3b;
  --p-green-950: #032c22;

  /* neutrals, very slightly green-shifted so they sit with the brand */
  --p-n-0:   #ffffff;
  --p-n-25:  #fbfdfc;
  --p-n-50:  #f4f8f6;
  --p-n-100: #e9efec;
  --p-n-200: #d8e3dd;
  --p-n-300: #b8c8c0;
  --p-n-400: #8ba098;
  --p-n-500: #5c7167;
  --p-n-600: #4a5d55;
  --p-n-700: #35463f;
  --p-n-800: #22302b;
  --p-n-850: #1a2622;
  --p-n-900: #131d1a;
  --p-n-950: #0b1311;

  /* category + state accents, contrast-checked against their own surfaces */
  --p-sky-500:   #0284c7;
  --p-sky-300:   #7dd3fc;
  --p-clay-500:  #c2620c;
  --p-clay-300:  #fdba74;
  --p-amber-500: #b45309;
  --p-amber-400: #d97706;
  --p-amber-300: #fcd34d;
  --p-rose-500:  #be123c;
  --p-rose-300:  #fda4af;
  --p-red-500:   #dc2626;
  --p-red-300:   #fca5a5;

  /* ---- non-colour primitives (theme independent) ---- */

  /* type scale */
  --fs-micro: 11px;   /* uppercase tracked labels only, never body copy */
  --fs-2xs:   12px;
  --fs-xs:    13px;
  --fs-sm:    14px;
  --fs-md:    15px;
  --fs-base:  16px;
  --fs-lg:    18px;
  --fs-xl:    21px;
  --fs-2xl:   25px;
  --fs-3xl:   30px;

  --lh-tight: 1.2;
  --lh-snug:  1.35;
  --lh-body:  1.55;

  /* 4 / 8 spacing rhythm */
  --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 20px;  --sp-6: 24px;  --sp-8: 32px;  --sp-10: 40px; --sp-12: 48px;

  /* radii — tightened from the old 28/22/16 to read precise, not bubbly */
  --r-xs:   8px;
  --r-sm:   10px;
  --r-md:   14px;
  --r-lg:   18px;
  --r-xl:   24px;
  --r-pill: 999px;

  /* icon sizes */
  --ic-sm: 16px;  --ic-md: 20px;  --ic-lg: 24px;  --ic-xl: 28px;

  /* motion — everything lands in the 150–300ms band */
  --dur-fast: 140ms;
  --dur:      220ms;
  --dur-slow: 300ms;
  --ease:     cubic-bezier(.2, .8, .3, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-pop: cubic-bezier(.2, 1.3, .4, 1);

  /* layout */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --tabbar-h: 72px;
  --header-h: 56px;
  --gutter: 18px;
  --tap: 44px;              /* minimum interactive box */

  /* type families — Latin webfont in front, system CJK behind it so
     繁體 / 简体 always render even when Google Fonts is unreachable */
  --ff-display: 'Lexend', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                'PingFang HK', 'PingFang SC', 'Hiragino Sans GB',
                'Microsoft JhengHei', 'Microsoft YaHei', 'Noto Sans CJK HK',
                Roboto, Helvetica, Arial, sans-serif;
  --ff-body:    'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                'PingFang HK', 'PingFang SC', 'Hiragino Sans GB',
                'Microsoft JhengHei', 'Microsoft YaHei', 'Noto Sans CJK HK',
                Roboto, Helvetica, Arial, sans-serif;
}

/* ============================ 2. SEMANTICS — LIGHT ======================= */
:root {
  color-scheme: light;

  /* surfaces */
  --bg:            var(--p-n-50);
  --bg-sunken:     var(--p-n-100);
  --surface:       var(--p-n-0);
  --surface-2:     var(--p-n-25);
  --surface-inset: var(--p-n-100);
  --surface-invert: var(--p-green-900);

  /* lines */
  --border:        var(--p-n-200);
  --border-strong: var(--p-n-300);
  --border-invert: rgba(255, 255, 255, .14);

  /* text — every pair below clears 4.5:1 on its own surface */
  --text:          var(--p-n-900);
  --text-2:        var(--p-n-700);
  --text-muted:    var(--p-n-500);
  --text-invert:   #eafff6;
  --text-invert-2: #a9d9c5;

  /* brand */
  --brand:          var(--p-green-700);   /* carries white text — 5.48:1 */
  --brand-hover:    var(--p-green-800);
  --brand-strong:   var(--p-green-800);
  --brand-text:     var(--p-green-700);   /* brand colour used AS text */
  --on-brand:       #ffffff;
  --brand-soft:     var(--p-green-50);
  --brand-soft-2:   var(--p-green-100);
  --on-brand-soft:  var(--p-green-800);

  /* status */
  --accent:        var(--p-amber-500);
  --accent-soft:   #fef3c7;
  --danger:        var(--p-red-500);
  --danger-soft:   #fee2e2;
  --on-danger-soft: #991b1b;
  --warn:          var(--p-amber-500);
  --warn-soft:     #fef3c7;
  --info:          var(--p-sky-500);
  --info-soft:     #e0f2fe;
  --neutral-soft:  var(--p-n-100);
  --on-neutral-soft: var(--p-n-700);

  /* misc */
  --focus:  var(--p-green-700);
  --scrim:  rgba(6, 24, 18, .52);
  --track:  var(--p-n-200);

  /* elevation — two layers: a tight contact shadow plus a soft ambient one.
     Softer than flat, far clearer than neumorphism. */
  --shadow-1: 0 1px 2px rgba(11, 41, 32, .05), 0 2px 6px rgba(11, 41, 32, .05);
  --shadow-2: 0 2px 4px rgba(11, 41, 32, .05), 0 10px 22px rgba(11, 41, 32, .07);
  --shadow-3: 0 6px 14px rgba(11, 41, 32, .09), 0 24px 52px rgba(11, 41, 32, .13);
  --shadow-brand: 0 6px 16px rgba(5, 150, 105, .28);
  --ring-inset: inset 0 0 0 1px var(--border);

  /* how strongly a service colour tints its own thumbnail */
  --tint-mix: 12%;
  --tint-base: var(--surface);
  --svc-shift: 0%;      /* light mode: use the service colour as authored */
}

/* ============================= 2b. SEMANTICS — DARK ====================== */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:            var(--p-n-950);
  --bg-sunken:     #070e0c;
  --surface:       var(--p-n-900);
  --surface-2:     var(--p-n-850);
  --surface-inset: var(--p-n-850);
  --surface-invert: var(--p-green-950);

  --border:        #24322d;
  --border-strong: #34463f;
  --border-invert: rgba(255, 255, 255, .12);

  --text:          #e8f2ed;
  --text-2:        #b9cbc3;
  --text-muted:    #8ba098;
  --text-invert:   #eafff6;
  --text-invert-2: #a9d9c5;

  --brand:          var(--p-green-500);
  --brand-hover:    var(--p-green-400);
  --brand-strong:   var(--p-green-400);
  --brand-text:     var(--p-green-300);
  --on-brand:       #04201a;
  --brand-soft:     rgba(16, 185, 129, .14);
  --brand-soft-2:   rgba(16, 185, 129, .22);
  --on-brand-soft:  var(--p-green-200);

  --accent:        var(--p-amber-300);
  --accent-soft:   rgba(252, 211, 77, .16);
  --danger:        #f87171;
  --danger-soft:   rgba(248, 113, 113, .16);
  --on-danger-soft: #fca5a5;
  --warn:          var(--p-amber-300);
  --warn-soft:     rgba(252, 211, 77, .16);
  --info:          var(--p-sky-300);
  --info-soft:     rgba(125, 211, 252, .16);
  --neutral-soft:  rgba(255, 255, 255, .07);
  --on-neutral-soft: #b9cbc3;

  --focus:  var(--p-green-400);
  --scrim:  rgba(0, 0, 0, .66);
  --track:  #2b3a34;

  /* dark surfaces separate by border and lift, not by drop shadow */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-2: 0 2px 6px rgba(0, 0, 0, .5), 0 12px 28px rgba(0, 0, 0, .38);
  --shadow-3: 0 8px 20px rgba(0, 0, 0, .6), 0 28px 60px rgba(0, 0, 0, .5);
  --shadow-brand: 0 6px 18px rgba(16, 185, 129, .22);

  --tint-mix: 22%;
  --tint-base: var(--surface);
  --svc-shift: 34%;     /* dark mode: lift service hues toward white for contrast */
}

/* ======================== 3. LEGACY ALIASES (compat) ===================== */
:root {
  --green-900: var(--p-green-900);
  --green-800: var(--brand-strong);
  --green-700: var(--brand-text);
  --green-600: var(--brand-hover);
  --green-500: var(--brand);
  --green-400: var(--p-green-400);
  --mint-300: var(--p-green-300);
  --mint-200: var(--brand-soft-2);
  --mint-100: var(--brand-soft);
  --sun: var(--accent);
  --sky: var(--info);
  --clay: var(--p-clay-500);
  --berry: var(--p-rose-500);
  --ink: var(--text);
  --ink-2: var(--text-2);
  --muted: var(--text-muted);
  --line: var(--border);
  --line-2: var(--border-strong);
  --bg-2: var(--bg-sunken);
  --card: var(--surface);
  --card-2: var(--surface-2);
  --shadow-sm: var(--shadow-1);
  --shadow: var(--shadow-2);
  --shadow-lg: var(--shadow-3);
  --r-2xl: var(--r-xl);
  --ff: var(--ff-body);
}

/* ================================= base ================================== */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { height: 100%; }
body {
  margin: 0; padding: 0; min-height: 100%;
  font-family: var(--ff-body);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  color: var(--text);
  background: var(--bg);
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1, h2, h3, h4 { font-family: var(--ff-display); font-weight: 600; letter-spacing: -.015em; line-height: var(--lh-tight); }
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, select, textarea { font-family: inherit; font-size: var(--fs-base); }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

/* one visible focus treatment for the whole app */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* icon defaults — one family, one stroke weight, colour from the token */
.ico { display: block; flex: 0 0 auto; }
.stars { display: inline-flex; align-items: center; gap: 1px; vertical-align: -2px; }
.stars .star-on { color: var(--accent); }
.stars .star-off { color: var(--border-strong); }

/* initials avatar — replaces the skin-tone emoji that stood in for people */
.avatar {
  display: grid; place-items: center; border-radius: 50%;
  font-family: var(--ff-display); font-weight: 600; letter-spacing: .01em;
  color: hsl(var(--av-h) 62% 26%);
  background: hsl(var(--av-h) 58% 88%);
  flex: 0 0 auto;
}
:root[data-theme="dark"] .avatar {
  color: hsl(var(--av-h) 70% 84%);
  background: hsl(var(--av-h) 34% 24%);
}

/* ======================= device frame (desktop only) ===================== */
#stage { min-height: 100vh; display: block; }
.device { width: 100%; min-height: 100dvh; }
.device-screen { position: relative; width: 100%; min-height: 100dvh; background: var(--bg); overflow: hidden; }
.device__btns, .device__island, .brand-side, .device-toggle { display: none; }
.status-fake { display: none; }

@media (min-width: 880px) {
  #stage {
    min-height: 100vh;
    display: grid; place-items: center;
    padding: var(--sp-8);
    background:
      radial-gradient(1200px 600px at 15% -10%, #10503c 0%, transparent 55%),
      radial-gradient(900px 500px at 100% 120%, #06251c 0%, transparent 50%),
      linear-gradient(160deg, #08322650 0%, #061e18 60%, #04120e 100%), #04120e;
  }
  .device {
    position: relative;
    width: 392px;
    height: min(852px, calc(100dvh - 72px));
    min-height: 0;
    background: #0b0b0e;
    border-radius: 58px;
    padding: 14px;
    box-shadow: var(--shadow-3), inset 0 0 0 2px #26262b, inset 0 0 0 8px #0b0b0e;
  }
  .device-screen { height: 100%; min-height: 0; border-radius: 46px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.4); }

  .device--ios .device__island {
    display: block; position: absolute;
    top: 24px; left: 50%; transform: translateX(-50%);
    width: 116px; height: 33px; background: #000; border-radius: var(--r-pill); z-index: 60;
  }
  .device--android { border-radius: 44px; padding: 10px; }
  .device--android .device-screen { border-radius: 36px; }
  .device--android .device__island {
    display: block; position: absolute;
    top: 20px; left: 50%; transform: translateX(-50%);
    width: 12px; height: 12px; border-radius: 50%;
    background: #05140f; box-shadow: 0 0 0 3px rgba(0,0,0,.5); z-index: 60;
  }
  .device__btns { display: block; }
  .device__btns::before, .device__btns::after { content: ""; position: absolute; background: #17171c; border-radius: 3px; }
  .device__btns::before { left: -3px; top: 150px; width: 3px; height: 64px; box-shadow: 0 96px 0 #17171c; }
  .device__btns::after { right: -3px; top: 180px; width: 3px; height: 96px; }

  .status-fake { display: flex; }

  .device-toggle {
    display: inline-flex;
    position: fixed; right: 6vw; bottom: 8vh; z-index: 2;
    gap: var(--sp-1); padding: var(--sp-1); border-radius: var(--r-pill);
    background: rgba(255,255,255,.08); backdrop-filter: blur(8px);
    border: 1px solid rgba(255,255,255,.16);
  }
  .device-toggle button {
    min-height: 36px; padding: 0 16px; border-radius: var(--r-pill);
    color: #cfeede; font-size: var(--fs-xs); font-weight: 600; letter-spacing: .01em;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .device-toggle button.on { background: var(--p-green-400); color: #04120c; }
}
@media (min-width: 1240px) { .brand-side { display: block; } }

.brand-side {
  position: fixed; left: 6vw; top: 50%; transform: translateY(-50%);
  max-width: 340px; color: #dff5ea; z-index: 1;
}
.brand-side__logo { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.brand-side__logo .mark { width: 52px; height: 52px; border-radius: 15px; box-shadow: 0 10px 24px rgba(0,0,0,.35); }
.brand-side h1 { font-size: 34px; margin: 0 0 var(--sp-3); letter-spacing: -.025em; }
.brand-side p { color: #a9d9c5; font-size: var(--fs-md); line-height: var(--lh-body); margin: 0 0 var(--sp-4); }
.brand-side .hint {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-xs); color: #8fceb4;
  background: rgba(255,255,255,.07); padding: var(--sp-2) var(--sp-4); border-radius: var(--r-pill);
}

/* fake status bar (desktop frame only) */
.status-fake {
  align-items: center; justify-content: space-between;
  height: 40px; padding: 0 26px 0 30px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--text);
  position: relative; z-index: 40; flex: 0 0 auto; background: transparent;
}
.status-fake .sf-icons { display: flex; align-items: center; gap: 7px; }
.status-fake svg { display: block; }

/* =============================== app shell =============================== */
.app { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); }
@media (min-width: 880px) { .app { top: 40px; } }

.app-header {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: calc(var(--safe-top) + var(--sp-3)) var(--gutter) var(--sp-3);
  background: transparent; position: relative; z-index: 20;
}
.app-header.solid {
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: 1px solid var(--border);
}
.app-header .h-title { font-family: var(--ff-display); font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.02em; }
.app-header .spacer { flex: 1; }

/* header controls all satisfy the 44px minimum via the tap box below */
.h-back, .icon-btn {
  width: var(--tap); height: var(--tap); border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-1), var(--ring-inset);
  position: relative;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.h-back:active, .icon-btn:active { background: var(--surface-inset); }
.icon-btn.small { width: var(--tap); height: var(--tap); }
.icon-btn .dot {
  position: absolute; top: 10px; right: 11px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--surface);
}

.lang-btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  height: var(--tap); padding: 0 var(--sp-4); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-1), var(--ring-inset);
  font-weight: 600; font-size: var(--fs-xs);
  transition: background var(--dur) var(--ease);
}
.lang-btn:active { background: var(--surface-inset); }
.lang-btn .globe { display: grid; place-items: center; color: var(--text-muted); }

.app-main {
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--sp-4));
  scroll-behavior: smooth; scrollbar-width: none;
}
.app-main.no-tabbar { padding-bottom: calc(var(--safe-bottom) + var(--sp-4)); }
.app-main::-webkit-scrollbar { display: none; }

.screen { padding: var(--sp-1) var(--gutter) var(--sp-2); animation: fade var(--dur-slow) var(--ease-out); }

/* The launch reveal, timed against the splash rather than queued behind it.
   splashOut holds the logo opaque to 68% (680ms) and dissolves by 1000ms, so
   the sections start rising at 600ms — while the logo is still fading. The
   home screen assembles itself as the splash clears instead of sitting there
   finished behind it, which is the whole difference between a launch and a
   slideshow.

   `both` fill is what holds each section back until its turn, and that is only
   safe for two reasons: an opaque splash covers the entire delay, and the
   prefers-reduced-motion block at the end of this file now zeroes delays as
   well as durations. Without that second one this would blank the screen for
   600ms for exactly the people who asked for less motion.

   Applied by app.js on the first render only — see bootReveal — and STRIPPED
   by app.js once the sequence should be over. That removal is not tidying, it
   is the safety net: `both` fill holds a section at opacity 0 until its
   animation actually runs, and in a backgrounded tab the animation clock does
   not advance at all. Measured, not assumed — every section reported
   playState "running" with currentTime 0 and stayed invisible indefinitely.
   With the class gone the sections are simply themselves again.

   Note there is deliberately no `.screen.boot-in { animation: none }` here.
   Suppressing the parent fade would mean restoring it on removal, and an
   animation property going from none back to a name RESTARTS it — the screen
   would flash a second time at the end. The parent fade is 300ms and runs
   entirely behind an opaque splash, so it costs nothing to leave alone. */
.boot-in > * { animation: bootRise 460ms var(--ease-out) both; }
.boot-in > *:nth-child(1) { animation-delay: 600ms; }
.boot-in > *:nth-child(2) { animation-delay: 660ms; }
.boot-in > *:nth-child(3) { animation-delay: 715ms; }
.boot-in > *:nth-child(4) { animation-delay: 765ms; }
.boot-in > *:nth-child(5) { animation-delay: 810ms; }
.boot-in > *:nth-child(6) { animation-delay: 850ms; }
/* The steps shorten as they go — 60, 55, 50, 45, 40 — because an even ladder
   reads as a machine dealing cards. Everything past the sixth shares the last
   delay: it is below the fold on any phone, and a ladder that kept growing
   would still be arriving after the customer had started scrolling. */
.boot-in > *:nth-child(n+7) { animation-delay: 885ms; }
@keyframes bootRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes slideIn { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }

/* ================================ tab bar ================================ */
.tabbar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 30;
  height: calc(var(--tabbar-h) + var(--safe-bottom));
  padding-bottom: var(--safe-bottom);
  display: flex; align-items: stretch;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(18px);
  border-top: 1px solid var(--border);
}
.tabbar button {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1);
  color: var(--text-muted); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .01em;
  position: relative; min-height: var(--tap);
  transition: color var(--dur) var(--ease);
}
.tabbar button .ic {
  width: var(--ic-lg); height: var(--ic-lg); display: grid; place-items: center;
  position: relative; z-index: 1;
}
.tabbar button.on { color: var(--brand-text); }
.tabbar button::before {
  content: ""; position: absolute; top: 10px;
  width: 44px; height: 30px; border-radius: var(--r-pill);
  background: var(--brand-soft);
  opacity: 0; transform: scale(.8);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease-pop);
}
.tabbar button.on::before { opacity: 1; transform: scale(1); }

/* ================================ buttons ================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 48px; padding: 0 var(--sp-5); border-radius: var(--r-md);
  font-family: var(--ff-display);
  font-size: var(--fs-md); font-weight: 600; letter-spacing: 0;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease), opacity var(--dur-fast) var(--ease);
}
/* press feedback that does not move surrounding content */
.btn:active { opacity: .82; }
.btn-primary { background: var(--brand); color: var(--on-brand); box-shadow: var(--shadow-brand); }
.btn-primary:active { background: var(--brand-hover); }
.btn-dark { background: var(--brand-strong); color: var(--text-invert); }
:root[data-theme="dark"] .btn-dark { color: var(--on-brand); }
.btn-ghost { background: var(--surface); color: var(--text); box-shadow: var(--shadow-1), var(--ring-inset); }
.btn-ghost:active { background: var(--surface-inset); }
.btn-line { background: transparent; box-shadow: inset 0 0 0 1.5px var(--border-strong); color: var(--text); }
.btn-line:active { background: var(--surface-inset); }
.btn-danger { background: var(--danger-soft); color: var(--on-danger-soft); }
.btn-block { display: flex; width: 100%; }
.btn-lg { min-height: 54px; font-size: var(--fs-base); border-radius: var(--r-lg); }
.btn:disabled { opacity: .45; pointer-events: none; }
/* Running estimate inside a CTA. A dark inset rather than a light one: a
   translucent white pill over the brand green drops white text to 3.9:1. */
.btn .badge-price {
  margin-left: auto;
  padding: 3px var(--sp-2);
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .16);
  font-family: var(--ff-body);
  font-size: var(--fs-2xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chip {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tap); padding: 0 var(--sp-4); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text-2);
  box-shadow: var(--shadow-1), var(--ring-inset);
  font-size: var(--fs-xs); font-weight: 600; white-space: nowrap; flex: 0 0 auto;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.chip .emoji { display: grid; place-items: center; color: var(--text-muted); }
.chip.on {
  background: var(--brand-strong); color: var(--text-invert);
  box-shadow: var(--shadow-brand);
}
:root[data-theme="dark"] .chip.on { color: var(--on-brand); }
.chip.on .emoji { color: currentColor; }

.pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-height: 24px; padding: 2px var(--sp-3); border-radius: var(--r-pill);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}
.pill-eco { background: var(--brand-soft); color: var(--on-brand-soft); }
.pill-sun { background: var(--accent-soft); color: var(--accent); }
.pill-hot { background: var(--danger-soft); color: var(--on-danger-soft); }

/* ============================= rows / cards ============================== */
/* Clips at the content edge rather than bleeding to the screen edge. Full-bleed
   only reads correctly when the last visible card is cut; with a fixed card
   width there is always some viewport where two cards land flush against the
   bezel instead. Stopping at the gutter keeps the right-hand breathing room
   constant and guarantees a partial card as the scroll affordance. */
.row-scroll {
  display: flex; gap: var(--sp-3); overflow-x: auto;
  padding: var(--sp-1) 0 var(--sp-2);
  scrollbar-width: none;
  /* proximity, not mandatory: a flick still lands where the finger left it,
     but a card never comes to rest half-hidden behind the content edge */
  scroll-snap-type: x proximity;
}
.row-scroll > * { scroll-snap-align: start; }
.row-scroll::-webkit-scrollbar { display: none; }

.section-head { display: flex; align-items: baseline; justify-content: space-between; margin: var(--sp-6) 0 var(--sp-3); }
.section-head h2 { font-size: var(--fs-lg); margin: 0; }
/* Grows leftwards to reach a 44px target. Padding on the right plus a negative
   margin would make the box overhang the content edge — in a flex row the
   negative margin shifts the border box out rather than absorbing the padding. */
.section-head .link {
  color: var(--brand-text); font-size: var(--fs-xs); font-weight: 600;
  min-height: var(--tap); min-width: var(--tap); padding-left: var(--sp-3);
  display: inline-flex; align-items: center; justify-content: flex-end;
}

.card { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-1), var(--ring-inset); }

/* ------------------------------ hero / promo ---------------------------- */
.hero {
  margin-top: var(--sp-2);
  border-radius: var(--r-xl);
  padding: var(--sp-5);
  color: var(--text-invert);
  position: relative; overflow: hidden;
  background:
    radial-gradient(120% 120% at 100% 0%, rgba(110, 231, 183, .3) 0%, transparent 48%),
    linear-gradient(150deg, var(--p-green-700), var(--p-green-900));
  box-shadow: 0 12px 30px rgba(6, 78, 59, .3);
  transition: opacity var(--dur-fast) var(--ease);
}
.hero:active { opacity: .93; }
.hero__leaf {
  position: absolute; right: -26px; bottom: -34px;
  color: #fff; opacity: .1; pointer-events: none;
  transform: rotate(-18deg);
}
.hero__leaf .ico { width: 168px; height: 168px; }
.hero .pill { margin-bottom: var(--sp-3); }
.hero h3 { font-size: var(--fs-xl); margin: 0 0 var(--sp-2); color: #fff; }
.hero p { margin: 0 0 var(--sp-4); color: #cdeede; font-size: var(--fs-sm); max-width: 78%; line-height: var(--lh-snug); }
.hero__foot { display: flex; align-items: center; gap: var(--sp-3); }
.hero__price { font-size: var(--fs-sm); font-weight: 600; }
.hero__price b { font-family: var(--ff-display); font-size: var(--fs-2xl); font-weight: 600; letter-spacing: -.02em; }
.hero .btn { min-height: 44px; }

/* ---------------------------- category grid ----------------------------- */
.cat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); }
.cat-card {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-2); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-1), var(--ring-inset);
  text-align: center; min-height: var(--tap);
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.cat-card:active { background: var(--surface-inset); }
.cat-card .ic {
  width: 48px; height: 48px; border-radius: var(--r-md);
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--svc) var(--tint-mix), var(--tint-base));
  color: color-mix(in srgb, var(--svc), #fff var(--svc-shift));
}
.cat-card .ic .ico { width: var(--ic-lg); height: var(--ic-lg); }
.cat-card .nm { font-family: var(--ff-display); font-size: var(--fs-xs); font-weight: 600; line-height: var(--lh-tight); }
.cat-card .ct { font-size: var(--fs-2xs); color: var(--text-muted); }

/* ---------------------------- service cards ----------------------------- */
.svc-thumb {
  width: 46px; height: 46px; border-radius: var(--r-md);
  display: grid; place-items: center; flex: 0 0 auto;
  background: color-mix(in srgb, var(--svc) var(--tint-mix), var(--tint-base));
  color: color-mix(in srgb, var(--svc), #fff var(--svc-shift));
}
.svc-thumb .ico { width: var(--ic-lg); height: var(--ic-lg); }

.svc-hcard {
  width: 172px; flex: 0 0 auto;
  background: var(--surface); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1), var(--ring-inset);
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2);
  position: relative;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.svc-hcard:active { background: var(--surface-2); box-shadow: var(--shadow-2), var(--ring-inset); }

/* Popular services as a three-across grid rather than a scrolling strip.
   The card is fixed at 172px for the scroller, which has to give way to the
   column here, and the two-line blurb is dropped: at a third of a phone
   there is room for what the service IS and what it costs, and a clamped
   sentence squeezed under those two only makes both harder to read. The blurb
   is still on the service list and the detail page.

   THREE across, not four, and the track is minmax(0, 1fr) rather than a bare
   1fr. Both halves of that matter:

   `1fr` is really `minmax(auto, 1fr)`, and that `auto` floor is the item's
   MIN-CONTENT width — the longest unbreakable word in it. "Formaldehyde" and
   "Conditioner" are wider than a quarter of a phone, so the columns refused to
   shrink to their share and the four tracks came out 118+98+100+81 = 398px
   inside a 328px grid. The overflow is invisible rather than scrollable: the
   fourth card sat at x=352 and was clipped away by the phone frame, so the
   home screen advertised five popular services while six were in the DOM.
   minmax(0, ...) drops that floor to zero, which is what makes 1fr behave the
   way it reads.

   Three columns then fall out of the content: there are six popular services,
   so 3 x 2 fills both rows exactly, and 101px a card is a good deal more
   readable than the 73px four-across would give on the same phone. If a
   seventh is ever flagged popular, the last row goes ragged — that is the
   moment to revisit this, not before. */
.pop-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); align-items: stretch; }
.pop-grid .svc-hcard { width: auto; padding: var(--sp-3); gap: var(--sp-1); }
.pop-grid .svc-hcard .ds { display: none; }
.pop-grid .svc-hcard .nm { font-size: var(--fs-xs); }
.pop-grid .svc-hcard .pr { font-size: var(--fs-2xs); margin-top: var(--sp-1); }
/* The favourite button is absolutely positioned in the card corner and would
   sit on top of a title that now starts higher. Not worth keeping at this
   size — the heart is still on the service list and the detail page. */
.pop-grid .svc-hcard .fav-btn { display: none; }
/* Only the genuinely small handsets fall back. Everything phone-sized and up
   keeps the three columns. minmax(0, ...) here too: the min-content floor is
   what broke the wider grid, and two tracks on a 320px handset have even less
   room to absorb a long service name. */
@media (max-width: 340px) { .pop-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* No right inset for the favourite button: it is absolutely positioned in the
   card's top corner, level with the thumbnail, and never reaches the title. */
.svc-hcard .nm { font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600; line-height: var(--lh-tight); }
.svc-hcard .ds {
  font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-snug);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.svc-hcard .pr { margin-top: auto; font-size: var(--fs-xs); font-weight: 700; color: var(--brand-text); }
.svc-hcard .pr small { color: var(--text-muted); font-weight: 400; }

/* icon-only control: 44px hit box, 20px glyph */
.fav-btn {
  position: absolute; top: 6px; right: 6px;
  width: var(--tap); height: var(--tap); border-radius: 50%;
  display: grid; place-items: center;
  background: transparent; color: var(--text-muted);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.fav-btn .ico { width: var(--ic-md); height: var(--ic-md); }
.fav-btn:active { background: var(--surface-inset); }
.fav-btn.on { color: var(--p-rose-500); }
:root[data-theme="dark"] .fav-btn.on { color: var(--p-rose-300); }

.svc-row {
  display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-3);
  background: var(--surface); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1), var(--ring-inset);
  margin-bottom: var(--sp-3); position: relative;
  transition: background var(--dur) var(--ease);
}
.svc-row:active { background: var(--surface-2); }
.svc-row .svc-thumb { width: 52px; height: 52px; }
.svc-row .meta { flex: 1; min-width: 0; }
.svc-row .nm {
  font-family: var(--ff-display); font-size: var(--fs-md); font-weight: 600; letter-spacing: -.01em;
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
}
.svc-row .ds {
  font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-snug); margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.svc-row .pr { font-size: var(--fs-xs); font-weight: 700; color: var(--brand-text); margin-top: var(--sp-1); }
.svc-row .pr small { color: var(--text-muted); font-weight: 400; }
.svc-row .go { flex: 0 0 auto; color: var(--text-muted); }
.svc-row .fav-btn { position: static; margin-left: calc(var(--sp-2) * -1); align-self: center; }

/* ------------------------------ steps / why ----------------------------- */
.steps { display: flex; flex-direction: column; gap: var(--sp-4); }
.step { display: flex; gap: var(--sp-4); align-items: flex-start; }
.step .n {
  width: 36px; height: 36px; flex: 0 0 auto; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--on-brand-soft);
  position: relative;
}
.step .n .ico { width: var(--ic-md); height: var(--ic-md); }
.step:not(:last-child) .n::after {
  content: ""; position: absolute; top: 42px; left: 50%; transform: translateX(-50%);
  width: 2px; height: 24px; background: var(--border);
}
.step .tx b { font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600; }
.step .tx p { margin: 2px 0 0; font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-snug); }

.why-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.why-card { padding: var(--sp-4); border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--shadow-1), var(--ring-inset); }
.why-card .ic { color: var(--brand-text); margin-bottom: var(--sp-2); }
.why-card .ic .ico { width: var(--ic-lg); height: var(--ic-lg); }
.why-card b { font-family: var(--ff-display); font-size: var(--fs-xs); font-weight: 600; line-height: var(--lh-tight); display: block; }
.why-card p { margin: var(--sp-1) 0 0; font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-snug); }

/* ----------------------------- contact card ----------------------------- */
.contact {
  padding: var(--sp-5); border-radius: var(--r-xl);
  background: var(--surface-invert); color: var(--text-invert);
  position: relative; overflow: hidden;
  border: 1px solid var(--border-invert);
}
.contact h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-lg); color: #fff; }
.contact p { margin: 0 0 var(--sp-4); font-size: var(--fs-2xs); color: var(--text-invert-2); }
.contact__row { display: flex; gap: var(--sp-3); }
.contact__btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-2); min-height: var(--tap);
  border-radius: var(--r-md); background: rgba(255,255,255,.09);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--text-invert);
  transition: background var(--dur) var(--ease);
}
.contact__btn:active { background: rgba(255,255,255,.16); }
.contact__btn .ic { display: grid; place-items: center; }
.contact__areas {
  margin-top: var(--sp-4); font-size: var(--fs-2xs); color: var(--text-invert-2);
  display: flex; align-items: center; gap: var(--sp-2);
}

/* --------------------------------- search -------------------------------- */
.search {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 48px; padding: 0 var(--sp-4);
  background: var(--surface); border-radius: var(--r-pill);
  box-shadow: var(--shadow-1), var(--ring-inset);
  margin: var(--sp-1) 0 var(--sp-2);
  transition: box-shadow var(--dur) var(--ease);
}
.search:focus-within { box-shadow: var(--shadow-1), inset 0 0 0 1.5px var(--brand); }
.search > .ico, .search svg { color: var(--text-muted); }
.search input {
  border: none; outline: none; background: transparent; flex: 1;
  font-size: var(--fs-md); color: var(--text); min-width: 0;
}
.search input::placeholder { color: var(--text-muted); }
.search .clr {
  color: var(--text-muted); width: var(--tap); height: var(--tap);
  display: grid; place-items: center; margin-right: calc(var(--sp-3) * -1);
}

/* ============================= service detail ============================ */
.detail-hero {
  border-radius: var(--r-xl); padding: var(--sp-6) var(--sp-5) var(--sp-5);
  color: var(--text-invert); position: relative; overflow: hidden; margin-top: var(--sp-1);
}
.detail-hero .big {
  position: absolute; right: var(--sp-4); top: var(--sp-4);
  color: #fff; opacity: .22; pointer-events: none;
}
.detail-hero .big .ico { width: 84px; height: 84px; }
.detail-hero .pill { margin-bottom: var(--sp-3); }
.detail-hero h1 { font-size: var(--fs-2xl); margin: 0 0 var(--sp-2); max-width: 74%; color: #fff; }
.detail-hero .sub { font-size: var(--fs-xs); color: #cdeede; max-width: 78%; line-height: var(--lh-body); }

.detail-stats { display: flex; gap: var(--sp-3); margin: var(--sp-4) 0; }
.stat { flex: 1; background: var(--surface); border-radius: var(--r-md); box-shadow: var(--shadow-1), var(--ring-inset); padding: var(--sp-3); text-align: center; }
.stat .v {
  font-family: var(--ff-display); font-size: var(--fs-md); font-weight: 600; color: var(--text);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-1);
}
.stat .v .ico { color: var(--accent); }
.stat .l { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 2px; }

.block { margin-top: var(--sp-5); }
.block h3 { font-size: var(--fs-base); margin: 0 0 var(--sp-3); }
.incl { display: flex; flex-direction: column; gap: var(--sp-3); padding: 0; margin: 0; }
.incl li { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: var(--fs-sm); color: var(--text-2); line-height: var(--lh-snug); list-style: none; }
.incl li .tick {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--brand-soft); color: var(--on-brand-soft);
  display: grid; place-items: center; margin-top: 1px;
}
.note {
  font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-body);
  background: var(--surface-2); border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border);
}

.reviews { display: flex; flex-direction: column; gap: var(--sp-3); }
.review { background: var(--surface); border-radius: var(--r-md); box-shadow: var(--shadow-1), var(--ring-inset); padding: var(--sp-3) var(--sp-4); }
.review .top { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.review .nm { font-family: var(--ff-display); font-weight: 600; font-size: var(--fs-xs); }
.review .st { margin-left: auto; }
.review p { margin: 0; font-size: var(--fs-2xs); color: var(--text-2); line-height: var(--lh-snug); }

/* sticky action bar */
.action-bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 25;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--gutter) calc(var(--sp-3) + var(--safe-bottom));
  background: linear-gradient(180deg, transparent, var(--bg) 24%);
}
.action-bar .price { display: flex; flex-direction: column; line-height: var(--lh-tight); }
.action-bar .price small { font-size: var(--fs-2xs); color: var(--text-muted); }
.action-bar .price b { font-family: var(--ff-display); font-size: var(--fs-xl); font-weight: 600; }
.action-bar .btn { flex: 1; }

/* ============================== booking flow ============================= */
/* progress rail — distinct from the quantity stepper below */
.stepper { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) var(--gutter) 0; }
.stepper .s { flex: 1; height: 4px; border-radius: 2px; background: var(--track); overflow: hidden; transition: background var(--dur) var(--ease); }
.stepper .s.done { background: var(--brand); }
.stepper .lbl { padding: var(--sp-2) var(--gutter) 0; font-size: var(--fs-2xs); color: var(--text-muted); font-weight: 600; }

/* Sits directly under the size box, so it reads as an explanation of what was
   just typed rather than as a separate notice. */
.band-note { margin-top: var(--sp-2); }

/* ------------------------- quantity stepper (+/−) ------------------------ */
/* Named .qty, not .stepper: .stepper above is the four-segment progress bar at
   the top of the booking flow. The counter used to carry that class too and was
   silently inheriting its flex and padding rules. */
.qty { display: inline-flex; align-items: center; gap: var(--sp-2); }
.qty button {
  width: 40px; height: 40px; border-radius: var(--r-md);
  display: grid; place-items: center;
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-1), var(--ring-inset);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.qty button:hover:not(:disabled) { background: var(--brand-soft); }
.qty button:active:not(:disabled) { transform: scale(.94); }
/* Disabled rather than hidden: the control staying put tells the customer they
   have hit a limit, where a vanishing button just reads as a broken layout. */
.qty button:disabled { opacity: .32; cursor: not-allowed; }
.qty > b {
  min-width: 2.5ch; text-align: center;
  font-size: var(--fs-md); font-weight: 700; font-variant-numeric: tabular-nums;
}

/* ------------------------- per-type unit counts -------------------------- */
.mixlist { display: flex; flex-direction: column; gap: var(--sp-2); }
.mixrow {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md); background: var(--surface);
  box-shadow: var(--shadow-1), var(--ring-inset);
  transition: box-shadow var(--dur) var(--ease);
}
/* Rows at zero stay visible but unhighlighted, so the ones actually being
   booked are readable at a glance without hiding the others. */
.mixrow.on { box-shadow: var(--shadow-1), 0 0 0 1.5px var(--brand) inset; }
.mixic { display: grid; place-items: center; color: var(--text-muted); flex: 0 0 auto; }
.mixrow.on .mixic { color: var(--brand); }
.mixnm { flex: 1; min-width: 0; font-size: var(--fs-sm); font-weight: 600; }
.mixrow .qty { margin-left: auto; }
.mixtotal {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-top: var(--sp-3); padding: 0 var(--sp-3);
  font-size: var(--fs-xs); color: var(--text-muted);
}
.mixtotal b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }

.field { margin-bottom: var(--sp-4); }
.field > label { display: block; font-family: var(--ff-display); font-size: var(--fs-xs); font-weight: 600; margin-bottom: var(--sp-2); color: var(--text); }
.field .hint { font-size: var(--fs-2xs); color: var(--text-muted); font-weight: 400; margin-left: var(--sp-2); }
.input, .select, .textarea {
  width: 100%; min-height: 50px; padding: 0 var(--sp-4); border-radius: var(--r-md);
  background: var(--surface); border: 1.5px solid var(--border);
  font-size: var(--fs-md); color: var(--text);
  outline: none; transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.textarea { min-height: 88px; padding: var(--sp-3) var(--sp-4); resize: vertical; line-height: var(--lh-body); }
.input::placeholder, .textarea::placeholder { color: var(--text-muted); }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); outline: none;
}
.select {
  -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364796f' stroke-width='2' stroke-linecap='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 14px center; padding-right: 42px;
}
:root[data-theme="dark"] .select {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238ba098' stroke-width='2' stroke-linecap='round'><path d='M6 9l6 6 6-6'/></svg>");
}
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }

/* option / choice tiles */
.opt-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.opt {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4); border-radius: var(--r-md); min-height: var(--tap);
  background: var(--surface); border: 1.5px solid var(--border); text-align: left;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.opt.on { border-color: var(--brand); background: var(--brand-soft); }
.opt .oi {
  width: 40px; height: 40px; border-radius: var(--r-sm);
  display: grid; place-items: center;
  background: var(--surface-inset); color: var(--text-2); flex: 0 0 auto;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.opt.on .oi { background: var(--surface); color: var(--brand-text); }
.opt .ot { flex: 1; min-width: 0; }
.opt .ot b { font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600; display: block; }
.opt .ot span { font-size: var(--fs-2xs); color: var(--text-muted); }
.opt .chk {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--border-strong); flex: 0 0 auto;
  display: grid; place-items: center; color: transparent;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.opt.on .chk { border-color: var(--brand); background: var(--brand); color: var(--on-brand); }

.grid-choice { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); }
.tile {
  padding: var(--sp-3) var(--sp-2); min-height: var(--tap); border-radius: var(--r-md);
  background: var(--surface); border: 1.5px solid var(--border);
  text-align: center; font-family: var(--ff-display); font-weight: 600; font-size: var(--fs-xs); color: var(--text);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
}
/* Illustration above the label so the option can be recognised before it is
   read — the point is identifying your own unit, room or situation. */
.tile .ico {
  width: var(--ic-lg); height: var(--ic-lg);
  margin: 2px auto var(--sp-2);
  color: var(--text-muted);
  transition: color var(--dur) var(--ease);
}
.tile.on .ico { color: var(--brand-text); }
.tile small { display: block; font-family: var(--ff-body); font-size: var(--fs-2xs); color: var(--text-muted); font-weight: 400; margin-top: 2px; }
.tile.on { border-color: var(--brand); background: var(--brand-soft); color: var(--on-brand-soft); }
.tile.on small { color: var(--on-brand-soft); opacity: .8; }

/* quantity stepper (rooms, beds, hours) — scoped so it cannot collide
   with the progress rail that shares the .stepper name upstream */
.field .stepper, .qty-stepper { display: flex; align-items: center; gap: var(--sp-4); padding: 0; }
.field .stepper button, .qty-stepper button {
  width: var(--tap); height: var(--tap); border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-1), var(--ring-inset);
  color: var(--brand-text); display: grid; place-items: center;
  transition: background var(--dur) var(--ease);
}
.field .stepper button:active, .qty-stepper button:active { background: var(--surface-inset); }
.field .stepper b, .qty-stepper b { font-family: var(--ff-display); font-size: var(--fs-lg); min-width: 30px; text-align: center; }

/* date / time */
.calendar { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-1), var(--ring-inset); padding: var(--sp-4) var(--sp-2); }
.cal-head { padding: 0 var(--sp-2); }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-3); }
.cal-month { font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600; }
.cal-nav {
  width: var(--tap); height: var(--tap); border-radius: 50%;
  background: var(--surface-inset); display: grid; place-items: center; color: var(--text);
  transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.cal-nav svg { width: var(--ic-sm); height: var(--ic-sm); }
.cal-nav:disabled { opacity: .3; }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 0; }
.cal-grid.cal-dows { margin-bottom: var(--sp-1); }
.cal-grid.cal-days { grid-auto-rows: 44px; }
.cal-dow { text-align: center; font-size: var(--fs-micro); font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; padding: var(--sp-1) 0; }
.cal-cell {
  width: 100%; height: 100%; min-width: 0; padding: 0;
  display: grid; place-items: center; border-radius: 50%;
  font-size: var(--fs-xs); font-weight: 600; color: var(--text);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cal-cell.empty { visibility: hidden; }
.cal-cell.disabled { color: var(--text-muted); opacity: .38; }
.cal-cell.on { background: var(--brand); color: var(--on-brand); }
button.cal-cell:not(.on):active { background: var(--brand-soft); }

.recur-note {
  display: flex; align-items: flex-start; gap: var(--sp-2); margin-top: var(--sp-3);
  padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--brand-soft); color: var(--on-brand-soft);
  font-size: var(--fs-2xs); line-height: var(--lh-body);
}
.recur-note svg { flex: 0 0 auto; width: var(--ic-sm); height: var(--ic-sm); margin-top: 1px; }

/* Two across, not three. There are four slots, so three columns always left a
   lonely fourth on its own row — and the labels now carry a full clock range
   ("09:00 a.m. – 12:00 p.m.") that has nowhere to go in a third of a phone. */
.time-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
.time-slot {
  padding: var(--sp-3) var(--sp-2); min-height: var(--tap); border-radius: var(--r-md);
  background: var(--surface); border: 1.5px solid var(--border);
  text-align: center; font-weight: 600; font-size: var(--fs-sm); color: var(--text);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.time-slot.on { border-color: var(--brand); background: var(--brand-soft); color: var(--on-brand-soft); }

/* helper cards */
.helper {
  display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3);
  border-radius: var(--r-md); min-height: var(--tap);
  background: var(--surface); border: 1.5px solid var(--border);
  margin-bottom: var(--sp-3); text-align: left;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.helper.on { border-color: var(--brand); background: var(--brand-soft); }
.helper .av {
  width: 48px; height: 48px; border-radius: 50%;
  background: var(--brand-soft-2); color: var(--on-brand-soft);
  display: grid; place-items: center; flex: 0 0 auto;
}
.helper .info { flex: 1; min-width: 0; }
.helper .nm { font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.helper .sub { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 2px; }
.helper .stat2 { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-1); font-size: var(--fs-2xs); color: var(--text-2); font-weight: 600; }
.helper .stat2 .star { color: var(--accent); display: inline-flex; align-items: center; gap: 3px; }
.helper .chk {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--border-strong); flex: 0 0 auto;
  display: grid; place-items: center; color: transparent;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.helper.on .chk { border-color: var(--brand); background: var(--brand); color: var(--on-brand); }

.mini-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .03em;
  color: var(--on-brand-soft); background: var(--brand-soft);
  padding: 2px var(--sp-2); border-radius: var(--r-pill);
}

/* review summary */
.summary { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-1), var(--ring-inset); overflow: hidden; }
.summary .sr { display: flex; gap: var(--sp-3); padding: var(--sp-4); align-items: flex-start; }
.summary .sr + .sr { border-top: 1px solid var(--border); }
.summary .sr .k { font-size: var(--fs-2xs); color: var(--text-muted); font-weight: 600; width: 82px; flex: 0 0 auto; }
.summary .sr .v { font-size: var(--fs-sm); font-weight: 600; flex: 1; text-align: right; }
.summary .sr .v small { display: block; color: var(--text-muted); font-weight: 400; margin-top: 2px; }
.total-line { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-4); background: var(--brand-soft); }
.total-line .k { font-weight: 600; color: var(--on-brand-soft); }
.total-line .v { font-family: var(--ff-display); font-size: var(--fs-xl); font-weight: 600; color: var(--on-brand-soft); }

/* --------------------------------- success ------------------------------- */
.success { text-align: center; padding: var(--sp-10) var(--sp-6); }
.success .burst {
  width: 96px; height: 96px; margin: 0 auto var(--sp-5); border-radius: 50%;
  background: var(--brand-soft); color: var(--brand-text);
  display: grid; place-items: center;
  animation: pop 500ms var(--ease-pop);
}
.success .burst .ico { width: 48px; height: 48px; }
@keyframes pop { 0% { transform: scale(0); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.success h2 { font-size: var(--fs-2xl); margin: 0 0 var(--sp-2); }
.success p { color: var(--text-muted); font-size: var(--fs-sm); line-height: var(--lh-body); margin: 0 auto var(--sp-2); max-width: 280px; }
.success .refbox {
  display: inline-block; margin: var(--sp-2) 0 var(--sp-6);
  padding: var(--sp-3) var(--sp-5); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--shadow-1), var(--ring-inset);
  font-family: var(--ff-display); font-weight: 600; letter-spacing: .06em;
}

/* ================================ bookings =============================== */
.seg { display: flex; background: var(--bg-sunken); border-radius: var(--r-pill); padding: var(--sp-1); margin: var(--sp-2) 0 var(--sp-4); }
.seg button {
  flex: 1; min-height: var(--tap); border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.seg button.on { background: var(--surface); color: var(--brand-text); box-shadow: var(--shadow-1); }

.booking-card { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-1), var(--ring-inset); padding: var(--sp-4); margin-bottom: var(--sp-3); }
.booking-card .bc-top { display: flex; align-items: center; gap: var(--sp-3); }
.booking-card .svc-thumb { width: 44px; height: 44px; }
.booking-card .bc-nm { font-family: var(--ff-display); font-size: var(--fs-md); font-weight: 600; }
.booking-card .bc-ref { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 1px; }
.status-badge { margin-left: auto; font-size: var(--fs-micro); font-weight: 700; letter-spacing: .03em; padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill); }
.st-confirmed { background: var(--brand-soft); color: var(--on-brand-soft); }
.st-pending { background: var(--warn-soft); color: var(--warn); }
.st-completed { background: var(--neutral-soft); color: var(--on-neutral-soft); }
.st-cancelled { background: var(--danger-soft); color: var(--on-danger-soft); }
.bc-meta { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--border-strong); display: flex; flex-direction: column; gap: var(--sp-2); }
.bc-meta .m { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-2xs); color: var(--text-2); }
.bc-meta .m .ic { color: var(--text-muted); display: grid; place-items: center; }
.bc-actions { display: flex; gap: var(--sp-3); margin-top: var(--sp-3); }
.bc-actions .btn { flex: 1; min-height: var(--tap); font-size: var(--fs-xs); }

/* empty states */
.empty { text-align: center; padding: var(--sp-12) var(--sp-8); }
.empty .ic {
  width: 72px; height: 72px; margin: 0 auto var(--sp-4); border-radius: 50%;
  background: var(--surface-inset); color: var(--text-muted);
  display: grid; place-items: center;
}
.empty .ic .ico { width: 32px; height: 32px; }
.empty h3 { font-size: var(--fs-base); margin: 0 0 var(--sp-2); }
.empty p { color: var(--text-muted); font-size: var(--fs-sm); line-height: var(--lh-body); margin: 0 0 var(--sp-5); }

/* ================================= profile =============================== */
.profile-head {
  display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4);
  background: var(--surface); border-radius: var(--r-xl);
  box-shadow: var(--shadow-1), var(--ring-inset); margin-top: var(--sp-1);
  text-align: left; width: 100%;
}
.profile-head .av {
  width: 60px; height: 60px; border-radius: 50%;
  background: linear-gradient(140deg, var(--p-green-500), var(--p-green-700));
  display: grid; place-items: center;
  font-family: var(--ff-display); font-size: var(--fs-2xl); font-weight: 600;
  color: #fff; flex: 0 0 auto; overflow: hidden;
}
.profile-head .nm { font-family: var(--ff-display); font-size: var(--fs-lg); font-weight: 600; }
.profile-head .sub { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 2px; }

.badges-row { display: flex; gap: var(--sp-3); margin: var(--sp-4) 0; }
.trust { flex: 1; background: var(--surface); border-radius: var(--r-md); box-shadow: var(--shadow-1), var(--ring-inset); padding: var(--sp-3); text-align: center; }
.trust .ic { color: var(--brand-text); display: grid; place-items: center; }
.trust b { display: block; font-family: var(--ff-display); font-size: var(--fs-2xs); font-weight: 600; margin-top: var(--sp-1); }
.trust span { font-size: var(--fs-2xs); color: var(--text-muted); }

.list-group { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-1), var(--ring-inset); overflow: hidden; margin-bottom: var(--sp-4); }
.list-group .li {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); min-height: 56px; width: 100%; text-align: left;
  transition: background var(--dur) var(--ease);
}
button.li:active { background: var(--surface-inset); }
.list-group .li + .li { border-top: 1px solid var(--border); }
.list-group .li .ic {
  width: 36px; height: 36px; border-radius: var(--r-sm);
  background: var(--surface-inset); color: var(--text-2);
  display: grid; place-items: center; flex: 0 0 auto;
}
.list-group .li .tx { flex: 1; min-width: 0; }
.list-group .li .tx b { font-size: var(--fs-sm); font-weight: 600; }
.list-group .li .tx span { display: block; font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 1px; }
.list-group .li .val { font-size: var(--fs-xs); color: var(--text-muted); font-weight: 600; }
.list-group .li .go { color: var(--text-muted); display: grid; place-items: center; }
.list-title { font-size: var(--fs-micro); font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; margin: 0 var(--sp-1) var(--sp-2); }

/* theme picker — three-way segmented control */
.theme-seg { display: flex; gap: 2px; background: var(--bg-sunken); border-radius: var(--r-pill); padding: 2px; }
.theme-seg button {
  min-width: var(--tap); min-height: var(--tap); padding: 0 var(--sp-3); border-radius: var(--r-pill);
  display: grid; place-items: center; color: var(--text-muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.theme-seg button.on { background: var(--surface); color: var(--brand-text); box-shadow: var(--shadow-1); }
.theme-seg .ico { width: var(--ic-md); height: var(--ic-md); }

.app-foot { text-align: center; color: var(--text-muted); font-size: var(--fs-2xs); padding: var(--sp-3) 0 var(--sp-1); line-height: var(--lh-body); }
.app-foot .heart { color: var(--brand); }

/* =============================== bottom sheet ============================ */
/* ============================ launch splash ==============================
   Positioned exactly like .app, including the 40px desktop offset, so the fake
   status bar stays visible above it — which is what a real app launch looks
   like; covering it makes the phone frame read as a web page in a box.

   The whole thing is CSS. There is no JS that hides it, only JS that waits for
   it, because a splash that depends on a script to leave is a splash that can
   trap the app. It ends at opacity 0 AND visibility hidden: opacity alone
   leaves an invisible sheet swallowing every tap.

   Above the promo (95) rather than below. The offer is held back until the
   splash is done, but if it ever does open early it should be covered and then
   revealed, not flashed over the logo. */
.splash {
  position: absolute; inset: 0; z-index: 96;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; text-align: center;
  background: linear-gradient(165deg, #0f5d3f 0%, #0a3f2b 100%);
  animation: splashOut 1000ms var(--ease-out) forwards;
}
@media (min-width: 880px) { .splash { top: 40px; } }
.splash-mark {
  width: 84px; height: 84px; border-radius: 24px; overflow: hidden;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .35);
  animation: splashPop 440ms var(--ease-pop) both;
}
.splash-mark img { width: 100%; height: 100%; display: block; }
.splash-word {
  margin-top: var(--sp-4);
  font-family: var(--ff-display); font-size: 22px; font-weight: 600;
  letter-spacing: -.02em; color: #eafff6;
  animation: splashRise 420ms var(--ease-out) 150ms both;
}
/* Same #8fceb4 as the desktop panel beside the phone — the two are the same
   lockup and must not drift to two different greens. */
.splash-zh { font-size: 14px; color: #8fceb4; animation: splashRise 420ms var(--ease-out) 230ms both; }

@keyframes splashOut {
  0%, 68% { opacity: 1; visibility: visible; }
  100%    { opacity: 0; visibility: hidden; }
}
@keyframes splashPop  { from { opacity: 0; transform: scale(.82); } to { opacity: 1; transform: none; } }
@keyframes splashRise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Reduced motion needs no rule of its own here, and adding one was a mistake
   worth recording. The blanket prefers-reduced-motion block near the end of
   this file already collapses every animation to .01ms with !important, so it
   wins over anything written here regardless of order — a local override would
   be dead code that reads as though it were doing something.

   What that blanket rule means for the splash is worth being explicit about:
   splashOut finishes almost instantly and, because it is `forwards`, the
   element holds its 100% keyframe. That keyframe is `opacity: 0;
   visibility: hidden`, so somebody who asked for less motion gets no splash at
   all and an app they can touch immediately. This is also exactly why the
   endpoint has to live in the keyframe rather than in a script: collapse the
   duration of an animation that ends anywhere else and the logo would stay. */

.scrim {
  position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(2px); z-index: 80;
  opacity: 0; pointer-events: none; transition: opacity var(--dur-slow) var(--ease);
}
.scrim.show { opacity: 1; pointer-events: auto; }
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 81;
  background: var(--bg); border-radius: var(--r-xl) var(--r-xl) 0 0;
  padding: var(--sp-2) var(--gutter) calc(var(--sp-5) + var(--safe-bottom));
  transform: translateY(100%); transition: transform var(--dur-slow) var(--ease-out);
  max-height: 86%; overflow-y: auto; box-shadow: 0 -14px 40px rgba(0,0,0,.24);
  border-top: 1px solid var(--border);
}
.sheet.show { transform: translateY(0); }
.sheet .grab { width: 42px; height: 5px; border-radius: 3px; background: var(--border-strong); margin: var(--sp-2) auto var(--sp-4); }
.sheet h3 { margin: 0 0 var(--sp-4); font-size: var(--fs-lg); text-align: center; }

.lang-opt {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4); min-height: var(--tap); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--shadow-1); margin-bottom: var(--sp-3);
  border: 1.5px solid transparent; text-align: left; width: 100%;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.lang-opt.on { border-color: var(--brand); background: var(--brand-soft); }
.lang-opt .flag {
  width: 38px; height: 38px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center;
  background: var(--surface-inset); color: var(--text-2);
  font-family: var(--ff-display); font-size: var(--fs-sm); font-weight: 600;
}
.lang-opt.on .flag { background: var(--surface); color: var(--brand-text); }
.lang-opt .tx { flex: 1; }
.lang-opt .tx b { font-size: var(--fs-md); font-weight: 600; }
.lang-opt .tx span { display: block; font-size: var(--fs-2xs); color: var(--text-muted); }
.lang-opt .chk { color: var(--brand); display: grid; place-items: center; }

/* ================================== toast ================================ */
.toast-wrap {
  position: absolute; left: 0; right: 0; bottom: calc(var(--tabbar-h) + var(--sp-5)); z-index: 90;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  pointer-events: none; padding: 0 var(--sp-5);
}
.toast {
  background: var(--surface-invert); color: var(--text-invert);
  padding: var(--sp-3) var(--sp-5); border-radius: var(--r-pill);
  font-size: var(--fs-sm); font-weight: 600; box-shadow: var(--shadow-3);
  display: flex; align-items: center; gap: var(--sp-2);
  animation: toastIn var(--dur-slow) var(--ease-pop); max-width: 100%;
  border: 1px solid var(--border-invert);
}
@keyframes toastIn { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }
.toast.out { animation: toastOut var(--dur) var(--ease) forwards; }
@keyframes toastOut { to { opacity: 0; transform: translateY(10px); } }

/* =============================== utilities =============================== */
.muted { color: var(--text-muted); }
.center { text-align: center; }
.mt8 { margin-top: var(--sp-2); } .mt16 { margin-top: var(--sp-4); } .mt24 { margin-top: var(--sp-6); }
.disclaimer { font-size: var(--fs-2xs); color: var(--text-muted); text-align: center; line-height: var(--lh-body); padding: var(--sp-1) var(--sp-3) 0; }
.spin { width: 18px; height: 18px; border: 2.5px solid rgba(255,255,255,.35); border-top-color: #fff; border-radius: 50%; animation: rot .7s linear infinite; }
@keyframes rot { to { transform: rotate(360deg); } }

/* ============================= auth / sign-in ============================ */
.gbtn {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3); width: 100%;
  min-height: 52px; border-radius: var(--r-md); background: #fff; color: #1f1f1f;
  box-shadow: inset 0 0 0 1.5px #dadce0; font-size: var(--fs-md); font-weight: 600;
  transition: background var(--dur) var(--ease);
}
.gbtn:active { background: #f7f8f8; }
.gbtn svg { width: 20px; height: 20px; }
/* Sign in with Apple. Same box as .gbtn on purpose — Apple's guidance is that
   the button must be no smaller and no less prominent than the alternatives,
   so the two differ only in colour. Black-on-white is one of Apple's three
   permitted styles; the logo inherits currentColor. Deliberately NOT themed to
   the app's palette: this button's appearance is specified by Apple, and a
   green one would be wrong in a way a reviewer is asked to look for. */
.abtn {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3); width: 100%;
  min-height: 52px; border-radius: var(--r-md); background: #000; color: #fff;
  font-size: var(--fs-md); font-weight: 600;
  transition: opacity var(--dur) var(--ease);
}
.abtn:active { opacity: .75; }
.abtn svg { width: 19px; height: 19px; margin-top: -2px; }
/* On a dark background pure black loses its edge, so it inverts — also one of
   Apple's permitted styles rather than an invention. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .abtn { background: #fff; color: #000; }
}
:root[data-theme="dark"] .abtn { background: #fff; color: #000; }
.signin-hero { text-align: center; padding: var(--sp-2) var(--sp-2) var(--sp-1); }
.signin-hero img { width: 64px; height: 64px; border-radius: var(--r-lg); margin: 0 auto var(--sp-3); box-shadow: var(--shadow-2); }
.signin-hero h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-lg); }
.signin-hero p { margin: 0 0 var(--sp-5); font-size: var(--fs-xs); color: var(--text-muted); line-height: var(--lh-body); }
.acct {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-2); min-height: var(--tap);
  border-radius: var(--r-md); text-align: left; width: 100%;
  transition: background var(--dur) var(--ease);
}
.acct:active { background: var(--surface-2); }
.acct + .acct { border-top: 1px solid var(--border); }
.acct .av {
  width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center;
  font-family: var(--ff-display); font-size: var(--fs-md); font-weight: 600; color: #fff; flex: 0 0 auto;
}
.acct .tx b { font-size: var(--fs-sm); font-weight: 600; display: block; }
.acct .tx span { font-size: var(--fs-2xs); color: var(--text-muted); }
.demo-tag {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-micro); font-weight: 600; color: var(--warn);
  background: var(--warn-soft); padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
}
.divider-or { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-4) 0; color: var(--text-muted); font-size: var(--fs-2xs); }
.divider-or::before, .divider-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* WhatsApp escape hatch on the sign-in sheet. An account is required to book,
   so a customer without a Google account has no way in at all — this is it.
   Deliberately NOT WhatsApp brand green: #25D366 carries white text at 2.1:1,
   and it would be the loudest thing on a sheet whose primary action is Google.
   The soft brand pair is documented at ≥4.5:1 in both themes. */
.wa-cta {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  width: 100%; min-height: var(--tap); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md); background: var(--brand-soft); color: var(--on-brand-soft);
  font-size: var(--fs-sm); font-weight: 600;
  transition: background var(--dur) var(--ease);
}
.wa-cta:active { background: var(--brand-soft-2); }
.wa-cta svg { width: 20px; height: 20px; flex: 0 0 auto; }
.wa-cta-note {
  margin: var(--sp-2) 0 0; padding: 0 var(--sp-2); text-align: center;
  font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-body);
}

/* ================================= payment =============================== */
.demo-badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .06em;
  color: var(--warn); background: var(--warn-soft);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-pill);
}
.amount-card {
  background: var(--surface-invert); color: var(--text-invert);
  border-radius: var(--r-xl); padding: var(--sp-5); margin-top: var(--sp-1);
  display: flex; align-items: center; justify-content: space-between;
  position: relative; overflow: hidden; border: 1px solid var(--border-invert);
}
.amount-card .leaf {
  position: absolute; right: -18px; bottom: -28px;
  color: #fff; opacity: .1; pointer-events: none;
}
.amount-card .leaf .ico { width: 124px; height: 124px; }
.amount-card .lbl { font-size: var(--fs-2xs); color: var(--text-invert-2); }
.amount-card .amt { font-family: var(--ff-display); font-size: var(--fs-3xl); font-weight: 600; letter-spacing: -.025em; color: #fff; }
.amount-card .svc { font-size: var(--fs-2xs); color: var(--text-invert-2); margin-top: 2px; }

.pay-methods { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.pay-method {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); min-height: var(--tap); border-radius: var(--r-md);
  background: var(--surface); border: 1.5px solid var(--border); text-align: left;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease); position: relative;
}
.pay-method.on { border-color: var(--brand); background: var(--brand-soft); }
.pay-logo { width: 38px; height: 38px; border-radius: var(--r-sm); display: grid; place-items: center; flex: 0 0 auto; color: #fff; font-weight: 700; }
.pay-method .nm { font-family: var(--ff-display); font-size: var(--fs-xs); font-weight: 600; line-height: var(--lh-tight); }
.pay-method .sb { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 2px; line-height: var(--lh-tight); }
.pay-logo.card { background: linear-gradient(135deg,#3b3f6b,#22243d); }
.pay-logo.fps { background: linear-gradient(135deg,#00b3a4,#0a8f6f); font-size: var(--fs-micro); }
.pay-logo.wechat { background: #1aad19; }
.pay-logo.alipay { background: #1677ff; }

.pay-body { margin-top: var(--sp-4); animation: fade var(--dur) var(--ease); }

/* Where Stripe mounts its Payment Element. Deliberately almost unstyled: the
   fields inside are an iframe Stripe owns and we cannot reach, so anything more
   than a minimum height here would fight a box whose contents change size as
   the customer picks a method. The min-height stops the action bar jumping
   while it loads. */
.pay-el { min-height: 220px; }
.pay-el-load, .pay-el-err {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 220px; padding: var(--sp-4);
  font-size: var(--fs-sm); color: var(--text-muted); text-align: center; line-height: var(--lh-snug);
}
.pay-el-err { color: var(--danger, #b4232a); }

/* Account deletion and the legal links, in the account sheet.
   Deliberately quieter than the buttons above them: deleting an account is the
   rarer action and the irreversible one, so it should not compete for the same
   attention as Sign out. Quieter is not hidden — Apple requires this to be
   reachable in the app, and it is two taps from the avatar. */
.link-danger {
  display: block; width: 100%; margin-top: var(--sp-3); padding: var(--sp-2);
  background: none; font-size: var(--fs-sm); font-weight: 600;
  color: var(--danger, #b4232a); text-align: center;
}
.link-danger:active { opacity: .6; }
.legal-links {
  margin-top: var(--sp-3); text-align: center;
  font-size: var(--fs-xs); color: var(--text-muted);
  display: flex; gap: var(--sp-2); justify-content: center; align-items: center;
}
.legal-links a { color: var(--text-muted); text-decoration: underline; }

.card-visual {
  border-radius: var(--r-lg); padding: var(--sp-4) var(--sp-5); color: #fff;
  margin-bottom: var(--sp-4); position: relative; overflow: hidden;
  background: linear-gradient(135deg,#20223c,#3a3e69); box-shadow: var(--shadow-2); min-height: 92px;
}
.card-visual .chip {
  width: 34px; height: 25px; min-height: 0; padding: 0; border-radius: var(--r-xs);
  background: linear-gradient(135deg,#e6c66e,#c99a3a); box-shadow: none;
}
.card-visual .no { font-size: var(--fs-base); letter-spacing: 2px; margin: var(--sp-3) 0 var(--sp-2); font-variant-numeric: tabular-nums; }
.card-visual .row { display: flex; justify-content: space-between; font-size: var(--fs-micro); opacity: .85; text-transform: uppercase; letter-spacing: .05em; }
.card-visual .brand { position: absolute; top: var(--sp-3); right: var(--sp-4); font-size: var(--fs-xs); font-weight: 700; font-style: italic; letter-spacing: .5px; }
.input-icon { position: relative; }
.input-icon .brandmini { position: absolute; right: var(--sp-3); top: 50%; transform: translateY(-50%); font-size: var(--fs-2xs); font-weight: 700; color: var(--text-muted); }
.sqft-input { position: relative; }
.sqft-input input { padding-right: 72px; }
.sqft-input .unit { position: absolute; right: var(--sp-4); top: 50%; transform: translateY(-50%); font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted); }

/* QR pay */
.qr-wrap { background: var(--surface); border-radius: var(--r-xl); box-shadow: var(--shadow-1), var(--ring-inset); overflow: hidden; text-align: center; }
.qr-head { padding: var(--sp-4); color: #fff; font-weight: 600; font-size: var(--fs-md); display: flex; align-items: center; justify-content: center; gap: var(--sp-2); }
.qr-head.wechat { background: #1aad19; } .qr-head.alipay { background: #1677ff; } .qr-head.fps { background: linear-gradient(135deg,#00b3a4,#0a8f6f); }
.qr-body { padding: var(--sp-5); }
.qr-canvas { width: 208px; height: 208px; margin: 0 auto; border-radius: var(--r-md); display: block; box-shadow: var(--shadow-1); background: #fff; }
.qr-body .tip { font-size: var(--fs-2xs); color: var(--text-muted); margin: var(--sp-4) 0 0; line-height: var(--lh-body); }
.qr-meta { display: flex; justify-content: center; gap: var(--sp-2); margin-top: var(--sp-3); flex-wrap: wrap; }
.qr-meta .kv { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-pill); padding: var(--sp-1) var(--sp-3); font-size: var(--fs-2xs); font-weight: 600; }
.qr-meta .kv small { color: var(--text-muted); font-weight: 400; }

.secured { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); color: var(--text-muted); font-size: var(--fs-2xs); margin: var(--sp-4) 0 2px; }
.paid-pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-micro); font-weight: 700;
  color: var(--on-brand-soft); background: var(--brand-soft);
  padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
}
/* Sits where .due-pill would, but informational rather than a call to pay. */
.await-pill {
  display: flex; align-items: flex-start; gap: var(--sp-2); width: 100%;
  margin-top: var(--sp-3); padding: var(--sp-3);
  border-radius: var(--r-md); background: var(--brand-soft); color: var(--on-brand-soft);
  font-size: var(--fs-2xs); font-weight: 600; line-height: var(--lh-snug); text-align: left;
}
.await-pill svg { flex: 0 0 auto; width: var(--ic-sm); height: var(--ic-sm); margin-top: 1px; }
.est-pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-micro); font-weight: 700; color: var(--warn);
  background: var(--warn-soft); padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
}
.due-pill {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  margin-top: var(--sp-3); padding: var(--sp-3);
  border-radius: var(--r-md); background: var(--warn-soft); color: var(--warn);
  font-size: var(--fs-2xs); font-weight: 600; text-align: left;
}
.due-pill svg { flex: 0 0 auto; width: var(--ic-sm); height: var(--ic-sm); }
.due-pill .go { margin-left: auto; flex: 0 0 auto; font-weight: 700; text-decoration: underline; }
.sync-note {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%; margin-top: var(--sp-3);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--text-muted); text-align: left;
}
.sync-note.failed { color: var(--warn); }
.sync-note svg { width: var(--ic-sm); height: var(--ic-sm); flex: 0 0 auto; }
.sync-note .spin.sm {
  width: 12px; height: 12px; border-width: 2px;
  border-color: color-mix(in srgb, var(--text-muted) 30%, transparent); border-top-color: var(--text-muted);
}

/* profile signed-in avatar image */
.av-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

/* ---------------------------- photo upload ------------------------------ */
.photo-why { margin: 0 0 var(--sp-3); font-size: var(--fs-2xs); color: var(--text-muted); line-height: var(--lh-body); }

/* Fixed four-column grid: the tile count is capped at four, so the row stays
   put as pictures are added instead of reflowing under the customer. */
.shot-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-2); }

.shot { position: relative; aspect-ratio: 1; border-radius: var(--r-sm); overflow: hidden; background: var(--surface-inset); }
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Sits on top of a photograph, so it carries its own dark disc rather than
   relying on a token that has no idea what colour the image behind it is. */
.shot-x {
  position: absolute; top: 2px; right: 2px;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .55); color: #fff;
  transition: background var(--dur) var(--ease);
}
.shot-x:active { background: rgba(0, 0, 0, .75); }

.shot-add {
  aspect-ratio: 1; border-radius: var(--r-sm);
  border: 1.5px dashed var(--border-strong); background: var(--surface);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  color: var(--text-muted); font-size: var(--fs-micro); font-weight: 600; text-align: center;
  cursor: pointer; transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.shot-add:active { border-color: var(--brand); background: var(--brand-soft); }
.shot-add:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }

/* -------------------------- welcome offer modal -------------------------- */
/* Centred rather than a bottom sheet: this interrupts, and it should look like
   it means to. Clipped inside the device screen like every other overlay. */
.promo-wrap {
  position: absolute; inset: 0; z-index: 95;
  display: grid; place-items: center; padding: var(--sp-5);
  background: var(--scrim);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease);
}
.promo-wrap.show { opacity: 1; pointer-events: auto; }

.promo-card {
  width: 100%; max-width: 320px;
  background: var(--surface); border-radius: var(--r-xl);
  box-shadow: var(--shadow-3);
  /* The card is ~540px in English. That fits a modern phone, but on a 568px
     screen it would run past the edge and put the dismiss button somewhere
     unreachable — an offer nobody can close. */
  max-height: 100%; overflow-y: auto; overscroll-behavior: contain;
  transform: translateY(12px) scale(.97);
  transition: transform var(--dur-slow) var(--ease-out);
}
.promo-wrap.show .promo-card { transform: none; }

/* overflow-y:auto on the card stops it clipping the artwork to the rounded
   corner, so the art carries the top radius itself. */
/* The brand gradient stays underneath the photograph. The promo opens 900ms
   after boot, which on a cold first visit can be before a 30KB image has
   landed — and if it never lands the card still reads as designed rather than
   framing a broken-image glyph. */
.promo-art { position: relative; aspect-ratio: 320 / 150; overflow: hidden;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: linear-gradient(135deg, #047857, #064e3b); }
.promo-photo { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Sits on artwork, so it carries its own disc instead of trusting a token to
   contrast with whatever is behind it. */
.promo-x {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  width: var(--tap); height: var(--tap); border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .38); color: #fff;
  transition: background var(--dur) var(--ease);
}
.promo-x:active { background: rgba(0, 0, 0, .58); }

.promo-body { padding: var(--sp-5); text-align: center; }
.promo-body h3 { margin: var(--sp-3) 0 var(--sp-2); font-size: var(--fs-xl); }
.promo-body p { margin: 0 0 var(--sp-4); font-size: var(--fs-xs); color: var(--text-muted); line-height: var(--lh-body); }

.promo-code {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  margin-bottom: var(--sp-4); padding: var(--sp-3);
  border-radius: var(--r-md); background: var(--brand-soft);
  border: 1px dashed var(--brand);
}
.promo-code span { font-size: var(--fs-2xs); color: var(--on-brand-soft); }
.promo-code b {
  font-family: var(--ff-display); font-size: var(--fs-lg); font-weight: 600;
  letter-spacing: .08em; color: var(--on-brand-soft);
}

.promo-later {
  width: 100%; min-height: var(--tap); margin-top: var(--sp-2);
  font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted);
}
.promo-later:active { color: var(--text-2); }

/* ============================ reduced motion ============================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    /* Delays have to go too, and this line was missing. Collapsing only the
       DURATION leaves a delayed animation with `backwards` or `both` fill
       holding its from-state for the whole delay — so the boot reveal below,
       which starts at 600ms and begins at opacity 0, would have shown a
       reduced-motion visitor a blank screen for six tenths of a second and
       called it an accessibility feature. Negative rather than zero: the
       animation is then already past its own end the moment it is applied. */
    animation-delay: -1ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  .app-main { scroll-behavior: auto; }
}
