/* ============================================================
   CWC Delivery — customer app (v3)
   Warm editorial canvas · poster-style brand identities ·
   promos, badges & featured rows.
   ============================================================ */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
  --bg: #f6f2ec;
  --card: #ffffff;
  --ink: #1e1712;
  --muted: #8b7d70;
  --line: #ebe3d9;
  --accent: #1e1712;
  --accent-ink: #fff8ef;
  --soft: #efe8df;
  --r-lg: 24px;
  --r-md: 18px;
  --shadow-sm: 0 1px 2px rgba(30, 23, 18, .05), 0 4px 14px rgba(30, 23, 18, .05);
  --shadow-md: 0 2px 6px rgba(30, 23, 18, .06), 0 12px 32px rgba(30, 23, 18, .10);
  --shadow-lg: 0 8px 20px rgba(30, 23, 18, .14), 0 24px 60px rgba(30, 23, 18, .18);
  --font-display: 'Fraunces', Georgia, serif;
  --font-body: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --lux: #b08b43; /* gold detail color */
}
body[data-brand="coldstone"] { --accent: #9e1b32; --soft: #f9ecef; --accent-ink: #fff; }
body[data-brand="waccao"]    { --accent: #6f4518; --soft: #fdf3d7; --accent-ink: #fff; }
body[data-brand="censa"]     { --accent: #cf1f1f; --soft: #fbeded; --accent-ink: #fff; }

/* ============================================================
   THE APP SHELL
   The document does not scroll. It cannot: it is exactly the
   size of the glass, with overflow hidden. Each screen is its
   own scroller inside it.

   This is the difference between a site and an app. When the
   page itself scrolls, the browser owns the gesture — it
   rubber-bands past the ends, peels the header off the status
   bar, hides and shows its own toolbar, and fires
   pull-to-refresh. `overscroll-behavior` asks it not to, and
   is ignored often enough to be useless as the only defence.
   Taking the scroll off the document removes the question:
   there is nothing there to overscroll.
   ============================================================ */
html {
  -webkit-text-size-adjust: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
body {
  font-family: var(--font-body);
  background: radial-gradient(1200px 500px at 50% -200px, #fdfaf5 0%, var(--bg) 60%);
  color: var(--ink);
  height: 100%;
  overflow: hidden;
  line-height: 1.45;
  overscroll-behavior: none;
}
/* While a sheet or popup is open the screen underneath stays put. Nothing has
   to be pinned or restored any more — the scroll lives on the screen itself,
   so taking its overflow away holds the position on its own. */
body.locked .view.active { overflow: hidden; }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
/* ── hidden means hidden ──────────────────────────────────────────────────
   The browser hides [hidden] elements with `display: none` from its own
   stylesheet, which ANY author rule setting `display` on the same element
   outranks. So `.thing { display: flex }` silently defeats `thing.hidden = true`
   — the JavaScript looks right, the attribute is set, and the element is on
   screen anyway.

   That is not hypothetical: it is what put "We can't reach StarzClub" on every
   screen of the live app regardless of the connection, and it is the same shape
   as the ghost-screen bug from earlier in this project. Ten elements in this
   file were one edit away from it.

   One rule, once, so it cannot happen again. `!important` is the point here:
   it has to outrank whatever a component sets. */
[hidden] { display: none !important; }


input, textarea, select { font: inherit; color: inherit; }
img { display: block; }

.view { display: none; max-width: 560px; margin: 0 auto; padding: 18px 18px 34px; }
.view.pad-cart { padding-bottom: 130px; } /* space for the floating cart bar only when it's shown */
/* The active screen fills the glass and is the only thing that scrolls.
   `inset: 0` with a max-width and auto margins still centres it on a desktop
   window.

   THE SCROLL FEEL. Two things here were making every list read as a web page
   rather than a native one, and both were defensive measures that had stopped
   defending anything:

     · `overscroll-behavior: contain` was there to stop a flick at the end of a
       list reaching the browser. But <html> and <body> are `overflow: hidden`
       with `overscroll-behavior: none` — there is nothing behind this to chain
       to. All `contain` still did was suppress the rubber band at the top and
       bottom of the list, and that bounce is the single most recognisable thing
       about scrolling on iOS. Without it a list stops dead at its ends, which
       is exactly how a web page in a browser feels. `contain` stays on the X
       axis, where a horizontal rail genuinely can chain.

     · `animation: rise` ran on the scroll container itself every time a screen
       became active. An animating transform makes the element its own layer and
       restarts compositing mid-gesture — you feel it as the first flick after
       switching tabs being heavier than the rest. The screen transitions are
       handled properly by .is-push / .is-pop; this was a second, older one
       underneath them. */
.view.active {
  display: block;
  position: fixed; inset: 0; z-index: 1;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior-x: contain;
  overscroll-behavior-y: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.view.active::-webkit-scrollbar { width: 0; height: 0; }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ============ HOME ============ */
/* The bar stays put while you scroll, so points and notifications are always
   one thumb away. Negative margins + matching padding let the frosted panel run
   edge to edge while its contents stay on the page's own gutter. */
.home-top {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: -18px -18px 0; padding: calc(env(safe-area-inset-top, 0px) + 12px) 18px 12px;
  background: var(--bg);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease, box-shadow .2s ease;
}
/* The hairline only appears once you've actually scrolled under it */
.home-top.stuck { border-bottom-color: var(--line); box-shadow: 0 6px 20px -16px rgba(45,30,15,.5); }
.home-top-right { min-width: 0; display: flex; align-items: center; gap: 8px; }
/* Home carousel — greeting slide first, owner ad slides after. One fixed height
   for every slide; a warm gradient carries the greeting until an image is added. */
.home-carousel {
  position: relative; margin: 20px auto 18px; border-radius: 20px; overflow: hidden;
  width: 100%; max-width: 640px; aspect-ratio: 3 / 2;
  box-shadow: 0 10px 26px -18px rgba(45,30,15,.55);
}
.hc-track { display: flex; height: 100%; transition: transform .35s ease; }
.hc-slide { position: relative; min-width: 100%; height: 100%; overflow: hidden; }
/* greeting slide */
/* No welcome photo has been uploaded, so this card is nearly always the
   gradient alone — which left a big empty field above two small lines at the
   bottom. Centring the text, giving it a kicker and a larger greeting, and
   floating the three brands as a soft motif fills the card so it reads as a
   designed hero rather than an empty banner. */
.hc-greet {
  display: flex; align-items: center;
  background: linear-gradient(140deg, #e0a833 0%, #c56a24 42%, #a53a1c 74%, #8a2c17 100%);
}
/* a soft top-light so the flat gradient gains some depth */
.hc-greet::before {
  content: ''; position: absolute; top: -35%; right: -12%; width: 78%; height: 150%;
  background: radial-gradient(circle at center, rgba(255,255,255,.18), rgba(255,255,255,0) 62%);
  z-index: 0; pointer-events: none;
}
.hw-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; }
.hw-scrim { position: absolute; inset: 0; z-index: 1; background: linear-gradient(105deg, rgba(0,0,0,.42) 0%, rgba(0,0,0,.16) 46%, rgba(0,0,0,0) 72%); }
/* the three brands as one oversized, faint motif in the corner */
.hw-deco {
  position: absolute; right: 4px; bottom: -14px; z-index: 1;
  font-size: 84px; line-height: 1; letter-spacing: -22px; opacity: .22;
  transform: rotate(-9deg); transform-origin: bottom right;
  pointer-events: none; user-select: none; filter: drop-shadow(0 6px 16px rgba(0,0,0,.35));
}
.hw-body { position: relative; z-index: 2; padding: 20px 22px; color: #fff; }
.hw-kicker {
  display: block; font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(255,255,255,.82); margin-bottom: 7px; text-shadow: 0 1px 8px rgba(0,0,0,.4);
}
.hw-title { font-family: var(--font-display); font-size: 30px; font-weight: 800; letter-spacing: -.02em; line-height: 1.08; text-shadow: 0 2px 14px rgba(0,0,0,.42); }
.hw-title svg { width: 24px; height: 24px; vertical-align: -3px; }
.hw-sub { margin-top: 8px; font-size: 14px; font-weight: 600; opacity: .96; line-height: 1.4; max-width: 26ch; text-shadow: 0 1px 8px rgba(0,0,0,.45); }
/* ad slide — the image sits, whole, on a blurred fill of itself, so a tall
   portrait ad (made for the pop-up) still looks right in this wide banner. */
.hc-ad { background: #1a1512; }
.hc-ad-bg { position: absolute; inset: 0; background-size: cover; background-position: center; filter: blur(18px) brightness(.72); transform: scale(1.18); }
.hc-ad img { position: relative; width: 100%; height: 100%; object-fit: contain; display: block; }
/* dots */
.hc-dots { position: absolute; left: 0; right: 0; bottom: 9px; display: flex; justify-content: center; gap: 6px; }
.hc-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.5); box-shadow: 0 0 3px rgba(0,0,0,.35); transition: background .2s; }
.hc-dot.on { background: #fff; }
.wordmark { font-family: var(--font-display); font-weight: 700; font-size: 17px; display: flex; align-items: center; gap: 8px; flex-shrink: 0; white-space: nowrap; }
.wordmark .wm-dot { width: 30px; height: 30px; border-radius: 10px; background: var(--ink); color: #fff; display: grid; place-items: center; font-size: 15px; }

/* points + notifications, living in the header */
.pts-chip {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  background: var(--ink); color: #f4e7cd; border-radius: 999px;
  padding: 5px 12px 5px 5px; font-weight: 800; font-size: 13px;
  box-shadow: 0 2px 10px -4px rgba(45,30,15,.6);
}
.pts-chip .pts-coin { width: 24px; height: 24px; flex: none; }
.pts-chip .pts-num { font-variant-numeric: tabular-nums; letter-spacing: .2px; }
.pts-chip.guest { background: #fff; color: var(--ink); border: 1px solid var(--line); }
.pts-chip:active { transform: scale(.96); }
.bell-btn {
  position: relative; flex-shrink: 0;
  width: 36px; height: 36px; border-radius: 12px; display: grid; place-items: center;
  background: #fff; border: 1px solid var(--line); color: var(--ink);
  box-shadow: var(--shadow-sm);
}
.bell-btn .ic { width: 17px; height: 17px; }
.bell-btn:active { transform: scale(.94); }
.bell-dot {
  position: absolute; top: 5px; right: 5px;
  min-width: 8px; height: 8px; border-radius: 999px;
  background: #e0483b; box-shadow: 0 0 0 2px var(--bg);
}
.bell-btn.has .ic { animation: bell-nudge 2.4s ease-in-out 1s 2; transform-origin: 50% 15%; }
@keyframes bell-nudge {
  0%, 88%, 100% { transform: rotate(0); }
  90% { transform: rotate(11deg); } 94% { transform: rotate(-9deg); } 97% { transform: rotate(4deg); }
}
@media (prefers-reduced-motion: reduce) { .bell-btn.has .ic { animation: none; } }

/* the design A/B switcher, shown only while comparing */
.design-switch {
  display: flex; align-items: center; gap: 6px; justify-content: center;
  margin: 22px auto 0; padding: 6px; width: fit-content;
  background: var(--ink); border-radius: 999px; box-shadow: var(--shadow-lg);
}
.design-switch .ds-label { color: #a28f74; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; padding: 0 8px 0 10px; }
.design-switch button { color: #e8dcc6; font-size: 12.5px; font-weight: 700; padding: 7px 14px; border-radius: 999px; }
.design-switch button.on { background: #f6ead4; color: var(--ink); }
.hours-pill {
  display: inline-flex; align-items: center; gap: 7px; min-width: 0; flex-shrink: 1;
  background: #fff; border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 13px; font-size: 12px; font-weight: 600; color: var(--muted);
  box-shadow: var(--shadow-sm);
}
.hours-pill > span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hours-pill .dot { width: 7px; height: 7px; border-radius: 50%; background: #1fa353; box-shadow: 0 0 0 3px rgba(31,163,83,.15); }
.hours-pill.closed .dot { background: #d92c2c; box-shadow: 0 0 0 3px rgba(217,44,44,.15); }

.home-hero { padding: 36px 2px 24px; position: relative; }
.home-hero::before {
  content: 'ICE CREAM · WAFFLES · COFFEE';
  display: block; font-size: 10.5px; font-weight: 800; letter-spacing: 3px;
  color: var(--lux); margin-bottom: 12px;
}
.home-hero h1 {
  font-family: var(--font-display); font-weight: 800; font-size: 41px;
  line-height: 1.04; letter-spacing: -0.8px;
}
.home-hero h1 em { font-style: italic; font-weight: 600; color: var(--lux); }
.home-hero p { color: var(--muted); font-size: 15px; margin-top: 12px; max-width: 42ch; }
.hero-meta { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.meta-chip {
  font-size: 12.5px; font-weight: 700; color: #6e6156;
  background: #fff; border: 1px solid var(--line); border-radius: 999px; padding: 7px 13px;
  box-shadow: var(--shadow-sm);
}

/* --- promo banner (home + menu) --- */
.promo-banner {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: linear-gradient(100deg, #241c17, #4a2c17 130%);
  color: #ffe9c4; border-radius: var(--r-md); padding: 14px 16px; margin-bottom: 16px;
  box-shadow: var(--shadow-md); border: 1px solid #3a2a1c;
}
.promo-banner .pb-ico { font-size: 24px; }
.promo-banner .pb-title { font-weight: 800; font-size: 14px; }
.promo-banner .pb-msg { font-size: 12.5px; opacity: .85; margin-top: 1px; }
.promo-banner .pb-code {
  margin-left: auto; flex-shrink: 0; font-weight: 800; font-size: 12.5px; letter-spacing: 1px;
  border: 1.5px dashed #c89b57; color: #ffd98a; border-radius: 9px; padding: 6px 10px;
}

/* --- poster brand cards --- */
.brand-grid { display: grid; gap: 16px; }
.poster {
  position: relative; display: flex; flex-direction: column; gap: 18px; width: 100%; text-align: left;
  border-radius: 26px; padding: 21px; overflow: hidden; isolation: isolate;
  box-shadow: var(--shadow-md), inset 0 0 0 1px rgba(255, 255, 255, .14);
  transition: transform .16s ease, box-shadow .16s ease;
}
.poster::before {
  content: ''; position: absolute; inset: 7px; border-radius: 20px;
  border: 1px solid rgba(255, 255, 255, .18); pointer-events: none; z-index: 1;
}
.poster.waccao::before { border-color: rgba(74, 44, 16, .18); }
@media (hover: hover) { .poster:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); } }
.poster:active { transform: scale(.985); }
.poster-mark {
  position: absolute; right: -18px; bottom: -26px; font-size: 130px; opacity: .16; z-index: 0;
  transform: rotate(-12deg); pointer-events: none;
}
.poster-head { display: flex; align-items: center; gap: 15px; z-index: 1; }
.poster h2 { font-family: var(--font-display); font-weight: 700; font-size: 21px; letter-spacing: -.2px; }
.poster-tag { font-size: 12.5px; opacity: .8; display: block; margin-top: 3px; font-weight: 600; }
.poster-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 10px; z-index: 1; }
.poster-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.poster-chips span {
  font-size: 11px; font-weight: 700; padding: 5px 10px; border-radius: 999px;
  background: rgba(255,255,255,.16); backdrop-filter: blur(2px);
}
.poster-cta {
  flex-shrink: 0; font-weight: 800; font-size: 13.5px; padding: 10px 16px; border-radius: 999px;
  box-shadow: 0 3px 10px rgba(0,0,0,.18);
}
.brand-logo {
  width: 64px; height: 64px; border-radius: 17px; background: #fff;
  display: grid; place-items: center; overflow: hidden; flex-shrink: 0;
  box-shadow: 0 4px 14px rgba(0,0,0,.22);
}
.brand-logo img { width: 100%; height: 100%; object-fit: contain; padding: 5px; }

.poster.coldstone { background: linear-gradient(125deg, #6f1120 0%, #9e1b32 62%, #b8243e 100%); color: #fff5ee; }
.poster.coldstone .poster-cta { background: #ffd9a0; color: #6f1120; }
.poster.waccao { background: linear-gradient(125deg, #ffcf45 0%, #fdc12a 60%, #f0ad00 100%); color: #4a2c10; }
.poster.waccao .poster-chips span { background: rgba(255,255,255,.42); }
.poster.waccao .poster-cta { background: #4a2c10; color: #ffd977; }
.poster.waccao .brand-logo { background: #ffc72c; }
.poster.waccao .brand-logo img { padding: 0; object-fit: cover; }
.poster.censa { background: linear-gradient(125deg, #16100d 0%, #241a15 70%, #33221a 100%); color: #fdf6ee; border: 1px solid #33241b; }
.poster.censa .poster-cta { background: #e02020; color: #fff; }
.poster.censa .poster-mark { opacity: .22; }

.home-top-right { display: flex; align-items: center; gap: 8px; }
.lang-toggle {
  font-size: 12px; font-weight: 800; letter-spacing: .5px;
  background: var(--ink); color: #fff; border-radius: 999px; padding: 8px 13px;
  box-shadow: var(--shadow-sm);
}
.home-links { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 18px; }
.home-track {
  display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%;
  padding: 15px 10px; border-radius: var(--r-md);
  border: 1.5px dashed #d8ccbd; color: #6e6156; font-weight: 700; font-size: 13.5px;
}
.home-foot { text-align: center; color: #b5a897; font-size: 12px; margin-top: 26px; }

/* ============ TOPBAR ============ */
.topbar {
  display: flex; align-items: center; gap: 12px; padding: 12px 0 16px;
  position: sticky; top: 0; z-index: 6;
  /* Solid, not a fade to transparent — the gradient was the "blurry" edge under
     the title. A clean header on the page ground reads sharper and more premium. */
  background: var(--bg);
}
.back {
  width: 42px; height: 42px; border-radius: 13px; background: #fff; border: 1px solid var(--line);
  box-shadow: var(--shadow-sm); font-size: 20px; display: grid; place-items: center; flex-shrink: 0;
}
.topbar h1 { font-family: var(--font-display); font-size: 26px; font-weight: 800; letter-spacing: -.01em; }
.topbar .sub { font-size: 12.5px; color: var(--muted); font-weight: 600; }

/* ============ MENU ============ */
.brand-band {
  position: relative; display: flex; align-items: center; gap: 16px; overflow: hidden; isolation: isolate;
  border-radius: 26px; padding: 20px; margin-bottom: 14px; box-shadow: var(--shadow-md);
}
.brand-band .poster-mark { font-size: 110px; right: -14px; bottom: -30px; }
.brand-band h2 { font-family: var(--font-display); font-size: 22px; font-weight: 700; }
.brand-band .bb-tag { font-size: 12.5px; opacity: .82; font-weight: 600; margin-top: 2px; }
.brand-band .bb-meta { display: flex; gap: 6px; margin-top: 9px; flex-wrap: wrap; }
/* the way back, on the banner instead of in a title bar that repeated the name */
.bb-back {
  /* Same floor as the product page's button: the inset alone cannot be trusted,
     and 12px from the top of the screen puts this straight through the clock. */
  position: absolute; z-index: 3; top: max(calc(env(safe-area-inset-top, 0px) + 14px), 58px); left: 12px;
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(255,255,255,.9); color: #1a1710; font-size: 22px; line-height: 1; padding-bottom: 3px;
  box-shadow: 0 3px 10px -4px rgba(0,0,0,.45);
}

.brand-band .bb-meta span {
  font-size: 11.5px; font-weight: 700; padding: 5px 10px; border-radius: 999px;
  background: rgba(255,255,255,.18);
}
/* the branch chip: a solid, obviously-tappable bar inside the band, because
   which shop you order from changes the menu, the ETA and the phone number */
.bb-branch {
  position: relative; z-index: 2; display: flex; align-items: center; gap: 9px;
  margin-top: 12px; padding: 11px 12px 11px 13px; border-radius: 14px;
  background: rgba(255,255,255,.92); color: #1a1710; cursor: pointer;
  box-shadow: 0 4px 14px -8px rgba(0,0,0,.55);
}
.bb-branch .ic { width: 17px; height: 17px; flex: none; color: #8a857e; }
.bb-br-txt { flex: 1; min-width: 0; display: block; line-height: 1.25; }
.bb-br-txt small, .bb-branch.single small {
  display: block; font-size: 10px; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; color: #8a857e;
}
.bb-br-txt b, .bb-branch.single b {
  display: block; font-size: 14.5px; font-weight: 800; overflow-wrap: anywhere;
}
.bb-br-go {
  flex: none; display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; font-weight: 800; color: #1a1710;
  background: #eceade; border-radius: 999px; padding: 6px 10px 6px 12px;
}
.bb-br-chev { font-size: 15px; line-height: 1; margin-top: -1px; }
/* the real control sits invisibly over the whole bar, so a tap opens the phone's own picker */
.bb-branch select {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; appearance: none; border: 0; cursor: pointer;
}
/* Under the picker: either what we worked out for them, or the offer to. Both
   sit on the brand banner, so they are light-on-dark. */
.bb-near-note, .bb-near {
  position: relative; z-index: 2; display: inline-flex; align-items: center; gap: 6px;
  margin-top: 7px; padding: 6px 12px 6px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 800; line-height: 1.2;
  background: rgba(255,255,255,.16); color: #fff;
  backdrop-filter: blur(2px);
}
.bb-near-note .ic, .bb-near .ic { width: 13px; height: 13px; flex: none; opacity: .85; }
.bb-near { border: 1px solid rgba(255,255,255,.3); cursor: pointer; }
@media (hover: hover) { .bb-near:hover { background: rgba(255,255,255,.26); } }
.bb-near:disabled { opacity: .7; cursor: default; }
.bb-branch.single { cursor: default; }
.bb-br-addr { flex: none; font-size: 11.5px; font-weight: 700; color: #8a857e; text-align: right; }
.bb-branch.single span { flex: 1; min-width: 0; }

.brand-band.coldstone { background: linear-gradient(125deg, #6f1120, #9e1b32 62%, #b8243e); color: #fff5ee; }
.brand-band.waccao { background: linear-gradient(125deg, #ffcf45, #fdc12a 60%, #f0ad00); color: #4a2c10; }
.brand-band.waccao .bb-meta span { background: rgba(255,255,255,.45); }
.brand-band.waccao .brand-logo { background: #ffc72c; }
.brand-band.waccao .brand-logo img { padding: 0; object-fit: cover; }
.brand-band.censa { background: linear-gradient(125deg, #16100d, #241a15 70%, #33221a); color: #fdf6ee; }

/* --- featured row --- */
.featured-label {
  font-family: var(--font-display); font-size: 16px; font-weight: 700; margin: 6px 2px 10px;
  display: flex; align-items: center; gap: 8px;
}
.featured-row { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 14px; scrollbar-width: none; }
.featured-row::-webkit-scrollbar { display: none; }
.feat-card {
  flex: 0 0 156px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 13px; box-shadow: var(--shadow-sm); text-align: left; position: relative;
  transition: transform .15s ease, box-shadow .15s ease;
}
.feat-card .fc-emoji { font-size: 34px; height: 56px; display: grid; place-items: center; background: linear-gradient(140deg, #fff, var(--soft) 80%); border: 1px solid color-mix(in srgb, var(--accent) 8%, var(--line)); border-radius: 14px; margin-bottom: 9px; }
.feat-card .fc-name { font-size: 13px; font-weight: 700; line-height: 1.3; min-height: 34px; overflow-wrap: anywhere; }
.feat-card .fc-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; }
.feat-card .fc-price { font-family: var(--font-display); font-size: 13px; font-weight: 700; color: var(--accent); }
.feat-card .fc-add { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: var(--accent-ink); font-size: 15px; font-weight: 700; display: grid; place-items: center; box-shadow: 0 3px 8px color-mix(in srgb, var(--accent) 30%, transparent); }
.feat-badge {
  position: absolute; top: 8px; left: 8px; right: auto; font-size: 9.5px; font-weight: 800;
  background: var(--ink); color: #ffd98a; border-radius: 999px; padding: 3px 8px; z-index: 1;
  letter-spacing: .3px;
}

.cat-tabs {
  display: flex; gap: 8px; overflow-x: auto; padding: 4px 0 16px; scrollbar-width: none;
  /* Directly under the pinned brand bar, measured rather than guessed: the bar
     is 54px plus whatever the notch takes, and a fixed 62px left a band on a
     notched phone where cards scrolled through open space above the pills.
     That gap was most of why this screen felt unfinished. */
  position: sticky; top: calc(env(safe-area-inset-top, 0px) + 54px); z-index: 5;
  /* Opaque where the cards pass behind it, fading out at its own bottom edge —
     a translucent strip let product photos ghost through the pills. */
  background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 78%, transparent);
}
.cat-tabs::-webkit-scrollbar { display: none; }
.cat-tab {
  flex-shrink: 0; padding: 9px 16px; border-radius: 999px; font-size: 13px; font-weight: 700;
  background: #fff; border: 1px solid var(--line); color: var(--muted); box-shadow: var(--shadow-sm);
  transition: all .15s ease;
}
.cat-tab.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.cat-heading {
  font-family: var(--font-display); font-size: 17px; font-weight: 700; margin: 22px 2px 12px;
  display: flex; align-items: center; gap: 10px;
}
.cat-heading::after { content: ''; height: 1px; flex: 1; background: var(--line); }

.cat-group { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-bottom: 8px; }
.item-card {
  display: flex; flex-direction: column; background: var(--card);
  border: 1px solid var(--line); border-radius: 24px; overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.item-card.in-cart { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 16%, transparent), var(--shadow-sm); }
.item-visual {
  position: relative; height: 104px; display: grid; place-items: center;
  background:
    radial-gradient(120px 70px at 70% 20%, #ffffffcc 0%, transparent 60%),
    linear-gradient(150deg, #fff 0%, var(--soft) 90%);
  border-bottom: 1px solid var(--line);
}
.iv-emoji { font-size: 48px; filter: drop-shadow(0 6px 10px color-mix(in srgb, var(--accent) 22%, transparent)); }
.item-badge {
  position: absolute; top: 9px; left: 9px; font-size: 9.5px; font-weight: 800;
  background: var(--ink); color: #ffd98a; border-radius: 999px; padding: 4px 9px;
  letter-spacing: .3px; white-space: nowrap; z-index: 1;
}
.item-body { display: flex; flex-direction: column; flex: 1; padding: 13px 14px 14px; }
.item-body h3 {
  font-size: 15px; font-weight: 700; letter-spacing: -.1px; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  /* long single words like "Germanchokolatekake" must not run past the card */
  overflow-wrap: anywhere;
}
.item-body p {
  font-size: 12px; color: var(--muted); margin-top: 4px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.item-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: auto; padding-top: 11px; }
.item-foot .price { font-family: var(--font-display); font-size: 15.5px; font-weight: 700; color: var(--accent); white-space: nowrap; }
.add-btn {
  width: 40px; height: 40px; border-radius: 50%; background: var(--accent); color: var(--accent-ink);
  font-size: 20px; flex-shrink: 0; display: grid; place-items: center;
  box-shadow: 0 5px 14px color-mix(in srgb, var(--accent) 38%, transparent), inset 0 1px 0 rgba(255,255,255,.25);
  transition: transform .12s ease;
}
.add-btn:active { transform: scale(.9); }
.qty-stepper { display: flex; align-items: center; gap: 6px; flex-shrink: 0; background: var(--soft); border-radius: 999px; padding: 3px; }
.qty-stepper button { width: 30px; height: 30px; border-radius: 50%; background: #fff; box-shadow: var(--shadow-sm); font-size: 15px; font-weight: 800; color: var(--accent); }
.qty-stepper span { font-weight: 800; min-width: 15px; text-align: center; font-size: 13.5px; }

/* ============ CART BAR & SHEET ============ */
.cart-bar {
  position: fixed; left: 18px; right: 18px; bottom: 20px; max-width: 524px; margin: 0 auto;
  background: var(--accent); color: var(--accent-ink); border-radius: 999px; padding: 16px 22px;
  display: none; align-items: center; justify-content: space-between; font-weight: 800; font-size: 14.5px;
  box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,.2); z-index: 20;
  animation: rise .3s cubic-bezier(.2,.7,.2,1);
}
.cart-bar.show { display: flex; }
.cart-bar .cb-count { background: rgba(255,255,255,.18); border-radius: 999px; padding: 4px 11px; font-size: 12.5px; }

.sheet-backdrop { position: fixed; inset: 0; background: rgba(24, 16, 10, .5); backdrop-filter: blur(2px); z-index: 30; display: none; }
.sheet-backdrop.show { display: block; animation: fadein .2s ease; }
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 31; background: var(--card);
  border-radius: 26px 26px 0 0; max-width: 560px; margin: 0 auto; padding: 12px 20px 28px;
  max-height: 84svh; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  transform: translateY(102%); transition: transform .3s cubic-bezier(.2,.7,.2,1);
}
.sheet.show { transform: none; }
.sheet-handle { width: 40px; height: 4px; border-radius: 99px; background: var(--line); margin: 4px auto 16px; }
.sheet h2 { font-family: var(--font-display); font-size: 20px; font-weight: 700; margin-bottom: 12px; }
.cart-row { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.cart-row .name { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.cart-row .amt { font-weight: 800; font-size: 13.5px; white-space: nowrap; }

.totals { padding-top: 14px; font-size: 14px; }
.totals .row { display: flex; justify-content: space-between; padding: 4px 0; color: var(--muted); }
.totals .row.discount { color: #1a8a4a; font-weight: 700; }
.totals .row.grand {
  color: var(--ink); font-weight: 800; font-size: 16px; margin-top: 8px; padding-top: 12px;
  border-top: 1.5px dashed var(--line);
}
.totals .row.grand span:last-child { font-family: var(--font-display); font-size: 19px; }
.cart-row .amt, .cart-bar #cart-bar-total { font-variant-numeric: tabular-nums; }

.primary-btn {
  width: 100%; background: var(--accent); color: var(--accent-ink); border-radius: 999px;
  padding: 17px; font-size: 15px; font-weight: 800; letter-spacing: .3px; margin-top: 16px;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 32%, transparent), inset 0 1px 0 rgba(255,255,255,.22);
  transition: transform .12s ease;
}
.primary-btn:active { transform: scale(.985); }
.primary-btn:disabled { opacity: .45; box-shadow: none; }

/* ============ CHECKOUT ============ */
.form-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px; margin-bottom: 14px; box-shadow: var(--shadow-sm);
}
.form-card > h2 {
  font-family: var(--font-display); font-size: 16px; font-weight: 700; margin-bottom: 14px;
  display: flex; align-items: center; gap: 8px;
}
.form-card > h2 .n {
  width: 24px; height: 24px; border-radius: 50%;
  background: linear-gradient(135deg, #eddbb2, #cfa964); color: #4a3413;
  font-family: var(--font-body); font-size: 12px; font-weight: 800; display: grid; place-items: center;
  box-shadow: 0 2px 6px rgba(176, 139, 67, .35);
}
.field { margin-bottom: 12px; }
.field:last-child { margin-bottom: 2px; }
.field label { display: block; font-size: 12px; font-weight: 700; margin-bottom: 6px; color: #6e6156; }
.field input, .field textarea {
  width: 100%; background: #fbf9f6; border: 1.5px solid var(--line); border-radius: 13px;
  padding: 13px 14px; font-size: 15px; outline: none; transition: border-color .15s, background .15s;
}
.field input:focus, .field textarea:focus { border-color: var(--accent); background: #fff; }

.pay-options { display: grid; gap: 9px; }
.pay-option {
  display: flex; align-items: center; gap: 13px; background: #fbf9f6;
  border: 1.5px solid var(--line); border-radius: 15px; padding: 13px 14px; text-align: left; width: 100%;
  transition: all .15s ease;
}
.pay-option.selected { border-color: var(--accent); background: var(--soft); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
.pay-ico {
  width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
  font-size: 13px; font-weight: 800; flex-shrink: 0; background: #fff; border: 1px solid var(--line);
}
.pay-ico.cash { font-size: 19px; }
.pay-ico.momo { background: #ffcb05; color: #00427a; border-color: #eebc00; }
.pay-ico.om { background: #ff7900; color: #fff; border-color: #ef6f00; }
.pay-option .t { font-size: 14px; font-weight: 700; }
.pay-option .d { font-size: 12px; color: var(--muted); margin-top: 1px; }
.pay-option .radio {
  margin-left: auto; width: 20px; height: 20px; border-radius: 50%; border: 2px solid #d8ccbd; flex-shrink: 0;
  display: grid; place-items: center; transition: all .15s ease;
}
.pay-option.selected .radio { border-color: var(--accent); }
.pay-option.selected .radio::after { content: ''; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }

.promo-row { display: flex; gap: 9px; }
.promo-row input {
  flex: 1; background: #fbf9f6; border: 1.5px solid var(--line); border-radius: 13px;
  padding: 13px 14px; font-size: 14.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  outline: none; min-width: 0;
}
.promo-row input:focus { border-color: var(--accent); background: #fff; }
.promo-apply { background: var(--ink); color: #fff; border-radius: 13px; padding: 0 20px; font-weight: 800; font-size: 13.5px; }
.promo-status { font-size: 13px; font-weight: 700; margin-top: 10px; display: none; }
.promo-status.ok { display: block; color: #1a8a4a; }
.promo-status.bad { display: block; color: #b42323; }

.geo-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%;
  padding: 13px; border-radius: 13px; font-weight: 800; font-size: 13.5px;
  background: #eef6ff; color: #175cd3; border: 1.5px dashed #a8cbf5;
  transition: all .15s ease;
}
.geo-btn.captured { background: #eefaf1; color: #186a37; border-color: #9ed9b1; border-style: solid; }
.geo-status { font-size: 12.5px; font-weight: 600; margin-top: 8px; color: var(--muted); display: none; }
.geo-status.show { display: block; }
.geo-status.err { color: #b45309; }

.eta-note {
  display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 600; color: #6e6156;
  background: var(--soft); border-radius: 13px; padding: 12px 14px; margin-bottom: 14px;
}

.error-msg { background: #fdefef; color: #b42323; border: 1px solid #f5cfcf; border-radius: 13px; padding: 12px 14px; font-size: 13.5px; margin-top: 12px; display: none; font-weight: 600; }
.error-msg.show { display: block; }

/* ============ TRACKING ============ */
.confirm-hero { text-align: center; padding: 26px 10px 8px; }
.confirm-hero .big {
  width: 84px; height: 84px; margin: 0 auto; border-radius: 50%;
  background: var(--soft); display: grid; place-items: center; font-size: 40px;
  box-shadow: inset 0 0 0 8px #fff, var(--shadow-sm); border: 1px solid var(--line);
}
.confirm-hero h1 { font-family: var(--font-display); font-size: 26px; font-weight: 700; margin-top: 16px; }
.confirm-hero .code { font-size: 13.5px; color: var(--muted); margin-top: 8px; }
.code-chip {
  display: inline-block; background: var(--ink); color: #f0d9a8; font-weight: 800; letter-spacing: 2px;
  border-radius: 9px; padding: 4px 12px; font-size: 14px; margin-left: 4px;
  border: 1px solid #3a2c1c; box-shadow: 0 3px 10px rgba(30, 23, 18, .25);
}
.eta-banner {
  display: flex; align-items: center; gap: 14px; margin-top: 20px;
  background: var(--soft); border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--line));
  border-radius: var(--r-md); padding: 16px;
}
.eta-banner .clock { font-size: 26px; }
.eta-banner .eta-label { font-size: 12px; font-weight: 700; color: #6e6156; text-transform: uppercase; letter-spacing: .5px; }
.eta-banner .eta-time { font-family: var(--font-display); font-size: 21px; font-weight: 700; font-style: italic; color: var(--accent); margin-top: 2px; }
.eta-banner .eta-sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

.timeline {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 22px; margin-top: 14px; box-shadow: var(--shadow-sm);
}
.step { display: flex; gap: 15px; position: relative; padding-bottom: 26px; }
.step:last-child { padding-bottom: 0; }
.step::before { content: ''; position: absolute; left: 14px; top: 32px; bottom: 0; width: 2px; background: var(--line); border-radius: 2px; }
.step.done::before { background: var(--accent); }
.step:last-child::before { display: none; }
.step .dot-lg {
  width: 30px; height: 30px; border-radius: 50%; background: #fbf9f6; border: 2px solid var(--line);
  display: grid; place-items: center; font-size: 13px; flex-shrink: 0; z-index: 1; transition: all .3s ease;
}
.step.done .dot-lg { background: var(--accent); border-color: var(--accent); }
.step.current .dot-lg { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 15%, transparent); }
.step .t { font-size: 14.5px; font-weight: 700; padding-top: 5px; color: #c0b4a5; }
.step.done .t { color: var(--ink); }

.paybox { margin-top: 14px; background: #fff8e6; border: 1px solid #f3dfa4; color: #8a6116; border-radius: 13px; padding: 14px; font-size: 13.5px; font-weight: 600; }

.summary-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px; margin-top: 14px; box-shadow: var(--shadow-sm);
}
.summary-card h3 { font-family: var(--font-display); font-size: 15px; font-weight: 700; margin-bottom: 8px; }

.track-lookup-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 22px; box-shadow: var(--shadow-sm); text-align: center;
}
.track-lookup-card p { color: var(--muted); font-size: 14px; }
.track-input { display: flex; gap: 10px; margin-top: 14px; }
.track-input input {
  flex: 1; background: #fbf9f6; border: 1.5px solid var(--line); border-radius: 13px;
  padding: 13px 14px; font-size: 16px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase;
  text-align: center; outline: none; min-width: 0;
}
.track-input input:focus { border-color: var(--ink); }
.ghost-btn { background: var(--ink); color: #fff; border-radius: 13px; padding: 12px 20px; font-weight: 800; font-size: 14px; }

/* ============ HELP CENTRE ============ */
.help-link {
  display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%;
  margin-top: 12px; padding: 13px; border-radius: var(--r-md);
  background: #fff; border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  color: #6e6156; font-weight: 700; font-size: 13.5px;
}
.help-contact { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.help-cta {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 15px; border-radius: var(--r-md); font-weight: 800; font-size: 14px; text-decoration: none;
  box-shadow: var(--shadow-sm);
}
.help-cta.call { background: var(--ink); color: #fff; }
.help-cta.wa { background: #1fa855; color: #fff; }
.faq-item {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md);
  margin-bottom: 10px; box-shadow: var(--shadow-sm); overflow: hidden;
}
.faq-item summary {
  list-style: none; cursor: pointer; padding: 16px 18px;
  font-weight: 700; font-size: 14.5px; position: relative; padding-right: 42px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+'; position: absolute; right: 18px; top: 50%; transform: translateY(-50%);
  font-size: 20px; font-weight: 600; color: var(--muted); transition: transform .2s ease;
}
.faq-item[open] summary::after { transform: translateY(-50%) rotate(45deg); color: var(--accent); }
.faq-item p { padding: 0 18px 16px; font-size: 13.5px; color: var(--muted); line-height: 1.65; }

/* ============ REWARDS ============ */
.rew-card {
  background: linear-gradient(135deg, #241c17, #453022 130%);
  color: #fdf4e7; border-radius: var(--r-lg); padding: 22px; box-shadow: var(--shadow-md);
}
.rew-top { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
.rew-label { font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .8px; opacity: .7; }
.rew-logo { display: block; width: 132px; height: auto; margin-bottom: 12px; }
.rew-points { font-family: var(--font-display); font-size: 44px; font-weight: 800; line-height: 1.05; margin-top: 4px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rew-coin { width: 38px; height: 38px; flex: none; filter: drop-shadow(0 2px 6px rgba(0,0,0,.35)); }
.rew-points small { font-size: 16px; font-weight: 700; opacity: .7; }
.rew-phone { font-size: 13px; font-weight: 700; opacity: .75; margin-top: 6px; }
.rew-qr { background: #fff; border-radius: 14px; padding: 8px; align-self: flex-start; box-shadow: 0 4px 14px rgba(0,0,0,.3); }
.rew-qr img, .rew-qr canvas { display: block; }
.qr-fallback { color: #241c17; font-weight: 800; font-size: 15px; letter-spacing: 1px; padding: 30px 6px; }
.rew-card .loy-bar { height: 10px; background: rgba(255,255,255,.15); border-radius: 999px; overflow: hidden; }
.rew-card .loy-fill { height: 100%; background: linear-gradient(90deg, #ffd98a, #ff9d5c); border-radius: 999px; transition: width .5s ease; }
.rew-progress { font-size: 13.5px; font-weight: 800; margin-top: 10px; color: #ffd98a; }
.rew-progress.ready { color: #7ef0a2; }
.rew-reward { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; font-weight: 600; margin-top: 14px; background: rgba(255,255,255,.08); border-radius: 12px; padding: 12px 14px; }
.rew-hint { font-size: 12px; opacity: .65; margin-top: 12px; line-height: 1.5; }
.rew-how { font-size: 13.5px; color: var(--muted); line-height: 1.6; }

/* ============ PROMO POPUP ============ */
.promo-pop-backdrop {
  position: fixed; inset: 0; background: rgba(20, 13, 8, .55); backdrop-filter: blur(3px);
  z-index: 60; display: none; place-items: center; padding: 24px;
}
.promo-pop-backdrop.show { display: grid; animation: fadein .25s ease; }
.promo-pop {
  position: relative; background: var(--card); border-radius: 28px; padding: 34px 26px 26px;
  width: 100%; max-width: 380px; text-align: center; box-shadow: var(--shadow-lg);
  animation: pop .35s cubic-bezier(.2,.9,.3,1.2);
  background:
    radial-gradient(400px 200px at 50% -80px, #fdf3d7 0%, var(--card) 55%);
}
@keyframes pop { from { opacity: 0; transform: scale(.85) translateY(16px); } to { opacity: 1; transform: none; } }
.pp-close {
  position: absolute; top: 14px; right: 14px; width: 34px; height: 34px; border-radius: 50%;
  background: var(--soft); color: var(--muted); font-size: 14px; font-weight: 700;
}
.pp-burst { font-size: 52px; }
.promo-pop h2 { font-family: var(--font-display); font-size: 24px; font-weight: 800; margin-top: 10px; letter-spacing: -.3px; }
.promo-pop p { color: var(--muted); font-size: 14px; margin-top: 8px; line-height: 1.55; }
.pp-code {
  display: inline-block; margin-top: 16px; border: 2px dashed #c89b57; color: #8a5a1e;
  background: #fdf6e5; font-weight: 800; font-size: 16px; letter-spacing: 2px;
  border-radius: 12px; padding: 10px 18px;
}

/* Home pop-up ad — a full-bleed interstitial card with the ✕ below it. */
.ad-pop-backdrop {
  position: fixed; inset: 0; background: rgba(15, 12, 8, .74); backdrop-filter: blur(3px);
  z-index: 61; display: none; place-items: center; padding: 26px;
}
.ad-pop-backdrop.show { display: grid; animation: fadein .25s ease; }
.ad-pop-wrap { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; max-width: 340px; }
.ad-pop {
  position: relative; width: 100%; border-radius: 22px; overflow: hidden; background: #fff;
  box-shadow: 0 26px 64px -22px rgba(0,0,0,.6); animation: pop .35s cubic-bezier(.2,.9,.3,1.2);
}
.ad-track { display: flex; transition: transform .35s ease; }
.ad-slide { min-width: 100%; }
.ad-slide img { display: block; width: 100%; height: auto; }
.ad-dots { position: absolute; left: 0; right: 0; bottom: 12px; display: flex; justify-content: center; gap: 6px; }
.ad-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.55); box-shadow: 0 0 3px rgba(0,0,0,.3); }
.ad-dot.on { background: #fff; }
.ad-close {
  width: 42px; height: 42px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255,255,255,.18); color: #fff; font-size: 18px; font-weight: 700;
  display: grid; place-items: center; -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.ad-close:active { transform: scale(.94); }

/* ============ ICONS ============ */
.ic { width: 16px; height: 16px; vertical-align: -3px; flex-shrink: 0; }
.wm-dot .ic { width: 15px; height: 15px; vertical-align: 0; }
.meta-chip .ic, .bb-meta .ic { width: 13px; height: 13px; vertical-align: -2px; }
.featured-label .ic { width: 15px; height: 15px; color: var(--lux); }
.home-track .ic, .help-link .ic { width: 17px; height: 17px; }
.help-cta .ic { width: 18px; height: 18px; }
.pay-ico .ic { width: 19px; height: 19px; }
.eta-note .ic { width: 17px; height: 17px; color: var(--accent); }
.eta-banner .clock .ic-xl, .eta-banner .clock .ic { width: 27px; height: 27px; color: var(--accent); }
.confirm-hero .big .ic { width: 38px; height: 38px; color: var(--accent); }
.step .dot-lg .ic { width: 13px; height: 13px; }
.step.done .dot-lg .ic { color: #fff; }
.paybox .ic { width: 15px; height: 15px; }
.pb-ico .ic { width: 22px; height: 22px; color: #ffd98a; }
.pp-burst .ic-burst { width: 46px; height: 46px; color: var(--lux); }
.rew-reward .ic { width: 17px; height: 17px; color: #ffd98a; }
.rew-phone .ic { width: 13px; height: 13px; }
.pm-svg { width: 138px; height: 138px; stroke-width: 1.4; }
.poster-mark { font-size: 0; }
.brand-band .pm-svg { width: 116px; height: 116px; }

/* ============ DESKTOP ============ */
@media (min-width: 900px) {
  .view { max-width: 1100px; padding: 26px 40px 70px; }
  #view-track, #view-rewards, #view-help { max-width: 700px; }

  /* home */
  .home-hero { padding: 48px 2px 32px; }
  .home-hero h1 { font-size: 58px; }
  .home-hero p { font-size: 16.5px; max-width: 48ch; }
  .brand-grid { grid-template-columns: repeat(3, 1fr); gap: 18px; }
  .poster { min-height: 330px; justify-content: space-between; padding: 24px; }
  .poster-head { flex-direction: column; align-items: flex-start; gap: 16px; }
  .poster h2 { font-size: 23px; }
  .brand-logo { width: 72px; height: 72px; border-radius: 19px; }
  .poster-mark { transform: rotate(-10deg); }
  .pm-svg { width: 170px; height: 170px; }
  .poster-foot { flex-direction: column; align-items: flex-start; gap: 14px; }
  .home-links, .help-link { max-width: 580px; margin-left: auto; margin-right: auto; }
  .home-links { margin-top: 26px; }

  /* menu */
  .brand-band { padding: 26px; }
  .brand-band h2 { font-size: 26px; }
  .cat-group { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .item-visual { height: 130px; }
  .iv-emoji { font-size: 56px; }
  .item-body h3 { font-size: 15.5px; }
  .item-body p { font-size: 12.5px; }
  .cat-tabs { top: 66px; }
  .feat-card { flex: 0 0 172px; }
  .cart-bar { max-width: 560px; }

  /* checkout: two columns */
  #view-checkout.active { display: grid; grid-template-columns: 1.05fr .95fr; gap: 0 24px; align-items: start; }
  #view-checkout .topbar, #view-checkout .eta-note { grid-column: 1 / -1; }
  #view-checkout .form-card { grid-column: 2; }
  #view-checkout .eta-note + .form-card { grid-column: 1; grid-row: span 4; }
  #view-checkout .error-msg, #view-checkout .primary-btn { grid-column: 2; }
}

/* hover states (pointer devices) */
@media (hover: hover) {
  .item-card:hover {
    transform: translateY(-2px); box-shadow: var(--shadow-md);
    border-color: color-mix(in srgb, var(--accent) 28%, var(--line));
  }
  .add-btn:hover { transform: scale(1.09); }
  .feat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
  .cat-tab:hover { border-color: var(--accent); color: var(--accent); }
  .cat-tab.active:hover { color: var(--accent-ink); }
  .home-track:hover, .help-link:hover { border-color: var(--lux); color: #8a6b1f; }
  .pay-option:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
}

/* ============ LOYALTY 2.0 ============ */
.loy-hint { font-size: 12.5px; color: var(--muted); margin-bottom: 12px; }
.loy-check-btn {
  width: 100%; padding: 13px; border-radius: 13px; font-weight: 800; font-size: 13.5px;
  background: var(--soft); color: var(--accent); border: 1.5px dashed color-mix(in srgb, var(--accent) 35%, var(--line));
}
.loy-msg { font-size: 13px; font-weight: 700; margin-top: 12px; }
.loy-msg.ok { color: #1a8a4a; }
.loy-msg.err { color: #b42323; }
.loy-opts { display: grid; gap: 8px; margin-top: 10px; }
.loy-opt {
  display: flex; align-items: center; gap: 10px; text-align: left; width: 100%;
  background: #fbf9f6; border: 1.5px solid var(--line); border-radius: 13px; padding: 12px 14px;
  transition: all .14s ease;
}
.loy-opt:disabled { opacity: .45; }
.loy-opt.selected { border-color: var(--accent); background: var(--soft); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
.loy-opt .lo-t { flex: 1; font-size: 13.5px; font-weight: 700; min-width: 0; }
.loy-opt .lo-d { font-size: 12px; font-weight: 800; color: var(--accent); white-space: nowrap; }
.loy-opt .radio { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #d8ccbd; flex-shrink: 0; display: grid; place-items: center; }
.loy-opt.selected .radio { border-color: var(--accent); }
.loy-opt.selected .radio::after { content: ''; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }

/* Flexible points redemption at checkout */
.pts-apply { margin-top: 10px; border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px; background: var(--soft); }
.pts-apply-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; font-weight: 700; }
.pts-max { border: 1px solid var(--accent); color: var(--accent); background: transparent; font-weight: 800; font-size: 12px; border-radius: 999px; padding: 5px 12px; cursor: pointer; white-space: nowrap; }
.pts-max:active { transform: scale(.96); }
.pts-apply input[type=range] { width: 100%; margin: 12px 0 6px; accent-color: var(--accent); }
.pts-apply-foot { display: flex; align-items: center; justify-content: space-between; font-weight: 800; }
.pts-apply-foot #pts-count { display: inline-flex; align-items: center; gap: 5px; }
.pts-off { color: var(--accent); }

.rew-tier-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 12px; flex-wrap: wrap; }
.tier-chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 800;
  border-radius: 999px; padding: 6px 14px; letter-spacing: .3px;
}
.tier-chip .ic { width: 13px; height: 13px; }
.tier-bronze { background: linear-gradient(120deg, #b0876211, #8a5a2e33); color: #d8a878; border: 1px solid #8a5a2e66; }
.tier-silver { background: linear-gradient(120deg, #cfd4dc22, #9aa3b044); color: #dfe5ee; border: 1px solid #9aa3b077; }
.tier-gold { background: linear-gradient(120deg, #e9c67e33, #c9a35a55); color: #ffd98a; border: 1px solid #c9a35a88; }
.tier-mult { font-size: 12px; font-weight: 700; opacity: .75; }

.rw-row {
  display: flex; align-items: center; gap: 12px; padding: 12px 0;
  border-bottom: 1px solid var(--line); opacity: .55;
}
.rw-row.afford { opacity: 1; }
.rw-row:last-of-type { border-bottom: none; }
.rw-ico { width: 38px; height: 38px; border-radius: 12px; background: var(--soft); display: grid; place-items: center; color: var(--accent); flex-shrink: 0; }
.rw-ico .ic { width: 17px; height: 17px; }
.rw-t { flex: 1; font-size: 13.5px; font-weight: 700; min-width: 0; }
.rw-t small { display: block; color: var(--muted); font-weight: 600; font-size: 12px; margin-top: 1px; }
.rw-cost { font-family: var(--font-display); font-size: 14px; font-weight: 800; color: var(--accent); white-space: nowrap; }
.rw-note { font-size: 12px; color: var(--muted); padding-top: 10px; }
.rw-ev { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--muted); padding: 6px 0; border-bottom: 1px dashed var(--line); }
.rw-ev:last-child { border-bottom: none; }
.rw-ev .pos { color: #1a8a4a; font-weight: 800; }
.rw-ev .neg { color: #b42323; font-weight: 800; }

/* install */
.install-btn {
  display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%;
  margin: 12px auto 0; padding: 15px; border-radius: var(--r-md); max-width: 580px;
  background: var(--ink); color: #f0d9a8; font-weight: 800; font-size: 14px;
  box-shadow: var(--shadow-md);
}
.install-tip {
  max-width: 580px; margin: 10px auto 0; background: #fff8e6; border: 1px solid #f3dfa4;
  color: #8a6116; border-radius: 13px; padding: 12px 14px; font-size: 13px; font-weight: 600; text-align: center;
}

/* ============ ACCOUNT ============ */
.auth-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
.auth-tab {
  padding: 11px; border-radius: 12px; font-weight: 800; font-size: 13.5px;
  background: #fbf9f6; border: 1.5px solid var(--line); color: var(--muted);
}
.auth-tab.active { background: var(--ink); color: #f0d9a8; border-color: var(--ink); }
.acct-order {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 11px 0; border-bottom: 1px dashed var(--line);
}
.acct-order:last-of-type { border-bottom: none; }
.ao-code { font-family: var(--font-display); font-weight: 800; font-size: 13.5px; letter-spacing: .5px; }
.ao-meta { flex: 1; font-size: 12.5px; color: var(--muted); font-weight: 600; min-width: 0; }
.ao-status { font-size: 10.5px; font-weight: 800; padding: 4px 10px; border-radius: 999px; text-transform: uppercase; letter-spacing: .3px; white-space: nowrap; }
.ao-status.st-new { background: #fdf2e3; color: #a06514; }
.ao-status.st-confirmed { background: #eef4fd; color: #1d5cc2; }
.ao-status.st-preparing { background: #fefce8; color: #9c7511; }
.ao-status.st-out_for_delivery { background: #eefafd; color: #0d7a94; }
.ao-status.st-delivered { background: #effaf2; color: #1f7a45; }
.ao-status.st-cancelled { background: #fdf0f0; color: #b02a2a; }
.ghost-btn.signout { width: 100%; margin-top: 4px; background: transparent; color: var(--muted); border: 1.5px dashed #d8ccbd; }

/* ============ CELEBRATIONS ============
   Two elements per piece, and the split is the whole trick. The OUTER one only
   falls and drifts sideways. The INNER one is the paper: it turns on a tilted
   axis and squashes to a sliver as it goes edge-on, then flashes flat again.
   One element cannot do both, because a single transform makes the spin and the
   fall share a timing function — and they have nothing to do with each other.
   The fall is thrown-then-settles; the flutter is steady and much faster.     */
.confetti-layer { position: fixed; inset: 0; pointer-events: none; z-index: 90; overflow: hidden; }
.confetti-p {
  position: absolute; top: -22px;
  width: var(--w, 9px); height: var(--h, 14px);
  animation: confetti-fall var(--dur, 3s) cubic-bezier(.12, .58, .5, 1) var(--delay, 0s) forwards;
  will-change: transform, opacity;
}
.confetti-p i {
  display: block; width: 100%; height: 100%;
  background: var(--c, #e9c67e); border-radius: var(--r, 1px);
  animation: confetti-flutter var(--spin, 1s) linear var(--delay, 0s) infinite;
  will-change: transform;
}
@keyframes confetti-fall {
  0%   { transform: translate3d(0, -22px, 0); opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translate3d(var(--dx, 0px), 106vh, 0); opacity: 0; }
}
@keyframes confetti-flutter {
  0%   { transform: rotate3d(var(--ax,1), var(--ay,1), .25,   0deg) scaleX(1); }
  25%  { transform: rotate3d(var(--ax,1), var(--ay,1), .25,  90deg) scaleX(.12); }
  50%  { transform: rotate3d(var(--ax,1), var(--ay,1), .25, 180deg) scaleX(1); }
  75%  { transform: rotate3d(var(--ax,1), var(--ay,1), .25, 270deg) scaleX(.12); }
  100% { transform: rotate3d(var(--ax,1), var(--ay,1), .25, 360deg) scaleX(1); }
}

/* ── the delivered mark, drawn ──────────────────────────────────────────────
   A ring that sweeps round, then a tick written into it. Both start from
   nothing and end exactly at their own length, so neither ever overshoots. */
.dm-art .ic-draw { width: 46px; height: 46px; display: block; color: #1f7a45; }
.dm-art .dc-ring { stroke-dasharray: 139; stroke-dashoffset: 139; }
.dm-art .dc-tick { stroke-dasharray: 40;  stroke-dashoffset: 40; }
/* At rest — revisiting a delivered order — it is simply finished. Replaying a
   celebration every time someone opens an old receipt is the thing that makes
   an animation feel like a decoration rather than a moment. */
.dm-art.drawn .dc-ring, .dm-art.drawn .dc-tick { stroke-dashoffset: 0; }
.dm-art.draw .dc-ring {
  animation: dc-ring .62s cubic-bezier(.62, .02, .3, 1) forwards;
  transform: rotate(-90deg); transform-origin: 50% 50%;   /* start at 12 o'clock */
}
.dm-art.draw .dc-tick { animation: dc-tick .34s cubic-bezier(.5, 0, .3, 1) .44s forwards; }
@keyframes dc-ring { from { stroke-dashoffset: 139; } to { stroke-dashoffset: 0; } }
@keyframes dc-tick { from { stroke-dashoffset: 40; }  to { stroke-dashoffset: 0; } }

/* the softest possible acknowledgement behind it — one ring, once */
.dm-art.draw::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 46px; height: 46px; margin: -23px 0 0 -23px; border-radius: 50%;
  border: 2px solid #1f7a45; pointer-events: none;
  animation: dc-halo .9s cubic-bezier(.2, .7, .3, 1) .5s forwards;
}
.dm-art { position: relative; }
@keyframes dc-halo {
  from { transform: scale(.9); opacity: .5; }
  to   { transform: scale(2.1); opacity: 0; }
}

/* hero-pop kept for the non-delivered states, but without the rubber band:
   it rises and settles, and never grows past the size it is going to be. */
.confirm-hero .big.pop, .dm-art.pop { animation: hero-pop .42s cubic-bezier(.22, .78, .3, 1); }
@keyframes hero-pop {
  from { transform: translateY(7px) scale(.94); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
/* 1.6 on a spring curve is a bounce you can see land twice. The bag getting
   heavier should be felt, not watched. */
.cart-bar.bump { animation: cart-bump .3s cubic-bezier(.3, 1.2, .5, 1); }
@keyframes cart-bump {
  0% { transform: scale(1); }
  38% { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/* Nothing above runs for someone who has asked their phone to stop moving. */
@media (prefers-reduced-motion: reduce) {
  .confetti-p, .confetti-p i,
  .dm-art.draw .dc-ring, .dm-art.draw .dc-tick, .dm-art.draw::after,
  .confirm-hero .big.pop, .dm-art.pop, .cart-bar.bump { animation: none !important; }
  .dm-art .dc-ring, .dm-art .dc-tick { stroke-dashoffset: 0; }
}
.step.current .dot-lg { animation: dot-glow 1.6s ease-in-out infinite; }
@keyframes dot-glow {
  0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent); }
  50% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent) 7%, transparent); }
}

/* ============ PRECISE LOCATION ============ */
.loc-block {
  background: #fbf9f6; border: 1.5px solid var(--line); border-radius: 16px;
  padding: 13px; margin-top: 4px;
}
.loc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.loc-title { font-size: 13px; font-weight: 800; }
.loc-acc { font-size: 11.5px; font-weight: 700; color: var(--muted); }
.loc-acc.good { color: #1a8a4a; }
.loc-acc.rough { color: #b45309; }
.loc-map-wrap { position: relative; border-radius: 13px; overflow: hidden; border: 1px solid var(--line); }
#loc-map { height: 225px; width: 100%; background: #ece7df; }
.loc-empty {
  position: absolute; inset: 0; display: grid; place-items: center; text-align: center;
  background: linear-gradient(140deg, #f3ede4, #e7dfd3); color: var(--muted);
  font-size: 12.5px; font-weight: 600; padding: 16px; z-index: 400; pointer-events: none;
}
.loc-actions { display: flex; align-items: center; gap: 11px; margin-top: 11px; flex-wrap: wrap; }
.loc-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 11px 18px; border-radius: 999px;
  font-weight: 800; font-size: 13px; background: var(--accent); color: var(--accent-ink);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent);
}
.loc-hint { font-size: 11.5px; color: var(--muted); font-weight: 600; flex: 1; min-width: 120px; }
.leaflet-container { font-family: var(--font-body); }
#loc-map img { max-width: none; } /* Google Maps tiles must not inherit our img rules */

/* address read back from the pin */
.addr-auto { display: none; font-size: 12px; font-weight: 650; color: var(--muted); margin-top: 9px; line-height: 1.4; }
.addr-auto.show { display: block; }
.addr-auto.ok { color: #1a8a4a; }
input.autofilled {
  animation: autofill-flash 1.2s ease;
}
@keyframes autofill-flash {
  0% { background: #fff6dd; border-color: var(--gold, #c9a35a); }
  100% { background: #fff; }
}

/* saved addresses */
.sa-label { font-size: 11.5px; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 7px; }
.sa-row { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 14px; }
.sa-chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 700;
  background: #fff; border: 1.5px solid var(--line); border-radius: 999px; padding: 8px 14px;
  box-shadow: var(--shadow-sm);
}
.sa-chip .ic { width: 13px; height: 13px; }
.sa-chip.active { border-color: var(--accent); background: var(--soft); color: var(--accent); }
.sa-dot { width: 6px; height: 6px; border-radius: 50%; background: #1a8a4a; }

/* ============ AUTH ============ */
.auth-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 26px 22px 22px; box-shadow: var(--shadow-md);
}
.auth-hero { text-align: center; margin-bottom: 20px; }
.auth-badge {
  display: inline-block; font-size: 10px; font-weight: 800; letter-spacing: 3px;
  color: var(--lux); margin-bottom: 10px;
}
.auth-logo { display: block; width: 168px; max-width: 62%; height: auto; margin: 0 auto 14px; }
.auth-hero h2 { font-family: var(--font-display); font-size: 22px; font-weight: 800; line-height: 1.2; }
.auth-hero p { font-size: 13.5px; color: var(--muted); margin-top: 8px; }
.auth-back {
  font-size: 12.5px; font-weight: 700; color: var(--muted); margin-bottom: 12px;
  display: inline-flex; align-items: center; gap: 5px;
}
.pin-wrap { position: relative; }
.pin-wrap input { padding-right: 66px !important; letter-spacing: 4px; font-weight: 700; }
.pin-eye {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font-size: 11.5px; font-weight: 800; color: var(--accent); padding: 6px 8px; border-radius: 8px;
  background: var(--soft);
}
.auth-forgot { width: 100%; margin-top: 12px; font-size: 12.5px; font-weight: 700; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.error-msg.info { background: #f0f6ff; border-color: #c9dcf7; color: #1d5cc2; }

/* ---- the sign-in screen ----
   Its own screen now, so it gets to breathe: the card sits centred in the
   glass rather than shoved under a title bar, and there is always a way past
   it — an account is worth having, not a toll gate on the menu. */
/* CENTRING A SCROLLER IS WHERE THE LOGIN LOST ITS SCROLL.
   `display:flex` on the screen with `margin:auto` on the card centres it
   beautifully while it fits — and the moment the card is taller than the
   phone, the auto margins share the negative space equally and push the top of
   the card ABOVE scroll position zero, where nothing can reach it. The logo
   and the headline simply cannot be scrolled to. It shows up on a short phone,
   on a large text size, and every time the keyboard opens.

   min-height:100% does the same job without the trap: short content is centred
   by justify-content, tall content grows the box past the viewport and scrolls
   from the top like anything else, because there is no free space left for
   justify-content to distribute. */
#view-signin { display: none; }
#view-signin.active { display: block; }
.signin-wrap {
  width: 100%; max-width: 560px; margin: 0 auto;
  min-height: 100%;
  display: flex; flex-direction: column; justify-content: center;
  padding: 14px 0 34px;
}
.si-skip {
  align-self: flex-end; margin-bottom: 10px;
  font-size: 12.5px; font-weight: 800; color: var(--muted);
  padding: 8px 14px; border-radius: 999px; background: #fff;
  border: 1px solid var(--line); box-shadow: var(--shadow-sm);
}
.si-perks { list-style: none; margin: 18px 2px 0; display: grid; gap: 9px; }
.si-perks li {
  position: relative; padding-left: 24px;
  font-size: 12.8px; font-weight: 650; color: var(--muted); line-height: 1.35;
}
.si-perks li::before {
  content: ''; position: absolute; left: 0; top: 3px;
  width: 15px; height: 15px; border-radius: 50%; background: var(--soft);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}
.si-perks li::after {
  content: ''; position: absolute; left: 4.5px; top: 7.5px;
  width: 6px; height: 3.5px; border-left: 1.8px solid var(--accent); border-bottom: 1.8px solid var(--accent);
  transform: rotate(-45deg);
}
.forgot-box {
  margin-top: 14px; background: var(--soft); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 14px;
}
.forgot-box h4 { font-size: 13.5px; font-weight: 800; margin-bottom: 5px; }
.forgot-box p { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.forgot-calls { display: grid; gap: 8px; margin-top: 11px; }
.forgot-calls a {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 13px; font-size: 13px; font-weight: 700; box-shadow: var(--shadow-sm);
  color: var(--ink); text-decoration: none;
}
.forgot-calls a small { color: var(--accent); font-weight: 800; white-space: nowrap; }
/* WhatsApp reset: its own fields, plus the "or call the store" divider that
   separates the self-service flow from the fallback below it. */
#fpin-wa .field { margin-top: 10px; }
#fpin-wa #fpin-send, #fpin-wa #fpin-confirm { margin-top: 12px; }
#fpin-resend { margin-top: 8px; display: block; }
.fpin-or {
  display: flex; align-items: center; gap: 10px; margin: 14px 0 2px;
  color: var(--muted); font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
}
.fpin-or::before, .fpin-or::after { content: ''; flex: 1; height: 1px; background: var(--line); }
/* Notification diagnostics — a temporary readout for chasing a push that will
   not turn on. Deliberately plain and monospace-ish so values are easy to read
   and screenshot. */
.ntd { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.ntd summary { font-size: 12px; font-weight: 700; color: var(--muted); cursor: pointer; }
.ntd-row { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; padding: 4px 0; border-bottom: 1px dashed var(--line); }
.ntd-row span { color: var(--muted); }
.ntd-row b { text-align: right; word-break: break-word; }

/* ---- account & security ---- */
.tb-gear {
  margin-left: auto; width: 40px; height: 40px; border-radius: 13px;
  background: #fff; border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  display: grid; place-items: center; color: var(--ink); flex-shrink: 0;
}
.tb-gear svg { width: 19px; height: 19px; }
.acs-note { font-size: 11.8px; color: var(--muted); line-height: 1.45; margin: -2px 0 12px; }
#view-account .summary-card .primary-btn { margin-top: 4px; }
#view-account .error-msg { margin-top: 10px; }

/* StarPass strip on home */
.starpass-strip {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: linear-gradient(100deg, #241c17, #43301c 130%); color: #f2e3c8;
  border: 1px solid #3a2a1c; border-radius: var(--r-md); padding: 14px 16px; margin-bottom: 14px;
  box-shadow: var(--shadow-md); font-weight: 700; font-size: 13.5px;
}
.starpass-strip .sp-ico { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 10px; background: rgba(233,198,126,.15); color: #e9c67e; flex-shrink: 0; }
.starpass-strip .sp-coin { width: 32px; height: 32px; flex: none; filter: drop-shadow(0 2px 5px rgba(0,0,0,.4)); }
.starpass-strip .sp-txt { flex: 1; min-width: 0; }
.starpass-strip .sp-go { color: #a08a65; font-size: 20px; }
.starpass-strip.member .sp-ico { background: linear-gradient(135deg, #e9c67e, #c9a35a); color: #241a10; }

/* map + auth on desktop */
@media (min-width: 900px) {
  #loc-map { height: 300px; }
  .auth-card { max-width: 480px; margin: 0 auto; }
}

/* Starz Points coin, inline size */
.coin-xs { display: inline-block; width: 15px; height: 15px; vertical-align: -3px; margin-left: 1px; }

/* ============ mix-ins & add-ons ============ */
.add-btn.opt.has {
  background: var(--accent); color: var(--accent-ink);
  font-size: 15px; font-weight: 800;
}
.opt-sheet { max-height: 88svh; overflow-y: auto; }
.opt-head { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 6px; }
.opt-emoji { font-size: 38px; line-height: 1; flex: none; }
.opt-head h2 { font-family: var(--font-display); font-size: 21px; font-weight: 800; }
.opt-desc { font-size: 13px; color: var(--muted); margin-top: 3px; }
.opt-group { margin-top: 20px; }
.og-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
.og-name { font-size: 14.5px; font-weight: 800; }
.og-rule {
  font-size: 10.5px; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
  color: var(--muted); background: var(--soft); border-radius: 999px; padding: 4px 9px;
}
.og-rule.req { color: #8a5a12; background: #fdf1dc; }
.og-choices { display: grid; gap: 7px; }
.oc {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  border: 1.5px solid var(--line); border-radius: 14px; padding: 12px 14px; background: #fff;
  font-size: 14px; font-weight: 650; transition: border-color .12s ease, background .12s ease;
}
@media (hover: hover) { .oc:hover { border-color: #d9cdbd; } }
.oc.on { border-color: var(--accent); background: var(--soft); }
.oc.off { opacity: .42; }
.oc-box {
  width: 21px; height: 21px; flex: none; border-radius: 7px; border: 1.5px solid var(--line);
  display: grid; place-items: center; background: #fff; color: var(--accent-ink);
}
.oc-box.radio { border-radius: 50%; }
.oc.on .oc-box { background: var(--accent); border-color: var(--accent); }
.oc-box .ic { width: 13px; height: 13px; }
.oc-name { flex: 1; }
.oc-price { font-size: 13px; font-weight: 800; color: var(--lux); white-space: nowrap; }
.opt-note-field { margin-top: 20px; }
.opt-note-field label { display: block; font-size: 12.5px; font-weight: 700; color: var(--muted); margin-bottom: 7px; }
.opt-note-field input {
  width: 100%; border: 1.5px solid var(--line); border-radius: 13px; padding: 13px 14px;
  font-size: 14px; background: #fff; outline: none;
}
.opt-note-field input:focus { border-color: var(--accent); }
/* Left over from when the add button sat in the page flow. In the floating
   bar it just pushed the button 18px below the quantity stepper beside it. */
#btn-opt-add { margin-top: 0; }
#btn-opt-add:disabled { opacity: .45; }
.cart-row .name { display: flex; flex-direction: column; }
.cr-opts { font-size: 11.5px; font-weight: 600; color: var(--muted); line-height: 1.35; margin-top: 2px; }

/* cancellation message shown to the customer */
.cancel-note {
  background: #fdf0f0; border: 1px solid #f3d9d9; border-radius: var(--r-md);
  padding: 16px 18px; text-align: left;
}
.cn-head { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 14.5px; color: #a02b2b; margin-bottom: 7px; }
.cn-head .ic { width: 16px; height: 16px; }
.cancel-note p { font-size: 13.5px; color: #7a3a3a; line-height: 1.5; }
.row-opts { display: block; font-size: 11.5px; font-weight: 600; color: var(--muted); margin-top: 2px; }

/* signed-in customers: their orders, no code needed */
.track-mine { margin-bottom: 16px; }
.tm-head {
  font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .7px;
  color: var(--muted); margin-bottom: 9px; display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
}
.tm-head small { text-transform: none; letter-spacing: 0; font-weight: 600; font-size: 11.5px; opacity: .8; }
.tm-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: #fff; border: 1.5px solid var(--line); border-radius: 16px;
  padding: 13px 15px; margin-bottom: 8px; box-shadow: var(--shadow-sm);
}
@media (hover: hover) { .tm-row:hover { border-color: #d9cdbd; transform: translateY(-1px); } }
.tm-code { font-family: var(--font-display); font-weight: 800; font-size: 14px; flex: none; }
.tm-mid { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tm-brand { font-size: 12.5px; font-weight: 700; color: var(--muted); }
.tm-status { font-size: 12px; font-weight: 800; color: var(--lux); }
.tm-status.st-delivered { color: #1a8a4a; }
.tm-status.st-cancelled { color: #b03030; }
.tm-total { font-weight: 800; font-size: 13.5px; white-space: nowrap; }
.promo-join {
  display: inline-block; margin-left: 6px; font-weight: 800; font-size: 12.5px;
  text-decoration: underline; text-underline-offset: 3px; color: var(--accent);
}

/* product photos.
   The shop's photography is square, so the frame is square too — the product
   sits dead centre and nothing important gets cropped away. */
.item-visual.has-photo { padding: 0; overflow: hidden; height: auto; aspect-ratio: 1 / 1; }
.iv-photo { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
.fc-photo { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; object-position: center; border-radius: 14px; display: block; margin-bottom: 9px; }
.opt-photo { width: 54px; height: 54px; border-radius: 14px; object-fit: cover; display: block; }
.opt-emoji:has(.opt-photo) { font-size: 0; }

/* prepaid coupons — coming soon */
.coupon-soon { background: linear-gradient(150deg, #241c17, #3a2a1a); border-color: #3a2a1c; color: #f3e7d2; }
.coupon-soon h3 { color: #f7ecd8; }
.coupon-soon p { font-size: 13px; color: #cbb595; line-height: 1.5; margin-top: 10px; }
.cs-top { display: flex; align-items: center; gap: 13px; }
.cs-coin { width: 40px; height: 40px; flex: none; filter: drop-shadow(0 3px 8px rgba(0,0,0,.45)); }
.cs-badge {
  display: inline-block; margin-top: 5px; font-size: 10px; font-weight: 800; letter-spacing: 1.4px;
  text-transform: uppercase; color: #241a10; background: linear-gradient(120deg, #e9c67e, #c9a35a);
  border-radius: 999px; padding: 4px 10px;
}

/* receipt + call actions on tracking */
.track-actions { display: flex; gap: 9px; margin-top: 16px; flex-wrap: wrap; }
.ta-btn {
  flex: 1; min-width: 150px; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1.5px solid var(--line); border-radius: 14px; padding: 13px 16px; background: #fff;
  font-size: 13.5px; font-weight: 800; color: var(--ink); box-shadow: var(--shadow-sm);
}
@media (hover: hover) { .ta-btn:hover { border-color: #d9cdbd; transform: translateY(-1px); } }
.ta-btn .ic { width: 16px; height: 16px; }
.ta-btn.call { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* branches in the Help Centre */
.help-branches { margin-bottom: 20px; }
.hb-label { font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .7px; color: var(--muted); margin-bottom: 9px; }
.hb-row {
  display: flex; align-items: center; gap: 12px; background: #fff; border: 1.5px solid var(--line);
  border-radius: 16px; padding: 12px 14px; margin-bottom: 8px; box-shadow: var(--shadow-sm);
}
.hb-logo { width: 40px; height: 40px; border-radius: 12px; overflow: hidden; background: #fff; border: 1px solid var(--line); flex: none; }
.hb-logo img { width: 100%; height: 100%; object-fit: contain; padding: 3px; }
.hb-mid { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hb-name { font-weight: 800; font-size: 14px; }
.hb-addr { font-size: 12px; color: var(--muted); font-weight: 600; }
.hb-call {
  width: 42px; height: 42px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); box-shadow: 0 4px 12px rgba(0,0,0,.14);
}
.hb-call .ic { width: 18px; height: 18px; }


/* ============================================================
   BRAND WORLDS
   Each brand's pages are designed as its own little storefront:
   its own paper, masthead, patterns, section dividers, card
   shapes, buttons and cart bar. Nothing here is shared.
   ============================================================ */

/* the masthead gets room for artwork and a decorative bottom edge */
.brand-band { padding: 22px 20px 26px; }
.bb-art { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.bb-edge { position: absolute; left: 0; right: 0; bottom: 0; height: 14px; z-index: 2; pointer-events: none; }
.bb-eyebrow {
  display: block; font-size: 10px; font-weight: 800; letter-spacing: 2.6px;
  text-transform: uppercase; opacity: .72; margin-bottom: 5px;
}
.brand-band .brand-logo { z-index: 1; }

/* ------------------------------------------------------------
   COLD STONE CREAMERY — the parlour
   Burgundy & cream, waffle-cone lattice, scalloped scoop edges,
   italic serif headings with a cream awning stripe.
   ------------------------------------------------------------ */
.brand-band.coldstone { border-radius: 28px 28px 22px 22px; }
.brand-band.coldstone .bb-art {
  background:
    repeating-linear-gradient(45deg, rgba(255,240,225,.10) 0 2px, transparent 2px 16px),
    repeating-linear-gradient(-45deg, rgba(255,240,225,.10) 0 2px, transparent 2px 16px),
    radial-gradient(220px 160px at 84% 12%, rgba(255,214,180,.28), transparent 70%);
}
/* scalloped rim, like the top of a scoop */
.brand-band.coldstone .bb-edge {
  height: 13px;
  background: radial-gradient(circle at 9px -2px, #fcf5f3 9px, transparent 9.5px) repeat-x 0 0 / 18px 13px;
}
body[data-brand="coldstone"] .cat-heading {
  font-family: var(--font-display); font-style: italic; font-weight: 700;
  font-size: 19px; letter-spacing: -.2px; color: #6f1120;
  display: flex; align-items: center; gap: 11px;
}
body[data-brand="coldstone"] .cat-heading::after {
  content: ''; flex: 1; height: 5px; border-radius: 5px;
  background: repeating-linear-gradient(90deg, #9e1b32 0 9px, transparent 9px 17px);
  opacity: .35;
}
body[data-brand="coldstone"] .item-card {
  border-radius: 24px; border-color: #f0dcdd;
  box-shadow: 0 1px 2px rgba(111,17,32,.05), 0 10px 26px rgba(111,17,32,.07);
}
body[data-brand="coldstone"] .item-visual {
  border-radius: 20px 20px 4px 4px;
  background: linear-gradient(160deg, #fdf0f2, #f8e2e5);
  -webkit-mask: radial-gradient(circle at 10px 100%, transparent 9px, #000 9.5px) repeat-x 0 100% / 20px 12px, linear-gradient(#000 0 0) no-repeat 0 0 / 100% calc(100% - 11px);
  mask: radial-gradient(circle at 10px 100%, transparent 9px, #000 9.5px) repeat-x 0 100% / 20px 12px, linear-gradient(#000 0 0) no-repeat 0 0 / 100% calc(100% - 11px);
}
body[data-brand="coldstone"] .item-badge { background: #9e1b32; color: #fff3ef; border-radius: 999px; }
body[data-brand="coldstone"] .add-btn { border-radius: 50%; box-shadow: 0 0 0 3px #fdeaee; }
body[data-brand="coldstone"] .cat-tab { border-radius: 999px; }
body[data-brand="coldstone"] .cart-bar { border-radius: 999px; }

/* ------------------------------------------------------------
   WACCAO — the waffle house
   Golden batter, an embossed waffle grid, square-ish chunky
   cards and cocoa-brown ink.
   ------------------------------------------------------------ */
.brand-band.waccao { border-radius: 22px; }
/* deep, embossed waffle squares */
.brand-band.waccao .bb-art {
  background:
    linear-gradient(rgba(120,74,20,.20) 3px, transparent 3px) 0 0 / 42px 42px,
    linear-gradient(90deg, rgba(120,74,20,.20) 3px, transparent 3px) 0 0 / 42px 42px,
    radial-gradient(circle at 21px 21px, rgba(255,255,255,.35) 12px, transparent 13px) 0 0 / 42px 42px;
  box-shadow: inset 0 0 40px rgba(120,74,20,.20);
}
.brand-band.waccao .bb-edge {
  height: 10px;
  background: repeating-linear-gradient(90deg, #4a2c10 0 14px, transparent 14px 28px);
  opacity: .22;
}
body[data-brand="waccao"] .cat-heading {
  font-weight: 800; font-size: 17px; letter-spacing: -.3px; color: #5a3512;
  display: flex; align-items: center; gap: 10px;
}
body[data-brand="waccao"] .cat-heading::before {
  content: ''; width: 15px; height: 15px; flex: none; border-radius: 4px;
  background:
    linear-gradient(rgba(90,53,18,.55) 1.5px, transparent 1.5px) 0 0 / 7.5px 7.5px,
    linear-gradient(90deg, rgba(90,53,18,.55) 1.5px, transparent 1.5px) 0 0 / 7.5px 7.5px,
    #f0ad00;
}
body[data-brand="waccao"] .cat-heading::after {
  content: ''; flex: 1; height: 2px; background: rgba(90,53,18,.16); border-radius: 2px;
}
body[data-brand="waccao"] .item-card {
  border-radius: 18px; border: 2px solid #ecd9ab; background: #fffdf5;
  box-shadow: 0 2px 0 #ecd9ab, 0 10px 22px rgba(120,74,20,.08);
}
body[data-brand="waccao"] .item-visual {
  border-radius: 12px;
  background:
    linear-gradient(rgba(120,74,20,.10) 2px, transparent 2px) 0 0 / 26px 26px,
    linear-gradient(90deg, rgba(120,74,20,.10) 2px, transparent 2px) 0 0 / 26px 26px,
    linear-gradient(160deg, #fff4d4, #ffe9b0);
}
body[data-brand="waccao"] .item-badge { background: #4a2c10; color: #ffd977; border-radius: 8px; }
body[data-brand="waccao"] .add-btn { border-radius: 12px; }
body[data-brand="waccao"] .cat-tab { border-radius: 12px; }
body[data-brand="waccao"] .cat-tab.active { color: #ffd977; }
body[data-brand="waccao"] .cart-bar { border-radius: 16px; }

/* ------------------------------------------------------------
   CENSA COFFEE — the espresso bar
   Near-black roast, drifting steam, scattered beans, hairline
   rules and uppercase tracked type.
   ------------------------------------------------------------ */
.brand-band.censa { border-radius: 14px; }
.brand-band.censa .bb-art {
  background:
    /* coffee beans */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cg fill='none' stroke='%23e8d5c8' stroke-opacity='.16' stroke-width='1.6'%3E%3Cellipse cx='24' cy='30' rx='11' ry='7' transform='rotate(-28 24 30)'/%3E%3Cpath d='M15 32c4-4 12-5 18-3' /%3E%3Cellipse cx='86' cy='74' rx='11' ry='7' transform='rotate(18 86 74)'/%3E%3Cpath d='M77 73c4-4 12-4 18-2'/%3E%3Cellipse cx='58' cy='104' rx='10' ry='6.5' transform='rotate(-8 58 104)'/%3E%3C/g%3E%3C/svg%3E"),
    /* steam */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='120'%3E%3Cg fill='none' stroke='%23ffffff' stroke-opacity='.07' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M150 100c-8-14 8-20 0-34s8-20 0-34'/%3E%3Cpath d='M170 104c-8-14 8-20 0-34s8-20 0-34'/%3E%3C/g%3E%3C/svg%3E") right -10px top -6px no-repeat,
    radial-gradient(200px 150px at 88% 8%, rgba(207,31,31,.20), transparent 70%);
}
.brand-band.censa .bb-edge { height: 3px; background: linear-gradient(90deg, #cf1f1f, rgba(207,31,31,0)); }
body[data-brand="censa"] .cat-heading {
  text-transform: uppercase; letter-spacing: 2.4px; font-size: 12px; font-weight: 800;
  font-family: var(--font-body); color: #3a2a22;
  display: flex; align-items: center; gap: 10px;
}
body[data-brand="censa"] .cat-heading::before {
  content: ''; width: 7px; height: 7px; flex: none; border-radius: 50%; background: #cf1f1f;
}
body[data-brand="censa"] .cat-heading::after {
  content: ''; flex: 1; height: 1px; background: rgba(58,42,34,.18);
}
body[data-brand="censa"] .item-card {
  border-radius: 12px; border-color: #e6dcd4; background: #fffdfb;
  box-shadow: 0 1px 2px rgba(30,20,16,.05), 0 8px 20px rgba(30,20,16,.06);
}
body[data-brand="censa"] .item-visual {
  border-radius: 8px;
  background: linear-gradient(160deg, #efe6df, #e6dad1);
}
body[data-brand="censa"] .item-body { position: relative; }
body[data-brand="censa"] .item-badge { background: #16100d; color: #ffd9d0; border-radius: 4px; letter-spacing: .6px; }
body[data-brand="censa"] .add-btn { border-radius: 9px; }
body[data-brand="censa"] .cat-tab { border-radius: 7px; }
body[data-brand="censa"] .cart-bar { border-radius: 10px; }

/* ============ personalised messages ============ */
.msg-backdrop {
  position: fixed; inset: 0; z-index: 60; display: none;
  background: rgba(24,16,10,.55); backdrop-filter: blur(3px);
  align-items: center; justify-content: center; padding: 22px;
}
.msg-backdrop.show { display: flex; animation: fadein .2s ease; }
.msg-card {
  position: relative; width: 100%; max-width: 380px; text-align: center;
  background: var(--card); border-radius: 26px; padding: 30px 24px 24px;
  box-shadow: var(--shadow-lg); animation: rise .3s cubic-bezier(.2,.7,.2,1);
}
.msg-card.kind-tier, .msg-card.kind-reward, .msg-card.kind-almost {
  background: linear-gradient(165deg, #241c17, #3a2a1a);
  color: #f6ead4; border: 1px solid #4a3722;
}
.msg-card.kind-tier p, .msg-card.kind-reward p, .msg-card.kind-almost p { color: #cdb794; }
.msg-close {
  position: absolute; top: 12px; right: 14px; width: 32px; height: 32px; border-radius: 50%;
  font-size: 15px; color: var(--muted); background: rgba(0,0,0,.06);
}
.msg-card.kind-tier .msg-close, .msg-card.kind-reward .msg-close, .msg-card.kind-almost .msg-close {
  color: #cdb794; background: rgba(255,255,255,.08);
}
.msg-art { display: grid; place-items: center; margin-bottom: 14px; }
.msg-art img { width: 66px; height: 66px; filter: drop-shadow(0 5px 14px rgba(0,0,0,.4)); }
.msg-art .ic { width: 40px; height: 40px; color: var(--lux); }
.msg-card h2 { font-family: var(--font-display); font-size: 23px; font-weight: 800; line-height: 1.2; }
.msg-card p { font-size: 14px; color: var(--muted); margin-top: 9px; line-height: 1.5; }
.msg-code {
  display: inline-block; margin-top: 14px; font-weight: 800; letter-spacing: 2px; font-size: 14px;
  border: 1.5px dashed var(--lux); color: var(--lux); border-radius: 11px; padding: 9px 16px;
}
.msg-card .primary-btn { margin-top: 20px; }
.msg-more {
  display: block; width: 100%; margin-top: 10px; font-size: 13px; font-weight: 700;
  color: var(--muted); text-decoration: underline; text-underline-offset: 3px; padding: 6px;
}
.msg-card.kind-tier .msg-more, .msg-card.kind-reward .msg-more, .msg-card.kind-almost .msg-more { color: #e0c894; }

/* inbox on the account screen */
.msg-inbox h3 { margin-bottom: 10px; }
.mi-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 11px 0; border-bottom: 1px solid var(--line);
}
.mi-row:last-child { border-bottom: none; }
.mi-ico { width: 34px; height: 34px; flex: none; display: grid; place-items: center; }
.mi-ico img { width: 32px; height: 32px; }
.mi-ico .ic { width: 19px; height: 19px; color: var(--lux); }
.mi-mid { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mi-mid b { font-size: 13.5px; font-weight: 800; }
.mi-mid small {
  font-size: 12px; color: var(--muted); font-weight: 600; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.mi-code { font-size: 11.5px; font-weight: 800; letter-spacing: 1px; color: var(--lux); border: 1.5px dashed var(--lux); border-radius: 8px; padding: 5px 8px; white-space: nowrap; }
.mi-go { color: var(--muted); font-size: 20px; }

/* ============================================================
   BRAND SCENERY
   A drawn backdrop for each brand: a big line illustration at
   the top of the page and a scattered motif behind everything.
   Both are plain background layers, so scrolling stays smooth.
   ============================================================ */
body[data-brand="coldstone"] {
  background:
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='420'%20height='520'%20fill='none'%20stroke='%239e1b32'%20stroke-opacity='.10'%20stroke-width='7'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%20%3Cpath%20d='M150%20300l60%20170%2060-170'/%3E%20%3Cpath%20d='M150%20300h120'/%3E%20%3Cpath%20d='M168%20336l84%200M182%20372l56%200'/%3E%20%3Ccircle%20cx='210'%20cy='262'%20r='46'/%3E%3Ccircle%20cx='166'%20cy='232'%20r='40'/%3E%3Ccircle%20cx='254'%20cy='232'%20r='40'/%3E%20%3Ccircle%20cx='210'%20cy='196'%20r='42'/%3E%20%3Cpath%20d='M210%20154v-26M210%20128l-12%2012M210%20128l12%2012'/%3E%20%3C/svg%3E") no-repeat right -90px top 90px / 400px auto,
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='150'%20height='150'%3E%20%3Cg%20stroke='%239e1b32'%20stroke-opacity='.13'%20stroke-width='4'%20stroke-linecap='round'%3E%20%3Cpath%20d='M22%2026l10-6'/%3E%3Cpath%20d='M96%2052l9%207'/%3E%3Cpath%20d='M56%2096l-8%208'/%3E%3Cpath%20d='M124%20112l6-10'/%3E%20%3Cpath%20d='M38%20128l10%203'/%3E%3Cpath%20d='M110%2020l4%2011'/%3E%20%3C/g%3E%20%3Cg%20fill='none'%20stroke='%239e1b32'%20stroke-opacity='.09'%20stroke-width='2.6'%20stroke-linejoin='round'%3E%20%3Cpath%20d='M70%2044l7%2020%207-20z'/%3E%3Ccircle%20cx='77'%20cy='40'%20r='7'/%3E%20%3C/g%3E%20%3C/svg%3E") repeat 0 0 / 150px 150px,
    radial-gradient(1000px 460px at 50% -180px, #fdeef1 0%, transparent 72%) no-repeat,
    linear-gradient(180deg, #fcf5f3 0%, #f6ece9 100%) no-repeat;
}
body[data-brand="waccao"] {
  background:
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='440'%20height='520'%20fill='none'%20stroke='%236f4518'%20stroke-opacity='.10'%20stroke-width='7'%20stroke-linejoin='round'%3E%20%3Crect%20x='120'%20y='250'%20width='210'%20height='58'%20rx='16'/%3E%20%3Crect%20x='104'%20y='190'%20width='240'%20height='62'%20rx='18'/%3E%20%3Cpath%20d='M104%20220h240M164%20190v62M224%20190v62M284%20190v62'/%3E%20%3Cpath%20d='M120%20280h210M170%20250v58M230%20250v58M280%20250v58'/%3E%20%3Cpath%20d='M196%20172h56v-22h-56z'/%3E%20%3Cpath%20d='M150%20320c0%2022%2014%2026%2014%2044a14%2014%200%200%201-28%200c0-18%2014-22%2014-44z'/%3E%20%3C/svg%3E") no-repeat right -100px top 80px / 420px auto,
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='140'%20height='140'%3E%20%3Cg%20fill='none'%20stroke='%236f4518'%20stroke-opacity='.10'%20stroke-width='3'%3E%20%3Crect%20x='16'%20y='18'%20width='34'%20height='34'%20rx='7'/%3E%3Cpath%20d='M33%2018v34M16%2035h34'/%3E%20%3Crect%20x='88'%20y='84'%20width='30'%20height='30'%20rx='6'/%3E%3Cpath%20d='M103%2084v30M88%2099h30'/%3E%20%3C/g%3E%20%3Cg%20fill='none'%20stroke='%236f4518'%20stroke-opacity='.09'%20stroke-width='2.6'%3E%20%3Cpath%20d='M98%2026c0%209%206%2011%206%2019a6%206%200%200%201-12%200c0-8%206-10%206-19z'/%3E%20%3Cpath%20d='M34%20100c0%208%205%2010%205%2017a5%205%200%200%201-10%200c0-7%205-9%205-17z'/%3E%20%3C/g%3E%20%3C/svg%3E") repeat 0 0 / 140px 140px,
    radial-gradient(1000px 460px at 50% -180px, #fff3cf 0%, transparent 72%) no-repeat,
    linear-gradient(180deg, #fffbee 0%, #f7efd9 100%) no-repeat;
}
body[data-brand="censa"] {
  background:
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='420'%20height='520'%20fill='none'%20stroke='%23231814'%20stroke-opacity='.09'%20stroke-width='7'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%20%3Cpath%20d='M148%20250h124l-16%20190a18%2018%200%200%201-18%2016h-56a18%2018%200%200%201-18-16z'/%3E%20%3Crect%20x='138'%20y='222'%20width='144'%20height='34'%20rx='10'/%3E%20%3Cpath%20d='M160%20306h100'/%3E%20%3Cpath%20d='M186%20190c-14-20%2012-28%200-48s12-28%200-48'/%3E%20%3Cpath%20d='M234%20190c-14-20%2012-28%200-48s12-28%200-48'/%3E%20%3C/svg%3E") no-repeat right -80px top 86px / 380px auto,
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='150'%20height='150'%3E%20%3Cg%20fill='none'%20stroke='%23231814'%20stroke-opacity='.10'%20stroke-width='2.8'%3E%20%3Cellipse%20cx='34'%20cy='40'%20rx='15'%20ry='9.5'%20transform='rotate(-28%2034%2040)'/%3E%20%3Cpath%20d='M22%2043c5-6%2016-7%2024-4'/%3E%20%3Cellipse%20cx='112'%20cy='96'%20rx='15'%20ry='9.5'%20transform='rotate(16%20112%2096)'/%3E%20%3Cpath%20d='M100%2095c5-6%2016-6%2024-3'/%3E%20%3Cellipse%20cx='70'%20cy='132'%20rx='13'%20ry='8'%20transform='rotate(-6%2070%20132)'/%3E%20%3Cpath%20d='M59%20132c5-5%2014-5%2021-2'/%3E%20%3C/g%3E%20%3C/svg%3E") repeat 0 0 / 150px 150px,
    radial-gradient(1000px 460px at 50% -180px, #f6e9e6 0%, transparent 72%) no-repeat,
    linear-gradient(180deg, #f8f3ef 0%, #efe8e2 100%) no-repeat;
}

/* jumping to a dish from the homepage rail — a brief ring so you see where you landed */
.item-card.flash { animation: card-flash 1.4s ease; }
@keyframes card-flash {
  0%, 100% { box-shadow: var(--shadow-sm); }
  25%, 65% { box-shadow: 0 0 0 3px var(--accent), var(--shadow-md); }
}
/* design A hides the number by default so B and the old layout stay clean */
.poster-no { display: none; }


/* ============================================================================
   APP HOME — product-forward, card-based, with a floating tab bar.
   Cool light-grey canvas, generous corner radii, one high-energy accent (lime)
   reserved for actions and prices. Brand colour lives on the brand cards only.
   Every art box is a photo slot: set .bc-photo / .pc-photo src and unhide it.
   ========================================================================== */
:root {
  --app-bg: #eef0ea;
  --app-card: #ffffff;
  --lime: #d6ef4a;
  --lime-ink: #1b2007;
  --r-card: 22px;
  --r-pill: 999px;
}
body { background: var(--app-bg); }
#view-home { padding-bottom: 116px; }   /* room for the floating tab bar */

/* ---- top row ---- */
.app-top {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: -18px -18px 0; padding: calc(env(safe-area-inset-top, 0px) + 14px) 18px 14px;
  /* Solid ground, no blur. The frosted-glass blur read as smudgy over food
     photography; a clean opaque header is sharper and calmer. */
  background: var(--app-bg);
  transition: box-shadow .25s ease;
}
.app-top.stuck { box-shadow: 0 10px 24px -20px rgba(20,24,10,.55); }
/* The home screen reserved the status bar in its own padding-top and this bar
   reserved it again in its padding — the inset was counted twice and the header
   sat a status bar's height too low. The screen gives its top padding up
   entirely; the bar is the top of that screen and draws the gap itself.
   (A negative margin cannot do this job: the bar is sticky, and a sticky box is
   clamped inside its container's content box, so it springs back down.) */
#view-home { padding-top: 0; }
.app-top, .home-top { margin-top: 0; }
.at-user { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 4px 8px 4px 4px; border-radius: var(--r-pill); }
/* the chevron is the only thing telling people the balance is a door */
.at-chev { width: 15px; height: 15px; flex: none; color: #9aa38c; margin-left: -2px; }
.at-user:active .at-chev { color: var(--ink); }
.at-user:active { background: rgba(20,24,10,.05); }
.at-coin { width: 38px; height: 38px; flex: none; }
.at-user-txt { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2; min-width: 0; }
.at-user-txt small { font-size: 11px; font-weight: 600; color: #7c8470; }
.at-user-txt b { font-size: 14.5px; font-weight: 800; color: var(--ink); white-space: nowrap; }
.at-actions { display: flex; align-items: center; gap: 8px; }
.at-btn {
  position: relative; width: 42px; height: 42px; border-radius: 15px;
  display: grid; place-items: center; background: var(--app-card); color: var(--ink);
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.at-btn svg { width: 19px; height: 19px; }
.at-lang { font-size: 12px; font-weight: 800; letter-spacing: .04em; }
.at-btn:active { transform: scale(.94); }
.at-dot {
  position: absolute; top: 9px; right: 9px; width: 8px; height: 8px; border-radius: 999px;
  background: #ff4d3d; box-shadow: 0 0 0 2.5px var(--app-card);
}

/* ---- headline + search ---- */
.app-h1 {
  font-family: var(--font-body); font-weight: 800;
  font-size: clamp(27px, 7.6vw, 34px); line-height: 1.12; letter-spacing: -.03em;
  margin: 22px 0 18px; color: var(--ink);
}
.searchbar {
  display: flex; align-items: center; gap: 11px;
  background: var(--app-card); border-radius: var(--r-pill); padding: 0 16px; height: 52px;
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.searchbar .sb-ico { width: 19px; height: 19px; color: #8d9585; flex: none; }
.searchbar input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-size: 14.5px; font-weight: 500; color: var(--ink);
}
.searchbar input::placeholder { color: #9aa292; font-weight: 500; }
.searchbar input::-webkit-search-cancel-button { display: none; }
.sb-clear { width: 26px; height: 26px; border-radius: 999px; background: #eef0ea; color: #6f7767; font-size: 17px; line-height: 1; flex: none; }

.q-results { margin-top: 12px; display: grid; gap: 8px; }
.q-empty { padding: 22px 4px; color: var(--muted); font-size: 13.5px; }

/* ---- section heads ---- */
.sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 26px 0 13px; }
.sec-head h2 { font-size: 17px; font-weight: 800; letter-spacing: -.02em; color: var(--ink); }
.sec-head .sec-more { font-size: 12.5px; font-weight: 700; color: #7c8470; }

/* ---- StarzClub card ---- */
.pass-card {
  display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
  margin-top: 20px; padding: 15px 17px; border-radius: var(--r-card);
  background: linear-gradient(118deg, #1e2412 0%, #2c3418 58%, #3c4a1c 100%);
  color: #f2f6e2; box-shadow: 0 10px 24px -18px rgba(20,24,10,.9);
}
.pass-card .sp-coin { width: 40px; height: 40px; flex: none; }
.pass-card .sp-txt { flex: 1; min-width: 0; font-size: 13.5px; line-height: 1.35; }
.pass-card .sp-txt b { font-weight: 800; }
.pass-card .sp-go {
  width: 30px; height: 30px; border-radius: 999px; flex: none;
  display: grid; place-items: center; background: var(--lime); color: var(--lime-ink);
  font-size: 15px; font-weight: 800;
}
.pass-card:active { transform: scale(.99); }

/* ---- brand cards ----
   The card IS the logo — no caption, because every one of these marks already
   carries its own name. A product photo shrunk to 110px reads as brown mush;
   a logo is recognised instantly. The name lives on in `alt` for screen
   readers, which is the only place it was still doing work. */
.brand-rail { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.brand-card {
  position: relative; display: block; min-width: 0;
  aspect-ratio: 3 / 4; border-radius: 20px; overflow: hidden;
  background: var(--bc-tint, #f1f3ec);
  box-shadow: 0 6px 16px -10px rgba(20,24,10,.5);
}
.brand-card:active { transform: scale(.97); }
.bc-photo { display: block; width: 100%; height: 100%; object-fit: cover; }
/* each tint matches its tile, so the rounded corners never show a seam */
.brand-card.coldstone { --bc-tint: #fcebed; }
.brand-card.waccao    { --bc-tint: #fdc22a; }
.brand-card.censa     { --bc-tint: #f4ebe4; }

/* ---- product cards ----
   The photograph is the card: it fills the whole panel, a bottom scrim keeps
   the name and price legible over it, and the + sits in the bottom corner.
   Items with no photo fall back to a tinted panel with a big emoji.        */
.pc-rail {
  display: flex; gap: 13px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin: 0 -18px; padding: 10px 18px 22px; scroll-padding-inline: 18px;
  scrollbar-width: none; overscroll-behavior-x: contain;
}
.pc-rail::-webkit-scrollbar { display: none; }
.pc {
  /* a <button> centres its own content, so the card must be an explicit column */
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-end;
  position: relative; flex: 0 0 170px; scroll-snap-align: start; text-align: left;
  height: 214px; padding: 13px 13px 13px;
  border-radius: 22px;
  background: var(--pc-tint); color: #15120e;
  overflow: hidden;
  transition: transform .18s ease;
}
.pc:active { transform: scale(.975); }

/* the photo fills the panel edge to edge, behind everything */
.pc-art {
  position: absolute; inset: 0; z-index: 0; display: grid; place-items: center;
  font-size: 74px; line-height: 1; pointer-events: none;
}
.pc-art img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* with no photo the emoji sits above the text instead of behind it */
.pc:not(.has-photo) .pc-art { inset: 24px 0 76px 0; }

/* scrim: only on photo cards, so emoji cards keep their clean tint */
.pc.has-photo::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to top,
    rgba(10,7,4,.90) 0%, rgba(10,7,4,.68) 26%, rgba(10,7,4,.22) 55%, rgba(10,7,4,0) 82%);
}

.pc-txt { display: block; position: relative; z-index: 2; }
.pc-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  font-size: 16px; font-weight: 800; line-height: 1.12; letter-spacing: -.02em;
  overflow-wrap: anywhere; hyphens: none;
}
.pc-meta, .pc-price { display: block; line-height: 1.4; }
.pc-meta {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: rgba(21,18,14,.5); margin-bottom: 4px; order: -1;
}
.pc-price { font-size: 13px; font-weight: 700; margin-top: 3px; color: rgba(21,18,14,.66); }
/* meta sits above the name, so the text block is a column */
.pc-txt { display: flex; flex-direction: column; }

/* over a photograph the type turns white and picks up a soft shadow */
.pc.has-photo { color: #fff; }
.pc.has-photo .pc-name { text-shadow: 0 1px 14px rgba(0,0,0,.55); }
.pc.has-photo .pc-meta { color: rgba(255,255,255,.72); }
.pc.has-photo .pc-price { color: #fff; text-shadow: 0 1px 12px rgba(0,0,0,.5); }

/* the + lives in the top corner so the name below it gets the full width */
.pc-add {
  position: absolute; right: 10px; top: 10px; z-index: 3;
  width: 37px; height: 37px; border-radius: 50%; display: grid; place-items: center;
  background: var(--pc-pop); color: #fff;
  box-shadow: 0 8px 20px -8px rgba(10,7,4,.6);
}
.pc-add svg { width: 20px; height: 20px; }

/* warm, saturated grounds in the spirit of the reference, one per house */
.pc[data-brand="coldstone"] { --pc-tint: #f9d9d5; --pc-pop: #ec7159; }
.pc[data-brand="waccao"]    { --pc-tint: #fbe3c4; --pc-pop: #ef8a2c; }
.pc[data-brand="censa"]     { --pc-tint: #f6ded2; --pc-pop: #e2704a; }

/* ---- wide list rows (search results + today's picks) ---- */
.pl {
  display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
  background: var(--app-card); border-radius: 18px; padding: 9px 13px 9px 9px;
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.pl:active { transform: scale(.99); }
.pl-art {
  position: relative; width: 62px; height: 62px; flex: none; border-radius: 14px; overflow: hidden;
  background: var(--pc-tint, #f1f3ec); display: grid; place-items: center; font-size: 27px;
}
.pl-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pl-mid { flex: 1; min-width: 0; display: block; }
.pl-brand { display: block; font-size: 9.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: #97a08c; }
.pl-name { display: block; font-size: 14px; font-weight: 750; color: var(--ink); margin-top: 3px; line-height: 1.3; overflow-wrap: anywhere; }
.pl-price { display: block; font-size: 13px; font-weight: 800; color: var(--ink); margin-top: 4px; }
.pl-go {
  width: 32px; height: 32px; border-radius: 999px; flex: none; display: grid; place-items: center;
  background: var(--lime); color: var(--lime-ink); font-size: 18px; font-weight: 700; line-height: 1;
}

/* ---- sold out ----
   The item keeps its place on the shelf but stops asking to be tapped: the
   photo drains, the price stays (people still want to know), and one flat
   label does the talking. Nothing here should look like an error. */
.sold-out { opacity: .74; }
.sold-out .pc-art, .sold-out .pl-art, .sold-out .item-visual { filter: grayscale(.85); opacity: .72; }
.item-card.sold-out { cursor: default; }
.oos-flag {
  position: absolute; top: 10px; left: 10px; z-index: 3;
  padding: 4px 9px; border-radius: 999px; background: rgba(26,23,16,.82); color: #fff;
  font-size: 10px; font-weight: 800; letter-spacing: .02em; text-transform: uppercase;
}
.oos-tag {
  padding: 6px 10px; border-radius: 999px; background: #eceade; color: #6e6a5e;
  font-size: 10.5px; font-weight: 800; white-space: nowrap; flex: none;
}
/* on a narrow card the price and the label share the row rather than clipping */
.item-card.sold-out .item-foot { flex-wrap: wrap; row-gap: 6px; }
.item-card.sold-out .item-foot .price { font-size: 14px; }
/* a "Bestseller" ribbon on something you cannot buy just reads as a tease */
.item-card.sold-out .item-badge { display: none; }
.pl-oos { margin-left: 8px; font-size: 11px; font-weight: 800; color: #8a857e; }
.branch-warn {
  margin-top: 8px; padding: 10px 13px; border-radius: 12px;
  background: #fdf3e2; color: #8a5a12; font-size: 12.5px; font-weight: 700; line-height: 1.4;
}
#view-item.sold-out .ip-art, #view-item.sold-out #ip-art { filter: grayscale(.85); opacity: .75; }
#view-item.sold-out .ip-qty, #view-item.sold-out .ip-add-ico,
#view-item.sold-out .ip-note-field, #view-item.sold-out #opt-groups { display: none; }
#view-item.sold-out .ip-add { background: #e6e3d8; color: #6e6a5e; box-shadow: none; justify-content: center; }

/* ---- store line ---- */
.store-line {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 30px; padding: 15px 17px; border-radius: 18px;
  background: var(--app-card); font-size: 12px; font-weight: 600; color: #7c8470;
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.store-line .hours-pill { background: none; border: 0; box-shadow: none; padding: 0; font-size: 12px; }

/* ---- the tab bar, docked ---- */
/* Sits on the bottom edge and meets it, the way a phone's own navigation does.
   It floated before, which left a strip of page sliding along underneath and
   made the bar read as one more card rather than as the frame of the app. */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 45; display: none; align-items: stretch; gap: 4px;
  max-width: none; margin: 0;
  /* Anchored at bottom: 0, so the bar's surface always reaches the physical
     edge whatever the inset is. What the padding controls is where the labels
     sit inside it — and reserving the WHOLE inset pushed them a system
     navigation bar's height up the screen, which is what kept reading as the
     footer sitting too high. A share of the inset is enough to clear a gesture
     pill; the rest of that space is the bar's own surface, not empty air. */
  /* As low as the glass allows. iPhone reports ~34px here for the home
     indicator; reserving a third of it still floated the labels visibly above
     the edge, so this keeps only enough to stop the indicator sitting on the
     text. */
  padding: 6px 10px max(4px, calc(env(safe-area-inset-bottom, 0px) * 0.16));
  /* Exactly --sb, which is also what the phone paints its own navigation strip
     with. Android tints that strip from the theme-colour meta tag, and the web
     page cannot draw into it — so the only way to make the bar reach the very
     bottom of the screen is for the strip below it to be the same colour. Match
     them and the two read as one bar running off the bottom edge. */
  background: var(--sb, #faf7f2);
  border-radius: 0;
  -webkit-backdrop-filter: saturate(180%) blur(18px); backdrop-filter: saturate(180%) blur(18px);
  box-shadow: 0 -1px 0 rgba(20,24,10,.06);
}
body.show-tabs .tabbar { display: flex; }
/* and the screen keeps its last line clear of it */
body.show-tabs .view.active { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 92px); }
.tb {
  position: relative; flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 7px 4px; border-radius: 18px; color: #7e8674;
  font-size: 10px; font-weight: 700; letter-spacing: .01em;
  transition: background .18s ease, color .18s ease;
}
.tb svg { width: 20px; height: 20px; }
.tb.active { background: var(--ink); color: #fff; }
.tb:active { transform: scale(.96); }
.tb-dot {
  position: absolute; top: 7px; right: 50%; margin-right: -16px;
  width: 7px; height: 7px; border-radius: 999px; background: #ff4d3d;
}

/* the promo banner, restyled as an offer card */
#view-home #home-promo .promo-banner {
  border: 0; border-radius: var(--r-card); margin: 14px 0 0; padding: 15px 17px;
  background: linear-gradient(112deg, #1e2412, #38431c);
  color: #eef4dc; box-shadow: 0 10px 24px -18px rgba(20,24,10,.9);
}
#view-home #home-promo .pb-ico { color: var(--lime); }
#view-home #home-promo .pb-title { font-size: 13.5px; font-weight: 800; }
#view-home #home-promo .pb-msg { font-size: 12px; opacity: .82; }
#view-home #home-promo .pb-code {
  background: var(--lime); color: var(--lime-ink); border: 0; border-radius: var(--r-pill);
  padding: 7px 12px; font-size: 11px; font-weight: 800; letter-spacing: .06em; white-space: nowrap;
}

@media (min-width: 720px) {
  .brand-rail { gap: 14px; }
  .pc-rail { margin-inline: 0; padding-inline: 2px; }
  #view-home { padding-bottom: 130px; }
}

/* ============================================================================
   PRODUCT SCREEN
   Name and price share the top line, then an oversized piece of product art on
   a soft tinted ground with two floating pills. Options, a note, related items,
   and a lime add-to-bag bar pinned to the bottom.
   #ip-art is a photo slot — a cut-out PNG on transparent looks best here.
   ========================================================================== */
#view-item { padding-bottom: 132px; }
/* The product page keeps the brand's scenery. Stripping it back to the plain
   app canvas was what made walking from a burgundy menu into a grey-green
   product page feel like leaving the shop — the two screens are the same
   visit, so they get the same ground. Only ::before (the status-bar strip)
   is suppressed, because the photo header owns the top edge here. */

.ip-top { display: flex; align-items: center; justify-content: space-between; padding-top: 2px; }
.ip-icon {
  width: 42px; height: 42px; border-radius: 15px; display: grid; place-items: center;
  background: var(--app-card); color: var(--ink); box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.ip-icon svg { width: 19px; height: 19px; }
.ip-icon:active { transform: scale(.94); }
.ip-icon.on { background: var(--ink); color: #fff; }

.ip-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-top: 22px; }
#ip-name {
  flex: 1; min-width: 0;
  font-size: clamp(27px, 7.4vw, 34px); font-weight: 800; line-height: 1.08;
  letter-spacing: -.035em; color: var(--ink);
}
.ip-price {
  flex: none; font-size: 19px; font-weight: 800; letter-spacing: -.02em; color: var(--ink);
  white-space: nowrap; padding-top: 4px;
}
.ip-cat {
  display: none;
  font-size: 10.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: #8b9382;
}

/* the hero: art much bigger than its box, so it reads as a cut-out */
.ip-hero {
  position: relative; margin-top: 18px; border-radius: 30px;
  aspect-ratio: 1 / .82; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 18%, #fff 0%, var(--ip-tint, #f1f3ec) 72%);
  display: grid; place-items: center;
}
.ip-art { display: grid; place-items: center; width: 100%; height: 100%; font-size: clamp(120px, 40vw, 190px); line-height: 1; }
/* A photograph fills the whole hero; an emoji or cut-out floats in the middle. */
.ip-art img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.ip-hero:has(img) { background: none; }
.ip-pill {
  position: absolute; z-index: 2; border-radius: var(--r-pill);
  padding: 8px 14px; font-size: 11px; font-weight: 800; letter-spacing: .02em;
  max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ip-pill.dark { left: 16px; top: 16px; background: rgba(23,27,15,.9); color: #f2f6e2; -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.ip-pill.lime { right: 16px; bottom: 16px; background: var(--lime); color: var(--lime-ink); }

.ip-desc { margin-top: 18px; font-size: 14px; line-height: 1.55; color: #6f7767; }

/* option groups, restyled as cards to match the rest */
#view-item .opt-group { margin-top: 24px; }
#view-item .og-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 11px; }
#view-item .og-name { font-size: 16px; font-weight: 800; letter-spacing: -.02em; color: var(--ink); }
#view-item .og-rule {
  font-size: 10.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: #97a08c; background: none; border-radius: 0; padding: 0;
}
#view-item .og-rule.req { color: var(--lime-ink); background: var(--lime); border-radius: var(--r-pill); padding: 4px 10px; }
#view-item .og-choices { display: grid; gap: 8px; }
#view-item .oc {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--app-card); border-radius: 16px; padding: 13px 15px;
  box-shadow: 0 1px 2px rgba(20,24,10,.05); border: 2px solid transparent;
}
#view-item .oc.on { border-color: var(--ink); }
#view-item .oc.off { opacity: .42; }
#view-item .oc-box {
  width: 22px; height: 22px; flex: none; border-radius: 7px; border: 2px solid #dbe0d3;
  display: grid; place-items: center; color: #fff;
}
#view-item .oc-box.radio { border-radius: 999px; }
#view-item .oc.on .oc-box { background: var(--ink); border-color: var(--ink); }
#view-item .oc-box svg { width: 12px; height: 12px; }
#view-item .oc-name { flex: 1; min-width: 0; font-size: 14px; font-weight: 650; color: var(--ink); }
#view-item .oc-price { font-size: 13px; font-weight: 800; color: #6f7767; }

.ip-note-field { margin-top: 26px; }
.ip-note-field label { display: block; font-size: 12.5px; font-weight: 700; color: #7c8470; margin-bottom: 8px; }
.ip-note-field input {
  width: 100%; height: 50px; border: 0; border-radius: 16px; padding: 0 16px;
  background: var(--app-card); font-size: 14px; color: var(--ink);
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.ip-note-field input:focus { outline: 2px solid var(--ink); outline-offset: -2px; }

#ip-more .pl { margin-bottom: 8px; }

/* pinned add-to-bag bar */
.ip-bar {
  position: fixed; left: 14px; right: 14px; bottom: calc(env(safe-area-inset-bottom, 0px) + 4px);
  z-index: 46; display: none; align-items: center; gap: 10px;
  max-width: 532px; margin: 0 auto;
}
body.show-item-bar .ip-bar { display: flex; }
.ip-qty {
  display: flex; align-items: center; gap: 2px; flex: none; height: 58px; padding: 0 6px;
  background: var(--app-card); border-radius: var(--r-pill);
  box-shadow: 0 8px 26px -14px rgba(20,24,10,.5);
}
.ip-qty button { width: 36px; height: 40px; font-size: 19px; font-weight: 700; color: var(--ink); }
.ip-qty button:active { opacity: .5; }
.ip-qty span { min-width: 22px; text-align: center; font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; }
.ip-add {
  flex: 1; min-width: 0; height: 58px; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 0 8px 0 18px; border-radius: var(--r-pill);
  background: var(--lime); color: var(--lime-ink);
  font-size: 14px; font-weight: 800; letter-spacing: -.015em; white-space: nowrap;
  box-shadow: 0 10px 26px -12px rgba(120,150,20,.75);
}
.ip-add:active { transform: scale(.985); }
.ip-add:disabled { background: #dfe3d8; color: #939c8b; box-shadow: none; }
.ip-add-ico {
  width: 42px; height: 42px; flex: none; border-radius: 999px; display: grid; place-items: center;
  background: var(--ink); color: var(--lime);
}
.ip-add-ico svg { width: 18px; height: 18px; }
.ip-add:disabled .ip-add-ico { background: #c3c9bb; color: #eef0ea; }

/* item tints per brand, so the hero ground matches the house */
#view-item[data-tint="coldstone"] { --ip-tint: #fbe8ec; }
#view-item[data-tint="waccao"]    { --ip-tint: #fdf3d6; }
#view-item[data-tint="censa"]     { --ip-tint: #f5eae7; }

/* ============ CUSTOMER REVIEWS ============ */
/* Stars are drawn as five glyphs clipped to a fraction, so a 4.4 reads as
   four and a bit rather than rounding itself up to five. */
.stars { display: inline-flex; gap: 1px; line-height: 1; vertical-align: middle; }
.stars .st { color: #d9ddd0; font-size: 13px; }
.stars .st.full { color: #f0a63c; }
.stars .st.half { background: linear-gradient(90deg, #f0a63c 50%, #d9ddd0 50%); -webkit-background-clip: text; background-clip: text; color: transparent; }
.stars.big .st { font-size: 19px; }

/* product page: rating under the title */
.ip-stars {
  display: flex; align-items: center; gap: 7px; margin: 6px 0 0; padding: 0;
  background: none; font-size: 13px; color: var(--muted);
}
.ip-stars b { font-size: 14px; font-weight: 800; color: var(--ink); }

/* product page: the review list */
.rv-summary { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.rv-summary b { font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.rv-summary small { color: var(--muted); font-size: 12.5px; }
.rv-item {
  background: var(--app-card); border-radius: 16px; padding: 12px 14px; margin-bottom: 9px;
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.rv-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rv-who { font-size: 13px; font-weight: 800; }
.rv-text { font-size: 13.5px; line-height: 1.5; margin-top: 5px; color: #3f463a; }
.rv-reply {
  font-size: 12.5px; line-height: 1.5; margin-top: 8px; padding-left: 10px;
  border-left: 2px solid var(--lime); color: var(--muted);
}
.rv-reply b { color: var(--ink); font-weight: 800; }

/* card chips */
.pc-star { margin-left: 8px; font-weight: 700; }
.ic-stars { display: flex; align-items: center; gap: 5px; margin-top: 7px; }
.ic-stars small { font-size: 11px; color: var(--muted); font-weight: 600; }

/* "rate your order" call to action on the tracking screen */
.rate-cta {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  margin: 14px 0 0; padding: 13px 15px; border-radius: 18px;
  background: linear-gradient(118deg, #1e2412 0%, #2c3418 58%, #3c4a1c 100%);
  color: #f2f6e2; box-shadow: 0 10px 24px -18px rgba(20,24,10,.9);
}
.rate-cta .rc-txt { flex: 1; font-size: 13.5px; font-weight: 700; }
.rate-cta .rc-go { font-size: 19px; color: var(--lime); }
.rate-cta .stars .st { font-size: 15px; color: rgba(255,255,255,.28); }
.rate-cta .stars .st.full { color: var(--lime); }
.rate-cta:active { transform: scale(.99); }

/* the rating sheet */
.rv-backdrop {
  position: fixed; inset: 0; z-index: 90; display: none;
  align-items: flex-end; justify-content: center;
  background: rgba(16,20,10,.5); backdrop-filter: blur(2px);
}
.rv-backdrop.show { display: flex; }
.rv-sheet {
  width: 100%; max-width: 520px; max-height: 88vh; display: flex; flex-direction: column;
  background: var(--app-card); border-radius: 26px 26px 0 0; padding: 6px 18px 18px;
  animation: rv-up .26s cubic-bezier(.2,.8,.3,1);
}
@keyframes rv-up { from { transform: translateY(26px); opacity: 0; } }
.rv-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 0 4px; }
.rv-head h2 { font-family: var(--font-display); font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.rv-close { font-size: 26px; line-height: 1; color: var(--muted); padding: 0 4px; background: none; }
.rv-body { overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
.rv-sub { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
.rv-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
}
.rv-label { font-size: 13.5px; font-weight: 700; min-width: 0; }
.rv-label small { display: block; font-size: 11.5px; font-weight: 600; color: var(--muted); margin-top: 2px; }
.rv-pick { display: flex; gap: 2px; flex-shrink: 0; }
/* big tap targets — this is the one thing people do one-handed on a phone */
.rv-star {
  width: 34px; height: 34px; font-size: 23px; line-height: 1; background: none;
  color: #d9ddd0; transition: transform .12s ease, color .12s ease;
}
.rv-star.on { color: #f0a63c; }
.rv-star:active { transform: scale(1.2); }
.rv-body textarea {
  width: 100%; margin-top: 13px; padding: 12px 13px; border-radius: 14px;
  border: 1px solid var(--line); background: #f4f6f0; font: inherit; font-size: 14px; resize: none;
}
.rv-foot { padding-top: 12px; }
.rv-send {
  width: 100%; padding: 15px; border-radius: 999px; font-size: 15px; font-weight: 800;
  background: var(--lime); color: var(--lime-ink);
}
.rv-send:disabled { opacity: .6; }

.rv-toast {
  position: fixed; left: 50%; bottom: 96px; z-index: 120; transform: translate(-50%, 12px);
  background: #1e2412; color: #f2f6e2; padding: 12px 18px; border-radius: 999px;
  font-size: 13.5px; font-weight: 700; opacity: 0; transition: opacity .3s ease, transform .3s ease;
  box-shadow: 0 12px 30px -14px rgba(0,0,0,.8); pointer-events: none; max-width: 88vw; text-align: center;
}
.rv-toast.show { opacity: 1; transform: translate(-50%, 0); }

/* boot failure fallback — see the guard script in index.html */
#boot-fail {
  position: fixed; inset: 0; z-index: 999; display: grid; place-items: center;
  padding: 24px; background: var(--app-bg, #eef0ea);
}
#boot-fail[hidden] { display: none; }
#boot-fail .bf-card {
  max-width: 380px; text-align: center; background: #fff; border-radius: 22px;
  padding: 26px 22px; box-shadow: 0 18px 40px -24px rgba(20,24,10,.6);
}
#boot-fail h2 { font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin-bottom: 8px; }
#boot-fail p { font-size: 14px; line-height: 1.55; color: #5b6353; }
#boot-fail button {
  width: 100%; margin: 16px 0 12px; padding: 14px; border-radius: 999px;
  font-size: 15px; font-weight: 800; background: #d6ef4a; color: #1b2007;
}
#boot-fail .bf-small { font-size: 12.5px; color: #7c8470; }
#boot-fail .bf-err { margin-top: 10px; font-size: 11px; color: #9aa290; word-break: break-word; }

/* notification opt-in on the tracking screen */
.push-card {
  display: flex; align-items: center; gap: 12px; margin-top: 14px;
  background: var(--app-card); border-radius: 18px; padding: 14px 15px;
  box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.push-card .pk-ico { display: grid; place-items: center; width: 38px; height: 38px; flex: none; border-radius: 12px; background: #f2f6e2; color: #4a5a1d; }
.push-card .pk-ico svg { width: 20px; height: 20px; }
.push-card .pk-txt { flex: 1; min-width: 0; }
.push-card .pk-txt b { display: block; font-size: 13.5px; font-weight: 800; }
.push-card .pk-txt small { display: block; font-size: 12px; color: var(--muted); line-height: 1.45; margin-top: 2px; }
.push-card .pk-go {
  flex: none; padding: 10px 16px; border-radius: 999px; font-size: 13px; font-weight: 800;
  background: var(--lime); color: var(--lime-ink);
}
.push-card .pk-go:disabled { opacity: .6; }
.push-card.done { color: #2f6b3d; font-size: 13.5px; font-weight: 700; gap: 8px; }

/* branch picker */
.branch-row {
  display: flex; align-items: center; gap: 9px; margin: 12px 0 4px;
  background: var(--app-card); border-radius: 16px; padding: 11px 14px;
  font-size: 13.5px; box-shadow: 0 1px 2px rgba(20,24,10,.05);
}
.branch-row .ic { width: 17px; height: 17px; flex: none; color: var(--muted); }
.branch-row .br-lab { color: var(--muted); font-weight: 650; flex: none; }
.branch-row select {
  flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-weight: 800;
  color: var(--ink); padding: 2px 0; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 6px center, right 1px center; background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat; padding-right: 18px;
}
.branch-row.single span { font-weight: 650; color: var(--muted); }
.branch-row.single b { color: var(--ink); font-weight: 800; }
.branch-row.checkout { margin: 0 0 14px; }
.branch-row.checkout span { flex: 1; min-width: 0; }
.branch-row .br-change {
  flex: none; font-size: 12.5px; font-weight: 800; color: var(--ink);
  background: #f1f3ec; border-radius: 999px; padding: 6px 13px;
}

/* notification toggle in the account screen */
.nt-row { display: flex; align-items: center; gap: 14px; }
.nt-txt { flex: 1; min-width: 0; }
.nt-txt b { display: block; font-size: 14px; font-weight: 800; }
.nt-txt small { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.45; margin-top: 2px; }
.nt-note { font-size: 13px; color: var(--muted); line-height: 1.55; }
.nt-toggle {
  flex: none; width: 50px; height: 30px; border-radius: 999px; background: #d9ddd0;
  padding: 3px; transition: background .18s ease;
}
.nt-toggle span { display: block; width: 24px; height: 24px; border-radius: 50%; background: #fff; transition: transform .18s ease; box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.nt-toggle.on { background: var(--lime); }
.nt-toggle.on span { transform: translateX(20px); }
.nt-toggle:disabled { opacity: .55; }


/* ============================================================
   BRAND PAGE — edge to edge
   The banner is the top of the screen, not a card floating below
   one. It runs the full width and up behind the status bar, so on
   an installed phone the colour meets the clock instead of leaving
   a white strip. `.view` pads 18px, so the banner cancels exactly
   that much and adds it back inside.
   ============================================================ */
#view-menu { padding-top: 0; }
#view-menu .brand-band {
  margin: 0 -18px 14px;
  border-radius: 0 0 26px 26px;
  padding: calc(env(safe-area-inset-top, 0px) + 22px) 20px 26px;
}
/* each brand rounds its own corners differently; on this page only the
   bottom two exist, so they are re-stated here to win on specificity */
#view-menu .brand-band.coldstone { border-radius: 0 0 22px 22px; }
#view-menu .brand-band.waccao { border-radius: 0 0 22px 22px; }
#view-menu .brand-band.censa { border-radius: 0 0 14px 14px; }

/* ---- sticky category tabs ----
   Frosted and full-bleed. The old version was a bare strip: cards slid
   through it at the edges and the row read as a rendering fault rather
   than a control. Blurring what passes underneath is what makes it look
   deliberate over a patterned background. */
.cat-tabs {
  /* Stops below the clock, not under it. Pinning at top: 0 ran the pills and
     the frosted strip up behind the phone's own status text, which read as the
     page bleeding off the top of the screen. The inset is on `top` rather than
     as padding on purpose — padding would change the bar's height the moment
     it stuck and jog the whole list down by that much. */
  position: sticky; top: env(safe-area-inset-top, 0px); z-index: 30;
  margin: 0 -18px; padding: 11px 18px 10px;
  background: color-mix(in srgb, var(--soft, #f3f5ee) 78%, transparent);
  -webkit-backdrop-filter: saturate(150%) blur(16px);
  backdrop-filter: saturate(150%) blur(16px);
  box-shadow: 0 1px 0 rgba(20,24,10,.06);
}
/* the first category heading sits right under the sticky bar */
.cat-tabs + #menu-list .cat-heading:first-child { margin-top: 18px; }

/* ---- the strip behind the phone's own clock ----
   With a translucent status bar the page runs to the very top of the screen,
   which is what lets a brand header meet the clock. Every other screen paints
   that strip itself so the white system text always has something dark under
   it — a header that blends on one screen must not mean invisible text on
   the next. */
html { background: var(--sb, #faf7f2); transition: background .25s ease; }
body::before {
  content: ''; position: fixed; top: 0; left: 0; right: 0; z-index: 200;
  height: env(safe-area-inset-top, 0px);
  background: var(--sb, #faf7f2);
  pointer-events: none;
  transition: background .25s ease;
}
/* the brand banner and the delivery map are their own top edge */
body[data-view="menu"]::before, body[data-view="item"]::before, body[data-view="track"]::before { background: transparent; }
/* The tracking screen is data-view="order", not "track" — "track" is the list
   of your orders, which has an ordinary header. So the one screen in the app
   whose whole point is a full-bleed map was the one still wearing a solid strip
   across the top of it, and the map stopped at a cream band under the clock.
   The class is set from the same place that decides whether there IS a map, so
   the strip comes back the moment the screen falls back to its card. */
body.map-top::before {
  /* not transparent: the clock and the battery still have to be readable over
     whatever street the map happens to be showing. A short white fade does that
     without drawing an edge. */
  height: calc(env(safe-area-inset-top, 0px) + 22px);
  background: linear-gradient(to bottom, rgba(252,250,246,.92) 0%, rgba(252,250,246,.62) 55%, rgba(252,250,246,0) 100%);
}
/* ...until the banner has scrolled away. Then the clock needs its own backdrop
   again, in the colour the banner just left behind, or it sits on whatever card
   happens to be passing underneath. */
body[data-view="menu"].past-band::before { background: var(--sb, #faf7f2); }

/* everything that isn't drawing its own top edge keeps clear of the clock */
.view { padding-top: calc(18px + env(safe-area-inset-top, 0px)); }
#view-menu, #view-item { padding-top: 0; }

/* ============================================================
   PRODUCT PAGE — the same house as the brand page
   It used to be a grey-green page with lime buttons sitting inside
   a burgundy parlour, because it painted itself from the global
   palette instead of the brand's. Everything below reads from
   --accent / --soft, so a Cold Stone item looks like Cold Stone and
   a Waccao item looks like Waccao, with no per-brand code.
   ============================================================ */
#view-item { background: transparent; }

/* the photo is the header */
#view-item .ip-hero {
  position: relative; margin: 0 -18px 0; border-radius: 0;
  aspect-ratio: 1 / .84; max-height: 46vh; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 18%, #fff 0%, var(--ip-tint, #f1f3ec) 72%);
  display: grid; place-items: center;
}
#view-item .ip-art { border-radius: 0; }
#view-item .ip-art img { border-radius: 0; }
/* a scrim only at the very top, so the two round buttons stay readable over
   a pale photo without dimming the food itself */
.ip-hero-scrim {
  position: absolute; inset: 0 0 auto 0; height: 40%; z-index: 1;
  background: linear-gradient(180deg, rgba(20,16,12,.45), transparent);
  pointer-events: none;
}
#view-item .ip-icon {
  /* Clear of the clock, not level with it. The inset alone is not enough to
     trust: on some phones and in some browser modes it comes back as 0 even
     though the clock is still drawn over the photo, which is how this button
     ended up sitting on the same line as the time. The floor keeps it below
     the clock in that case, and the inset keeps it correct everywhere else. */
  position: absolute; z-index: 3;
  top: max(calc(env(safe-area-inset-top, 0px) + 26px), 58px);
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(255,255,255,.94); color: #1a1710;
  box-shadow: 0 4px 14px -6px rgba(0,0,0,.5);
}
#view-item #ip-back { left: 14px; }
#view-item #ip-fav { right: 14px; }
#view-item .ip-pill { z-index: 3; }
#view-item .ip-pill.dark { top: auto; bottom: 26px; left: 18px; background: rgba(20,16,12,.72); }
#view-item .ip-pill.lime { right: 18px; bottom: 26px; background: var(--accent); color: var(--accent-ink); }
/* the brand's own edge, reused: scallops for Cold Stone, and so on */
.ip-edge { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; }

/* name, price, category — the brand's display face, like the banner */
#view-item .ip-head { margin-top: 20px; align-items: flex-start; }
#view-item .ip-head h1 {
  font-family: var(--font-display); font-size: 25px; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.12; color: var(--ink); overflow-wrap: anywhere;
}
#view-item .ip-price {
  font-family: var(--font-display); font-size: 21px; font-weight: 700;
  color: var(--accent); white-space: nowrap;
}
#view-item .ip-cat {
  margin-top: 7px; font-size: 10.5px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: color-mix(in srgb, var(--accent) 62%, #8a857e);
}
#view-item .ip-desc { margin-top: 12px; color: #6f6a62; }

/* controls take the house colour instead of lime */
#view-item .og-rule.req { background: var(--accent); color: var(--accent-ink); }
#view-item .oc.on { border-color: var(--accent); }
#view-item .ip-add {
  background: var(--accent); color: var(--accent-ink);
  box-shadow: 0 10px 26px -12px color-mix(in srgb, var(--accent) 70%, transparent);
}
#view-item .ip-add-ico { background: rgba(255,255,255,.22); color: var(--accent-ink); }
#view-item .ip-add:disabled { background: #dfdbd3; color: #918c84; box-shadow: none; }
#view-item .ip-add:disabled .ip-add-ico { background: #cbc6bd; color: #f4f2ee; }
#view-item .pl-go { background: var(--accent); color: var(--accent-ink); }

/* the note field and section headings, in the same voice as the menu */
#view-item .ip-note-field label { color: color-mix(in srgb, var(--accent) 55%, #7c7770); }
#view-item .ip-note-field input:focus { outline-color: var(--accent); }
#view-item .sec-head h2 {
  font-family: var(--font-display); font-size: 19px; font-weight: 700; letter-spacing: -.02em;
}

/* the same edge the banner uses, under the product photo, so the two headers
   are recognisably the same object in the same house */
body[data-brand="coldstone"] .ip-edge {
  height: 13px;
  background: radial-gradient(circle at 9px -2px, #fcf5f3 9px, transparent 9.5px) repeat-x 0 0 / 18px 13px;
}
body[data-brand="waccao"] .ip-edge {
  height: 10px;
  background: repeating-linear-gradient(90deg, #4a2c10 0 14px, transparent 14px 28px);
  opacity: .22;
}
body[data-brand="censa"] .ip-edge { height: 3px; background: linear-gradient(90deg, #cf1f1f, rgba(207,31,31,0)); }

/* ============================================================
   TRACKING — the same house as everything else
   Two states from one layout. Before the rider leaves there is
   nothing moving, so `.no-map` collapses the map away and the
   card simply sits at the top of the page. Once it is on the way
   the map appears behind the same card. The old version was a
   black sheet borrowed from another app; this one is the brand's
   own paper, colour and display face.
   ============================================================ */
.dm {
  position: relative; margin: 0 -18px 0;
  height: 58vh; min-height: 380px; max-height: 520px; overflow: hidden;
  background: var(--soft, #f1f3ec);
}
.dm-map, .dm-map-fallback { position: absolute; inset: 0; }
.dm-map-fallback { display: none; background: var(--soft, #f1f3ec); }
.dm-map .leaflet-tile-pane { filter: grayscale(.7) contrast(.9) brightness(1.07); }
.dm-map .leaflet-container { background: var(--soft, #f1f3ec); }
/* Google's own map: the palette is set in the style array so the markers stay
   the brightest thing on screen. Its footer links are left tappable — the terms
   require them — but pulled clear of the recentre button. */
.dm-map .gm-style { background: var(--soft, #f1f3ec); }
.dm-map .gmnoprint, .dm-map .gm-style-cc { opacity: .55; }
.dm-map .gm-style > div > a { display: block; }
/* HTML markers: Google positions the box, we centre it on the point */
.dm-gm-marker { position: absolute; transform: translate(-50%, -50%); will-change: transform; }

/* the bike's ring — a still emoji on a busy map reads as decoration */
.dm-bike-ring {
  position: absolute; left: 50%; top: 50%; width: 46px; height: 46px; margin: -23px 0 0 -23px;
  border-radius: 50%; background: var(--accent, #1a1710); opacity: .3;
  animation: dmPulse 2.6s cubic-bezier(.22, .61, .36, 1) infinite; pointer-events: none;
}
/* A radar ping, not a throb: it leaves fast, thins as it goes, and the pause
   before the next one is most of the cycle. `ease-out` on a loop with no rest
   made it read as a pulsing blob. */
@keyframes dmPulse {
  0%   { transform: scale(.62); opacity: .34; }
  55%  { transform: scale(1.7);  opacity: 0; }
  100% { transform: scale(1.7);  opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .dm-bike-ring { animation: none; opacity: .2; } }

/* why there is no bike on the map yet */
.dm-note {
  position: absolute; z-index: 615; left: 50%; transform: translateX(-50%);
  bottom: 16px; max-width: calc(100% - 108px);
  padding: 8px 14px; border-radius: 999px;
  background: rgba(255,255,255,.94); box-shadow: 0 4px 14px -6px rgba(0,0,0,.4);
  font-size: 12px; font-weight: 700; color: #6f6a62;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dm-note[hidden] { display: none; }

/* put the whole journey back in frame */
.dm-recentre {
  position: absolute; z-index: 618; right: 14px; bottom: 16px;
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(255,255,255,.94); color: #1a1710;
  box-shadow: 0 4px 14px -6px rgba(0,0,0,.4);
  opacity: 0; transform: scale(.8); pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.dm-recentre svg { width: 19px; height: 19px; }
.dm-recentre[hidden] { display: none; }
.dm-recentre.show { opacity: 1; transform: none; pointer-events: auto; }

.dm-topbar {
  position: absolute; z-index: 620; left: 14px; right: 14px;
  top: max(calc(env(safe-area-inset-top, 0px) + 12px), 54px);
  display: flex; align-items: center; gap: 10px;
}
.dm-round {
  width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: rgba(255,255,255,.94); color: #1a1710; font-size: 23px; line-height: 1;
  box-shadow: 0 4px 14px -6px rgba(0,0,0,.4);
}
.dm-round svg { width: 17px; height: 17px; }
.dm-round.back { padding-bottom: 3px; }
/* the whole status, in one line, between the two buttons */
.dm-pill {
  flex: 1; min-width: 0; display: flex; align-items: baseline; justify-content: center; gap: 7px;
  height: 40px; padding: 0 14px; border-radius: 999px;
  background: rgba(255,255,255,.94); box-shadow: 0 4px 14px -6px rgba(0,0,0,.4);
  align-items: center; overflow: hidden;
}
.dm-pill b {
  font-family: var(--font-display); font-size: 14.5px; font-weight: 700; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dm-pill span {
  font-size: 12px; font-weight: 800; color: var(--accent-ink); white-space: nowrap; flex: none;
  background: var(--accent); border-radius: 999px; padding: 4px 9px;
}
.dm-pill b:empty + span, .dm-pill span:empty { display: none; }
/* With nothing to say the pill hides — but `display:none` collapsed the row and
   slid Back and Share together into the top-left corner, which looks like a
   layout that broke rather than a pill that had no content. It keeps its space
   and gives up only its ink. */
/* the big card is the no-map presentation only */
.dm:not(.no-map) .dm-card { display: none; }
.dm.no-map .dm-topbar { display: none; }

.dm-card {
  position: absolute; z-index: 610; left: 14px; right: 14px;
  top: max(calc(env(safe-area-inset-top, 0px) + 64px), 106px);
  background: var(--app-card, #fff); border-radius: 24px;
  padding: 16px 18px 17px; text-align: center;
  box-shadow: 0 18px 44px -22px rgba(20,24,10,.5);
}
.dm-art { display: block; font-size: 30px; line-height: 1; margin-bottom: 6px; }
.dm-art svg { width: 30px; height: 30px; color: var(--accent, #1a1710); }
.dm-date {
  font-size: 10.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: color-mix(in srgb, var(--accent, #7c8470) 55%, #8a857e);
}
.dm-card h1 {
  font-family: var(--font-display); font-size: 21px; font-weight: 700;
  letter-spacing: -.02em; color: var(--ink); margin-top: 3px; line-height: 1.15;
}
.dm-eta {
  font-family: var(--font-display); font-size: 36px; font-weight: 700;
  letter-spacing: -.03em; line-height: 1.05; margin-top: 8px; color: var(--accent, #1a1710);
}
.dm-eta-lab {
  font-size: 10.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: #8a857e; margin-top: 2px;
}
.dm-sub { font-size: 12.5px; font-weight: 650; color: #6f6a62; margin-top: 6px; }

/* ---- no map yet: the same card, on the page ---- */
.dm.no-map {
  height: auto; min-height: 0; max-height: none; overflow: visible;
  background: none; margin-bottom: 14px;
}
.dm.no-map .dm-map, .dm.no-map .dm-map-fallback, .dm.no-map .dm-round,
.dm.no-map .dm-note, .dm.no-map .dm-recentre { display: none; }
.dm.no-map .dm-card {
  position: static; margin: calc(env(safe-area-inset-top, 0px) + 16px) 4px 0;
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(20,24,10,.05));
}

/* markers */
.dm-bike-wrap, .dm-dot-wrap { background: none; border: 0; }
.dm-bike {
  display: grid; place-items: center; width: 46px; height: 46px; border-radius: 50%;
  background: #fff; font-size: 23px; box-shadow: 0 6px 16px -6px rgba(0,0,0,.5);
}
.dm-dot { display: block; width: 15px; height: 15px; border-radius: 50%; box-shadow: 0 0 0 3px #fff; }
.dm-dot.dest { background: var(--accent, #1a1710); }
.dm-dot.shop { background: #1a1710; }

/* ---- the status card ---- */
.tk-card {
  background: var(--app-card, #fff); border-radius: 24px; padding: 18px 18px 16px;
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(20,24,10,.05)); margin-top: 14px;
}
.tk-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.tk-state { font-size: 14.5px; font-weight: 750; color: var(--ink); min-width: 0; }
.tk-code { font-size: 11px; font-weight: 700; color: #9a948b; white-space: nowrap; }
.tk-code b { font-weight: 800; color: var(--ink); letter-spacing: .05em; }

.dm-bar { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; }
.dm-seg { height: 6px; border-radius: 999px; background: #e6e2da; transition: background .3s ease; }
.dm-seg.done { background: var(--accent, #1a1710); }
.dm-seg.now { background: linear-gradient(90deg, var(--accent, #1a1710) 55%, #e6e2da 55%); }

.dm-steps { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; margin-top: 9px; }
.dm-step {
  font-size: 10.5px; font-weight: 700; color: #a29c93; line-height: 1.3;
  overflow-wrap: anywhere; text-align: center;
}
.dm-step.done { color: #6f6a62; }
.dm-step.now { color: var(--accent, #1a1710); font-weight: 800; }

.dm-help { margin-top: 14px; font-size: 12.5px; font-weight: 650; color: #8a857e; text-align: center; }
.dm-help a { color: var(--accent, #1a1710); font-weight: 800; text-decoration: none; }

/* cancelled turns the accent to a warning, without re-theming the page */
#view-order.is-cancelled { --accent: #b4442f; }

/* the map is the top of the screen, so it cancels the view's own top padding */
.dm:not(.no-map) { margin-top: calc((18px + env(safe-area-inset-top, 0px)) * -1); }
.dm.no-map .dm-card { margin-top: 4px; }

/* the tracking screen wears the order's brand, so its buttons do too */
#view-order .pk-go { background: var(--accent); color: var(--accent-ink); }
#view-order .pk-ico { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
#view-order .ta-btn.call { color: var(--accent); }
#view-order .summary-card h3, #view-order .rate-card h3 { font-family: var(--font-display); font-weight: 700; }

/* ---- screens push on and pop off ----
   A tab switch fades up in place; opening a product or an order slides in
   from the right and slides back out the same way. Native easing, short
   enough that it never feels like waiting. */
/* `backwards`, not `both`: a finished animation that still holds a transform
   leaves the element as a containing block, which quietly breaks any
   position:fixed child inside it — that is what stopped the pinned product
   header from pinning.

   The curve matters more than the distance. Sliding a long way and fading hard
   is what read as rough: the eye tracks two things changing at different rates
   over too short a time. A shorter travel on a decelerating curve, with the
   fade finishing early, reads as one movement. `translate3d` keeps it on the
   compositor so a long menu behind it does not judder. */
.view.is-push { animation: screen-in .34s cubic-bezier(.16,.84,.28,1) backwards; will-change: transform, opacity; }
.view.is-pop { animation: screen-out .24s cubic-bezier(.36,0,.66,-.05) forwards; will-change: transform, opacity; }
@keyframes screen-in {
  from { transform: translate3d(7%, 0, 0); opacity: 0; }
  40% { opacity: 1; }
  to { transform: translate3d(0, 0, 0); opacity: 1; }
}
@keyframes screen-out {
  from { transform: translate3d(0, 0, 0); opacity: 1; }
  60% { opacity: .5; }
  to { transform: translate3d(9%, 0, 0); opacity: 0; }
}
/* The fade-up on a plain tab switch. It animates the SCROLL CONTAINER, which
   promotes it to its own layer and re-composites it — and the cost lands on the
   first flick after a tab switch, which is precisely the moment the scroll is
   being judged. It runs on native (where the push/pop transitions own the
   motion anyway and this is a fallback for tab switches) and it is deliberately
   short; what it must not do is still be running while a finger is down. */
.view.active { animation: rise .22s cubic-bezier(.16,.84,.28,1); }
/* ...and the moment a finger touches the screen it is over. A gesture that
   starts mid-animation is the one that feels sticky. */
html.dragging .view.active { animation: none !important; }
@media (prefers-reduced-motion: reduce) {
  .view.is-push, .view.is-pop, .view.active { animation: none; }
}

/* the empty notifications screen */
.nt-empty {
  display: grid; place-items: center; gap: 8px; text-align: center;
  padding: 60px 24px; color: #8a857e;
}
.nt-empty svg { width: 30px; height: 30px; color: #b8b2a8; }
.nt-empty b { font-size: 16px; font-weight: 800; color: var(--ink); }
.nt-empty span { font-size: 13px; font-weight: 600; max-width: 240px; line-height: 1.5; }
#notif-list .msg-inbox { padding-top: 6px; }

/* ---- the product name, pinned ----
   Scroll past the photo on a long product and the screen stops telling you
   what it is about. This slides down at exactly the moment the real title
   leaves, carrying the name, the price and the way back. */
.ip-pinned {
  position: fixed; top: 0; left: 0; right: 0; z-index: 120;
  display: flex; align-items: center; gap: 12px;
  padding: calc(env(safe-area-inset-top, 0px) + 9px) 14px 11px;
  background: color-mix(in srgb, var(--soft, #f4f5ef) 86%, transparent);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  backdrop-filter: saturate(160%) blur(18px);
  box-shadow: 0 1px 0 rgba(20,24,10,.07);
  transform: translateY(-102%); opacity: 0;
  /* visibility, not just opacity: slid off the top the bar still occupies a box,
     so its back button stayed a live tap target across the top of the screen and
     screen readers still announced it. visibility is delayed to the end of the
     transition on the way out, so the slide is still seen. */
  visibility: hidden;
  transition: transform .22s cubic-bezier(.22,.68,.24,1), opacity .18s ease,
              visibility 0s linear .22s;
}
.ip-pinned.show {
  transform: none; opacity: 1; visibility: visible;
  transition: transform .22s cubic-bezier(.22,.68,.24,1), opacity .18s ease,
              visibility 0s linear 0s;
}
.ip-pin-back {
  width: 34px; height: 34px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--app-card, #fff); color: var(--ink); font-size: 21px; line-height: 1;
  padding-bottom: 3px; box-shadow: 0 1px 3px rgba(20,24,10,.12);
  position: relative;
}
/* The circle stays 34px because the pinned bar is short and a 44px disc in it
   looks like a button that outgrew its bar. The TARGET is 44 either way: an
   invisible square hung off the middle of it, which is what a thumb actually
   hits. Growing the element itself was the other option and it pushed the
   title sideways by ten pixels every time the bar appeared. */
.ip-pin-back::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.ip-pin-name {
  flex: 1; min-width: 0; font-family: var(--font-display); font-size: 16px; font-weight: 700;
  letter-spacing: -.02em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ip-pin-price {
  flex: none; font-family: var(--font-display); font-size: 16px; font-weight: 700; color: var(--accent);
}

/* ============================================================
   TRACKING — one screen, no scrolling
   "Where is my food" is a glance, not a document. The map (or the
   status card in its place) takes the space that is left, the
   status card is pinned under it, and everything that used to
   make this page long now lives behind Order details.
   ============================================================ */
/* `.active` is load-bearing here, not decoration. `.view` hides a screen with a
   plain class selector; this rule carries an id, so its `display: flex` outranks
   that and the order screen stayed on screen after you had navigated away — the
   tracking map showing through the home screen, two screens drawn over each
   other. The pinned layout only ever applies to the screen that is up. */
#view-order.pinned.active {
  /* the one screen that does not scroll at all */
  padding-bottom: 0; overflow: hidden;
  display: flex; flex-direction: column;
}
#view-order.pinned.active #track-result { display: flex !important; flex-direction: column; flex: 1; min-height: 0; }
/* the screen is pinned before the order has arrived, so hold the empty body
   back until there is something in it */
#view-order.pinned.loading #track-result { display: none !important; }
#view-order.pinned.active .dm { flex: 1; min-height: 0; height: auto; max-height: none; }
#view-order.pinned.active .dm.no-map { flex: 0 0 auto; }
#view-order.pinned.active .tk-card {
  flex: none; margin: 0 -18px; border-radius: 26px 26px 0 0;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 84px);
  box-shadow: 0 -10px 30px -18px rgba(20,24,10,.4);
}
/* when there is no map the card floats normally and the space above it is the page */
#view-order.pinned.active:has(.dm.no-map) { justify-content: flex-start; }
#view-order.pinned.active:has(.dm.no-map) #track-result { flex: 1; }
#view-order.pinned.active:has(.dm.no-map) .dm { flex: 0 0 auto; }
#view-order.pinned.active:has(.dm.no-map) .tk-card { margin-top: auto; }

.tk-acts { display: flex; gap: 8px; margin-top: 14px; }
.tk-act {
  flex: 1; min-width: 0; text-align: center; text-decoration: none;
  padding: 13px 12px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 13.5px; font-weight: 800;
}
.tk-act.call { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }

/* the details sheet */
.tk-sheet-back {
  position: fixed; inset: 0; z-index: 300; background: rgba(20,16,12,.42);
  opacity: 0; transition: opacity .22s ease;
  display: flex; align-items: flex-end;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
/* display:flex above would otherwise beat the hidden attribute */
.tk-sheet-back[hidden] { display: none; }
.tk-sheet-back.show { opacity: 1; }
.tk-sheet {
  width: 100%; max-width: 560px; margin: 0 auto;
  max-height: 82svh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--app-bg, #eef0ea); border-radius: 26px 26px 0 0;
  padding: 10px 16px calc(env(safe-area-inset-bottom, 0px) + 22px);
  transform: translateY(100%); transition: transform .28s cubic-bezier(.22,.68,.24,1);
}
.tk-sheet-back.show .tk-sheet { transform: none; }
.tk-grab { display: block; width: 42px; height: 4px; border-radius: 999px; background: #d4cfc6; margin: 4px auto 12px; }
.tk-sheet .summary-card { margin-top: 0; }

/* the answerable bits, between the two cards; it scrolls on its own only if
   several of them land at once, so the screen itself never does */
/* capped, so a long prompt never squeezes the map down to nothing on a small
   phone — it scrolls inside itself instead */
.tk-mid { flex: 0 1 auto; min-height: 0; max-height: 32%; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.tk-mid:empty, .tk-mid > *:empty { margin: 0; }
#view-order.pinned .tk-mid > * { margin-top: 12px; }

/* ---- app behaviour, not page behaviour ----
   No scrollbar track down the side of a phone screen, no double-tap zoom, no
   long-press callout on a photo, and no text selection dragging out of a card
   when you meant to scroll. Inputs keep their selection, obviously. */
* { -webkit-tap-highlight-color: transparent; }
html { scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }
.tk-sheet::-webkit-scrollbar, .tk-mid::-webkit-scrollbar,
.cat-tabs::-webkit-scrollbar, .pc-rail::-webkit-scrollbar,
.featured-row::-webkit-scrollbar, .og-items::-webkit-scrollbar { width: 0; height: 0; display: none; }
.tk-sheet, .tk-mid, .cat-tabs, .pc-rail, .featured-row, .og-items { scrollbar-width: none; }
body {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;          /* kills the 300ms double-tap-to-zoom */
}
input, textarea, [contenteditable] { -webkit-user-select: text; user-select: text; }
/* the map is the one place a pinch should still do something */
.dm-map, .leaflet-container { touch-action: auto; }

/* saved items */
.saved-list { display: grid; gap: 8px; }
.saved-list .pl { background: #f7f8f3; }
.saved-list .hint { margin: 0; }
.ip-icon.on { background: var(--accent); color: var(--accent-ink); }
.ip-icon.on svg { fill: currentColor; }

/* ---- the product bottom bar ----
   The stepper and the button were two different heights on their own baselines,
   so the count sat a few pixels below the thing it belonged to. One height,
   one row, stretched to match. */
.ip-bar { align-items: stretch; }
.ip-qty, .ip-add { height: 56px; margin-top: 0; }


/* ---- what the ride costs ----
   The fee stops being a fixed number the moment it depends on distance, so it
   has to explain itself where the customer meets it. */
.fee-note:empty { display: none; }
.fee-note {
  display: flex; align-items: flex-start; gap: 7px;
  margin-top: 10px; padding: 10px 12px; border-radius: 12px;
  background: var(--soft); border: 1px solid var(--line);
  font-size: 12.5px; font-weight: 650; color: var(--muted); line-height: 1.45;
}
.fee-note .ic { width: 14px; height: 14px; flex: none; margin-top: 2px; }
.fee-note.bad {
  background: #fdf0ef; border-color: #f0cfcb; color: #a8342a; font-weight: 700;
}


/* ---- brands sharing this address ----
   The moment to learn you can add a coffee is while you are choosing the
   waffle, not at checkout when the bag is already closed. */
#also-here:empty { display: none; }
#also-here { margin: 12px 0 2px; }
.ah-lab {
  font-size: 10.5px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 7px;
}
.ah-row { display: flex; gap: 8px; flex-wrap: wrap; }
.ah-chip {
  display: inline-flex; align-items: center; gap: 8px;
  background: #fff; border: 1.5px solid var(--line); border-radius: 999px;
  padding: 6px 13px 6px 6px; font-size: 13px; font-weight: 750;
  box-shadow: var(--shadow-sm); transition: border-color .13s ease;
}
@media (hover: hover) { .ah-chip:hover { border-color: var(--accent); } }
.ah-chip img { width: 26px; height: 26px; border-radius: 50%; object-fit: contain; background: #fff; flex: none; }
.ah-chip i { font-style: normal; font-weight: 800; }
.ah-chip .ah-add { color: var(--accent); font-size: 15px; line-height: 1; }
.ah-chip .ah-in { color: #1a8a4a; }

/* a heading per shop, once a bag holds more than one */
.cart-brand {
  display: flex; align-items: center; gap: 8px;
  margin: 14px 0 4px; font-size: 11px; font-weight: 800;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
}
.cart-brand:first-child { margin-top: 2px; }
.cart-brand img { width: 20px; height: 20px; border-radius: 50%; object-fit: contain; background: #fff; }


/* ============ FIRST RUN ============
   Three steps, once. Same furniture as the sign-in screen it follows, so the
   flow reads as one thing rather than three borrowed screens. */
/* Same trap, same fix — see the note on .signin-wrap. The address step is the
   tallest screen in the app on a small phone and was the worst affected. */
#view-onboard { display: none; }
#view-onboard.active { display: block; }
.ob-wrap {
  width: 100%; max-width: 560px; margin: 0 auto;
  min-height: 100%;
  display: flex; flex-direction: column; justify-content: center; padding: 14px 0 34px;
}
.ob-dots { display: flex; gap: 6px; justify-content: center; margin-bottom: 18px; }
.ob-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--line);
  transition: all .2s ease;
}
.ob-dot.on { width: 22px; border-radius: 999px; background: var(--accent); }
.ob-step {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 26px 22px 22px; box-shadow: var(--shadow-md);
}
.ob-logo { display: block; width: 150px; max-width: 56%; height: auto; margin: 0 auto 16px; }
.ob-step h2 {
  font-family: var(--font-display); font-size: 22px; font-weight: 800;
  line-height: 1.2; text-align: center;
}
.ob-sub { font-size: 13.5px; color: var(--muted); margin: 8px 0 18px; text-align: center; line-height: 1.45; }

/* the location button — the fast path, so it gets the weight */
.ob-locate {
  width: 100%; display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 14px; border-radius: 15px; margin-bottom: 4px;
  background: var(--soft); border: 1.5px solid var(--line);
  transition: all .14s ease;
}
@media (hover: hover) { .ob-locate:hover { border-color: var(--accent); } }
.ob-locate.set { background: #f1fbf4; border-color: #bfe6cd; }
.ob-loc-ic { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 12px;
  background: #fff; border: 1px solid var(--line); color: var(--accent); flex: none; }
.ob-loc-ic .ic { width: 18px; height: 18px; }
.ob-locate.set .ob-loc-ic { color: #1a8a4a; border-color: #bfe6cd; }
.ob-loc-txt { min-width: 0; }
.ob-loc-txt b { display: block; font-size: 14px; font-weight: 800; }
/* Two lines is fine here — truncating the reason to "an ex…" helps nobody. */
.ob-loc-txt small { display: block; font-size: 11.5px; font-weight: 650; color: var(--muted);
  margin-top: 2px; line-height: 1.35; overflow-wrap: anywhere; }

.ob-or {
  display: flex; align-items: center; gap: 10px; margin: 16px 0 12px;
  font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
}
.ob-or::before, .ob-or::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.ob-skip {
  width: 100%; margin-top: 12px; font-size: 12.5px; font-weight: 700; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px; padding: 6px;
}

/* language: two cards, not a dropdown — it is the last thing before the food */
.ob-langs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 4px 0 18px; }
.ob-lang {
  padding: 18px 12px; border-radius: 15px; text-align: center;
  background: #fff; border: 1.5px solid var(--line); transition: all .14s ease;
}
.ob-lang b { display: block; font-size: 15.5px; font-weight: 800; }
.ob-lang small { display: block; font-size: 11.5px; font-weight: 650; color: var(--muted); margin-top: 3px; }
@media (hover: hover) { .ob-lang:hover { border-color: var(--accent); } }
.ob-lang.on { border-color: var(--accent); background: var(--soft); box-shadow: 0 0 0 3px var(--soft); }

/* nothing else on the glass while the three steps run */
body.onboarding .tabbar, body.onboarding .cart-bar { display: none !important; }

@media (min-width: 900px) { .ob-step { max-width: 480px; margin: 0 auto; } }

/* Why the live map is not open yet. Only ever visible on a card with no map. */
.dm-why { display: none; }
.dm.no-map .dm-why:not([hidden]) {
  display: block; margin: 10px 0 0; padding: 11px 13px;
  background: var(--soft); border: 1px solid var(--line); border-radius: 13px;
  font-size: 12.8px; font-weight: 650; color: var(--muted); line-height: 1.45;
}


/* ============ SPECIAL DAYS ============
   A shop dresses its window for Christmas and the 20th of May. The app does the
   same, and no more than that: one accent colour and one line. A customer
   hunting for the waffle they always order must still find it where it was. */
.theme-strip[hidden] { display: none; }
.theme-strip {
  display: flex; align-items: center; gap: 9px;
  margin: 0 0 14px; padding: 11px 15px; border-radius: var(--r-md);
  background: var(--fest, var(--accent)); color: var(--fest-ink, #fff);
  font-size: 13.5px; font-weight: 800; letter-spacing: -.01em;
  box-shadow: var(--shadow-md);
}
.theme-strip .ts-mark { font-size: 17px; line-height: 1; }

/* Where the festive colour is allowed to show through. Buttons and prices keep
   the brand's own colours — the day is decoration, not a rebrand. */
body.themed .app-h1::after {
  content: ''; display: block; width: 46px; height: 3px; border-radius: 2px;
  margin-top: 10px; background: var(--fest, var(--accent));
}
body.themed .sec-head h2 { position: relative; }
body.themed .starpass-strip { border-color: var(--fest, #3a2a1c); }
body.themed .tb.active { color: var(--fest-ink, #fff); }

body[data-theme="christmas"] .theme-strip {
  background: linear-gradient(100deg, var(--fest, #b3242c), var(--fest-deep, #8d1a20));
}
/* The three national days wear the flag, in its own order and proportions. */
body[data-theme="national"] .theme-strip,
body[data-theme="youth"] .theme-strip,
body[data-theme="unification"] .theme-strip {
  background: linear-gradient(100deg, #1f7a45 0%, #1f7a45 33%, #c0392b 33%, #c0392b 66%, #e6b800 66%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.28);
}
body[data-theme="newyear"] .theme-strip {
  background: linear-gradient(100deg, #241a10 0%, #3a2a17 55%, var(--fest-deep, #8d6a24) 100%);
  color: #f6e6c4;
}
body[data-theme="valentines"] .theme-strip {
  background: linear-gradient(100deg, var(--fest, #d2476b), var(--fest-deep, #a52c4c));
}

/* ═══════════════════════════════════════════════════════════════════════════
   SPECIAL DAYS — how far the decoration is allowed to go
   ═══════════════════════════════════════════════════════════════════════════
   The rule the whole thing obeys: a customer opening the app to reorder the
   waffle they always order must find it in the same place, the same size, at
   the same price, in the same colour. So the day changes the GROUND and the
   DECORATION, and never the controls. Buttons stay gold, prices stay ink, the
   add button stays where it was. What changes is the wash the app sits on, the
   strip at the top, the rewards card, and — on two of the six days — something
   small moving behind the home screen.

   And none of it touches a shop's own page: inside Cold Stone the app is Cold
   Stone's, not the calendar's. Hence :not([data-brand]) on the ground rules. */

body.themed:not([data-brand]) { background: var(--fest-tint, var(--bg)); }
body.themed:not([data-brand]) .app-top,
body.themed:not([data-brand]) .home-top { background: var(--fest-tint, var(--bg)); }
/* the rule under the greeting picks up the day */
body.themed .app-h1::after { background: var(--fest, var(--accent)); }
/* the loyalty card is the one big surface worth dressing */
body.themed:not([data-brand]) .rew-card,
body.themed:not([data-brand]) .pts-card {
  background: linear-gradient(140deg,
    color-mix(in srgb, var(--fest, #c9a35a) 16%, #fff) 0%,
    color-mix(in srgb, var(--fest, #c9a35a) 30%, #fff) 100%);
}
/* the active tab, and nothing else in the bar */
body.themed .tb.active { color: var(--fest-deep, var(--fest, var(--gold-deep))); }
body.themed .tb.active svg { color: inherit; }

/* ── snow, and sparks ───────────────────────────────────────────────────────
   Behind the content, never over it, and never on a screen where someone is
   trying to do something. Sizes, speeds, drift and start times all vary per
   piece for the same reason the confetti does: identical particles moving in
   step read as a screensaver. */
/* THE MOTIF LIVES IN THE BODY, NOT IN THE VIEW, and that is not a preference.
   Putting it inside #view-home meant giving #view-home `position: relative` to
   contain it — and an ID beats `.view.active`, which is `position: fixed` and IS
   the app's scroll container. So the home screen quietly stopped being able to
   scroll at all: everything below the fold was simply unreachable. The sibling
   rule was as bad in a quieter way — `position: relative` on every direct child
   overrides `position: sticky`, which is what pins the home header.

   A body-level fixed layer at z-index 0 sits behind every .view (they are
   z-index 1) and in front of the page background, needs nothing from the view's
   own positioning, and cannot break the scroll model however it is styled. */

.motif-layer {
  position: fixed; inset: 0; z-index: 55;
  pointer-events: none; overflow: hidden;
  display: none;
}
/* and only on the home screen, on a day that has one */
body.themed[data-view="home"] .motif-layer { display: block; }
.motif {
  position: absolute; top: -14px;
  width: var(--s, 5px); height: var(--s, 5px); border-radius: 50%;
  opacity: var(--o, .5);
  animation: motif-fall var(--dur, 12s) linear var(--delay, 0s) infinite;
  will-change: transform;
}
/* Pure white snow on a cream page is invisible, which is a nice metaphor and a
   useless decoration. A cool blue-white with a soft white halo reads as snow
   against warm paper — the contrast has to come from temperature, not value. */
.motif.flake {
  background: #dfe8f2;
  box-shadow: 0 0 0 1px rgba(255,255,255,.9), 0 0 7px rgba(190,205,225,.75);
}
.motif.spark { background: var(--fest, #c9a35a); box-shadow: 0 0 6px color-mix(in srgb, var(--fest, #c9a35a) 60%, transparent); }
/* Cameroon flag confetti — little squares in the three flag colours, dealt in JS */
.motif.cmfetti {
  background: var(--c, #1f7a45); border-radius: 2px;
  box-shadow: 0 1px 3px rgba(20,24,10,.22);
  transform: rotate(var(--rot, 0deg));
  animation: cmfetti-sway var(--dur, 4.5s) ease-in-out var(--delay, 0s) infinite alternate;
}
@keyframes cmfetti-sway {
  from { transform: rotate(var(--rot, 0deg)) translateY(0); }
  to   { transform: rotate(calc(var(--rot, 0deg) + 8deg)) translateY(4px); }
}

/* ---------- the themed footer graphic ----------
   A decorative band that rides just above the tab bar on a themed day — the
   Cameroon flag wave, a Christmas garland, a New Year firework wash. It sits in
   the tab bar's own ::before so it is always pinned to the bottom with it, is
   never tappable, and disappears the instant the decoration is switched off. */
body.themed.show-tabs .tabbar::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: 58px; background-repeat: no-repeat; background-position: bottom center;
  background-size: 100% 100%; pointer-events: none;
}
body[data-theme="national"] .tabbar::before,
body[data-theme="youth"] .tabbar::before,
body[data-theme="unification"] .tabbar::before { background-image: url('/img/theme/cm-wave.svg'); }
body[data-theme="christmas"] .tabbar::before { background-image: url('/img/theme/xmas-wave.svg'); }
body[data-theme="newyear"] .tabbar::before { background-image: url('/img/theme/ny-wave.svg'); }
/* Give the last row of content clearance so the wave never sits on top of it */
body.themed.show-tabs .view.active { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 132px); }
@keyframes motif-fall {
  from { transform: translate3d(0, -14px, 0); }
  to   { transform: translate3d(var(--dx, 0px), 108vh, 0); }
}
/* New Year's sparks rise, they do not fall */
body[data-theme="newyear"] .motif { top: auto; bottom: -14px; }
body[data-theme="newyear"] .motif { animation-name: motif-rise; }
@keyframes motif-rise {
  from { transform: translate3d(0, 14px, 0); opacity: 0; }
  12%  { opacity: var(--o, .5); }
  to   { transform: translate3d(var(--dx, 0px), -108vh, 0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .motif-layer { display: none; } }
@media (prefers-reduced-motion: no-preference) {
  .theme-strip { animation: fest-in .45s cubic-bezier(.2,.7,.2,1); }
  @keyframes fest-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
}

/* ---------- Sign in with Apple ----------
   Shown only inside the iOS app. Apple's guidelines fix the shape of this
   button fairly tightly: their mark, their wording, a hit area no smaller than
   the primary action next to it. It sits under the phone field rather than
   above it, because the phone number is still the main route — Apple is the
   alternative, not the default. */
.apple-row { margin-top: 16px; display: grid; gap: 12px; justify-items: stretch; }
.apple-or {
  text-align: center; font-size: 13px; color: var(--muted, #8a7f72);
  position: relative; text-transform: lowercase;
}
.apple-or::before, .apple-or::after {
  content: ''; position: absolute; top: 50%; width: calc(50% - 26px); height: 1px;
  background: var(--line, #e8e0d4);
}
.apple-or::before { left: 0; }
.apple-or::after { right: 0; }
.apple-btn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; min-height: 52px; border: 0; border-radius: 14px;
  background: #000; color: #fff; font-size: 16.5px; font-weight: 600;
  font-family: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.apple-btn svg { width: 17px; height: 20px; margin-top: -2px; }
.apple-btn:active { opacity: .82; }
.apple-btn:disabled { opacity: .55; }
/* The error box doubles as a "here is what to do next" line during the Apple
   flow, where nothing has gone wrong — so it needs to not look like alarm. */
.error-msg.info { color: var(--ink, #2b2119); background: var(--field, #f8f5ef); }

/* ---------- can't reach the server ----------
   Deliberately not a red alarm. The most common cause is a customer walking
   into a lift, and the second most common is us — neither deserves a scare.
   It sits at the top of the home screen, keeps the retry within thumb reach,
   and disappears on its own when the signal comes back. */
/* `hidden` must win. An author rule that sets `display` on the same element
   beats the browser's own [hidden] { display: none }, so this box stayed on
   screen with its placeholder text no matter what the code said — on every
   screen, whether or not anything was wrong. That is the bug Krish saw as
   "why is it saying everywhere we can't reach starzclub". Same shape as the
   ghost-screen bug this app had before: a display rule quietly outranking the
   thing meant to hide the element. */
.conn-trouble {
  display: flex; align-items: center; gap: 14px;
  margin: 0 0 18px; padding: 15px 16px;
  background: var(--field, #f8f5ef); border: 1px solid var(--line, #e8e0d4);
  border-radius: 16px;
}
.conn-trouble > div { flex: 1; min-width: 0; }
.conn-trouble b { display: block; font-size: 15.5px; margin-bottom: 3px; }
.conn-trouble small { display: block; font-size: 13.5px; line-height: 1.45; color: var(--muted, #8a7f72); }
.conn-trouble button {
  flex: none; min-height: 44px; padding: 0 17px; border: 0; border-radius: 12px;
  background: var(--ink, #2b2119); color: #fff; font: inherit; font-size: 14.5px;
  font-weight: 600; cursor: pointer;
}
.conn-trouble button:disabled { opacity: .55; }

/* ═══════════════════════════════════════════════════════════════════════════
   NATIVE MOTION  —  packaged apps only (html.is-native)
   ═══════════════════════════════════════════════════════════════════════════
   The website keeps the restrained 7% slide above. In a browser a full-width
   push fights the back button and the browser's own swipe-back, and reads as a
   site trying too hard. Inside the app there is no browser chrome to argue
   with, and the full push is what people's hands already expect.

   Everything here is scoped to html.is-native so starzclub.net cannot change. */

/* The screen being left behind. It has to stay on stage — a push where the old
   screen simply vanishes is the single biggest tell that something is a web
   page. It keeps .view's own padding and centring; only its position, layer and
   interactivity change. overflow:hidden freezes its scroll mid-transition. */
html.is-native .view.leaving {
  display: block;
  position: fixed; inset: 0; z-index: 1;
  overflow: hidden;
  pointer-events: none;
  will-change: transform;
}
html.is-native .view.active { z-index: 3; }

/* THE INCOMING SCREEN HAS TO BE OPAQUE.
   A .view paints no background of its own — it has never needed one, because at
   rest it sits directly on the body and the body's colour is the app's ground.
   The moment two screens are on stage at once that stops being true: the new
   screen slides in transparent and you watch the old one through it for the
   whole 420ms, which reads as the page being drawn rather than a screen
   arriving. It was most obvious pushing from home, where a whole populated list
   showed through.

   `inherit` rather than a literal, so a screen sliding in on a special day
   carries that day's ground and not the ordinary cream. */
html.is-native .view.is-push,
html.is-native .view.is-pop,
html.is-native .view.leaving { background-color: inherit; }

/* The scrim between them. iOS darkens the outgoing screen as it recedes, which
   is what sells the depth — without it the two screens read as sliding on one
   flat plane. A separate fixed element rather than a filter or a pseudo, because
   filtering a long scrolled list costs a repaint of the whole subtree. */
#nav-scrim {
  position: fixed; inset: 0; z-index: 2;
  background: #000; opacity: 0; pointer-events: none;
  display: none;
}
html.is-native #nav-scrim.on { display: block; }

/* ---- push: new screen in from the right, old one recedes ---- */
html.is-native .view.is-push {
  animation: ios-push-in .42s cubic-bezier(.32,.72,0,1) backwards;
  box-shadow: -10px 0 30px rgba(20, 12, 4, .18);
}
html.is-native .view.leaving.push-out { animation: ios-push-out .42s cubic-bezier(.32,.72,0,1) forwards; }
html.is-native #nav-scrim.push { animation: scrim-in .42s cubic-bezier(.32,.72,0,1) forwards; }

@keyframes ios-push-in  { from { transform: translate3d(100%,0,0); } to { transform: translate3d(0,0,0); } }
@keyframes ios-push-out { from { transform: translate3d(0,0,0); }    to { transform: translate3d(-26%,0,0); } }
@keyframes scrim-in     { from { opacity: 0; } to { opacity: .14; } }

/* ---- pop: reverse, and a touch quicker, because going back should feel
        lighter than going forward ---- */
html.is-native .view.is-pop {
  animation: ios-pop-out .36s cubic-bezier(.32,.72,0,1) forwards;
  box-shadow: -10px 0 30px rgba(20, 12, 4, .18);
}
html.is-native .view.leaving.pop-in { animation: ios-pop-in .36s cubic-bezier(.32,.72,0,1) forwards; }
html.is-native #nav-scrim.pop { animation: scrim-out .36s cubic-bezier(.32,.72,0,1) forwards; }

@keyframes ios-pop-out { from { transform: translate3d(0,0,0); }     to { transform: translate3d(100%,0,0); } }
@keyframes ios-pop-in  { from { transform: translate3d(-26%,0,0); }  to { transform: translate3d(0,0,0); } }
@keyframes scrim-out   { from { opacity: .14; } to { opacity: 0; } }

/* A tab switch is not a push — tabs are places, not steps forward, so they
   cross-fade in place instead of sliding. */
html.is-native .view.active { animation: tab-in .26s cubic-bezier(.32,.72,0,1); }
@keyframes tab-in { from { opacity: 0; } to { opacity: 1; } }
html.is-native .view.is-push, html.is-native .view.is-pop { animation-name: ios-push-in; }
html.is-native .view.is-pop { animation-name: ios-pop-out; }

/* ---- the drag itself ----
   While a finger is down nothing is animated; the transform is written straight
   from the touch position each frame. Transitions would lag the finger. */
html.is-native .view.dragging,
html.is-native .view.leaving.dragging { animation: none !important; transition: none; }
html.is-native .view.settling,
html.is-native .view.leaving.settling { transition: transform .3s cubic-bezier(.32,.72,0,1); }
html.is-native #nav-scrim.settling { transition: opacity .3s cubic-bezier(.32,.72,0,1); }

@media (prefers-reduced-motion: reduce) {
  html.is-native .view.is-push, html.is-native .view.is-pop,
  html.is-native .view.leaving.push-out, html.is-native .view.leaving.pop-in,
  html.is-native #nav-scrim.push, html.is-native #nav-scrim.pop { animation: none; }
}

/* ───────── the system font, in the app only ─────────
   Fraunces stays: it is the brand, it carries the headings, and losing it would
   make all three shops look like everyone else. What changes is the body text —
   the paragraphs, labels, prices and buttons — which is where a phone's own
   typeface is doing quiet work most people never consciously notice. SF Pro
   sets tighter and hints better at small sizes on an iPhone than any webfont,
   Roboto does the same on Android, and the text stops looking half a shade
   foreign to the rest of the phone.

   It also means the app paints its text on the very first frame instead of
   waiting on a font download it may not have signal for. */
html.is-native {
  --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, system-ui, sans-serif;
}
html.is-android { --font-body: Roboto, system-ui, -apple-system, sans-serif; }

/* iOS sets its interface text a touch tighter than a webfont default. Without
   this the same words take more room and the layout drifts. */
html.is-ios body { letter-spacing: -.01em; }

/* ───────── the tab bar ─────────
   Frosted rather than solid: on iOS a bottom bar is expected to show a hint of
   what is scrolling beneath it. On the website it stays opaque, because a
   browser's own chrome sits under it and two layers of blur look muddy. */
html.is-native .tabbar {
  background: color-mix(in srgb, var(--sb, #faf7f2) 82%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  backdrop-filter: saturate(180%) blur(22px);
  border-top: .5px solid color-mix(in srgb, var(--line, #e8e0d4) 90%, transparent);
}
/* A hairline means one device pixel, not one CSS pixel — on a 3× screen a 1px
   border is three times heavier than the one iOS draws next to it. */
@media (-webkit-min-device-pixel-ratio: 2) {
  html.is-native .tabbar { border-top-width: .33px; }
}
/* The pressed state is the platform's: a quick dip in opacity, no ripple, no
   colour wash. */
html.is-native .tabbar .tab:active { opacity: .45; transition: opacity .04s; }
html.is-native .tabbar .tab { transition: opacity .18s; }

/* The grabber, made to look like the platform's own: a touch wider, softer, and
   with enough room above it that a thumb reaching for it does not catch the
   first row of the basket instead. */
html.is-native .sheet-handle { width: 36px; height: 5px; background: color-mix(in srgb, var(--muted, #8a7f72) 40%, transparent); margin: 6px auto 14px; }
html.is-native .sheet { padding-top: 8px; }
html.is-native .sheet-backdrop { transition: opacity .3s cubic-bezier(.32,.72,0,1); }

/* ───────── skeletons ─────────
   A spinner says "something is happening". A skeleton says "a menu is coming,
   and this is where the pictures and prices will be" — the eye starts parsing
   the layout before the data lands, so the wait feels roughly half as long even
   when it is exactly as long.

   The sweep is slow and low-contrast on purpose. A fast bright shimmer draws
   attention to the loading rather than away from it. */
.sk { position: relative; overflow: hidden; background: color-mix(in srgb, var(--line, #e8e0d4) 55%, transparent); border-radius: 12px; }
.sk::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, #fff 55%, transparent), transparent);
  animation: sk-sweep 1.5s cubic-bezier(.4,0,.6,1) infinite;
}
@keyframes sk-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }

.sk-menu { display: grid; gap: 12px; padding: 4px 0; }
.sk-row { display: flex; align-items: center; gap: 13px; }
.sk-thumb { width: 74px; height: 74px; border-radius: 16px; flex: none; }
.sk-lines { flex: 1; display: grid; gap: 8px; }
.sk-line { height: 12px; }
.sk-line.w70 { width: 70%; } .sk-line.w45 { width: 45%; } .sk-line.w30 { width: 30%; }

/* The home shelf's placeholder. The numbers are copied from .pc deliberately —
   218 wide, 278 tall, 26 radius — because the whole value of a skeleton is that
   nothing moves when the real thing arrives. A placeholder of roughly the right
   size is worse than none: it promises a layout and then changes it.
   Three cards, not four: only about two and a half fit on a phone, and the ones
   past the fold are animating off-screen for nobody. */
.sk-rail { pointer-events: none; }
.sk-card {
  flex: 0 0 170px; height: 214px; border-radius: 22px; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 8px; padding: 13px;
  background: color-mix(in srgb, var(--line, #e8e0d4) 40%, transparent);
}
.sk-card .sk-shot { flex: 1; margin: -16px -16px 7px; border-radius: 0; }
.sk-card .sk-line { height: 13px; }

/* ───────── pull to refresh ─────────
   Only inside the app. In a browser the platform already owns this gesture and
   two of them fighting is worse than neither. */
/* It has a fixed height and hangs ABOVE the list, pulled down by a transform.
   It used to be a zero-height box that grew — which meant every frame of the
   pull relaid out the whole list underneath it. */
.ptr {
  position: absolute; top: -46px; left: 0; right: 0; height: 46px;
  display: grid; place-items: center;
  pointer-events: none; z-index: 5;
  will-change: transform;
}
.ptr-spin {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--muted, #8a7f72) 35%, transparent);
  border-top-color: var(--accent, #c9a35a);
  opacity: 0;
}
.ptr.armed .ptr-spin { opacity: 1; }
.ptr.spinning .ptr-spin { animation: ptr-turn .7s linear infinite; opacity: 1; }
@keyframes ptr-turn { to { transform: rotate(360deg); } }

/* The saved-menu note. Deliberately quieter than the "we can't reach it at all"
   state: there is food on the screen, the app is working, and this is a caveat
   rather than a problem. */
.conn-trouble.soft { background: transparent; border-style: dashed; }
.conn-trouble.soft b { font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════════
   v4 · THE REFINEMENT PASS
   ═══════════════════════════════════════════════════════════════════════════
   Three things survive from before because they are the good parts: the product
   cards, the brand-patterned backgrounds, and the big photographs. Everything
   around them is rebuilt.

   What was wrong was not taste, it was DENSITY and STACKING.

   Density: the type was set for a laptop. A 41px headline, 16px labels and 26px
   of padding on every box meant a phone screen held about four facts. Native
   apps set interface text at 15px and titles at 20-22px, and the difference is
   not decoration — it is how much of the menu a customer can see at once.

   Stacking: nearly everything was a rounded white card floating on a coloured
   ground, and cards sat inside cards. Every nesting level costs two gutters and
   two radii, and the eye reads each border as a boundary worth checking. Apps
   put content on the surface and separate it with space and hairlines, keeping
   boxes for things that genuinely are objects — a product, an order.

   The rule behind every number below: nothing decorative may push food further
   down the screen.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* One scale, used everywhere. Sizes are the iOS text styles, which are what a
     thumb-held screen has been tuned around for fifteen years. */
  --t-xl: 30px;    /* the one big title on a screen */
  --t-lg: 21px;    /* section and screen titles */
  --t-md: 17px;    /* a product name */
  --t-bd: 15px;    /* body, buttons, inputs */
  --t-sm: 13.5px;  /* supporting text */
  --t-xs: 11.5px;  /* labels, eyebrows */

  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;
  --sp-4: 16px; --sp-5: 20px; --sp-6: 28px;

  --gutter: 16px;

  /* Softer, smaller radii. 24px on a full-width box reads as a web card; the
     platform uses 10-16px for most things and saves the big ones for photos. */
  --r-lg: 16px;
  --r-md: 12px;
  --r-sm: 9px;

  /* Shadows were doing the work borders should do. One quiet shadow, used
     sparingly, and hairlines everywhere else. */
  --shadow-sm: 0 1px 2px rgba(30, 23, 18, .04);
  --shadow-md: 0 2px 10px rgba(30, 23, 18, .07);
  --hair: color-mix(in srgb, var(--line) 75%, transparent);
}

.view { padding-left: var(--gutter); padding-right: var(--gutter); padding-bottom: 28px; }

/* ── the screen title ──────────────────────────────────────────────────────
   Was 41px and took a fifth of the screen before a single word of content. */
.app-h1 {
  font-size: var(--t-xl);
  line-height: 1.14;
  letter-spacing: -.02em;
  font-weight: 700;
  margin: var(--sp-4) 0 var(--sp-4);
}

/* ── search ───────────────────────────────────────────────────────────────
   A 68px white pill with a 17px placeholder was the second-largest object on
   the home screen. It is a control, not a feature. */
.searchbar {
  gap: var(--sp-2);
  padding: 0 var(--sp-3);
  height: 44px;
  border-radius: var(--r-md);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: none;
}
.searchbar input { font-size: var(--t-bd); }
.searchbar .ic { width: 17px; height: 17px; opacity: .5; }

/* ── section headings ─────────────────────────────────────────────────────
   Small, quiet, and close to what they introduce. A heading that shouts
   competes with the food underneath it. */
.sec-title, .home-body h2, .cat-heading {
  font-size: var(--t-lg);
  font-weight: 700;
  letter-spacing: -.015em;
  margin: var(--sp-6) 0 var(--sp-3);
}

/* ── the promo block ──────────────────────────────────────────────────────
   It was a dark slab as tall as three products, on both the home screen and
   every menu. It is an offer, not an announcement. */
.promo-banner {
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  align-items: center;
}
.promo-banner .pb-title { font-size: var(--t-bd); font-weight: 700; }
.promo-banner .pb-msg { font-size: var(--t-sm); line-height: 1.4; opacity: .85; }
.promo-banner .pb-ico { width: 22px; height: 22px; flex: none; }
.promo-banner .pb-code { font-size: var(--t-xs); letter-spacing: .08em; padding: 5px 10px; }

/* ── buttons ──────────────────────────────────────────────────────────────
   44px is the platform's touch target; anything taller is a web hero button. */
.btn, .btn-primary, .loc-btn {
  min-height: 46px;
  border-radius: var(--r-md);
  font-size: var(--t-bd);
  font-weight: 650;
  letter-spacing: -.01em;
}

/* ── the shop header ───────────────────────────────────────────────────────
   This was the single worst thing in the app. On a 390×844 screen the Waccao
   header alone ran 430px — half the glass — before "also at this address", a
   promo slab and the category strip. A customer opening a menu scrolled a full
   screen before seeing one waffle.

   Everything in it was true and almost none of it was urgent. Delivery time and
   fee matter at checkout, not while choosing. Which branch cooks it matters to
   perhaps one customer in twenty. So the pattern and the logo stay — they are
   what makes a shop feel like that shop — and the rest becomes one line that
   can be tapped if anyone cares.

   Result: ~168px, and the first product sits above the fold. */
.brand-band {
  min-height: 0;
  padding: calc(env(safe-area-inset-top, 0px) + 46px) var(--gutter) var(--sp-4);
}
.brand-band h2 { font-size: 24px; letter-spacing: -.015em; }
.brand-band .poster-mark { font-size: 78px; right: -8px; bottom: -18px; opacity: .1; }
.brand-band .brand-logo { width: 46px; height: 46px; border-radius: var(--r-md); }

/* Delivery time and price: true, but not urgent while choosing a waffle. They
   are said again at checkout, where they decide something. */
.brand-band .bb-meta { gap: 5px; margin-top: 6px; }
.brand-band .bb-meta span {
  font-size: var(--t-xs); font-weight: 650; padding: 4px 8px; border-radius: 99px;
  white-space: nowrap;
}

/* Which branch cooks it: one quiet line, not a white card with its own button. */
.bb-branch, .bb-branch.single {
  display: flex; align-items: center; gap: 5px;
  margin-top: var(--sp-2); padding: 0;
  background: none; border: none; box-shadow: none; border-radius: 0;
  font-size: var(--t-sm); opacity: .9;
}
.bb-branch .bb-br-txt { display: inline; }
.bb-branch .bb-br-txt small { display: none; }
.bb-branch .bb-br-txt b { font-weight: 650; }
.bb-branch .bb-br-addr { display: none; }
.bb-branch .ic, .bb-branch svg { width: 13px; height: 13px; }

/* ── the category strip ───────────────────────────────────────────────────
   It scrolled away with everything else, so a customer three screens into a
   menu had no way back to the top of a section without flicking. Pinned, it
   becomes the navigation it was always meant to be. */
.cat-tabs {
  position: sticky; top: 0; z-index: 30;
  margin: 0 calc(var(--gutter) * -1);
  padding: var(--sp-2) var(--gutter);
  /* 88% let section headings and 'Bestseller' badges read straight through the
     pills as cards passed behind. Blur alone does not hide text at this size. */
  background: color-mix(in srgb, var(--bg) 97%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
  border-bottom: .5px solid var(--hair);
}
.cat-tab {
  min-height: 32px; padding: 0 13px;
  font-size: var(--t-sm); font-weight: 600;
  border-radius: 99px; border: none;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  box-shadow: none;
}
.cat-tab.active { background: var(--ink); color: var(--accent-ink); }

/* ── "also at this address" ───────────────────────────────────────────────
   Two more cards between the customer and the menu. It is a footnote: the
   other shops are still one tap away on the home screen. */
.also-here { display: flex; gap: var(--sp-2); margin: var(--sp-3) 0 0; }
.also-here .ah-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px 6px 6px; border-radius: 99px;
  background: var(--card); border: .5px solid var(--hair); box-shadow: none;
  font-size: var(--t-sm); font-weight: 600;
}
.also-here .ah-btn img { width: 22px; height: 22px; border-radius: 50%; }
.also-title {
  font-size: var(--t-xs); letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); margin: var(--sp-4) 0 0;
}

/* ── the category heading between groups ─────────────────────────────────── */
.cat-heading {
  font-size: var(--t-lg); margin: var(--sp-5) 0 var(--sp-3);
  display: flex; align-items: center; gap: var(--sp-2);
}

/* ── the product card ─────────────────────────────────────────────────────
   Kept, because it works. Only the text under the photo is retuned: the name
   was 19px and the description ran three lines, which pushed the price of the
   second row off the screen. */
.item-card { border-radius: var(--r-lg); box-shadow: var(--shadow-sm); border: .5px solid var(--hair); }
.item-card .name { font-size: var(--t-md); line-height: 1.22; letter-spacing: -.01em; }
.item-card .desc {
  font-size: var(--t-sm); line-height: 1.35; margin-top: 3px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.item-card .price { font-size: var(--t-bd); font-weight: 700; }
.item-badge { font-size: var(--t-xs); padding: 4px 8px; border-radius: 99px; }

/* ── corrections against the markup the app actually renders ────────────── */

/* The back button was colliding with the shop logo once the header shrank.
   It gets its own corner, clear of everything. */
.brand-band .bb-back {
  top: calc(env(safe-area-inset-top, 0px) + 8px); left: var(--gutter);
  width: 34px; height: 34px; font-size: 22px; line-height: 1;
  display: grid; place-items: center;
  background: rgba(255,255,255,.9); border-radius: 50%;
  box-shadow: var(--shadow-sm); z-index: 3;
}
.brand-band .brand-logo { top: calc(env(safe-area-inset-top, 0px) + 6px); right: var(--gutter); left: auto; }

/* "Find the shop nearest me" was a full-width pill on a header that is trying
   to get out of the way. It says the same thing in a third of the space. */
.bb-near, .bb-near-note {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 6px; padding: 0; min-height: 0;
  background: none; border: none; box-shadow: none;
  font-size: var(--t-sm); font-weight: 600; opacity: .88;
  text-decoration: underline; text-underline-offset: 3px;
}
.bb-near svg, .bb-near-note svg { width: 13px; height: 13px; }

/* The other shops in the building: a footnote, on one line, scrollable. */
#also-here { margin: var(--sp-4) 0 0; }
.ah-lab {
  font-size: var(--t-xs); letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); margin-bottom: var(--sp-2); font-weight: 700;
}
.ah-row {
  /* nowrap is the point of this rule. The original said `flex-wrap: wrap`, and
     because a later rule only overrides the properties it actually names, two
     shops wrapped onto two lines and ate 110px of a screen whose job is to show
     food. They scroll sideways instead. */
  display: flex; flex-wrap: nowrap; gap: var(--sp-2); overflow-x: auto;
  margin: 0 calc(var(--gutter) * -1); padding: 0 var(--gutter) 2px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.ah-row::-webkit-scrollbar { display: none; }
.ah-chip {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  padding: 5px 11px 5px 5px; border-radius: 99px;
  background: var(--card); border: .5px solid var(--hair); box-shadow: none;
  font-size: var(--t-sm); font-weight: 600; white-space: nowrap;
}
.ah-chip img { width: 24px; height: 24px; border-radius: 50%; object-fit: contain; background: #fff; }
.ah-chip .ah-add { font-style: normal; opacity: .45; font-weight: 700; }

/* The promo. 119px for one sentence and a code, on a screen whose job is to
   show food. The message wrapped to three lines because the box was narrow AND
   the type was large — fixing the type fixes the height. */
.promo-banner .pb-msg { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
#menu-promo { margin-top: var(--sp-3); }

/* The product card's text. The real element names are h3 and p inside
   .item-body — my first pass guessed .name and .desc and did nothing at all,
   which is exactly the mistake that has cost this project time before. */
.item-card .item-body { padding: var(--sp-3); }
.item-card .item-body h3 {
  font-size: var(--t-md); line-height: 1.2; letter-spacing: -.012em; font-weight: 700;
}
.item-card .item-body p {
  font-size: var(--t-sm); line-height: 1.34; margin-top: 3px; color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* The promo, finally sized like an offer rather than an announcement. The gift
   icon was 22px of decoration next to 13px of text; the code is the only part
   anyone acts on, so it gets the emphasis. */
.promo-banner { padding: var(--sp-3); gap: var(--sp-3); }
.promo-banner .pb-ico { width: 18px; height: 18px; opacity: .75; }
.promo-banner .pb-title { font-size: var(--t-sm); }
.promo-banner .pb-msg { font-size: var(--t-xs); line-height: 1.35; -webkit-line-clamp: 2; }
.promo-banner .pb-code { font-size: var(--t-xs); font-weight: 800; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE FOOTER, THE BRAND CARDS, ORDERS AND REWARDS
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the tab bar ───────────────────────────────────────────────────────────
   The active tab was a hard black pill — the heaviest object on the screen,
   permanently, on a bar that should recede. Weight is the wrong tool for
   "you are here": it draws the eye to navigation instead of to food.

   Colour and a soft tint do the same job at a fraction of the volume, and the
   icon rides up two pixels so the change is felt as well as seen. */
.tb {
  gap: 3px; padding: 6px 4px 5px; border-radius: 14px;
  color: var(--muted); font-size: 10.5px; font-weight: 600; letter-spacing: 0;
  transition: color .16s ease;
}
.tb svg { width: 22px; height: 22px; stroke-width: 1.8; transition: transform .22s cubic-bezier(.32,.72,0,1); }
.tb.active { background: none; color: var(--ink); font-weight: 700; }
.tb.active svg { transform: translateY(-1px); stroke-width: 2.1; }

/* The tint sits behind the icon only, not the label — a lozenge behind both
   reads as a button, and a tab is not a button. */
.tb::before {
  content: ''; position: absolute; top: 3px; left: 50%;
  width: 46px; height: 30px; margin-left: -23px; border-radius: 10px;
  background: color-mix(in srgb, var(--lux) 20%, transparent);
  opacity: 0; transform: scale(.82);
  transition: opacity .18s ease, transform .22s cubic-bezier(.32,.72,0,1);
}
.tb.active::before { opacity: 1; transform: scale(1); }
.tb svg, .tb span { position: relative; z-index: 1; }
.tb:active { transform: none; }
.tb:active svg { transform: scale(.9); }

/* ── the brand cards ───────────────────────────────────────────────────────
   They were a logo centred on a colour swatch: correct, and inert. A customer
   deciding where to eat is choosing a shop, and a shop has a name and sells
   something. The photograph stays exactly as it is — it is the part that
   works — and gains a scrim and two lines, so the tile reads as a storefront
   rather than a brand asset. */
.brand-rail { gap: 10px; }
.brand-card {
  aspect-ratio: 1 / 1.18;
  border-radius: var(--r-lg);
  box-shadow: 0 2px 10px -4px rgba(30,23,18,.28);
  transition: transform .2s cubic-bezier(.32,.72,0,1), box-shadow .2s ease;
}
.brand-card .bc-photo { object-fit: cover; }
.bc-veil {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(12,8,5,.82) 0%, rgba(12,8,5,.34) 38%, transparent 66%);
}
.bc-text {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: block; padding: 10px 10px 11px; text-align: left;
}
.bc-text b {
  display: block; color: #fff; font-size: 13px; font-weight: 700;
  line-height: 1.16; letter-spacing: -.01em;
  text-shadow: 0 1px 6px rgba(0,0,0,.4);
}
.bc-text small {
  display: block; margin-top: 2px; color: rgba(255,255,255,.76);
  font-size: 10.5px; line-height: 1.25; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.brand-card:active { transform: scale(.965); box-shadow: 0 1px 4px -2px rgba(30,23,18,.3); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE STARZCLUB DESIGN
   ═══════════════════════════════════════════════════════════════════════════
   Built to the supplied design sheet. Colours are sampled from it rather than
   guessed: the CTA gold, the deeper gold of the active tab, the two ends of the
   rewards-card gradient and the hairline grey are all taken off the artwork.

   The four changes that carry the whole look:
     · the ground goes white. Warm paper is gone, and with it the tinted cards
       that had to sit on top of it.
     · gold does all the emphasis. One colour, three weights: a fill for
       actions, a mid tone for links and values, a deep tone for "you are here".
     · boxes are white with a single hairline, never a shadow.
     · a product is a ROW — thumbnail, name, brand, price, and a round + on the
       right — not a tile in a grid. Rows fit more food per screen and give the
       price and the add button a fixed, learnable position.
   ═══════════════════════════════════════════════════════════════════════════ */
:root,
body[data-brand="coldstone"],
body[data-brand="waccao"],
body[data-brand="censa"] {
  /* ── the ground ──────────────────────────────────────────────────────────
     Every screen used to be pure white, and a white card on a white page is
     not a card — it is a hairline drawn on nothing. The page is now a warm
     cream and cards stay pure white, so a card is a physical thing sitting on
     a surface, food photography has something warm to sit against instead of
     clinical white, and the app stops reading as a browser default.
     The greys go warm with it: a #efefef hairline on cream reads blue. */
  --bg: #faf7f2;          /* the page */
  --card: #ffffff;        /* anything that lifts off the page */
  --ink: #1a1a1a;
  --muted: #8a8177;
  --line: #ece5db;
  --hair: #ece5db;
  --soft: #f4ede3;        /* a quiet fill INSIDE a card, one step down again */

  --gold:      #c89b5a;   /* links, values, outline icons */
  --gold-btn:  #d7a851;   /* a filled action */
  --gold-deep: #b4832e;   /* the active tab, and big numbers */
  --gold-c1:   #f8edd9;   /* rewards card, top-left */
  --gold-c2:   #f3e3c6;   /* rewards card, bottom-right */

  --accent: var(--gold-btn);
  --accent-ink: #ffffff;
  --lux: var(--gold);

  /* The lime accent belonged to the palette before this one, and the token
     stayed alive — so the promo code chip and the notification toggle were
     still bright lime on a warm cream page. Pointing the old token at the new
     accent retires it everywhere at once, rather than hunting fifteen call
     sites and missing two. */
  --lime: var(--gold-btn);
  --lime-ink: #ffffff;

  --r-lg: 16px; --r-md: 14px; --r-sm: 10px;
  --shadow-sm: none; --shadow-md: none; --shadow-lg: none;
}
body { background: var(--bg); color: var(--ink); }
/* Deliberately NOT overridden here any more. The strip behind the phone's clock
   is already driven by --sb, which paintSystemBars() sets per screen — brand
   colour on a shop's page, page colour everywhere else. Hardcoding it white
   defeated that and capped every brand banner with a white band under the
   clock. The variable is the mechanism; this rule was fighting it. */

/* The brand tint that used to wash the whole screen is gone: the design keeps
   every screen white and lets the shop's own logo carry the identity. */
body[data-brand] { --soft: #f4ede3; }

/* ── home: the greeting ───────────────────────────────────────────────────── */
.app-top, .home-top {
  /* the sticky header is the page continuing upward, not a white bar laid on
     it — a white header over a cream page draws a line that means nothing */
  background: var(--bg); backdrop-filter: none; -webkit-backdrop-filter: none;
  border: none; padding-top: calc(env(safe-area-inset-top, 0px) + 10px);
}
.at-user { background: none; border: none; box-shadow: none; padding: 0; }
.at-user .at-coin, .at-chev { display: none; }
.at-user-txt small { display: none; }
.at-user-txt b {
  font-size: 22px; font-weight: 700; letter-spacing: -.02em; color: var(--ink);
}
.at-btn {
  background: none; border: none; box-shadow: none;
  width: 34px; height: 34px; color: var(--ink);
}
.at-btn svg { width: 23px; height: 23px; stroke-width: 1.7; }
.at-lang { font-size: 13px; font-weight: 600; color: var(--muted); }

/* The question under the greeting, which the old design did not have. */
.app-h1 {
  font-family: var(--font-body);
  font-size: 15px; font-weight: 400; line-height: 1.4;
  letter-spacing: 0; color: var(--muted);
  margin: 2px 0 18px;
}

/* ── search ──────────────────────────────────────────────────────────────── */
.searchbar {
  height: 50px; padding: 0 16px; gap: 11px;
  /* white with a hairline, like every other thing on this page that holds
     content. A flat #f4f4f4 was a neutral grey chosen against a white page —
     on cream it is the one cold object on the screen. */
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: none;
}
.searchbar .sb-ico, .searchbar svg { width: 19px; height: 19px; color: var(--muted); stroke-width: 2; opacity: 1; }
.searchbar input { font-size: 14.5px; }
.searchbar input::placeholder { color: var(--muted); }

/* ── section heads, with the gold "see all" the design uses ──────────────── */
.sec-head, .home-body .sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 28px 0 14px;
}
.sec-head h2, .sec-title, .home-body h2 {
  font-family: var(--font-body);
  font-size: 17px; font-weight: 700; letter-spacing: -.015em; margin: 0;
}
.sec-head h2::after, .sec-title::after { content: none; }
.sec-more { font-size: 13px; font-weight: 600; color: var(--gold); background: none; border: none; }

/* ── the brand tiles ─────────────────────────────────────────────────────── */
.brand-rail { display: grid; grid-template-columns: repeat(3, 1fr); gap: 11px; }
.brand-card {
  aspect-ratio: 1 / 1.06;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: none; overflow: hidden; display: grid; place-items: center; padding: 12px;
}
.brand-card .bc-photo { width: 100%; height: 100%; object-fit: contain; }
/* the storefront treatment from the previous pass is not in this design */
.brand-card .bc-veil, .brand-card .bc-text { display: none; }
.brand-card:active { transform: scale(.97); }

/* ── a product, as a row ─────────────────────────────────────────────────── */
/* .pc-rail is NOT in this selector any more. This block turns a scrolling rail
   into a stacked list, and `overflow: visible !important` is a SHORTHAND — it
   sets overflow-x to visible-important too. The later block that restores the
   horizontal rail sets `overflow-x: auto` without !important, so it lost, and
   "Popular right now" rendered as a row you could see the edge of but could not
   scroll. Any rule that has to fight this one needs !important on the exact
   longhand, which is why the cheaper fix is to stop this one matching the rail
   at all. */
#menu-list .cat-group, .item-grid {
  display: grid !important; grid-template-columns: 1fr; gap: 11px;
  overflow: visible !important;
}
.item-card {
  display: flex; align-items: center; gap: 14px;
  width: auto !important; min-width: 0; scroll-snap-align: none;
  padding: 12px; border: 1px solid var(--line); border-radius: var(--r-md);
  background: #fff; box-shadow: none; overflow: visible;
}
.item-card .item-visual {
  width: 84px; height: 84px; flex: none; border-radius: var(--r-sm);
  background: #fff; aspect-ratio: auto; overflow: hidden;
  display: grid; place-items: center;
}
.item-card .iv-photo { width: 100%; height: 100%; object-fit: cover; }
.item-card .iv-emoji { font-size: 34px; }
.item-card .item-body {
  flex: 1; min-width: 0; padding: 0; position: static;
  display: flex; flex-direction: column; gap: 2px; background: none;
}
.item-card .item-body h3 { font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.item-card .item-body p {
  font-size: 13.5px; color: var(--muted); margin: 0;
  -webkit-line-clamp: 1; line-clamp: 1;
}
.item-card .ic-stars { margin: 1px 0 0; }
.item-card .item-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 5px;
}
.item-card .price { font-size: 14.5px; font-weight: 700; color: var(--ink); }

/* The add (+) button is a FILLED brand-accent circle, so it reads as the one
   action on the card — the same language as the cart bar and the Place-order
   button — rather than a faint grey outline that looked disabled. It sits at the
   end of the foot, and because the foot is pinned to the bottom of the tile
   (below), its position never moves from one product to the next. */
.item-card .item-foot .add-btn {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink); border: none;
  font-size: 22px; font-weight: 500; line-height: 1;
  display: grid; place-items: center; padding: 0;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 34%, transparent),
              inset 0 1px 0 rgba(255,255,255,.22);
}
.item-card .item-foot .add-btn:active { transform: scale(.9); }
/* A card already in the basket shows the count in the same filled circle. */
.item-card .item-foot .add-btn.has { font-size: 15px; font-weight: 800; }
.item-badge {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  font-size: 9.5px; padding: 3px 6px; border-radius: 6px;
  background: rgba(0,0,0,.62); color: #fff;
}
.item-card .item-visual { position: relative; }

/* ── buttons ─────────────────────────────────────────────────────────────── */
.btn-primary, #btn-place, #btn-opt-add, .cta, .loc-btn.primary {
  background: var(--gold-btn); color: #fff; border: none;
  border-radius: var(--r-sm); min-height: 50px;
  font-size: 15px; font-weight: 600; letter-spacing: -.01em; box-shadow: none;
}
.btn-primary:active, #btn-place:active { background: #c99a45; }
.ghost-btn, .btn-ghost {
  background: #fff; border: 1px solid var(--line); color: var(--ink);
  border-radius: var(--r-sm); min-height: 46px; font-weight: 600; box-shadow: none;
}

/* ── the tab bar ─────────────────────────────────────────────────────────── */
.tabbar {
  background: #fff !important; border-top: 1px solid var(--line);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 7px 6px max(6px, calc(env(safe-area-inset-bottom, 0px) * .5));
}
.tb { color: #9b9b9b; font-size: 10.5px; font-weight: 500; gap: 4px; }
.tb svg { width: 23px; height: 23px; stroke-width: 1.7; }
.tb::before { content: none; }
.tb.active { color: var(--gold-deep); font-weight: 600; background: none; }
.tb.active svg { stroke-width: 2; transform: none; fill: color-mix(in srgb, var(--gold-deep) 16%, transparent); }

/* ── the product row, to the design ──────────────────────────────────────── */
.pl-list { display: grid; gap: 11px; }
.pl {
  /* min-width:0 is load-bearing. A grid item defaults to min-width:auto, which
     means it refuses to shrink below its own min-content — and with a
     no-wrapping product name that came to 391px inside a 358px column, so every
     row hung 33px off the right edge and clipped its own add button. */
  display: flex; align-items: center; gap: 14px; width: 100%; min-width: 0;
  padding: 12px; text-align: left;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: none;
}
.pl:active { background: #fcfcfc; }
.pl-art {
  width: 84px; height: 84px; flex: none; border-radius: var(--r-sm);
  background: #fff; overflow: hidden; display: grid; place-items: center;
}
.pl-art img { width: 100%; height: 100%; object-fit: cover; }
.pl-art span { font-size: 34px; }
.pl-mid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* Name first, then the shop, then the price — the order the design reads in,
   and the order a hungry person reads in. */
.pl-name {
  font-size: 15.5px; font-weight: 700; color: var(--ink); letter-spacing: -.01em;
  line-height: 1.22; overflow: hidden;
  /* Two lines, not one. "Nutella Cheese Cake Waffle" is a real product name and
     truncating it to "Nutella Cheese…" hides the thing that distinguishes it. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.pl-brand { font-size: 13.5px; color: var(--muted); font-weight: 400; }
.pl-price { font-size: 14.5px; font-weight: 700; color: var(--ink); margin-top: 3px; }
.pl-go { display: none; }
.pl-add {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  border: 1px solid #e2e2e2; background: #fff; color: var(--ink);
  font-size: 20px; font-weight: 400; line-height: 1;
  display: grid; place-items: center;
}
.pl-add.has { background: var(--gold-btn); border-color: var(--gold-btn); color: #fff; font-size: 14px; font-weight: 700; }
.pl-add:active { background: #f5f5f5; }
.pl .qty-stepper { flex: none; }
.pl.sold-out { opacity: .55; }
.pl-oos { font-size: 11px; color: #b23; font-weight: 600; }

/* The shop line under a product name is sentence case in the design, not the
   spaced uppercase eyebrow the old rail used. */
.pl .pl-brand { text-transform: none; letter-spacing: 0; font-size: 13.5px; color: var(--muted); font-weight: 400; }

/* The promo, in the new palette. It was a dark green slab left over from the
   previous look and was the only thing on a white screen with a colour of its
   own — which made an optional offer the loudest object on the page. */
#view-home #home-promo .promo-banner,
#menu-promo .promo-banner,
.promo-banner {
  background: linear-gradient(120deg, var(--gold-c1), var(--gold-c2));
  color: #6b4d18; border: none; border-radius: var(--r-md);
  padding: 13px 14px; box-shadow: none;
}
.promo-banner .pb-title, #view-home #home-promo .pb-title { color: #6b4d18; font-size: 13.5px; font-weight: 700; }
.promo-banner .pb-msg { color: #8a6b31; font-size: 12px; }
.promo-banner .pb-ico, #view-home #home-promo .pb-ico { color: var(--gold-deep); }
.promo-banner .pb-code {
  background: var(--gold-btn); color: #fff; border: none;
  font-size: 11px; font-weight: 800; letter-spacing: .06em; padding: 6px 11px; border-radius: 8px;
}

/* ── rewards, to the design ──────────────────────────────────────────────── */
/* The points card: a soft gold gradient panel, the number set large in deep
   gold, the coin to the right. It replaces the near-black card that carried a
   QR code the size of a beer mat — the QR is used at a counter perhaps once a
   visit, and it was the first and largest thing on the screen every time. */
.rew-card {
  background: linear-gradient(135deg, var(--gold-c1), var(--gold-c2));
  color: #6b4d18; border: none; border-radius: var(--r-lg);
  padding: 20px; box-shadow: none;
}

.rw-label {
  font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gold-deep);
}
.rw-points {
  font-size: 40px; font-weight: 700; line-height: 1.05; letter-spacing: -.02em;
  color: #935601; margin: 6px 0 2px;
}
.rw-sub { font-size: 13px; color: #a98e76; }
.rw-coin { width: 92px; height: 92px; flex: none; }

/* The QR moves behind a button. It is still one tap away for the counter. */
.rw-qr-btn {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 14px;
  background: rgba(255,255,255,.72); border: 1px solid rgba(180,131,46,.25);
  color: #6b4d18; border-radius: 10px; padding: 9px 14px;
  font-size: 13px; font-weight: 600;
}

/* History and redeem rows: white, hairline, gold outline icon, gold value. */
.rw-row {
  display: flex; align-items: center; gap: 13px;
  padding: 13px; background: #fff;
  border: 1px solid var(--line); border-radius: var(--r-md);
}
.rw-row + .rw-row { margin-top: 9px; }
.rw-row .rw-ico {
  width: 36px; height: 36px; flex: none; display: grid; place-items: center;
  color: var(--gold); border-radius: 10px;
}
.rw-row .rw-ico svg { width: 20px; height: 20px; stroke-width: 1.7; }
.rw-row .rw-txt { flex: 1; min-width: 0; }
.rw-row .rw-txt b { display: block; font-size: 14.5px; font-weight: 600; color: var(--ink); }
.rw-row .rw-txt small { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.rw-row .rw-val { font-size: 14px; font-weight: 700; color: var(--gold); white-space: nowrap; }

/* A reward you can spend sits on the soft gold, with a filled button. */
.rw-row.redeem { background: linear-gradient(120deg, #faf5ec, var(--gold-c1)); border-color: transparent; }
.rw-row.redeem .rw-btn {
  background: var(--gold-btn); color: #fff; border: none; border-radius: 9px;
  padding: 9px 15px; font-size: 13px; font-weight: 600; min-height: 40px;
}
.rw-row.redeem .rw-btn[disabled] { background: #e6ddcd; color: #a3927a; }

/* The reward list the app already had, brought into the same language. */
.reward-row, .rw-item {
  display: flex; align-items: center; gap: 13px; padding: 13px 0;
  border-bottom: 1px solid var(--line);
}
.reward-row:last-child, .rw-item:last-child { border-bottom: none; }
.reward-row .rr-ico, .rw-item .ri-ico {
  width: 36px; height: 36px; border-radius: 10px; background: var(--gold-c1);
  color: var(--gold-deep); display: grid; place-items: center; flex: none;
}
.reward-row .rr-cost, .rw-item .ri-cost { color: var(--gold); font-weight: 700; font-size: 14px; }

/* ── screen headers, to the design: back chevron left, title centred ─────── */
.topbar {
  display: grid; grid-template-columns: 40px 1fr 40px; align-items: center;
  background: none; border: none; box-shadow: none;
  padding: calc(env(safe-area-inset-top, 0px) + 6px) 0 10px; margin: 0;
}
.topbar .back {
  width: 40px; height: 40px; display: grid; place-items: center;
  background: none; border: none; box-shadow: none;
  font-size: 26px; line-height: 1; color: var(--ink);
}
.topbar > div { text-align: center; min-width: 0; }
.topbar h1 {
  /* Bigger, bolder, display face — this is the rule that actually wins for the
     sub-page headers (an earlier .topbar h1 at 26px was dead, overridden here).
     22px stays safe for a centred title sitting between two 40px controls. */
  font-family: var(--font-display); font-size: 22px; font-weight: 800;
  letter-spacing: -.01em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar .sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

/* ── the rewards card, against the markup it actually has ───────────────── */
.rew-card { color: #6b4d18; }
.rew-top { align-items: center; }
/* The white-on-dark wordmark cannot survive on a gold panel, and the card no
   longer needs it — the screen is already titled Rewards. */
.rew-logo { display: none; }
.rew-points {
  display: flex; align-items: center; gap: 10px;
  font-size: 40px; font-weight: 700; line-height: 1.05;
  letter-spacing: -.02em; color: #935601;
}
.rew-points small {
  display: block; font-size: 12px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold-deep); align-self: center;
}
.rew-coin { width: 40px; height: 40px; }
.rew-phone { font-size: 12.5px; color: #a98e76; margin-top: 4px; }
.rew-hint { color: #a98e76; font-size: 12px; }
.rew-tier-row .tier-chip, .rew-tier-row > * {
  background: rgba(255,255,255,.7); border: 1px solid rgba(180,131,46,.25); color: #6b4d18;
}
.rew-progress { color: var(--gold-deep); font-weight: 700; font-size: 13px; }
.loy-bar { background: rgba(180,131,46,.18); }
.loy-fill { background: var(--gold-deep); }
/* The QR is the size of a beer mat for something used once a visit. Small, and
   still perfectly scannable at 84px on a phone screen. */
.rew-qr { width: 84px; height: 84px; padding: 6px; background: #fff; border-radius: 10px; }
.rew-qr canvas, .rew-qr img, .rew-qr svg { width: 100% !important; height: 100% !important; }

/* Panels: the app wrapped most lists in a white "summary card" that, on a white
   page, drew a box around nothing. They become plain sections with a heading. */
.summary-card {
  background: none; border: none; box-shadow: none; padding: 0;
  margin-top: 26px; border-radius: 0;
}
.summary-card h3 {
  font-family: var(--font-body); font-size: 17px; font-weight: 700;
  letter-spacing: -.015em; margin-bottom: 12px;
}

/* ── orders ──────────────────────────────────────────────────────────────── */
/* A tab has no back button: it is a place, not a step. The chevron sat there
   promising an escape from a screen nobody had entered. */
.topbar.tab-top { grid-template-columns: 1fr; padding-bottom: 6px; }
.topbar.tab-top > div { text-align: left; }
.topbar.tab-top h1 { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }

.tm-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 17px; font-weight: 700; letter-spacing: -.015em;
  color: var(--ink); text-transform: none; margin: 12px 0 12px;
}
.tm-head small { font-size: 12.5px; font-weight: 400; color: var(--muted); }

/* An order is a card with the shop, the state and the money — the three things
   anyone opens this screen to find. */
.tm-row {
  display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; width: 100%;
  padding: 14px; text-align: left; background: #fff;
  border: 1px solid var(--line); border-radius: var(--r-md); box-shadow: none;
}
.tm-row + .tm-row { margin-top: 10px; }
.tm-row:active { background: #fcfcfc; }
.tm-mid { grid-column: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tm-brand { font-size: 15px; font-weight: 700; color: var(--ink); }
.tm-code {
  grid-row: 2; grid-column: 1; font-size: 12px; color: var(--muted);
  font-weight: 500; letter-spacing: .02em;
}
.tm-total { grid-column: 2; grid-row: 1 / span 2; align-self: center; font-size: 15px; font-weight: 700; }
/* The state, as a chip, in the palette's own colours. */
.tm-status {
  display: inline-flex; align-self: flex-start; align-items: center;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
  background: var(--gold-c1); color: #8a6b31;
}
.tm-status.st-out_for_delivery { background: #e8f3ec; color: #1a7a45; }
.tm-status.st-delivered { background: #f1f1f1; color: #6d6d6d; }
.tm-status.st-cancelled { background: #fdeceb; color: #b3261e; }

/* The code lookup is for the rare customer who ordered without an account. It
   was a full card in the middle of the screen; it becomes a quiet footer. */
.track-lookup-card {
  background: none; border: none; box-shadow: none; padding: 0;
  margin-top: 28px; border-top: 1px solid var(--line); padding-top: 18px;
}
.track-lookup-card p { font-size: 13px; color: var(--muted); text-align: left; margin-bottom: 10px; }
.track-input { gap: 9px; }
.track-input input {
  flex: 1; height: 46px; background: #f4f4f4; border: none; border-radius: var(--r-sm);
  padding: 0 14px; font-size: 14px; letter-spacing: .06em; text-align: left;
}
.track-input .ghost-btn { min-height: 46px; padding: 0 18px; }
/* The lookup block sat directly under the last order with no separation, so its
   top rule read as part of that card. */
#track-mine + .track-lookup-card { margin-top: 22px; }
.track-mine { display: block; }

/* ── the top gap ─────────────────────────────────────────────────────────────
   Every screen was reserving the status-bar inset TWICE: once on .view, which
   pads the whole screen down past the notch, and again on .topbar, which I gave
   its own inset when rebuilding the headers. On a Dynamic Island iPhone that is
   ~59px counted twice, so every page began about 120px down the glass with a
   band of nothing above the title.

   .view already owns the inset. Nothing inside it should reserve it again. */
.topbar { padding-top: 6px; }
.topbar.tab-top { padding-top: 2px; }
.app-top, .home-top { padding-top: 10px; margin-top: 0; }

/* ── the rewards card, finished ─────────────────────────────────────────────
   Three leftovers from the old dark card were still showing through on gold:
   a white pill behind the earn-rate line, an orange progress bar, and a bright
   green success line. None belong in a palette of one gold and one grey. */
.rew-tier-row { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.rew-tier-row .tier-chip, .rew-tier-row > * {
  background: rgba(255,255,255,.78); border: 1px solid rgba(180,131,46,.28);
  color: #6b4d18; border-radius: 999px; padding: 7px 13px;
  font-size: 13px; font-weight: 700;
}
/* The earn-rate sentence is a caption, not a chip. */
.rew-tier-row .rew-rate, .rew-rate {
  background: none !important; border: none !important; padding: 0 !important;
  font-size: 12.5px; font-weight: 600; color: #8a6b31;
}
.loy-bar { height: 6px; border-radius: 99px; background: rgba(180,131,46,.2); margin: 12px 0 8px; overflow: hidden; }
.loy-fill { background: var(--gold-deep) !important; border-radius: 99px; }
.rew-progress { color: var(--gold-deep) !important; font-weight: 700; font-size: 13px; }

/* The points coin is the StarzClub mark; at 40px in a white ring it read as an
   afterthought next to a 40px number. It fills its own space now. */
.rew-coin { width: 54px; height: 54px; border-radius: 50%; }
.rew-points { gap: 12px; }

/* ── brand tiles: the logo fills the tile ───────────────────────────────────
   The design centres each shop's mark in a white tile. Ours had 12px of padding
   AND `object-fit: contain` inside a square, so a wide logo like Cold Stone's
   sat in the middle at about half the width with air all round it. */
.brand-card { padding: 6px; }
.brand-card .bc-photo { width: 100%; height: 100%; object-fit: cover; }

/* The earn-rate line is .tier-mult, not .rew-rate — it is a sentence, and my
   blanket rule on the row's children had given it a white pill of its own. */
.rew-tier-row { flex-wrap: wrap; }
.rew-tier-row .tier-mult {
  background: none !important; border: none !important; padding: 0 !important;
  font-size: 12.5px; font-weight: 600; color: #8a6b31; width: 100%; margin-top: 2px;
}
.rew-tier-row .tier-chip { display: inline-flex; align-items: center; gap: 6px; }
.rew-tier-row .tier-chip .ic { width: 14px; height: 14px; }

/* A tab header with one action: title left, action right, on the same line. */
.topbar.tab-top { grid-template-columns: 1fr auto; align-items: center; }
.topbar.tab-top .tb-gear {
  width: 38px; height: 38px; display: grid; place-items: center;
  background: #fff; border: 1px solid var(--line); border-radius: 50%;
  box-shadow: none; color: var(--ink);
}
.topbar.tab-top .tb-gear svg { width: 19px; height: 19px; }

/* The reward rows were washed out: light text on white reads as disabled, and
   these are the things the whole loyalty scheme exists to sell. */
.rw-row .rw-txt b, .rw-row b { color: var(--ink); font-weight: 650; }
.rw-row .rw-txt small, .rw-row small { color: var(--muted); }
.rw-row .rw-ico { background: var(--gold-c1); color: var(--gold-deep); }
.rw-row .rw-cost, .rw-row .rw-val {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--gold-deep); font-weight: 700; font-size: 14px;
}
.rw-row .rw-cost img, .rw-row .rw-val img { width: 17px; height: 17px; }

/* ── tracking an order ───────────────────────────────────────────────────────
   Before a rider is assigned there is no map, and the screen filled that
   absence with the brand's wallpaper — a quarter of a metre of patterned beige
   between two floating cards. An empty state should be short, not decorated.
   The whole screen now sits on white and collapses to what it actually knows. */
#view-order, #view-track { background: var(--bg); }
.dm.no-map {
  background: var(--bg) !important; min-height: 0; padding: 0; margin: 0;
}
.dm.no-map::before, .dm.no-map::after { content: none !important; }
.dm.no-map .dm-map, .dm.no-map .dm-map-fallback { display: none; }

/* The status card at the top: white, hairline, sans. */
.dm-card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: none; padding: 22px 18px; text-align: center; margin: 0;
}
.dm-art { color: var(--gold); }
.dm-art svg { width: 30px; height: 30px; stroke-width: 1.6; }
.dm-date {
  font-family: var(--font-body); font-size: 11.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-top: 10px;
}
.dm-eta, .dm-eta-lab {
  font-family: var(--font-body); font-size: 20px; font-weight: 700;
  letter-spacing: -.02em; color: var(--ink); margin-top: 4px;
}
.dm-sub { font-size: 13px; color: var(--muted); margin-top: 10px; border: none; padding-top: 0; }
.dm-sub::before { content: none; }

/* The progress card, which used to float over the wallpaper. */
.tk-mid { position: static; margin-top: 16px; }
.tk-card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: none; padding: 16px;
}
.tk-head { align-items: baseline; }
.tk-state { font-size: 16px; font-weight: 700; letter-spacing: -.015em; color: var(--ink); }
.tk-code { font-size: 12px; color: var(--muted); font-weight: 500; letter-spacing: .03em; }

/* Four segments, gold for done, grey for not. */
.dm-bar { gap: 5px; margin: 14px 0 8px; }
.dm-seg { height: 4px; border-radius: 99px; background: #ececec; }
.dm-seg.now, .dm-seg.done { background: var(--gold-btn); }
.dm-steps { gap: 5px; }
.dm-step { font-size: 11px; color: var(--muted); font-weight: 500; }
.dm-step.now, .dm-step.done { color: var(--gold-deep); font-weight: 700; }
.dm-help { font-size: 12.5px; color: var(--muted); text-align: center; margin: 12px 0 0; }

.tk-acts { gap: 10px; margin-top: 14px; }
.tk-act {
  flex: 1; min-height: 48px; border-radius: var(--r-sm);
  font-size: 14.5px; font-weight: 600; border: 1px solid var(--line);
  background: #fff; color: var(--ink); box-shadow: none;
}
.tk-act:first-child { background: var(--gold-btn); border-color: var(--gold-btn); color: #fff; }
.tk-act.call { background: #fff; color: var(--gold-deep); border-color: var(--gold-c2); }

/* With a rider on the way the map returns, and it gets real height rather than
   sharing the screen with a wallpapered gap. */
.dm:not(.no-map) { border-radius: 0 0 var(--r-lg) var(--r-lg); overflow: hidden; }
.dm:not(.no-map) .dm-map { min-height: 320px; }

/* The progress card was a sheet pinned to the bottom of the glass, designed to
   float over a full-screen map. With no map it left a third of the screen empty
   between two cards. It joins the flow instead and sits under the status. */
.tk-card, .tk-mid > * { position: static !important; }
.dm.no-map + .tk-mid, .tk-mid { margin-top: 14px; }
.dm.no-map { height: auto !important; }
#dm-wrap.no-map { min-height: 0 !important; height: auto !important; }

/* Anything still set in the display serif on these screens: this is interface
   text — a status, a date, a code — and the serif is for shop content. */
#view-order .dm-eta, #view-order .dm-eta-lab, #view-order .tk-state,
#view-order h1, #view-track h1, #view-order b, #view-order strong {
  font-family: var(--font-body);
}

/* With no map, the pinned layout was still stretching to fill the glass: the
   status card at the top, the progress card jammed against the tab bar, and a
   third of a screen of nothing between them. Without a map there is nothing to
   fill with, so the screen stops being a full-height layout and becomes a short
   page that ends where its content ends. */
#view-order.pinned.active:has(.dm.no-map) {
  justify-content: flex-start; overflow-y: auto; padding-bottom: 28px;
}
#view-order.pinned.active:has(.dm.no-map) #track-result { flex: 0 0 auto; }
#view-order.pinned.active:has(.dm.no-map) .dm.no-map { flex: 0 0 auto; }
#view-order.pinned.active:has(.dm.no-map) .tk-card {
  flex: none; margin: 14px 0 0; border-radius: var(--r-lg);
  padding: 16px; box-shadow: none; border: 1px solid var(--line);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SIMPLER: PANELS BACK, WALLPAPER GONE
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the product panels come back ───────────────────────────────────────────
   The row layout fitted more per screen and was the wrong trade for this shop:
   people buy dessert with their eyes, and a 84px thumbnail of a Nutella waffle
   sells nothing. The photograph is the panel again. */
#menu-list .cat-group, .item-grid {
  display: grid !important; grid-template-columns: 1fr 1fr; gap: 12px;
}
.pc-rail {
  display: flex !important; gap: 12px; overflow-x: auto;
  margin: 0 calc(var(--gutter) * -1); padding: 0 var(--gutter) 4px;
  scroll-snap-type: x mandatory; scrollbar-width: none;
}
.pc-rail::-webkit-scrollbar { display: none; }
.item-card {
  /* A flex COLUMN, so the body can grow and the foot (price + button) can be
     pinned to the bottom. Two tiles side by side stretch to the same height in
     the grid, so once the foot is pinned every + lines up across the row —
     which it did not when the foot simply followed a caption of varying length. */
  display: flex; flex-direction: column; padding: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-lg); background: #fff;
}
.item-card .item-visual { width: auto; height: auto; aspect-ratio: 1 / 1; border-radius: 0; flex: none; }
/* flex:1 makes the body take up the slack, which is what pushes the foot down
   to the card's bottom edge regardless of how many lines the name/description run. */
.item-card .item-body { display: flex; flex-direction: column; flex: 1; padding: 11px 12px 12px; }
.item-card .item-body h3 { font-size: 15px; line-height: 1.22; }
.item-card .item-body p { -webkit-line-clamp: 2; font-size: 12.5px; }
.item-card .item-foot { margin-top: auto; padding-top: 10px; }

/* ── the brand wallpaper goes ───────────────────────────────────────────────
   Every shop screen carried a full-page tinted pattern behind the food, plus a
   giant watermark of the shop's mark. On a white design it fought the
   photographs and made three shops look like three different apps. The logo in
   the header says which shop you are in; that is enough. */
/* .ip-art is NOT on this list, and must never be put back on it. It is the
   product photo slot on the item screen — the picture of the thing you are
   about to buy — not decoration. Hiding it left every product page showing a
   blank gradient where the food should be, and the photo still LOADED, so it
   read as "images don't load" rather than as a CSS rule. Second time a
   `display:none !important` in this block has taken something real with it
   (the first was the strip behind the phone's clock), so the list stays
   explicit and stays short. */
.bb-art, .poster-mark, .bb-edge, .ip-edge { display: none !important; }
body[data-brand], body[data-brand] .view, #view-menu, #view-item, #view-checkout {
  background: var(--bg) !important;
}
/* NOT ::before. That pseudo-element is not wallpaper — it is the strip behind
   the phone's clock (line ~2128), the one thing on a brand page that must stay
   transparent so the header can run to the very top of the screen. This rule
   used to include it, and painted an opaque white band over the top 59px of
   every brand header.
   It hid from every test I ran: a desktop browser reports a safe-area inset of
   0, so the strip had zero height and there was nothing to see; the strip has
   pointer-events:none, so hit-testing at the top of the screen reported the
   header underneath and looked correct; and because the declaration was
   !important, every test colour I painted it lost the cascade silently and
   read as "not this element".
   Nothing here ever needed ::after either — body has no ::after content. */
.brand-band {
  background: var(--card) !important; border-radius: 0;
  border-bottom: 1px solid var(--line);
  padding: 6px var(--gutter) 14px;
}
.brand-band h2 { color: var(--ink) !important; }
.brand-band .bb-meta span { background: #f4f4f4; color: #6d6d6d; }
.brand-band .bb-back { background: none; box-shadow: none; color: var(--ink); position: static; }
.brand-band .brand-logo {
  position: static; width: 40px; height: 40px; border-radius: 9px;
  border: 1px solid var(--line); background: #fff; padding: 3px;
}
.bb-branch, .bb-near, .bb-near-note { color: var(--muted) !important; text-decoration: none; }
.bb-near, .bb-near-note { color: var(--gold-deep) !important; }

/* ── the shop logos, whole ──────────────────────────────────────────────────
   `cover` filled the tile by cropping, which sliced the C and the a off Censa.
   A logo is the one image you may never crop. */
.brand-card { padding: 8px; }
.brand-card .bc-photo { object-fit: contain; }

/* The sticky greeting bar was opaque white but had no edge, so a product photo
   scrolling underneath butted straight against the text with no seam — which
   reads as the header breaking rather than staying put. A hairline that only
   appears once the page has moved gives it an edge without drawing a line
   across the top of an unscrolled screen. */
.app-top, .home-top {
  border-bottom: 1px solid transparent;
  transition: border-color .15s ease;
}
body.scrolled .app-top, body.scrolled .home-top { border-bottom-color: var(--line); }

/* ── the order-details sheet ────────────────────────────────────────────────
   It opened at a fixed height with the tab bar drawn over its foot, so the
   total — the one number anyone opens this sheet for — sat under the Orders
   icon, and there was no way to scroll to it. It now stops above the tab bar,
   scrolls its own contents, and reserves the home indicator.

   The white background matters too: the sheet inherited the summary-card rules
   I flattened for the new design, which left it transparent over a blur. */
.tk-sheet {
  background: #fff; border-radius: 20px 20px 0 0;
  transform: translateY(100%);
  max-height: min(76svh, calc(100svh - 120px));
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 10px 18px calc(env(safe-area-inset-bottom, 0px) + 96px);
  box-shadow: 0 -12px 40px -20px rgba(20,16,10,.45);
}
.tk-sheet .summary-card { margin-top: 6px; }
.tk-sheet .summary-card h3 { font-size: 17px; }
/* the markup already carries a .tk-grab, so it just needs the new palette */
.tk-grab { background: #e2e2e2; width: 36px; height: 5px; }

/* ═══════════════════════════════════════════════════════════════════════════
   APP FEEL
   ═══════════════════════════════════════════════════════════════════════════
   The complaint was that the screens moved like a website pretending to be an
   app. Three things cause that, and none of them is the amount of animation:

     1. nothing responds to the finger while it is down. A native control dims
        or shrinks the instant you touch it, before anything else happens.
     2. easing that starts fast and ends slow (ease-out) reads as canned. iOS
        moves on a curve that starts slowly, covers ground, and settles.
     3. long durations. Anything over ~250ms for a small element feels like it
        is performing rather than responding.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The greeting is the top line now that the question under it is gone. */
.at-user-txt b { font-size: 24px; letter-spacing: -.025em; }
.app-top, .home-top { padding-bottom: 14px; }
.searchbar { margin-top: 2px; }

/* ── 1 · everything answers the finger ───────────────────────────────────── */
.item-card, .pl, .brand-card, .tm-row, .rw-row, .cat-tab,
.btn-primary, .ghost-btn, .btn-ghost, .tk-act, .pl-add, .add-btn, .sec-more {
  -webkit-tap-highlight-color: transparent;
  transition: transform .14s cubic-bezier(.32,.72,0,1), opacity .14s ease, background-color .14s ease;
}
.item-card:active, .brand-card:active, .tm-row:active, .rw-row:active, .pl:active { transform: scale(.975); }
.btn-primary:active, .ghost-btn:active, .btn-ghost:active, .tk-act:active { transform: scale(.97); opacity: .92; }
.pl-add:active, .add-btn:active { transform: scale(.9); }
.tabbar .tb:active { opacity: .5; transition: opacity .05s; }

/* ── 2 · one curve, everywhere ───────────────────────────────────────────── */
/* The same easing the screen transitions use, so a sheet, a chip and a screen
   all move as though the same hand moved them. */
.sheet, .tk-sheet, .sheet-backdrop, .modal, .msg-backdrop, .promo-pop-backdrop {
  transition-timing-function: cubic-bezier(.32,.72,0,1) !important;
}
.sheet, .tk-sheet { transition-duration: .34s !important; }

/* ── 3 · nothing performs on arrival ─────────────────────────────────────── */
/* A tab switch used to fade AND slide up over 300ms, which is a website's idea
   of an app. Native tabs simply appear. The push transition between screens
   still animates — that one is real navigation. */
html.is-native .view.active { animation: none; }
/* Restated in FULL, not just by name. `animation: none` above is the shorthand,
   so it resets duration and easing to their initial values too — and a rule
   that then sets only `animation-name` leaves the duration at 0s. The push
   still "ran": it started and ended in the same frame, fired animationend
   immediately, and tore itself down before drawing anything. Every screen
   arrived as a straight swap, which is exactly what "the animations look
   artificial" was describing. */
html.is-native .view.active.is-push {
  animation: ios-push-in .42s cubic-bezier(.32,.72,0,1) backwards;
}
html.is-native .view.active.is-pop {
  animation: ios-pop-out .36s cubic-bezier(.32,.72,0,1) forwards;
}

/* Cards no longer stagger themselves in. A list that assembles itself every
   time you land on a screen is the single most artificial thing an app can do. */
.item-card, .pl, .tm-row, .rw-row, .brand-card { animation: none !important; }

/* An empty pill is not a pill — but it is still the middle of a three-part row.
   `display: none` collapsed the flex row and slid Back and Share together into
   the corner, which reads as a broken layout rather than an absent label. It
   holds its space and gives up only its ink. */
/* The pill is retired. It existed to answer "when" over a map that had no card
   on it; the card now answers that in 30px type directly underneath, and the
   same time printed twice on one screen reads as a mistake. The top row is what
   it should have been: one button in each corner, and nothing between them. */
.dm-pill { display: none !important; }
.dm-topbar { justify-content: space-between; }

/* The two floating controls have to clear the card that now overlaps the
   bottom of the map, or they sit behind it. */
#view-order .dm:not(.no-map) .dm-note,
#view-order .dm:not(.no-map) .dm-recentre { bottom: 150px; }

/* ── rewards: make a reward look like something you can take ─────────────────
   The catalogue rows were text with a number beside them, so nothing said
   "this is a button". The cost now reads as a price tag and the whole row
   presses, and one you can afford is marked rather than left to be worked out
   by comparing two numbers. */
.rw-row { cursor: pointer; }
.rw-row .rw-cost, .rw-row .rw-val {
  background: var(--gold-c1); border-radius: 999px; padding: 6px 11px;
}
.rw-row.afford { border-color: color-mix(in srgb, var(--gold-btn) 55%, transparent); }
.rw-row.afford .rw-cost { background: var(--gold-btn); color: #fff; }
.rw-row:not(.afford) { opacity: .72; }
.rw-row:not(.afford) .rw-cost { background: #f3f3f3; color: #9a9a9a; }

/* ═══════════════════════════════════════════════════════════════════════════
   PAYMENT — to the design sheet
   ═══════════════════════════════════════════════════════════════════════════ */
.pay-options { display: grid; gap: 12px; }
.pay-option {
  display: grid; grid-template-columns: 46px 1fr 26px; align-items: center; gap: 14px;
  width: 100%; min-height: 84px; padding: 16px 16px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: none; text-align: left;
}
/* The chosen one is marked by its radio, not by a coloured card. A filled row
   plus a filled radio says the same thing twice and makes the other three look
   disabled. */
.pay-option.selected { border-color: color-mix(in srgb, var(--gold-btn) 60%, transparent); background: #fff; }

.pay-option .pay-ico {
  width: 46px; height: 34px; flex: none; display: grid; place-items: center;
  border-radius: 6px; background: none; color: var(--ink); font-size: 11px; font-weight: 800;
  overflow: hidden;
}
.pay-option .pay-ico .ic { width: 24px; height: 24px; stroke-width: 1.7; color: var(--muted); }
/* The wallets carry their own colours, as in the sheet. */
.pay-option .pay-ico.momo { background: #ffcc00; color: #1a1a1a; letter-spacing: -.02em; border-radius: 5px; }
.pay-option .pay-ico.om { background: #ff7900; color: #fff; border-radius: 5px; }
.pay-option .pay-ico.card { background: #1a4ea3; color: #fff; border-radius: 5px; }

.pay-option .t { font-size: 15.5px; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
.pay-option .d { font-size: 13px; color: var(--muted); font-weight: 400; }
.pay-option br + .d { display: inline-block; margin-top: 3px; }

/* The radio, exactly as drawn: a ring, and when chosen a gold disc with a
   white dot punched out of it. */
.pay-option .radio {
  width: 24px; height: 24px; border-radius: 50%;
  border: 1.5px solid #cfcfcf; background: #fff;
  display: grid; place-items: center; transition: all .16s cubic-bezier(.32,.72,0,1);
}
.pay-option.selected .radio { border-color: var(--gold-btn); background: var(--gold-btn); }
.pay-option .radio::after {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  background: #fff; transform: scale(0); transition: transform .16s cubic-bezier(.32,.72,0,1);
}
.pay-option.selected .radio::after { transform: scale(1); }
.pay-option:active { transform: scale(.985); }

/* The pay button, with its padlock, and the reassurance under it. */
#btn-place {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 56px; font-size: 16px; font-weight: 700;
  border-radius: 12px; letter-spacing: -.01em;
}
#btn-place .ic, #btn-place svg { width: 19px; height: 19px; stroke-width: 2; }
.pay-safe {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin-top: 12px; font-size: 12.5px; color: var(--muted);
}
.pay-safe svg { width: 15px; height: 15px; stroke-width: 1.8; }

/* ═══════════════════════════════════════════════════════════════════════════
   REWARDS — to the design sheet
   ═══════════════════════════════════════════════════════════════════════════
   The sheet stacks the card as label, number, caption, with the coin filling
   the right-hand third. Ours had the coin, the number and the label all on one
   line, which left the number no room to be the largest thing on the screen. */
.rew-top { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 14px; }
.rew-card { padding: 22px 20px; }
.rew-points {
  display: block; font-size: 44px; font-weight: 700; line-height: 1;
  letter-spacing: -.03em; color: #935601; margin: 8px 0 6px;
}
.rew-points small {
  display: block; font-size: 11.5px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--gold-deep); margin-bottom: 8px;
}
/* the coin returns to the size the sheet gives it */
.rew-coin { width: 84px; height: 84px; order: 2; margin: 0; }
.rew-phone { font-size: 13px; color: #a98e76; }

/* History and catalogue rows: an outline icon with no filled tile behind it,
   the value as a gold pill on the right. */
.rw-row .rw-ico { background: none; border: 1.5px solid var(--gold-c2); border-radius: 10px; }

/* ── the points card, laid out as the sheet draws it ─────────────────────── */
.rew-top { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 16px; }
.rew-figures { min-width: 0; }
.rew-label {
  display: block; font-size: 11.5px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--gold-deep);
}
.rew-points { display: block; font-size: 44px; font-weight: 700; line-height: 1; letter-spacing: -.03em; color: #935601; margin: 7px 0 5px; }
.rew-sub { font-size: 13px; color: #a98e76; }
.rew-phone { font-size: 12px; color: #b39c85; margin-top: 6px; }
.rew-coin { width: 88px; height: 88px; flex: none; }

.rew-qr-toggle {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; margin-top: 16px; min-height: 46px;
  background: rgba(255,255,255,.78); border: 1px solid rgba(180,131,46,.28);
  border-radius: 11px; color: #6b4d18; font-size: 14px; font-weight: 600;
}
.rew-qr-toggle svg { width: 17px; height: 17px; }
.rew-qr-toggle.open { background: #fff; }
.rew-qr[hidden] { display: none; }
.rew-qr {
  width: 168px; height: 168px; margin: 14px auto 0; padding: 10px;
  background: #fff; border-radius: 14px;
}
.rew-hint { margin-top: 12px; text-align: center; }

/* ── add-to-wallet buttons ───────────────────────────────────────────────── */
.rew-wallet { display: flex; flex-direction: column; gap: 9px; margin-top: 12px; }
.rew-wallet[hidden] { display: none; }
.wallet-btn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; min-height: 48px; border-radius: 12px;
  background: #000; color: #fff; font-size: 14.5px; font-weight: 600; border: none;
}
.wallet-btn svg { width: 19px; height: 19px; }
.wallet-btn:active { opacity: .85; }
.wallet-btn.google { background: #fff; color: #3c4043; border: 1px solid #dadce0; }

/* ═══ rewards, redesigned — code in the middle, earn & redeem ═══════════════ */
.rew-hero{display:flex;align-items:center;gap:14px;background:var(--card);border-radius:18px;
  padding:16px;margin:0 0 12px;box-shadow:0 1px 0 #efe7da,0 6px 18px rgba(60,40,10,.05)}
.rew-hero .rew-coin{width:52px;height:52px;flex:none;margin:0}
.rew-hero-figs{flex:1;min-width:0}
.rew-hero .rew-points{font-family:var(--font-display);font-weight:900;font-size:30px;line-height:1;color:#221a0e}
.rew-hero .rew-points span{font-family:var(--font-display)}
.rew-hero .rew-points small{font-family:var(--sans,inherit);font-size:12px;font-weight:700;color:var(--muted);letter-spacing:.03em;margin-left:4px}
.rew-since{font-size:12.5px;color:var(--muted);margin-top:5px}
.tier-badge{align-self:flex-start;background:linear-gradient(135deg,#fbeecb,#f3e0b6);color:#8a6417;
  font-size:11px;font-weight:800;padding:6px 11px;border-radius:999px;display:inline-flex;gap:5px;align-items:center;white-space:nowrap}
.tier-badge svg{width:13px;height:13px}
.tier-badge[data-tier="silver"]{background:linear-gradient(135deg,#eef1f4,#dfe4ea);color:#5c6673}
.tier-badge[data-tier="platinum"]{background:linear-gradient(135deg,#e7e9f0,#d3d7e6);color:#4a4f6b}

.rew-road{background:var(--card);border-radius:18px;padding:15px 16px;margin:0 0 12px;box-shadow:0 1px 0 #efe7da,0 6px 18px rgba(60,40,10,.05)}
.rew-road-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:9px}
.rew-road-top b{font-family:var(--font-display);font-weight:700;font-size:15.5px;color:#241c10}
.rew-road-top i{font-style:normal;font-weight:800;color:var(--gold-deep,#b4832e);font-size:14px}
.rew-road .rew-progress{margin:9px 0 0}

.rew-qrcard{background:var(--card);border-radius:18px;padding:20px 16px 18px;margin:0 0 12px;text-align:center;
  box-shadow:0 1px 0 #efe7da,0 6px 18px rgba(60,40,10,.05)}
.rew-qr-badge{display:inline-flex;gap:6px;align-items:center;background:var(--soft);color:#7a6a4a;
  font-size:11px;font-weight:800;letter-spacing:.02em;padding:5px 11px;border-radius:999px;margin-bottom:12px}
.rew-qr-badge svg{width:13px;height:13px}
.rew-qrcard .rew-qr{width:184px;height:184px;margin:0 auto 14px;padding:12px;background:#fff;border-radius:16px;
  box-shadow:0 6px 20px rgba(60,40,10,.14);border:1px solid #f0e7d7}
.rew-qrcard .rew-qr canvas,.rew-qrcard .rew-qr img{width:100%!important;height:100%!important;display:block}
.rew-qrcard h3{font-family:var(--font-display);font-weight:700;font-size:17px;margin:0 0 4px;color:#241c10}
.rew-qr-sub{margin:0;color:var(--muted);font-size:12.5px;line-height:1.45;padding:0 6px}
.rew-qrcard .rew-phone{justify-content:center;margin-top:10px;color:#8a8177;font-size:12.5px}
.rew-qrcard .rew-wallet{margin-top:14px}

.rew-act{display:flex;align-items:center;gap:13px;width:100%;padding:15px 16px;margin:0 0 11px;border-radius:16px;text-align:left}
.rew-act .ra-ic{width:42px;height:42px;border-radius:12px;flex:none;display:grid;place-items:center}
.rew-act .ra-ic svg{width:22px;height:22px}
.rew-act .ra-t{flex:1;min-width:0}
.rew-act .ra-t b{display:block;font-size:14.5px;font-weight:700;color:#241c10}
.rew-act .ra-t small{color:var(--muted);font-size:12px}
.rew-act .ra-go{color:#c2a878;font-size:20px}
.rew-act.invite{background:linear-gradient(120deg,#fbeecb,#f6e2ba);border:1px solid #eed9a6}
.rew-act.invite .ra-ic{background:#fff;color:var(--gold-deep,#b4832e)}
.rew-act.redeem{background:var(--card);border:1px solid var(--line)}
.rew-act.redeem .ra-ic{background:var(--soft);color:#8a6417}
.rew-act:active{transform:scale(.985)}

/* invite friends screen */
.iv-hero{background:linear-gradient(135deg,#d7a851,#c48f2d);color:#fff;border-radius:20px;padding:22px 20px;
  margin:4px 0 14px;box-shadow:0 10px 22px rgba(180,131,46,.28)}
.iv-lbl{font-size:11px;font-weight:800;letter-spacing:.14em;opacity:.9}
.iv-big{font-family:var(--font-display);font-weight:900;font-size:52px;line-height:1;margin:6px 0 10px}
.iv-big small{font-family:var(--sans,inherit);font-size:17px;font-weight:700;margin-left:6px;opacity:.95}
.iv-hero p{margin:0;font-size:13px;line-height:1.5;opacity:.95}
.iv-codecard{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:18px 16px;text-align:center;margin-bottom:14px}
.iv-code-lbl{font-size:11px;font-weight:800;letter-spacing:.14em;color:#a2977f;margin-bottom:8px}
.iv-code{font-family:var(--font-display);font-weight:900;font-size:34px;letter-spacing:.12em;color:#241c10;word-break:break-all}
.iv-copy{display:inline-flex;gap:7px;align-items:center;margin-top:14px;background:var(--soft);color:#3b2a07;
  font-weight:700;font-size:13.5px;padding:11px 18px;border-radius:999px;border:none}
.iv-copy svg{width:16px;height:16px}
.iv-share{display:flex;align-items:center;justify-content:center;gap:9px;width:100%;padding:15px;border-radius:14px;
  font-weight:700;font-size:15px;border:none;margin-bottom:10px}
.iv-share svg{width:20px;height:20px}
.iv-share.wa{background:#25d366;color:#fff}
.iv-share.ghost{background:var(--card);border:1px solid var(--line);color:#241c10}
.iv-stats{display:flex;background:#f4efe6;border-radius:14px;overflow:hidden;margin:2px 0 12px}
.iv-stat{flex:1;padding:15px;text-align:center}
.iv-stat+.iv-stat{border-left:1px solid #e7dfd0}
.iv-stat b{display:block;font-family:var(--font-display);font-weight:900;font-size:24px;color:#241c10}
.iv-stat small{color:var(--muted);font-size:12px;font-weight:600}
.iv-fine{color:#9a9084;font-size:11.5px;line-height:1.5;text-align:center;margin:2px 8px 0}

/* redeem screen */
.rd-input{width:100%;background:var(--card);border:1.5px solid #e3d8c4;border-radius:14px;padding:16px;
  font-family:var(--font-display);font-weight:700;font-size:22px;letter-spacing:.1em;color:#241c10;text-align:center}
.rd-input::placeholder{color:#c3b79c;letter-spacing:.2em}
.rd-sub{text-align:center;color:var(--muted);font-size:12.5px;margin:10px 6px 0}
.rd-out{margin:12px 0 0;padding:0;font-size:13.5px;text-align:center;font-weight:600}
.rd-out.ok{color:#186a37}
.rd-out.err{color:#b4442f}
.rd-kinds{margin-top:18px}
.rd-kind{display:flex;gap:12px;align-items:flex-start;padding:13px 16px;margin-bottom:10px;background:var(--card);border:1px solid var(--line);border-radius:14px}
.rd-kind .rd-kic{width:34px;height:34px;border-radius:9px;flex:none;display:grid;place-items:center;background:var(--soft);color:#8a6417}
.rd-kind .rd-kic svg{width:18px;height:18px}
.rd-kind b{font-size:13.5px;color:#241c10}
.rd-kind small{display:block;color:var(--muted);font-size:12px;margin-top:2px}
.rd-soon{background:#f0e7d7;color:#8a6417;font-size:10px;font-weight:800;padding:2px 7px;border-radius:6px;vertical-align:2px;margin-left:4px}

/* ── checkout: clearer address section ───────────────────────────────────── */
.co-sub {
  font-family: var(--font-display); font-size: 15.5px; font-weight: 700; color: #241c10;
  margin: 20px 0 12px; padding-top: 16px; border-top: 1px solid var(--line);
}
.co-locate {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 14px; margin-bottom: 12px;
  border-radius: 14px; background: linear-gradient(120deg,#fbeecb,#f6e2ba); border: 1px solid #eed9a6; text-align: left;
}
.co-locate .col-ic { width: 40px; height: 40px; border-radius: 11px; flex: none; display: grid; place-items: center; background: #fff; color: var(--gold-deep,#b4832e); }
.co-locate .col-ic svg { width: 22px; height: 22px; }
.co-locate .col-t { flex: 1; min-width: 0; }
.co-locate .col-t b { display: block; font-size: 14.5px; font-weight: 700; color: #241c10; }
.co-locate .col-t small { color: #8a6a3a; font-size: 12px; }
.co-locate .col-go { color: #c2a878; font-size: 20px; }
.co-locate:active { transform: scale(.99); }
.co-locate.set { background: #eefaf1; border-color: #bfe8cc; }
.co-locate.set .col-ic { color: #1c7a45; }

/* Opt-in "save this location" — deliberate, so addresses no longer pile up on
   their own. Off by default; only a checked box adds to the saved list. */
.save-addr-row {
  display: flex; align-items: center; gap: 9px; margin-top: 10px;
  font-size: 13.5px; color: #5c503f; cursor: pointer; user-select: none;
}
.save-addr-row input[type="checkbox"] {
  width: 18px; height: 18px; flex: none; accent-color: var(--gold-deep,#b4832e); cursor: pointer;
}

/* ── checkout, brought into the same language ────────────────────────────── */
/* The sticky title was drawing over the first card because the section beneath
   reserved no room for it. */
#view-checkout .topbar {
  position: sticky; top: 0; z-index: 40; background: #fff;
  margin: 0 calc(var(--gutter) * -1); padding: 8px var(--gutter) 12px;
  border-bottom: 1px solid transparent;
}
body.scrolled #view-checkout .topbar { border-bottom-color: var(--line); }

/* The numbered step badges are not on the sheet, and four gold discs down the
   left edge競 with the one gold thing that matters — the pay button. */
.co-step-num, .co-card .step-n, .co-card h3 .n { display: none !important; }
.co-card, .checkout-card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: none; padding: 16px; margin-bottom: 14px;
}
.co-card h3, .checkout-card h3 {
  font-family: var(--font-body); font-size: 16px; font-weight: 700;
  letter-spacing: -.015em; margin-bottom: 12px;
}

/* Every button on this page is either the gold action or a quiet outline. A
   black pill and a green sentence belonged to a palette this app no longer has. */
#btn-promo, .apply-btn, #btn-points-check {
  background: #fff !important; color: var(--gold-deep) !important;
  border: 1px solid var(--gold-c2) !important; border-radius: var(--r-sm);
  min-height: 46px; font-weight: 600; box-shadow: none;
}
.loc-note, .co-ok, .addr-ok { color: var(--muted) !important; font-weight: 500; }
.loc-btn, #btn-locate {
  background: #fff; color: var(--gold-deep); border: 1px solid var(--gold-c2);
  border-radius: var(--r-sm); min-height: 46px; font-weight: 600; box-shadow: none;
}
/* Scoped to the checkout. As a blanket rule this also hit the search field,
   which sits INSIDE its own grey pill — so the input drew a second white pill
   within the first, and the search bar looked like a box inside a box. */
#view-checkout input, #view-checkout textarea, #view-checkout select,
#view-signin input, #view-account input {
  border-radius: var(--r-sm) !important; border: 1px solid var(--line) !important;
  background: #fff !important;
}
#view-checkout input:focus, #view-checkout textarea:focus { border-color: var(--gold-btn) !important; outline: none; }

/* The search field is the pill's contents, not a field of its own. */
.searchbar input {
  background: none !important; border: none !important; border-radius: 0 !important;
  box-shadow: none !important; padding: 0 !important; height: auto !important;
  -webkit-appearance: none; appearance: none;
}
.searchbar input:focus { outline: none; border: none !important; }
.searchbar input::-webkit-search-cancel-button { display: none; }

/* ── the points banner, to the picture ──────────────────────────────────────
   Three things separate the sheet's banner from what I first built: the label
   is a full "balance" line rather than a bare noun, the number is set half as
   large again, and the coin is the size of a real object rather than an icon —
   it fills the right-hand third and runs close to the card's edges. */
.rew-card { padding: 24px 22px; border-radius: 18px; overflow: hidden; }
.rew-top { grid-template-columns: 1fr auto; gap: 8px; align-items: center; }
.rew-label {
  font-size: 12.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: #8a6a3a;
}
.rew-points {
  font-size: 48px; font-weight: 700; line-height: 1;
  letter-spacing: -.035em; color: #a9761f; margin: 10px 0 8px;
}
.rew-sub { font-size: 15px; color: #a98e76; font-weight: 500; }
/* Big, and allowed past the card's padding on the right the way it is drawn. */
.rew-coin {
  width: 132px; height: 132px; margin: -12px -10px -12px 0;
  filter: drop-shadow(0 8px 18px rgba(140, 96, 20, .22));
}
@media (max-width: 360px) { .rew-coin { width: 104px; height: 104px; } .rew-points { font-size: 40px; } }

/* The build stamp: quiet, at the foot of Help, where a version number belongs. */
.build-stamp {
  margin: 28px 0 10px; text-align: center;
  font-size: 11.5px; color: #c2c2c2; letter-spacing: .04em;
}

/* ── the missing home header ─────────────────────────────────────────────────
   THE BUG: #view-home gives up its top padding entirely (line ~1476), because
   the header itself was drawing the status-bar inset in its own padding. When I
   rebuilt the header for the new design I replaced that padding with a flat
   10px — so nothing reserved the inset any more. The header rendered at y=0,
   underneath the Dynamic Island, and the search bar became the first visible
   thing on the screen.

   It never showed up in testing because a desktop browser reports an inset of
   0, where the two behave identically. It only breaks on a real notch.

   The home header must therefore always carry the inset itself. */
.app-top, .home-top {
  padding-top: calc(env(safe-area-inset-top, 0px) + 10px);
}

/* ═══════════════════════════════════════════════════════════════════════════
   ORDERS — to the design sheet
   ═══════════════════════════════════════════════════════════════════════════ */
.os-sec {
  font-size: 17px; font-weight: 700; letter-spacing: -.015em;
  margin: 22px 0 12px;
}
.os-sec:first-child { margin-top: 8px; }

/* A live order: its own card, with the journey drawn out. */
.os-live {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px; margin-bottom: 6px;
}
.os-live-top { display: flex; align-items: flex-start; gap: 14px; }
.os-live-txt { flex: 1; min-width: 0; }
.os-live-txt b { display: block; font-size: 16px; font-weight: 700; letter-spacing: -.015em; }
.os-state {
  display: inline-block; margin-top: 5px;
  font-size: 13px; font-weight: 600; color: #1a7a45;
}
.os-state.st-cancelled { color: #b3261e; }
.os-state.st-delivered { color: #1a7a45; }
.os-eta-lab { display: block; margin-top: 10px; font-size: 12.5px; color: var(--muted); }
.os-eta { display: block; font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
.os-thumb {
  width: 76px; height: 76px; flex: none; border-radius: 14px; overflow: hidden;
  background: #faf7f2; display: grid; place-items: center; font-size: 30px;
}
.os-thumb img { width: 100%; height: 100%; object-fit: cover; }
.os-thumb.sm { width: 58px; height: 58px; border-radius: 12px; font-size: 24px; }

/* The four stages: filled discs joined by a rail, gold to where you are. */
.os-track { display: flex; align-items: center; margin: 18px 0 12px; }
.os-dot {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: #f4f4f4; color: #bdbdbd; border: 1px solid transparent;
}
.os-dot .ic { width: 18px; height: 18px; }
.os-dot.on { background: var(--gold-c1); color: var(--gold-deep); border-color: var(--gold-c2); }
.os-rail { flex: 1; height: 3px; border-radius: 99px; background: #ececec; }
.os-rail.on { background: var(--gold-btn); }
.os-note { font-size: 13.5px; color: var(--muted); margin-bottom: 14px; }
.os-cta {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; border-radius: 12px;
  background: var(--gold-btn); color: #fff; border: none;
  font-size: 15px; font-weight: 600;
}
.os-cta svg { width: 18px; height: 18px; }

/* History: one card, rows inside it, hairlines between. */
.os-list {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  overflow: hidden;
}
.os-row {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 13px 14px; text-align: left; background: none; border: none;
}
.os-row + .os-row { border-top: 1px solid var(--line); }
.os-row:active { background: #fbfbfb; }
.os-row-mid { flex: 1; min-width: 0; }
.os-row-mid b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.os-row-mid .os-state { margin-top: 2px; font-size: 12.5px; }
.os-row-mid small { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.os-row-end { display: flex; align-items: center; gap: 8px; flex: none; }
.os-row-end b { font-size: 15px; font-weight: 700; white-space: nowrap; }
.os-chev { color: #c9c9c9; font-size: 20px; line-height: 1; }

/* Help, as the sheet has it: the last row on the screen. */
.os-help {
  display: flex; align-items: center; gap: 13px; width: 100%;
  margin-top: 16px; padding: 15px 14px; text-align: left;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
}
.os-help-ico {
  width: 40px; height: 40px; flex: none; display: grid; place-items: center;
  border-radius: 11px; border: 1.5px solid var(--gold-c2); color: var(--gold);
}
.os-help-ico svg { width: 20px; height: 20px; }
.os-help-txt { flex: 1; min-width: 0; }
.os-help-txt b { display: block; font-size: 15px; font-weight: 700; }
.os-help-txt small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); }

/* the old row style is gone */
.tm-head, .tm-row { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   A SHOP'S OWN PAGE — to the design sheet
   ═══════════════════════════════════════════════════════════════════════════
   The identity lives in the header and nowhere else: a full-bleed band in the
   shop's own colour, and then white. That is the difference between a branded
   page and a tinted one — the earlier version washed the whole screen, which
   fought the photographs and made three shops look like three different apps. */
#view-menu { padding-top: 0; }
.bh {
  position: relative;
  margin: 0 calc(var(--gutter) * -1);
  padding: calc(env(safe-area-inset-top, 0px) + 10px) var(--gutter) 74px;
  background: var(--hero, #8c1420);
  color: #fff;
  overflow: hidden;
}
/* a soft light from the top-right, as in the sheet */
.bh::after {
  content: ''; position: absolute; top: -30%; right: -25%;
  width: 80%; height: 130%; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.16), transparent 68%);
  pointer-events: none;
}
.bh-top { display: flex; align-items: center; gap: 12px; position: relative; z-index: 2; }
.bh-back, .bh-spacer { width: 40px; height: 40px; flex: none; }
.bh-back {
  display: grid; place-items: center; border-radius: 50%;
  background: #fff; color: #1a1a1a; border: none; box-shadow: 0 2px 8px rgba(0,0,0,.18);
}
.bh-back svg { width: 20px; height: 20px; }
/* The logo, as a plate rather than a floating picture.
   Height is fixed and width follows the artwork, because the three logos have
   very different shapes — Cold Stone is nearly square, Waccao is a square tile,
   Censa is almost 2:1 wide. Forcing them all into one box either shrinks Censa's
   wordmark to something unreadable or leaves the other two swimming in space.
   Fixed height, free width, one radius: they read as a set without being
   squeezed into the same rectangle. */
.bh-logo {
  display: inline-block; margin-top: 14px;
  background: var(--logo-bg, #fff);
  /* the same radius as the card below it, so the two read as one system */
  border-radius: var(--r-lg); padding: 8px 13px;
  box-shadow: 0 8px 24px rgba(0,0,0,.30), 0 1px 0 rgba(255,255,255,.5) inset,
              0 0 0 1px rgba(0,0,0,.06);
}
.bh-logo img { display: block; height: 48px; width: auto; max-width: 190px; }
.bh-tag {
  position: relative; z-index: 2;
  margin: 16px 0 0; max-width: 62%;
  font-family: var(--font-body); font-size: 17px; font-weight: 800;
  line-height: 1.22; letter-spacing: .01em; text-transform: uppercase; color: #fff;
}
.bh-promises { position: relative; z-index: 2; display: flex; gap: 18px; margin-top: 18px; max-width: 66%; }
.bh-promise { flex: 1; min-width: 0; text-align: center; color: rgba(255,255,255,.92); }
.bh-promise-ic {
  display: grid; place-items: center; width: 40px; height: 40px; margin: 0 auto 7px;
  border-radius: 50%; border: 1.5px solid rgba(255,255,255,.45);
}
.bh-promise-ic .ic { width: 19px; height: 19px; }
.bh-promise small { display: block; font-size: 10.5px; line-height: 1.25; font-weight: 600; }

/* The white card that overlaps the band — the two facts anyone checks before
   ordering, and which branch they are ordering from. */
.bh-card {
  position: relative; z-index: 3; margin: -56px 0 18px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 10px 30px -22px rgba(20,16,10,.5);
  overflow: hidden;
}
.bh-facts { display: flex; align-items: center; padding: 14px 16px; }
.bh-fact { flex: 1; display: flex; align-items: center; gap: 11px; min-width: 0; }
.bh-fact .ic { width: 21px; height: 21px; color: var(--gold-deep); flex: none; }
.bh-fact b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.bh-fact small { display: block; font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.bh-split { width: 1px; align-self: stretch; background: var(--line); margin: 2px 12px; }
.bh-branch { border-top: 1px solid var(--line); padding: 12px 16px; }
.bh-branch .bp-row, .bh-branch > * { margin: 0; }

/* ── the loyalty banner at the foot of a menu ────────────────────────────── */
.sc-band {
  display: flex; align-items: center; gap: 12px;
  margin: 26px 0 8px; padding: 18px;
  background: linear-gradient(120deg, #faf4e9, var(--gold-c1));
  border-radius: var(--r-lg); overflow: hidden;
}
.sc-txt { flex: 1; min-width: 0; }
.sc-txt b { display: block; font-size: 16px; font-weight: 700; color: #6b4d18; line-height: 1.25; letter-spacing: -.015em; }
.sc-txt small { display: block; margin-top: 5px; font-size: 13px; color: #8a6b31; }
.sc-btn {
  margin-top: 12px; min-height: 40px; padding: 0 16px; border-radius: 10px;
  background: var(--gold-btn); color: #fff; border: none; font-size: 13.5px; font-weight: 600;
}
.sc-coin { width: 96px; height: 96px; flex: none; margin: -6px -8px -6px 0; }
/* Labels wrap to different depths ("Made fresh" vs "Joy in every spoonful"),
   which left the three icons sitting at different heights. Reserving two lines
   keeps the row level whatever the language does to the words. */
.bh-promise small { min-height: 2.5em; display: flex; align-items: flex-start; justify-content: center; }
.bh-promises { align-items: flex-start; gap: 12px; max-width: 72%; }

/* ── the shop banner ─────────────────────────────────────────────────────────
   The artwork is laid over the flat colour rather than replacing it: if the
   file is missing, or still downloading on a slow connection, the header is a
   solid brand colour rather than a grey hole. */
.bh {
  background-color: var(--hero, #8c1420);
  background-repeat: no-repeat;
  background-position: right center;
  background-size: cover;
}
/* A scrim on the left only, so the tagline and the promises stay readable over
   whatever the photograph happens to be doing there. */
.bh::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, var(--hero) 22%, color-mix(in srgb, var(--hero) 55%, transparent) 52%, transparent 78%);
  pointer-events: none;
}
.bh.has-banner::before {
  background: linear-gradient(100deg, color-mix(in srgb, var(--hero) 92%, transparent) 8%, color-mix(in srgb, var(--hero) 30%, transparent) 46%, transparent 72%);
}
.bh.has-banner .bh-logo { display: none; }
.bh.has-banner .bh-top { min-height: 40px; }
/* With the banner carrying the name, the words underneath get more room. */
.bh.has-banner .bh-tag { max-width: 74%; margin-top: 92px; }
.bh-tag, .bh-promises, .bh-top, .bh-logo { position: relative; z-index: 2; }

/* ── the banner, shown whole ─────────────────────────────────────────────────
   The artwork is a 5.4:1 strip: a logo at the left, the food at the right. Used
   as a `cover` background in a header that is nearly square, both ends get
   cropped away and all that survives is a zoomed patch of the middle — which is
   what happened. So it is an <img> at its own ratio, full width, and the header
   is built around it rather than the other way round.

   The brand colour still runs up under the clock (that is --sb), so the header
   reaches the top of the phone; the artwork simply starts below the status bar
   where nothing overlaps it. */
.bh.has-banner { background-image: none; padding-top: env(safe-area-inset-top, 0px); }
.bh-banner {
  display: block; width: 100%; height: auto;
  margin: 0 calc(var(--gutter) * -1); width: calc(100% + var(--gutter) * 2);
  position: relative; z-index: 2;
}
/* The back button gets its own row ABOVE the artwork. Floated over it, it sat
   squarely on the "COLD STONE" wordmark — the one part of a brand banner that
   must never have anything on top of it. */
.bh.has-banner .bh-top { position: relative; z-index: 3; padding: 8px 0 10px; }
.bh.has-banner .bh-logo, .bh.has-banner .bh-spacer { display: none; }
.bh.has-banner .bh-top { justify-content: flex-start; }
.bh.has-banner .bh-tag { margin-top: 16px; max-width: 78%; }
.bh.has-banner::before { content: none; }

/* ── a photographic hero (roughly 2:1, no logo baked in) ─────────────────────
   These are shot with the food on one side and empty ground on the other. The
   picture fills the header and the logo goes where that empty ground is, which
   is what the space was left for. */
.bh.has-photo {
  background-size: cover; background-position: center right;
  padding-bottom: 78px;
}
/* A wash from the left so the logo, tagline and promises hold against whatever
   the photograph is doing behind them, without dulling the food on the right. */
.bh.has-photo::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  /* Reaching further across than looks necessary on a desktop preview. The text
     column runs to about 60% of the width, and white type over a lit sundae is
     unreadable at any size — the wash has to clear the whole column, not just
     its first third. */
  background: linear-gradient(96deg,
    color-mix(in srgb, var(--hero) 97%, transparent) 0%,
    color-mix(in srgb, var(--hero) 92%, transparent) 30%,
    color-mix(in srgb, var(--hero) 68%, transparent) 52%,
    color-mix(in srgb, var(--hero) 18%, transparent) 74%,
    transparent 90%);
}
/* and a floor under the card, so the white panel does not sit on a busy photo */
.bh.has-photo::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 45%; z-index: 1;
  background: linear-gradient(to top, var(--hero) 12%, transparent);
  pointer-events: none;
}
/* Over a photograph the lockup runs down the empty left-hand side, which is
   the side these hero shots are composed to leave clear. */
.bh.has-photo .bh-top { justify-content: flex-start; gap: 14px; }
.bh.has-photo .bh-spacer { display: none; }
.bh.has-photo .bh-logo { margin-top: 10px; }
.bh.has-photo .bh-logo img { height: 44px; max-width: 168px; }
.bh.has-photo .bh-tag { max-width: 58%; margin-top: 14px; font-size: 16px; }
.bh.has-photo .bh-promises { max-width: 66%; margin-top: 16px; }

/* ═══════════════════════════════════════════════════════════════════════════
   SETTINGS — the fourth tab
   ═══════════════════════════════════════════════════════════════════════════
   Everything a customer can change about their own account, on one screen.
   Built from the cards that already exist elsewhere in the app, so it reads as
   the same product rather than a bolted-on preferences pane. */

/* A row with a label on the left and a control on the right. The same shape as
   the notification rows (.nt-row) on purpose — a language switch and a
   notification switch are the same kind of thing and should not look like two
   different designs. */
.set-row {
  display: flex; align-items: center; gap: 14px;
  padding: 4px 0;
}
.set-txt { flex: 1; min-width: 0; }
.set-txt b { display: block; font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.set-txt small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); line-height: 1.35; }

/* A two-up segmented control. Not a toggle: a toggle implies on/off, and
   neither language is the "off" one. */
.set-seg {
  display: flex; flex: none; gap: 2px; padding: 3px;
  background: #f3f3f3; border-radius: 999px;
}
.set-seg button {
  border: none; background: none; cursor: pointer;
  min-width: 46px; padding: 7px 12px; border-radius: 999px;
  font: inherit; font-size: 13px; font-weight: 700; letter-spacing: .02em;
  color: var(--muted); transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.set-seg button.on {
  background: #fff; color: var(--ink);
  box-shadow: 0 1px 3px rgba(0,0,0,.12);
}
.set-seg button:active { transform: scale(.97); }

/* A row that goes somewhere. */
/* .summary-card is flat in this design — no surface of its own — so a row that
   is meant to be TAPPED has to bring its own, or it reads as a heading. */
.set-links { padding: 0; }
.set-link {
  display: flex; align-items: center; gap: 14px; width: 100%;
  cursor: pointer; text-align: left;
  padding: 12px 14px; font: inherit; color: inherit;
  background: #fafafa; border: 1px solid var(--line); border-radius: var(--r-lg);
  transition: background .15s ease, transform .12s ease;
}
.set-link:active { background: #f2f2f2; transform: scale(.995); }
.set-link-ico {
  flex: none; width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: 50%; background: var(--gold-c1); color: var(--gold-deep);
}
.set-link-ico svg { width: 20px; height: 20px; }
.set-link-txt { flex: 1; min-width: 0; }
.set-link-txt b { display: block; font-size: 15px; font-weight: 650; }
.set-link-txt small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); }
.set-link-go { flex: none; color: #c9c9c9; font-size: 22px; line-height: 1; }

/* Signed out there is nothing to configure, so the screen says so plainly
   instead of showing a form with no account behind it. */
.set-signedout {
  text-align: center; padding: 34px 26px 30px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  margin-bottom: 14px;
}
.set-so-art {
  width: 62px; height: 62px; margin: 0 auto 14px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--gold-c1); color: var(--gold-deep);
}
.set-so-art svg { width: 28px; height: 28px; }
.set-signedout b { display: block; font-size: 18px; font-weight: 700; letter-spacing: -.02em; }
.set-signedout p {
  margin: 8px auto 18px; max-width: 30ch;
  font-size: 13.5px; color: var(--muted); line-height: 1.45;
}
.set-signedout .primary-btn { max-width: 220px; margin: 0 auto; }

/* The tab bar floats over the foot of the screen, so the last card needs room
   to clear it — without this the sign-out button sits underneath it. */
#view-settings { padding-bottom: 116px; }
/* One hairline per group. Without them six flat headings on a white page read
   as one long column of text rather than a list of separate things. */
#view-settings .summary-card + .summary-card {
  border-top: 1px solid var(--line); padding-top: 20px;
}

/* ── the sticky title needs something behind it ──────────────────────────────
   .topbar is `position: sticky` (line ~271) and the redesign later set
   `background: none` on it (line ~3680). Sticky and transparent means the page
   scrolls UNDERNEATH the title and shows straight through it — which is why a
   screen title kept appearing on top of a card, and why the header looked like
   it "disappeared" on scroll. It never moved; it just had nothing behind it.

   Painted as a short fade rather than a hard band so content dissolves under
   it instead of hitting an edge. */
.topbar {
  /* Solid, not a fade. A fade leaves the bottom third of the bar see-through,
     and content scrolling up through it still collides with the title — which
     is most of what the bug looked like. The dissolve is a short strip drawn
     BELOW the bar instead, so the bar itself is always opaque. */
  background: var(--bg, #fff);
}
.topbar::after {
  content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 14px;
  background: linear-gradient(to bottom, var(--bg, #fff), transparent);
  pointer-events: none;
}
.topbar { position: sticky; }   /* re-stated: the ::after needs a positioned host */
#view-menu > .topbar::after, #view-item > .topbar::after { content: none; }
/* The brand page draws its own top edge — a white fade over the banner would
   undo the whole point of the header reaching the top of the screen. */
#view-menu > .topbar, #view-item > .topbar { background: none; }

/* An item sold in sizes shows the cheapest size, labelled as a starting point.
   A bare number would be a promise the checkout breaks the moment someone picks
   the large one. */
.item-foot .price.from { display: inline-flex; align-items: baseline; gap: 4px; }
.item-foot .price.from small {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}

/* Where a required choice sets the price there is no price on the card — just
   a note that the size decides it. Quieter than a price, because it is not one:
   it should not compete with the real numbers on cards that do have them. */
.item-foot .price.choose, .pl-choose {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--muted);
}
.pc-price .pl-choose { color: rgba(255,255,255,.82); font-size: 11px; }

/* ---- a helping count on a mix-in ----
   Some people want two of the same thing. The row is still a toggle; the
   stepper beside it changes how many, and stops its taps reaching the row
   underneath — otherwise "+" would also switch the mix-in off. */
.oc-qty {
  display: inline-flex; align-items: center; gap: 2px; flex: none;
  margin-left: 8px; padding: 2px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent, #c89b5a) 12%, transparent);
}
.oc-qty b {
  min-width: 18px; text-align: center; font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.oc-qty .ocq {
  width: 26px; height: 26px; border: none; border-radius: 50%; cursor: pointer;
  background: #fff; color: var(--ink); font-size: 15px; line-height: 1;
  display: grid; place-items: center; box-shadow: 0 1px 2px rgba(0,0,0,.12);
}
.oc-qty .ocq:disabled { opacity: .35; box-shadow: none; cursor: default; }
.oc-qty .ocq:active:not(:disabled) { transform: scale(.92); }

/* ---- artwork that is already finished ----
   The Waccao and Censa heroes carry the logo and the strapline inside the
   picture. Drawing the app's own logo plate and tagline over the top would say
   everything twice, in two different typefaces — so on these the header IS the
   artwork, and the delivery card follows straight after it. Cold Stone's hero
   is a bare product shot, keeps its overlay, and is unaffected. */
.bh.art-full .bh-tag,
.bh.art-full .bh-promises,
.bh.art-full .bh-logo { display: none; }
/* The delivery card rides 56px up into the header. Over a photograph that is
   the point — it sits on empty ground. Over finished artwork it would slice
   through the bottom of the composition, so the header keeps just enough room
   below the picture for the card to overlap nothing but the shop's colour. */
.bh.art-full { padding-bottom: 60px; }
/* The back button gets its own row ABOVE the picture, on the shop's colour, so
   it never sits on the logo — and, more to the point, so it exists at all. The
   artwork comes first in the markup, and the delivery card overlaps the foot of
   the header, so a back row left in source order was covered by the card and
   the screen had no way out but the phone's own gesture. */
.bh.art-full { display: flex; flex-direction: column; }
.bh.art-full .bh-top { order: -1; padding: 4px 0 10px; }
.bh.art-full .bh-banner { order: 0; }

/* ── iOS zooms into any field smaller than 16px ──────────────────────────────
   Focus an input whose text is under 16px and mobile Safari scales the whole
   page up to meet it — and does not scale back when the field is dismissed. On
   a phone it reads as the app lurching sideways, and it is the single loudest
   "this is a web page" tell there is.
   The packaged app suppresses it through maximum-scale in the viewport, but
   Safari has ignored that since iOS 10, so the WEBSITE — which is what most
   customers meet first — still lurches. 16px is the only real fix.
   Declared here, last, so it applies whatever a component asked for. */
input, textarea, select {
  font-size: 16px;
}
.field input, .field textarea,
.searchbar input,
.opt-note-field input,
.ip-note-field input,
.track-input input,
.promo-row input {
  font-size: 16px;
}

/* ── the quantity stepper has to be hittable ─────────────────────────────────
   The − and + were 30×30. Apple asks for 44pt, and the reason is not pedantry:
   a 30px circle beside another 30px circle is the shape people miss, tap twice,
   and end up with two of something. The circles STAY 30px — making them bigger
   would bloat every product row — and the touch target grows past them instead,
   which is what a native stepper does. */
.qty-stepper button { position: relative; }
.qty-stepper button::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
  /* the target only, never a visual */
  border-radius: 50%;
}
/* Same for the mix-in stepper, which is built to the same size. */
.oc-qty .ocq { position: relative; }
.oc-qty .ocq::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%); border-radius: 50%;
}

/* ═══════════════════════════════════════════════════════════════════════════
   GROUPED LISTS — Settings and the Rewards hub
   ═══════════════════════════════════════════════════════════════════════════
   Settings was one long column of cards and Rewards was one long scroll, which
   is the shape of a web page, not an app. Both are now the iOS pattern people
   already know: short labelled groups of rows, each row a door to its own
   screen. The rule that makes it work is that a group is ONE card with hairline
   dividers between its children — not a stack of cards with gaps — so the eye
   reads a set of related settings, not five unrelated panels. */

.set-group-label {
  font-size: 12.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
  margin: 26px 4px 8px;
}
/* the first label on a screen sits under the title, not a screen apart from it */
#set-account > .set-group-label:first-child { margin-top: 16px; }

.set-group {
  background: var(--card); border: 1px solid var(--hair, var(--line));
  border-radius: var(--r-lg, 16px); overflow: hidden;
}
/* dividers BETWEEN rows only — a line above the first row would double the
   card's own border, which is the tell of a hand-rolled list */
.set-group > * + * { border-top: 1px solid var(--hair, var(--line)); }
.set-group > .set-row { padding: 15px 16px; }
.set-group > #notif-card { padding: 15px 16px; }
/* a card the notification code hides on unsupported browsers must not leave a
   divider hanging where it used to be */
.set-group > #notif-card[style*="display: none"] { border-top: 0; }

.set-row-link {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 13px 16px; min-height: 64px;
  background: none; border: 0; text-align: left; color: inherit; font: inherit;
  -webkit-tap-highlight-color: transparent;
  transition: background .16s ease;
}
/* the press state is the whole row, the way a table cell highlights on iOS */
.set-row-link:active { background: color-mix(in srgb, var(--ink) 6%, transparent); }
@media (hover: hover) {
  .set-row-link:hover { background: color-mix(in srgb, var(--ink) 3%, transparent); }
}

.srl-ico {
  flex: none; width: 38px; height: 38px; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--soft, #faf7f2); color: var(--ink);
}
.srl-ico svg { width: 19px; height: 19px; }
/* the rewards rows carry the gold, so the two hubs are not the same grey list */
.srl-ico.gold { background: var(--gold-c1, #f8edd9); color: var(--gold-deep, #b4832e); }

.srl-txt { flex: 1; min-width: 0; }
.srl-txt b { display: block; font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.srl-txt small {
  display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted);
  line-height: 1.35;
  /* a subtitle that wraps to three lines turns a tidy list into a paragraph */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* rows whose subtitle is filled in at runtime must not reserve an empty line */
.srl-txt small:empty { display: none; }

.srl-go {
  flex: none; font-size: 21px; line-height: 1; font-weight: 400;
  color: color-mix(in srgb, var(--muted) 65%, transparent);
}

/* sign-out is destructive-ish and belongs away from the groups, not in one */
#view-settings .signout { margin-top: 28px; width: 100%; }

/* ── Orders: find-by-code first ──────────────────────────────────────────────
   The code box used to live under the list of your own orders, which is
   backwards: someone who has their own order taps it in the list, and someone
   who does not is here precisely because they need the box. */
.ord-search {
  display: flex; align-items: center; gap: 10px;
  background: var(--card); border: 1px solid var(--hair, var(--line));
  border-radius: 999px; padding: 5px 5px 5px 16px; height: 54px;
  margin-top: 6px;
}
.os-ico { width: 18px; height: 18px; flex: none; color: var(--muted); }
.ord-search input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  /* 16px or iOS zooms the page the moment this is focused */
  font-size: 16px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink);
}
.ord-search input::placeholder {
  color: var(--muted); font-weight: 500; letter-spacing: 0; text-transform: none;
}
.os-go {
  flex: none; height: 42px; padding: 0 20px; border-radius: 999px; border: 0;
  background: var(--accent, #d7a851); color: var(--accent-ink, #fff);
  font-size: 14px; font-weight: 700; letter-spacing: -.01em;
  -webkit-tap-highlight-color: transparent;
}
.os-go:active { filter: brightness(.94); }

/* The orders empty state borrows the signed-out layout — same shape, so the
   app has one way of saying "there is nothing here yet" rather than three. */
.ord-empty { margin-top: 46px; }

/* The same shape again, inside a sheet or a short list rather than filling a
   whole tab. The card chrome is dropped because the bag sheet and the saved
   panel are already cards — a bordered white box inside a bordered white box
   reads as a rendering mistake. Padding comes down with it, so the state sits
   in the space that is there instead of forcing the sheet taller. */
.app-empty {
  background: none; border: 0; border-radius: 0;
  padding: 26px 18px 8px; margin-bottom: 0;
}
.app-empty .set-so-art { width: 56px; height: 56px; margin-bottom: 12px; }
.app-empty .set-so-art svg { width: 26px; height: 26px; }
.app-empty b { font-size: 16.5px; }
.app-empty p { margin: 7px auto 16px; font-size: 13px; }
.app-empty .primary-btn:last-child { margin-bottom: 4px; }
/* No button under it — the copy is the last thing, so it shouldn't leave the
   space a button would have filled. */
.app-empty p:last-child { margin-bottom: 2px; }
/* the rewards hub: the list is a separate thing from the points card above it */
#view-rewards .set-group { margin-top: 18px; }

/* ── Waccao's tile fills its panel ──────────────────────────────────────────
   The three brand logos are not the same kind of file. Cold Stone and Censa
   are cut-outs that need air around them, so the rail letterboxes every logo
   with `contain` and a padding. Waccao's is a square tile that carries its own
   yellow ground, and letterboxing it left white bands top and bottom — the
   brand read as a smaller, weaker card than the two beside it. Its artwork is
   the panel. */
.brand-card.waccao { padding: 0; }
.brand-card.waccao .bc-photo { object-fit: cover; }

/* One accent, everywhere. The + on a popular card was still drawing from
   --pc-pop, a per-brand salmon/amber from the reference palette that came
   before this one — three different button colours on one scrolling row. */
.pc-add { background: var(--gold-btn); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE TRACKING SCREEN
   ═══════════════════════════════════════════════════════════════════════════
   It was a map in a box with a white panel underneath and a gap between them,
   which is a web page's idea of tracking. Every app people already use for this
   does the same thing: the map IS the screen, and the status floats on top of
   it as a card you could drag. That is what this is now.                    */

/* the map runs to the bottom of the screen and the card sits over it */
#view-order .dm:not(.no-map) {
  height: 72vh; max-height: none; min-height: 420px;
  margin-bottom: -132px;                 /* the card overlaps its lower edge */
}
/* Warm, not grey. A desaturated map under a warm cream app looked like a
   different product bolted on; a light warm wash ties it to the rest. */
.dm-map .leaflet-tile-pane {
  filter: saturate(.55) contrast(.92) brightness(1.06) sepia(.14);
}

/* ── the rider, seen from above ─────────────────────────────────────────────
   The old marker was the 🛵 emoji, which is drawn in profile — a side-on
   scooter on a top-down map is the one thing on the screen facing the wrong
   way. This is drawn from above and rotates to its heading, so the marker
   points where the food is actually going. */
.dm-rider {
  /* No disc, no border, no card shadow. The scooter carries its own shading
     and its own ground shadow inside the SVG, so anything drawn around it is
     a second object competing with it — and the white plate used to sit on top
     of the blue route, breaking the line exactly where the eye follows it. */
  display: grid; place-items: center;
  width: 44px; height: 52px;
  background: none; border: 0; box-shadow: none;
}
.dm-rider-art { display: block; width: 44px; height: 52px; }
.dm-rider-art svg { width: 100%; height: 100%; display: block; }
/* the rotation itself is set inline from the bearing; this only smooths it,
   so a new position turns the scooter rather than snapping it */
.dm-rider-art { transition: transform .6s cubic-bezier(.32,.72,0,1); }

/* ── the two ends of the journey ────────────────────────────────────────────
   Two coloured dots could not say which end was the shop and which was home.
   These are pins with the glyph inside, rotated the classic 45° so the point
   sits exactly on the coordinate. */
.dm-pin {
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border-radius: 50% 50% 50% 3px;
  transform: rotate(-45deg);
  box-shadow: 0 5px 14px -6px rgba(35,25,10,.6);
  border: 2px solid #fff;
}
.dm-pin svg { width: 16px; height: 16px; transform: rotate(45deg); }
.dm-pin.shop { background: var(--gold-deep, #b4832e); color: #fff; }
.dm-pin.dest { background: #2c2620; color: #fff; }
.dm-dot-wrap { background: none; border: 0; }

/* ── the card over the map ──────────────────────────────────────────────── */
.tk-card {
  position: relative; z-index: 620;
  background: var(--card); border-radius: 26px 26px 20px 20px;
  padding: 10px 18px 16px;
  border: 1px solid var(--line);
  /* it is floating over a map, so it earns the one real shadow in the app */
  box-shadow: 0 -2px 30px -12px rgba(35,25,10,.35), 0 8px 26px -18px rgba(35,25,10,.5);
  margin-top: 0;
}
.tk-grip {
  display: block; width: 38px; height: 4px; border-radius: 999px;
  background: var(--line); margin: 0 auto 12px;
}

/* the answer to "when", in the biggest type on the screen */
.tk-hero { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.tk-hero-txt { display: block; min-width: 0; }
.tk-hero-txt small {
  display: block; font-size: 11.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.tk-hero-txt b {
  display: block; font-size: 30px; font-weight: 750; letter-spacing: -.03em;
  line-height: 1.08; color: var(--ink); margin-top: 3px;
}
.tk-hero-txt b:empty, .tk-hero-txt small:empty { display: none; }

.tk-state {
  font-size: 15px; font-weight: 700; color: var(--ink);
  margin: 12px 0 12px; letter-spacing: -.01em;
}
.dm-state-sub {
  margin-top: 10px; font-size: 12.5px; font-weight: 600;
  color: var(--muted); text-align: center;
}
.dm-state-sub:empty { display: none; }

/* ── who is bringing it ─────────────────────────────────────────────────── */
.tk-rider {
  display: flex; align-items: center; gap: 12px; margin-top: 14px;
  padding: 10px 12px; border-radius: var(--r-md);
  background: var(--soft); border: 1px solid var(--line);
}
.tk-rider[hidden] { display: none; }
.tkr-art {
  flex: none; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; background: #fff; border: 1px solid var(--line);
}
.tkr-art svg { width: 23px; height: 23px; display: block; }
.tkr-txt { flex: 1; min-width: 0; }
.tkr-txt b { display: block; font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.tkr-txt small { display: block; font-size: 12px; color: var(--muted); font-weight: 600; margin-top: 1px; }
.tkr-call {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--gold-btn); color: #fff;
}
.tkr-call svg { width: 18px; height: 18px; }
.tkr-call[hidden] { display: none; }
/* WhatsApp chat sits beside the call button, in WhatsApp's own green so it
   reads instantly as "message" rather than a second call. */
.tkr-chat { background: #25d366; }
.tkr-chat svg { width: 21px; height: 21px; }
.tkr-chat[hidden] { display: none; }

/* Proof-of-delivery code. Gold, centred, and big enough to read across a
   doorstep — this is the one thing on the screen the customer has to say out
   loud, so it leads. */
.tk-otp {
  margin-top: 14px; padding: 13px 16px 14px; border-radius: var(--r-md, 16px);
  background: color-mix(in srgb, var(--accent, #c9a35a) 13%, #fff);
  border: 1px solid color-mix(in srgb, var(--accent, #c9a35a) 42%, transparent);
  text-align: center;
}
.tk-otp[hidden] { display: none; }
.tk-otp-label {
  font-size: 11.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--gold-deep, #8a6d2f);
}
.tk-otp-code {
  font-family: var(--font-display, inherit);
  font-size: 34px; font-weight: 800; letter-spacing: .14em;
  color: var(--ink, #1d160f); margin: 3px 0 3px; line-height: 1.1;
}
.tk-otp-hint { font-size: 12.5px; font-weight: 600; color: var(--muted, #7a6f5c); }

/* the rider's number now lives on that row, so the action strip is two
   buttons wide again instead of three squeezed ones */
#view-order .tk-acts #tk-call-rider { display: none !important; }

/* ── the tracking screen has one card, in both states ───────────────────────
   The map-less presentation used to be a second card that repeated the stage,
   the time and the date above the real one. */
.dm .dm-card { display: none !important; }
.dm.no-map { border: 0; }

.tk-mark { display: none; width: 46px; height: 46px; margin: 2px 0 12px; }
/* only when there is no map — over a moving scooter it is one mark too many */
body:not(.map-top) .tk-mark { display: block; }
.tk-mark svg { width: 46px; height: 46px; display: block; }
.tk-mark .ic { color: var(--gold-deep); }

/* off the map the card is the page, not a sheet floating on one */
body:not(.map-top) .tk-card {
  border-radius: var(--r-lg); box-shadow: none; padding-top: 18px;
}
body:not(.map-top) .tk-grip { display: none; }

/* The rate bar was a dark olive-green gradient with lime stars — the last
   surviving piece of the palette before this one, and the only green object
   left in a cream and gold app. */
.rate-cta {
  background: linear-gradient(118deg, #241d14 0%, #33291c 58%, #43351f 100%);
  color: #f6efe2;
  box-shadow: 0 10px 24px -18px rgba(40,28,12,.9);
}
.rate-cta .rc-go { color: var(--gold-btn); }
.rate-cta .stars .st { color: rgba(255,255,255,.24); }
.rate-cta .stars .st.full { color: var(--gold-btn); }

/* Order matters more than markup order here: the status card is the answer and
   belongs directly under the map (or at the top, when there is no map). The
   rate prompt and anything else that needs answering follows it. In the DOM the
   mid strip sits between the two, which put "Rate your order" above the receipt
   on every delivered order. */
#track-result { display: flex; flex-direction: column; }
#track-result > .dm { order: 0; }
#track-result > .tk-card { order: 1; }
#track-result > .tk-mid { order: 2; }
#track-result > .tk-sheet-back { order: 3; }

/* ═══════════════════════════════════════════════════════════════════════════
   SPECIAL DAYS, PROPERLY
   ═══════════════════════════════════════════════════════════════════════════
   The first attempt at this changed one accent and added a banner, which is not
   a theme — it is a sticker. A day the shop dresses its window for should change
   what the app FEELS like when you open it, so each of these does four things:

     1 · repoints the whole accent family, so every button, price, active tab
         and progress bar takes the day's colour. This is the part that was
         missing: the tokens the app already reads from are what get swapped, so
         nothing downstream has to know a special day exists;
     2 · paints a full-bleed band behind the top of the home screen — a fixed
         layer, so it changes NOTHING about layout or scrolling;
     3 · gives the ground its own faint texture, drawn with gradients;
     4 · dresses the loyalty card, which is the largest single surface in the app.

   What it still refuses to do is move a control or change what anything says.
   Someone reordering their usual waffle finds it in the same place, at the same
   price, with the add button in the same corner. */

/* ── 1 · the accent family ─────────────────────────────────────────────────
   Every one of these is already read by buttons, prices, tabs and bars, so
   repointing them here dresses the whole app in one place. It is deliberately
   NOT scoped away from brand pages: on Christmas Day, Cold Stone's page should
   be Christmassy too. What stays untouched is each shop's banner colour, which
   is its identity rather than its palette. */
body.themed {
  --fest-edge-h: 26px;
  --gold:      var(--fest);
  --gold-btn:  var(--fest);
  --gold-deep: var(--fest-deep, var(--fest));
  --gold-c1:   color-mix(in srgb, var(--fest) 13%, #fff);
  --gold-c2:   color-mix(in srgb, var(--fest) 27%, #fff);
  --lux:       var(--fest);
  --lime:      var(--fest);
  --lime-ink:  var(--fest-ink, #fff);
}

/* ── 2 · the band ──────────────────────────────────────────────────────────
   A fixed layer behind the top of the home screen. Fixed and behind means it
   cannot affect layout, cannot affect scrolling, and cannot be the reason a
   list stops moving — which, given how this file has gone, is the property
   worth having. */
/* THE BAND IS THE HEADER. It used to be a separate fixed layer behind
   everything, with the header made transparent so the band could show through.
   That is what "the scroll gets mixed" was: the band never moved, the header
   never moved, and the cards scrolled through the gap BETWEEN them, so the
   greeting sat printed over a photograph of an ice cream. A sticky header that
   paints its own ground has nothing to get out of step with. */
body.themed[data-view="home"] .app-top {
  /* opaque, so the list disappears cleanly behind it */
  background-color: var(--fest-deep, var(--fest));
  -webkit-backdrop-filter: none; backdrop-filter: none;
  /* Give the band its own compositor layer. It carries FIVE stacked background
     layers — an ornament strip, a sheen, grain blended in overlay, the day's
     pattern and the colour under it — and it is sticky, so without this the
     browser repaints all five, including the blend, every frame it moves.
     Promoted, the compositor just slides the finished layer around instead.
     This is why the scroll was smooth with no theme on and rough with one. */
  transform: translateZ(0);
  will-change: transform;
  /* room under the text for the ornament to hang in */
  padding-bottom: calc(12px + var(--fest-edge-h, 26px));
  border-bottom-left-radius: 22px; border-bottom-right-radius: 22px;
}
/* the header sits ON the band, so it stops painting its own ground and its
   text flips to the band's ink */
/* --fest-band-ink, NOT --fest-ink. They are different questions: --fest-ink is
   what reads on the accent (New Year's accent is gold, so its ink is near
   black), and the band is a different surface entirely (New Year's band is
   midnight, so its ink is cream). Sharing one token put black text on a black
   band and made the greeting invisible. */
body.themed[data-view="home"] .app-top,
body.themed[data-view="home"] .home-top { color: var(--fest-band-ink, #fff); }
body.themed[data-view="home"] .at-user-txt b,
body.themed[data-view="home"] .at-user-txt small { color: var(--fest-band-ink, #fff); }
body.themed[data-view="home"] .at-btn {
  background: rgba(255,255,255,.16); color: var(--fest-band-ink, #fff);
  border-color: rgba(255,255,255,.28);
}
/* The band runs to the very top of the phone, so the strip behind the clock
   must get out of its way — otherwise a cream bar sits across a midnight
   header, which is the same seam the brand banners had. */
body.themed[data-view="home"]::before { background: transparent; }
/* The search field now sits on paper below the header rather than on the band,
   so it keeps its ordinary border and needs no clearance hack. The 34px gap
   that used to be here existed only to stop the next card covering an ornament
   drawn by a different element; the ornament lives inside the header now. */
body.themed[data-view="home"] .searchbar { margin-top: 14px; }

/* ── 3 · the ground ────────────────────────────────────────────────────────
   A texture at an opacity you notice only if you look for it. Anything you can
   see clearly behind a menu is a texture that will be hated by March. */
body.themed:not([data-brand]) {
  background-color: var(--fest-tint, var(--bg));
  background-image: var(--fest-texture, none);
  /* NOT `background-attachment: fixed`.
     Fixed means the texture must stay put while the content moves over it,
     which the browser can only do by repainting the background on every single
     scroll frame — it is the single most reliable way to make a page stutter,
     and worst of all in Android's WebView. It was also the reason the home
     screen scrolled smoothly with no theme on and badly with one.
     The texture is at an opacity you can barely see; scrolling with the page
     instead of under it is not a difference anyone will notice, and it costs
     nothing. */
  background-attachment: scroll;
}

/* ── 4 · the loyalty card ──────────────────────────────────────────────────*/
body.themed .rew-card {
  background: var(--fest-card, linear-gradient(140deg, var(--fest-deep, var(--fest)), var(--fest)));
}
body.themed .rew-card .loy-fill { background: rgba(255,255,255,.85); }

/* the one hairline everybody sees all the time */
body.themed .tabbar { border-top-color: color-mix(in srgb, var(--fest) 30%, var(--line)); }

/* ── CHRISTMAS ─────────────────────────────────────────────────────────────
   Cranberry against pine, and a candy stripe running through the band at an
   opacity that reads as fabric rather than as a pattern. */
body[data-theme="christmas"] {
  --fest-bar: #14432c;
  --fest-band-ink: #ffffff;
  --fest-band-2: repeating-linear-gradient(125deg, rgba(255,255,255,.06) 0 13px, transparent 13px 26px);
  --fest-band: linear-gradient(140deg, #14432c 0%, #1d5c3a 42%, #8f1a22 100%);
  --fest-texture: radial-gradient(circle at 12% 18%, rgba(29,92,58,.045) 0 2px, transparent 2px),
                  radial-gradient(circle at 62% 74%, rgba(179,36,44,.04) 0 2px, transparent 2px);
  --fest-card: linear-gradient(140deg, #14432c 0%, #1d5c3a 46%, #8f1a22 100%);
}
body[data-theme="christmas"] .rew-card { color: #fdf6ea; }
/* a garland along the foot of the band */
body[data-theme="christmas"][data-view="home"]::after {
  border-bottom: 3px solid transparent;
  border-image: repeating-linear-gradient(90deg, #d8b24a 0 14px, #b3242c 14px 28px, #1d5c3a 28px 42px) 3;
}

/* ── NEW YEAR ──────────────────────────────────────────────────────────────
   Midnight and gold, with the band lit from the bottom the way a night sky is
   lit from a city. */
body[data-theme="newyear"] {
  --fest-bar: #100c07;
  --fest-band-ink: #f6e6c4;
  --fest-band-2: radial-gradient(120% 90% at 50% 118%, rgba(228,184,92,.45) 0%, transparent 62%);
  --fest-band: linear-gradient(160deg, #100c07 0%, #241a10 52%, #3a2a17 100%);
  --fest-texture: radial-gradient(circle at 22% 30%, rgba(201,163,90,.055) 0 1.6px, transparent 1.6px),
                  radial-gradient(circle at 78% 66%, rgba(201,163,90,.045) 0 1.6px, transparent 1.6px);
  --fest-card: radial-gradient(120% 100% at 50% 120%, rgba(228,184,92,.5) 0%, transparent 60%),
               linear-gradient(160deg, #100c07 0%, #2a1e12 100%);
}
body[data-theme="newyear"] .rew-card { color: #f6e6c4; }

/* ── VALENTINE'S ───────────────────────────────────────────────────────────*/
body[data-theme="valentines"] {
  --fest-bar: #8e2347;
  --fest-band-ink: #ffffff;
  --fest-band-2: radial-gradient(90% 120% at 82% -10%, rgba(255,255,255,.24) 0%, transparent 60%);
  --fest-band: linear-gradient(140deg, #8e2347 0%, #c33d63 52%, #e0748f 100%);
  --fest-texture: radial-gradient(circle at 18% 26%, rgba(210,71,107,.05) 0 3px, transparent 3px),
                  radial-gradient(circle at 70% 70%, rgba(210,71,107,.038) 0 3px, transparent 3px);
  --fest-card: linear-gradient(140deg, #8e2347 0%, #c33d63 60%, #e0748f 100%);
}
body[data-theme="valentines"] .rew-card { color: #fff2f5; }

/* ── THE THREE NATIONAL DAYS ───────────────────────────────────────────────
   The flag is a band of three equal parts, and the app wears it as one: green
   ground, and the red and yellow as a rule along the bottom edge rather than
   three blocks of colour behind a greeting nobody could then read. */
body[data-theme="youth"],
body[data-theme="national"],
body[data-theme="unification"] {
  --fest-bar: #0f5230;
  --fest-band-ink: #ffffff;
  --fest-band: linear-gradient(140deg, #0f5230 0%, #1f7a45 60%, #2d9b5a 100%);
  --fest-texture: radial-gradient(circle at 16% 24%, rgba(31,122,69,.05) 0 2px, transparent 2px),
                  radial-gradient(circle at 74% 68%, rgba(230,184,0,.04) 0 2px, transparent 2px);
  --fest-card: linear-gradient(140deg, #0f5230 0%, #1f7a45 100%);
}
body[data-theme="youth"] .rew-card,
body[data-theme="national"] .rew-card,
body[data-theme="unification"] .rew-card { color: #f1faf4; }
body[data-theme="youth"][data-view="home"]::after,
body[data-theme="national"][data-view="home"]::after,
body[data-theme="unification"][data-view="home"]::after {
  border-bottom: 4px solid transparent;
  border-image: linear-gradient(90deg, #1f7a45 0 33.33%, #c0392b 33.33% 66.66%, #e6b800 66.66%) 4;
}

/* The strip is gone. The greeting at the top of the band now reads "Merry
   Christmas 🎄" in the largest type on the screen, and a banner underneath it
   saying "Merry Christmas 🎄" was the app telling you twice. */
body.themed .theme-strip { display: none !important; }

/* hearts drift UP, slowly, and there are far fewer of them than there are
   snowflakes — a screen full of hearts is a greetings card, not an app */
.motif.heart {
  background: none; border-radius: 0;
  width: calc(var(--s, 5px) * 2.2); height: calc(var(--s, 5px) * 2.2);
  top: auto; bottom: -20px;
  animation-name: motif-rise;
}
.motif.heart::before, .motif.heart::after {
  content: ''; position: absolute; width: 60%; height: 90%;
  background: var(--fest, #d2476b); border-radius: 50% 50% 0 0;
}
.motif.heart::before { left: 50%; transform: translateX(-100%) rotate(-45deg); transform-origin: 100% 100%; }
.motif.heart::after  { left: 50%; transform: rotate(45deg); transform-origin: 0 100%; }


/* ═══════════════════════════════════════════════════════════════════════════
   SPECIAL DAYS · THE SECOND PASS — making it not look cheap
   ═══════════════════════════════════════════════════════════════════════════
   A flat gradient with a rounded corner is what a theme looks like when nobody
   spent any time on it, and it reads as cheap for a reason you can name: real
   printed things have GRAIN, real decoration has DEPTH, and a designed edge is
   never a plain straight line. So every band now carries four layers instead of
   one — a swept highlight, a fine grain, an ornament along its edge, and the
   colour underneath — and the edge itself is an ellipse rather than a rectangle
   with soft corners. All of it is drawn in CSS and inline SVG: no image files,
   nothing extra to download, and it still costs one fixed element. */

/* ── the shared build of every band ──────────────────────────────────────── */
body.themed[data-view="home"] .app-top {
  /* FIVE layers, and the count is load-bearing. background-repeat/-size/
     -position/-blend-mode are per-layer lists that CYCLE when they are shorter
     than the layer list — so a theme whose --fest-band happened to hold two
     gradients pushed the stack to five, every list fell out of step by one, and
     the base colour got tiled at 60×20px and blended as grain. The band went
     grey and it looked like a blend-mode bug. Each theme therefore gets exactly
     two slots of its own, --fest-band-2 for a pattern and --fest-band for the
     colour, and both must be a SINGLE layer. */
  background:
    /* 1 · the ornament, sitting on the bottom edge */
    var(--fest-edge, none),
    /* 2 · a slow highlight that crosses the band and leaves it alone again */
    linear-gradient(102deg, transparent 32%, rgba(255,255,255,.13) 47%, transparent 62%),
    /* 3 · grain — the layer that does the most work for how expensive a flat
           colour looks, at an opacity you cannot consciously see */
    var(--grain),
    /* 4 · the day's own pattern: a candy stripe, a glow from below, a bokeh */
    var(--fest-band-2, none),
    /* 5 · the colour underneath all of it */
    var(--fest-band, linear-gradient(135deg, var(--fest), var(--fest-deep, var(--fest))));
  background-repeat: repeat-x, no-repeat, repeat, no-repeat, no-repeat;
  background-position: bottom center, 0 0, 0 0, 0 0, 0 0;
  background-size: var(--fest-edge-size, 60px 20px), 260% 100%, 140px 140px, cover, cover;
  /* The grain has to BLEND, not cover: laid on normally it is a grey rectangle
     over the artwork. Overlay keeps the hue underneath and disturbs only the
     surface. */
  background-blend-mode: normal, normal, overlay, normal, normal;
  /* AFTER the shorthand, which resets it. The five layers do cover the bar, but
     only as long as all five are present — one theme shipping a --fest-band of
     `none` would leave a transparent header with the list showing through it,
     which is the exact bug this rewrite exists to kill. An opaque colour
     underneath makes that impossible. */
  background-color: var(--fest-deep, var(--fest));
  animation: band-sheen 9s cubic-bezier(.45,.05,.3,1) 1.4s infinite;
}
@keyframes band-sheen {
  0%, 62%  { background-position: bottom center, -80% 0, 0 0, 0 0, 0 0; }
  92%,100% { background-position: bottom center, 180% 0, 0 0, 0 0, 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  body.themed[data-view="home"] .app-top { animation: none; }
}

/* One grain, defined once. feTurbulence at a high base frequency is film grain;
   desaturated and held at a low opacity it reads as paper stock rather than as
   noise, and it is what stops a two-stop gradient looking like a screenshot of
   a gradient. */
body.themed {
  --grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23g)' opacity='0.09'/></svg>");
}

/* ── the greeting, set like a greeting ──────────────────────────────────────
   "Merry Christmas" in the same 22px sans as "Welcome" is the app refusing to
   dress up. On a special day it goes into the display face, larger, with the
   day's own mark beside it. */
body.themed[data-view="home"] .at-user-txt b {
  font-family: var(--font-display);
  font-size: 27px; font-weight: 700; letter-spacing: -.02em; line-height: 1.05;
  white-space: normal; text-wrap: balance;
  text-shadow: 0 1px 12px rgba(0,0,0,.22);
}
body.themed[data-view="home"] .at-user-txt small:not(.fest-note) {
  display: block; margin-top: 3px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  opacity: .78;
}
/* the shop's own line is a sentence, not a label — uppercasing it turned a
   friendly note into signage */
/* ...and it needs the same specificity as the rule it was carved out of, or
   `.at-user-txt small { display: none }` from the design block takes it back. */
body.themed[data-view="home"] .at-user-txt .fest-note {
  display: block; text-transform: none; letter-spacing: 0; text-align: left;
}
body.themed[data-view="home"] .at-user-txt .fest-note:empty { display: none; }
body.themed[data-view="home"] .at-user { align-items: flex-start; }

/* ── CHRISTMAS · a garland strung along the bottom of the band ───────────── */
body[data-theme="christmas"] {
  /* Bunting, not a garland. A swag of wire with three 3px baubles on it is
     legible at 200% in a design tool and reads as a gold zigzag on a phone —
     the shapes have to survive being 22 pixels tall on a moving screen, and
     flags do while beads do not. */
  --fest-edge: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='66' height='26' viewBox='0 0 66 26'><path d='M0 3.2h66' stroke='%23f0e6cf' stroke-width='1.7'/><path d='M2 3.5h19l-9.5 17z' fill='%23c62b33'/><path d='M23.5 3.5h19L33 20.5z' fill='%23e6c05c'/><path d='M45 3.5h19l-9.5 17z' fill='%232e7a4e'/></svg>");
  --fest-edge-size: 66px 26px; --fest-edge-h: 26px;
}

/* ── NEW YEAR · foil, and a rule of small diamonds ──────────────────────── */
body[data-theme="newyear"] {
  /* a struck gold rule with a diamond on the beat — the New Year band is the
     one that should read as pressed foil rather than as decoration */
  --fest-edge: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='44' height='20' viewBox='0 0 44 20'><path d='M0 14.5h44' stroke='%23c9a35a' stroke-width='1.4' opacity='0.85'/><path d='M22 6.5l4.6 5-4.6 5-4.6-5z' fill='%23e4b85c'/><path d='M4 10.9l2.4 2.6L4 16.1l-2.4-2.6z' fill='%23c9a35a' opacity='0.8'/><path d='M40 10.9l2.4 2.6L40 16.1l-2.4-2.6z' fill='%23c9a35a' opacity='0.8'/></svg>");
  --fest-edge-size: 44px 20px; --fest-edge-h: 20px;
}
/* the loyalty card gets the same foil sweep as the band */
body[data-theme="newyear"] .rew-card { position: relative; overflow: hidden; }
body[data-theme="newyear"] .rew-card::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(104deg, transparent 36%, rgba(228,184,92,.22) 50%, transparent 64%);
  background-size: 260% 100%;
  animation: band-sheen 7s cubic-bezier(.45,.05,.3,1) 2s infinite;
}

/* ── VALENTINE'S · a scalloped lace hem ─────────────────────────────────── */
body[data-theme="valentines"] {
  /* a lace hem: deep scallops with a pricked dot in each, in the cream the
     rest of the app is built on rather than in white */
  --fest-edge: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='22' viewBox='0 0 40 22'><path d='M0 22V9a10 10 0 0 1 20 0a10 10 0 0 1 20 0v13z' fill='%23faf7f2'/><circle cx='10' cy='13' r='2' fill='%23c33d63' opacity='0.55'/><circle cx='30' cy='13' r='2' fill='%23c33d63' opacity='0.55'/></svg>");
  --fest-edge-size: 40px 22px; --fest-edge-h: 22px;
}

/* ── THE NATIONAL DAYS · a woven ribbon, not a painted stripe ──────────── */
body[data-theme="youth"],
body[data-theme="national"],
body[data-theme="unification"] {
  /* At 14px tall the woven ribbon was a coloured smudge. Same bunting the other
     days wear, in the flag's own three colours and its own order. */
  --fest-edge: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='66' height='26' viewBox='0 0 66 26'><path d='M0 3.2h66' stroke='%23ffffff' stroke-width='1.7' opacity='0.75'/><path d='M2 3.5h19l-9.5 17z' fill='%231f7a45'/><path d='M23.5 3.5h19L33 20.5z' fill='%23c0392b'/><path d='M45 3.5h19l-9.5 17z' fill='%23e6b800'/></svg>");
  --fest-edge-size: 66px 26px; --fest-edge-h: 26px;
}
/* the flat border rule from the first pass is replaced by the ribbon above */
body[data-theme="youth"][data-view="home"]::after,
body[data-theme="national"][data-view="home"]::after,
body[data-theme="unification"][data-view="home"]::after,
body[data-theme="christmas"][data-view="home"]::after { border-bottom: 0; border-image: none; }

/* ── the motifs, drawn rather than dotted ──────────────────────────────────
   A snowflake is not a circle, and the difference between the two is most of
   the difference between "themed" and "decorated". These are real six-armed
   flakes and four-point sparkles, each turning at its own rate as it falls. */
.motif.flake, .motif.spark {
  background: none; box-shadow: none; border-radius: 0;
  width: calc(var(--s, 5px) * 2.6); height: calc(var(--s, 5px) * 2.6);
}
.motif.flake i, .motif.spark i, .motif.flake::before, .motif.spark::before {
  content: ''; display: block; width: 100%; height: 100%;
  background: var(--glyph) center / contain no-repeat;
  animation: motif-turn var(--spin-slow, 7s) linear infinite;
}
.motif.flake {
  --glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e7f0fa' stroke-width='1.5' stroke-linecap='round'><path d='M12 1.5v21M2.9 6.75l18.2 10.5M2.9 17.25l18.2-10.5'/><path d='M12 5.2 9.6 7.6M12 5.2l2.4 2.4M12 18.8l-2.4-2.4M12 18.8l2.4-2.4M4.7 8.6l3.3.1M4.7 8.6l.1 3.3M19.3 15.4l-3.3-.1M19.3 15.4l-.1-3.3M4.8 15.4l.1-3.3M4.8 15.4l3.3-.1M19.2 8.6l-.1 3.3M19.2 8.6l-3.3.1'/></svg>");
  filter: drop-shadow(0 0 4px rgba(210,225,245,.55));
}
.motif.spark {
  --glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 0c1.15 7.6 3.25 10.85 12 12-8.75 1.15-10.85 4.4-12 12-1.15-7.6-3.25-10.85-12-12C8.75 10.85 10.85 7.6 12 0z' fill='%23e4b85c'/></svg>");
  filter: drop-shadow(0 0 5px rgba(228,184,92,.6));
}
@keyframes motif-turn { to { transform: rotate(360deg); } }

/* ── the shop tiles wear a small ornament ─────────────────────────────────
   Three white squares in a row is the most neutral thing on the home screen,
   and on a dressed day the most obviously undressed. A corner mark is enough:
   the logo still has to be the thing you see. */
/* A corner ribbon on all three read as three sale tags — the shop's own logo
   has to stay the loudest thing in the tile. A hairline in the day's colour and
   the faintest glow underneath is enough to say the tiles belong to the day. */
body.themed .brand-card {
  border-color: color-mix(in srgb, var(--fest) 32%, var(--line));
  box-shadow: 0 6px 18px -14px color-mix(in srgb, var(--fest) 70%, transparent);
}

/* ── section headings get the day's rule ──────────────────────────────────*/
body.themed .sec-head h2::before,
body.themed .home-sec h2::before { background: var(--fest); }


/* ═══════════════════════════════════════════════════════════════════════════
   SPECIAL DAYS · THIRD PASS — past the top of the screen
   ═══════════════════════════════════════════════════════════════════════════
   A band across the header is a hat. A shop that dresses its window does not
   stop at the lintel: there is something on the counter, something on the door,
   something above the till. Each of these takes one more surface the customer
   actually looks at, and none of them moves a control or costs a request. */

/* ── no second garland ─────────────────────────────────────────────────────
   The same bunting used to hang above the tab bar so the screen was "framed top
   and bottom". Framed is what it looked like empty. In use it is a strip
   permanently across the bottom of a moving list, and every card slid behind it
   half-cut — which is most of what "the theme mixes everything up" meant. One
   ornament, on the header, where nothing scrolls past it. */
/* NO `position` here. .tabbar is `position: fixed; bottom: 0` and a bare
   `.tabbar { position: relative }` at the end of the file outranks nothing but
   still wins on source order — which dropped the tab bar out of its fixed
   corner and left it sitting at the top of the home screen. A fixed element is
   already a containing block for its absolutely positioned children, so the
   rule bought nothing and cost the whole layout. Third time a stray `position`
   has broken something structural in this file; there is now a test for it. */

/* ── the tab bar itself ────────────────────────────────────────────────────
   The active tab gets a soft pool of the day's colour behind it. Nothing moves
   and nothing changes size — the target stays exactly where the thumb learned
   it — but the bar stops being the one neutral strip on a dressed screen. */
body.themed .tb { position: relative; }
body.themed .tb.active::before {
  content: ''; position: absolute; left: 50%; top: 2px;
  width: 46px; height: 34px; margin-left: -23px; border-radius: 12px;
  background: color-mix(in srgb, var(--fest) 13%, transparent);
  z-index: -1;
}

/* ── every section heading wears the day's mark ────────────────────────────
   Small, before the text, in the day's colour: a tree, a star, a heart, a
   ribbon. It is the detail that says somebody dressed this on purpose rather
   than dropping a banner in at the top. */
body.themed .sec-head h2::before,
body.themed .cat-heading::before {
  content: ''; display: inline-block; vertical-align: -2px;
  width: 15px; height: 15px; margin-right: 7px;
  background: var(--fest-mark, none) center / contain no-repeat;
}
body[data-theme="christmas"] {
  --fest-mark: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%232e7a4e'><path d='M12 1.5 6.2 9h2.6L4.4 15h3.2L3 21h18l-4.6-6h3.2L15.2 9h2.6z'/><rect x='10.6' y='20.2' width='2.8' height='3' fill='%238a5a2b'/></svg>");
}
body[data-theme="newyear"] {
  --fest-mark: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 0c1.15 7.6 3.25 10.85 12 12-8.75 1.15-10.85 4.4-12 12-1.15-7.6-3.25-10.85-12-12C8.75 10.85 10.85 7.6 12 0z' fill='%23c9a35a'/></svg>");
}
body[data-theme="valentines"] {
  --fest-mark: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 21S3 14.6 3 8.9A5 5 0 0 1 12 6a5 5 0 0 1 9 2.9C21 14.6 12 21 12 21z' fill='%23c33d63'/></svg>");
}
body[data-theme="youth"],
body[data-theme="national"],
body[data-theme="unification"] {
  --fest-mark: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 3v18' stroke='%23155c33' stroke-width='2.4' stroke-linecap='round'/><path d='M6 4h5.5v6H6z' fill='%231f7a45'/><path d='M11.5 4H17v6h-5.5z' fill='%23c0392b'/><path d='M17 4h4.5v6H17z' fill='%23e6b800'/></svg>");
}

/* ── the shop's own page is dressed too ───────────────────────────────────
   Walking into Cold Stone on Christmas Eve and finding it undecorated is worse
   than never dressing the app at all. The bunting hangs under the brand banner,
   where the banner's own colour ends — so the shop keeps its identity and the
   day is laid over it rather than replacing it. */
body.themed .bh { position: relative; }
body.themed .bh::before {
  content: ''; position: absolute; left: 0; right: 0; top: calc(env(safe-area-inset-top, 0px) + 44px);
  height: 26px; z-index: 3; pointer-events: none;
  background: var(--fest-edge, none) repeat-x top center;
  background-size: var(--fest-edge-size, 66px 26px);
  opacity: .95;
}

/* ── the rewards screen gets the weather too ──────────────────────────────*/
body.themed[data-view="rewards"] .motif-layer { display: block; }

/* ── the loading shimmer takes the day's light ────────────────────────────*/
body.themed .sk::after {
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--fest) 16%, rgba(255,255,255,.7)), transparent);
}

/* ── a line the shop writes itself ────────────────────────────────────────
   The one part of a special day that is not decoration: "Open until 10pm on the
   24th", "Free delivery all weekend". It sits under the greeting, on the band,
   and it is empty unless somebody has written something. */
.fest-note {
  display: block; margin-top: 6px; max-width: 30ch;
  font-size: 12.5px; font-weight: 600; line-height: 1.35;
  color: var(--fest-band-ink, #fff); opacity: .82;
}
.fest-note:empty { display: none; }


/* ═══════════════════════════════════════════════════════════════════════════
   MOTION — the four moves that separate an app from a page
   ═══════════════════════════════════════════════════════════════════════════
   None of these is decoration. Each one answers a question the customer would
   otherwise have to work out from a static screen: where am I in this list,
   did that tap land, which way did the categories just move, and is the app
   awake yet. That is the test every animation here had to pass. */

/* ── 1 · the large title collapses ─────────────────────────────────────────
   --collapse runs 0 → 1 over the first 64px of scroll, set from JavaScript and
   spent entirely here. The greeting shrinks toward the size of a navigation
   title rather than a header that just gains a shadow, so the top of the screen
   tells you how far down the list you are without a scrollbar. */
.app-top { --collapse: 0; }

/* WHY THIS IS ALL transform AND opacity.
   It was not. The greeting animated `font-size`, the sub-lines animated
   `max-height`, and the bar animated `padding-bottom` — and --collapse is
   rewritten on every scroll frame. Every one of those is a LAYOUT property, so
   each frame relaid out the header and reflowed the entire home list beneath
   it. On a desktop that is invisible. On a mid-range Android phone it is the
   stutter, and no amount of throttling the JavaScript could have fixed it,
   because the cost was not in the JavaScript.
   transform and opacity are composited: they never touch layout, never touch
   paint, and the whole collapse now runs on the GPU. The look is unchanged. */

#view-home .at-user-txt b {
  /* 22px at rest shrinking toward ~17px, done as a scale from the left edge so
     the text still grows out of the same point it used to. */
  transform: scale(calc(1 - 0.23 * var(--collapse)));
  transform-origin: left center;
  transition: none;                 /* the value IS the animation */
  will-change: transform;
}
body.themed[data-view="home"] .at-user-txt b {
  /* A special day starts larger, so it has further to travel. */
  transform: scale(calc(1 - 0.33 * var(--collapse)));
}

/* The sub-lines fade out first, so the title is the last thing to go. They also
   slide up as they fade, which is what `max-height` used to do by collapsing
   the box — except this costs nothing. The box keeps its height; the content
   moves out of it. */
#view-home .at-user-txt small,
#view-home .fest-note {
  opacity: calc(1 - 1.6 * var(--collapse));
  transform: translateY(calc(-6px * var(--collapse)));
  will-change: opacity, transform;
}

/* The bar itself no longer tightens. Animating its padding was the single most
   expensive thing on this screen — it changed the header's height on every
   frame, which moves every card in the list below it. It now keeps one height,
   and the shrinking title does the work of saying you have scrolled. */
#view-home .app-top { padding-bottom: 10px; }
body.themed[data-view="home"] #view-home .app-top {
  padding-bottom: calc(10px + var(--fest-edge-h, 26px));
}
@media (prefers-reduced-motion: reduce) {
  #view-home .at-user-txt b { transform: none; }
  #view-home .at-user-txt small, #view-home .fest-note { opacity: 1; transform: none; }
}

/* ── 2 · the tab icon kicks ────────────────────────────────────────────────
   90ms. Long enough to feel, too short to wait for. A tab that only recolours
   reads as a link; the kick is what makes it read as a button that fired. */
.tb svg.kick { animation: tb-kick .32s cubic-bezier(.34,1.4,.5,1); }
@keyframes tb-kick {
  0%   { transform: scale(1); }
  35%  { transform: scale(.82) translateY(1px); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .tb svg.kick { animation: none; } }

/* ── 3 · one pill travels between the categories ───────────────────────────
   The filled background moves off .cat-tab.active and onto a single element
   that slides between them. Sliding says WHICH WAY the list just moved; two
   pills appearing and disappearing says only that something changed.

   This block used to open with `.cat-tabs { position: relative }`, to give the
   pill something to be absolute against. It did — and it also silently undid
   the `position: sticky` set further up, because both are just `position` and
   this rule comes later. The category strip stopped pinning the day the pill
   was added, and on a sixty-item menu it scrolled away with everything else.
   `sticky` is already a positioned ancestor, so the pill never needed this. */
.cat-tabs {
  /* Under the pinned brand bar, which is fixed and 54px tall plus the notch. */
  position: sticky;
  top: calc(env(safe-area-inset-top, 0px) + 54px);
}
.cat-pill {
  position: absolute; left: 0; top: 50%; margin-top: -17px;
  border-radius: 999px; background: var(--accent); z-index: 0;
  transition: transform .34s cubic-bezier(.32,.72,0,1), width .34s cubic-bezier(.32,.72,0,1);
  pointer-events: none;
}
.cat-tab { position: relative; z-index: 1; }
/* the active tab keeps its ink and gives up its own background to the pill */
.cat-tab.active { background: transparent !important; border-color: transparent !important; color: var(--accent-ink); }
@media (prefers-reduced-motion: reduce) { .cat-pill { transition: none; } }

/* ── 4 · the app arrives, once ─────────────────────────────────────────────
   Not a stagger on every render — that was tried and removed, and it was right
   to remove it: a list that reassembles itself every time you land on a screen
   is the most artificial thing an app can do. This runs ONCE, on the first
   paint after launch, and it exists to cover the hand-off from the splash
   screen so the app appears rather than cuts. */
html.first-run #view-home.active > *:nth-child(-n+4) {
  animation: arrive .5s cubic-bezier(.22,.78,.28,1) backwards;
}
html.first-run #view-home.active > *:nth-child(1) { animation-delay: .02s; }
html.first-run #view-home.active > *:nth-child(2) { animation-delay: .07s; }
html.first-run #view-home.active > *:nth-child(3) { animation-delay: .12s; }
html.first-run #view-home.active > *:nth-child(4) { animation-delay: .17s; }
@keyframes arrive {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html.first-run #view-home.active > * { animation: none; }
}

/* An image that has failed twice is not shown at all — see the error handler in
   app.js. `visibility` rather than `display` so it keeps its box and nothing
   below it jumps as the failure lands. */
img.img-dead { visibility: hidden; }
/* ...and the slots that hold product photos have a ground of their own to fall
   back to, so what you see is an empty plate rather than a hole. */
.item-visual.has-photo:has(img.img-dead),
.pc:has(img.img-dead) .pc-art,
.ip-art:has(img.img-dead) {
  background: linear-gradient(150deg, var(--soft), color-mix(in srgb, var(--soft) 60%, var(--card)));
}


/* ── the photo drifts as the sheet slides over it ──────────────────────────
   The one piece of the product-page pass that stays. It is not a layout change
   — nothing moves, nothing is re-arranged, no control is anywhere different.
   --ip-scroll runs 0 → 1 over the height of the hero and the image moves at
   roughly half the speed of the page, which is what tells the eye the photo is
   BEHIND the content rather than the first paragraph of it. That is a scroll
   feeling, and the scroll feeling is the part that was wrong. */
#view-item .ip-hero { --ip-scroll: 0; }
#view-item .ip-art img {
  transform: translate3d(0, calc(24px * var(--ip-scroll)), 0) scale(calc(1 + .05 * var(--ip-scroll)));
  transform-origin: center 30%;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  #view-item .ip-art img { transform: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   CHECKOUT — less to look at, same number of answers
   ═══════════════════════════════════════════════════════════════════════════
   The form asked for six things and showed about twenty. Two banner cards
   before the first field, a map most people never touch taking a third of a
   screen, two numbered sections for two optional discounts, and "Place order"
   at the bottom of all of it — so the last thing you did before paying was
   scroll past everything you had already filled in.

   Nothing has been removed. The optional parts are closed until they are
   wanted, which is a different thing from being gone. */

/* ── the two facts, on one line ────────────────────────────────────────────*/
.co-facts {
  display: flex; align-items: center; gap: 10px;
  margin: 4px 0 14px; padding: 0 2px;
  font-size: 13px; color: var(--muted); font-weight: 600;
}
.co-facts .cof { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.co-facts .cof b { color: var(--ink); font-weight: 700; white-space: nowrap; }
.co-facts svg, .co-facts .ic { width: 15px; height: 15px; flex: none; opacity: .75; }
.cof-sep { width: 3px; height: 3px; border-radius: 50%; background: var(--muted); flex: none; opacity: .6; }
.co-facts .br-change {
  margin-left: 6px; font-size: 12px; font-weight: 700; color: var(--gold-deep);
  background: none; border: 0; padding: 0;
}
#checkout-branch-warn:empty { display: none; }

/* ── a row that opens ──────────────────────────────────────────────────────
   <details> rather than a toggle written in JavaScript: it is open/closed
   without any state to keep, it works before the script has run, and a browser
   that has never heard of any of this still shows the content. */
.co-more { border-top: 1px solid var(--hair, var(--line)); }
.co-more:first-of-type { margin-top: 4px; }
.co-more > summary {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 2px; cursor: pointer; list-style: none;
  -webkit-tap-highlight-color: transparent;
}
.co-more > summary::-webkit-details-marker { display: none; }
.cm-txt { flex: 1; min-width: 0; }
.cm-txt b { display: block; font-size: 14.5px; font-weight: 650; letter-spacing: -.01em; }
.cm-txt small { display: block; margin-top: 1px; font-size: 12.5px; color: var(--muted); font-weight: 500; }
.cm-go {
  flex: none; font-size: 20px; line-height: 1; font-weight: 400;
  color: color-mix(in srgb, var(--muted) 65%, transparent);
  transition: transform .22s cubic-bezier(.32,.72,0,1);
}
.co-more[open] > summary .cm-go { transform: rotate(90deg); }
.co-more[open] > summary { padding-bottom: 6px; }
.co-more > *:not(summary) { padding-bottom: 14px; }
@media (hover: hover) { .co-more > summary:hover .cm-txt b { color: var(--gold-deep); } }

.opt-tag { font-weight: 400; font-size: 12px; color: var(--muted); }

/* ── the total and the button, pinned together ────────────────────────────*/
.co-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 46;
  display: none; align-items: center; gap: 14px;
  padding: 10px 16px calc(env(safe-area-inset-bottom, 0px) + 12px);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(1.6) blur(18px);
  -webkit-backdrop-filter: saturate(1.6) blur(18px);
  border-top: 1px solid var(--hair, var(--line));
}
body[data-view="checkout"] .co-bar { display: flex; }
.cob-total { display: block; flex: none; min-width: 0; }
.cob-total small {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.cob-total b { display: block; font-size: 19px; font-weight: 750; letter-spacing: -.02em; }
.co-bar .primary-btn { flex: 1; margin: 0; height: 54px; }
/* and the form has to be able to clear it */
#view-checkout { padding-bottom: 104px; }




/* the action that stays on a collapsed row */
.cm-act {
  flex: none; padding: 8px 14px; border-radius: 999px;
  background: var(--soft); border: 1px solid var(--line);
  font-size: 12.5px; font-weight: 700; color: var(--gold-deep);
  -webkit-tap-highlight-color: transparent;
}
.cm-act:active { transform: scale(.96); }
.co-more[open] > summary .cm-act { display: none; }

/* ============================================================
   CLOSING THE ACCOUNT
   The one screen in the app that is allowed to look serious. Everything else
   is warm cream and gold; this is deliberately cooler and plainer, because a
   person should be able to tell from across the room that this screen is not
   the same kind of screen as the others. It still is not red all over — a wall
   of red reads as an error the app has made, not as a decision the person is
   about to make. The red is spent on exactly two things: the row that gets
   here, and the button that does it.
   ============================================================ */
:root { --bad: #b03a2e; --bad-soft: #fbeceb; }

/* The row in Settings. Muted, not shouting — it sits under three ordinary rows
   and should not be the first thing the eye lands on. */
.set-row-link.danger .srl-ico { background: var(--bad-soft); color: var(--bad); }
.set-row-link.danger .srl-txt b { color: var(--bad); }

/* The Privacy row is an <a>, because it opens a real page outside the app and
   should be long-pressable and shareable like any link. That also means it
   arrives wearing the browser's underline, which no other row has — so it is
   taken off here rather than by turning the row back into a button. */
a.set-row-link { text-decoration: none; color: inherit; }
a.set-row-link:visited { color: inherit; }

.del-card { display: grid; gap: 16px; }
.del-lead {
  margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--ink);
  font-weight: 600;
}

/* Gone / kept, side by side in a list, because the question everyone actually
   has at this moment is "what happens to my orders" and the answer should not
   be buried in a paragraph. */
.del-list { display: grid; gap: 10px; }
.del-line {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 13px 14px; border-radius: 16px;
  background: var(--soft); border: 1px solid var(--line);
}
.del-line .dl-ico {
  flex: none; width: 30px; height: 30px; border-radius: 10px;
  display: grid; place-items: center; background: var(--card);
}
.del-line .dl-ico svg { width: 16px; height: 16px; }
.del-line.gone { background: var(--bad-soft); border-color: color-mix(in srgb, var(--bad) 18%, transparent); }
.del-line.gone .dl-ico { color: var(--bad); }
.del-line.kept .dl-ico { color: var(--muted); }
.del-line b { display: block; font-size: 13.5px; font-weight: 700; letter-spacing: -.01em; }
.del-line small {
  display: block; margin-top: 3px; font-size: 12.5px; line-height: 1.45; color: var(--muted);
}
.del-line.gone b { color: var(--bad); }

/* The confirm button. Disabled until both locks are open, and it looks
   disabled — a button that is greyed but still bright invites a tap that does
   nothing, which reads as the app being broken. */
.primary-btn.danger-btn {
  background: var(--bad); color: #fff;
  box-shadow: 0 2px 6px rgba(176, 58, 46, .18), 0 10px 26px rgba(176, 58, 46, .16);
}
/* White on pale pink is unreadable, and an unreadable label on a button reads
   as a rendering fault rather than as "not yet". Disabled says so in words the
   eye can actually pick up. */
.primary-btn.danger-btn:disabled {
  background: color-mix(in srgb, var(--bad) 12%, var(--soft));
  color: color-mix(in srgb, var(--bad) 55%, var(--muted));
  box-shadow: none; opacity: 1;
}

/* ============================================================
   THE APP'S OWN "ARE YOU SURE?"
   The last window.confirm in the app. A browser confirm is a grey OS box that
   says "starzclub.net says" inside the packaged app — it announces that the
   app is a website, it cannot be translated by the app, and on iOS it can
   freeze the webview behind it. This is the same card the rest of the app
   uses, so a question looks like the app asking it.
   ============================================================ */
.ask-backdrop {
  position: fixed; inset: 0; z-index: 9000;
  display: grid; place-items: center; padding: 22px;
  background: rgba(30, 23, 18, .42);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .2s ease;
}
.ask-backdrop.show { opacity: 1; }
.ask-backdrop[hidden] { display: none; }

.ask {
  width: 100%; max-width: 360px;
  background: var(--card); border-radius: var(--r-lg);
  padding: 24px 22px 18px;
  box-shadow: var(--shadow-lg);
  /* Comes up from slightly below and small, the way a sheet does, rather than
     appearing all at once like a system alert. */
  transform: translateY(10px) scale(.97);
  transition: transform .24s cubic-bezier(.2, .9, .3, 1);
}
.ask-backdrop.show .ask { transform: none; }

.ask h2 {
  margin: 0 0 8px; font-family: var(--font-display); font-optical-sizing: auto;
  font-size: 21px; font-weight: 600; letter-spacing: -.015em; line-height: 1.2;
}
.ask p { margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--muted); }

/* Keep, then empty. The safe choice is the filled one and it is on top; the one
   that throws work away is quieter and below, and reads in the colour the rest
   of the app uses for "this cannot be undone". */
.ask-acts { display: grid; gap: 9px; margin-top: 20px; }
.ask-acts .ghost-btn, .ask-acts .primary-btn { width: 100%; margin: 0; }
.ask-acts .ask-danger { color: var(--bad, #b03a2e); }


/* ---- the brand name, pinned ----
   The same bar as the product page, and deliberately so: one component, one
   behaviour, so the app does not have two slightly different headers. The only
   difference is what it carries — a shop instead of a dish, and no price. */
.mn-pinned .ip-pin-name { text-align: left; }
.mn-pinned { z-index: 110; }   /* under the cart bar, over the list */

/* ============================================================
   ANDROID: NO BLUR ON ANYTHING THAT IS VISIBLE WHILE SCROLLING
   backdrop-filter is cheap on iOS, where it is composited on the GPU. In
   Android's WebView it re-samples and re-blurs everything behind it on every
   frame — and the three elements using it are the three that are on screen for
   the entire scroll: the home header, the tab bar and the category strip. That
   is the stutter: not the JavaScript, which is all rAF-throttled, but three
   full-width blurs being recomputed sixty times a second.

   The blur stays everywhere it is cheap: sheets, dialogs and backdrops, none of
   which are on screen while a list is moving. Here it becomes a near-solid
   ground, which at these opacities looks all but identical and scrolls cleanly.
   ============================================================ */
html.is-android .app-top,
html.is-android .cat-tabs,
html.is-android .ip-pinned,
html.is-android .tabbar {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}
html.is-android .app-top  { background: var(--app-bg, #eef0ea); }
html.is-android .cat-tabs { background: var(--bg); }
html.is-android .ip-pinned { background: var(--soft, #f4f5ef); }
html.is-android .tabbar   { background: var(--app-card, #fff); }

/* A themed day paints the header itself, so it must keep its own artwork —
   only the blur is dropped, which the seasonal rules already do. */
html.is-android body.themed[data-view="home"] .app-top {
  background-color: var(--fest-deep, var(--fest));
}

/* The two long lists get their own compositor layer, so scrolling them does not
   repaint the header sitting on top of them.
   Deliberately NOT `contain: paint` on the cards: it would clip anything that
   overflows one, and the Popular and Bestseller badges sit right on the edge.
   Not a trade worth making without an Android device in hand to check it. */
html.is-android #view-home,
html.is-android #view-menu { will-change: scroll-position; }

/* ============================================================
   THE TWO CARDS AT THE TOP OF HOME
   One slot, and only ever one of them in it. They are deliberately built from
   the same parts — the same height, the same artwork well, the same three-line
   text block — so that an order arriving and an order being offered read as the
   same kind of object in the same place, rather than two unrelated banners.
   ============================================================ */
.live, .again {
  display: flex; align-items: center; gap: 14px; width: 100%;
  /* Room above as well as below. This had margin only underneath, so the card
     sat hard against the search bar — and this is the one card on the screen
     that is about something happening to you right now. Crowding it against
     the furniture above made it read as another row of chrome rather than as
     the most important thing on the page. */
  padding: 14px 16px; margin: 18px 0;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  text-align: left; font: inherit; color: inherit;
  -webkit-tap-highlight-color: transparent;
  transition: transform .16s ease, box-shadow .16s ease;
}
.live:active, .again:active { transform: scale(.985); box-shadow: none; }

.live-art, .again-art {
  flex: none; width: 46px; height: 46px; border-radius: 14px;
  display: grid; place-items: center; overflow: hidden;
  background: var(--soft); border: 1px solid var(--line);
}
/* contain, not cover — a wide brand lockup (Poulet & Pizza) would otherwise be
   cropped to a meaningless centre slice in these small square thumbnails. Square
   brand logos still fill the tile, so this changes nothing for them. */
.live-art img, .again-art img { width: 100%; height: 100%; object-fit: contain; padding: 4px; background: #fff; }

.live-txt, .again-txt { flex: 1; min-width: 0; display: block; }
.live-txt b, .again-txt b {
  display: block; font-size: 15px; font-weight: 700; letter-spacing: -.01em;
}
.live-txt small, .again-txt small {
  display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The line that only appears when something changed. Same colour the app uses
   everywhere else for "read this before you tap". */
.again-txt em {
  display: block; margin-top: 4px; font-size: 11.5px; font-style: normal;
  font-weight: 600; color: var(--bad, #b03a2e);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---- the live order ---- */
/* Four segments, one per stage, filling as the order moves. A bar rather than a
   number, because "preparing" is not 50% of anything — what a customer wants is
   how far along, not how much is left. */
.live-bar { display: flex; gap: 4px; margin-top: 8px; }
.live-bar i {
  flex: 1; height: 3px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 12%, transparent);
  transition: background .3s ease;
}
.live-bar i.on { background: var(--lux, #b08b43); }
/* Out for delivery is the one stage worth colouring differently: it is the only
   one where the customer might get up and go to the door. */
.live[data-stage="out_for_delivery"] .live-bar i.on { background: var(--ok, #2b7a4b); }
.live[data-stage="out_for_delivery"] .live-art { border-color: color-mix(in srgb, var(--ok, #2b7a4b) 40%, transparent); }

.live-go {
  flex: none; font-size: 12.5px; font-weight: 700; color: var(--lux, #b08b43);
  white-space: nowrap;
}

/* ---- order it again ---- */
.again-go { flex: none; display: grid; justify-items: end; gap: 4px; }
.again-total { font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.again-cta {
  font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
  padding: 5px 10px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   v5 · THE HOME SCREEN, WITH SOME LIFE IN IT
   ═══════════════════════════════════════════════════════════════════════════
   The screen was correct and completely inert: everything arrived at once,
   fully formed, and nothing responded to being touched. That is what "cheap"
   means on a phone — not the colours or the type, but the absence of any
   evidence that the thing is real and reacting to you.

   ONE RULE RUNS THROUGH ALL OF IT: transform and opacity only, never a
   property that forces layout. Themed pages on this app have already been
   janky once, from a CSS variable driving font-size and max-height on every
   scroll frame. Transform and opacity are composited — the GPU moves a layer
   that is already painted — so these animate at 60fps on a cheap Android in
   Douala, which is the only phone that matters here. */

/* ---- the entrance ---------------------------------------------------------
   Sections arrive in sequence rather than all at once. The eye follows the
   order the page wants to be read in — who you can order from, then what is
   good today — instead of being handed a finished wall.

   Deliberately quick and deliberately small: 26px of travel, not 60, and the
   whole cascade is over inside half a second. An entrance you have time to
   notice is an entrance you will resent by the tenth launch. */
@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
  to   { opacity: 1; transform: none; }
}
/* Driven by a class JS adds once and takes away again, NOT by a
   body[data-view="home"] selector. With the selector, every return to home —
   from a menu, from the bag, from tracking — re-matches the rule and replays
   the whole cascade. Charming the first time and irritating by the fifth. */
.home-enter > * {
  animation: rise-in .42s cubic-bezier(.22,.68,.24,1) both;
}
/* The stagger is per section. nth-child rather than a class on each, so
   markup added later joins the cascade without anyone remembering to. */
.home-enter > *:nth-child(1) { animation-delay: .02s; }
.home-enter > *:nth-child(2) { animation-delay: .07s; }
.home-enter > *:nth-child(3) { animation-delay: .12s; }
.home-enter > *:nth-child(4) { animation-delay: .17s; }
.home-enter > *:nth-child(5) { animation-delay: .22s; }
.home-enter > *:nth-child(6) { animation-delay: .27s; }
.home-enter > *:nth-child(n+7) { animation-delay: .3s; }

/* ---- things you touch move -----------------------------------------------
   Every tappable card now has real press physics: it goes down fast and comes
   back slowly, which is how a physical button behaves and is most of why a
   press feels "connected" rather than registered.

   The two curves are different on purpose. Down is .09s and nearly linear —
   any slower and the finger has left before the card has reacted. Up is .34s
   with an overshoot, so it settles rather than snapping. */
.brand-card, .pc, .live, .again, .item-card, .pc-add {
  transition: transform .34s cubic-bezier(.22,1.2,.3,1), box-shadow .34s ease;
  will-change: transform;
}
.brand-card:active, .pc:active, .live:active, .again:active, .item-card:active {
  transition-duration: .09s;
  transition-timing-function: cubic-bezier(.3,0,.7,1);
}
.brand-card:active { transform: scale(.955); }
.pc:active         { transform: scale(.955); }
.live:active, .again:active { transform: scale(.98); }
.item-card:active  { transform: scale(.97); }

/* ---- depth ----------------------------------------------------------------
   Two shadows, not one. A single soft shadow reads as blur; a tight dark one
   for contact plus a wide faint one for ambience is how a real object sits on
   a surface, and it is the difference between a card ON the page and a card
   PRINTED on it. Pressed, the contact shadow tightens as though the card has
   moved closer to the paper. */
.brand-card {
  box-shadow: 0 1px 2px rgba(20,24,10,.10), 0 10px 24px -14px rgba(20,24,10,.42);
}
.brand-card:active {
  box-shadow: 0 1px 2px rgba(20,24,10,.14), 0 4px 10px -8px rgba(20,24,10,.4);
}
.pc {
  box-shadow: 0 1px 2px rgba(20,24,10,.10), 0 14px 30px -18px rgba(20,24,10,.5);
}
.pc:active {
  box-shadow: 0 1px 2px rgba(20,24,10,.14), 0 6px 14px -10px rgba(20,24,10,.45);
}

/* ---- the photographs arrive, rather than appearing ------------------------
   A product photo used to pop in the instant it decoded, which on a slow
   connection is a series of unrelated flashes. Now each one fades up and
   settles from a hair over-scale, so the card assembles itself.

   Applied to the IMG, so a photo already in cache animates in the same frame
   it would have popped in and the difference is invisible — the effect only
   shows where it is needed, on the slow load. */
@keyframes photo-in {
  from { opacity: 0; transform: scale(1.06); }
  to   { opacity: 1; transform: scale(1); }
}
.pc-art img, .bc-photo, .item-visual img {
  animation: photo-in .5s cubic-bezier(.22,.68,.24,1) both;
}

/* ---- section headings ------------------------------------------------------
   A short gold rule under each heading. Small thing, but it puts a deliberate
   mark on the page: someone chose where this section starts. */
.sec-head h2 { position: relative; }
.sec-head h2::after {
  content: ''; position: absolute; left: 0; bottom: -6px;
  width: 26px; height: 3px; border-radius: 3px;
  background: var(--accent, #c9a35a);
  transform-origin: left center;
  animation: rule-in .5s cubic-bezier(.22,.68,.24,1) both .12s;
}
@keyframes rule-in { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ---- the add button acknowledges the tap ----------------------------------
   It used to add silently and leave the customer looking for confirmation. A
   quick punch of scale is enough — the bag counter is already the real
   feedback, this just says "yes, that one". */
@keyframes add-punch {
  0%   { transform: scale(1); }
  38%  { transform: scale(.82); }
  100% { transform: scale(1); }
}
.pc-add.did { animation: add-punch .34s cubic-bezier(.22,.68,.24,1); }

/* ---- respect the setting --------------------------------------------------
   Motion sensitivity is not a preference to override with taste. Everything
   above becomes instant; nothing above carries meaning that the movement
   alone conveys, so nothing is lost. */
@media (prefers-reduced-motion: reduce) {
  .home-enter > *,
  .pc-art img, .bc-photo, .item-visual img,
  .sec-head h2::after, .pc-add.did {
    animation: none !important;
  }
  .brand-card, .pc, .live, .again, .item-card, .pc-add { transition: none; }
}

/* ---- the brand tiles get some substance -----------------------------------
   Three white squares with a logo in each is the flattest thing on the screen,
   and it is also the first thing anyone looks at. The logos themselves are
   fine — they are real brand marks and over-designing around them would look
   worse — so this adds depth to the TILE and leaves the mark alone.

   A wash rather than a fill: the tint is barely there at the top and only
   really lands at the bottom, which is where a surface catches light. Strong
   enough that the three tiles read as three different shops, faint enough that
   Cold Stone's red badge and Censa's black wordmark still sit on near-white. */
.brand-card {
  border-radius: 22px;
  background:
    linear-gradient(180deg, #fff 0%, #fff 52%, var(--bc-wash, #f6f3ee) 100%);
  border: 1px solid color-mix(in srgb, var(--line, #e8e0d4) 70%, transparent);
  position: relative;
}
.brand-card.coldstone { --bc-wash: #fdeff1; }
.brand-card.waccao    { --bc-wash: #fef3d3; }
/* Poulet & Pizza's home logo is a solid cream-field artwork (#f3ecd6), not a
   cut-out like Cold Stone's. On the white→wash tile it read as a pasted box.
   Making the whole tile that same cream removes the seam so the mark sits on
   one clean surface. */
.brand-card.censa     { --bc-wash: #f3ecd6; background: #f3ecd6; }

/* A single hairline of light along the top edge. This is the oldest trick
   there is for making a flat rectangle look like a physical object, and it
   costs one pseudo-element and no repaints. */
/* NO GLOSS PSEUDO-ELEMENT, and it is worth recording why.
   A top highlight is the standard way to make a flat rectangle look physical,
   and it was the obvious next move — but .bc-photo is a real logo sitting
   inside this card, and a full-bleed ::after lands ON TOP of it. With
   mix-blend-mode it painted a white slab across Cold Stone's badge and a grey
   one behind Censa's wordmark; putting it underneath instead hides it beneath
   an opaque logo, where it does nothing.
   The wash and the two-part shadow already do the work. A highlight that has
   to dodge the content is a highlight this card does not need. */

/* Waccao's mark is a full-bleed yellow tile rather than a cut-out, so a wash
   underneath it would never show — its own colour already fills the card. */
.brand-card.waccao { background: #fff; }

/* The logo breathes a little more. 12px of padding on a 3-across grid left the
   marks nearly touching the edges, which is what made them read as pasted on
   rather than placed. */
.brand-card { padding: 15px; }
.brand-card.waccao { padding: 0; }

/* ---- type: the display face finally gets used ----------------------------
   Fraunces has been in the font stack and loading on every page for months,
   used only on a couple of small headers. Meanwhile the home screen — the
   thing that decides what this app looks like — set every word in the same
   heavy geometric sans. One typeface doing everything is the single strongest
   signal of a template, and no amount of shadow or animation fixes it.

   Fraunces is a variable serif with an OPTICAL SIZE axis, which is the part
   that matters here: at `opsz 96` the letterforms open up, the contrast rises
   and the serifs sharpen, so large text looks drawn for that size rather than
   scaled up to it. That is the difference between a serif that reads as
   expensive and one that reads as a Word document.

   Used on exactly two things — the greeting and the section headings — because
   a display face everywhere is just a different template. The sans keeps the
   body, the prices and the numerals, which is what it is good at. */
.at-user-txt b {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 96, 'SOFT' 0;
  font-weight: 600;
  font-size: 27px;
  letter-spacing: -.015em;
  line-height: 1.08;
}

.sec-head h2 {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 48;
  font-weight: 600;
  font-size: 20.5px;
  letter-spacing: -.005em;
}
/* The gold rule was drawn for the old 17px sans heading; a larger serif needs
   it to sit a touch lower and run a touch longer or it reads as a stray mark. */
.sec-head h2::after { bottom: -8px; width: 30px; }

/* The product name is the other place a serif earns its keep — it is a dish
   on a menu, and a menu is the one document type that has always been set in
   a serif. The price stays in the sans: lining numerals in a geometric face
   are easier to compare down a rail, which is the whole job of a price. */
.pc-name {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 36;
  font-weight: 600;
  letter-spacing: -.005em;
}

/* The category label above it goes the other way — small, spaced, upper — so
   the pair reads as a hierarchy rather than as two sizes of the same thing. */
.pc-meta {
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  opacity: .82;
}

/* A little more air between sections. The screen was set to a 14px rhythm
   throughout, which is efficient and reads as dense; the sections are
   different kinds of thing and should be allowed to separate. */
.sec-head { margin-top: 26px; }
#home-body > .sec-head:first-of-type { margin-top: 22px; }

/* ---- the header, quieted down ---------------------------------------------
   It was doing three loud things at once: a large greeting, a waving-hand
   emoji, and two controls all competing at the same weight. "Minimal" here is
   not about removing the greeting — it is the one warm thing on the screen —
   but about letting it be the ONLY thing with any weight, and giving it room
   to be that.

   The emoji goes (see app.js). A 👋 next to a serif greeting is two different
   registers in four characters, and it is the single most template-looking
   thing on the page. */
.app-top {
  align-items: center;
  padding-top: calc(env(safe-area-inset-top, 0px) + 16px);
  padding-bottom: 0;
}

/* ---- v6 · the app says its own name ---------------------------------------
   The previous pass made the greeting the only thing with weight, which was
   right about the noise and wrong about what was missing: with the mark hidden
   the home screen opened on the words "Good afternoon" and nothing else. No
   star, no StarzClub, no hint which shop this is. It read as a page rather
   than as an app you had installed.

   It also left the header lopsided — a 27px serif on the left against two
   grey glyphs on the right, with nothing in between. Giving the left an
   anchor and the right two proper buttons balances the bar in both
   directions at once. */
.at-user .at-coin { display: block; }
.at-coin {
  width: 40px; height: 40px; flex: none;
  filter: drop-shadow(0 2px 5px rgba(20, 24, 10, .13));
}
/* THE SERIF COMES OFF THE HEADER.
   Fraunces earns its keep on a product name and a section heading — a dish on
   a menu is the one thing that has always been set in a serif. A greeting is
   not a dish. At the top of a phone screen, above a search field and a row of
   controls, the same face reads as a restaurant sign bolted onto an app, and
   it is the widest possible setting of the longest possible string.
   The sans it already uses everywhere else, at a heavy weight and tight
   tracking, says the same thing in less room and looks like software.

   It also has to SHRINK AND WRAP. Signed out it is two words; signed in it is
   "Good afternoon, Krish" — 225px of nowrap text in 206px of space on a 390px
   phone. Sizing it to the viewport keeps the common case on one line, and
   dropping nowrap means an unusually long name folds instead of colliding. */
.at-user-txt b {
  font-family: var(--font-body);
  font-variation-settings: normal;
  font-size: clamp(20px, 5.6vw, 25px);
  font-weight: 800;
  letter-spacing: -.033em;
  line-height: 1.12;
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 100%;
}
.at-user-txt { min-width: 0; max-width: 100%; }
/* Signed out this is the three brands; signed in it is points and tier. Either
   way it is the line that tells you the chip is worth tapping. */
.at-user-txt small { display: block; font-size: 11.5px; font-weight: 650; color: #857d6f; letter-spacing: .005em; }
.at-user-txt small:empty { display: none; }
/* Signed out this line invites you in, so it is coloured like something you
   can press rather than like a caption. */
.at-user-txt small.at-join { color: var(--lux, #a8853f); font-weight: 750; }
.at-user { gap: 11px; }

/* The controls stop shouting but stop whispering too: muted ink, as before,
   now on a soft ground so they read as buttons opposite the mark rather than
   as two glyphs floating at the edge of the screen. */
.at-btn {
  color: var(--muted, #7c8470);
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--card, #fff) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--line, #e8e0d4) 75%, transparent);
}
.at-btn:active { background: rgba(20,24,10,.06); }
.at-btn svg { stroke-width: 1.7; width: 18px; height: 18px; }
.at-lang {
  font-size: 12.5px; font-weight: 800; letter-spacing: .04em;
  color: var(--muted, #7c8470);
}
/* The hairline that used to separate them existed because they were two bare
   glyphs with nothing to tell them apart. Each has its own ground now, so the
   rule is one mark too many. */
.at-actions { gap: 7px; }
.at-actions .at-lang { padding-right: 0; margin-right: 0; }
.at-actions .at-lang::after { content: none; }

/* ---- air between the header and the search bar ----------------------------
   They were touching, which made the greeting read as a label ON the search
   field rather than as the top of the page. A search bar is the first thing
   you DO; the greeting is the first thing you READ. Different jobs, and they
   need a gap that says so. */
#view-home .searchbar { margin-top: 20px; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE HEADER RIDING THE RUBBER BAND  —  packaged app only
   ═══════════════════════════════════════════════════════════════════════════
   In the app on a phone, pulling down at the top of a screen dragged the whole
   page with it — header included — and let it snap back. It reads as the screen
   coming loose from the top of the phone, which is the single cheapest-feeling
   thing an app can do.

   It is not a layout bug. `.view.active` is the scroller and it carries
   `overscroll-behavior-y: auto`, which was a deliberate choice: on the WEBSITE
   that bounce is what a page in a browser does, and taking it away would make
   starzclub.net feel wrong in Safari. But `.app-top` is `position: sticky`
   INSIDE that scroller, and a sticky element is stuck to its scroll container,
   not to the screen. Bounce the container and the header goes with it.

   A native app is not a web page and should not bounce at its own edges, so
   inside the packaged app the scroller keeps its scroll and loses its overscroll.
   `none` rather than `contain`: contain only stops the bounce being handed to
   the page behind, which was never the problem — the bounce we can feel is the
   one happening on this element itself.

   Scoped to html.is-native so the website is untouched. */
html.is-native .view.active {
  overscroll-behavior-y: none;
}
/* The same for the two other things that scroll: a sheet pulled past its end,
   and the horizontal product rails. Both bounce for the same reason. */
html.is-native .sheet,
html.is-native .opt-sheet { overscroll-behavior: none; }

/* And belt-and-braces at the top of the tree: if any scroll does escape to the
   document in a future layout, the document must not bounce either. */
html.is-native, html.is-native body { overscroll-behavior: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   FIRST RUN  —  hello, then sign in, then where to
   ═══════════════════════════════════════════════════════════════════════════ */

/* The three kitchens, before the logo. Someone opening this app for the first
   time has heard of Cold Stone and Waccao; StarzClub is the new name in the
   sentence, so the familiar thing vouches for the unfamiliar one. */
.obh-marks, .auth-marks {
  display: flex; justify-content: center; gap: 10px; margin-bottom: 16px;
}
.obh-marks img, .auth-marks img {
  width: 46px; height: 46px; border-radius: 13px; object-fit: contain;
  background: #fff; padding: 5px; border: 1px solid var(--line);
  box-shadow: 0 4px 12px -8px rgba(20, 24, 10, .4);
}
.auth-marks img { width: 38px; height: 38px; border-radius: 11px; }

.ob-hello { text-align: center; }
.ob-hello .ob-logo { width: 172px; max-width: 60%; margin: 0 auto 16px; }
/* Sans, not the serif — the same call as the home header. Fraunces belongs on
   a dish and a section heading; on a screen of app chrome and buttons it reads
   as a restaurant sign bolted to software. */
.ob-hello h2 {
  font-family: var(--font-body);
  font-variation-settings: normal;
  font-size: 25px; font-weight: 800; letter-spacing: -.03em; line-height: 1.18;
}
.ob-hello .ob-sub { margin-top: 9px; }

/* Three reasons, left-aligned inside a centred column: a centred list is a
   poster, and these are meant to be read one after another. */
.obh-points {
  list-style: none; margin: 20px auto 0; max-width: 330px;
  display: grid; gap: 11px; text-align: left;
}
.obh-points li {
  position: relative; padding-left: 27px;
  font-size: 13.5px; font-weight: 650; color: var(--ink); line-height: 1.4;
}
.obh-points li::before {
  content: ''; position: absolute; left: 0; top: 2px;
  width: 17px; height: 17px; border-radius: 50%;
  background: var(--soft); box-shadow: inset 0 0 0 1.5px var(--accent);
}
.obh-points li::after {
  content: ''; position: absolute; left: 5px; top: 7px;
  width: 7px; height: 4px; border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}

/* The language choice sits above the button because it changes the button. */
.obh-lang { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.obh-lang-lbl {
  display: block; font-size: 11px; font-weight: 750; letter-spacing: .04em;
  color: var(--muted); margin-bottom: 10px;
}
.obh-lang .ob-langs { margin-bottom: 18px; }

/* ---- the sign-in screen ---- */
/* Dots and the way out on one line. They were stacked, which cost a whole band
   of the screen and made a fat white "Not now" pill the most prominent thing
   above a card asking you to join. */
/* The dots belong in the middle of the row whatever sits beside them, so they
   are centred against the row rather than packed against its left edge — a
   three-dot progress meter hard left reads as a stray mark. */
.si-top {
  position: relative;
  display: flex; align-items: center; justify-content: flex-end;
  gap: 12px; margin-bottom: 14px; min-height: 34px;
}
.si-top .ob-dots {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  margin-bottom: 0;
}
.si-top .ob-dots:empty { display: none; }
.si-skip {
  align-self: center; margin-bottom: 0;
  background: none; border: 0; box-shadow: none;
  padding: 8px 4px; font-size: 13px; font-weight: 700;
  color: var(--muted); text-decoration: underline; text-underline-offset: 3px;
}
.si-skip:active { color: var(--ink); }

.auth-hero h2 {
  font-family: var(--font-body);
  font-variation-settings: normal;
  font-size: 21.5px; font-weight: 800; letter-spacing: -.028em; line-height: 1.2;
}
.auth-logo { width: 142px; margin-bottom: 12px; }
/* A first run has three screens to get through; the welcome one is the longest
   and should not need scrolling on an ordinary phone just to reach its button. */
.ob-hello .ob-logo { width: 150px; margin-bottom: 13px; }
.ob-hello .obh-marks { margin-bottom: 13px; }
.obh-points { margin-top: 17px; gap: 10px; }
.obh-lang { margin-top: 18px; padding-top: 15px; }

/* The perks moved above the field, so they read as reasons rather than as an
   epilogue to a decision already taken. Tighter than before, because up here
   they are supporting the ask rather than filling the bottom of a card. */
.si-perks { margin: 0 2px 20px; gap: 8px; }

.si-legal {
  margin-top: 14px; font-size: 11.5px; line-height: 1.5;
  color: var(--muted); text-align: center;
}

/* ── login, made premium ─────────────────────────────────────────────────── */
/* A warm gold hero fills the top of the card edge-to-edge, so the screen leads
   with the brand and the reward rather than a plain white form. */
.auth-card { padding: 0 22px 24px; overflow: hidden; }
.auth-hero {
  margin: 0 -22px 20px; padding: 28px 22px 22px;
  background: linear-gradient(160deg, #fbeecb 0%, #f6e2ba 58%, #f2dba6 100%);
  border-bottom: 1px solid #edd6a0; text-align: center;
}
.auth-hero .auth-marks { display: flex; justify-content: center; gap: 9px; margin-bottom: 14px; }
.auth-hero .auth-marks img { width: 44px; height: 44px; border-radius: 12px; border: 2px solid #fff; box-shadow: 0 3px 9px rgba(60,40,10,.16); }
.auth-hero .auth-logo { width: 156px; max-width: 70%; margin: 0 auto 12px; }
.auth-hero h2 { font-family: var(--font-display); font-size: 23px; font-weight: 800; color: #241c10; letter-spacing: -.01em; line-height: 1.18; }
.auth-hero p { font-size: 13.5px; color: #8a6a3a; margin-top: 7px; }

.si-perks { margin: 2px 0 20px; gap: 12px; }
.si-perks li { font-size: 13.4px; font-weight: 600; color: var(--ink); padding-left: 28px; line-height: 1.35; }
.si-perks li::before {
  width: 19px; height: 19px; top: 0; left: 0; background: #fbeecb;
  box-shadow: inset 0 0 0 1.6px var(--gold-deep, #b4832e);
}
.si-perks li::after {
  left: 6px; top: 7px; width: 6.5px; height: 3.5px;
  border-left: 1.9px solid var(--gold-deep, #b4832e); border-bottom: 1.9px solid var(--gold-deep, #b4832e);
}
#a-phone { font-size: 17px; padding: 15px 16px; letter-spacing: .02em; }
#auth-step-phone .field label, #auth-step-pin .field label { font-weight: 700; }

/* ── first-run notifications row ─────────────────────────────────────────── */
.ob-notif {
  display: flex; align-items: center; gap: 12px; margin: 6px 0 8px; padding: 13px 14px;
  border-radius: 14px; background: var(--card); border: 1px solid var(--line);
}
.ob-notif[hidden] { display: none; }
.ob-notif-ic { width: 38px; height: 38px; border-radius: 11px; flex: none; display: grid; place-items: center; background: var(--soft); color: var(--gold-deep, #b4832e); }
.ob-notif-ic svg { width: 20px; height: 20px; }
.ob-notif-txt { flex: 1; min-width: 0; }
.ob-notif-txt b { display: block; font-size: 13.5px; font-weight: 700; color: var(--ink); }
.ob-notif-txt small { color: var(--muted); font-size: 11.5px; }
.ob-notif-btn { flex: none; background: #241c10; color: #fbe9c4; font-weight: 700; font-size: 12.5px; padding: 9px 15px; border-radius: 999px; border: none; }
.ob-notif.on .ob-notif-btn { background: #eefaf1; color: #186a37; }
.ob-notif.on .ob-notif-ic { background: #eefaf1; color: #186a37; }

/* ── login C · social-first, FULL-SCREEN ─────────────────────────────────────
   No card, no border, no shadow — the content sits on the page itself, edge to
   edge, the way the prototype did. The floating white card with a hairline was
   what read as cheap. */
.ac-social {
  background: transparent; border: 0; box-shadow: none; border-radius: 0;
  padding: 8px 4px 4px; overflow: visible; max-width: 100%;
}
.ac-social .auth-hero { margin: 0 0 28px; padding: 0; background: none; border: 0; text-align: center; }
.ac-social .auth-logo { width: 168px; max-width: 66%; margin: 0 auto 14px; }
.ac-social .ac-tag { font-size: 13.5px; color: var(--muted); margin: 0; line-height: 1.45; }
.ac-choose { display: flex; flex-direction: column; gap: 11px; }
.ac-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 16px; border-radius: 14px; font-weight: 700; font-size: 15px;
  border: 1px solid var(--line); background: #fff; color: var(--ink);
}
.ac-btn svg { width: 19px; height: 19px; flex: none; }
.ac-btn.apple { background: #000; color: #fff; border-color: #000; }
.ac-btn.google { background: #fff; color: #3c4043; }
.ac-btn[hidden] { display: none; }
.ac-btn:active { transform: scale(.99); }
#phone-entry { margin-top: 4px; }
#phone-entry .field { margin-bottom: 12px; }
.ac-social .si-legal { margin-top: 16px; }

/* ═══════════════════════════════════════════════════════════════════════════
   v129 · THE LEGIBILITY PASS

   Measured, not eyeballed. t-legibility.mjs walks the real screens at 390x844
   and reports, for every visible piece of text, its size and its contrast
   against the thing actually painted behind it. 33 distinct pieces of text
   were below WCAG AA and 15 were under 11.5px. Most of them traced back to
   two decisions, so most of them are fixed here by changing two things.

   ── one · white ink on the gold fill ──────────────────────────────────────
   --gold-btn (#d7a851) is the app's filled action, and it carried white text.
   White on that gold is 2.19:1. The threshold for body text is 4.5:1, so the
   main call to action on every screen — "Go to checkout" — was roughly half
   as legible as the minimum, and so were the WELCOME10 chip, the "Learn more"
   button, the selected category pill and the round + on a product row.

   There were two ways out. Darken the gold until white passes: that lands
   around #96690a, a dark mustard, and the app stops looking gold. Or keep the
   gold exactly as designed and put dark ink on it, which is what a gold
   surface wants anyway — gold is a light colour and light colours take dark
   type. #3b2a07 on #d7a851 is 6.3:1, and the button still reads as gold.

   ── two · gold and grey as small TEXT on the cream page ────────────────────
   Separate problem, opposite direction. --gold (#c89b5a) and --muted
   (#8a8177) are fine as decoration and fine at size, but at 10–13px on the
   cream page they measure 2.37:1 and 3.58:1. That is the greeting's "Join
   StarzClub — earn points", the tab bar's own labels, every settings group
   heading, "ALSO AT THIS ADDRESS", and every product's brand line. Deepening
   the two tokens to 5.0:1 and 4.8:1 lifts all of them at once, and neither
   stops reading as gold or as grey.

   Sunlight is the reason this is worth doing properly rather than nudging.
   This is a delivery app used outdoors in Douala, on a phone at arm's length,
   often one-handed while a rider waits. Contrast that is merely adequate in a
   dim room is gone entirely at midday.
   ═══════════════════════════════════════════════════════════════════════════ */

:root,
body[data-brand="coldstone"],
body[data-brand="waccao"],
body[data-brand="censa"] {
  /* Ink for anything sitting ON the gold fill. 6.3:1. */
  --gold-on: #3b2a07;
  /* Gold as small text on the cream page. 5.0:1, still unmistakably gold. */
  --gold-text: #8a6420;

  --accent-ink: var(--gold-on);
  --lime-ink: var(--gold-on);
  --lux: var(--gold-text);
  --muted: #756d63;   /* was #8a8177 — 3.58:1 on cream, now 4.8:1 */
}

/* The unticked checkbox draws its tick in --accent-ink on a white box, and
   relies on the tick being invisible until .oc.on flips the box to gold. With
   --accent-ink now dark that tick would show through on every unselected
   option, which is exactly the wrong thing for a list of choices. Pin it. */
.oc-box { color: #fff; }
.oc.on .oc-box { color: var(--accent-ink); }

/* ── the tab bar ──────────────────────────────────────────────────────────
   Four permanent labels, and they were the smallest type in the app at
   10.5px, in #9b9b9b — 2.78:1. The bar is how you move around the app; it
   should not be the hardest thing on the screen to read. */
.tb { font-size: 11px; color: #6f6a63; font-weight: 550; }
.tb.active { color: var(--gold-text); font-weight: 700; }
.tb.active svg { fill: color-mix(in srgb, var(--gold-text) 15%, transparent); }

/* ── the floor of the type scale ──────────────────────────────────────────
   Nothing a customer has to read should be under 11px. These were 9.5–10.5px:
   the "Popular" flag, the brand line and price under a product photo, the
   sold-out tag, the promo code, and the "Choose" affordance on a row. */
.item-badge { font-size: 11px; }
.pc-meta { font-size: 11.5px; }
.pc.has-photo .pc-meta { color: rgba(255,255,255,.86); }
.oos-tag { font-size: 11px; }
.promo-banner .pb-code,
#view-home #home-promo .pb-code { font-size: 11.5px; }
.item-foot .price.choose, .pl-choose { font-size: 11.5px; }
.pc-price .pl-choose { font-size: 11.5px; color: rgba(255,255,255,.88); }
.pl-brand { font-size: 12.5px; }

/* The build stamp is not something anyone needs to read, but #c2c2c2 on cream
   is 1.67:1 — not quiet, absent. Quiet is --muted at three-quarter strength. */
.build-stamp { color: var(--muted); opacity: .72; }

/* ── settings: two things the screenshot showed and the audit could not ────
   1 · An empty white strip with a divider above it, sitting under Language.
       #notif-card reserves its padding whether or not the notification code
       has put anything inside it, and the existing guard only catches the one
       path that hides the card with an inline style. Keying off the body
       being empty catches every path, including "never rendered at all".
   2 · "Branches, opening hours and how to reach us" was being cut to
       "...and how to ..." on a 390px screen. One line was the rule because a
       subtitle that runs to three turns a tidy list into a paragraph — but
       one line is not enough to finish a sentence. Two is the compromise:
       the list stays a list, and the sentence survives. */
.set-group > #notif-card:has(> #notif-body:empty) { display: none; }
.srl-txt small {
  white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; text-overflow: ellipsis;
}
.srl-txt small:empty { display: none; }

/* ── every remaining white-on-gold surface ────────────────────────────────
   Retiring --accent-ink and --lime-ink caught the surfaces that read their
   ink from a token. These fourteen write `color: #fff` next to
   `background: var(--gold-btn)` literally, so the token never reaches them:
   the promo chip, "Learn more", the round + on a product row, the reward
   cost, the tracking action, the notification rail. Same fill, same 2.19:1,
   same fix — listed rather than tokenised because the declarations are
   scattered across four design layers and a selector list is honest about
   how many places actually paint gold. */
.promo-banner .pb-code,
#view-home #home-promo .pb-code,
.sc-btn,
.pl-add.has,
.tk-act:first-child,
.rw-row.afford .rw-cost,
.os-go,
.again-cta,
.pc-add { color: var(--gold-on); }

/* The inactive category tabs: grey type on a grey chip is a fifth of a step
   above the page in both directions, and measured 3.42:1. The chip can stay
   quiet; the word on it cannot. */
.cat-tab { color: #514b44; }

/* Out of stock has to be readable to do its job — it is the reason someone
   stops tapping. 4.47:1 rounds the wrong side of the line. */
.oos-tag { color: #5a5249; }

/* The three promises across the shop's cover photo: 10.5px is the smallest
   type in the app and it sits over photography, which is the hardest place
   to read anything. */
.bh-promise small { font-size: 11.5px; }

/* ── two things the type changes above caused, and one they exposed ────────
   1 · Widening the promo chip from 11px to 11.5px cost the message about
       eight pixels, and eight pixels was the whole margin: "use the code at
       checkout." became "use the code at…". A promo whose last word is an
       ellipsis is worse than no promo. The chip keeps the readable size and
       gives the width back by tightening its own padding and tracking, and
       the message is allowed a third line rather than being cut mid-sentence.
   2 · The three promises across the shop's cover photo are white type with
       nothing behind them, and the photograph under them is whatever the
       shop uploaded — a dark tub on one, a lit cone on another. A scrim on
       the whole hero would flatten the photography; a shadow on the type
       alone travels with the words and costs the picture nothing. */
.promo-banner .pb-code,
#view-home #home-promo .pb-code { padding: 6px 9px; letter-spacing: .04em; }
.promo-banner .pb-msg,
#view-home #home-promo .pb-msg {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.bh-promise small,
.bh-promise-ic { text-shadow: 0 1px 10px rgba(0, 0, 0, .45); }
.bh-promise-ic { box-shadow: 0 1px 10px rgba(0, 0, 0, .18); }

/* ── the festive theme owns its own ink ───────────────────────────────────
   A REGRESSION I INTRODUCED ABOVE, CAUGHT BY RE-MEASURING.

   The reasoning behind --gold-on was "gold is a light colour, so put dark
   type on it". True — but --gold-btn is not always gold. body.themed
   repoints the whole accent family at --fest, and a festive palette is a
   DARK red (#b3242c at Christmas). So the buttons on every branded page
   went from white on dark red at 6.4:1 to my dark brown on dark red at
   2.11:1, which is worse than the problem I set out to fix, and on the
   busiest control in the app.

   The mistake was treating --accent-ink as a constant when it is a function
   of whatever --accent currently is. So the theme, which is the thing that
   changes the fill, is also the thing that has to name the ink: --fest-ink
   is already declared for exactly this and was already being used by
   --lime-ink two lines from where the fill is set.

   The rule sits after the v129 block so it wins on order, since body.themed
   and body[data-brand="…"] have identical specificity. */
body.themed {
  --gold-on:    var(--fest-ink, #fff);
  --accent-ink: var(--fest-ink, #fff);
  --lime-ink:   var(--fest-ink, #fff);
  --gold-text:  var(--fest-deep, var(--fest));
  --lux:        var(--fest-deep, var(--fest));

  /* AND THE FILL HAS TO FOLLOW, ON EVERY SCREEN — not just branded ones.
     --accent is declared as `var(--gold-btn)` on a selector list that starts
     with :root. A custom property is resolved where it is DECLARED, so on a
     screen with no data-brand the only declaration in play is the one on
     :root, and it reads :root's --gold-btn — the gold — however the theme
     repaints --gold-btn further down on <body>. Branded screens happen to
     escape it because that same list also matches body[data-brand="…"],
     which does see the theme's value.
     The visible result was a festive app whose shop pages had festive red
     buttons and whose home, rewards, settings and help had gold ones, with
     white festive ink on them at 2.19:1. Re-declaring --accent here puts the
     resolution on <body>, where the theme actually lives. */
  --accent: var(--gold-btn);
}

/* ── the cards without a photograph ───────────────────────────────────────
   A product card comes in two forms: a photograph with a dark scrim and white
   type, or — when the shop has not uploaded a picture yet — an emoji on a pale
   tinted ground. The white type was never scoped to the first form, so on a
   tinted card "Price by size" was white on pink at 1.25:1, which is to say
   invisible, and the category line above the name was 50% black at 3.31:1.

   These have been there since the cards were built; the audit missed them
   because it lumped every .pc in with the photo cards it cannot score. Now
   that it only excuses cards actually carrying artwork, they surfaced.

   The white stays where there is a scrim to earn it, and the tinted cards get
   ink dark enough for the ground they are actually on. */
.pc:not(.has-photo) .pc-meta { color: #6b5f55; opacity: 1; }
.pc:not(.has-photo) .pc-price .pl-choose { color: #6b5f55; }

/* ── the support inbox row in the Help Centre ─────────────────────────────
   A separate setting from the privacy address, and a separate row from the
   Call / WhatsApp pair above it, because it is a slower channel and should
   not look like a third equal option next to two instant ones. */
.help-mail {
  display: flex; align-items: center; gap: 13px;
  margin: 12px 0 4px; padding: 14px 16px;
  background: var(--card); border: 1px solid var(--hair, var(--line));
  border-radius: var(--r-lg, 16px); color: inherit; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.help-mail .hm-ic {
  flex: none; width: 38px; height: 38px; border-radius: 12px; display: grid; place-items: center;
  background: var(--soft, #f4ede3); color: var(--gold-deep, #b4832e);
}
.help-mail .hm-ic .ic { width: 19px; height: 19px; }
.help-mail .hm-txt { flex: 1; min-width: 0; }
.help-mail .hm-txt b { display: block; font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.help-mail .hm-txt small {
  display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.help-mail .hm-go { flex: none; font-size: 21px; line-height: 1; color: var(--muted); }
@media (hover: hover) { .help-mail:hover { border-color: var(--gold, #c89b5a); } }

/* ── delivered, or collected ──────────────────────────────────────────────
   Two equal choices at the top of checkout, because it changes what the rest
   of the form asks for. Deliberately not a small toggle: this decides whether
   somebody rides across Douala, and it should be as easy to see as it is to
   get wrong. */
.ful-switch {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin: 0 0 14px;
}
.ful-opt {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 13px 12px; border-radius: var(--r-lg, 16px);
  background: var(--card); border: 1.5px solid var(--hair, var(--line));
  color: inherit; -webkit-tap-highlight-color: transparent;
  transition: border-color .14s ease, background .14s ease;
}
.ful-opt svg { width: 21px; height: 21px; flex: none; color: var(--muted); }
.ful-opt span { min-width: 0; }
.ful-opt b { display: block; font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.ful-opt small {
  display: block; margin-top: 1px; font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ful-opt.active {
  border-color: var(--gold-btn, var(--accent));
  background: color-mix(in srgb, var(--gold-btn, var(--accent)) 8%, var(--card));
}
.ful-opt.active svg { color: var(--gold-deep, var(--accent)); }
@media (hover: hover) { .ful-opt:hover { border-color: var(--gold, #c89b5a); } }

/* Where to come and get it, repeated in the form so nobody has to go looking. */
.pick-where {
  display: flex; align-items: center; gap: 11px; margin: 2px 0 14px;
  padding: 12px 14px; border-radius: var(--r-md, 14px);
  background: var(--soft, #f4ede3);
}
.pick-where .pw-ic { flex: none; color: var(--gold-deep, var(--accent)); display: grid; place-items: center; }
.pick-where .pw-ic svg { width: 19px; height: 19px; }
.pick-where .pw-txt { min-width: 0; }
.pick-where b { display: block; font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.pick-where small { display: block; margin-top: 2px; font-size: 14px; font-weight: 650; color: var(--ink); }

/* The kitchen board and the customer's order screen both need to say, loudly,
   that nobody is delivering this one. */
.ful-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--soft, #f4ede3); color: var(--gold-deep, #b4832e);
  font-size: 11.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
}

/* Customer's own "Cancel order" — a full-width, muted destructive action on its
   own row beneath Details/Call, so it is unmistakable and never a mis-tap, and
   the longer French label has room. Only shown while the order is new/confirmed. */
.tk-acts { flex-wrap: wrap; }
.tk-act.cancel {
  flex-basis: 100%;
  background: transparent;
  color: #c0392b;
  border-color: color-mix(in srgb, #c0392b 30%, transparent);
  box-shadow: none;
  font-weight: 600;
}
.tk-act.cancel:active { opacity: .9; }

/* pay provider logos — brand-coloured wordmark chips, with an official-logo
   image that auto-covers the chip when a file exists at /img/pay/. */
.pay-option .pay-ico.momo, .pay-option .pay-ico.om { position: relative; overflow: hidden; }
.pay-option .pay-ico .pay-logo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #fff; }
.pay-option .pay-ico .wm { display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.02; font-weight: 800; letter-spacing: -.02em; }
.pay-option .pay-ico .wm.momo { color: #111; }
.pay-option .pay-ico .wm.momo .a { font-size: 13px; }
.pay-option .pay-ico .wm.momo .b { font-size: 8px; letter-spacing: .02em; }
.pay-option .pay-ico .wm.om { color: #fff; }
.pay-option .pay-ico .wm.om .a { font-size: 10.5px; }
.pay-option .pay-ico .wm.om .b { font-size: 7.5px; font-weight: 700; opacity: .95; }
