/* ============================================================================
   Rounded liquid-glass — warm cream by default, every surface a soft glass
   slab (gradient fill + bright rim + layered shadow). When the owner sets a
   background photo, body.has-shop-bg turns it translucent + refractive.
   Light only. Big rounding, pill CTAs, deep-red accent + dusty-blue secondary.
   ========================================================================== */

:root {
  color-scheme: light;
  --font: "Plus Jakarta Sans", "Sarabun", "IBM Plex Sans Thai", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --bg: #fbf9ec;                 /* warm cream by default — a shop bg photo overrides it */
  --text: #3a2e24;
  --muted: #8a7a68;

  --accent: #ae2c36;
  --accent-2: #8a2129;
  --accent-ink: #ffffff;
  --accent-soft: rgba(174, 44, 54, .13);
  /* "coral" slot repurposed as the palette's dusty-blue secondary (product
     image tints, links, the cart bar) — name kept so every existing call
     site (.link, pill-new, etc.) just picks up the new color for free. */
  --coral: #9ac3d7;
  --coral-2: #3f6f8a;
  --coral-soft: rgba(154, 195, 215, .22);
  /* Dedicated "success" green — kept SEPARATE from --accent (now the brand
     red used for buttons/selection). A "จัดส่งสำเร็จ" pill or "สินค้าพร้อม"
     stock chip painted brand-red would read as an error to most people; the
     old green lives on here specifically for genuine done/OK semantics. */
  --ok: #22c55e;
  --ok-2: #17a44c;
  --ok-soft: rgba(34, 197, 94, .13);
  /* Tan — pre-order's own surface color (badge/banner/status pill), kept
     separate from --warn-bg (amber) below so a real stock-warning chip
     never gets confused with "this order is a pre-order". */
  --tan: #dbd3c4;
  --tan-wash: rgba(219, 211, 196, .55);
  --tan-ink: #6b5738;

  --glass: rgba(255, 255, 255, .78);
  --glass-strong: rgba(255, 255, 255, .92);
  --glass-border: rgba(58, 46, 36, .08);
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .85);
  --hair: rgba(58, 46, 36, .12);
  --line: rgba(58, 46, 36, .11);

  /* ---- rounded surface fill: flat, no gradient/sheen ---- */
  --glass-fill: rgba(255, 255, 255, .86);
  --glass-slab:
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 0 0 1px rgba(255, 255, 255, .5),
    inset 0 -16px 26px -16px rgba(255, 255, 255, .55),
    0 0 0 1px rgba(22, 32, 26, .05),
    0 2px 4px rgba(22, 32, 26, .04),
    0 18px 38px -14px rgba(22, 32, 26, .14);
  --glass-slab-sm:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 0 0 1px rgba(255, 255, 255, .45),
    0 0 0 1px rgba(22, 32, 26, .045),
    0 6px 16px -8px rgba(22, 32, 26, .12);

  --warn-bg: rgba(255, 176, 70, .18);
  --warn-ink: #b06a1c;
  --danger: #ef5350;

  --radius: 28px;
  --radius-sm: 20px;
  --radius-pill: 999px;
  /* No blur by default: nothing sits behind these panels but flat --bg, so
     backdrop-filter would be invisible while still costing a full composited
     layer per element — on a 2-col grid of dozens of product cards that's the
     #1 cause of scroll jank on Android. body.has-shop-bg (a photo IS behind
     things) re-enables it below, only on the handful of single persistent
     bars/panels that actually show a visible blur — never on repeating list
     items (.product-card/.order-row/.line-item stay blur-free in both modes). */
  --blur: none;
  --shadow-sm: 0 4px 16px rgba(20, 32, 26, .06);
  --shadow: 0 12px 32px rgba(20, 32, 26, .10);
  --shadow-lg: 0 24px 56px rgba(20, 32, 26, .16);
  --maxw: 480px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font);
  font-size: 15px; line-height: 1.55; letter-spacing: -.005em;
  color: var(--text);
  /* Flat --bg only — no gradient wash (used to fade a sky-blue tint into the
     cream at the top/bottom of the page). */
  background-color: var(--bg);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  min-height: 100dvh;
}

/* ---- iOS "liquid glass" mode: only when the owner sets a background photo ----
   (admin → ตั้งค่า → แบรนด์ร้าน). The photo sits behind everything; panels turn
   into thick refractive frosted glass. With no photo the UI is flat white. */
body.has-shop-bg {
  --glass: rgba(255, 255, 255, .5);
  --glass-strong: rgba(255, 255, 255, .62);
  --glass-border: rgba(255, 255, 255, .55);
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .92), inset 0 0 0 1px rgba(255, 255, 255, .14);
  --blur: saturate(1.9) blur(26px);
  --shadow-sm: 0 8px 24px rgba(12, 18, 14, .16);
  --shadow: 0 18px 44px rgba(12, 18, 14, .22);
  --glass-fill: rgba(255, 255, 255, .5);
  --glass-slab:
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 0 0 1px rgba(255, 255, 255, .28),
    inset 0 -20px 34px -18px rgba(255, 255, 255, .35),
    0 0 0 1px rgba(255, 255, 255, .18),
    0 22px 46px -16px rgba(10, 16, 12, .3);
  --glass-slab-sm:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 0 0 1px rgba(255, 255, 255, .22),
    0 10px 24px -12px rgba(10, 16, 12, .28);
}
body.has-shop-bg::after {   /* the wallpaper */
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background: var(--shop-bg, none) center / cover no-repeat fixed;
}
body.has-shop-bg::before {  /* whisper-thin flat wash so a very dark photo keeps text legible */
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: rgba(255, 255, 255, .17);
}
/* the lens wobble — Chrome only; Safari falls back to the plain frost above.
   Deliberately excludes .order-row / .product-card / .line-item — those can
   repeat dozens of times in a scrolling list and the SVG feDisplacementMap
   lens is the single heaviest filter in this stylesheet; keeping it off list
   items is what keeps scrolling smooth on Android even in photo mode. */
body.has-shop-bg .card-block,
body.has-shop-bg .banner,
body.has-shop-bg .shop-logo,
body.has-shop-bg .cartbar {
  backdrop-filter: saturate(1.85) blur(24px) url(#glassLens);
}
/* .topbar, .cat-tabs, .pay-panel, .review-block, .radio-card, .summary,
   .push-toggle, and .icon-btn are all excluded — every one now a flat solid color (red or
   cream/blue), not glass; blurring behind an opaque background has no visible effect,
   only a wasted composited layer. */

button { font-family: inherit; cursor: pointer; }
h1, h2, h3 { font-weight: 800; letter-spacing: -.025em; }

@keyframes viewIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes pop { from { transform: scale(.85); } to { transform: scale(1); } }

/* ============ screen shell ============ */
/* No min-height:100dvh here on purpose: that plus .screen-body{flex:1} used to
   force EVERY screen to stretch to full viewport height even when its content
   was short (a cart with 1 item, a product with no description) — leaving a
   big, unstyled blank gap between the content and the action button. .actionbar
   is `position:fixed` (below) so the button still floats reachable at the
   bottom of the viewport either way; only the artificial full-height stretch
   is gone. */
.screen { max-width: var(--maxw); margin: 0 auto; background: transparent; display: flex; flex-direction: column; }
.topbar {
  /* A rounded floating bar now, inset from the screen edges, matching
     .cat-tabs's pill treatment — not a flush full-width strip touching the
     very top/sides. */
  position: sticky; top: 10px; z-index: 20;
  display: grid; grid-template-columns: 44px 1fr 44px; align-items: center;
  height: 56px; padding: 0 12px; margin: 10px 12px 0;
  border-radius: var(--radius-pill);
  /* Flat solid red + white text, no glass/blur — matches the brand-red
     treatment used everywhere else (cart bar, primary buttons) instead of
     a pale frosted pill that read as disconnected from the rest of the UI. */
  background: var(--accent); border: 0;
  box-shadow: 0 10px 24px rgba(20, 32, 26, .16);
}
.topbar-title { margin: 0; font-size: 18px; font-weight: 800; letter-spacing: -.03em; text-align: center;
  color: var(--accent-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-right { display: flex; justify-content: flex-end; }
/* Button — white icon/text on blue, regardless of hue (see the "buttons
   always get white text" exception in the color-pairing rule), same
   treatment as .push-toggle. Sits on the red .topbar either way. */
.icon-btn {
  width: 44px; height: 44px; border: 0; border-radius: var(--radius-pill);
  background: #99c2d6; box-shadow: var(--glass-slab-sm);
  font-size: 19px; line-height: 1; color: var(--accent-ink); display: grid; place-items: center;
  transition: transform .16s ease;
}
.icon-btn:active { transform: scale(.9); }
.icon-btn.is-ghost { visibility: hidden; box-shadow: none; }

/* overflow-y:auto removed on purpose: .screen never actually caps .screen-body's
   height (it just grows with content; the real page/window scrolls for a long
   catalog), so this never provided real internal scrolling — it only had the
   side effect of making .screen-body the CSS "nearest scrolling ancestor" for
   position:sticky descendants (.cat-tabs), which then stick relative to a box
   that itself never scrolls — i.e. don't stick at all. Removing it lets sticky
   children resolve against the real window scroll instead. */
.screen-body { flex: 1; padding: 22px 18px 130px; animation: viewIn .32s cubic-bezier(.2,.7,.3,1) both; }
.actionbar {
  /* fixed (not sticky): stays reachable at the bottom of the viewport on a
     short page too, now that .screen no longer force-stretches to fill it */
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 0;
  width: 100%; max-width: var(--maxw);
  border: 0;
  padding: 22px 18px calc(16px + env(safe-area-inset-bottom));
  display: grid; gap: 10px;
}
/* No frosted-glass backdrop behind the action bar at all — user asked for
   the glass effect gone entirely (it was ALSO the thing behind the earlier
   ghosting bug fix, so this removes that whole layer rather than just
   working around it). .cartbar/.btn-primary sit directly on the page now. */

/* ============ buttons ============ */
.btn {
  border: 0; border-radius: var(--radius-pill);
  padding: 16px 22px; font-size: 15px; font-weight: 800; letter-spacing: -.01em;
  background: var(--glass-fill); color: var(--text); box-shadow: var(--glass-slab-sm);
  transition: transform .16s ease, box-shadow .16s ease, filter .16s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--glass-slab-sm), var(--shadow); }
.btn:active { transform: translateY(0) scale(.99); }
.btn-primary {
  /* Flat solid red, no gradient/sheen; neutral (not color-tinted) drop shadow. */
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 14px 28px rgba(20, 32, 26, .18);
}
.btn-primary:hover { filter: brightness(1.05); }
.btn-primary:disabled { opacity: .5; box-shadow: none; transform: none; filter: none; }
/* Button — light blue + white text (color-pairing rule's button exception,
   same as .icon-btn/.push-toggle). Every customer-facing use is a slip
   attach/remove/edit action inside the payment panel. */
.btn-ghost { background: #99c2d6; color: var(--accent-ink); box-shadow: none; border: 0; font-weight: 700; }
.btn-ghost:hover { filter: brightness(1.05); }
.btn-block { display: block; width: 100%; }
.btn-sm { padding: 11px 18px; font-size: 13px; }
.btn:disabled { cursor: not-allowed; }
.btn-add {
  /* Flat solid red, no gradient — matches .cartbar's plain-color treatment. */
  border: 0; border-radius: var(--radius-pill); width: 36px; height: 36px; padding: 0;
  font-size: 21px; line-height: 1; font-weight: 700;
  background: var(--accent); color: var(--accent-ink);
  box-shadow: 0 10px 20px rgba(20, 32, 26, .18);
  display: grid; place-items: center; animation: pop .2s ease;
  transition: transform .16s ease;
}
.btn-add:active { transform: scale(.86); }
.btn-add.btn-add-wide { width: auto; padding: 0 18px; height: 36px; font-size: 13px; font-weight: 800; letter-spacing: 0; }
.link { border: 0; background: transparent; color: var(--coral-2); font-weight: 800; font-size: 13px; padding: 4px; }
.link:hover { text-decoration: underline; }

/* ============ toast ============ */
.toast {
  position: fixed; left: 50%; bottom: 98px; transform: translate(-50%, 14px);
  background: rgba(28, 40, 32, .94); color: #fff; padding: 13px 24px; border-radius: var(--radius-pill);
  font-size: 14px; font-weight: 700; z-index: 100; opacity: 0; transition: .26s cubic-bezier(.2,.7,.3,1);
  max-width: 90vw; text-align: center; backdrop-filter: blur(10px); box-shadow: var(--shadow-lg);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ============ misc ============ */
.muted { color: var(--muted); font-size: 13px; }
s.muted { text-decoration-thickness: 1.5px; }
.center { text-align: center; }
.minus { color: var(--accent-2); font-weight: 800; }

/* shared rounded liquid-glass surface — single-instance panels only (blur is
   cheap here: at most one of each per screen) */
.card-block, .banner {
  background: var(--glass-fill); backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
  border: 0; border-radius: var(--radius-sm); box-shadow: var(--glass-slab);
}
/* .radio-card / .pay-panel / .review-block / .order-row: cream, red text —
   color-pairing rule (see .line-item/.summary/.bar-total in the cart
   section). Split out of the shared glass rule above; .card-block/.banner
   stay on the plain white/glass fill (not part of a themed flow shown so
   far). .order-row can repeat many times in a list — no backdrop-filter
   (see the --blur:none comment above for why), same as before. */
.radio-card, .pay-panel, .review-block, .order-row {
  background: #fbf6e3; color: var(--accent);
  border: 0; border-radius: var(--radius-sm); box-shadow: var(--glass-slab);
}
/* .line-item / .summary / .bar-total: NOT plain white/glass — color-pairing
   rule for this app (remember for future surfaces too): a red background
   always gets white text (.topbar/.cartbar/.btn-primary), and a yellow/cream
   or blue background always gets red text (.product-main, these three).
   Cream for the line items themselves, blue for the cost-breakdown summary
   (visually separates "what you're buying" from "what it costs"), cream
   again for the final total bar — same two-tone language product cards
   already use (image=cream, price-info=blue), just reused here. */
.line-item {
  background: #fbf6e3;
  border: 0; border-radius: var(--radius-sm); box-shadow: var(--glass-slab);
}
.line-name, .line-price { color: var(--accent); }

/* ============ storefront ============ */
.shop-head { display: flex; gap: 15px; align-items: center; margin-bottom: 24px; }
.shop-logo {
  width: 64px; height: 64px; border-radius: var(--radius-sm); flex: none;
  background: var(--glass-fill); box-shadow: var(--glass-slab-sm);
  display: grid; place-items: center; font-size: 32px; overflow: hidden;
}
.shop-logo img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.shop-name { margin: 0; font-weight: 800; font-size: 24px; letter-spacing: -.035em; }
.shop-tagline { margin: 3px 0 0; color: var(--muted); font-size: 12.5px; }
.shop-open { margin: 7px 0 0; font-size: 12.5px; font-weight: 800; }
.status-badge { width: 14px; height: 14px; object-fit: contain; vertical-align: -2px; }

/* Pre-order — a compact tappable pill sitting to the right of the shop-head
   block (only rendered at all while the owner has pre-order switched on),
   plus a banner right below stating the delivery-time message directly so
   the customer doesn't have to tap anything to learn it. */
/* Row of contact-channel icons under the open-status line — right now LINE is
   the only channel with a real data source (owner sets it at ตั้งค่า → ข้อมูลร้าน
   → "LINE ร้าน"; see catalog.js's lineContactHref()), so it's the only one
   rendered, but the row wrapper is ready to hold more icons later.
   status-line.png is already a self-contained branded icon (LINE's own
   app-icon style), so no extra background/border chrome needed, just a
   comfortable tap target. */
.shop-contact-row { display: flex; gap: 8px; margin-top: 8px; }
.shop-line-link { flex: none; display: block; width: 30px; height: 30px; transition: transform .16s ease; }
.shop-line-link img { width: 100%; height: 100%; object-fit: contain; display: block; }
.shop-line-link:active { transform: scale(.9); }
.preorder-badge {
  margin-left: auto; align-self: flex-start; flex: none;
  background: var(--accent); color: var(--accent-ink); border: 0; border-radius: var(--radius-pill);
  padding: 7px 12px; font-size: 11.5px; font-weight: 800; white-space: nowrap;
}

.banner { border-radius: var(--radius-sm); padding: 13px 15px; font-size: 13px; font-weight: 700; margin-bottom: 18px; }
/* Literal rgba here, NOT --warn-bg/--warn-ink (those stay amber — still used
   by .pill-low, a real stock warning elsewhere, unrelated to this banner) —
   blue at ~80% opacity ("โปร่งด้วยสัก80%"), red text: color-pairing rule. */
.banner-warn { background: rgba(153, 194, 214, .8); color: var(--accent); }
/* Literal rgba here too, NOT --tan-wash/--tan-ink (those stay tan — still
   used by .pill-status.pill-preorder elsewhere) — red at ~80% opacity, white
   text: color-pairing rule (red bg → white text, matches .topbar/.cartbar). */
.banner-preorder { background: rgba(174, 44, 54, .8); color: var(--accent-ink); }

.search-row { margin-bottom: 18px; }
.search-row .input {
  padding-top: 12px; padding-bottom: 12px;
  background: #f2eaca; color: var(--accent);
}
.search-row .input::placeholder { color: var(--accent); opacity: 1; }

.cat-tabs[hidden] { display: none; } /* .cat-tabs sets its own display:flex below, which otherwise beats the browser's default [hidden] rule */
/* A rounded floating glass pill now, not a flat full-bleed bar — sits WITH
   the page's normal side margins (no more negative-margin edge-to-edge span)
   so its rounded corners + shadow read as one more glass surface floating
   over the header gradient, the same language every card/button here
   already uses, instead of a flat slab that looked like a separate layer. */
.cat-tabs {
  position: sticky; top: 74px; z-index: 10;
  display: flex; align-items: center; gap: 4px; overflow-x: auto; overscroll-behavior-x: contain;
  padding: 6px 8px; margin: 0 0 18px;
  border-radius: var(--radius-pill);
  /* Flat solid red, no glass/blur — matches .topbar's red pill treatment. */
  background: var(--accent);
  box-shadow: 0 8px 20px rgba(20, 32, 26, .14);
  scrollbar-width: none;
  /* No more right-edge fade mask — on a solid opaque bar, masking to
     transparent revealed the page behind it (the cream --bg) blending into
     the red as a hazy smudge instead of a clean edge. That fade only ever
     made sense back when the bar itself was translucent glass. */
}
.cat-tabs::-webkit-scrollbar { display: none; }
/* Plain text tabs + one shared sliding underline (.cat-tab-indicator, a
   single absolutely-positioned bar catalog.js moves via transform/width —
   see updateIndicator()) — replaced the old filled-pill style, whose "fill"
   was a scroll-linked sweep tracking how far down the active SECTION you'd
   scrolled. That tied tab color to reading progress, which is a much busier
   idea than a tab bar needs; a bar that glides to wherever the active tab
   is (on tap OR on scroll-driven auto-highlight) reads as calmer and more
   like the tab systems this was modeled after. */
.cat-tab {
  position: relative; z-index: 1;
  flex: none; border: 0; background: none; box-shadow: none;
  color: rgba(255, 255, 255, .75); border-radius: var(--radius-pill); /* white on the red bar; dimmer until active */
  padding: 9px 14px; font-size: 13.5px; font-weight: 700; display: flex; gap: 6px; align-items: center;
  transition: color .16s ease;
}
.cat-tab.is-active { color: var(--accent-ink); font-weight: 800; } /* full white */
.cat-tab-indicator {
  position: absolute; left: 0; bottom: 3px; z-index: 0; height: 3px; border-radius: 3px;
  background: var(--accent-ink); width: 0; pointer-events: none; /* white underline on the red bar */
  transition: transform .32s cubic-bezier(.34, 1.4, .5, 1), width .32s cubic-bezier(.34, 1.4, .5, 1);
}

/* every category (+ "แนะนำ") renders in one continuous scroll now, instead of
   swapping the grid per tab — scroll-margin-top keeps a section's own heading
   clear of the sticky topbar + tabs bar when a tab tap scrolls it into view */
.cat-section { scroll-margin-top: 128px; }
.cat-section + .cat-section { margin-top: 28px; }
.cat-section-title { margin: 0 0 13px; font-size: 16px; font-weight: 800; letter-spacing: -.02em; color: var(--accent); }

.product-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
.product-card {
  display: flex; flex-direction: column; overflow: hidden;
  border: 0; border-radius: var(--radius);
  /* no backdrop-filter: up to 60+ of these render in the catalog grid at once */
  background: var(--glass-fill);
  box-shadow: var(--glass-slab);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), box-shadow .2s ease;
}
.product-card:hover { transform: translateY(-4px); box-shadow: var(--glass-slab), var(--shadow); }
.product-card.is-out { opacity: .55; }
/* Two-tone swapped per the mockup: image area cream (was blue), info area
   blue (was the default white/glass card fill) — .product-main below now
   paints its own blue background over the card's inherited white --glass-fill. */
.product-emoji {
  width: 100%; aspect-ratio: 1 / 1; overflow: hidden;
  background: #fbf6e3; /* flat, no gradient/sheen */
  display: grid; place-items: center; font-size: 56px;
}
.product-emoji img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
.product-card:hover .product-emoji img { transform: scale(1.07); }
.product-main {
  flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 13px 14px 14px;
  background: #99c2d6; /* flat, no gradient — per mockup */
}
.product-title-row { display: flex; gap: 6px; align-items: flex-start; }
.product-name { font-weight: 800; font-size: 14px; letter-spacing: -.02em; line-height: 1.28; color: var(--accent);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* No line-clamp — full description always shows, wrapping across as many
   lines as it needs, instead of getting cut off mid-sentence with "...".
   .product-card (flex-column) + .product-main (flex:1, also flex-column) +
   .product-foot (margin-top:auto) already push the price/button row to the
   bottom of the card regardless of how tall the description grows, and
   .product-list being CSS Grid (not flex) already stretches every card in
   the same row to match the tallest one automatically — both pieces this
   technique needs were already in place, this was the one rule fighting them. */
.product-desc { margin: 4px 0 0; color: var(--muted); font-size: 11.5px; }
.product-foot { display: flex; align-items: center; justify-content: space-between; margin-top: auto; padding-top: 11px; gap: 8px; }
.product-price { font-weight: 800; font-size: 16px; letter-spacing: -.03em; color: var(--accent); }
.card-out { font-size: 11px; font-weight: 800; color: var(--danger); }

/* ============ pills ============ */
.pill { font-size: 10px; font-weight: 800; padding: 3px 9px; border-radius: var(--radius-pill); white-space: nowrap; letter-spacing: .02em; }
.pill-ok { background: var(--ok-soft); color: var(--ok-2); }
.pill-low { background: var(--warn-bg); color: var(--warn-ink); }
.pill-out { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
.pill-status.pill-preorder { background: var(--tan-wash); color: var(--tan-ink); }
/* Hardcoded, NOT var(--coral) — that slot is now the palette's blue (product
   tints/cart bar/links), which would read as near-identical to .pill-prep
   (also blue) right below. "NEW" keeps its own distinct warm tone. */
.pill-status.pill-new { background: rgba(255, 122, 69, .16); color: #ec5f2b; }
.pill-status.pill-prep { background: rgba(70, 130, 220, .16); color: #3f74c4; }
.pill-status.pill-done { background: rgba(140, 90, 220, .16); color: #7a52c8; }
.pill-status.pill-ship { background: rgba(60, 160, 200, .16); color: #2f8fb0; }
.pill-status.pill-ok { background: var(--ok-soft); color: var(--ok-2); }
.pill-status.pill-cancel { background: var(--hair); color: var(--muted); }

/* ============ stepper ============ */
.stepper { display: inline-flex; align-items: center; gap: 3px; border: 0; border-radius: var(--radius-pill); padding: 3px; background: rgba(255, 255, 255, .8); box-shadow: var(--shadow-sm), var(--sheen); }
.step {
  /* Flat solid red, no gradient — matches .btn-add/.cartbar. */
  width: 30px; height: 30px; border: 0; border-radius: var(--radius-pill);
  background: var(--accent); color: var(--accent-ink);
  font-size: 17px; line-height: 1; display: grid; place-items: center;
  transition: transform .14s ease, filter .14s ease;
}
.step:hover { filter: brightness(1.06); }
.step:active { transform: scale(.86); }
.step:disabled { opacity: .35; }
.step-val { min-width: 28px; text-align: center; font-weight: 800; font-size: 14px; }
.stepper-sm { padding: 2px; }
.stepper-sm .step { width: 26px; height: 26px; font-size: 15px; }
.stepper-sm .step-val { min-width: 20px; font-size: 13px; }

/* ============ cart bar ============ */
/* Flat solid red (--accent), no gradient/sheen, no glass backdrop, and a
   neutral (not color-tinted) drop shadow instead of a colored glow. */
.cartbar {
  border: 0; width: 100%;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius-pill); padding: 16px 20px; display: flex; align-items: center; gap: 12px;
  font-size: 15px; font-weight: 800;
  box-shadow: 0 18px 36px rgba(20, 32, 26, .2);
  transition: transform .16s ease, filter .16s ease;
}
.cartbar:hover { transform: translateY(-2px); filter: brightness(1.04); }
.cartbar-count { background: rgba(255, 255, 255, .3); border-radius: var(--radius-pill); min-width: 27px; height: 27px; display: grid; place-items: center; font-size: 13px; padding: 0 7px; }
.cartbar-label { flex: 1; text-align: left; letter-spacing: -.02em; }

/* ============ empty ============ */
.empty { text-align: center; padding: 58px 16px; }
.empty-emoji { font-size: 54px; }
.empty-title { font-weight: 800; font-size: 19px; margin: 14px 0 5px; letter-spacing: -.03em; }
.empty-hint { color: var(--muted); font-size: 13px; margin: 0 0 18px; }

/* ============ cart lines ============ */
.line-list { display: grid; gap: 11px; margin-bottom: 16px; }
.line-item { display: flex; gap: 13px; padding: 13px; position: relative; border-radius: var(--radius-sm); }
.line-emoji { width: 54px; height: 54px; border-radius: var(--radius-sm); overflow: hidden; flex: none;
  background: #99c2d6; display: grid; place-items: center; font-size: 25px; } /* flat, no gradient */
.line-emoji img { width: 100%; height: 100%; object-fit: cover; }
.line-main { flex: 1; min-width: 0; }
.line-name { margin: 0; font-weight: 800; font-size: 14px; letter-spacing: -.02em; }
.line-detail { margin: 3px 0 0; color: var(--muted); font-size: 12px; }
.line-note { margin: 3px 0 0; color: var(--muted); font-size: 12px; }
.line-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 11px; }
.line-price { font-weight: 800; font-size: 14px; letter-spacing: -.03em; }
.line-remove { position: absolute; top: 9px; right: 9px; border: 0; background: transparent; color: var(--muted); font-size: 14px; width: 24px; height: 24px; border-radius: var(--radius-pill); }
.line-remove:hover { color: var(--danger); }

/* ============ summary ============ */
/* Blue, red text — see the .line-item comment above for the color-pairing rule. */
.summary {
  background: #99c2d6; color: var(--accent);
  border-radius: var(--radius-sm); padding: 15px 17px; margin-top: 6px; display: grid; gap: 10px;
}
.summary-row { display: flex; justify-content: space-between; font-size: 14px; }
.summary-hint { color: var(--muted); font-size: 11.5px; margin: 0; }
.summary-total { border-top: 1px solid var(--line); padding-top: 13px; font-size: 15px; font-weight: 800; }
.summary-total strong { font-size: 21px; font-weight: 800; letter-spacing: -.03em; }
/* Cream, red text, same pill treatment as .line-item — was bare text with no
   background before, sitting directly on the page. */
.bar-total {
  background: #fbf6e3; color: var(--accent);
  border-radius: var(--radius-sm); padding: 13px 16px;
  display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; font-weight: 700;
}
.bar-total strong { font-size: 20px; font-weight: 800; letter-spacing: -.03em; }

/* ============ product hero + options ============ */
.product-hero { text-align: center; padding: 4px 0 22px; }
.product-hero-emoji {
  width: 100%; aspect-ratio: 4 / 3; border-radius: var(--radius); overflow: hidden;
  background: #99c2d6; /* flat, no gradient/sheen */
  box-shadow: var(--shadow-sm), var(--sheen);
  display: grid; place-items: center; font-size: 78px; margin: 0 auto 18px;
}
.product-hero-emoji img { width: 100%; height: 100%; object-fit: cover; }
.product-hero-name { font-weight: 800; font-size: 23px; margin: 0; letter-spacing: -.035em; }
.product-hero-desc { color: var(--muted); font-size: 13px; margin: 6px 0 0; line-height: 1.55; }
.product-hero-price { font-weight: 800; font-size: 19px; margin: 10px 0 0; letter-spacing: -.03em; }

.opt-group { margin: 20px 0; }
.opt-title { font-weight: 800; font-size: 13px; margin: 0 0 10px; letter-spacing: -.02em; color: var(--accent); }
.req { font-size: 11px; color: var(--accent-2); font-weight: 800; }
/* Cream, red text — color-pairing rule (see .line-item/.summary/.bar-total
   in the cart section). .opt-delta/.delta (the "+5"/"−5" price note) stays
   muted, matching every other secondary annotation elsewhere. */
.opt-row { display: flex; align-items: center; gap: 11px; padding: 14px; margin-bottom: 9px; font-size: 14px;
  border: 1px solid var(--glass-border); border-radius: var(--radius-sm); background: #fbf6e3; color: var(--accent); box-shadow: var(--sheen); }
.opt-row input { accent-color: var(--accent); width: 18px; height: 18px; flex: none; }
.opt-row.is-out { opacity: .4; }
.opt-row.is-fixed { color: var(--muted); }
.opt-label { flex: 1; }
.opt-delta, .delta { color: var(--muted); font-size: 13px; font-weight: 800; }
.addon-row { display: flex; align-items: center; gap: 11px; padding: 12px 14px; margin-bottom: 9px; font-size: 14px;
  border: 1px solid var(--glass-border); border-radius: var(--radius-sm); background: #fbf6e3; color: var(--accent); box-shadow: var(--sheen); }
.addon-row.has-qty { border-color: var(--accent); }
.addon-row.is-out { opacity: .4; }

.qty-row { display: flex; align-items: center; justify-content: space-between; }
.qty-label { font-weight: 800; font-size: 13px; }

/* ============ forms ============ */
.form { display: grid; gap: 16px; }
.field { display: grid; gap: 7px; }
.field-label { font-weight: 800; font-size: 12.5px; letter-spacing: -.01em; color: var(--accent); }
/* Cream, red text — same color-pairing rule as everywhere else (see
   .line-item/.summary/.bar-total in the cart section for the precedent). */
.input {
  width: 100%; border: 0; border-radius: var(--radius-sm); padding: 14px 16px;
  font: inherit; font-size: 15px; background: #f2eaca; color: var(--accent); resize: vertical;
  box-shadow: inset 0 0 0 1px rgba(22, 32, 26, .1), inset 0 1px 2px rgba(22, 32, 26, .05);
  transition: box-shadow .16s ease;
}
.input::placeholder { color: var(--accent); opacity: 1; }
.input:focus { outline: 0; box-shadow: inset 0 0 0 1.5px var(--accent), 0 0 0 4px var(--accent-soft); }

/* ============ steps indicator ============ */
.steps { list-style: none; display: flex; gap: 6px; padding: 0; margin: 0 0 22px; }
.steps li { flex: 1; text-align: center; font-size: 10.5px; color: var(--muted); display: grid; gap: 6px; justify-items: center; }
/* Pending (not-yet-reached) steps: light blue, white number — matches
   .icon-btn/.tl-dot's pending treatment. Reached (is-active/is-done) steps
   below stay red, same as ever. */
.step-dot {
  width: 27px; height: 27px; border-radius: var(--radius-pill); background: #99c2d6; color: var(--accent-ink);
  display: grid; place-items: center; font-size: 11px; font-weight: 800; border: 0; box-shadow: var(--sheen);
}
.steps li.is-active { color: var(--text); font-weight: 800; }
.steps li.is-active .step-dot, .steps li.is-done .step-dot {
  background: var(--accent); /* flat, no gradient/sheen */
  color: var(--accent-ink); border-color: transparent;
}

/* ============ radio cards ============ */
.radio-card { display: flex; align-items: center; gap: 13px; padding: 16px; border-radius: var(--radius-sm); margin-bottom: 11px; transition: border-color .16s ease, box-shadow .16s ease; }
.radio-card.is-on { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft), var(--sheen); }
.radio-card.is-disabled { opacity: .4; }
.radio-card input { accent-color: var(--accent); width: 18px; height: 18px; flex: none; }
.radio-emoji { font-size: 25px; }
.radio-body { display: grid; gap: 2px; }
.radio-title { font-weight: 800; font-size: 15px; letter-spacing: -.02em; }
.radio-sub { color: var(--muted); font-size: 12px; }

/* ============ delivery time rounds ("เลือกเวลารับสินค้า") ============ */
.slot-now { font-size: 12.5px; font-weight: 700; color: var(--muted); margin: 0 0 12px; }
.slot-now b { color: var(--text); }
.slot-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 8px 0 18px; }
/* cream/red-text default, red-bg/white-text when picked — same color-pairing
   rule as .radio-card, but a picked round reads as a firmly "chosen" action
   (like .btn-primary) rather than just a highlighted passive option, so it
   goes full red instead of just an accent border. */
.slot-card {
  position: relative; background: #fbf6e3; color: var(--accent); border: 0; border-radius: var(--radius-sm);
  box-shadow: var(--glass-slab); padding: 12px 14px; text-align: left; font: inherit; cursor: pointer;
  display: grid; gap: 3px; transition: background .16s ease, color .16s ease, box-shadow .16s ease;
}
.slot-card-order { font-size: 11px; opacity: .7; }
.slot-card-deliver { font-size: 14.5px; font-weight: 800; }
.slot-card-last { font-size: 10px; opacity: .7; }
.slot-card.is-on { background: var(--accent); color: var(--accent-ink); box-shadow: 0 0 0 4px var(--accent-soft), var(--sheen); }
.slot-card:disabled { opacity: .35; cursor: not-allowed; }
.slot-check { position: absolute; top: 10px; right: 12px; font-size: 13px; font-weight: 800; }

/* ============ payment ============ */
.pay-panel { border-radius: var(--radius-sm); padding: 20px; margin-bottom: 14px; display: grid; gap: 12px; justify-items: center; text-align: center; }
.qr { width: 190px; height: 190px; padding: 14px; background: #fff; border-radius: var(--radius-sm); fill: #1a231c; box-shadow: var(--shadow); }
.pay-name { font-size: 12.5px; color: var(--muted); margin: 0; }
.pay-amount { font-size: 13.5px; margin: 0; }
.pay-amount strong { font-size: 21px; font-weight: 800; letter-spacing: -.03em; }
.slip-preview { display: grid; gap: 10px; justify-items: center; }
.slip-preview img { max-width: 200px; max-height: 240px; border-radius: var(--radius-sm); border: 1px solid var(--glass-border); }

/* ============ review blocks ============ */
.review-block { border-radius: var(--radius-sm); padding: 15px; margin-bottom: 11px; }
.review-block-head { display: flex; justify-content: space-between; align-items: center; font-weight: 800; font-size: 13px; margin-bottom: 8px; letter-spacing: -.02em; }
.review-block p { margin: 3px 0; font-size: 14px; }
.review-line { display: flex; gap: 11px; padding: 8px 0; font-size: 14px; align-items: flex-start; }
.review-line + .review-line { border-top: 1px solid var(--line); }
.review-qty { font-weight: 800; flex: none; } /* inherits red from .review-block */
.review-body { flex: 1; display: grid; }
.review-name { font-weight: 800; letter-spacing: -.01em; }
.review-price { font-weight: 800; flex: none; letter-spacing: -.03em; }

/* ============ order status / timeline ============ */
.status-hero { text-align: center; padding: 10px 0 6px; }
.status-emoji {
  width: 84px; height: 84px; margin: 0 auto; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.status-emoji img { width: 46px; height: 46px; object-fit: contain; }
.status-now { font-weight: 800; font-size: 22px; margin: 8px 0 3px; letter-spacing: -.035em; }
/* Horizontal step row (dots connected by a line, label under each dot) —
   was a vertical bulleted list before, redesigned to match the mockup's
   order-tracking-style stepper. Three dot states: pending (pale blue, not
   reached), current (red — the step actually in progress right now, same
   "in-progress" red used for the active checkout step), done (green — a
   step that's GENUINELY finished, deliberately kept on --ok rather than
   red; see the --ok token comment up top on why real completion stays its
   own color, distinct from the brand-red used for everything else). */
.timeline { list-style: none; padding: 0; margin: 16px 0 22px; display: flex; align-items: flex-start; }
.tl-step {
  flex: 1; display: grid; justify-items: center; gap: 8px;
  color: var(--muted); position: relative; text-align: center; font-size: 11.5px;
}
.tl-step::before {
  /* spans from THIS step's dot-center to the NEXT step's dot-center — each
     .tl-step is an equal-width flex item, so 100% width of one = exactly
     one step's worth of horizontal distance */
  content: ""; position: absolute; left: 50%; top: 13px; width: 100%; height: 2px;
  background: var(--hair); z-index: 0;
}
.tl-step:last-child::before { display: none; }
.tl-dot {
  width: 26px; height: 26px; border-radius: var(--radius-pill);
  background: #99c2d6; color: var(--accent-ink);
  display: grid; place-items: center; font-size: 12px; font-weight: 800;
  position: relative; z-index: 1;
}
.tl-step.is-current .tl-dot { background: var(--accent); }
.tl-step.is-done .tl-dot { background: var(--ok); }
.tl-step.is-done, .tl-step.is-current { color: var(--text); }
.tl-step.is-done .tl-label, .tl-step.is-current .tl-label { font-weight: 800; }

/* Scroll-linked progress fill: a step already passed (.is-done) gets its
   connector line to the NEXT step recolored green and drawn in left-to-right
   (scaleX from a fixed left origin) instead of just appearing solid —
   `.pre-reveal` on the parent <ul> (removed by order.js the moment the list
   scrolls into view) holds every eligible segment collapsed at 0 until then.
   Steps not yet reached are untouched by any of this — always the plain
   static grey line above, so the animation can only ever reveal REAL
   progress, never suggest more happened than actually did. */
.tl-step.is-done::before {
  background: var(--ok);
  transform-origin: left;
  transition: transform .5s cubic-bezier(.3, 0, .2, 1);
}
.timeline.pre-reveal .tl-step.is-done::before { transform: scaleX(0); }
/* stagger so the fill visibly cascades left-to-right rather than all segments
   snapping in at once — delay lives here (not under .pre-reveal) so it's
   still in effect at the moment .pre-reveal is removed and the transition fires */
.tl-step:nth-child(2).is-done::before { transition-delay: .12s; }
.tl-step:nth-child(3).is-done::before { transition-delay: .24s; }
.tl-step:nth-child(4).is-done::before { transition-delay: .36s; }

/* A button, not a passive info panel — white text on a solid color
   regardless of hue (matches .btn-primary/.cartbar), not the red-on-cream/
   blue pairing used for passive cards. Blue while notifications are OFF
   (still an available action), red once turned ON (matches how "selected/
   active" reads red everywhere else — .cat-tab.is-active, the current
   timeline dot above). */
.push-toggle {
  width: 100%; background: #99c2d6; color: var(--accent-ink); border: 0;
  border-radius: var(--radius-sm); padding: 14px; font-weight: 800; font-size: 13.5px; margin-bottom: 14px;
  box-shadow: 0 8px 18px rgba(20, 32, 26, .14);
  transition: background .16s ease;
}
.push-toggle.is-on { background: var(--accent); }

/* ============ order history ============ */
.order-list { display: grid; gap: 11px; }
.order-row { width: 100%; text-align: left; border-radius: var(--radius-sm); padding: 14px 16px; display: grid; gap: 7px; transition: transform .16s ease; }
.order-row:hover { transform: translateY(-2px); }
.order-row-top { display: flex; justify-content: space-between; align-items: center; }
.order-no { font-weight: 800; font-size: 15px; letter-spacing: -.03em; }
.order-row-bot { display: flex; justify-content: space-between; align-items: center; }
.order-row-bot strong { font-size: 15px; font-weight: 800; letter-spacing: -.03em; }

/* ============ closed screen ============ */
.closed-hero { text-align: center; padding: 36px 0 22px; }
.closed-lock img { width: 72px; height: 72px; object-fit: contain; }
.closed-hero h2 { font-weight: 800; margin: 12px 0 5px; font-size: 22px; letter-spacing: -.035em; }
.card-block { border-radius: var(--radius-sm); padding: 17px; margin: 14px 0; }
.block-title { font-weight: 800; font-size: 13px; margin: 0 0 10px; letter-spacing: -.02em; }
.hours-row { display: flex; justify-content: space-between; font-size: 14px; padding: 5px 0; }

/* ============ skeleton loading (initial boot only — see catalog.js) ============
   Shimmering placeholder shapes (YouTube/Facebook-style) shown while boot()
   hasn't resolved yet. Base .skel sets the flat fill + rounded corners; the
   shape is up to the caller (width/height/border-radius inline or a modifier
   class). Respects prefers-reduced-motion — a static fill instead of a sweep. */
.skel {
  position: relative; overflow: hidden; background: color-mix(in srgb, var(--text) 10%, transparent);
  border-radius: var(--radius-sm);
}
.skel::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--text) 8%, transparent), transparent);
  animation: skel-sweep 1.6s ease-in-out infinite;
}
@keyframes skel-sweep { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; display: none; } }
.skel-circle { border-radius: 50%; }
