/* ═══════════════════════════════════════════════════════════════
   bk-shop.css — extracted from brand_assets/shop.html
   Extraction wave: Wave EE-ShopSplit Phase 2
   Date: 2026-05-29
   Source HEAD at extraction: ac07815 (origin/main 2026-05-28)
   See docs/active/MRC_SHOP_SPLIT_AUDIT_2026_05_29.md for full plan.
   Precedent: caaf3dc (Mr.B admin_shop Phase 2, bk-admin-shop.css).

   Source ranges (7 inline <style> blocks, body-only, in source order):
     L56-L784      <style id="tenant-vars">  — tenant CSS vars + elevation tokens
     L786-L4311    <style>                   — primary CSS (nav/hero/cart/PDP/PLP/checkout/account)
     L4312-L4452   <style>                   — Interaction Feedback Standard kit (CSS half)
     L16784-L16819 <style>                   — cookie banner CSS
     L16880-L17062 <style>                   — WP-2 AI shopping assistant (F-15 bottom-sheet)
     L17116-L17247 <style>                   — A0 customer login (nav-account + auth-overlay)
     L17351-L17461 <style>                   — Phase 3 Wave 7 floating AI bk-fab (customer)

   Per-tenant override still happens via JS injection at shop.html ~L7314
   (storefront fetch); the :root tokens here are first-paint defaults.
   ═══════════════════════════════════════════════════════════════ */

    /* pk 2026-06-01 — hide ALL scrollbars storefront-wide (mobile + desktop,
       product rails + the whole landing page). Scroll/swipe/drag still works;
       only the visible bar is removed. Global because pk asked for "ออกทั้งหมด".
       The high-specificity .shop-tab-rail rule is neutralised at its own block. */
    * { scrollbar-width: none; -ms-overflow-style: none; }
    *::-webkit-scrollbar { width: 0; height: 0; display: none; }

    :root {
      /* Wave H-1 (F-SHOP-002) — accent set to trust-blue per DESIGN.md §2.1.
         Per-tenant override remains via the JS injection at ~line 7314
         (storefront fetch). Per-vertical override map below (§2.6 canonical). */
      --accent:        var(--brand-primary, #2563EB);  /* v3-7: derive from --brand-primary (§2.6 source-of-truth; was the inverted source) */
      --accent-soft:   #2A2A2A;
      --bg:            #FFFFFF;
      --bg-soft:       #FAFAFA;
      --text:          #0F172A;
      --text-mute:     #64748B;
      --border:        #E5E7EB;
      --card-radius:   12px;
      /* Wave H-1 (F-SHOP-001) — three-tier elevation per DESIGN.md §6.1.
         Shadows derive from --brand-primary so per-tenant + per-vertical
         brand bleed shifts hue automatically (§9.7 mandatory gate).
         v3-7 (§9.7 storefront fix): --brand-primary is now the SOURCE-OF-TRUTH
         (§2.6); --accent + elevation derive from it, so applyBranding (sets
         --brand-primary), [data-vertical] overrides, AND every --accent-using
         component (.btn-primary etc.) all cascade. Was inverted (accent=source),
         which left .btn-primary stuck Bookku-blue under per-vertical overrides. */
      --brand-primary: #2563EB;
      --bk-elev-base:
          0 1px 0 0 color-mix(in oklch, var(--brand-primary) 4%, transparent);
      --bk-elev-card:
          0 1px 2px  0 color-mix(in oklch, var(--brand-primary) 6%, transparent),
          0 4px 12px 0 color-mix(in oklch, var(--brand-primary) 8%, transparent);
      --bk-elev-float:
          0  2px  6px 0 color-mix(in oklch, var(--brand-primary)  8%, transparent),
          0  8px 24px 0 color-mix(in oklch, var(--brand-primary) 10%, transparent),
          0 24px 48px 0 color-mix(in oklch, var(--brand-primary)  6%, transparent);
      /* Wave H7 (F-CUS-H7) — semantic z-index ladder per DESIGN.md §6.4.
         Named rungs replace arbitrary z-index: 9999 sprinkles. Order is the
         contract: content < sticky-bar < dropdown < modal < toast < tooltip.
         The mobile bottom tab bar uses --z-sticky (above content, below the
         drawers / modals / toasts it must never cover). */
      --z-base:           0;
      --z-sticky:      1100;   /* sticky topbar / bottom tab bar / toolbar */
      --z-dropdown:    1200;
      --z-modal:       1300;   /* dialog, drawer */
      --z-toast:       1400;
      --z-tooltip:     1500;   /* always on top */
      --section-pad-y: 64px;
      --container-w:   1240px;
      --hero-h:        72vh;
      --card-aspect:   1 / 1;
      /* Wave H-6a (F-SHOP-003) — Fraunces editorial display face per
         DESIGN.md §3.1.1 v2 LOCKED. Modern/catalog/default templates use
         Fraunces; luxury overrides to Cormorant Garamond below. Sarabun
         is the Thai display fallback (no glyph-coverage in Fraunces). */
      --display-font:  'Fraunces', 'Sarabun', Georgia, 'Noto Sans Thai', serif;
      --body-font:     'Plus Jakarta Sans', 'Noto Sans Thai', system-ui, sans-serif;
      /* pk 2026-06-01 — Bebas Neue for PRICE DISPLAYS ONLY (digits in every
         ฿ amount: cards / PDP / flash / cart / checkout / account / pawn /
         voucher). Scope is "ราคา เท่านั้น" = price only — NOT product names
         (incl. names with numbers like "Diver 42mm"), NOT descriptions /
         highlights, NOT the flash countdown clock, NOT order-ref IDs.
         Bebas Neue is Latin-only (no Thai Baht ฿ U+0E3F glyph), so the ฿
         deliberately falls through this chain to --body-font while the
         digits render in Bebas — accepted + common. The --body-font default
         keeps tabular-nums working on the fallback glyphs. */
      --price-font:    'Bebas Neue', var(--body-font, system-ui, sans-serif);
      /* Wave H-6c (F-SHOP-012) — semantic success color for savings /
         discount amounts (negative-sign rows in cart + checkout summary
         and the per-line .flash-saved label) per DESIGN.md §3.6.5.
         Token replaces prior hardcoded #059669 hex sprinkles. */
      --bk-success:    #059669;
      --tracking-hero: -0.02em;
      /* Phase 4 — Premium template uses uppercase ALL CAPS section
         headers per Cartier visual fidelity §A.0 #1. Modern/Catalog/
         Luxury keep these defaults (none + 0). */
      --section-header-transform: none;
      --section-header-tracking:  0;
    }

    /* Wave H bundle (2026-05-28) — §2.6 per-vertical brand-primary override
       map (CANONICAL — Webster WCAG-AA refined picks, DESIGN.md Wave H1).
       Sets the vertical hex as the BASE atmospheric identity for verticalized
       storefronts (clinic / hotel / padel / yoga). The full §2.1 cascade
       (--brand-primary-hover/-active/-soft/-tint/-ring) AND the §6.1 elevation
       tier shadows (--bk-elev-base/-card/-float, declared above) derive
       automatically via color-mix — verified per §9.7 cascade audit gate.

       SPECIFICITY NOTE (Mr.D follow-up — out of this PR's scope):
       The tenant-override JS injection at ~line 7314 writes
       `:root { --accent: <tenant-hex> }`, which cascades to --brand-primary
       via the alias at line 71. CSS specificity has [data-vertical=...] >
       :root, so a vertical override here BEATS tenant identity. The right
       composition (tenant beats vertical for verticalized custom-brand
       tenants) requires the JS injection point to write at body-level
       (or with !important on --brand-primary directly). Queued as a
       cross-lane Mr.D follow-up; the default-vertical case (no tenant
       override) is correct as-shipped here.

       Anti-pattern compliance: only --brand-primary + --brand-primary-ink
       are overridden per §2.6. The hover/active/soft/tint/ring tokens are
       NOT hand-picked per vertical — they derive via color-mix in :root. */
    [data-vertical="clinic"] {
      --brand-primary:     #0F766E;   /* teal-700 — healing teal, 5.41:1 on white (AA) */
      --brand-primary-ink: #FFFFFF;
    }
    [data-vertical="hotel"] {
      --brand-primary:     #B45309;   /* amber-700 — hospitality amber, 4.94:1 on white (AA) */
      --brand-primary-ink: #FFFFFF;
    }
    [data-vertical="padel"] {
      --brand-primary:     #BE123C;   /* rose-700 — high-energy coral, 6.31:1 on white (AA) */
      --brand-primary-ink: #FFFFFF;
    }
    [data-vertical="yoga"] {
      --brand-primary:     #9A3412;   /* orange-800 — earthy clay, 7.28:1 on white (AAA) */
      --brand-primary-ink: #FFFFFF;
    }

    /* ═══ Wave H-1 — §2.6 per-vertical brand override map (canonical) ═══
       Webster WCAG-AA picks. Setting --accent on the body cascades into
       --brand-primary → --bk-elev-* tokens, so card shadows + focus rings
       + gradient stops adopt the vertical's identity automatically.
       Verification gate (§9.7): toggle data-vertical to each value on
       <body> in DevTools; card shadows MUST shift hue per vertical. */
    body[data-vertical="clinic"] { --accent: #0F766E; }  /* teal-700 */
    body[data-vertical="hotel"]  { --accent: #B45309; }  /* amber-700 */
    body[data-vertical="padel"]  { --accent: #BE123C; }  /* rose-700 */
    body[data-vertical="yoga"]   { --accent: #9A3412; }  /* orange-800 */

    /* ── Dark theme (pk 2026-06-02, "Bold") ─────────────────────────────
       FIX (pk 2026-06-02): the per-template var blocks (e.g.
       body[data-template="luxury"]{ --bg:#FAFAFA }) have specificity 0,1,1 —
       higher than :root — so they SWALLOWED the dark palette injected into
       :root, leaving the storefront WHITE on a "dark" theme. The override
       below uses TWO attribute selectors (0,2,1) so it outranks ANY single
       body[data-template=…] block on every template → the dark scheme always
       wins. Result: white surfaces → near-black, dark text → near-white
       (exactly the swap pk asked for). Light themes never set the attribute
       → zero effect on them. */
    body[data-theme-mode="dark"][data-template] {
      --bg:          #0F0F0F;   /* page background — near-black */
      --bg-soft:     #1A1A1A;   /* cards / lifted surfaces */
      --text:        #F5F5F5;   /* body text — near-white */
      --text-mute:   #9CA3AF;   /* muted text, readable on near-black */
      --border:      #2E2E2E;   /* hairlines */
      --accent-soft: #2A2A2A;
      /* #(pk 2026-06-02): --ink is the price/heading token (prices use
         var(--bk-ink, var(--ink))). :root pins it #1E293B (dark) and the dark
         override forgot it → the regular price was dark-on-dark (invisible).
         Flip it to near-white so prices + ink text read on the dark theme. */
      --ink:         #F5F5F5;
      --bk-ink:      #F5F5F5;
      color-scheme:  dark;      /* native scrollbars + form controls go dark */
    }
    /* #(pk 2026-06-02): flash-sale countdown NUMBERS → yellow on dark (pk
       "ตัวเลข countdown ... สีเหลืองก็ได้"). The big section clock + the pill
       timer text use the accent (yellow on the Bold palette). */
    body[data-theme-mode="dark"] .flash-bigclock .fc-num,
    body[data-theme-mode="dark"] .flash-bigclock .fc-colon,
    body[data-theme-mode="dark"] .flash-countdown-text {
      color: var(--accent);
    }
    /* #2d (pk 2026-06-02): the product-card frame edge must be TRANSPARENT on
       dark (the hairline read as an ugly light edge). Cards stay distinct via
       the --bg-soft surface vs --bg. */
    body[data-theme-mode="dark"] .prod-card { border: 1px solid transparent; }
    /* #1 (pk 2026-06-02): nav chrome on dark. The active lang button was
       `background: var(--text)` = WHITE on dark + white text → invisible. Make
       the shop name + language toggle readable grey. */
    body[data-theme-mode="dark"] .nav-brand,
    body[data-theme-mode="dark"] #brand-name { color: #C9CDD4; }
    body[data-theme-mode="dark"] .lang-toggle button { color: #9CA3AF; }
    body[data-theme-mode="dark"] .lang-toggle button.active {
      background: var(--bg-soft); color: #E5E7EB;
    }
    /* #1b (pk 2026-06-02): the sign-in card is hard-coded background:#fff →
       make it dark too. Inputs/borders already read vars so they follow. */
    body[data-theme-mode="dark"] .auth-modal {
      background: #1A1A1A; color: var(--text); border: 1px solid #2E2E2E;
    }
    body[data-theme-mode="dark"] .auth-modal .auth-sub { color: var(--text-mute); }
    /* #(pk 2026-06-02, DARK THEME ONLY — pk "แก้แค่ธีมเข้ม / อย่าแก้ทั้งหมด"):
       the Cart summary panel (luxury override hard-codes background:#fff) and the
       Checkout accordion sections (.co-acc-section: background:#fff, no flip)
       stayed WHITE on the dark theme → their text (now light) went
       white-on-white. Flip JUST these two surfaces dark; text inside already
       reads vars (--text / --ink / --text-mute) so it becomes visible. Light
       themes are untouched. */
    /* #6 fix (review wj80mn8r2): +[data-template] lifts this to (0,3,1) so it
       beats body[data-template="luxury"] .cart-summary (0,2,1, line ~3417) which
       hard-sets background:#fff — without it the cart-summary panel stayed
       white-on-dark (totals near-invisible). .co-acc-section gains a level too
       (harmless — still beats its (0,1,0) base). */
    body[data-theme-mode="dark"][data-template] .cart-summary,
    body[data-theme-mode="dark"][data-template] .co-acc-section { background: var(--bg-soft); }
    /* #(pk 2026-06-02, DARK THEME ONLY): form fields hard-code background:#fff
       (e.g. .ec-field input) → on dark the box stayed white while the typed
       text (var(--text), now light) went white-on-white (pk: "ตัวอักษรที่กรอก
       ...เป็นสีดำ / มองไม่เห็น"). Flip EVERY text field dark: dark surface +
       light text + muted placeholder. Checkbox/radio excluded. The :not()s
       lift specificity above any component/luxury input rule. Light themes
       untouched. */
    body[data-theme-mode="dark"] input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    body[data-theme-mode="dark"] textarea,
    body[data-theme-mode="dark"] select {
      background-color: var(--bg-soft); color: var(--text); border-color: var(--border);
    }
    body[data-theme-mode="dark"] input::placeholder,
    body[data-theme-mode="dark"] textarea::placeholder { color: var(--text-mute); }
    /* #(pk 2026-06-02, DARK THEME ONLY): the customer-profile dropdown
       (.nav-acct-menu) keeps its WHITE card, but its items use color:var(--text)
       (now light) → white-on-white. Per pk "ปรับ text เป็นสีดำ" keep the white
       menu + force the text DARK (and a light hover so it stays readable). */
    body[data-theme-mode="dark"] .nav-acct-head,
    body[data-theme-mode="dark"] .nav-acct-item { color: #1A1A1A; }
    body[data-theme-mode="dark"] .nav-acct-item:hover { background: #F1F5F9; }
    /* Flip the few HARD-CODED light surfaces that don't read a var, so they
       invert on dark too (white badge → dark; pk "swap white↔black"). */
    body[data-theme-mode="dark"] .bk-chip {
      background: #1A1A1A; color: #F5F5F5; border-color: #3A3A3A;
    }

    /* ── Luxury template ─────────────────────────────────────────────── */
    body[data-template="luxury"] {
      --bg:            #FAFAFA;
      --bg-soft:       #F1EDE6;
      /* ConWerse r2 (2026-05-31): trim the oversized luxury hero (was 82vh)
         + section rhythm (was 96px) — pk flagged a large void between hero
         and the first content band. 64vh hero + 56px section padding keeps
         the editorial feel without the dead space. */
      --section-pad-y: 56px;
      --hero-h:        64vh;
      --card-aspect:   4 / 5;
      --card-radius:   2px;
      /* pk 2026-05-31 — storefront display font → Fraunces (the bold editorial
         serif in pk's reference image; already loaded via the head <link>).
         Cormorant kept as fallback; Noto Thai covers Thai glyphs. */
      --display-font:  'Fraunces', 'Cormorant Garamond', 'Noto Sans Thai', serif;
      --tracking-hero: -0.01em;
      /* Wave 4 (2026-05-28) — explicit luxury palette tokens for the
         per-Mr.D 2026-05-23 demo (matte black + champagne gold). Other
         components reference these via var(...) so polish stays single-
         source-of-truth. */
      --lx-ink:        #1A1A1A;       /* matte black headlines */
      --lx-gold:       #C9A961;       /* champagne gold accent */
      --lx-mono:       'Menlo', 'Consolas', 'Roboto Mono', 'Courier New', monospace;
    }

    /* ═══ Wave 4 (2026-05-28) — Luxury template polish ═══
       Brief items 2 — ALL CAPS section headers, monospace prices,
       generous whitespace, animation discipline (var(--bk-dur-fast) only on
       interactive), weight restraint (300-500). Applied as scoped
       overrides under body[data-template="luxury"] so other templates
       inherit the un-polished defaults. */
    body[data-template="luxury"] .section-h h2,
    body[data-template="luxury"] .nav-links a,
    body[data-template="luxury"] .footer-col h2,
    body[data-template="luxury"] .footer-col h4,
    body[data-template="luxury"] .acct-rail a,
    body[data-template="luxury"] .btn-primary,
    body[data-template="luxury"] .btn-ghost {
      text-transform: uppercase;
      letter-spacing: 0.10em;
      font-weight: 500;     /* restraint per luxury convention */
    }
    /* Wave H-shop-P1-batch (F-CUS-104) — lock platform CTAs at weight 500
       sentence-case across all templates per DESIGN.md §3.5 + §4.5.
       The luxury override above was bleeding uppercase into transactional
       CTAs; this rule re-pins the CTAs to sentence-case while letting
       luxury override only its OWN h1/h2/footer/rail typography. */
    body[data-template="luxury"] .btn-primary,
    body[data-template="luxury"] .btn-ghost {
      text-transform: none;
      letter-spacing: normal;
      font-weight: 500;
    }
    /* Luxury pricing — single-source-of-truth across cards / cart /
       checkout / order summary. pk 2026-06-01: prices now use the global
       --price-font (Bebas Neue) like every other template, NOT --lx-mono,
       so price typography is consistent storefront-wide. Tabular numbers +
       letter-spacing:0 retained for clean digit columns. */
    body[data-template="luxury"] .prod-card .price,
    body[data-template="luxury"] .cart-row strong,
    body[data-template="luxury"] .cart-row .total,
    body[data-template="luxury"] #co-total,
    body[data-template="luxury"] #co-subtotal,
    body[data-template="luxury"] #co-shipping-cost,
    body[data-template="luxury"] .pd-price,
    body[data-template="luxury"] .pd-price-compare {
      font-family: var(--price-font);
      font-variant-numeric: tabular-nums;
      letter-spacing: 0;
    }
    /* .conf-ref strong is the ORDER REFERENCE ID (e.g. BK-XXXXX), NOT a
       price — pk's scope is price-only, so it keeps its monospace face
       (mirrors the non-luxury .conf-ref strong ui-monospace rule). */
    body[data-template="luxury"] .conf-ref strong {
      font-family: var(--lx-mono);
      font-variant-numeric: tabular-nums;
      letter-spacing: 0;
    }
    /* Headline weight discipline — 300-500 range only. h1 uses 400
       (regular weight; serif handles the visual weight via display
       face); h2 inherits 500 via the cap rule above. */
    body[data-template="luxury"] h1 {
      font-weight: 400;
      letter-spacing: -0.005em;
    }
    body[data-template="luxury"] h2 { font-weight: 500; }
    body[data-template="luxury"] h3 { font-weight: 500; }
    /* Whitespace — section padding 1.5× default. var(--section-pad-y)
       is already 96px on luxury; extend the cart / checkout / account
       page containers' top-padding for breathing room around the
       hero-less interior surfaces. */
    body[data-template="luxury"] #page-cart > .container,
    body[data-template="luxury"] #page-checkout > .container,
    body[data-template="luxury"] #page-account > .container,
    body[data-template="luxury"] #page-conf > .container {
      padding-top: 64px;
      padding-bottom: 64px;
    }
    /* pk 2026-06-01 (SF-21) — _acctRenderActive toggles body.acct-subpage on the
       account sub-page views (cleared on the hub). The intent (per the JS
       comment) is to "collapse the hub padding so a sub-page reads as its own
       stacked screen." The only real top padding to collapse is the 64px luxury
       container reserve above; a sub-page owns its top edge with its own
       .acct-back row, so trim that reserve. Scoped to luxury (the default
       template has no #page-account top padding, so nothing to collapse there). */
    body.acct-subpage[data-template="luxury"] #page-account > .container {
      padding-top: 24px;
    }
    /* Animation discipline — var(--bk-dur-fast) max on hover/focus/press; no
     decorative loops. This UNDERWRITES the Wave 4 brief "restraint"
     directive without breaking existing transitions on non-luxury
     templates. */
    body[data-template="luxury"] .btn-primary,
    body[data-template="luxury"] .btn-ghost,
    body[data-template="luxury"] .prod-card,
    body[data-template="luxury"] .nav-acct-btn,
    body[data-template="luxury"] .acct-tab {
      transition-duration: var(--bk-dur-fast);
      transition-timing-function: ease;
    }
    /* prefers-reduced-motion still wins; cited inline so a future reader
       knows the override is intentional. */
    @media (prefers-reduced-motion: reduce) {
      body[data-template="luxury"] * { transition: none !important; }
    }

    /* ═══ Wave 5 (2026-05-31, pk) — Luxury aesthetic redesign ═══
       pk asked the luxury storefront to read more editorial / aesthetic.
       Cite DESIGN.md §5.1 (cinematic spacing — luxury storefront is an
       editorial surface, not transactional), §3.1 (Cormorant display),
       §2.3 (ink hierarchy), §6.1 (restrained elevation). All rules are
       scoped under body[data-template="luxury"] so modern / catalog /
       premium are untouched. */

    /* Whitespace / rhythm (§5.1) — give the product grid editorial air.
       Luxury wants generous gutters; bump the grid gap above the global
       16/28 and lift the min card width so portrait 4:5 cards never feel
       squeezed (→ ~2 cols on a phone-landscape, 3–4 on desktop). */
    body[data-template="luxury"] .prod-grid {
      gap: 24px;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    }
    @media (min-width: 768px) {
      body[data-template="luxury"] .prod-grid { gap: 44px; }
    }

    /* Container gutters (§5.1) — lift mobile breathing room 20px → 28px on
       luxury only; desktop already has 40px from the base rule. */
    @media (max-width: 767px) {
      body[data-template="luxury"] .container { padding-left: 28px; padding-right: 28px; }
    }

    /* Card meta (§2.3 + §3.1) — symmetric padding (was 12/4 asymmetric),
       Cormorant title at a calmer single-line clamp (luxury product names
       are long; a graceful ellipsis reads more considered than a ragged
       2-line wrap). Slightly looser line-height for editorial feel. */
    body[data-template="luxury"] .prod-card .meta {
      /* pk 2026-05-31 — flush to card L/R edges (was 14px inset): price/name/
         highlight/Add-to-Cart hug the left, brand-pill/Buy-Now hug the right. */
      padding: 16px 0 6px 0;
    }
    body[data-template="luxury"] .prod-card .name {
      -webkit-line-clamp: 1;
      line-height: 1.35;
      margin-bottom: 6px;
    }

    /* Elevation (§6.1) — resting state stays near-flat (radius 2px + no
       resting shadow suits luxury restraint); a restrained lift appears
       only on hover. Replaces the global translateY(-3px) jump with a
       gentler 2px rise + soft shadow. */
    body[data-template="luxury"] .prod-card {
      box-shadow: none;
      /* pk 2026-05-31 — rounded CARD corners (separate from the image's). */
      border-radius: 16px;
      transition: transform var(--bk-dur-fast) ease, box-shadow var(--bk-dur-fast) ease;
    }
    body[data-template="luxury"] .prod-card:hover {
      /* #2c (pk 2026-06-02): card itself no longer lifts (the .img does, via
         the base rule) — was translateY(-2px), which bounced the card frame. */
      transform: none;
    }

    /* Badges + heart (§5.1 spacing) — inset further from the corner so they
       don't crowd the portrait crop on the taller 4:5 luxury card. */
    body[data-template="luxury"] .prod-card .prod-badge { top: 14px; left: 14px; }
    body[data-template="luxury"] .prod-card .prod-heart { top: 14px; right: 14px; }

    /* ═══ end Wave 5 luxury redesign ═══ */
    /* ═══ end Wave 4 luxury polish ═══ */
    /* ── Catalog template ────────────────────────────────────────────── */
    body[data-template="catalog"] {
      --bg-soft:       #F5F5F5;
      --section-pad-y: 40px;
      --hero-h:        auto;
      --card-aspect:   1 / 1;
      --card-radius:   6px;
    }
    /* ── Premium template (Phase 4, Mr.C 2026-05-25) ───────────────────
       Cartier-aesthetic minimal luxury. Pre-build prep doc §2.3 spec'd
       the override list; mockup §A.0 spec'd the 9 visual fidelity
       patterns. ALL CAPS section headers + chip badges + wordmark-center
       + paper-tone restraint + 80-120px whitespace + subtle chevrons.
       Inherits tenant --accent from applyBranding() (no template change
       needed — pipeline is generic per prep doc §1.2). */
    body[data-template="premium"] {
      --bg:                       #F7F9FC;  /* v3: warm paper #FAFAF7 BANNED (hue-85) → cool-tinted white oklch(98% 0.008 260). Luxury warm-paper aesthetic neutralized per pk "ห้าม warm-white เด็ดขาด" — revisit if luxury wants an exception. */
      --bg-soft:                  #FFFFFF;
      --text:                     #1A1A1A;
      --text-mute:                #707070;
      --border:                   rgba(0,0,0,0.08);
      --card-radius:              4px;
      /* Wave H-1 (F-SHOP-001) — premium template suppresses card elevation
         intentionally (flat luxury aesthetic); override the new tier token. */
      --bk-elev-card:             none;
      --section-pad-y:            80px;
      --container-w:              1280px;
      --hero-h:                   auto;
      --card-aspect:              1 / 1;
      --display-font:             'Cormorant Garamond', 'Times New Roman', 'Noto Sans Thai', serif;
      --body-font:                'Inter', 'Noto Sans Thai', system-ui, sans-serif;
      --tracking-hero:            -0.005em;
      --section-header-transform: uppercase;
      --section-header-tracking:  0.08em;
    }
    /* Premium hero-adjacent sections get 120px breathing room (§A.0 #5). */
    body[data-template="premium"] section.section--hero-adj {
      padding-block: 120px;
    }

    /* ── bk.chip primitive (Phase 4, used by Premium template) ──────────
       Cartier paper-tone chip — white bg, 1px border, uppercase 10-11px,
       letter-spacing 0.08em. Q-C-5 (pk-locked): only kind='discount'
       uses red text (#991B1B); others stay neutral. Outside Premium,
       chip degrades gracefully via inherited tokens (still readable). */
    .bk-chip {
      display: inline-block; position: absolute; top: 12px; left: 12px;
      padding: 4px 8px; font-size: 10px; font-weight: 600;
      letter-spacing: 0.08em; text-transform: uppercase;
      background: #FFFFFF; color: #1A1A1A;
      border: 1px solid #1A1A1A; border-radius: 2px;
      line-height: 1.2; z-index: 2; pointer-events: none;
    }
    .bk-chip-discount { color: #991B1B; border-color: #991B1B; }
    .bk-chip-sold-out {
      color: #FFFFFF; background: rgba(0,0,0,0.6);
      border-color: transparent;
    }
    body[data-template="premium"] .bk-chip {
      /* Premium-specific: slightly tighter padding + serif-free chip
         (chips stay sans even on serif-display template, per §A.0 #7). */
      padding: 4px 10px;
      font-family: var(--body-font);
    }

    /* ── bk.plp component (Phase 4, Mr.C 2026-05-25) ────────────────────
       Sticky desktop sidebar (280px) + 4-col grid; mobile = single col +
       slide-up filter drawer triggered by the mobile FILTERS button.
       Tablet inherits desktop grid but with 240px sidebar + 3-col grid
       via shared .prod-grid responsive rules. */
    .bk-plp { width: 100%; }
    .bk-plp-grid {
      display: grid; gap: 32px;
      grid-template-columns: 1fr;
    }
    @media (min-width: 768px) {
      .bk-plp-grid {
        grid-template-columns: 240px 1fr;
        gap: 24px;
      }
    }
    @media (min-width: 1024px) {
      .bk-plp-grid {
        grid-template-columns: 280px 1fr;
        gap: 32px;
      }
    }
    .bk-plp-sidebar-desktop {
      display: none;
    }
    @media (min-width: 768px) {
      .bk-plp-sidebar-desktop {
        display: block;
        position: sticky; top: 16px; align-self: start;
        max-height: calc(100vh - 32px); overflow-y: auto;
      }
    }
    .bk-plp-sidebar-title {
      font-family: var(--display-font); font-size: 14px; font-weight: 600;
      letter-spacing: 0.08em; text-transform: uppercase;
      margin: 0 0 16px 0; color: var(--text);
    }
    .bk-plp-filter-group {
      border-bottom: 1px solid var(--border);
      padding: 12px 0;
    }
    .bk-plp-filter-group summary {
      cursor: pointer; font-size: 13px; font-weight: 600;
      letter-spacing: 0.06em; text-transform: uppercase;
      padding: 4px 0; list-style: none; user-select: none;
      color: var(--text);
    }
    .bk-plp-filter-group summary::-webkit-details-marker { display: none; }
    .bk-plp-filter-group summary::after {
      content: '▾'; float: right; font-size: 12px; color: var(--text-mute);
      transition: transform var(--bk-dur-base);
    }
    .bk-plp-filter-group:not([open]) summary::after {
      transform: rotate(-90deg);
    }
    .bk-plp-filter-rows {
      padding: 8px 0 4px 0;
    }
    .bk-plp-filter-row {
      display: flex; align-items: center; gap: 8px;
      padding: 6px 0; font-size: 14px; color: var(--text);
      cursor: pointer;
    }
    .bk-plp-filter-row input[type="checkbox"] {
      width: 16px; height: 16px; accent-color: var(--accent);
      cursor: pointer;
    }
    .bk-plp-count-pill {
      color: var(--text-mute); font-size: 12px;
    }
    .bk-plp-clear {
      margin-top: 16px; width: 100%;
      background: transparent; color: var(--text);
      border: 1px solid var(--border); border-radius: var(--card-radius);
      padding: 10px 14px; font-size: 12px; font-weight: 600;
      letter-spacing: 0.08em; text-transform: uppercase;
      cursor: pointer; transition: background 180ms, border-color 180ms;
    }
    .bk-plp-clear:hover:not(:disabled) {
      background: var(--bg-soft); border-color: var(--text-mute);
    }
    .bk-plp-clear:disabled {
      opacity: 0.4; cursor: not-allowed;
    }
    /* H3 (2026-05-31) — brand-story header on the brand-filtered catalog view.
       Self-hidden by JS for logo-only brands (the section isn't emitted). Hero
       renders as a contained banner; meta sits below with generous spacing for
       a luxury feel. Tokens mirror the rest of the PLP. */
    .bk-plp-brand-header {
      margin-bottom: 32px;
    }
    .bk-plp-brand-hero {
      width: 100%; aspect-ratio: 16 / 5; min-height: 160px;
      background-size: cover; background-position: center;
      background-repeat: no-repeat;
      border-radius: var(--card-radius); background-color: var(--bg-soft);
      margin-bottom: 20px;
    }
    .bk-plp-brand-meta {
      max-width: 720px;
    }
    .bk-plp-brand-name {
      font-family: var(--display-font);
      font-size: 30px; font-weight: 600; line-height: 1.15;
      letter-spacing: 0.01em; margin: 0; color: var(--text);
    }
    .bk-plp-brand-tagline {
      font-size: 15px; letter-spacing: 0.04em; text-transform: uppercase;
      color: var(--text-mute); margin: 10px 0 0 0;
    }
    .bk-plp-brand-desc {
      font-size: 15px; line-height: 1.7; color: var(--text);
      margin: 16px 0 0 0; white-space: pre-line;
    }
    @media (min-width: 1024px) {
      .bk-plp-brand-name { font-size: 36px; }
    }
    .bk-plp-header-row {
      display: flex; align-items: center; justify-content: space-between;
      gap: 16px; padding-bottom: 16px;
      border-bottom: 1px solid var(--border); margin-bottom: 24px;
    }
    .bk-plp-count-text {
      font-size: 14px; color: var(--text-mute);
    }
    .bk-plp-sort-wrap {
      display: flex; align-items: center; gap: 8px;
    }
    .bk-plp-sort-label {
      font-size: 13px; color: var(--text-mute);
      letter-spacing: 0.04em; text-transform: uppercase;
    }
    .bk-plp-sort {
      font-family: var(--body-font); font-size: 14px;
      padding: 6px 28px 6px 10px; border: 1px solid var(--border);
      border-radius: var(--card-radius); background: var(--bg);
      color: var(--text); cursor: pointer;
      appearance: none;
      background-image:
        linear-gradient(45deg, transparent 50%, var(--text-mute) 50%),
        linear-gradient(135deg, var(--text-mute) 50%, transparent 50%);
      background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%;
      background-size: 5px 5px, 5px 5px;
      background-repeat: no-repeat;
    }
    .bk-plp-grid-cards {
      /* Inherits responsive grid from .prod-grid — 2-col mobile / 3-col
         tablet / 4-col desktop. PLP override clamps the desktop to 4-col
         per R2 mockup §A.8 even though .prod-grid default is 3. */
    }
    /* pk 2026-06-01 — force a real 2-up grid on the mobile PLP main column.
       .prod-grid's base `repeat(auto-fill, minmax(220px,1fr))` needs ≥454px
       for two 220px tracks; the PLP main column is only ~320px at 360px / ~350px
       at 390px (viewport − 2×20px container padding), so auto-fill collapsed to
       ONE column. Pin exactly 2 columns + a tighter 12px gap so two cards fit
       cleanly with no horizontal clip (card ≈ (320−12)/2 ≈ 154px; square image,
       2-line name, price+cart row, and "ขายได้ N ชิ้น" all stay legible). */
    @media (max-width: 767px) {
      /* #page-plp scope (1,1,0) deliberately out-specifies the base .prod-grid
         (0,1,0) AND `body[data-template="luxury"] .prod-grid` (0,2,1) so the
         2-up grid + gap hold on every template. pk 2026-06-01 — bumped 12px→16px
         ("เพิ่มระยะ ... นิดหน่อย"): a touch more breathing room between the two
         columns; still fits 2-up at 360px. pk 2026-06-01 — bumped 16px→20px
         to match the landing 2-up gap (still fits 2-up at 360px: 20px gap
         leaves ~162px per card, well within the legible-card floor). */
      #page-plp .bk-plp-grid-cards { grid-template-columns: repeat(2, 1fr); gap: 20px; }
    }
    @media (min-width: 1024px) {
      .bk-plp .bk-plp-grid-cards { grid-template-columns: repeat(4, 1fr); }
    }
    .bk-plp-pagination {
      margin-top: 48px; text-align: center;
    }
    .bk-plp-load-more {
      display: inline-block; padding: 12px 32px;
      font-family: var(--body-font); font-size: 12px; font-weight: 600;
      letter-spacing: 0.1em; text-transform: uppercase;
      background: transparent; color: var(--text);
      border: 1px solid var(--text); border-radius: 2px;
      cursor: pointer; transition: background 180ms, color 180ms;
    }
    .bk-plp-load-more:hover {
      background: var(--text); color: var(--bg);
    }
    .bk-plp-load-more-count, .bk-plp-load-more-end {
      margin-top: 12px; font-size: 12px; color: var(--text-mute);
      letter-spacing: 0.04em;
    }
    .bk-plp-empty {
      grid-column: 1 / -1;
      text-align: center; padding: 80px 24px;
      color: var(--text-mute);
    }
    .bk-plp-empty-headline {
      font-family: var(--display-font); font-size: 18px;
      color: var(--text); margin-bottom: 24px;
    }
    .bk-plp-empty-clear { display: inline-flex; }

    /* Mobile filter button + slide-up drawer */
    .bk-plp-mobile-filter-btn {
      display: inline-flex; align-items: center; gap: 6px;
      padding: 8px 14px; margin-bottom: 16px;
      background: var(--bg); color: var(--text);
      border: 1px solid var(--border); border-radius: var(--card-radius);
      font-size: 13px; font-weight: 600; cursor: pointer;
    }
    @media (min-width: 768px) {
      .bk-plp-mobile-filter-btn { display: none; }
    }
    .bk-plp-filter-dot {
      display: inline-flex; align-items: center; justify-content: center;
      min-width: 18px; height: 18px; padding: 0 5px;
      background: var(--accent); color: #fff; border-radius: 999px;
      font-size: 11px; font-weight: 700; line-height: 1; margin-left: 4px;
    }
    .bk-plp-mobile-drawer {
      position: fixed; left: 0; right: 0; bottom: 0;
      max-height: 80vh; background: var(--bg);
      border-top-left-radius: 12px; border-top-right-radius: 12px;
      box-shadow: 0 -8px 32px rgba(0,0,0,0.2);
      display: flex; flex-direction: column;
      z-index: 100;
    }
    .bk-plp-mobile-drawer[hidden] { display: none; }
    .bk-plp-drawer-header {
      display: flex; align-items: center; justify-content: space-between;
      padding: 16px 20px; border-bottom: 1px solid var(--border);
      font-family: var(--display-font); font-size: 16px; font-weight: 600;
    }
    .bk-plp-drawer-header button {
      background: none; border: none; font-size: 20px;
      color: var(--text); cursor: pointer; padding: 4px 8px;
    }
    .bk-plp-drawer-body {
      flex: 1 1 auto; overflow-y: auto;
      padding: 16px 20px;
    }
    .bk-plp-drawer-action {
      padding: 12px 20px calc(20px + env(safe-area-inset-bottom, 0px)) 20px;
      border-top: 1px solid var(--border);
      background: var(--bg);
    }
    .bk-plp-drawer-action .btn-primary {
      width: 100%; justify-content: center;
    }

    /* ── bk.carousel — Premium horizontal-scroll chevrons (Phase 4) ────
       Subtle ghost-button chevrons per Cartier §A.0 #6 (thin 1.5px
       stroke, 40-48px circular container, opacity-fades on boundary).
       Attaches to any scroll-container; scroll-snap + smooth-scroll-
       behavior handled in CSS. Used by Premium best-sellers / latest /
       categories rows + the hero carousel. Hidden on mobile (per
       Q-NEW-15: arrows desktop only, swipe on mobile). */
    .bk-carousel { position: relative; }
    .bk-carousel-scroll {
      display: flex; gap: 16px; overflow-x: auto;
      scroll-snap-type: x mandatory;
      scrollbar-width: none;
      padding-bottom: 4px; /* breathing room so card hover-lift doesn't clip */
    }
    .bk-carousel-scroll::-webkit-scrollbar { display: none; }
    .bk-carousel-scroll > * {
      flex: 0 0 auto; scroll-snap-align: start;
    }
    .bk-carousel-arrow {
      display: none;             /* mobile: hide arrows */
      position: absolute; top: 50%;
      transform: translateY(-50%);
      width: 44px; height: 44px;
      align-items: center; justify-content: center;
      background: rgba(255,255,255,0.92);
      border: 1px solid var(--border);
      border-radius: 50%;
      color: var(--text);
      cursor: pointer; z-index: 3;
      transition: background 180ms, opacity 180ms;
    }
    .bk-carousel-arrow:hover { background: rgba(255,255,255,1); }
    .bk-carousel-arrow.bk-disabled { opacity: 0.3; cursor: not-allowed; }
    .bk-carousel-arrow svg { width: 18px; height: 18px; stroke: currentColor;
                              stroke-width: 1.5; fill: none;
                              stroke-linecap: round; stroke-linejoin: round; }
    .bk-carousel-arrow.bk-prev { left: -20px; }
    .bk-carousel-arrow.bk-next { right: -20px; }
    @media (min-width: 768px) {
      .bk-carousel-arrow { display: inline-flex; }
    }

    /* ═══ F-7 Recently-viewed strip on Home — localStorage-only, no API ═══
       Reuses the bk-carousel-scroll pattern + the existing .prod-card markup
       (single source of truth for card chrome). Cards are tighter than the
       grid version so multiple fit in the strip at 375px (~2.3 visible).
       Strip hides entirely when _recentViewed is empty per pk's
       "Empty-state hidden if no recently-viewed items". */
    #section-recent-viewed .bk-carousel-scroll {
      gap: 12px; padding: 4px 0 8px 0;
    }
    #section-recent-viewed .prod-card {
      width: 158px;
    }
    #section-recent-viewed .prod-card .img { aspect-ratio: 1 / 1; }
    #section-recent-viewed .prod-card .name {
      font-size: 13px; -webkit-line-clamp: 2;
    }
    #section-recent-viewed .prod-card .price { font-size: 13px; }
    /* Respect prefers-reduced-motion per pk's V3b hint. */
    @media (prefers-reduced-motion: reduce) {
      #section-recent-viewed .bk-carousel-scroll { scroll-behavior: auto; }
      #section-recent-viewed .prod-card { transition: none; }
      #section-recent-viewed .prod-card:hover { transform: none; }
    }
    /* ═══ end F-7 ═══ */

    /* Premium template — wordmark-centered hero layout (§A.0 #2-3).
       The existing .hero-inner is 2-col on desktop (text + media);
       Premium centers a single text/image stack instead. */
    body[data-template="premium"] .hero {
      padding: 80px 24px;
    }
    body[data-template="premium"] .hero-inner {
      grid-template-columns: 1fr;
      text-align: center;
      max-width: 720px;
      margin: 0 auto;
    }
    body[data-template="premium"] .hero-text h1 {
      font-weight: 400;
      font-size: clamp(36px, 6vw, 64px);
      letter-spacing: var(--tracking-hero);
    }
    body[data-template="premium"] .hero-text p {
      color: var(--text-mute);
      font-size: 16px;
      max-width: 480px;
      margin: 0 auto 32px auto;
    }
    /* 2-row synchronized carousel for latest (§A.6 desktop sync, mobile
       independent). On desktop, .bk-2row-carousel pairs two .bk-
       carousel-scroll children whose scroll positions sync via the
       attach() helper; on mobile the two rows scroll independently. */
    .bk-2row-carousel {
      display: flex; flex-direction: column; gap: 16px;
    }
    .bk-2row-carousel .bk-carousel-scroll {
      gap: 16px;
    }

    /* Premium product-card chrome: paper-tone, 1px hairline border,
       no shadow (per §A.0 #4 + #7 + Premium scope decision). */
    body[data-template="premium"] .prod-card {
      background: var(--bg-soft);
      border: 1px solid var(--border);
      border-radius: var(--card-radius);
      box-shadow: none;
    }
    body[data-template="premium"] .prod-card:hover {
      transform: none;
      border-color: rgba(0,0,0,0.16);
    }
    body[data-template="premium"] .prod-card .name {
      font-family: var(--display-font);
      font-weight: 500;
      font-size: 16px;
    }
    body[data-template="premium"] .prod-card .price {
      font-weight: 500;
      font-size: 14px;
      letter-spacing: 0;
    }

    /* Premium round-2: switch landing-section grids from CSS-grid 2D
       layout to horizontal flex/scroll so bk.carousel.attach() can wrap
       them with chevron arrows. Card min-width keeps cards readable
       (would collapse to 0 width inside a plain flex container). Other
       templates keep the existing 2D grid layout — no regression. */
    body[data-template="premium"] #section-hot .prod-grid,
    body[data-template="premium"] #section-latest .prod-grid,
    body[data-template="premium"] #section-cats .cat-grid {
      /* display:flex makes grid-template-columns N/A so the .prod-grid
         default repeat() rule is implicitly overridden — no extra
         override line needed (§5.2 cleanup, Mr.C 2026-05-25). */
      display: flex;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scrollbar-width: none;
      gap: 24px;
      padding-bottom: 4px;
    }
    body[data-template="premium"] #section-hot .prod-grid::-webkit-scrollbar,
    body[data-template="premium"] #section-latest .prod-grid::-webkit-scrollbar,
    body[data-template="premium"] #section-cats .cat-grid::-webkit-scrollbar {
      display: none;
    }
    body[data-template="premium"] #section-hot .prod-grid > .prod-card,
    body[data-template="premium"] #section-latest .prod-grid > .prod-card {
      flex: 0 0 auto;
      width: 220px;
      scroll-snap-align: start;
    }
    body[data-template="premium"] #section-cats .cat-grid > .cat-card {
      flex: 0 0 auto;
      width: 180px;
      scroll-snap-align: start;
    }
    /* Mobile (375px) — narrower cards to peek 1.6-2 at a time so the
       horizontal-scroll affordance is obvious; arrows hide per
       .bk-carousel-arrow @media rule. */
    @media (max-width: 767px) {
      body[data-template="premium"] #section-hot .prod-grid > .prod-card,
      body[data-template="premium"] #section-latest .prod-grid > .prod-card {
        width: 60vw;
        max-width: 240px;
      }
      body[data-template="premium"] #section-cats .cat-grid > .cat-card {
        width: 50vw;
        max-width: 200px;
      }
    }

    /* ── Premium mobile polish — P2 + P3 + Mr.F #36 contrast flag
       (Mr.C 2026-05-26 per MRC_PHASE_4_MOBILE_AUDIT_2026_05_25.md §3 +
        Mr.F Review #36 / architecture audit refresh) ───────────────────

       P2 — `body[data-template="premium"] .hero { padding: 80px 24px }`
            at :393 has specificity (0,2,1) — beats the generic mobile
            rule `.hero { padding: 28px 0 36px 0 }` at :2835 with
            specificity (0,1,0). At 375×667 iPhone SE Premium kept
            160px vertical padding (24% of viewport), pushing hero
            content well below the fold. Matching-specificity Premium
            mobile override brings padding in line with Modern/Catalog
            breathing room.

       P3 — `--section-pad-y: 80px` at :111 applied at every viewport;
            160px between sections meant ~2 sections per mobile screen.
            Reduced to 56px on mobile only — preserves the paper-tone
            airiness on mobile (still generous vs Catalog 40px) and
            keeps the full 80px Cartier restraint at desktop+.

       Mr.F Review #36 contrast flag — Premium `--bg-soft: #FFFFFF`
       on page `--bg: #FAFAF7` produces ~0.5% luminance contrast →
       `.bk-skel` placeholders rendered nearly invisible on Premium.
       Scoped override on `.bk-skel` only (NOT `--bg-soft` itself)
       preserves the intentional white-on-cream paper-tone for cards /
       chips / search inputs (Cartier aesthetic). Cream tone #EFEBE3
       chosen for ~4% contrast against #FAFAF7 — visible without
       breaking the restraint. */
    @media (max-width: 767px) {
      body[data-template="premium"] .hero {
        padding: 36px 16px;
      }
      body[data-template="premium"] {
        --section-pad-y: 56px;
      }
    }
    body[data-template="premium"] .bk-skel {
      background-color: #EFEBE3;
    }

    /* ── Premium wordmark-center nav (Phase 4 polish, Mr.C 2026-05-25) ──
       Closes the P2 finding from MRC_PHASE_4_STATIC_REVIEW_2026_05_25.md
       §2.3. Cartier §A.0 #3 spec'd: wordmark CENTERED in top banner,
       utility chrome flanks left/right. The existing nav HTML structure
       (.nav-inner → .nav-left[.nav-brand + .nav-links], .nav-search,
       .nav-right) stays intact — Premium just lifts .nav-brand out of
       .nav-left's flex flow via absolute positioning and centers it in
       .nav-inner. Other templates (modern / catalog / luxury) get zero
       CSS change.
       Mobile (<768px) deliberately falls back to the default nav layout
       — Premium mobile uses the existing .nav-inner flex-wrap behavior
       (brand stays in nav-left flow; nav-links wraps to its own row).
       The hamburger-drawer pattern from mockup §A.1 mobile is a
       separate future polish item, not in scope here. */
    @media (min-width: 768px) {
      body[data-template="premium"] .nav-inner {
        position: relative;
      }
      body[data-template="premium"] .nav-brand {
        position: absolute;
        left: 50%; top: 50%;
        transform: translate(-50%, -50%);
        margin: 0; z-index: 2;
        /* Stays clickable + tabbable; only the visual position changes.
           Click handler (shop.goto('landing')) attached to the <a>
           element is preserved unmodified. */
      }
      /* Cartier nav doesn't use inline horizontal nav-links — discovery
         lives in the wordmark + utility actions + footer. Hide the
         Shop/Brands/New/Contact row for Premium only. */
      body[data-template="premium"] .nav-left .nav-links {
        display: none;
      }
      /* Collapse .nav-left to zero footprint since its only visible
         child (.nav-brand) is now absolutely positioned and .nav-links
         is hidden. DOM stays intact so any handlers attached to .nav-left
         children remain wired. */
      body[data-template="premium"] .nav-left {
        flex: 0 0 0;
        min-width: 0;
        overflow: hidden;
      }
      /* Optional Premium chrome: serif wordmark with slight tracking +
         smaller weight matches Cartier visual restraint. (Existing luxury
         template already does this at shop.html:551; mirroring for
         Premium so the wordmark feels weighted-right when centered.) */
      body[data-template="premium"] .nav-brand {
        font-weight: 500;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        font-size: 18px;
      }
    }
    /* ── Base ────────────────────────────────────────────────────────── */
    * { box-sizing: border-box; }
    html, body { margin: 0; padding: 0; }

    /* Top loading bar — superseded 2026-05-21 by the Interaction Feedback
       Standard kit (#bk-progress, injected before </head>). The kit's bar
       widens the fetch filter from /api/shop/ to /api/. */
    body {
      font-family: var(--body-font);
      background: var(--bg);
      color: var(--text);
      font-size: 15px; line-height: 1.6;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      /* Wave H-6a (F-SHOP-003) — enable Fraunces variable optical-sizing
         axis so display-size text picks the wider-aperture cut and body
         falls back gracefully per DESIGN.md §3.1.1. */
      font-optical-sizing: auto;
    }
    /* Wave H-6a (F-SHOP-003) — global heading default routes to the
       editorial display face (Fraunces by token). Component-level rules
       below already declare --display-font explicitly; this is the safety
       net for ad-hoc h1/h2/h3 (e.g. confirmation H1 at #5821). */
    h1, h2, h3 { font-family: var(--display-font); }
    a { color: inherit; text-decoration: none; }
    img { display: block; max-width: 100%; height: auto; }
    button {
      font-family: inherit; cursor: pointer;
      border: none; background: none; color: inherit;
    }
    .container {
      max-width: var(--container-w);
      margin: 0 auto; padding: 0 20px;
    }
    @media (min-width: 768px) { .container { padding: 0 40px; } }
    /* pk 2026-06-03 (§5.1) — TIGHTER L/R gutter for the home CARD sections only
       (flash rail · category rail · product-tabs grid · article/content) so the
       cards reach closer to the edge. Desktop 12 / luxury-mobile 6 / phone 4.
       The text sections (hero, trust band, footer) keep the standard gutter.
       id-scoped (1,1,0) out-specifies the base .container (0,1,0). */
    #section-flash-strip > .container,
    #section-cats > .container,
    #section-shop-tabs > .container,
    #section-content > .container { padding-left: 4px; padding-right: 4px; }
    @media (min-width: 768px) {
      #section-flash-strip > .container,
      #section-cats > .container,
      #section-shop-tabs > .container,
      #section-content > .container { padding-left: 12px; padding-right: 12px; }
    }
    @media (max-width: 767px) {
      body[data-template="luxury"] #section-flash-strip > .container,
      body[data-template="luxury"] #section-cats > .container,
      body[data-template="luxury"] #section-shop-tabs > .container,
      body[data-template="luxury"] #section-content > .container { padding-left: 6px; padding-right: 6px; }
    }
    /* pk 2026-06-03 — keep the TEXT section HEADINGS at the ORIGINAL gutter (the
       tightening above moves only the CARDS). Re-inset each card-section heading
       by (standard − tight) = 28 desktop / 22 luxury-mobile / 16 phone, so the
       title sits where it was while the grid below reaches the edge. Flash has no
       text heading (centred countdown) → left as-is. Selector specificity (1,2,0)
       out-specifies the base .section-h / .shop-tabs-head. §5.1. */
    #section-cats > .container > .section-h,
    #section-content > .container > .section-h,
    #section-shop-tabs > .container > .shop-tabs-head { margin-left: 16px; margin-right: 16px; }
    @media (min-width: 768px) {
      #section-cats > .container > .section-h,
      #section-content > .container > .section-h,
      #section-shop-tabs > .container > .shop-tabs-head { margin-left: 28px; margin-right: 28px; }
    }
    @media (max-width: 767px) {
      body[data-template="luxury"] #section-cats > .container > .section-h,
      body[data-template="luxury"] #section-content > .container > .section-h,
      body[data-template="luxury"] #section-shop-tabs > .container > .shop-tabs-head { margin-left: 22px; margin-right: 22px; }
    }
    /* pk 2026-05-31 — REVERTED the landing full-bleed: only the HERO bleeds
       edge-to-edge (its image is absolute-inset, independent of the container
       gutter), every OTHER section keeps the normal .container L/R padding. */

    /* Body sections start hidden — JS reveals after first data load
       to avoid layout flash with empty state. */
    .needs-data { opacity: 0; transition: opacity 220ms ease; }
    .needs-data.ready { opacity: 1; }

    /* ── Top bar ────────────────────────────────────────────────────── */
    .nav {
      position: sticky; top: 0; z-index: 50;
      background: rgba(255,255,255,0.92);
      backdrop-filter: saturate(180%) blur(12px);
      border-bottom: 1px solid var(--border);
    }
    .nav-inner {
      display: flex; align-items: center;
      padding: 14px 20px; gap: 16px;
    }
    @media (min-width: 768px) { .nav-inner { padding: 18px 40px; } }
    /* nav-left takes the slack so search + lang + cart sit flush right
       (B3 — replaces the old justify-content:space-between, which only
       worked with two children; the search box is now a third child). */
    .nav-left { display: flex; align-items: center; gap: 16px; flex: 1 1 auto; min-width: 0; }
    .nav-brand {
      font-family: var(--display-font);
      font-weight: 700; font-size: 22px; letter-spacing: -0.01em;
      color: var(--text);
      display: flex; align-items: center; gap: 10px;
    }
    .nav-brand img { height: 32px; width: auto; object-fit: contain; }
    body[data-template="luxury"] .nav-brand { font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; font-size: 18px; }
    .nav-links { display: none; gap: 24px; }
    .nav-links a { color: var(--text-mute); font-size: 14px; font-weight: 500; }
    .nav-links a:hover { color: var(--accent); }
    @media (min-width: 768px) { .nav-links { display: flex; } }
    .nav-right { display: flex; align-items: center; gap: 12px; }
    .lang-toggle {
      display: inline-flex; border: 1px solid var(--border);
      border-radius: 999px; overflow: hidden; font-size: 12px; font-weight: 600;
    }
    .lang-toggle button {
      /* B3 — ~40px tap target for the TH/EN toggle (was ~29px). */
      padding: 11px 14px; color: var(--text-mute);
      transition: background 160ms ease, color 160ms ease;
    }
    .lang-toggle button.active { background: var(--text); color: #fff; }
    .nav-cart {
      width: 40px; height: 40px; border-radius: 999px;  /* B3 — 40px tap */
      background: var(--bg-soft); display: inline-flex; align-items: center;
      justify-content: center; font-size: 18px;
      transition: background 160ms ease;
    }
    .nav-cart:hover { background: var(--border); }
    /* pk 2026-06-03: equalize the 3 topbar control glyphs (cart / bell / account)
       — each button had a different font-size (cart 18 / acct 17 / bell unset) so
       the icons rendered at slightly different sizes. Pin the inner .bk-icon to
       18px regardless of the button's font-size. (Boxes are all 40px at base; the
       two mobile media-queries below now resize all three together.) */
    .nav-cart > .bk-icon,
    .nav-bell > .bk-icon,
    #nav-acct-icon > .bk-icon { width: 18px; height: 18px; font-size: 18px; }
    /* T4.5 — global product search box in the nav
       Round 29 — heavier border + inline leading magnifying-glass icon
       (pk UAT walk item 1: "Heavier search bar with magnifying glass
       INSIDE"). The form is the positioning context; the SVG sits
       absolute-left inside it; the input owns the padding-left that
       clears the icon. */
    .nav-search {
      display: flex; align-items: center; position: relative;
    }
    .nav-search-icon {
      position: absolute; left: 14px; top: 50%;
      transform: translateY(-50%);
      width: 18px; height: 18px; color: var(--text-mute);
      pointer-events: none;
      transition: color 160ms ease;
    }
    .nav-search:focus-within .nav-search-icon { color: var(--accent); }
    .nav-search input {
      width: 150px; max-width: 44vw; font-family: inherit; font-size: 14px;
      /* B3 — 44px tap target. Round 29: 1.5px border + extra left
         padding to clear the icon. */
      padding: 11px 16px 11px 40px;
      border: 1.5px solid var(--border); border-radius: 999px;
      background: var(--bg); color: var(--text); outline: none;
      transition: border-color 160ms ease, box-shadow 160ms ease, width 160ms ease;
    }
    .nav-search input:hover { border-color: var(--text-mute); }
    .nav-search input:focus {
      border-color: var(--accent);
      box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
    }
    @media (min-width: 768px) { .nav-search input { width: 220px; } }

    /* ── ConWerse r6 (2026-05-31, pk) — mobile top bar: one line ──
       On phones, hide the TH/EN language toggle and let the search box sit in
       the top bar inline (brand · search · cart · profile) on a single row.
       The language switch still exists in the account/menu surface; removing
       it from the bar reclaims the width the search needs. Cite DESIGN.md §8. */
    @media (max-width: 767px) {
      .nav-inner { flex-wrap: nowrap; gap: 10px; padding: 10px 14px; }
      /* brand shrinks so search has room; links already hidden < 768px. */
      .nav-left { flex: 0 0 auto; gap: 8px; }
      .nav-brand { font-size: 18px; }
      .nav-brand img { height: 26px; }
      /* search takes the remaining slack as the middle element, one line. */
      .nav-search { flex: 1 1 auto; min-width: 0; }
      .nav-search input { width: 100%; max-width: none; padding: 9px 12px 9px 36px; }
      .nav-search-icon { left: 11px; width: 16px; height: 16px; }
      /* hide language toggle on phones per pk. */
      .nav-right .lang-toggle { display: none; }
      .nav-right { gap: 6px; flex: 0 0 auto; }
      /* pk 2026-06-03: resize all 3 topbar controls together (was .nav-cart only
         → bell/account stayed 40px = the size mismatch). */
      .nav-cart, .nav-bell, .nav-acct-btn { width: 36px; height: 36px; }
      /* …but keep language reachable from the account dropdown on mobile. */
      .nav-acct-menu .nav-acct-lang { display: inline-flex; margin: 8px 12px 4px; }
    }
    /* In-menu lang row is mobile-only: the bar toggle covers desktop. */
    .nav-acct-menu .nav-acct-lang { display: none; }

    /* ── Hero (default = modern) ─────────────────────────────────────── */
    .hero {
      padding: 48px 0 64px 0; background: var(--bg);
    }
    .hero-inner {
      display: grid; gap: 32px;
      grid-template-columns: 1fr;
      align-items: center;
    }
    @media (min-width: 900px) {
      .hero-inner { grid-template-columns: 1.05fr 1fr; gap: 56px; }
    }
    .hero-text h1 {
      font-family: var(--display-font);
      font-size: clamp(36px, 6vw, 64px);
      letter-spacing: var(--tracking-hero);
      line-height: 1.05;
      font-weight: 700;
      margin: 0 0 18px 0;
      color: var(--text);
    }
    .hero-text p {
      font-size: clamp(15px, 2vw, 18px);
      color: var(--text-mute);
      max-width: 540px; margin: 0 0 28px 0;
    }
    .btn-primary {
      display: inline-flex; align-items: center; gap: 10px;
      background: var(--accent); color: #fff;
      padding: 14px 24px; border-radius: 999px;
      font-weight: 600; font-size: 14px;
      transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
    }
    .btn-primary:hover { transform: translateY(-1px); box-shadow: 0 12px 30px -10px rgba(0,0,0,0.3); }
    /* #2 (2026-06-01) — real disabled look for the PDP add/buy CTAs.
       _pdpPaintPriceStock already sets pd-add-btn / pd-buy-now .disabled for a
       sold-out product, but .btn-primary had no :disabled style, so it still
       looked clickable (pk's "nothing happens, no feedback"). DESIGN.md §4.5. */
    .btn-primary:disabled, .btn-primary[disabled] {
      opacity: 0.45; cursor: not-allowed; box-shadow: none; transform: none;
    }
    .btn-primary:disabled:hover, .btn-primary[disabled]:hover { transform: none; box-shadow: none; }
    .btn-ghost {
      display: inline-flex; align-items: center; gap: 8px;
      color: var(--text); font-weight: 600; font-size: 14px;
      padding: 14px 0;
    }
    .btn-ghost::after { content: "→"; transition: transform 180ms ease; }
    .btn-ghost:hover::after { transform: translateX(4px); }
    .hero-media {
      position: relative; aspect-ratio: 4 / 5;
      background: var(--bg-soft) center/cover;
      border-radius: var(--card-radius); overflow: hidden;
    }
    .hero-media.empty {
      /* v3-2 §1 — the banned striped skeleton (repeating stripe gradient)
         is replaced with a solid inset surface. --bk-surface-inset is the
         DESIGN.md token; falls back to this file's --bg-soft, since
         bk-shop.css doesn't define the bk-* surface scale. */
      background: var(--bk-surface-inset, var(--bg-soft));
    }

    /* Luxury hero: full-bleed image with title overlay */
    body[data-template="luxury"] .hero { padding: 0; }
    /* pk 2026-05-31 — the hero (banner carousel) is FULL-BLEED: no L/R margin.
       The earlier "revert landing full-bleed" restored .container's 40px gutter
       + 1280px max-width to ALL sections incl. the hero — re-strip them HERE so
       the hero spans the viewport edge-to-edge (other sections keep the gutter).
       The headline/CTA overlay (.hero-text / .hero-slide-copy) keeps its own
       inset for readability. */
    body[data-template="luxury"] .hero > .container {
      max-width: none; padding-left: 0; padding-right: 0;
    }
    body[data-template="luxury"] .hero-inner {
      grid-template-columns: 1fr;
      min-height: var(--hero-h);
      position: relative;
      gap: 0;
    }
    body[data-template="luxury"] .hero-media {
      position: absolute; inset: 0; aspect-ratio: auto;
      border-radius: 0;
    }
    body[data-template="luxury"] .hero-media::after {
      content: ''; position: absolute; inset: 0;
      background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.5));
    }
    body[data-template="luxury"] .hero-text {
      position: relative; z-index: 2; padding: 0 20px 80px 20px;
      color: #fff; align-self: end; max-width: var(--container-w);
      margin: 0 auto; width: 100%;
    }
    @media (min-width: 768px) {
      body[data-template="luxury"] .hero-text { padding: 0 56px 96px 56px; }
    }
    body[data-template="luxury"] .hero-text h1 {
      color: #fff;
      font-size: clamp(44px, 7vw, 92px);
      font-weight: 500; letter-spacing: -0.005em;
    }
    body[data-template="luxury"] .hero-text p { color: rgba(255,255,255,0.86); }
    body[data-template="luxury"] .btn-primary { background: #fff; color: var(--accent); }

    /* ── Luxury hero carousel (pk 2026-05-31) ──────────────────────────
       Endless-loop banner carousel that replaces the single-image luxury
       top hero. The carousel lives INSIDE #hero-media (still position:
       absolute; inset:0 — the full-bleed bg layer) so the existing
       luxury hero geometry (min-height:var(--hero-h), bottom gradient
       via .hero-media::after) is reused unchanged. Each slide is a
       full-bleed clickable image with its own copy overlay.
       Motion: only transform/opacity per DESIGN.md §4.4; spring/standard
       easing tokens mirrored locally (bk-shop.css predates the bk-* token
       scale). Touch targets ≥44px per §8.2. Reduced-motion handled below. */
    .hero-carousel {
      position: absolute; inset: 0; overflow: hidden;
    }
    /* The sliding track: a horizontal row of full-width slides translated
       by index. translateX only (transform) — never left/width per §4.4. */
    .hero-carousel-track {
      display: flex; height: 100%;
      will-change: transform;
      transition: transform 520ms cubic-bezier(0.2, 0, 0, 1); /* --bk-ease-standard */
    }
    .hero-carousel.is-dragging .hero-carousel-track { transition: none; }
    .hero-carousel.no-anim .hero-carousel-track { transition: none; } /* clone wrap jump */
    .hero-slide {
      position: relative; flex: 0 0 100%; width: 100%; height: 100%;
      background: var(--bg-soft) center/cover no-repeat;
      /* pk 2026-05-31 — per-viewport carousel image: desktop crop by default,
         mobile/app crop below 768px (set as --bg-d / --bg-m inline). */
      background-image: var(--bg-d, none);
      display: block; color: #fff; text-decoration: none;
      -webkit-user-drag: none; user-select: none;
    }
    @media (max-width: 767px) {
      .hero-slide { background-image: var(--bg-m, var(--bg-d, none)); }
    }
    /* Bottom-up scrim so overlay copy stays legible on any image. Mirrors
       the single-image .hero-media::after gradient. */
    .hero-slide::after {
      content: ''; position: absolute; inset: 0; pointer-events: none;
      background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.5));
    }
    .hero-slide-copy {
      position: absolute; inset: auto 0 0 0; z-index: 2;
      padding: 0 20px 80px 20px; max-width: var(--container-w);
      margin: 0 auto; width: 100%; pointer-events: none;
    }
    .hero-slide-copy > * { pointer-events: auto; }
    @media (min-width: 768px) {
      .hero-slide-copy { padding: 0 56px 96px 56px; }
    }
    .hero-slide-copy h2 {
      font-family: var(--display-font);
      color: #fff; margin: 0 0 18px 0;
      font-size: clamp(44px, 7vw, 92px);
      font-weight: 500; letter-spacing: -0.005em; line-height: 1.05;
      text-wrap: balance;
    }
    .hero-slide-copy p {
      color: rgba(255,255,255,0.86);
      font-size: clamp(15px, 2vw, 18px);
      max-width: 540px; margin: 0 0 28px 0;
    }
    .hero-slide-cta {
      display: inline-flex; align-items: center; gap: 10px;
      background: #fff; color: var(--accent);
      padding: 14px 24px; border-radius: 999px;
      font-weight: 600; font-size: 14px; min-height: 44px;
      transition: transform 180ms cubic-bezier(0.2,0,0,1),
                  box-shadow 180ms cubic-bezier(0.2,0,0,1);
    }
    .hero-slide-cta:hover  { transform: translateY(-1px); box-shadow: 0 12px 30px -10px rgba(0,0,0,0.35); }
    .hero-slide-cta:active { transform: translateY(0); }
    /* Prev/next arrows — 44px hit target per §8.2; transform/opacity motion. */
    .hero-carousel-arrow {
      position: absolute; top: 50%; transform: translateY(-50%);
      z-index: 3; width: 44px; height: 44px; border-radius: 999px;
      display: flex; align-items: center; justify-content: center;
      border: none; cursor: pointer; color: #111;
      background: rgba(255,255,255,0.82);
      box-shadow: 0 6px 18px -8px rgba(0,0,0,0.45);
      transition: transform 160ms cubic-bezier(0.34,1.56,0.64,1), /* spring */
                  background-color 160ms cubic-bezier(0.2,0,0,1),
                  opacity 160ms cubic-bezier(0.2,0,0,1);
    }
    .hero-carousel-arrow:hover  { background: #fff; transform: translateY(-50%) scale(1.06); }
    .hero-carousel-arrow:active { transform: translateY(-50%) scale(0.96); }
    .hero-carousel-arrow:focus-visible {
      outline: 3px solid var(--brand-primary-ring, rgba(255,255,255,0.9));
      outline-offset: 2px;
    }
    .hero-carousel-arrow.prev { left: 16px; }
    .hero-carousel-arrow.next { right: 16px; }
    .hero-carousel-arrow svg { width: 18px; height: 18px; }
    /* Dot indicators — each dot ≥44px hit area via padding; visible pill 8px. */
    .hero-carousel-dots {
      position: absolute; left: 0; right: 0; bottom: 18px; z-index: 3;
      display: flex; gap: 6px; justify-content: center; pointer-events: none;
    }
    .hero-carousel-dot {
      pointer-events: auto; appearance: none; border: none; cursor: pointer;
      width: 44px; height: 44px; padding: 0; background: none;
      display: flex; align-items: center; justify-content: center;
    }
    .hero-carousel-dot::before {
      content: ''; width: 8px; height: 8px; border-radius: 999px;
      background: rgba(255,255,255,0.5);
      transition: transform 200ms cubic-bezier(0.34,1.56,0.64,1),
                  background-color 200ms cubic-bezier(0.2,0,0,1);
    }
    .hero-carousel-dot[aria-current="true"]::before {
      background: #fff; transform: scale(1.5);
    }
    .hero-carousel-dot:focus-visible { outline: none; }
    .hero-carousel-dot:focus-visible::before {
      outline: 3px solid var(--brand-primary-ring, rgba(255,255,255,0.9));
      outline-offset: 3px;
    }
    /* §8.6 reveal safety + §4.4 reduced-motion: kill slide/track motion but
       keep everything fully visible and operable (arrows/dots still switch
       slides instantly; autoplay is disabled in JS under reduced-motion). */
    @media (prefers-reduced-motion: reduce) {
      .hero-carousel-track { transition: none; }
      .hero-carousel-arrow,
      .hero-carousel-dot::before,
      .hero-slide-cta { transition: none; }
    }

    /* Catalog hero: thin promo banner replaces the big hero entirely */
    body[data-template="catalog"] .hero {
      display: none;
    }
    .catalog-strip {
      display: none; padding: 12px 20px; background: var(--accent); color: #fff;
      font-size: 13px; font-weight: 500; text-align: center;
    }
    body[data-template="catalog"] .catalog-strip { display: block; }
    .catalog-strip a { text-decoration: underline; }

    /* ── Section heading ─────────────────────────────────────────────── */
    .section { padding: var(--section-pad-y) 0; }
    /* pk 2026-05-31 — close the big gap below the hero. The earlier flash-only
       rule missed because the first VISIBLE band isn't always the flash strip
       (sections toggle on/off), so the next band still showed its 56px top
       padding. Zero the top padding of every top-level landing band (their
       bottom padding still spaces them apart); the hero (padding:0) now butts
       right up to whatever section is first. Direct-child only, so the nested
       shop-tab panels keep their internal spacing. */
    body[data-template="luxury"] #page-landing > .section { padding-top: 0; }
    /* pk 2026-05-31 (items 1+9) — the 56px section padding left too much air
       between every band (esp. hero↔flash). Tighten the gap between landing
       bands to a tidy, even rhythm: 28px bottom padding per band on luxury
       (was --section-pad-y 56px). hero stays padding:0 so flash sits close. */
    /* pk 2026-05-31 — section rhythm bumped to 36px (mobile) / 48px (≥768). */
    body[data-template="luxury"] #page-landing > .section { padding-bottom: 36px; }
    body[data-template="luxury"] #page-landing > .benefits-strip,
    body[data-template="luxury"] #page-landing > .brand-strip { padding-top: 0; padding-bottom: 36px; }
    @media (min-width: 768px) {
      body[data-template="luxury"] #page-landing > .section,
      body[data-template="luxury"] #page-landing > .benefits-strip,
      body[data-template="luxury"] #page-landing > .brand-strip { padding-bottom: 48px; }
    }
    /* pk 2026-05-31 — the hero is full-bleed (padding:0) so it contributed NO
       gap below itself → hero↔flash sat flush (0) while every other band has a
       28/36px gap. Give the hero the SAME bottom spacing so the rhythm is even:
       hero↔flash now matches band↔band. (Promotion banner #section-p6-hero is a
       .section, so its top/bottom already use this same 28/36 rhythm.) */
    body[data-template="luxury"] #page-landing > .hero { margin-bottom: 36px; }
    @media (min-width: 768px) {
      body[data-template="luxury"] #page-landing > .hero { margin-bottom: 48px; }
    }
    /* pk 2026-06-01 — the PROMOTION BANNER (#section-p6-hero) is the second
       full-bleed band (alongside the hero carousel): strip its .container gutter
       (28px mobile ~267 / 40px desktop ~914) so the promo image runs to both
       screen edges, and drop .p6-hero's 16px corner rounding in this context so
       it matches the carousel's flush edges. Mirrors the hero rule above. All
       OTHER landing bands keep their gutter (DESIGN.md §2 spacing scale). */
    body[data-template="luxury"] #section-p6-hero > .container {
      max-width: none; padding-left: 0; padding-right: 0;
    }
    body[data-template="luxury"] #section-p6-hero .p6-hero { border-radius: 0; }
    /* pk 2026-05-31 — value-props icons+text sat too HIGH: the inner
       .benefits-grid has symmetric 36px top/bottom padding, but the luxury
       strip override added padding-bottom (28/36) with padding-top:0 → more air
       below the content than above. Match the strip's top padding to its bottom
       so the content is vertically CENTERED in the band. */
    body[data-template="luxury"] #page-landing > .benefits-strip { padding-top: 36px; }
    @media (min-width: 768px) {
      body[data-template="luxury"] #page-landing > .benefits-strip { padding-top: 48px; }
    }
    /* pk 2026-05-31 — extra breathing room between the value-props band and the
       Promotion banner below it (more than the standard band rhythm). */
    body[data-template="luxury"] #page-landing > #section-p6-hero { margin-top: 36px; }
    @media (min-width: 768px) {
      body[data-template="luxury"] #page-landing > #section-p6-hero { margin-top: 48px; }
    }
    /* pk 2026-05-31 — remove the trust-signals band + the blog/Stories section
       from the luxury landing. !important beats the JS reveal (it sets inline
       display:''). Reversible: delete this rule (data/markup untouched). */
    body[data-template="luxury"] #section-trust,
    body[data-template="luxury"] #section-content { display: none !important; }
    /* T1.4 — homepage key-benefits strip */
    .benefits-strip {
      background: var(--bg-soft);
      border-top: 1px solid var(--border);
      border-bottom: 1px solid var(--border);
    }
    .benefits-grid {
      display: grid; grid-template-columns: repeat(2, 1fr);
      gap: 28px 16px; padding: 36px 0;
    }
    @media (min-width: 760px) {
      .benefits-grid { grid-template-columns: repeat(4, 1fr); }
    }
    .benefit {
      display: flex; flex-direction: column; align-items: center;
      text-align: center; gap: 9px;
    }
    .benefit svg { width: 30px; height: 30px; color: var(--accent); flex-shrink: 0; }
    .benefit-title { font-size: 14px; font-weight: 700; color: var(--text); }
    .benefit-desc { font-size: 12px; color: var(--text-mute); line-height: 1.45; max-width: 22ch; }
    /* Phase 4.5-F F-8 — PDP trust-signals strip. Reuses storefront.value_props
       from the admin (pk amendment LOCKED: no parallel schema). Compact
       horizontal scroll-snap layout on mobile; balanced 2-3 col grid on
       desktop. Hidden until paintPdpValueProps() reveals it on a product
       whose tenant has cards configured. */
    .pd-value-props {
      margin: 20px 0 8px;
      background: var(--bg-soft);
      border-top: 1px solid var(--border);
      border-bottom: 1px solid var(--border);
      padding: 16px 0;
    }
    .pd-value-props-grid {
      display: flex; gap: 12px;
      overflow-x: auto; overflow-y: hidden;
      scroll-snap-type: x mandatory;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
      padding: 4px 12px;
    }
    .pd-value-props-grid::-webkit-scrollbar { display: none; }
    .pd-vp-card {
      flex: 0 0 64%;
      max-width: 240px;
      scroll-snap-align: start;
      display: flex; flex-direction: column; align-items: flex-start;
      gap: 6px;
      padding: 12px 14px;
      background: var(--bg); border: 1px solid var(--border);
      border-radius: 10px;
    }
    .pd-vp-card svg {
      width: 22px; height: 22px; color: var(--accent); flex-shrink: 0;
    }
    .pd-vp-title {
      font-size: 13px; font-weight: 700; color: var(--text);
      line-height: 1.3;
    }
    .pd-vp-sub {
      font-size: 12px; color: var(--text-mute);
      line-height: 1.45;
    }
    @media (min-width: 700px) {
      .pd-value-props-grid {
        overflow: visible;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        padding: 4px 16px;
      }
      .pd-vp-card {
        flex: initial; max-width: none;
      }
    }
    /* T1.5 — admin-configurable trust signals */
    .trust-stats {
      display: grid; grid-template-columns: repeat(2, 1fr);
      gap: 28px 16px;
    }
    @media (min-width: 700px) {
      .trust-stats { grid-template-columns: repeat(var(--trust-cols, 4), 1fr); }
    }
    .trust-stat { text-align: center; }
    .trust-stat-value {
      font-family: var(--display-font);
      font-size: clamp(28px, 4vw, 40px); font-weight: 700;
      color: var(--accent); line-height: 1.1;
    }
    .trust-stat-label {
      font-size: 13px; font-weight: 600; color: var(--text-mute);
      margin-top: 6px;
    }
    .section-h {
      display: flex; align-items: end; justify-content: space-between;
      gap: 16px; margin-bottom: 32px;
    }
    .section-h h2 {
      font-family: var(--display-font);
      font-size: clamp(24px, 3vw, 36px);
      font-weight: 700;
      letter-spacing: -0.01em;
      margin: 0;
      /* Phase 4 — Premium template uses uppercase + 0.08em tracking via
         these CSS variables (defaults are 'none' + '0' so modern/catalog/
         luxury unchanged). */
      text-transform: var(--section-header-transform);
    }
    body[data-template="luxury"] .section-h h2 { font-weight: 500; letter-spacing: 0; }
    body[data-template="premium"] .section-h h2 {
      font-weight: 500;
      font-size: clamp(15px, 1.6vw, 18px);  /* Cartier headers are SMALLER + uppercase */
      letter-spacing: var(--section-header-tracking);
    }
    .section-h .more {
      color: var(--text-mute); font-size: 14px; font-weight: 500;
      display: inline-block; padding: 6px 0;   /* B3 — taller tap row */
    }
    .section-h .more:hover { color: var(--accent); }

    /* ── Categories grid ─────────────────────────────────────────────── */
    .cat-grid {
      display: grid; gap: 12px;
      grid-template-columns: repeat(2, 1fr);
    }
    @media (min-width: 768px) { .cat-grid { grid-template-columns: repeat(4, 1fr); gap: 16px; } }
    body[data-template="catalog"] .cat-grid {
      grid-template-columns: repeat(4, 1fr);
    }
    @media (min-width: 768px) {
      body[data-template="catalog"] .cat-grid { grid-template-columns: repeat(8, 1fr); }
    }
    .cat-card {
      display: block; aspect-ratio: 1 / 1;
      border-radius: var(--card-radius); overflow: hidden;
      background: var(--bg-soft) center/cover;
      position: relative;
      transition: transform 240ms ease;
    }
    .cat-card:hover { transform: translateY(-2px); }
    .cat-card .label {
      position: absolute; left: 12px; bottom: 12px; right: 12px;
      color: #fff; font-weight: 600; font-size: 14px;
      text-shadow: 0 1px 4px rgba(0,0,0,0.3);
    }
    body[data-template="catalog"] .cat-card .label { font-size: 11px; }
    .cat-card::after {
      content: ''; position: absolute; inset: 0;
      background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.45));
    }

    /* ═══ ConWerse redesign (2026-05-31, pk) ═══════════════════════════════
       Cites DESIGN.md §5.1 (cinematic spacing), §3.1 (Fraunces display),
       §3.5 (sentence case), §4.0 (radius scale), §4.5 (button states),
       §8.5 (intrinsic grid), §2.7 (contrast). Storefront landing only. */

    /* ── Brands + Categories: even spacing, horizontal scroll when many ──
       pk: distribute evenly; scroll left/right when >5 brands (>4 on phone).
       Implemented with scroll-snap + flex; min tile width forces overflow
       once the count exceeds the comfortable fit, while `justify-content`
       keeps a short row evenly spread. Replaces the old wrap-on-desktop. */
    .brand-strip-inner {
      scroll-snap-type: x proximity;
    }
    @media (min-width: 768px) {
      /* Even distribution for a short row; once tiles overflow the track,
         flex-start + scroll kicks in naturally (space-between collapses to
         start when content exceeds width). No wrap → always a single
         scrollable row, per pk. */
      .brand-strip-inner { flex-wrap: nowrap; justify-content: space-between; gap: 16px; }
    }
    .brand-item { scroll-snap-align: start; }

    /* Category row: same horizontal-scroll affordance as brands when the
       catalogue has many categories. Mobile shows ~4, desktop distributes
       and scrolls past 5. Uses flex (overrides the grid above on all
       widths) so the scroll-snap row is consistent with brands. */
    #section-cats .cat-grid {
      display: flex; gap: 12px; overflow-x: auto;
      scroll-snap-type: x proximity; scrollbar-width: none;
      padding: 4px 4px 8px; scroll-padding-left: 4px;
    }
    #section-cats .cat-grid::-webkit-scrollbar { display: none; }
    #section-cats .cat-grid > .cat-card {
      flex: 0 0 auto; scroll-snap-align: start;
      /* #2b (pk 2026-06-02): the row already scrolls (flex + overflow-x on
         #section-cats .cat-grid). Smaller min-width so on a phone ~3 show with
         a PEEK of the next → it reads as a swipeable row, nothing looks cut. */
      width: calc((100% - 30px) / 3.4);
      min-width: 116px; aspect-ratio: 1 / 1;
    }
    @media (min-width: 768px) {
      #section-cats .cat-grid > .cat-card { width: 200px; min-width: 200px; }
    }

    /* ── 3-tab product showcase (New arrivals / On sale / Best sellers) ──
       Reuses .prod-grid for panel bodies. Tab buttons are a segmented strip;
       active tab = brand-tinted underline. Panels toggle via JS (one shown). */
    /* pk 2026-05-31 — tabs row: tabs on the left, "ดูทั้งหมด" pinned bottom-right,
       sharing one underline. (Was centered tabs + a centered link under each rail.) */
    /* pk 2026-06-01: tabs (NEW ARRIVALS / BEST SELLERS / PROMOTION) + the
       "See all →" link MUST sit on ONE line on mobile (was wrapping to 2–3
       lines at 360–390px). No-wrap row; the tab GROUP takes the slack and may
       shrink / horizontal-scroll, while the see-all link is pinned to the right
       (margin-left:auto) and never wraps to its own line. */
    #section-shop-tabs .shop-tabs-head {
      display: flex; align-items: flex-end; justify-content: flex-start;
      gap: 12px; flex-wrap: nowrap;
      margin: 0 0 28px; border-bottom: 1px solid var(--border);
    }
    #section-shop-tabs .shop-tabs {
      display: flex; gap: 20px; justify-content: flex-start; flex-wrap: nowrap;  /* pk 2026-06-01: more space between tabs */
      min-width: 0;                 /* allow the tab group to shrink below content size */
      overflow-x: auto;             /* if 3 tabs still don't fit, scroll the tabs (not wrap) */
      scrollbar-width: none;        /* hide the scrollbar (Firefox) */
      -webkit-overflow-scrolling: touch;
    }
    #section-shop-tabs .shop-tabs::-webkit-scrollbar { width: 0; height: 0; display: none; }
    #section-shop-tabs .shop-tabs-seeall {
      flex: 0 0 auto; align-self: flex-end; margin-bottom: 12px;
      margin-left: auto;            /* pin to the right on the SAME row */
      font-size: 14px; font-weight: 600; white-space: nowrap;
      color: var(--accent);
    }
    #section-shop-tabs .shop-tabs-seeall:hover { text-decoration: underline; }
    #section-shop-tabs .shop-tab {
      appearance: none; background: none; border: 0; cursor: pointer;
      font-family: var(--display-font);
      font-size: clamp(18px, 2.4vw, 26px); font-weight: 600;
      letter-spacing: -0.01em; color: var(--text-mute);
      padding: 8px 18px 14px; margin-bottom: -1px;
      border-bottom: 2px solid transparent;
      transition: color 180ms ease, border-color 180ms ease;
      min-height: 44px;  /* §4.5 tap target */
    }
    #section-shop-tabs .shop-tab:hover { color: var(--text); }
    #section-shop-tabs .shop-tab.active {
      color: var(--text); border-bottom-color: var(--accent);
    }
    #section-shop-tabs .shop-tab-panel { padding: 0; }
    #section-shop-tabs .shop-tab-panel .section-h { display: none; } /* tab IS the heading */
    body[data-template="luxury"] #section-shop-tabs .shop-tab { text-transform: uppercase; letter-spacing: 0.08em; font-size: clamp(14px,1.6vw,18px); white-space: nowrap; }
    /* pk 2026-06-01: on mobile, tighten the tab font / letter-spacing / padding
       so all 3 tabs + "See all →" fit on ONE row (≥360px). The tab labels never
       wrap internally (white-space:nowrap above); if the row is still too wide
       the tab group scrolls horizontally (.shop-tabs overflow-x). See-all stays
       pinned right via margin-left:auto. Tap target ≥44px preserved (§8.2). */
    @media (max-width: 600px) {
      #section-shop-tabs .shop-tabs-head { gap: 10px; }
      #section-shop-tabs .shop-tabs { gap: 14px; }   /* pk 2026-06-01: keep spacing between tabs on mobile (scrolls if tight) */
      #section-shop-tabs .shop-tab { padding: 8px 4px 12px; }
      body[data-template="luxury"] #section-shop-tabs .shop-tab {
        /* tightest legible size so all 3 uppercase tabs + "See all →" sit on
           ONE row (full fit ≥390px; at 360–375px the 3rd tab peeks and the tab
           group scrolls — NEVER wraps, see-all stays pinned right). pk dislikes
           edge-fades / visible scrollbars (2026-05-31), so no fade cue added. */
        font-size: 11px; letter-spacing: 0.005em;
      }
      #section-shop-tabs .shop-tabs-seeall { font-size: 12px; }
    }

    /* ── Promotion CTA band — full-bleed image + headline + button ──
       ConWerse "Build Your Style" band. Rounded, image-cover, dark overlay
       for text contrast (§2.7), centered serif headline (§3.1). */
    #section-promo-cta .promo-cta {
      position: relative; overflow: hidden;
      border-radius: var(--card-radius, 16px);
      min-height: 280px;
      display: flex; align-items: center; justify-content: center;
      background: var(--bg-soft) center/cover no-repeat;
      padding: 48px 24px;
    }
    #section-promo-cta .promo-cta-overlay {
      position: absolute; inset: 0;
      background: linear-gradient(180deg, rgba(0,0,0,0.30), rgba(0,0,0,0.50));
      pointer-events: none;
    }
    #section-promo-cta .promo-cta:not(.has-img) .promo-cta-overlay { background: rgba(0,0,0,0.06); }
    #section-promo-cta .promo-cta-inner {
      position: relative; z-index: 1; text-align: center;
      display: flex; flex-direction: column; align-items: center; gap: 22px;
      max-width: 680px;
    }
    #section-promo-cta .promo-cta-title {
      font-family: var(--display-font);
      font-size: clamp(28px, 5vw, 52px); font-weight: 700;
      line-height: 1.1; letter-spacing: -0.02em;
      color: #fff; text-wrap: balance; margin: 0;
      text-shadow: 0 2px 12px rgba(0,0,0,0.35);
    }
    #section-promo-cta .promo-cta:not(.has-img) .promo-cta-title {
      color: var(--text); text-shadow: none;
    }

    /* ── ConWerse r2 (2026-05-31) — product card: price+pill row + actions ──
       Matches the ref: large price on the left, a soft category pill on the
       right, product name below, then a 2-button row (Add to cart / Buy now).
       pk 2026-06-01 — card image is now FULL-BLEED (no tile background); the
       .img rule at ~1998 owns the no-bg + cover + radius. Only the rounded
       corners are restated here for source-order clarity. */
    .prod-card .img {
      border-radius: 18px;   /* editorial full-bleed image radius (non-luxury) */
    }
    body[data-template="luxury"] .prod-card .img { border-radius: 20px; }  /* pk 2026-06-01 — full-bleed rounded image (ref) */
    /* pk 2026-05-31 — FIXED card size on the luxury grid: this rule (higher
       specificity than .shop-tab-rail) is the one that actually renders the
       showcase, so a lone product used to stretch FULL-WIDTH via auto-fit+1fr.
       auto-FILL keeps the empty tracks → every card is the SAME ~240px width
       regardless of item count (a single product no longer blows up). Placed
       AFTER the line-253 luxury rule so it wins on source order. Mobile resizes
       to a 2-up responsive grid (pk: fixed size always, EXCEPT mobile). */
    body[data-template="luxury"] .prod-grid {
      grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
      grid-auto-flow: row;        /* undo .shop-tab-rail column-flow on luxury */
      grid-template-rows: auto;   /* (so cards read left→right, fixed size) */
    }
    @media (max-width: 767px) {
      body[data-template="luxury"] .prod-grid {
        grid-template-columns: repeat(2, 1fr); gap: 14px;
      }
    }
    /* pk 2026-06-01 (editorial card redesign — ref: brand on LEFT, price on
       RIGHT). The brand/category pill sits LEFT (small UPPERCASE muted, letter-
       spaced); the price is pushed to the RIGHT via space-between. The name now
       lives ABOVE this row (markup order), so this row sits at the BOTTOM. */
    .prod-card .prod-price-row {
      display: flex; align-items: baseline; justify-content: space-between;
      gap: 8px; margin-top: 8px;
      min-width: 0;   /* flex parent can shrink below content */
    }
    /* Price hugs the RIGHT. Non-sale = one bold ink price. On sale, the price
       slot reads: ~~฿was~~ (small grey, line-through) then ฿sale (red bold) —
       laid out INLINE side-by-side, sale price prominent on the far right.
       cqi math: 6.7cqi → 16px @≥239px (luxury track), 14.7px @220, 13px @154. */
    .prod-card .prod-price-row .price {
      margin: 0; font-weight: 700;
      font-size: clamp(13px, 6.7cqi, 16px);
      min-width: 0; white-space: nowrap;
      color: var(--bk-ink, var(--ink));
      display: inline-flex; flex-direction: row; align-items: baseline;
      flex: 0 0 auto; justify-content: flex-end;
      gap: 6px; line-height: 1.2; text-align: right;
    }
    /* Brand/category pill — pk 2026-06-01: NOT a chip anymore. Per the ref it's
       bare UPPERCASE muted micro-text on the left (like "MEN'S SHOES"), letter-
       spaced, no background/border. Shrinks first so it ellipsises instead of
       colliding with the price at the 2-up floor. */
    .prod-card .prod-pill {
      flex: 0 1 auto;
      min-width: 0;
      font-size: clamp(9px, 4.4cqi, 11px); font-weight: 600;
      letter-spacing: 0.08em; text-transform: uppercase;
      color: var(--text-mute);
      background: none; border: none; border-radius: 0; padding: 0;
      white-space: nowrap; max-width: 60%;
      overflow: hidden; text-overflow: ellipsis;
    }
    /* pk 2026-06-01 — name fluid; MAX 15px keeps non-luxury identical (luxury
       has its own 17px clamp below). 6.8cqi → 15px @≥221px, 13px floor @154. */
    .prod-card .meta .name { font-size: clamp(12px, 6.8cqi, 15px); }
    /* pk 2026-06-01 (editorial card redesign) — the action buttons now FLOAT on
       the image tile (the .prod-actions wrapper lives INSIDE .img), pinned near
       the bottom, HIDDEN by default and revealed on hover (desktop) / .is-
       revealed (touch tap). Centered horizontally; Buy-now (text) + the square
       cart-icon button sit side by side. z-index keeps them above the photo;
       they clip to the rounded tile (DESIGN.md §4.14 — deliberate in-bounds
       overlay, not an escaping popover). Animate transform+opacity only. */
    .prod-card .img .prod-actions {
      position: absolute; left: 0; right: 0; bottom: 0;
      z-index: 4; margin: 0;
      display: flex; flex-wrap: nowrap; gap: 8px;
      justify-content: center; align-items: center;
      padding: 10px;
      /* hidden resting state — slid down + faded */
      opacity: 0; transform: translateY(8px);
      pointer-events: none;
      transition: opacity var(--bk-dur-fast, 160ms) ease,
                  transform var(--bk-dur-fast, 160ms) ease;
    }
    /* Desktop (fine pointer): reveal on card hover OR keyboard focus. The
       :focus-within reveal (pk 2026-06-01 a11y / WCAG 2.4.7) makes the floating
       Buy-now / cart buttons visible+interactive when a keyboard user Tabs onto
       them — without it the focus-visible ring below paints onto an opacity:0
       box and pointer-events:none blocks activation. */
    @media (hover: hover) {
      .prod-card:hover .img .prod-actions,
      .prod-card:focus-within .img .prod-actions {
        opacity: 1; transform: translateY(0); pointer-events: auto;
      }
    }
    /* Touch (coarse pointer): buttons stay visible — the card itself navigates
       on tap (pk 2026-06-01: tap a button to add/buy via stopPropagation, tap
       the card to open the product; no more tap-to-reveal that blocked nav). */
    @media (hover: none) {
      .prod-card .img .prod-actions {
        opacity: 1; transform: translateY(0); pointer-events: auto;
      }
    }
    /* #5 (2026-06-01, pk) — desktop-only prev/next arrows on the card cover.
       Both buttons use the chevron-right icon (icons.svg has no left); .prev
       flips its glyph. Hover-reveal mirrors .prod-actions; hidden on touch
       (PDP keeps swipe). DESIGN.md §8.2 (44px), §6.1 (white-on-photo elevation). */
    .prod-card .img .prod-img-nav {
      position: absolute; top: 50%; transform: translateY(-50%);
      z-index: 4; width: 44px; height: 44px; border-radius: 999px;
      display: flex; align-items: center; justify-content: center;
      border: none; cursor: pointer; color: #111;
      background: rgba(255, 255, 255, 0.82);
      box-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.45);
      opacity: 0; pointer-events: none;
      transition: opacity var(--bk-dur-fast, 160ms) ease, background-color 160ms ease;
    }
    .prod-card .img .prod-img-nav.prev { left: 8px; }
    .prod-card .img .prod-img-nav.next { right: 8px; }
    .prod-card .img .prod-img-nav .bk-icon { width: 18px; height: 18px; }
    .prod-card .img .prod-img-nav.prev .bk-icon { transform: scaleX(-1); }  /* chevron-right → left */
    .prod-card .img .prod-img-nav:hover { background: #fff; }
    .prod-card .img .prod-img-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; pointer-events: auto; }
    @media (hover: hover) { .prod-card:hover .img .prod-img-nav { opacity: 1; pointer-events: auto; } }
    @media (hover: none) { .prod-card .img .prod-img-nav { display: none; } }
    @media (prefers-reduced-motion: reduce) { .prod-card .img .prod-img-nav { transition: none; } }
    .prod-card .prod-act { flex: 0 0 auto; width: auto; }
    /* pk 2026-05-31 — cart action is a SQUARE icon button (right of Buy now).
       Specificity (.prod-actions .prod-act-cart) deliberately beats the later
       generic ".prod-card .prod-act { padding: 8px 18px }" so the square holds. */
    .prod-card .prod-actions .prod-act-cart {
      width: 40px; min-width: 40px; padding: 0;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .prod-card .prod-act-cart .bk-icon { width: 18px; height: 18px; }
    .prod-card .prod-act {
      appearance: none; cursor: pointer;
      min-height: 44px; padding: 10px 12px;        /* §4.5 tap target */
      border-radius: 10px; font-size: 13px; font-weight: 600;
      transition: background 160ms ease, color 160ms ease, border-color 160ms ease, transform 120ms ease;
    }
    .prod-card .prod-act:active { transform: translateY(1px); }
    .prod-card .prod-act-add {
      background: var(--bg-soft); color: var(--text);
      border: 1px solid var(--border);
    }
    .prod-card .prod-act-add:hover { border-color: var(--text-mute); }
    .prod-card .prod-act-buy {
      background: var(--accent); color: #fff; border: 1px solid var(--accent);
    }
    .prod-card .prod-act-buy:hover { filter: brightness(0.94); }
    /* pk 2026-06-01 — floating-on-tile treatment: the buttons now sit ON the
       (light) image tile, so give them a soft float-shadow + a solid white cart
       button so it reads against var(--bg-soft). focus-visible ring for the
       keyboard path (the buttons are reachable once hover-revealed on desktop;
       on touch they appear after the first tap). */
    .prod-card .img .prod-actions .prod-act {
      box-shadow: 0 4px 12px -4px rgba(26,26,26,0.28),
                  0 1px 2px rgba(26,26,26,0.10);
    }
    .prod-card .img .prod-actions .prod-act-add {
      background: #fff; color: var(--text); border-color: rgba(26,26,26,0.08);
    }
    .prod-card .img .prod-actions .prod-act-add:hover {
      background: #fff; border-color: rgba(26,26,26,0.18);
    }
    .prod-card .img .prod-actions .prod-act:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    /* The whole card is an <a>; keep the hover-lift off when a button is hovered
       so button clicks (which stopPropagation) feel distinct from card→PDP. */
    .prod-card:has(.prod-actions:hover) { transform: none; }

    /* ── Taxonomy cards (categories + brands): rounded ConWerse tiles ── */
    #section-cats .cat-card { border-radius: 16px; }
    .brand-item { border-radius: 16px; }

    /* ── ConWerse r3 (2026-05-31) — product card: smaller + more rounded ──
       pk: reduce card size, rounder corners, remove L/R card margin. */
    /* pk 2026-05-31 — auto-FILL (not auto-fit): a lone product stays its normal
       ~220px width instead of stretching to fill the whole row. */
    /* pk 2026-06-01 — mobile column gap 14px→17px ("เพิ่มระยะ ... นิดหน่อย");
       desktop 18px kept (already roomy). */
    .prod-grid { gap: 17px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
    @media (min-width: 768px) { .prod-grid { gap: 18px; } }
    .prod-card .img { border-radius: 18px; }
    /* pk 2026-06-01 — button LABEL scales with card width so "Buy now" doesn't
       overflow the short button @2-up; tap area is unchanged (min-height 40px
       below + the square cart 40px hit box at line ~1644 are NOT touched, so
       §8.2 ≥40px stands). 5.0cqi → 12px MAX pinned @≥240px (desktop unchanged),
       11px floor @154. */
    .prod-card .prod-act { border-radius: 10px; min-height: 40px; font-size: clamp(11px, 5cqi, 12px); padding: 8px 18px; }
    /* This rule used to re-pin a FIXED 16px and would DEFEAT the clamp above;
       now it carries the SAME clamp so the fluid value wins on source order.
       MAX 16px = today's rendered desktop size → full-width cards unchanged. */
    .prod-card .prod-price-row .price { font-size: clamp(13px, 6.7cqi, 16px); }
    .prod-card .meta { padding: 10px 0 2px; }   /* L/R card inner margin → 0 (pk) */
    .prod-card .meta .name { font-size: clamp(12px, 6.8cqi, 15px); }
    /* pk 2026-06-01 (editorial card redesign) — the highlight/description line
       is REMOVED from the card (ref shows only name + brand/price row). The
       renderer no longer emits .prod-desc; this rule is kept as a defensive
       hide so any stale/cached markup path that still emits it stays invisible. */
    .prod-card .prod-desc { display: none; }

    /* ── pk 2026-05-31 — product showcase PEEK-SCROLL rail (chose "peek + edge
       fade" over arrows). 2 rows; column width is sized so 2 full cards + a
       PEEK of the next show, and a soft right-edge fade tells the user "scroll
       for more" — no arrows, manual horizontal scroll. Desktop shows ~3 full +
       peek (wider viewport). The #section-shop-tabs id-scope is DELIBERATE: it
       raises specificity above `body[data-template="luxury"] .prod-grid` (0,2,1),
       which would otherwise force the demo's rail into a wrapping auto-fill grid.
       pk 2026-05-31 r2: dropped the gradient fade (pk: "ไม่สวยเลย") + fixed
       desktop mouse (couldn't scroll a hidden-scrollbar rail) by (a) CLICK-DRAG
       to scroll via JS (cursor: grab) and (b) a slim VISIBLE scrollbar as the
       clean "more →" cue. Still 2 rows; a small peek of the next card remains. */
    #section-shop-tabs .shop-tab-rail {
      display: grid;
      grid-template-columns: none;             /* override base .prod-grid auto-fill */
      grid-auto-flow: column;
      grid-template-rows: repeat(2, auto);     /* 2 rows */
      /* 2 full cards + a small peek (mobile). pk 2026-06-01 — rail-gap
         fallback 16px→20px to match the landing 2-up card gap (denominator
         2.3 unchanged so the 2-up + peek holds at 360px). */
      grid-auto-columns: calc((100% - 1.3 * var(--rail-gap, 20px)) / 2.3);
      gap: var(--rail-gap, 20px);
      overflow-x: auto;
      scroll-snap-type: x proximity;
      scroll-behavior: smooth;
      padding-bottom: 10px;
      cursor: grab;                            /* mouse affordance: drag to scroll */
      /* pk 2026-06-01 — scrollbar fully hidden (drag/swipe is the affordance);
         was a slim visible scrollbar, removed per "นำแถบ scroll bar ออกทั้งหมด". */
      scrollbar-width: none;
    }
    #section-shop-tabs .shop-tab-rail.is-dragging { cursor: grabbing; scroll-snap-type: none; }
    #section-shop-tabs .shop-tab-rail.is-dragging > * { pointer-events: none; }
    #section-shop-tabs .shop-tab-rail::-webkit-scrollbar { width: 0; height: 0; display: none; }
    #section-shop-tabs .shop-tab-rail > * { scroll-snap-align: start; }
    /* Desktop: wider cards still leave a peek → ~3 full + sliver of the 4th. */
    @media (min-width: 768px) {
      #section-shop-tabs .shop-tab-rail {
        --rail-gap: 18px;
        grid-auto-columns: calc((100% - 2.3 * var(--rail-gap)) / 3.3);
      }
    }
    /* pk 2026-06-01 — small-phone rail gap 12px→14px ("เพิ่มระยะ ... นิดหน่อย");
       still preserves the 2-up + peek (denominator 2.3 unchanged). */
    @media (max-width: 600px) { #section-shop-tabs .shop-tab-rail { --rail-gap: 14px; } }

    /* ── Landing L/R gutter (pk 2026-06-01) ───────────────────────────────
       pk reversed the earlier ConWerse "edge-to-edge on phones" rule: he now
       wants a clear, consistent L/R gutter on EVERY landing section EXCEPT the
       two full-bleed bands — the hero carousel (#hero) and the promotion banner
       (#section-p6-hero). So on mobile the content sections keep the luxury
       28px .container gutter (set ~267), and we ONLY zero the gutter for the two
       full-bleed bands. (Desktop already kept its 40px gutter on content
       sections; only the hero bled — see the revert note ~916.)
       DESIGN.md §2 spacing scale: 28px gutter = the same inset every other band
       uses; the previous 12px inner-rail re-insets are dropped so rails/grids
       line up flush with their section's gutter (no double inset). */
    @media (max-width: 600px) {
      /* The hero carousel is the FULL-BLEED banner — kill its container gutter
         entirely (was 16px) so the slide images run to both viewport edges.
         The slide copy (.hero-slide-copy / .hero-text) keeps its OWN readable
         inset, so zeroing the container does not crowd the headline/CTA. */
      #page-landing .hero .container,
      #page-landing #hero .container { padding-left: 0; padding-right: 0; }
      /* (The promotion banner #section-p6-hero is zeroed at ALL viewports by the
         dedicated luxury rule near the hero block, so it needs no entry here.) */
    }
    /* ═══ end ConWerse redesign ═══ */

    /* ── Brand strip — Round 29 UAT-walk item 5 ──────────────────────
       Tiles are now small cards: bigger logo (~88px tall) + the brand
       name underneath, regardless of whether the brand has a logo
       uploaded. Horizontal scroll preserved on mobile + tablets so a
       shop with many brands stays tappable; the desktop layout still
       distributes when the row fits without scroll. */
    .brand-strip {
      padding: 32px 0;
      /* bg + top/bottom borders removed per pk 2026-05-31 — brand strip now
         sits flush on the page surface (no banded background / hairlines). */
    }
    /* pk 2026-06-01 — FULL-BLEED brand row: zero the .container gutter for THIS
       section only (like the hero / promo bands) so the image-fill tiles run
       edge-to-edge. The .brand-marquee edge-fade mask still dissolves tiles at
       the viewport edges. Scoped to #section-brands so other .container blocks
       keep the luxury gutter. */
    #section-brands > .container {
      padding-left: 0; padding-right: 0;
      max-width: none;
    }
    /* pk 2026-05-31 (item 4) — endless-loop brand marquee.
       .brand-marquee is the clipping viewport (with a soft edge-fade mask so
       tiles dissolve in/out at the edges); .brand-strip-inner is the animated
       track. renderBrands() repeats the tile set --bk-marquee-reps times and
       the keyframe shifts the track by exactly one set (-100%/reps), giving a
       seamless infinite glide. Pauses on hover/focus; prefers-reduced-motion
       turns it back into a plain manual horizontal scroll. */
    .brand-marquee {
      overflow: hidden;
      padding: 8px 0;                 /* headroom for the tile hover-lift */
      -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
              mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
    }
    .brand-strip-inner {
      display: flex; flex-wrap: nowrap; width: max-content;
      gap: 16px; align-items: stretch;
      will-change: transform;
      animation: bk-brand-marquee var(--bk-marquee-dur, 28s) linear infinite;
    }
    .brand-marquee:hover .brand-strip-inner,
    .brand-strip-inner:focus-within { animation-play-state: paused; }
    @keyframes bk-brand-marquee {
      from { transform: translateX(0); }
      to   { transform: translateX(calc(-100% / var(--bk-marquee-reps, 2))); }
    }
    @media (min-width: 768px) {
      .brand-strip-inner { gap: 20px; }
    }
    @media (prefers-reduced-motion: reduce) {
      .brand-marquee { overflow-x: auto; scrollbar-width: none; }
      .brand-marquee::-webkit-scrollbar { display: none; }
      .brand-strip-inner { animation: none; width: auto; }
    }
    /* pk 2026-06-01 — brand tile is now an IMAGE TILE that fills its frame
       (hero_image_url cover, fall back to logo_url, then a neutral name tile).
       The brand text moves ONTO the image as an OPTIONAL overlay with a
       legibility scrim (rendered only when present). Borderless, rounded,
       floating — UNIFIES the brand row with the full-bleed hero/promo bands. */
    .brand-item {
      flex: 0 0 auto;
      position: relative; display: block;
      width: 132px; height: 132px;
      padding: 0;
      /* pk 2026-06-01 — the IMAGE *is* the card: NO background tile. Was
         `background: var(--bg-soft)` (a grey tile showed behind the image / when
         only a logo existed). Now transparent so the brand cover fills the whole
         card edge-to-edge with nothing behind it. */
      background: transparent; border: none;
      border-radius: 16px; overflow: hidden;
      transition: transform 240ms ease;
      cursor: pointer; text-align: left;
      /* pk 2026-06-01 — flat brand tile: no frame, no border, NO glow/shadow;
         the cover image fills the whole card. */
      box-shadow: none;
    }
    @media (min-width: 768px) {
      .brand-item { width: 180px; height: 150px; }
    }
    .brand-item:hover {
      transform: translateY(-2px);   /* subtle lift only — no glow */
    }
    .brand-item:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    /* The cover image fills the whole tile. */
    .brand-item .brand-cover {
      position: absolute; inset: 0;
      width: 100%; height: 100%;
      object-fit: cover; display: block;
    }
    .brand-item:hover .brand-cover { /* no scale — keep edges crisp on small tiles */ }
    /* Logo-fallback (no hero image): the logo FILLS the card like a cover image
       — pk 2026-06-01 "ทำให้รูปเป็น cards ... ไม่มีพื้นหลัง". No grey tile, no
       inset frame: the logo becomes the card surface itself (object-fit: cover
       so it reaches all four rounded edges). */
    .brand-item.is-logo .logo-frame {
      position: absolute; inset: 0;
      display: block; padding: 0;
    }
    .brand-item.is-logo .logo-frame img {
      width: 100%; height: 100%; object-fit: cover; display: block;
    }
    /* OPTIONAL overlay text — only rendered when the brand has a name (and/or
       tagline). Bottom-anchored over a legibility scrim. */
    .brand-item .brand-overlay {
      position: absolute; inset: auto 0 0 0;
      padding: 26px 12px 10px;
      background: linear-gradient(to top,
        rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.30) 55%, transparent 100%);
      display: flex; flex-direction: column; gap: 2px;
      pointer-events: none;
    }
    .brand-item .brand-overlay .name {
      font-family: var(--display-font);
      font-size: 14px; font-weight: 600; letter-spacing: 0.01em;
      color: #fff; line-height: 1.2;
      text-shadow: 0 1px 3px rgba(0,0,0,0.35);
      display: -webkit-box; -webkit-box-orient: vertical;
      -webkit-line-clamp: 2; overflow: hidden;
    }
    .brand-item .brand-overlay .tagline {
      font-size: 11px; font-weight: 500; letter-spacing: 0.01em;
      color: rgba(255,255,255,0.85); line-height: 1.25;
      text-shadow: 0 1px 2px rgba(0,0,0,0.3);
      display: -webkit-box; -webkit-box-orient: vertical;
      -webkit-line-clamp: 1; overflow: hidden;
    }
    /* Name-only fallback (NO logo + NO hero — nothing to show as the image).
       This is the ONLY brand variant that keeps a surface, because there is
       literally no image to be the card; without it the name would float on the
       bare page. Image/logo brands stay background-free per pk. */
    .brand-item.no-image {
      background: var(--bg-soft);
    }
    /* Centre the name on that surface with bumped type so the tile doesn't read
       as half-empty (no scrim — it's not over a photo). */
    .brand-item.no-image .brand-overlay {
      inset: 0; background: none; justify-content: center; align-items: center;
      text-align: center; padding: 12px;
    }
    .brand-item.no-image .brand-overlay .name {
      color: var(--text); font-size: 16px; -webkit-line-clamp: 3;
      text-shadow: none;
    }
    .brand-item.no-image .brand-overlay .tagline {
      color: var(--text-mute); text-shadow: none;
    }

    /* ── Product grid ────────────────────────────────────────────────── */
    /* v3-2 §8.5 — intrinsic auto-fit grid. Replaces the fixed repeat(N)
       + per-breakpoint column-count media queries: the track count now
       derives from available width vs a min card width, so it reflows
       fluidly without breakpoint bookkeeping. Keep the gap (28px at ≥768). */
    .prod-grid {
      display: grid; gap: 16px;
      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    }
    @media (min-width: 768px) {
      .prod-grid { gap: 28px; }
    }
    body[data-template="luxury"] .prod-grid { gap: 36px; }
    body[data-template="catalog"] .prod-grid {
      grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px;
    }
    .prod-card {
      cursor: pointer; position: relative;
      transition: transform 240ms ease;
      /* pk 2026-05-31 (clarified) — the CARD itself is TRANSPARENT: no border,
         no background. Only the cover-image section (.img below) is rounded.
         (Reverts the brief card border+bg from the previous round.) */
      background: none; border: none;
      /* pk 2026-06-01 — make the card a query container so the meta text
         (price / struck price / brand pill / name / highlight / button labels)
         SCALES WITH CARD WIDTH via `cqi` units (pk: "ย่อขนาดของ text ตามไซส์
         ของรูปภาพ"). At 2-up mobile (~154px) the type shrinks to its legibility
         floor so the price never collides with the brand pill; at full-width
         luxury desktop cards (~240-280px) the clamps pin to today's MAX, so
         desktop sizes are unchanged. `inline-size` containment ONLY contains
         the inline axis — it does NOT establish a containing block for
         absolutely-positioned descendants, so the .prod-heart / .prod-ribbon /
         .prod-soldout-veil inside .img stay anchored to .img (which has its own
         position:relative) exactly as before. */
      container-type: inline-size;
    }
    /* #2c (pk 2026-06-02): on hover lift only the IMAGE, not the whole card
       frame (the card edge bouncing looked off). */
    .prod-card .img { transition: transform var(--bk-dur-fast, 200ms) ease; }
    .prod-card:hover { transform: none; }
    .prod-card:hover .img { transform: translateY(-4px); }
    /* pk 2026-05-31 — NO "New / Promo / Flash" badge box on the cover image. */
    .prod-badge {
      display: none;
      position: absolute; top: 10px; left: 10px; z-index: 2;
      font-size: 10px; font-weight: 800; letter-spacing: 0.05em;
      text-transform: uppercase; padding: 4px 9px; border-radius: 999px;
      color: #fff; pointer-events: none; line-height: 1.4;
    }
    .prod-badge-flash { background: #E11D48; }
    .prod-badge-promo { background: var(--accent); }
    .prod-badge-new   { background: #059669; }
    .prod-card .img {
      /* pk 2026-06-01 (full-bleed card — "รูปภาพ เต็ม cards ขอบมน ไม่มีพื้นหลัง"):
         the product image now FILLS the rounded square edge-to-edge — NO soft
         tile background, NO internal padding. The cover image crops to the
         square (object-fit:cover below) so the card reads as a full-bleed photo
         with rounded corners. Still a square (aspect-ratio 1/1); radius + overflow
         + position retained so the heart / sold-out veil / countdown clip to it. */
      aspect-ratio: 1 / 1;
      border-radius: 18px;
      overflow: hidden;
      position: relative;
    }
    /* Phase 4 P3 batch §3.3 (Mr.C 2026-05-25): cards emit <img loading="lazy"
       decoding="async">; the parent .img keeps aspect-ratio + radius + overflow.
       pk 2026-06-01 — object-fit is now COVER (was contain) so the image fills
       the rounded square edge-to-edge; deliberate cropping per pk's full-bleed
       reference. */
    .prod-card .img img {
      width: 100%; height: 100%;
      object-fit: cover;
      display: block;
    }
    /* pk 2026-06-01 — drop the bottom darkening gradient; on a light floating
       tile it just dirties the bottom edge. The floating action buttons get
       their own legibility from their solid pill backgrounds. */
    .prod-card .img::after { content: none; }
    .prod-card .meta {
      padding: 12px 4px 4px 4px;
    }
    .prod-card .name {
      /* pk 2026-06-01 — fluid; MAX 14px = original base size (non-luxury
         unchanged). 6.8cqi → 14px @≥206px, 12px floor @154. */
      font-weight: 600; font-size: clamp(12px, 6.8cqi, 14px);
      letter-spacing: -0.01em;
      color: var(--text);
      margin: 0 0 4px 0;
      overflow: hidden; text-overflow: ellipsis;
      display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    }
    body[data-template="luxury"] .prod-card .name {
      font-family: var(--display-font); font-weight: 500;
      /* pk 2026-06-01 — luxury name fluid; MAX 17px = today's luxury size, so
         full-width luxury cards are unchanged. 7.4cqi → 17px @≥230px, 13px
         floor @154. This is the HIGHEST-specificity .name rule on luxury so it
         is the one that actually renders — converting it (not just the base) is
         what makes the luxury name scale. */
      font-size: clamp(13px, 7.4cqi, 17px); letter-spacing: 0;
    }
    .prod-card .price {
      /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast.
         pk 2026-06-01 — MAX 14px kept; this 0,0,2 rule LOSES to the 0,0,3
         .prod-price-row .price clamp above on source order anyway, but make it
         fluid too so any context that hits THIS rule still scales. */
      font-weight: 600; font-size: clamp(12px, 6.4cqi, 14px); color: var(--bk-ink, var(--ink));
    }
    body[data-template="modern"] .prod-card .price { font-weight: 700; }
    /* Phase E — "🔥 N sold" line on a best-seller card. */
    .prod-card .prod-sold {
      margin-top: 3px; font-size: 11px; font-weight: 700; color: #B45309;
    }
    .prod-card .quickadd {
      position: absolute; right: 10px; bottom: 64px;
      /* v3-2 §8.2 — ≥44×44 AAA tap target (was 36×36 + hover-only ⇒
         untappable on touch). */
      width: 44px; height: 44px; min-width: 44px; min-height: 44px;
      border-radius: 999px;
      background: var(--accent); color: #fff;
      display: none; align-items: center; justify-content: center;
      font-size: 18px; line-height: 1;
      transition: transform 180ms ease;
    }
    /* v3-2 §8.2 — reveal-on-hover is fine-pointer only; gate it behind
       (hover:hover) so coarse-pointer (touch) devices, which can't hover,
       always show the quick-add and can actually tap it. */
    @media (hover: hover) {
      body[data-template="modern"] .prod-card:hover .quickadd {
        display: inline-flex;
      }
    }
    @media (hover: none) {
      body[data-template="modern"] .prod-card .quickadd {
        display: inline-flex;
      }
    }
    .prod-card .quickadd:hover { transform: scale(1.08); }

    /* F-9 quick-wishlist heart on product card (top-right of card image).
       Always visible (mobile users have no hover); 36px touch target so
       thumb tap works in the one-handed grid view. Outline = not wishlisted;
       filled rose = wishlisted. State paint is JS-side via .is-on class
       (shop._paintHeart). Click toggles via /api/customer/wishlist; anon
       users get a bk.toast nudge instead. */
    .prod-card .prod-heart {
      position: absolute; top: 10px; right: 10px; z-index: 3;
      width: 36px; height: 36px; border-radius: 999px;
      background: rgba(255,255,255,0.92);
      backdrop-filter: blur(4px);
      -webkit-backdrop-filter: blur(4px);
      border: 1px solid rgba(0,0,0,0.06);
      box-shadow: 0 1px 2px rgba(0,0,0,0.08);
      display: inline-flex; align-items: center; justify-content: center;
      cursor: pointer;
      color: #6b7280;
      transition: transform 160ms ease, color 160ms ease, background 160ms ease;
      padding: 0;
    }
    .prod-card .prod-heart svg { width: 18px; height: 18px; display: block; }
    /* Touch-up 2026-05-28 (Mr.F #54 advisory): expand hit area to 44×44
       AAA per WCAG 2.5.8 while keeping visual at 36×36. Apple-recommended
       transparent-pseudo pattern — 4px ring around the visible button
       (36+8=44). Pointer events on the pseudo bubble to the parent's
       onclick. Visual unchanged. */
    .prod-card .prod-heart::before {
      content: ''; position: absolute; inset: -4px;
      border-radius: inherit;
    }
    .prod-card .prod-heart:hover { transform: scale(1.08); }
    .prod-card .prod-heart.is-on { color: var(--accent); background: #fff; }  /* #3 (pk): yellow like PDP wishlist */
    .prod-card .prod-heart.is-on svg { fill: currentColor; stroke: currentColor; }
    .prod-card .prod-heart:not(.is-on) svg { fill: none; stroke: currentColor; stroke-width: 2; }
    /* Quick bounce on toggle (added/removed via JS via .pulse class). */
    @keyframes prodHeartPulse {
      0%   { transform: scale(1); }
      40%  { transform: scale(1.25); }
      100% { transform: scale(1); }
    }
    .prod-card .prod-heart.pulse { animation: prodHeartPulse 280ms ease; }

    /* ── Banner rows ─────────────────────────────────────────────────── */
    .banner-row {
      display: grid; gap: 16px;
      grid-template-columns: 1fr;
    }
    @media (min-width: 768px) { .banner-row { grid-template-columns: repeat(3, 1fr); gap: 24px; } }
    .banner-card {
      position: relative; aspect-ratio: 3 / 2;
      overflow: hidden; border-radius: var(--card-radius);
      background: var(--bg-soft) center/cover;
      color: #fff;
      display: flex; align-items: flex-end;
      padding: 24px;
    }
    .banner-card::after {
      content: ''; position: absolute; inset: 0;
      background: linear-gradient(180deg, transparent 45%, rgba(0,0,0,0.55));
    }
    .banner-card .text { position: relative; z-index: 1; }
    .banner-card .text h3 {
      font-family: var(--display-font);
      font-size: 22px; font-weight: 600; margin: 0 0 4px 0;
      letter-spacing: -0.01em;
    }
    .banner-card .text p { font-size: 14px; margin: 0 0 12px 0; opacity: 0.92; }
    .banner-card .cta {
      display: inline-block; font-size: 13px; font-weight: 600;
      padding: 8px 16px; background: #fff; color: var(--accent);
      border-radius: 999px;
    }

    /* Luxury hero-carousel overlay banner (slot 1 doubles as hero copy when no hero image set) */

    /* ── Footer ──────────────────────────────────────────────────────── */
    /* pk 2026-05-31 (item 7) — shorter footer (was 64/40 padding) + brand-left,
       links-right layout. */
    .footer {
      padding: 44px 0 28px 0; background: var(--bg-soft);
      border-top: 1px solid var(--border);
      color: var(--text-mute); font-size: 14px;
    }
    /* pk 2026-06-01: hide the footer on mobile — THIS is the "bottom bar" pk
       wanted removed (Habitat brand + ช้อป/ติดต่อ/นโยบาย columns). The bottom
       tab nav + AI chat are kept (restored). Footer still shows on desktop. */
    @media (max-width: 767px) {
      .footer { display: none; }
    }
    /* Mobile: brand stacked above the link columns; the 3 link columns sit in a
       wrapping row so they read as one compact block instead of 4 tall stacks. */
    .footer-grid { display: flex; flex-direction: column; gap: 28px; }
    .footer-links { display: flex; flex-wrap: wrap; gap: 24px 40px; }
    .footer-links > div { min-width: 120px; }
    @media (min-width: 768px) {
      /* Desktop: brand left, link columns pushed to the right edge. */
      .footer-grid { flex-direction: row; justify-content: space-between; align-items: flex-start; gap: 48px; }
      .footer-brand { max-width: 340px; }
      .footer-links { flex-wrap: nowrap; gap: 64px; }
    }
    .footer h2, .footer h4 {
      font-size: 13px; font-weight: 700; letter-spacing: 0.06em;
      text-transform: uppercase; color: var(--text); margin: 0 0 16px 0;
    }
    .footer a {
      color: var(--text-mute);
      /* B3 — vertical padding so stacked footer links are comfortably
         tappable on a phone (was an 18px-tall hit row). */
      display: inline-block; padding: 5px 0;
    }
    .footer a:hover { color: var(--accent); }
    .footer-social { display: flex; gap: 14px; margin-top: 12px; }
    .footer-social a {
      width: 40px; height: 40px; border-radius: 999px;
      background: #fff; border: 1px solid var(--border);
      display: inline-flex; align-items: center; justify-content: center;
      font-size: 14px; padding: 0;   /* B3 — 40px tap; cancel .footer a pad */
    }
    .footer-bottom {
      margin-top: 28px; padding-top: 20px;
      border-top: 1px solid var(--border);
      font-size: 12px;
    }

    /* ── Product detail page ─────────────────────────────────────────── */
    #page-product { padding: 48px 0; }
    .pd-grid {
      display: grid; gap: 32px; grid-template-columns: 1fr;
    }
    @media (min-width: 900px) { .pd-grid { grid-template-columns: 1.2fr 1fr; gap: 56px; } }
    .pd-gallery {
      display: grid; gap: 12px;
      grid-template-columns: 1fr; align-content: start;
    }
    .pd-gallery img {
      aspect-ratio: var(--card-aspect);
      object-fit: cover; border-radius: var(--card-radius);
      background: var(--bg-soft);
    }
    .pd-info h1 {
      font-family: var(--display-font);
      font-size: clamp(28px, 4vw, 40px);
      font-weight: 700; margin: 0 0 12px 0; letter-spacing: -0.01em;
    }
    body[data-template="luxury"] .pd-info h1 { font-weight: 500; }
    .pd-info .price {
      font-size: 22px; font-weight: 700; color: var(--accent);
      margin: 0 0 24px 0;
      /* Wave H-6b (F-SHOP-006) — tabular-nums per DESIGN.md §3.6.3. */
      font-variant-numeric: tabular-nums;
    }
    .pd-info .desc {
      color: var(--text-mute); margin-bottom: 28px;
      white-space: pre-wrap;
    }
    .pd-actions { display: flex; flex-direction: column; gap: 12px; max-width: 360px; }
    .pd-back {
      display: inline-flex; align-items: center; gap: 6px;
      color: var(--text-mute); font-size: 13px; font-weight: 500;
      margin-bottom: 16px;
      padding: 8px 0;   /* B3 — taller tap row for the back link */
    }
    .pd-back:hover { color: var(--accent); }
    .pd-back::before { content: "←"; }
    /* T4.2 — PDP breadcrumb (Shop › Category › Product) */
    .pd-breadcrumb {
      display: flex; align-items: center; flex-wrap: wrap;
      gap: 4px 7px; font-size: 13px; margin-bottom: 24px;
    }
    .pd-breadcrumb a {
      color: var(--text-mute); text-decoration: none;
      display: inline-block; padding: 5px 0;   /* B3 — taller tap row */
    }
    .pd-breadcrumb a:hover { color: var(--accent); }
    .pd-crumb-sep { color: var(--text-mute); user-select: none; }
    .pd-crumb-current {
      color: var(--text); font-weight: 600;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      max-width: 60vw;
    }

    /* ── PDP Phase 1: gallery (main + thumb strip) ───────────────────── */
    .pd-gallery-main {
      position: relative;   /* pk 2026-06-01 — anchor for the <> gallery arrows */
      aspect-ratio: var(--card-aspect);
      background: var(--bg-soft);
      border-radius: var(--card-radius);
      overflow: hidden; margin-bottom: 12px;
    }
    /* pk 2026-06-01 — <> prev/next arrows on the PDP gallery (desktop main image
       + mobile carousel). Shown on BOTH views (unlike the desktop-only card
       arrows); wrap-around handled in JS. Mirrors prod-img-nav (chevron-right,
       .prev flipped). The other view's host is display:none, hiding its set. */
    .pd-gallery-mobile { position: relative; }
    .pd-gal-nav {
      position: absolute; top: 50%; transform: translateY(-50%);
      width: 40px; height: 40px; border-radius: 999px; z-index: 6;
      display: flex; align-items: center; justify-content: center;
      background: rgba(255, 255, 255, 0.92);
      border: 1px solid var(--line, rgba(0, 0, 0, 0.08));
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
      color: var(--ink, #1a1a1a); cursor: pointer;
      transition: background 120ms ease;
    }
    .pd-gal-nav.prev { left: 10px; }
    .pd-gal-nav.next { right: 10px; }
    .pd-gal-nav .bk-icon { width: 20px; height: 20px; }
    .pd-gal-nav.prev .bk-icon { transform: scaleX(-1); }  /* chevron-right → left */
    .pd-gal-nav:hover { background: #fff; }
    .pd-gal-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    @media (prefers-reduced-motion: reduce) { .pd-gal-nav { transition: none; } }
    /* pk 2026-06-01 — DESKTOP-ONLY arrows (DESIGN.md §1 restraint): on touch the
       mobile gallery already navigates via swipe + dots, and a 2nd "‹" next to the
       floating back chevron reads as clutter. Hide the <> arrows on touch — mirrors
       the desktop-only card-cover prod-img-nav pattern. */
    @media (hover: none) { .pd-gal-nav { display: none; } }
    /* pk 2026-05-31 — PDP product images are ALWAYS square + rounded (luxury;
       was 4/5 portrait at 2px radius via the card tokens). */
    body[data-template="luxury"] .pd-gallery-main,
    body[data-template="luxury"] .pd-gallery img { aspect-ratio: 1 / 1; border-radius: 16px; }
    .pd-gallery-main img {
      width: 100%; height: 100%; object-fit: cover; display: block;
    }
    .pd-thumbs {
      display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
    }
    .pd-thumb {
      aspect-ratio: 1/1; background: var(--bg-soft);
      border-radius: 6px; overflow: hidden; cursor: pointer;
      border: 2px solid transparent; padding: 0;
      transition: border-color 120ms ease, opacity 120ms;
      opacity: 0.75;
    }
    .pd-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .pd-thumb.active { border-color: var(--accent); opacity: 1; }
    .pd-thumb:hover { opacity: 1; }

    /* ── Phase 4.5-F F-6 — Pull-to-refresh indicator ─────────────────
       Fixed-position bar at the top of the viewport. CSS default state
       is fully off-screen (translateY(-100%)) + opacity 0. JS touch
       handlers set inline transform + opacity to follow the finger;
       .ready and .loading classes drive the label state machine. */
    .ptr-indicator {
      position: fixed; top: 0; left: 0; right: 0;
      height: 50px;
      display: flex; align-items: center; justify-content: center;
      gap: 10px;
      pointer-events: none;   /* don't intercept taps on content under it */
      background: var(--bg);
      border-bottom: 1px solid var(--border);
      color: var(--text-mute); font-size: 13px; font-weight: 500;
      z-index: 50;
      transform: translateY(-100%);
      opacity: 0;
      transition: transform var(--bk-dur-base) var(--bk-ease-out), opacity 180ms ease;
    }
    /* .loading holds the indicator visible (translateY(0)) until JS
       clears it after _ptrRefresh() resolves. */
    .ptr-indicator.loading {
      transform: translateY(0); opacity: 1;
    }
    /* Label state machine — only the relevant text shows per state. */
    .ptr-text-release, .ptr-text-loading { display: none; }
    .ptr-indicator.ready .ptr-text-pull { display: none; }
    .ptr-indicator.ready .ptr-text-release { display: inline; }
    .ptr-indicator.loading .ptr-text-pull,
    .ptr-indicator.loading .ptr-text-release { display: none; }
    .ptr-indicator.loading .ptr-text-loading { display: inline; }
    .ptr-spinner {
      width: 18px; height: 18px; border-radius: 50%;
      border: 2px solid var(--border); border-top-color: var(--accent);
      transition: transform 160ms ease;
    }
    .ptr-indicator.ready .ptr-spinner {
      transform: rotate(180deg);   /* visual hint: threshold reached */
    }
    .ptr-indicator.loading .ptr-spinner {
      animation: ptr-spin 0.7s linear infinite;
    }
    @keyframes ptr-spin { to { transform: rotate(360deg); } }
    @media (prefers-reduced-motion: reduce) {
      .ptr-indicator.loading .ptr-spinner { animation: none; }
    }

    /* ── Phase 4.5-F F-2 — PDP swipeable image gallery (mobile) ─────────
       Mr.E spec §2.2. Mobile-only horizontal scroll-snap carousel of
       full-width images + dot pagination + tap-to-zoom modal. Desktop
       keeps the existing #pd-gallery-main + 4-thumb-strip layout
       (no regression). Single-image case hides all carousel chrome
       (dots, zoom hint) so it degrades to a static hero on every
       viewport.

       Native scroll-snap (no JS carousel lib) per Mr.E §2.2; pinch-zoom
       works in the modal via touch-action: manipulation on the modal
       image (browser handles it). Dot pagination driven by
       IntersectionObserver on each .pd-gal-slide. */
    .pd-gallery-mobile {
      display: none;   /* desktop ≥768px hides; mobile @media re-enables */
      position: relative;
      margin-bottom: 12px;
    }
    .pd-gal-strip {
      display: flex;
      overflow-x: auto;
      overflow-y: hidden;
      scroll-snap-type: x mandatory;
      -webkit-overflow-scrolling: touch;
      scroll-behavior: smooth;
      gap: 8px;
      /* Hide scrollbar — the dot pagination is the affordance. */
      scrollbar-width: none;
    }
    .pd-gal-strip::-webkit-scrollbar { display: none; }
    .pd-gal-slide {
      flex: 0 0 100%;
      scroll-snap-align: center;
      scroll-snap-stop: always;
      aspect-ratio: var(--card-aspect);
      background: var(--bg-soft);
      border-radius: var(--card-radius);
      overflow: hidden;
      position: relative;
      cursor: zoom-in;
    }
    .pd-gal-slide img {
      width: 100%; height: 100%; object-fit: cover; display: block;
      /* Stop the browser from intercepting horizontal swipes for
         pinch-zoom inside the strip; pan-x is needed for scroll-snap. */
      touch-action: pan-x;
      /* Pointer-events kept on so the slide tap-zoom handler fires. */
    }
    .pd-gal-dots {
      display: flex; justify-content: center; gap: 8px;
      margin-top: 10px; padding: 4px 0;
    }
    .pd-gal-dot {
      width: 8px; height: 8px; border-radius: 50%;
      background: var(--text-mute);
      opacity: 0.35;
      transition: opacity 160ms ease, transform 160ms ease,
                  background-color 160ms ease;
      /* B3 — taller invisible tap row around each dot (~32px) so users
         can tap to jump to image-N as well as swipe. */
      cursor: pointer;
      border: none;
      padding: 0;
      position: relative;
    }
    .pd-gal-dot::after {
      content: ""; position: absolute;
      inset: -12px;   /* 8px dot + 12px padding = 32px tap area */
    }
    .pd-gal-dot.active {
      background: var(--accent);
      opacity: 1;
      transform: scale(1.15);
    }
    /* Zoom hint — visible only on multi-image carousels at mobile. Fades
       after the first user interaction (.pd-gal-zoom-hint.dismissed). */
    .pd-gal-zoom-hint {
      position: absolute; top: 12px; right: 12px;
      background: rgba(0, 0, 0, 0.55);
      color: #fff; font-size: 11px; letter-spacing: 0.04em;
      padding: 4px 8px; border-radius: 12px;
      pointer-events: none;
      transition: opacity var(--bk-dur-base) var(--bk-ease-out);
    }
    .pd-gal-zoom-hint.dismissed { opacity: 0; }
    /* Mobile: enable carousel, hide the existing main+thumb layout. */
    @media (max-width: 767px) {
      .pd-gallery-mobile { display: block; }
      .pd-gallery-main, .pd-thumbs { display: none; }
    }

    /* Fullscreen zoom modal (tap any slide to open). Pinch-zoom on the
       full-size image is native (touch-action: manipulation). Closes on
       tap outside the image, on the close button, or ESC. */
    .pd-gal-zoom-modal {
      position: fixed; inset: 0; z-index: 100;
      background: rgba(0, 0, 0, 0.92);
      display: none;
      align-items: center; justify-content: center;
      padding: 24px;
    }
    .pd-gal-zoom-modal.active { display: flex; }
    .pd-gal-zoom-modal img {
      max-width: 100%; max-height: 100%;
      object-fit: contain;
      touch-action: manipulation;   /* let browser handle pinch-zoom */
      user-select: none;
      -webkit-user-drag: none;
    }
    .pd-gal-zoom-close {
      position: absolute; top: 16px; right: 16px;
      width: 44px; height: 44px;
      background: rgba(255, 255, 255, 0.12);
      color: #fff;
      border: none; border-radius: 50%;
      font-size: 22px; line-height: 1; cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .pd-gal-zoom-counter {
      position: absolute; top: 24px; left: 50%;
      transform: translateX(-50%);
      color: rgba(255, 255, 255, 0.85);
      font-size: 13px; letter-spacing: 0.04em;
      font-variant-numeric: tabular-nums;
    }

    /* ── PDP Phase 1: header (brand label, heart/share, title, price) ── */
    .pd-header-row {
      display: flex; align-items: flex-start; justify-content: space-between;
      gap: 12px; margin-bottom: 4px;
    }
    .pd-brand-label {
      font-size: 11px; font-weight: 600; letter-spacing: 0.12em;
      text-transform: uppercase; color: var(--text-mute);
    }
    .pd-icon-row { display: flex; gap: 12px; }
    .pd-icon-btn {
      background: none; border: none; padding: 4px;
      color: var(--text-mute); cursor: pointer; font-size: 18px;
      line-height: 1; transition: color 160ms ease;
      /* v3-2 §8.2 — ≥44px AAA tap area on mobile (was 40px; orig ~22px box). */
      min-width: 44px; min-height: 44px;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .pd-icon-btn:hover { color: var(--accent); }

    /* ── PDP Phase 1: price stack ────────────────────────────────────── */
    .pd-price-stack {
      display: flex; align-items: baseline; gap: 10px;
      margin: 0 0 16px 0; flex-wrap: wrap;
    }
    /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
    .pd-price-current { font-size: 24px; font-weight: 700; color: var(--bk-ink, var(--ink)); }
    .pd-price-compare {
      font-size: 15px; color: var(--text-mute); text-decoration: line-through;
    }

    /* ── WP-3: PDP option-type selectors (Size / Colour pills) ───────── */
    .pd-options { margin: 0 0 16px 0; display: flex; flex-direction: column; gap: 12px; }
    .pd-opt-group { display: flex; flex-direction: column; gap: 7px; }
    .pd-opt-label {
      font-size: 11px; font-weight: 600; letter-spacing: 0.10em;
      text-transform: uppercase; color: var(--text-mute);
    }
    .pd-opt-pills { display: flex; flex-wrap: wrap; gap: 8px; }
    .pd-opt-pill {
      border: 1.5px solid var(--border); background: #fff; color: var(--ink, #1E293B);
      border-radius: 999px; padding: 10px 16px; font: inherit; font-size: 13px;
      font-weight: 600; cursor: pointer; transition: border-color 120ms ease,
      background 120ms ease, color 120ms ease;
      /* v3-2 §8.2 — ≥44px AAA tap height for the variant pills (was 40px). */
      min-height: 44px;
    }
    .pd-opt-pill:hover:not(.is-selected) { border-color: var(--accent); }
    .pd-opt-pill.is-selected {
      background: var(--accent); border-color: var(--accent); color: #fff;
    }
    .pd-opt-pill:active { transform: translateY(1px); }
    .pd-opt-pill.is-soldout {
      color: var(--text-mute); text-decoration: line-through; opacity: 0.6;
    }
    .pd-opt-pill.is-soldout.is-selected {
      background: var(--text-mute); border-color: var(--text-mute);
      color: #fff; text-decoration: line-through;
    }

    /* ── PDP Phase 1: qty stepper + stock note ───────────────────────── */
    .pd-qty-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
    .pd-qty-stepper {
      display: inline-flex; align-items: center;
      border: 1px solid var(--border); border-radius: 999px; overflow: hidden;
    }
    .pd-qty-btn {
      background: none; border: none; padding: 8px 16px;
      cursor: pointer; font-size: 16px; line-height: 1; color: var(--accent);
      transition: background 120ms ease;
      /* B3 — ≥44px tap height for the PDP qty stepper. */
      min-height: 44px; min-width: 44px;
    }
    .pd-qty-btn:hover:not(:disabled) { background: var(--bg-soft); }
    .pd-qty-btn:disabled { opacity: 0.35; cursor: not-allowed; }
    .pd-qty-value {
      min-width: 36px; text-align: center; font-size: 14px; font-weight: 600;
    }
    .pd-stock-note { font-size: 12px; color: var(--text-mute); }

    /* ── PDP Phase 1: dual CTAs (grid, not stacked) ──────────────────── */
    /* Phase E — "Buy now" express CTA, full-width above the CTA grid. */
    .pd-buy-now { margin: 8px 0 10px 0; }
    .pd-buy-now:disabled { opacity: 0.5; cursor: not-allowed; }

    /* ── Phase 4.5-F F-3: PDP sticky variant + price bar ────────────
       Bottom-fixed bar that appears via IntersectionObserver on
       #pd-price (the hero price). When the hero price scrolls out of
       viewport on PDP, body.pd-sticky-active is added by JS and the
       bar fades in. Mirrors the price stack + variant label + hosts
       a one-tap Add-to-bag CTA so the conversion path is never more
       than one viewport away. Hidden + opacity:0 by default; the
       active-class transition makes the appearance feel calm.
       Mobile DoD: total visible height ≤96px (incl. safe-area). */
    .pd-sticky-bar {
      position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
      display: none;
      align-items: center; gap: 12px;
      padding: 10px 14px;
      padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
      background: #fff; color: var(--text);
      border-top: 1px solid var(--border);
      box-shadow: 0 -6px 18px rgba(var(--bk-shadow-color) / 8%);
      font-family: inherit;
      opacity: 0;
      transform: translateY(8px);
      transition: opacity 180ms ease, transform 180ms ease;
    }
    body.pd-sticky-active .pd-sticky-bar {
      display: flex;
      opacity: 1;
      transform: translateY(0);
    }
    .pd-sticky-img {
      width: 44px; height: 44px; object-fit: cover; border-radius: 8px;
      flex-shrink: 0; background: #F4F4F5;
    }
    .pd-sticky-meta {
      flex: 1; min-width: 0;
      display: flex; flex-direction: column; gap: 2px;
    }
    .pd-sticky-name {
      font-size: 13px; font-weight: 600; line-height: 1.2;
      color: var(--text);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .pd-sticky-sub {
      display: flex; align-items: baseline; gap: 8px;
      font-size: 12px; line-height: 1.2;
      min-width: 0;
    }
    .pd-sticky-variant {
      color: var(--text-mute);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      min-width: 0; flex: 1;
    }
    .pd-sticky-price {
      /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
      font-weight: 700; color: var(--bk-ink, var(--ink));
      white-space: nowrap;
      margin-left: auto;
    }
    .pd-sticky-cta {
      flex-shrink: 0;
      padding: 10px 16px;
      font-size: 13px;
      border-radius: 999px;
      white-space: nowrap;
      margin: 0;
    }
    @media (max-width: 767px) {
      .pd-sticky-bar { padding: 8px 12px; gap: 10px;
                       padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px)); }
      .pd-sticky-img { width: 40px; height: 40px; }
      .pd-sticky-name { font-size: 12px; }
      .pd-sticky-sub { font-size: 11px; gap: 6px; }
      .pd-sticky-cta { padding: 8px 14px; font-size: 12px; }
    }
    /* Drop the variant text on very-narrow widths so price + CTA stay
       legible. The hero still shows it; sticky is a recap, not the
       source-of-truth selector (caveat in [[feedback-honest-scoping-caveat-as-comment]]). */
    @media (max-width: 359px) {
      .pd-sticky-variant { display: none; }
    }

    /* ── Wave H7 (F-CUS-H7): mobile bottom tab bar ──────────────────
       Fixed 5-tab bar, mobile-only (hidden ≥768px per DESIGN.md §8 — the
       storefront's canonical max-width:767px breakpoint). Surface bg with a
       clean top hairline + slight float elevation (§2.2 surfaces / §6.1) —
       no heavy shadow (luxury restraint, §7). Sits on the --z-sticky rung
       (§6.4): above page content, below drawers / modals / toasts. Active
       tab = brand accent (--accent → --brand-primary, §2 tokens); inactive =
       --text-mute. Label ~11px, icon 24px, 44px+ touch target per §3/§4/§8.2.
       Hidden on PDP / checkout (body.on-product / body.on-checkout) — those
       routes own the bottom edge with their own sticky CTAs. */
    .bk-mobnav {
      display: none;   /* base: hidden; the max-width:767px block reveals it */
    }
    @media (max-width: 767px) {
      /* Mobile bottom tab bar (Home/Products/Orders/Notifications/Profile).
         pk 2026-06-01 FINAL: KEEP on mobile. The "bottom bar" pk wanted gone
         was the FOOTER (hidden below), NOT this nav nor the AI chat — pk:
         "ส่วนที่คุณลบออกคือ bottom nav + ai chat เอากลับมา". Restored to display:flex. */
      .bk-mobnav {
        display: flex;
        position: fixed;
        left: 0; right: 0; bottom: 0;
        z-index: var(--z-sticky);
        align-items: stretch;
        background: var(--bg);
        border-top: 1px solid var(--border);
        box-shadow: 0 -1px 0 0 color-mix(in oklch, var(--brand-primary) 4%, transparent),
                    0 -6px 18px 0 color-mix(in oklch, var(--brand-primary) 5%, transparent);
        padding-bottom: env(safe-area-inset-bottom, 0px);
        -webkit-backdrop-filter: saturate(180%) blur(6px);
        backdrop-filter: saturate(180%) blur(6px);
      }
      .bk-mobnav-item {
        flex: 1 1 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        min-height: 56px;          /* ≥44px touch target incl. label (§8.2) */
        padding: 7px 2px 6px;
        background: transparent;
        border: 0;
        cursor: pointer;
        color: var(--text-mute);
        font-family: inherit;
        -webkit-tap-highlight-color: transparent;
        transition: color 160ms ease;
      }
      .bk-mobnav-item:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: -3px;
        border-radius: 8px;
      }
      /* Descendant selector (0,2,0) so sprite tabs (.bk-mobnav-icon.bk-icon)
         beat the later .bk-icon {width:1em} rule — without it sprite icons
         render ~15px while inline-SVG tabs render 24px (size mismatch). */
      .bk-mobnav .bk-mobnav-icon {
        width: 24px; height: 24px;
        flex-shrink: 0;
        color: inherit;
      }
      .bk-mobnav-label {
        font-size: 11px;
        line-height: 1.1;
        letter-spacing: 0.01em;
        font-weight: 500;
        color: inherit;
      }
      .bk-mobnav-item.is-active {
        color: var(--accent);
      }
      .bk-mobnav-item.is-active .bk-mobnav-label {
        font-weight: 600;
      }
    }
    /* Reveal-safety belt-and-braces: never on desktop (§8 mobile-only). */
    @media (min-width: 768px) {
      .bk-mobnav { display: none !important; }
    }
    /* PDP + checkout + cart own the bottom edge (pd-sticky-cta /
       co-sticky-cta / cart-sticky-cta). Cart added so the mobnav (z 1100)
       can't cover the cart→checkout button (.cart-sticky-cta, z 50). */
    body.on-product .bk-mobnav,
    body.on-checkout .bk-mobnav,
    body.on-cart .bk-mobnav { display: none !important; }
    /* pk 2026-06-01 FINAL: bottom tab bar KEPT on mobile → reserve space so page
       content clears the fixed bar. PDP / checkout / cart still own their own
       sticky-CTA bottom padding via body.on-product / on-checkout / on-cart. */
    @media (max-width: 767px) {
      body { padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }
    }

    /* ── Phase 4.5-F F-4: Add-to-cart feedback drawer ───────────────
       Bottom-sheet drawer shown on every successful cartAdd /
       cartAddVoucher. Replaces the previous "jump to /cart" behavior
       so the customer stays on PDP / PLP and can keep browsing. 3s
       auto-dismiss, manual × dismiss, or "View cart" → shop.goto('cart').
       Cart badge update is a side-effect of saveCart() → updateCartBadge()
       (the function the brief calls shop.paintCartBadge() — verified
       in-tree as updateCartBadge per [[feedback-embed-backend-contract-in-frontend-brief]]).
       Drawer hides .pd-sticky-bar while visible to avoid bottom-edge
       UI collision (DoD: z-order). */
    .cf-drawer {
      position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
      pointer-events: none;
      opacity: 0;
      transform: translateY(100%);
      transition: opacity 220ms ease, transform 220ms var(--bk-ease-spring);
    }
    body.cf-drawer-active .cf-drawer {
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }
    .cf-drawer-inner {
      margin: 0 auto;
      max-width: 540px;
      padding: 14px 16px;
      padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
      background: #fff;
      border-top-left-radius: 18px;
      border-top-right-radius: 18px;
      box-shadow: 0 -10px 30px rgba(var(--bk-shadow-color) / 18%);
      border: 1px solid var(--border);
      border-bottom: none;
      display: flex; flex-direction: column; gap: 10px;
      font-family: inherit;
    }
    .cf-head { display: flex; align-items: center; gap: 10px; }
    .cf-tick {
      display: inline-flex; align-items: center; justify-content: center;
      width: 22px; height: 22px; border-radius: 999px;
      background: var(--bk-success, #15803D); color: #fff;
      font-size: 13px; font-weight: 700;
      flex-shrink: 0;
    }
    .cf-headline {
      font-size: 14px; font-weight: 700; color: var(--text);
      flex: 1; line-height: 1.2;
    }
    .cf-close {
      background: transparent; border: none; cursor: pointer;
      font-size: 22px; line-height: 1;
      color: var(--text-mute); padding: 0 4px;
      flex-shrink: 0;
      /* v3-2 §8.2 — ≥44×44 AAA tap target for the drawer close X. */
      min-width: 44px; min-height: 44px;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .cf-close:hover { color: var(--text); }
    .cf-body { display: flex; align-items: center; gap: 12px; }
    .cf-img {
      width: 56px; height: 56px; object-fit: cover; border-radius: 10px;
      flex-shrink: 0; background: #F4F4F5;
    }
    .cf-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .cf-name {
      font-size: 13px; font-weight: 600; color: var(--text);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .cf-sub {
      display: flex; align-items: baseline; gap: 8px;
      font-size: 12px; color: var(--text-mute);
    }
    .cf-variant {
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      min-width: 0; flex: 1;
    }
    .cf-qty {
      white-space: nowrap; font-weight: 600; color: var(--text);
    }
    .cf-price {
      /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
      font-size: 14px; font-weight: 700; color: var(--bk-ink, var(--ink));
      white-space: nowrap;
      flex-shrink: 0;
    }
    .cf-cta-row { display: flex; gap: 8px; }
    .cf-cta {
      flex: 1; padding: 12px 14px; font-size: 13px;
      border-radius: 999px;
      margin: 0; justify-content: center;
    }
    body.cf-drawer-active .pd-sticky-bar {
      display: none !important;
    }
    /* pk 2026-06-01 (SF-11) — On mobile the bottom tab bar (.bk-mobnav, z 1100)
       paints over the drawer (z 90), hiding its bottom strip incl. the only
       "View cart" CTA. PDP/checkout/cart hide the mobnav already; on every OTHER
       route (landing/PLP/account) the mobnav is present, so lift the drawer
       clear of it by offsetting bottom by the 56px bar (mirrors the body
       padding-bottom reserve above). Raising z-index alone would only overlap
       the bar — offsetting bottom is the clean fix. */
    @media (max-width: 767px) {
      .cf-drawer-inner { padding: 12px 14px;
                         padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
      .cf-img { width: 48px; height: 48px; }
      .cf-cta { padding: 11px 14px; }
      body.cf-drawer-active:not(.on-product):not(.on-checkout):not(.on-cart) .cf-drawer {
        bottom: calc(56px + env(safe-area-inset-bottom, 0px));
      }
    }
    /* Wave H8 (F-CUS-122) — PDP CTA stack per DESIGN.md §7 (single primary)
       + Wave-2 §B (Shopify/Stripe convention). Add-to-bag stays as the
       loud filled primary; Buy-now + Buy-via-LINE drop to text-link
       sub-affordances on a centered row. The id="pd-cta-grid" hook is
       preserved so JS toggling (oosNow / contact-only mode) still works. */
    .pd-cta-stack {
      display: flex; flex-direction: column; gap: 12px;
      margin: 0 0 24px 0;
    }
    .btn-pd-add {
      width: 100%; justify-content: center;
      font-size: 17px; padding: 16px 24px; min-height: 56px;
    }
    .pd-cta-secondary-row {
      display: flex; align-items: center; justify-content: center;
      gap: 12px; flex-wrap: wrap;
    }
    .btn-text {
      display: inline-flex; align-items: center; gap: 6px;
      color: var(--accent); background: transparent; border: 0;
      padding: 8px 12px; min-height: 44px; cursor: pointer;
      text-decoration: none; font: inherit; font-weight: 600;
    }
    .btn-text:hover { text-decoration: underline; }
    .btn-text:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
      border-radius: 6px;
    }
    .btn-text:disabled { opacity: 0.5; cursor: not-allowed; text-decoration: none; }
    .pd-cta-separator { color: var(--text-mute); opacity: 0.5; user-select: none; }

    /* ── PDP Phase 1: mini-spec table (5 rows per Q5) ────────────────── */
    .pd-mini-specs {
      border-top: 1px solid var(--border); padding-top: 14px;
    }
    .pd-mini-specs h2, .pd-mini-specs h3 {
      font-size: 11px; font-weight: 600; letter-spacing: 0.12em;
      text-transform: uppercase; color: var(--text-mute);
      margin: 0 0 10px 0;
    }
    .pd-mini-row {
      display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
      padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px;
    }
    .pd-mini-row:last-child { border-bottom: none; }
    .pd-mini-row .key { color: var(--text-mute); }
    .pd-mini-row .val { color: var(--accent); font-weight: 500; }

    /* ── PDP Phase 1: highlight description (pk Q6) ──────────────────── */
    .pd-highlight {
      background: #FBF7E9;
      border-left: 3px solid #C9A14A;
      padding: 14px 18px;
      margin: 20px 0;
      border-radius: 8px;
      font-style: italic;
      font-size: 15px;
      line-height: 1.6;
      color: #4A3D1A;
    }
    .pd-highlight p { margin: 0; white-space: pre-wrap; }

    /* ── PDP Phase 1: section dividers ───────────────────────────────── */
    .pd-section {
      border-top: 1px solid var(--border);
      padding-top: 32px; margin-top: 32px;
    }
    .pd-section h2 {
      font-family: var(--display-font);
      font-size: 22px; font-weight: 600; margin: 0 0 16px 0;
      letter-spacing: -0.01em;
    }
    body[data-template="luxury"] .pd-section h2 { font-weight: 500; }
    .pd-section .description {
      color: var(--text); line-height: 1.7; white-space: pre-wrap;
    }

    /* ── PDP Phase 1: condition strip (5 steps, gold-active) ─────────── */
    .pd-cond-row {
      display: flex; align-items: baseline; justify-content: space-between;
      margin-bottom: 4px;
    }
    .pd-cond-grade { font-size: 12px; color: var(--text-mute); font-weight: 600; }
    .pd-cond-strip {
      display: flex; align-items: flex-start;
      position: relative; margin: 18px 0 10px 0;
    }
    .pd-cond-step { position: relative; flex: 1; text-align: center; }
    .pd-cond-dot {
      width: 12px; height: 12px; border-radius: 50%;
      background: #E5E7EB; margin: 0 auto;
      transition: background 160ms, box-shadow 160ms;
    }
    .pd-cond-step.past .pd-cond-dot { background: #94A3B8; }
    .pd-cond-step.active .pd-cond-dot {
      background: #C9A14A; box-shadow: 0 0 0 4px #E8D7A6;
    }
    .pd-cond-line {
      position: absolute; top: 5px; left: 50%; right: -50%;
      height: 2px; background: #E5E7EB; z-index: 0;
    }
    .pd-cond-step:last-child .pd-cond-line { display: none; }
    .pd-cond-label {
      font-size: 11px; color: var(--text-mute);
      margin-top: 10px; text-align: center;
    }
    .pd-cond-step.active .pd-cond-label { color: var(--accent); font-weight: 600; }
    .pd-cond-includes {
      font-size: 13px; color: var(--text-mute); margin-top: 6px;
    }

    /* ── PDP Phase 1: verbose spec sheet (2-col grid) ────────────────── */
    .pd-spec-grid {
      display: grid; grid-template-columns: 1fr; gap: 0 48px;
    }
    @media (min-width: 720px) { .pd-spec-grid { grid-template-columns: 1fr 1fr; } }
    .pd-spec-row {
      display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
      padding: 10px 0; border-bottom: 1px solid var(--border);
      font-size: 13px;
    }
    .pd-spec-row .key { color: var(--text-mute); }
    .pd-spec-row .val { color: var(--accent); }

    /* ── PDP Phase 1: related products carousel (4 cards) ────────────── */
    .pd-related-grid {
      display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
    }
    @media (min-width: 720px) { .pd-related-grid { grid-template-columns: repeat(4, 1fr); } }
    .pd-related-card { display: block; transition: opacity 120ms; }
    .pd-related-card:hover { opacity: 0.85; }
    .pd-related-img {
      aspect-ratio: 1/1; background: var(--bg-soft);
      border-radius: var(--card-radius); overflow: hidden; margin-bottom: 8px;
    }
    .pd-related-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .pd-related-name {
      font-size: 13px; font-weight: 500; line-height: 1.3;
      color: var(--accent); margin-bottom: 4px;
    }
    /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
    .pd-related-price { font-size: 13px; font-weight: 600; color: var(--bk-ink, var(--ink)); }
    .pd-related-grade {
      font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
      color: var(--text-mute); margin-top: 4px;
    }

    /* ── PDP Phase 1: toast notification (heart/share placeholders) ──── */
    .pd-toast {
      position: fixed; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); left: 50%; transform: translateX(-50%);
      background: var(--accent); color: #FFF; padding: 10px 18px;
      border-radius: 999px; font-size: 13px; font-weight: 500;
      box-shadow: 0 6px 20px rgba(0,0,0,0.18);
      opacity: 0; pointer-events: none; transition: opacity var(--bk-dur-base) var(--bk-ease-out);
      z-index: 9999;
    }
    .pd-toast.show { opacity: 1; }

    /* ── Placeholder pages (cart/checkout/confirmation) ─────────────── */
    .stub {
      max-width: 480px; margin: 96px auto; text-align: center;
      padding: 0 24px;
    }
    .stub .emoji { font-size: 56px; margin-bottom: 16px; }
    .stub h2 {
      font-family: var(--display-font);
      font-size: 28px; font-weight: 600; margin: 0 0 12px 0;
    }
    .stub p { color: var(--text-mute); margin: 0 0 24px 0; }

    /* ═══ F-13 Illustrated empty states (Wave 3, 2026-05-28) ═══
       Replaces the emoji-only .stub treatment with a brand-color line-art
       illustration for 4 specific surfaces: cart / orders / search /
       wishlist. Inline SVG (no extra asset request) keeps the bundle
       size flat. ~140px illustration + 24px headline + 16px muted body
       + primary CTA back-link. Color: var(--accent) stroke; line-width
       1.5; rounded corners; minimal — matches the rest of the app
       chrome (Cartier §A.0 #6 + Mr.E L-3 patterns). */
    .bk-empty {
      max-width: 480px; margin: 64px auto; text-align: center;
      padding: 24px 24px;
    }
    .bk-empty .bk-empty-art {
      width: 140px; height: 140px; margin: 0 auto 24px auto;
      color: var(--accent, #1A4D40);
      display: block;
    }
    .bk-empty .bk-empty-art svg { width: 100%; height: 100%; display: block; }
    .bk-empty h2 {
      font-family: var(--display-font, system-ui);
      font-size: 22px; font-weight: 700; margin: 0 0 8px 0;
      letter-spacing: -0.01em;
      color: var(--text, #1E293B);
    }
    .bk-empty p {
      font-size: 15px; color: var(--text-mute, #64748B);
      margin: 0 0 24px 0; line-height: 1.5;
    }
    .bk-empty .bk-empty-cta {
      display: inline-flex; align-items: center; gap: 6px;
    }
    /* Compact variant for account-hub tabs (orders / wishlist inside
       smaller card-bounded contexts). */
    .bk-empty.bk-empty-compact { margin: 32px auto; padding: 24px 12px; }
    .bk-empty.bk-empty-compact .bk-empty-art { width: 96px; height: 96px; margin-bottom: 16px; }
    /* Wave H5 (F-CUS-109) — explicit slot classes per DESIGN.md §4.x.
       Mirror the default h2/p treatment so refactored sites that use
       .bk-empty-title/.bk-empty-body on non-h2/non-p elements stay
       visually consistent. */
    .bk-empty .bk-empty-title {
      font-family: var(--display-font, system-ui);
      font-size: 22px; font-weight: 700; margin: 0 0 8px 0;
      letter-spacing: -0.01em; color: var(--text, #1E293B);
    }
    .bk-empty .bk-empty-body {
      font-size: 15px; color: var(--text-mute, #64748B);
      margin: 0 0 24px 0; line-height: 1.5;
    }
    .bk-empty .bk-empty-actions {
      display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
    }
    .bk-empty.bk-empty-compact h2 { font-size: 18px; }
    .bk-empty.bk-empty-compact p { font-size: 14px; margin-bottom: 16px; }
    @media (prefers-reduced-motion: reduce) {
      .bk-empty .bk-empty-art svg * { animation: none !important; }
    }
    /* ═══ end F-13 ═══ */

    /* ── Page switching ─────────────────────────────────────────────── */
    .page { display: none; }
    .page.active { display: block; }

    /* ── Cart / Checkout / Confirmation ─────────────────────────────── */
    .cart-grid {
      display: grid; gap: 32px;
      grid-template-columns: 1fr;
    }
    /* B3 — grid items default to min-width:auto, so a wide/no-wrap child
       (a co-card form) can push the column past the viewport on a phone.
       min-width:0 lets the column shrink to its 1fr track. */
    .cart-grid > * { min-width: 0; }
    @media (min-width: 900px) {
      .cart-grid { grid-template-columns: 1.4fr 1fr; gap: 48px; align-items: start; }
    }
    .cart-lines { display: flex; flex-direction: column; gap: 16px; }
    .cart-line {
      display: grid; grid-template-columns: 88px 1fr auto;
      gap: 16px; align-items: center;
      padding: 16px; background: #fff;
      border: 1px solid var(--border); border-radius: var(--card-radius);
    }
    body[data-template="luxury"] .cart-line { background: var(--bg-soft); }
    .cart-line .img {
      width: 88px; aspect-ratio: 1 / 1; border-radius: 8px;
      background: var(--bg-soft) center/cover;
    }
    .cart-line h4 { margin: 0 0 4px 0; font-size: 15px; font-weight: 600; }
    body[data-template="luxury"] .cart-line h4 { font-family: var(--display-font); font-weight: 500; font-size: 17px; }
    .cart-line .meta { color: var(--text-mute); font-size: 13px; }
    /* WP-3 — option summary ("M / Black") under the cart-line title. */
    .cart-line-variant {
      color: var(--text-mute); font-size: 12px; font-weight: 600;
      margin: 0 0 4px 0;
    }
    .cart-line .right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
    /* Wave H5 (F-SHOP-005) — bk-stepper primitive per DESIGN.md §4.11.2
       slot grammar (.bk-stepper-dec / .bk-stepper-input / .bk-stepper-inc).
       Legacy .qty-stepper / .pd-qty-stepper aliases are KEPT on the same
       elements so prior CSS rules (focus-visible at line ~2489, mobile
       overrides at ~3998) continue to target the same DOM. */
    .bk-stepper {
      display: inline-flex; align-items: center; gap: 0;
      border: 1px solid var(--border); border-radius: 10px;
      overflow: hidden; background: #fff;
    }
    .bk-stepper-dec, .bk-stepper-inc {
      min-width: 44px; min-height: 44px;
      display: inline-flex; align-items: center; justify-content: center;
      background: transparent; border: 0; cursor: pointer;
      color: var(--text);
    }
    .bk-stepper-dec:hover:not(:disabled),
    .bk-stepper-inc:hover:not(:disabled) { background: var(--bg-soft); }
    .bk-stepper-dec:disabled,
    .bk-stepper-inc:disabled { opacity: 0.35; cursor: not-allowed; }
    .bk-stepper-dec:focus-visible,
    .bk-stepper-inc:focus-visible {
      outline: 2px solid var(--accent); outline-offset: -2px;
    }
    .bk-stepper-dec .bk-icon,
    .bk-stepper-inc .bk-icon { font-size: 16px; }
    .bk-stepper-input {
      width: 56px; border: 0; text-align: center;
      font-family: inherit; font-weight: 600; font-size: 14px;
      background: transparent; padding: 0;
      font-variant-numeric: tabular-nums;
    }
    .bk-stepper-input:focus { outline: none; }
    /* Span-variant of .bk-stepper-input (PDP uses a span not input). */
    span.bk-stepper-input { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }

    /* Legacy stepper aliases — Wave H5 kept .qty-stepper rounded pill +
       38px input width preserved by re-aliasing to the new primitive. */
    .qty-stepper { border-radius: 999px; }
    .qty-stepper .bk-stepper-input { width: 36px; }
    .line-total {
      /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
      font-weight: 700; font-size: 15px; color: var(--bk-ink, var(--ink));
      /* Wave H-6b (F-SHOP-006) — tabular-nums + right-align for column-true
         price stack on cart-line items per DESIGN.md §3.6.3. */
      font-variant-numeric: tabular-nums;
      text-align: right;
    }
    .line-remove {
      font-size: 12px; color: var(--text-mute); cursor: pointer;
      background: none; border: none;
      /* Wave H-2 (F-CUS-112) — enforce 44×44 tap-target minimum per
         WCAG 2.5.5 + DESIGN.md §8.2. Prior padding 9px 6px (~30×24)
         was below threshold. Icon-sprite swap (bk-icon-trash) is H-4
         scope; this commit only fixes hit-box dimensions. */
      min-width: 44px; min-height: 44px;
      padding: 12px 14px;
    }
    .line-remove:hover { color: #DC2626; }

    .cart-summary {
      background: var(--bg-soft); border: 1px solid var(--border);
      border-radius: var(--card-radius); padding: 24px;
      position: sticky; top: 96px;
    }
    body[data-template="luxury"] .cart-summary { background: #fff; }
    .cart-summary h2, .cart-summary h3 {
      font-size: 14px; font-weight: 700; letter-spacing: 0.06em;
      text-transform: uppercase; margin: 0 0 16px 0;
    }
    .cart-row {
      display: flex; justify-content: space-between; align-items: baseline;
      padding: 8px 0; font-size: 14px;
      /* Wave H-6b (F-SHOP-006) — tabular-nums on every cart/checkout summary
         row so the price column reads as a true digit stack per §3.6.3. */
      font-variant-numeric: tabular-nums;
    }
    .cart-row.total {
      padding-top: 16px; border-top: 1px solid var(--border);
      margin-top: 8px; font-size: 16px;
    }
    .cart-row.total strong {
      /* v3-2 §2.7 — total uses ink, not accent, for ≥4.5:1 contrast. */
      font-size: 20px; color: var(--bk-ink, var(--ink)); font-weight: 700;
      font-variant-numeric: tabular-nums;
    }
    .cart-help { font-size: 12px; color: var(--text-mute); margin-top: 12px; line-height: 1.5; }

    /* Wave H7 (F-CUS-114) — T1.1 .co-steps* rules removed alongside the
       markup; the F-10 .co-sub-progress below is the surviving in-page
       progress UI. */

    .co-card {
      background: #fff; border: 1px solid var(--border);
      border-radius: var(--card-radius); padding: 24px;
      margin-bottom: 16px;
    }
    body[data-template="luxury"] .co-card { background: var(--bg-soft); }
    /* Ref-display addendum item 2 — contact-page map embed. */
    .contact-map { border-radius: 10px; overflow: hidden; line-height: 0; }
    .contact-map iframe { width: 100%; height: 220px; border: 0; display: block; }
    .co-card h2, .co-card h3 {
      font-size: 14px; font-weight: 700; letter-spacing: 0.06em;
      text-transform: uppercase; margin: 0 0 16px 0;
    }
    .co-help { font-size: 13px; color: var(--text-mute); margin: 0 0 16px 0; }

    /* ═══ Saved-address picker (logged-in checkout) ═══════════════════════
       A returning customer picks a saved shipping address from radio cards
       (selected one gets a brand-tinted ring per DESIGN §2.5) or taps the
       outlined "+ Add address" button to type a new one. Guests never see
       this — the manual #co-manual-form shows instead. The picked address
       fills the manual fields so the placeOrder #co-address contract is
       unchanged. DESIGN §4.2 selectable cards · §2.5 brand bleed via
       color-mix · §4.5 outlined button (hover/focus-visible/active). */
    .co-saved-addr { margin-bottom: 16px; }
    .co-saved-addr-title {
      font-size: 14px; font-weight: 700; letter-spacing: 0.06em;
      text-transform: uppercase; color: var(--text); margin: 0 0 12px 0;
    }
    .co-addr-list { display: flex; flex-direction: column; gap: 10px; }
    .co-addr-card {
      display: flex; align-items: flex-start; gap: 12px; cursor: pointer;
      background: #fff; border: 1.5px solid var(--border);
      border-radius: var(--card-radius); padding: 14px 16px;
      transition: border-color 140ms cubic-bezier(0.2,0,0,1),
                  box-shadow 140ms cubic-bezier(0.2,0,0,1),
                  background-color 140ms cubic-bezier(0.2,0,0,1);
    }
    body[data-template="luxury"] .co-addr-card { background: var(--bg-soft); }
    .co-addr-card:hover {
      border-color: color-mix(in oklch, var(--accent) 40%, var(--border));
    }
    .co-addr-card.is-selected {
      border-color: var(--accent);
      background: color-mix(in oklch, var(--accent) 6%, #fff);
      box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 22%, transparent);
    }
    /* native radio kept, accent-tinted; the whole card is the click target */
    .co-addr-radio {
      flex: 0 0 auto; width: 18px; height: 18px; margin-top: 2px;
      accent-color: var(--accent); cursor: pointer;
    }
    .co-addr-card:focus-within {
      outline: 3px solid color-mix(in oklch, var(--accent) 45%, transparent);
      outline-offset: 2px;
    }
    .co-addr-card-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
    .co-addr-card-top {
      display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    }
    .co-addr-card-name { font-size: 14px; font-weight: 700; color: var(--text); }
    .co-addr-tag {
      display: inline-block; font-size: 11px; font-weight: 700;
      letter-spacing: 0.02em; padding: 2px 9px; border-radius: 999px;
      background: color-mix(in oklch, var(--accent) 14%, #fff);
      color: var(--accent);
    }
    .co-addr-card-line {
      font-size: 13px; line-height: 1.55; color: var(--text-mute);
      word-break: break-word;
    }
    .co-addr-add {
      display: inline-flex; align-items: center; gap: 8px; margin-top: 12px;
      min-height: 44px; padding: 10px 18px;
      background: transparent; border: 1.5px dashed var(--border);
      border-radius: 10px; color: var(--accent); cursor: pointer;
      font: inherit; font-size: 14px; font-weight: 600;
      transition: border-color 140ms cubic-bezier(0.2,0,0,1),
                  background-color 140ms cubic-bezier(0.2,0,0,1),
                  transform 140ms cubic-bezier(0.2,0,0,1);
    }
    .co-addr-add svg { width: 18px; height: 18px; flex: 0 0 auto; }
    .co-addr-add:hover {
      border-color: var(--accent);
      background: color-mix(in oklch, var(--accent) 6%, #fff);
    }
    .co-addr-add:focus-visible {
      outline: 3px solid color-mix(in oklch, var(--accent) 45%, transparent);
      outline-offset: 2px;
    }
    .co-addr-add:active { transform: translateY(1px); }

    /* ═══ F-10 one-thumb checkout: accordion + sticky CTA + sub-progress ═══
       Three collapsible sections (Address → Shipping → Payment) with only
       one open at a time. Sticky bottom CTA dominates on mobile so a thumb
       can reach Next/Place-order without scrolling. Sub-progress 1/3 → 2/3
       → 3/3 sits above the accordion (separate from the Cart→Checkout→Done
       top-level indicator). Mobile-first: sticky CTA shows below 768px;
       desktop falls back to the existing summary-aside place-order button. */
    .co-sub-progress {
      display: flex; align-items: center; gap: 8px;
      margin: 0 0 20px 0; font-size: 12px; color: var(--text-mute);
      font-weight: 600;
    }
    .co-sub-progress .co-sub-bar {
      flex: 1; height: 4px; background: var(--border); border-radius: 999px;
      overflow: hidden; position: relative;
    }
    .co-sub-progress .co-sub-bar > span {
      display: block; height: 100%; width: 100%; background: var(--accent);
      /* Wave H7 (F-SHOP-011) — animate transform (composited) instead of
         width (layout) per DESIGN.md §7. scaleX 0..1 fills left→right. */
      transform-origin: left; transform: scaleX(0);
      transition: transform 240ms ease;
    }
    .co-accordion {
      display: flex; flex-direction: column; gap: 0;
      margin-bottom: 16px;
    }
    .co-acc-section {
      background: #fff; border: 1px solid var(--border);
      border-radius: var(--card-radius);
      margin-bottom: 12px; overflow: hidden;
      transition: border-color var(--bk-dur-base) var(--bk-ease-out);
    }
    .co-acc-section.is-open { border-color: var(--accent); }
    .co-acc-section.is-done .co-acc-num {
      background: var(--bk-success, #15803D); color: #fff;
    }
    .co-acc-header {
      display: flex; align-items: center; gap: 12px;
      width: 100%; padding: 16px 18px; cursor: pointer;
      background: none; border: 0; text-align: left;
      font-family: inherit; font-size: inherit; color: inherit;
    }
    .co-acc-section.is-open .co-acc-header { border-bottom: 1px solid var(--border); }
    .co-acc-num {
      flex-shrink: 0; width: 28px; height: 28px; border-radius: 999px;
      background: var(--border); color: var(--text-mute);
      display: inline-flex; align-items: center; justify-content: center;
      font-size: 13px; font-weight: 700;
      transition: background var(--bk-dur-base) var(--bk-ease-out), color var(--bk-dur-base) var(--bk-ease-out);
    }
    .co-acc-section.is-open .co-acc-num {
      background: var(--accent); color: #fff;
    }
    .co-acc-title {
      flex: 1; font-weight: 700; font-size: 15px; color: var(--text);
      letter-spacing: 0.01em;
    }
    .co-acc-summary {
      font-size: 12px; color: var(--text-mute);
      max-width: 50%; overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap;
    }
    .co-acc-section.is-open .co-acc-summary { display: none; }
    .co-acc-chev {
      flex-shrink: 0; width: 18px; height: 18px;
      transition: transform var(--bk-dur-base) var(--bk-ease-out);
      color: var(--text-mute);
    }
    .co-acc-section.is-open .co-acc-chev { transform: rotate(180deg); color: var(--accent); }
    .co-acc-body {
      display: none; padding: 18px;
    }
    .co-acc-section.is-open .co-acc-body { display: block; }
    /* Inside-accordion .co-card resets margin so cards don't double-pad. */
    .co-acc-body .co-card {
      margin-bottom: 12px; padding: 16px; box-shadow: none;
    }
    .co-acc-body .co-card:last-child { margin-bottom: 0; }
    .co-acc-body .co-card h2, .co-acc-body .co-card h3 { margin-top: 0; }

    /* Manual address fields — 2-col grid for district/province on wider
       screens, stacks vertically below 480px. Mirrors .ec-field-row's
       per-field margin but tighter to fit accordion-confined space. */
    .co-addr-grid {
      display: grid; gap: 12px;
      grid-template-columns: 1fr 1fr;
    }
    @media (max-width: 480px) {
      .co-addr-grid { grid-template-columns: 1fr; }
    }
    .co-addr-grid .ec-field { margin-bottom: 0; }

    /* Sticky bottom CTA — mobile-first one-thumb pattern. Pinned above the
       safe-area inset (iOS notch). Hidden ≥768px since the summary aside's
       place-order is reachable on desktop. Always shows the current step's
       primary action: Next: Shipping / Next: Payment / Place order. */
    .co-sticky-cta {
      position: fixed; left: 0; right: 0; bottom: 0;
      z-index: 50;
      padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0)) 16px;
      background: rgba(255,255,255,0.96);
      backdrop-filter: blur(8px);
      -webkit-backdrop-filter: blur(8px);
      border-top: 1px solid var(--border);
      box-shadow: 0 -2px 14px rgba(0,0,0,0.05);
      display: none;
    }
    /* Only mount on the checkout page; sticky element is moved into the
       checkout page DOM, so this scoping prevents it leaking onto other
       SPA pages even if the JS forgets to hide it. */
    body.on-checkout .co-sticky-cta { display: block; }
    @media (min-width: 768px) {
      body.on-checkout .co-sticky-cta { display: none; }
    }
    .co-sticky-cta .co-sticky-row {
      display: flex; align-items: center; gap: 12px;
      max-width: 720px; margin: 0 auto;
    }
    .co-sticky-cta .co-sticky-total {
      flex: 1; min-width: 0;
    }
    .co-sticky-cta .co-sticky-total-label {
      font-size: 11px; color: var(--text-mute); font-weight: 600;
      text-transform: uppercase; letter-spacing: 0.06em;
    }
    .co-sticky-cta .co-sticky-total-value {
      /* v3-2 §2.7 — total uses ink, not accent, for ≥4.5:1 contrast. */
      font-size: 18px; font-weight: 800; color: var(--bk-ink, var(--ink));
      letter-spacing: -0.01em;
    }
    .co-sticky-cta .btn-primary {
      flex-shrink: 0; padding: 12px 22px; font-weight: 700;
      min-width: 160px; justify-content: center;
    }
    .co-sticky-cta .btn-primary[disabled] { opacity: 0.5; pointer-events: none; }
    /* Extra bottom padding on #page-checkout when sticky CTA is mounted so
       the last accordion section isn't covered by the fixed bar. */
    body.on-checkout #page-checkout {
      padding-bottom: 96px;
    }
    @media (min-width: 768px) {
      body.on-checkout #page-checkout { padding-bottom: 0; }
    }
    /* ═══ end F-10 ═══ */

    /* ═══ Wave H-3 (F-CUS-110) — mobile sticky checkout CTA ═══
       Cart page below 900px currently has no sticky CTA — the .cart-summary
       sticky-top doesn't help because it scrolls below the line list. This
       bar mirrors the F-10 co-sticky-cta pattern from the checkout page
       (DESIGN.md §5.5-amend Wave-2 §B; §8.2 CTA min-height 44px).
       Gated by body.on-cart so it can't leak to other SPA routes; hidden
       at ≥900px since the desktop summary-aside place-order is reachable. */
    .cart-sticky-cta {
      position: fixed; left: 0; right: 0; bottom: 0;
      z-index: 50;
      padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0)) 16px;
      background: rgba(255,255,255,0.96);
      backdrop-filter: blur(8px);
      -webkit-backdrop-filter: blur(8px);
      border-top: 1px solid var(--border);
      box-shadow: 0 -2px 14px rgba(0,0,0,0.05);
      display: none;
    }
    body.on-cart .cart-sticky-cta { display: block; }
    @media (min-width: 900px) {
      body.on-cart .cart-sticky-cta { display: none; }
    }
    .cart-sticky-cta .row {
      display: flex; align-items: center; gap: 12px;
      max-width: 720px; margin: 0 auto;
    }
    .cart-sticky-cta .row > span {
      font-size: 11px; color: var(--text-mute); font-weight: 600;
      text-transform: uppercase; letter-spacing: 0.06em;
    }
    .cart-sticky-cta .row > strong {
      flex: 1; min-width: 0;
      /* v3-2 §2.7 — total uses ink, not accent, for ≥4.5:1 contrast. */
      font-size: 18px; font-weight: 800; color: var(--bk-ink, var(--ink));
      letter-spacing: -0.01em;
    }
    .cart-sticky-cta .btn-primary {
      flex-shrink: 0; padding: 12px 22px; font-weight: 700;
      min-width: 160px; min-height: 44px;
      justify-content: center;
    }
    /* Extra bottom padding on #page-cart when sticky bar is mounted so the
       last line / "Or buy via LINE" CTA isn't covered. */
    body.on-cart #page-cart { padding-bottom: 96px; }
    @media (min-width: 900px) {
      body.on-cart #page-cart { padding-bottom: 0; }
    }
    /* ═══ end Wave H-3 ═══ */

    /* ═══ (B+C) pk 2026-06-01 — prominent standout checkout/confirm CTA ═══
       The cart "ชำระเงิน" + checkout "ยืนยันสั่งซื้อ" buttons used the plain
       .btn-primary = var(--accent), which on luxury renders white-on-pale
       (line ~1151: background:#fff; color:var(--accent)) → invisible as the
       primary action. pk locked: "ทำให้เด่น". These four CTAs (cart in-content
       + cart sticky + checkout in-content #co-place + checkout sticky
       #co-sticky-next) get a dedicated high-contrast treatment: solid matte
       ink (--lx-ink #1A1A1A) with white text — clearly POPs vs the champagne
       page on luxury and reads as the decisive action on every template.
       DESIGN.md §3.5 (button labels stay sentence-case, ≤4 words — unchanged).
       Layered low-opacity ink-tinted shadow per anti-generic depth rule. */
    .co-cta-standout,
    body[data-template="luxury"] .co-cta-standout {
      background: #1A1A1A;
      color: #FFFFFF;
      border: none;
      box-shadow:
        0 1px 2px rgba(26,26,26,0.20),
        0 8px 22px -8px rgba(26,26,26,0.45);
      transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
                  box-shadow 180ms cubic-bezier(0.22, 1, 0.36, 1),
                  background 180ms ease;
    }
    .co-cta-standout:hover,
    body[data-template="luxury"] .co-cta-standout:hover {
      background: #000000;
      color: #FFFFFF;
      transform: translateY(-1px);
      box-shadow:
        0 2px 4px rgba(26,26,26,0.24),
        0 14px 30px -10px rgba(26,26,26,0.55);
    }
    .co-cta-standout:active,
    body[data-template="luxury"] .co-cta-standout:active {
      transform: translateY(0);
      box-shadow:
        0 1px 2px rgba(26,26,26,0.24),
        0 5px 14px -8px rgba(26,26,26,0.45);
    }
    .co-cta-standout:focus-visible,
    body[data-template="luxury"] .co-cta-standout:focus-visible {
      outline: 2px solid #1A1A1A;
      outline-offset: 3px;
    }
    /* Keep the disabled sticky CTA legible-but-muted (overrides .btn-primary
       opacity:0.5 path on the checkout sticky-next). */
    .co-cta-standout[disabled],
    body[data-template="luxury"] .co-cta-standout[disabled] {
      background: #BDBDBD; color: #FFFFFF; box-shadow: none;
      transform: none; opacity: 1; pointer-events: none;
    }

    /* (C) pk 2026-06-01 — ONE CTA on the mobile sticky bottom bar
       ("เหลือปุ่มเดียวบน bottom bar"). On mobile, hide the in-content
       checkout/place-order button so only the sticky bottom-bar CTA remains.
       Breakpoints match each page's sticky-bar gate: the cart sticky bar
       shows < 900px, the checkout sticky bar shows < 768px. Desktop keeps
       the in-content button (sticky bars are display:none there). */
    @media (max-width: 899.98px) {
      #page-cart .co-cta-incontent { display: none; }
    }
    @media (max-width: 767.98px) {
      #page-checkout .co-cta-incontent { display: none; }
    }
    /* ═══ end (B+C) ═══ */

    /* ═══ Wave H-2 (F-CUS-102) — :focus-visible sweep ═══
       The shop file had only 2 :focus-visible rules in 16K LOC — every
       other interactive element fell back to the UA default outline (or
       no outline on Safari mobile). This single multi-selector rule covers
       CTAs / links / inputs / form controls so every keyboard tab-stop
       shows a brand-tinted ring. Brand-bleed cascade: outline color uses
       color-mix(var(--accent)) so per-tenant + per-vertical override
       re-tints automatically (DESIGN.md §4.5 + §7 + §8.2). */
    .btn-primary:focus-visible, .btn-ghost:focus-visible, .nav-cart:focus-visible,
    .nav-acct-btn:focus-visible, .auth-btn:focus-visible, .auth-google:focus-visible,
    .auth-x:focus-visible, .auth-link:focus-visible, .auth-tab:focus-visible,
    .co-signin-btn:focus-visible, .co-signin-guest:focus-visible,
    .qty-stepper button:focus-visible,
    .pd-qty-btn:focus-visible, .line-remove:focus-visible, .acct-tab:focus-visible,
    .coupon-apply:focus-visible, .voucher-apply:focus-visible,
    .bk-plp-mobile-filter-btn:focus-visible, .bk-plp-drawer-close:focus-visible,
    .bk-plp-load-more:focus-visible, .bk-plp-clear:focus-visible,
    .bk-plp-pagination button:focus-visible, .bk-plp-sort:focus-visible,
    .nav-acct-item:focus-visible, .ai-send:focus-visible, .ai-x:focus-visible,
    .cf-action:focus-visible, .pd-icon-btn:focus-visible, .nav-links a:focus-visible,
    .footer-col a:focus-visible {
      outline: 3px solid color-mix(in oklch, var(--accent) 45%, transparent);
      outline-offset: 2px;
    }

    /* T1.2 — security / trust badges on the payment step */
    .co-trust {
      display: flex; flex-wrap: wrap; gap: 9px 18px; align-items: center;
      margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border);
    }
    .co-trust-item {
      display: flex; align-items: center; gap: 7px;
      font-size: 12px; font-weight: 600; color: var(--text-mute);
    }
    .co-trust-item svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--bk-success, #15803D); }
    /* Wave H-shop-remainder-4 (F-SHOP-013) — bk-trust-strip primitive per
       DESIGN.md §4.6. Layered alongside the legacy .co-trust + .co-trust-item
       classes (both kept as aliases) so callers can opt in to the primitive
       without coupling. Strip sets color: --bk-success at the parent so
       bk-icon currentColor + bk-trust-label both inherit the success tone
       per §2.4 semantic accent contract. */
    .bk-trust-strip {
      display: flex; flex-wrap: wrap; gap: 9px 18px; align-items: center;
      color: var(--bk-success, #15803D);
    }
    .bk-trust-item {
      display: inline-flex; align-items: center; gap: 7px;
      font-size: 12px; font-weight: 600;
    }
    .bk-trust-item .bk-icon { font-size: 14px; flex-shrink: 0; }
    /* When .bk-trust-strip and .co-trust live on the same element, the
       co-trust-item text-mute color wins for label text but the icon
       still picks up success via bk-icon currentColor under bk-trust-strip. */
    .bk-trust-strip .co-trust-item { color: var(--text-mute); }
    .bk-trust-strip .co-trust-item .bk-icon { color: var(--bk-success, #15803D); }
    .ec-field { margin-bottom: 14px; }
    .ec-field label {
      display: block; font-size: 12px; font-weight: 600;
      color: var(--text); margin-bottom: 6px;
      letter-spacing: 0.04em;
    }
    .ec-field label small { font-weight: 400; color: var(--text-mute); letter-spacing: 0; }
    .ec-field input, .ec-field textarea {
      width: 100%; padding: 10px 12px; font-size: 14px;
      border: 1px solid var(--border); border-radius: 10px;
      font-family: inherit; background: #fff; color: var(--text);
      box-sizing: border-box;
    }
    .ec-field input:focus, .ec-field textarea:focus {
      outline: none; border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(0,0,0,0.05);
    }
    .ec-field-row { display: flex; gap: 12px; }
    .ec-field-row .ec-field { flex: 1; }
    /* Wave H7 (F-CUS-115) — inline helper text under inputs per DESIGN.md §1
       (anchor expected input shape, lower checkout-error rate). */
    .ec-field-help {
      display: block; margin-top: 6px;
      font-size: 12px; color: var(--text-mute); line-height: 1.4;
    }
    /* T4.1 — Contact page */
    .contact-line {
      display: flex; gap: 10px; align-items: flex-start;
      font-size: 14px; color: var(--text); margin-bottom: 12px;
    }
    .contact-line:last-child { margin-bottom: 0; }
    .contact-line .ic { font-size: 16px; line-height: 1.4; flex-shrink: 0; }
    .contact-line a {
      color: var(--accent);
      /* B3 — pad the phone/email/LINE links to a comfortable tap row. */
      display: inline-block; padding: 5px 0;
    }
    .contact-hours-row {
      display: flex; justify-content: space-between; gap: 16px;
      font-size: 13px; color: var(--text); padding: 4px 0;
    }
    /* Ref-display item 3 — a closed day reads muted in the hours table. */
    .contact-hours-row.is-closed { color: var(--text-mute); }
    .faq-item {
      border-bottom: 1px solid var(--border); padding: 4px 0;
    }
    .faq-item:last-child { border-bottom: none; }
    .faq-item summary {
      cursor: pointer; padding: 12px 0; font-size: 14px; font-weight: 600;
      color: var(--text); list-style: none; display: flex;
      justify-content: space-between; gap: 12px;
    }
    .faq-item summary::-webkit-details-marker { display: none; }
    .faq-item summary::after { content: "+"; color: var(--text-mute); font-weight: 400; }
    .faq-item[open] summary::after { content: "−"; }
    .faq-item .faq-a {
      font-size: 13px; color: var(--text-mute); line-height: 1.6;
      padding: 0 0 14px 0;
    }

    /* Phase 1.5 Section C — shipping selector */
    .co-shipping-select {
      width: 100%; padding: 10px 12px;
      border: 1px solid var(--border); border-radius: 10px;
      background: #fff; color: var(--accent);
      font: inherit; font-size: 14px;
      cursor: pointer; appearance: none;
      background-image: linear-gradient(45deg, transparent 50%, var(--text-mute) 50%),
                        linear-gradient(135deg, var(--text-mute) 50%, transparent 50%);
      background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
      background-size: 5px 5px, 5px 5px;
      background-repeat: no-repeat;
      padding-right: 32px;
      transition: border-color 120ms ease;
    }
    .co-shipping-select:focus { outline: none; border-color: var(--accent); }
    .co-shipping-label { font-size: 13px; }

    .co-qr {
      text-align: center; padding: 16px; background: var(--bg-soft);
      border-radius: var(--card-radius);
    }
    .co-qr img {
      max-width: 240px; width: 100%; margin: 0 auto;
      background: #fff; padding: 12px; border-radius: 12px;
    }
    .co-qr .co-amount {
      margin-top: 12px; font-size: 14px; color: var(--text-mute);
    }
    .co-qr .co-amount strong { color: var(--accent); font-size: 18px; }
    .co-qr-fallback {
      text-align: center; padding: 32px 16px; background: var(--bg-soft);
      border-radius: var(--card-radius); color: var(--text-mute);
    }
    .co-qr-fallback p { margin: 0 0 12px 0; }

    /* ═══ P1 Payment methods (2026-05-28) — consumes
         storefrontBundle.payment_methods[] per Mr.D contract.
         Rendered into #co-pm-list inside #co-pm-card; hides itself when
         the tenant has 0 methods configured (legacy PromptPay-only QR
         block in the next .co-card remains the fallback). ═══ */
    .co-pm-list { display: grid; gap: 12px; }
    .co-pm-row {
      display: grid; grid-template-columns: 48px 1fr; gap: 14px;
      padding: 14px; border: 1px solid var(--border);
      border-radius: var(--card-radius); background: #fff;
    }
    .co-pm-row + .co-pm-row { margin-top: 0; }  /* gap handled by .co-pm-list */
    .co-pm-logo {
      width: 48px; height: 48px; border-radius: 10px;
      display: flex; align-items: center; justify-content: center;
      background: var(--bg-soft); color: var(--text);
      font-weight: 700; font-size: 13px; letter-spacing: 0.04em;
      text-transform: uppercase;
      flex-shrink: 0;
    }
    .co-pm-body { min-width: 0; }
    .co-pm-bank { font-weight: 700; font-size: 14px; color: var(--text); }
    .co-pm-acct {
      margin-top: 4px; font-size: 13px; color: var(--text-mute);
      font-variant-numeric: tabular-nums;
      word-break: break-all;          /* long masked numbers wrap cleanly */
    }
    .co-pm-name { margin-top: 2px; font-size: 13px; color: var(--text-mute); }
    .co-pm-qr {
      grid-column: 1 / -1;
      margin-top: 10px; text-align: center;
    }
    .co-pm-qr img {
      max-width: 180px; width: 100%; height: auto;
      border-radius: 6px; background: var(--bg-soft);
    }
    .co-pm-qr-label {
      display: block; margin-bottom: 6px;
      font-size: 12px; color: var(--text-mute);
      text-transform: uppercase; letter-spacing: 0.04em;
    }
    .co-pm-hint {
      font-size: 12px; color: var(--text-mute);
      margin-top: 4px; line-height: 1.5;
    }
    /* ═══ end P1 ═══ */

    .co-slip-preview {
      position: relative; min-height: 120px;
      border: 2px dashed var(--border); border-radius: var(--card-radius);
      background: var(--bg-soft);
      display: flex; align-items: center; justify-content: center;
      overflow: hidden;
    }
    .co-slip-empty {
      width: 100%; padding: 32px; text-align: center;
      color: var(--text-mute); cursor: pointer; font-size: 14px;
    }
    .co-slip-empty:hover { color: var(--accent); }

    /* (F) pk 2026-06-01 ("แตะเพื่อเลือกไฟล์ ทำให้เห็นชัดขึ้น") — make the
       slip dropzone clearly visible + inviting: taller drop area, accent-
       tinted dashed border + hover/focus feedback, upload glyph, a strong
       prompt line + a quieter formats line. Mirrors the admin .bk-img-field
       dashed-dropzone pattern. Scoped to the --dropzone modifier so the
       img-preview state (after a slip is picked) keeps its plain frame. */
    .co-slip-preview--dropzone {
      min-height: 168px;
      border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
      background:
        linear-gradient(0deg,
          color-mix(in srgb, var(--accent) 5%, transparent),
          color-mix(in srgb, var(--accent) 5%, transparent)),
        var(--bg-soft);
      transition: border-color 180ms ease, background 180ms ease;
    }
    .co-slip-preview--dropzone:hover,
    .co-slip-preview--dropzone:focus-within {
      border-color: color-mix(in srgb, var(--accent) 72%, var(--border));
      background:
        linear-gradient(0deg,
          color-mix(in srgb, var(--accent) 9%, transparent),
          color-mix(in srgb, var(--accent) 9%, transparent)),
        var(--bg-soft);
    }
    .co-slip-preview--dropzone .co-slip-empty {
      display: flex; flex-direction: column; align-items: center; gap: 8px;
      padding: 28px 20px; border-radius: var(--card-radius);
    }
    .co-slip-icon {
      width: 38px; height: 38px;
      color: var(--accent);
      padding: 9px; border-radius: 999px;
      background: color-mix(in srgb, var(--accent) 12%, transparent);
    }
    .co-slip-prompt {
      font-size: 15px; font-weight: 700;
      color: var(--bk-ink, var(--text));
    }
    .co-slip-formats {
      font-size: 12px; color: var(--text-mute); letter-spacing: 0.02em;
    }
    .co-slip-empty:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 3px;
      border-radius: var(--card-radius);
    }
    #co-slip-img {
      max-width: 100%; max-height: 240px; display: block; margin: 0 auto;
    }
    #co-slip-clear {
      position: absolute; top: 6px; right: 6px;
      width: 26px; height: 26px; border-radius: 999px;
      background: rgba(var(--bk-shadow-color) / 70%); color: #fff; font-size: 14px;
      cursor: pointer; padding: 0; border: none;
    }

    /* ═══ Wave H9 (F-CUS-117) — PromptPay slip UX overhaul ═══════════════
       Three coupled primitives that share one inline panel grammar per
       DESIGN.md §11.2.4 (modal/drawer/inline panel decision):

         .bk-slip-education  — collapsible <details> disclosure on slip vs
                               screenshot education (good/bad SVG examples)
         .bk-slip-preview    — post-pick confirm panel with checklist + 2
                               actions (Pick another / Confirm + send)
         .bk-slip-banner-*   — semantic-tinted error banner per §2.4 +
                               Wave-2 §B, revealed by showSlipError()

       Reuses --accent / --bg-soft tokens so per-tenant brand bleed cascades
       in per Wave H-1 (F-CUS-132). Color tokens with #-fallbacks because
       --bk-* semantic accent tokens haven't shipped on shop.html yet. */
    .bk-slip-education {
      background: var(--bg-soft, #FBF8F1);
      border: 1px solid var(--border);
      border-radius: 8px; padding: 10px 12px; margin-bottom: 12px;
    }
    .bk-slip-edu-trigger {
      display: flex; align-items: center; gap: 8px; cursor: pointer;
      color: var(--accent); font-weight: 600; font-size: 14px;
      list-style: none; min-height: 32px;
    }
    .bk-slip-edu-trigger::-webkit-details-marker { display: none; }
    .bk-slip-edu-trigger .bk-icon { font-size: 16px; }
    .bk-slip-edu-body {
      padding-top: 10px; color: var(--text-mute);
      font-size: 13px; line-height: 1.5;
    }
    .bk-slip-edu-body p { margin: 0 0 10px 0; }
    .bk-slip-edu-examples {
      display: grid; grid-template-columns: 1fr 1fr;
      gap: 12px; margin-top: 12px;
    }
    .bk-slip-edu-good, .bk-slip-edu-bad { margin: 0; }
    .bk-slip-edu-img {
      width: 100%; aspect-ratio: 3 / 4; border-radius: 6px;
      display: block; background: #fff;
    }
    .bk-slip-edu-good figcaption,
    .bk-slip-edu-bad figcaption {
      display: flex; align-items: center; gap: 4px;
      font-size: 12px; margin-top: 6px; line-height: 1.4;
    }
    .bk-slip-edu-good figcaption { color: #059669; /* §2.4 success token */ }
    .bk-slip-edu-bad  figcaption { color: #DC2626; /* §2.4 danger  token */ }
    .bk-slip-edu-good .bk-icon,
    .bk-slip-edu-bad  .bk-icon { font-size: 14px; flex-shrink: 0; }

    .bk-slip-preview {
      padding: 14px; background: #fff;
      border: 1px solid var(--border); border-radius: 8px;
      box-shadow: 0 6px 16px -10px rgba(var(--bk-shadow-color) / 18%);
      margin-top: 12px;
    }
    .bk-slip-preview[hidden] { display: none; }
    .bk-slip-preview-head { margin-bottom: 10px; }
    .bk-slip-preview-title {
      font-family: var(--display-font);
      font-size: 16px; font-weight: 700; margin: 0;
      color: var(--text);
    }
    .bk-slip-preview-checklist {
      list-style: none; padding: 0; margin: 0 0 14px 0;
      font-size: 13px; color: var(--text); line-height: 1.5;
    }
    .bk-slip-preview-checklist li {
      display: flex; align-items: flex-start; gap: 8px;
    }
    .bk-slip-preview-checklist .bk-icon { font-size: 15px; color: #059669; flex-shrink: 0; margin-top: 2px; }
    .bk-slip-preview-actions {
      display: flex; align-items: center; justify-content: space-between;
      gap: 10px; flex-wrap: wrap;
    }
    @media (max-width: 640px) {
      .bk-slip-preview-actions { flex-direction: column-reverse; }
      .bk-slip-preview-actions > button { width: 100%; justify-content: center; }
    }

    .bk-slip-banner {
      display: flex; gap: 12px; padding: 14px;
      border-radius: 8px; margin: 0 0 12px 0;
    }
    .bk-slip-banner[hidden] { display: none; }
    .bk-slip-banner-error {
      background: #FEF2F2; /* danger-soft */
      color: #7F1D1D;
      border-left: 4px solid #DC2626;
    }
    .bk-slip-banner-error > .bk-icon {
      font-size: 18px; color: #DC2626; flex-shrink: 0; margin-top: 2px;
    }
    .bk-slip-banner-body { flex: 1 1 auto; }
    .bk-slip-banner-title {
      font-weight: 700; margin: 0 0 4px 0;
      color: #7F1D1D; font-size: 14px;
    }
    .bk-slip-banner-reason {
      color: var(--text); font-size: 14px; margin: 0 0 8px 0; line-height: 1.5;
    }
    .bk-slip-banner-reason:empty { display: none; }
    .bk-slip-banner-hints {
      color: var(--text-mute); font-size: 13px; margin: 0 0 10px 0; line-height: 1.5;
    }
    /* ═══ end Wave H9 ═══════════════════════════════════════════════════ */
    .co-line {
      display: flex; justify-content: space-between;
      align-items: baseline; padding: 6px 0; font-size: 13px;
    }
    .co-line .name {
      flex: 1; padding-right: 12px;
      color: var(--text-mute);
      overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap;
    }
    .co-line .qty { color: var(--text-mute); margin-right: 12px; }

    .conf-card {
      background: #fff; border: 1px solid var(--border);
      border-radius: var(--card-radius); padding: 48px 32px;
      text-align: center;
      box-shadow: var(--bk-elev-card);
    }
    body[data-template="luxury"] .conf-card { background: var(--bg-soft); padding: 64px 32px; }
    .conf-icon {
      width: 64px; height: 64px; border-radius: 999px;
      background: #DCFCE7; color: #166534;
      display: inline-flex; align-items: center; justify-content: center;
      font-size: 32px; margin-bottom: 20px;
    }
    .conf-card h1 {
      font-family: var(--display-font);
      font-size: clamp(26px, 4vw, 36px); margin: 0 0 8px 0;
      font-weight: 700; letter-spacing: -0.01em;
    }
    body[data-template="luxury"] .conf-card h1 { font-weight: 500; }
    .conf-card > p { color: var(--text-mute); margin: 0 0 24px 0; }
    .conf-ref {
      display: inline-flex; flex-direction: column;
      padding: 14px 20px; background: var(--bg-soft);
      border-radius: var(--card-radius); margin-bottom: 24px;
      gap: 4px;
    }
    .conf-ref span { font-size: 12px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.06em; }
    .conf-ref strong { font-family: ui-monospace, monospace; font-size: 20px; font-weight: 700; }
    .conf-recap {
      text-align: left; margin: 0 auto 24px auto;
      max-width: 460px; padding: 20px;
      background: var(--bg-soft); border-radius: var(--card-radius);
    }
    /* B4 — confirmation payment-status block */
    .conf-payment {
      max-width: 460px; margin: 0 auto 20px auto; padding: 14px 16px;
      border-radius: var(--card-radius); text-align: left;
      font-size: 14px; line-height: 1.55; display: flex; gap: 10px;
    }
    .conf-payment .pi { flex: 0 0 auto; font-size: 18px; line-height: 1.4; }
    .conf-payment.verifying { background: rgba(59,130,246,0.10); color: #1D4ED8; }
    .conf-payment.pending   { background: rgba(245,158,11,0.13); color: #92400E; }
    .conf-payment b { font-weight: 700; }
    /* B4 — checkout out-of-stock recovery */
    .cart-oos-banner {
      background: rgba(220,38,38,0.08); border: 1px solid rgba(220,38,38,0.3);
      color: #B91C1C; border-radius: var(--card-radius);
      padding: 13px 16px; margin-bottom: 16px; font-size: 13px;
      font-weight: 600; line-height: 1.5;
    }
    .cart-line.cart-line-oos { border: 1.5px solid rgba(220,38,38,0.45); }
    .cart-line .cart-line-oos-warn {
      margin-top: 8px; font-size: 12px; font-weight: 700; color: #B91C1C;
    }
    /* Cart-B — a deactivated product line, hydrated with is_active:false */
    .cart-line.cart-line-inactive { opacity: 0.6; }
    /* Wave H-6b (F-SHOP-006) — tabular-nums on confirmation recap rows so
       subtotal/shipping/discount/total digits stack column-true per §3.6.3. */
    .conf-recap .line { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; font-variant-numeric: tabular-nums; }
    .conf-recap .line.total { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
    .conf-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
    /* Wave H7 (F-CUS-135) — narrow-viewport reverse-stack per DESIGN.md §5.3
       (primary action sits ABOVE secondary when stacked). Above 640px the
       row layout keeps primary on the left. */
    @media (max-width: 640px) {
      .conf-actions { flex-direction: column-reverse; gap: 8px; }
      .conf-actions > a { width: 100%; justify-content: center; }
    }
    /* Wave H5 (F-SHOP-004) — bk-receipt primitive aliases per DESIGN.md §4.13.
       Layer on top of .conf-* without disturbing existing visual treatment. */
    .bk-receipt { display: block; }
    .bk-receipt-head { text-align: center; }
    .bk-receipt-title { font-family: var(--display-font); }
    .bk-receipt-body { display: block; }
    .bk-receipt-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
    /* Wave H5 + Wave H7 (F-CUS-135 cascade) — bk-receipt-actions inherits
       the narrow-viewport reverse-stack since it's aliased on the same
       .conf-actions element; this targets pure bk-receipt-actions usage. */
    @media (max-width: 640px) {
      .bk-receipt-actions { flex-direction: column-reverse; gap: 8px; }
      .bk-receipt-actions > a { width: 100%; justify-content: center; }
    }

    /* Cart count badge on nav-cart */
    .nav-cart { position: relative; }
    .nav-cart .badge {
      position: absolute; top: -4px; right: -4px;
      min-width: 18px; height: 18px; padding: 0 5px;
      border-radius: 999px; background: var(--accent); color: #fff;
      font-size: 11px; font-weight: 700; line-height: 18px;
      text-align: center;
    }
    .nav-cart .badge.hidden { display: none; }

    /* ── Skeleton (initial loading) ─────────────────────────────────── */
    .skeleton-page {
      min-height: 70vh; display: flex; align-items: center;
      justify-content: center; color: var(--text-mute);
      font-size: 14px;
    }
    .skeleton-page.gone { display: none; }

    /* ── Shop-not-found state (Mr.D backend returns 404 → Mr.C renders) ──
       Centred card replaces the skeleton overlay when the storefront API
       returns 404 (deleted / non-existent tenant). #root stays hidden so
       the nav/cart/search/footer chrome never appears — keeps the visitor
       from clicking through a non-functional shop UI.
       Mobile-first: tested at 375px width per the Mr.C brief DoD. */
    .skeleton-page.is-shop-not-found {
      flex-direction: column; padding: 24px;
      min-height: 100vh; color: #0F172A;
    }
    .bk-shop-not-found {
      max-width: 420px; width: 100%;
      background: white;
      border: 1px solid #E5E7EB;
      border-radius: 18px;
      box-shadow: 0 24px 60px -16px rgba(var(--bk-shadow-color) / 18%),
                  0 8px 20px -10px rgba(37,99,235,0.08);
      padding: 32px 24px;
      text-align: center;
      font-family: 'Plus Jakarta Sans', 'Noto Sans Thai', system-ui, -apple-system, sans-serif;
    }
    .bk-shop-not-found .icon {
      width: 64px; height: 64px; margin: 0 auto 16px;
      border-radius: 50%;
      background: color-mix(in oklch, var(--accent, var(--brand-primary, #2563EB)) 12%, white);
      display: flex; align-items: center; justify-content: center;
      font-size: 28px; color: #4F46E5;
    }
    .bk-shop-not-found h1 {
      font-size: 22px; font-weight: 700; color: #0F172A;
      margin: 0 0 8px; letter-spacing: -0.01em;
    }
    .bk-shop-not-found p {
      font-size: 14px; color: #475569; line-height: 1.55;
      margin: 0 0 24px;
    }
    .bk-shop-not-found a.cta {
      display: inline-flex; align-items: center; gap: 8px;
      padding: 12px 22px; border-radius: 12px;
      background: var(--accent, var(--brand-primary, #2563EB));
      color: white; text-decoration: none;
      font-size: 14px; font-weight: 700;
      box-shadow: 0 8px 18px -6px rgba(79,70,229,0.45);
      transition: transform var(--bk-dur-fast) var(--bk-ease-out), box-shadow var(--bk-dur-fast) var(--bk-ease-out);
    }
    .bk-shop-not-found a.cta:hover {
      transform: translateY(-1px);
      box-shadow: 0 12px 24px -8px rgba(79,70,229,0.55);
    }
    @media (max-width: 420px) {
      .bk-shop-not-found { padding: 28px 20px; border-radius: 16px; }
      .bk-shop-not-found h1 { font-size: 20px; }
    }

    /* ── Round 28 — per-section skeletons + stale-while-revalidate ──
       Used by every section/route that refetches after first paint
       (account-hub tabs, PDP open, category/brand/search refilter,
       article detail). The shimmer is the same greyed-block treatment
       across the storefront so transitions feel consistent. Sized to
       match the eventual content roughly so layout doesn't jump (CLS
       hygiene). See _skel() + _swrCache() in the JS half. */
    @keyframes bk-skel-shimmer {
      0%   { background-position: -400px 0; }
      100% { background-position:  400px 0; }
    }
    .bk-skel {
      /* Phase 4.5-F F-1 round-2 — Mr.F Review #33 Amendment #1:
         theme-aware background. Per-template body[data-template='luxury'|
         'catalog'|'premium'] selectors already define --bg-soft (luxury
         #F1EDE6 / catalog #F5F5F5 / premium #FFFFFF / modern default
         #FAFAFA). var() with #EEF0F3 fallback so non-templated pages
         retain the original tone. Mr.E §2.1 DoD "color tokens match
         per-template" satisfied. */
      background-color: var(--bg-soft, #EEF0F3);
      background-image: linear-gradient(
        90deg,
        rgba(255,255,255,0) 0%,
        rgba(255,255,255,0.55) 50%,
        rgba(255,255,255,0) 100%);
      background-size: 400px 100%;
      background-repeat: no-repeat;
      animation: bk-skel-shimmer 1.2s ease-in-out infinite;
      border-radius: 8px;
    }
    /* Common skeleton primitives */
    .bk-skel-line { height: 12px; margin: 8px 0; }
    .bk-skel-line.lg { height: 18px; }
    .bk-skel-line.sm { height: 10px; }
    .bk-skel-line.w-30 { width: 30%; }
    .bk-skel-line.w-50 { width: 50%; }
    .bk-skel-line.w-70 { width: 70%; }
    .bk-skel-line.w-90 { width: 90%; }
    .bk-skel-block { height: 120px; }
    .bk-skel-card  { padding: 16px; border: 1px solid var(--border);
                     border-radius: 12px; background: var(--bg); margin-bottom: 14px; }
    /* Product-grid skeleton: respects the existing .prod-grid layout */
    .bk-skel-prod {
      aspect-ratio: 1 / 1; min-height: 220px; border-radius: 12px;
    }
    /* Phase 4.5-F F-1: category-tile skeleton — squat compared to .bk-skel-prod
       since the real category tile is a single label-over-image card, not a
       full product card. Sized to roughly match .cat-card so paintInitialSkeletons
       → renderCategories swap doesn't reflow. */
    .bk-skel-cat {
      aspect-ratio: 1 / 1; min-height: 140px; border-radius: 12px;
    }
    /* PDP skeleton: large gallery + info column */
    .bk-skel-pdp-gallery { width: 100%; aspect-ratio: 1 / 1; min-height: 320px; }
    .bk-skel-pdp-info { padding: 8px 0; }
    /* Article detail skeleton */
    .bk-skel-article-cover { width: 100%; aspect-ratio: 16 / 9; min-height: 200px; }
    /* Cart skeleton row */
    .bk-skel-cart-row { height: 96px; margin-bottom: 12px; }
    /* Stale-while-revalidate dim: applied to the section root when
       a cached payload is showing and a background refetch is in flight.
       Pointer events disabled to avoid double-clicks on stale buttons. */
    .bk-swr-revalidating {
      opacity: 0.55;
      pointer-events: none;
      transition: opacity 120ms linear;
    }

    /* ── Phase 3: Flash-sale chrome ─────────────────────────────────── */
    .flash-strike {
      /* pk 2026-06-01 (editorial card redesign) — the struck "was" price is the
         SMALL/MUTED/line-through item that sits LEFT of the red sale price (the
         .price slot is now a row flex; its gap handles spacing, so no
         margin-right here — that would double the gap). Scales with card width
         (it's the widest item, so shrinking it frees room @2-up). 5.5cqi → 13px
         MAX @≥236px (covers ~240px track), 12.1px @220, 11px floor @154. */
      font-size: clamp(11px, 5.5cqi, 13px); color: var(--text-mute);
      text-decoration: line-through; font-weight: 500;
    }
    /* pk 2026-06-01 — the SALE price is the prominent/front price: red + bold. */
    .flash-price {
      color: #DC2626; font-weight: 800;
    }
    /* pk 2026-06-01 — on-sale effective price fluid (MAX 17px = today; floor
       13px). 7.2cqi → 17px pinned @≥236px (covers the ~240px luxury track so
       desktop is unchanged), 15.8px @220, 13px floor @154. Scaling this
       alongside .flash-strike keeps the whole sale price block from over-
       running the brand pill @2-up. */
    .prod-card .price.flash-on .flash-price { font-size: clamp(13px, 7.2cqi, 17px); }
    /* ── SF2 (2026-05-31): standing promotion-price chrome ──────────────
       Mirrors the flash chrome 1:1 (struck base + red effective price) so a
       standing promo and a flash sale read identically. No countdown — a
       promo is permanent until the admin clears it. Flash still wins when
       both apply; this class set only renders when there's no active flash. */
    .promo-strike {
      /* pk 2026-06-01 — mirrors .flash-strike 1:1 (small/muted/line-through,
         LEFT of the red sale price; no margin-right — .price gap handles it). */
      font-size: clamp(11px, 5.5cqi, 13px); color: var(--text-mute);
      text-decoration: line-through; font-weight: 500;
    }
    /* pk 2026-06-01 — promo sale price = red + bold, the prominent front price. */
    .promo-price {
      color: #DC2626; font-weight: 800;
    }
    /* pk 2026-06-01 — mirrors flash effective price (MAX 17px, floor 13px, 7.2cqi). */
    .prod-card .price.promo-on .promo-price { font-size: clamp(13px, 7.2cqi, 17px); }
    .flash-countdown {
      display: inline-block; padding: 2px 8px;
      background: #FEF3C7; color: #92400E;
      font-size: 11px; font-weight: 600;
      border-radius: 999px; margin-left: 6px;
      letter-spacing: 0.01em; white-space: nowrap;
    }
    .flash-countdown.ended { background: #E5E7EB; color: #6B7280; }
    /* pk 2026-06-01 — countdown now = static red clock SVG + time text (no ⏳
       emoji). Lay the pill out as inline-flex so the icon + .flash-countdown-text
       sit centered on one row with a small gap; the icon is painted red
       (--bk-danger) and the sprite glyph inherits that via currentColor. Applies
       to the inline product-card pill, the PDP pill, and the flash-strip header
       pill (all share .flash-countdown / .flash-strip-countdown). */
    .flash-countdown,
    .flash-strip-countdown {
      display: inline-flex; align-items: center; gap: 5px;
    }
    .flash-countdown-icon {
      color: var(--bk-danger, #B91C1C);
      width: 1.05em; height: 1.05em; flex-shrink: 0;
    }
    /* The text span carries the tabular-nums so the seconds digit doesn't
       jitter the pill width as it ticks. */
    .flash-countdown-text { font-variant-numeric: tabular-nums; }
    /* M-7 (Phase 2D, pk Q10): flash-sale lower-banner strip on the
       storefront landing. Branded peach gradient with subtle border;
       horizontal product strip below the header on desktop, scroll-snap
       horizontal carousel on mobile. Theme override for luxury swaps
       peach for a muted gold-charcoal palette so the strip doesn't
       shatter the luxury aesthetic. */
    .flash-strip {
      /* ConWerse r3 (2026-05-31): removed peach background + orange border +
         shadow per pk — flash strip now sits flush on the page surface like
         the brand strip. Only the label/countdown keep the warm accent. */
      /* pk 2026-06-01 — NO extra L/R gutter: the flash strip already sits inside
         the page .container (28px mobile / 40px desktop), so it must NOT add its
         own side padding — that double-inset it vs the other sections. Zero L/R
         here → the flash row aligns flush at the SAME edge as the other rails. */
      padding: 0;
      max-width: 100%;
    }
    /* ═══ Flash section header → BIG standalone countdown (pk 2026-06-01 REF 2) ══
       The old .flash-strip-head (⚡label + campaign name + small pill + See-all)
       is GONE — replaced by .flash-bigclock: a large centered HH:MM:SS timer with
       "Hours / Minutes / Seconds" labels under each segment + colon separators.
       Section-level (one per campaign), ticked by the existing per-second walker
       via data-flash-bigclock="1" + _flashBigClock(). Cites DESIGN.md §2.4
       (semantic --bk-danger numerals), §4.0 (radius scale), §2.7 (contrast). */
    .flash-bigclock {
      display: flex; align-items: flex-start; justify-content: center;
      gap: 10px; margin: 4px 0 26px;
    }
    .flash-bigclock .fc-seg {
      display: flex; flex-direction: column; align-items: center; gap: 6px;
      min-width: 64px;
    }
    .flash-bigclock .fc-num {
      font-variant-numeric: tabular-nums; font-weight: 800;
      font-size: clamp(34px, 11vw, 52px); line-height: 1;
      letter-spacing: -0.02em; color: var(--text, #1A1A1A);
    }
    .flash-bigclock .fc-label {
      font-size: 12px; font-weight: 600; letter-spacing: 0.04em;
      color: var(--text-mute, #6B7280); text-transform: none;
    }
    .flash-bigclock .fc-colon {
      font-weight: 800; line-height: 1;
      font-size: clamp(30px, 9vw, 46px);
      color: var(--text-mute, #9CA3AF);
      /* nudge the colon up so it optically centers on the numerals (which sit
         above their labels) rather than the whole segment column. */
      align-self: flex-start; margin-top: clamp(2px, 1vw, 6px);
    }
    /* Luxury template — numerals in the warm ink, danger reserved for price. */
    body[data-template="luxury"] .flash-bigclock .fc-num {
      font-family: var(--display-font); color: #2A2118;
    }
    body[data-template="luxury"] .flash-bigclock .fc-colon { color: #B79A63; }
    body[data-template="luxury"] .flash-bigclock .fc-label { color: #6B4F1F; }
    /* pk 2026-06-01 — flash cards swipe left/right EXACTLY like the
       มาใหม่/ขายดี/โปรโมชัน product rails: a single HORIZONTAL row that
       overflows and scrolls — native touch-swipe on mobile + mouse drag-scroll
       on desktop (the container gets _enableDragScroll, same as .shop-tab-rail).
       Was a wrapping auto-fit GRID (couldn't swipe on desktop; wrapped to rows
       on mobile). Now flex + overflow-x at ALL widths with fixed-basis cards so
       it never wraps. Scrollbar hidden (drag/swipe is the affordance), matching
       the rails. */
    .flash-strip-cards {
      display: flex; flex-wrap: nowrap;
      gap: 12px;
      overflow-x: auto;
      scroll-snap-type: x proximity; scroll-padding-inline: 0;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;           /* hidden scrollbar like the rails */
      cursor: grab;                    /* mouse affordance: drag to scroll */
      padding-bottom: 6px;
      /* pk 2026-06-01 — center the row on screen. `safe center` centers the
         cards when they fit (few flash items) but falls back to start-align
         when they overflow, so the leftmost card is never clipped/unreachable
         on a scroll row. margin-inline:auto centers the container itself. */
      justify-content: safe center;
      margin-inline: auto;
    }
    .flash-strip-cards::-webkit-scrollbar { width: 0; height: 0; display: none; }
    .flash-strip-cards.is-dragging { cursor: grabbing; scroll-snap-type: none; }
    .flash-strip-cards.is-dragging > * { pointer-events: none; }
    .flash-strip-cards > * {
      /* pk 2026-06-01 — wider mobile basis (~1.7-up + peek, was 2.3-up). The
         restacked 2-line price (big promo over small struck) + the 44px cart
         circle need more room than the old single-line price; 1.7-up gives the
         square card breathing space so the bottom pill reads clean, not
         stretched/clipped. */
      flex: 0 0 calc((100% - 0.7 * 12px) / 1.7);
      scroll-snap-align: start;
    }
    @media (max-width: 767px) {
      /* pk 2026-06-01 — no extra L/R gutter (rely on .container) so flash aligns
         with the other sections; keep the 16px top/bottom rhythm. */
      .flash-strip { padding: 16px 0; }
      /* pk 2026-06-01 — tighter price-pill inner padding on phones so the
         2-line price stack fits the narrow square card. Cart circle stays
         44px (≥ the a11y tap-target floor, DESIGN.md §4.5). */
      .flash-strip .flash-card .flash-card-bar { padding: 5px 5px 5px 12px; gap: 6px; }
      /* pk 2026-06-01 REF 2 — tighten the big countdown on phones. */
      .flash-bigclock { gap: 6px; margin-bottom: 18px; }
      .flash-bigclock .fc-seg { min-width: 0; }
    }

    /* ═══ Flash-sales card (.flash-card) — distinct campaign card ═══════════
       pk 2026-06-01 ref: a clean vertical campaign card used ONLY for products
       in an active flash campaign (the renderer branches on opts.flashCard).
       Layout top→bottom: segmented countdown pill → FULL-BLEED product image
       (heart top-right) → name (left) + price (right) → full-width OUTLINED
       Add-To-Cart pill (always visible). NO star rating, NO "N sold".
       Cites DESIGN.md §4.0 (radius scale), §2.4 (semantic --bk-danger on the
       countdown), §6 (brand-mixed elevation, never flat black), §4.4 (the
       auto-lift cycle targets only transform/box-shadow; reduced-motion off),
       §4.5 (button hover/active/focus-visible states on the CTA). SCOPED to
       .flash-card so the normal full-bleed card (Change A) is untouched.
       pk 2026-06-01 r6 — the flash card is now UNIFIED with the normal card:
       full-bleed cover image (no soft tile), and the card itself is FLOATING
       (transparent bg, no border, soft shadow only) with a subtle auto-lift
       cycle. (Resolves the earlier tile-vs-fullbleed flag.) */
    /* Flush on the page surface (ref has no surrounding panel) — drop the peach
       box/border/shadow the generic luxury .flash-strip rule applied above.
       pk 2026-06-01 — no extra L/R gutter (rely on .container, align w/ other sections). */
    body[data-template="luxury"] .flash-strip {
      padding: 0; background: none; border: none; box-shadow: none;
    }
    /* Desktop: wider basis so ~3 full cards + a peek of the next show in the
       single scrollable row (mirrors the .shop-tab-rail desktop denominator). */
    @media (min-width: 768px) {
      .flash-strip-cards { gap: 18px; }
      .flash-strip-cards > * { flex: 0 0 calc((100% - 2.3 * 18px) / 3.3); }
      body[data-template="luxury"] .flash-strip-cards { gap: 20px; }
      body[data-template="luxury"] .flash-strip-cards > * { flex: 0 0 calc((100% - 2.3 * 20px) / 3.3); }
    }
    /* ═══ Square editorial flash card (pk 2026-06-01 REF 1) ═════════════════
       A SQUARE (aspect-ratio 1/1) card whose product image is the full-bleed
       background; everything overlays it: a white BRAND pill + a large white
       NAME title (top-left), and an inset white PRICE PILL BAR (bottom) with the
       sale price on the left + a dark/charcoal CIRCULAR cart button on the right.
       The card is the .img square itself — .meta is dropped. No per-card
       countdown (now section-level), no outlined CTA pill, no heart. Cites
       DESIGN.md §4.2 (image+name+price slots), §6.1 (overlay on the card
       surface, brand-mixed elevation), §4.5 (cart button states), §2.7 (white
       text on a scrim clears the contrast floor). */
    .flash-strip .flash-card {
      display: block; padding: 0;
      /* container so the overlaid title can size with cqi against the card. */
      container-type: inline-size;
      background: transparent; border: none;
      border-radius: var(--bk-radius-xl, 18px);
      box-shadow: 0 1px 2px -1px color-mix(in oklch, var(--accent, #1A1A1A) 12%, transparent);
      /* §4.4 — only transform + box-shadow animate (never transition-all). Calm
         hover lift only; no auto-motion (pk's just-shipped flatness stays). */
      transition: transform 220ms cubic-bezier(0.22,1,0.36,1),
                  box-shadow 220ms cubic-bezier(0.22,1,0.36,1);
    }
    .flash-strip .flash-card:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 14px -8px color-mix(in oklch, var(--accent, #1A1A1A) 28%, transparent);
    }
    /* The square cover — full-bleed product photo, everything overlays it. */
    .flash-strip .flash-card .img {
      position: relative;
      aspect-ratio: 1 / 1;
      width: 100%; height: auto; inset: auto;
      background: #EFEFEF;
      border-radius: var(--bk-radius-xl, 18px);
      overflow: hidden; padding: 0; box-sizing: border-box;
    }
    .flash-strip .flash-card .img img {
      width: 100%; height: 100%; object-fit: cover; display: block;
      transition: transform 500ms cubic-bezier(0.22,1,0.36,1);
    }
    .flash-strip .flash-card:hover .img img { transform: scale(1.04); }
    /* Top scrim so the white brand pill text + title read on any photo (§2.7);
       a softer bottom scrim seats the white price pill. Both clip to the radius
       (inside .img overflow:hidden). z below the overlay content. */
    .flash-strip .flash-card .img::before {
      content: ""; position: absolute; inset: 0 0 auto 0; height: 58%;
      background: linear-gradient(to bottom, rgba(0,0,0,0.42), rgba(0,0,0,0));
      z-index: 1; pointer-events: none;
    }
    .flash-strip .flash-card .img::after {
      content: ""; position: absolute; inset: auto 0 0 0; height: 38%;
      background: linear-gradient(to top, rgba(0,0,0,0.30), rgba(0,0,0,0));
      z-index: 1; pointer-events: none;
    }
    /* Top overlay — brand pill + name title (top-left). */
    .flash-strip .flash-card .flash-card-top {
      position: absolute; top: 14px; left: 14px; right: 14px;
      z-index: 2; pointer-events: none;
      display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
    }
    /* White rounded BRAND pill (brand → category fallback). */
    .flash-strip .flash-card .flash-card-brand {
      display: inline-block; max-width: 100%;
      padding: 4px 11px;
      /* pk 2026-06-01 — frosted: semi-transparent white so the product shows
         through; backdrop-blur keeps the dark label legible. */
      background: rgba(255,255,255,0.5);
      -webkit-backdrop-filter: blur(8px) saturate(120%);
      backdrop-filter: blur(8px) saturate(120%);
      color: #1A1A1A;
      border-radius: var(--bk-radius-pill, 999px);
      font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
      line-height: 1.2; white-space: nowrap;
      overflow: hidden; text-overflow: ellipsis;
      box-shadow: 0 1px 4px rgba(0,0,0,0.18);
    }
    /* Large white overlaid NAME title (1-2 lines, soft text-shadow). */
    .flash-strip .flash-card .flash-card-title {
      margin: 0; color: #fff; font-weight: 800;
      font-size: clamp(18px, 5.2cqi, 24px); line-height: 1.18;
      letter-spacing: -0.02em;
      display: -webkit-box; -webkit-box-orient: vertical;
      -webkit-line-clamp: 2; overflow: hidden;
      text-shadow: 0 1px 6px rgba(0,0,0,0.5), 0 0 2px rgba(0,0,0,0.4);
    }
    body[data-template="luxury"] .flash-strip .flash-card .flash-card-title {
      font-family: var(--display-font); font-weight: 600; letter-spacing: -0.01em;
    }
    /* Bottom inset white PRICE PILL bar — price (left) + dark cart circle (right). */
    .flash-strip .flash-card .flash-card-bar {
      position: absolute; left: 12px; right: 12px; bottom: 12px;
      z-index: 2;
      display: flex; align-items: center; justify-content: space-between;
      gap: 10px;
      padding: 6px 6px 6px 16px;
      /* pk 2026-06-01 — frosted: semi-transparent white price bar so the product
         shows through; backdrop-blur keeps the price + cart legible. */
      background: rgba(255,255,255,0.5);
      -webkit-backdrop-filter: blur(8px) saturate(120%);
      backdrop-filter: blur(8px) saturate(120%);
      border-radius: var(--bk-radius-pill, 999px);
      box-shadow: 0 4px 14px -6px rgba(0,0,0,0.35);
    }
    /* pk 2026-06-01 — VERTICAL price stack (was a long horizontal row that
       over-stretched the pill). Markup order is struck-first then sale, so
       column-reverse puts the BIG red sale price on TOP and the small muted
       struck "was" price BELOW. Tight line-height keeps the 2-line stack
       compact; left-aligned so it balances against the cart circle on the
       right of the bar. */
    .flash-strip .flash-card .flash-card-bar .price {
      min-width: 0; flex: 1 1 auto;
      display: flex; flex-direction: column-reverse;
      align-items: flex-start; gap: 0;
      font-weight: 800; color: #1A1A1A;
      line-height: 1.05; white-space: nowrap;
      overflow: hidden; text-overflow: ellipsis;
    }
    /* Struck "was" price — smaller + muted, sits BELOW the promo. */
    .flash-strip .flash-card .flash-card-bar .price .flash-strike,
    .flash-strip .flash-card .flash-card-bar .price .promo-strike {
      font-size: 11px; color: var(--text-mute, #9CA3AF);
      text-decoration: line-through; font-weight: 500;
      line-height: 1.1;
    }
    /* Promo (sale) price — BIGGER + red, sits ON TOP. cqi-clamped against the
       card width (.flash-card is container-type:inline-size) so it reads big on
       roomy cards but scales down on the narrow mobile square instead of
       overflowing/clipping the pill. */
    .flash-strip .flash-card .flash-card-bar .price .flash-price,
    .flash-strip .flash-card .flash-card-bar .price .promo-price {
      color: #DC2626; font-weight: 800;
      font-size: clamp(15px, 9.5cqi, 20px);
      line-height: 1.05;
    }
    /* pk 2026-06-01 — on MOBILE the flash card hides the product NAME (both
       read too big on the small square card); the card keeps the brand pill +
       image + the price stack (big red promo over small struck) + cart.
       (The struck-price mobile hide from 224fcaf is REVERTED — the struck
       price now shows again, small + below the promo, per pk.) */
    @media (max-width: 767px) {
      .flash-strip .flash-card .flash-card-title { display: none; }
    }
    /* Dark/charcoal CIRCULAR cart button (white glyph), ≥44px tap target. */
    .flash-strip .flash-card .flash-card-cart {
      flex: 0 0 auto;
      display: inline-flex; align-items: center; justify-content: center;
      width: 44px; height: 44px; padding: 0;
      background: #1A1A1A; color: #fff;
      border: none; border-radius: 50%;
      cursor: pointer;
      transition: background 180ms ease, transform 120ms ease,
                  box-shadow 180ms ease;
    }
    .flash-strip .flash-card .flash-card-cart .bk-icon {
      width: 19px; height: 19px; color: #fff;
    }
    .flash-strip .flash-card .flash-card-cart:hover {
      background: #000;
      box-shadow: 0 3px 10px -3px rgba(0,0,0,0.5);
    }
    .flash-strip .flash-card .flash-card-cart:active { transform: scale(0.94); }
    .flash-strip .flash-card .flash-card-cart:focus-visible {
      outline: 2px solid var(--accent, #1A1A1A); outline-offset: 2px;
    }
    /* The flash card has NO wishlist heart / quick-add / brand pill row / floating
       actions — guard against stray boot-race fallback markup. */
    .flash-strip .flash-card .prod-heart,
    .flash-strip .flash-card .prod-pill,
    .flash-strip .flash-card .quickadd,
    .flash-strip .flash-card .prod-actions,
    .flash-strip .flash-card .meta { display: none; }
    @media (max-width: 720px) {
      .flash-strip-cards,
      body[data-template="luxury"] .flash-strip-cards { gap: 14px; }
      .flash-strip .flash-card .flash-card-title { font-size: clamp(17px, 5.4vw, 22px); }
    }

    .prod-card .flash-countdown {
      position: absolute; top: 8px; right: 8px;
      z-index: 2;
    }
    .prod-card { position: relative; }
    .pd-price-stack .flash-countdown { font-size: 12px; padding: 3px 10px; }
    /* PDP per-variant flash row chrome (Phase 3) — when variant picker exists */
    .pd-variant-row.flash-on .pd-variant-price-base {
      color: var(--text-mute); text-decoration: line-through; font-size: 12px;
    }
    .pd-variant-row.flash-on .pd-variant-price-sale {
      color: #DC2626; font-weight: 700;
    }
    .cart-line .flash-saved {
      /* Wave H-6c (F-SHOP-012) — token-driven success color per §3.6.5. */
      color: var(--bk-success); font-size: 11px; margin-top: 2px;
    }

    /* ── Phase 3: Coupon card on checkout ───────────────────────────── */
    .co-coupon-card { /* inherits co-card box; nothing extra needed */ }
    .co-coupon-row {
      display: flex; gap: 8px; align-items: stretch;
    }
    .co-coupon-input {
      /* B3 — min-width:0 lets this flex input shrink below an <input>'s
         intrinsic width so the coupon row fits a phone (the Apply button
         is fixed-width; without this the row overflowed ~34px @ 360). */
      flex: 1; min-width: 0; padding: 10px 12px;
      border: 1px solid var(--border); border-radius: 8px;
      font-family: ui-monospace, 'SF Mono', Menlo, monospace;
      font-size: 14px; letter-spacing: 0.05em;
      text-transform: uppercase;
      background: #fff; color: var(--text);
    }
    .co-coupon-input:focus {
      outline: none; border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(124,58,237,0.12);
    }
    .co-coupon-apply {
      min-width: 110px; padding: 10px 16px;
      background: var(--accent); color: #fff;
      border: none; border-radius: 8px;
      font-weight: 600; font-size: 14px;
      cursor: pointer; transition: opacity 120ms ease;
    }
    .co-coupon-apply:hover:not(:disabled) { opacity: 0.9; }
    .co-coupon-apply:disabled { opacity: 0.5; cursor: not-allowed; }
    .co-coupon-applied {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 14px;
      background: rgba(124,58,237,0.08);
      border: 1px solid rgba(124,58,237,0.25);
      border-radius: 8px;
      font-size: 14px; color: var(--text);
    }
    .co-coupon-applied .code {
      font-family: ui-monospace, 'SF Mono', Menlo, monospace;
      font-weight: 700; color: var(--accent);
    }
    .co-coupon-applied .label { flex: 1; color: var(--text-mute); font-size: 13px; }
    .co-coupon-remove {
      background: none; border: none; cursor: pointer;
      color: var(--text-mute); font-size: 18px; line-height: 1;
      padding: 4px 6px; border-radius: 4px;
    }
    .co-coupon-remove:hover { background: rgba(0,0,0,0.05); color: var(--text); }
    .co-coupon-public {
      margin-top: 14px; border-top: 1px dashed var(--border); padding-top: 12px;
    }
    .co-coupon-public-toggle {
      background: none; border: none; cursor: pointer;
      font-size: 13px; color: var(--text-mute); padding: 4px 0;
      font-family: inherit;
    }
    .co-coupon-public-toggle:hover { color: var(--accent); }
    .co-coupon-public-list { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }
    .co-coupon-public-list[hidden] { display: none; }
    .co-coupon-chip {
      display: inline-flex; align-items: center; gap: 6px;
      padding: 6px 10px; border-radius: 999px;
      background: #fff; border: 1px solid var(--border);
      font-family: ui-monospace, 'SF Mono', Menlo, monospace;
      font-size: 12px; font-weight: 600; color: var(--accent);
      cursor: pointer; transition: background 120ms ease, border-color 120ms ease;
    }
    .co-coupon-chip:hover { background: rgba(124,58,237,0.06); border-color: var(--accent); }
    .co-coupon-chip .info {
      display: inline-flex; align-items: center; justify-content: center;
      width: 14px; height: 14px; border-radius: 50%;
      background: rgba(124,58,237,0.15); color: var(--accent);
      font-size: 10px; font-weight: 700; font-family: var(--body-font);
    }
    .co-coupon-chip[title] { /* native title attribute provides hover terms */ }
    /* Wave H-6c (F-SHOP-012) — token-driven success color on discount /
       voucher value spans (the U+2212-prefixed amounts) per §3.6.5. */
    .cart-row.discount span:last-child { color: var(--bk-success); }

    /* ── Phase 3: Toast (cart/checkout-level) ───────────────────────── */
    .shop-toast {
      position: fixed; left: 50%; bottom: 80px;
      transform: translateX(-50%);
      background: #1F2937; color: #fff;
      padding: 12px 18px; border-radius: 8px;
      font-size: 14px; max-width: min(90vw, 480px);
      box-shadow: 0 8px 24px rgba(0,0,0,0.2);
      opacity: 0; pointer-events: none;
      transition: opacity 180ms ease, transform 180ms ease;
      z-index: 10000; text-align: center;
    }
    .shop-toast.show { opacity: 1; transform: translateX(-50%) translateY(-4px); }
    .shop-toast.error { background: #B91C1C; }
    .shop-toast.success { background: #047857; }

    /* ── Phase 4: vouchers (reward-card/stamps CSS removed 2026-06-01) ── */
    .filter-strip {
      display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 20px 0;
    }
    .filter-chip {
      display: inline-flex; align-items: center; gap: 6px;
      padding: 7px 14px; border-radius: 999px;
      background: rgba(0,0,0,0.04); color: var(--text);
      border: 1px solid transparent; cursor: pointer;
      font-size: 14px; font-weight: 500; line-height: 1;
      transition: background 120ms ease, border-color 120ms ease;
    }
    .filter-chip:hover  { background: rgba(0,0,0,0.07); }
    .filter-chip.active {
      background: var(--accent); color: #fff;
      border-color: var(--accent);
    }
    .filter-chip .ic { font-size: 15px; }

    .prod-card.voucher-tile { position: relative; }
    .voucher-badge {
      position: absolute; top: 12px; left: 12px; z-index: 2;
      display: inline-flex; align-items: center; gap: 4px;
      padding: 4px 10px; border-radius: 999px;
      background: #FBBF24; color: #1F2937;
      font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
    }
    .voucher-stock-low {
      position: absolute; top: 12px; right: 12px; z-index: 2;
      padding: 3px 8px; border-radius: 4px;
      background: #DC2626; color: #fff;
      font-size: 11px; font-weight: 700;
    }

    /* Voucher detail page — mirrors PDP basic layout */
    #page-voucher { padding: 48px 0; }
    #page-voucher .voucher-detail {
      display: grid; grid-template-columns: 1fr; gap: 32px;
    }
    @media (min-width: 760px) {
      #page-voucher .voucher-detail { grid-template-columns: 1fr 1fr; gap: 56px; }
    }
    #page-voucher .voucher-img-wrap {
      position: relative;
      background: rgba(0,0,0,0.04);
      border-radius: 16px; overflow: hidden;
      aspect-ratio: 1 / 1;
    }
    #page-voucher .voucher-img-wrap img {
      width: 100%; height: 100%; object-fit: contain;
    }
    #page-voucher .voucher-img-wrap .no-img {
      position: absolute; inset: 0;
      display: flex; align-items: center; justify-content: center;
      font-size: 64px; opacity: 0.4;
    }
    #page-voucher .voucher-info h1 {
      font-family: var(--display-font);
      font-size: clamp(24px, 3vw, 32px);
      font-weight: 700; margin: 0 0 8px 0; letter-spacing: -0.01em;
    }
    #page-voucher .voucher-price {
      font-size: 28px; font-weight: 700; color: var(--accent);
      margin: 16px 0;
      /* Wave H-6b (F-SHOP-006) — tabular-nums per DESIGN.md §3.6.3. */
      font-variant-numeric: tabular-nums;
    }
    #page-voucher .voucher-validity {
      display: inline-block; padding: 6px 12px; border-radius: 999px;
      background: rgba(124,58,237,0.08); color: var(--accent);
      font-size: 13px; font-weight: 500; margin-bottom: 16px;
    }
    #page-voucher .voucher-receive {
      margin: 24px 0; padding: 16px 18px;
      background: rgba(251,191,36,0.10);
      border: 1px solid rgba(251,191,36,0.30);
      border-radius: 12px;
    }
    #page-voucher .voucher-receive h2, #page-voucher .voucher-receive h3 {
      margin: 0 0 6px 0; font-size: 14px; font-weight: 700;
      letter-spacing: 0.02em; text-transform: uppercase;
      color: #92400E;
    }
    #page-voucher .voucher-receive p {
      margin: 0; font-size: 15px; line-height: 1.5; color: var(--text);
    }
    #page-voucher .voucher-terms {
      margin-top: 24px; font-size: 14px; line-height: 1.6;
      color: var(--text-mute);
    }
    #page-voucher .voucher-stock-note {
      margin-top: 12px; font-size: 13px; color: var(--text-mute);
    }
    #page-voucher .voucher-stock-note.low { color: #DC2626; font-weight: 600; }

    /* Voucher redemption input card on checkout (sister to coupon) */
    .co-voucher-card { /* inherits co-card box */ }

    /* Cart voucher line: 🎁 prefix + faint gold accent */
    .cart-line.voucher-line h4::before {
      content: '🎁 '; margin-right: 4px;
    }
    .cart-line.voucher-line .voucher-stock-warn {
      color: #DC2626; font-size: 13px; font-weight: 600; margin-top: 4px;
    }
    .cart-line.voucher-sold-out {
      border-left: 3px solid #DC2626;
      padding-left: 12px;
    }

    /* Purchased voucher codes (confirmation page) */
    .conf-vouchers {
      margin: 24px 0; padding: 16px 18px;
      background: rgba(251,191,36,0.08);
      border: 1px solid rgba(251,191,36,0.25);
      border-radius: 12px;
    }
    .conf-vouchers h2, .conf-vouchers h3 {
      margin: 0 0 12px 0; font-size: 15px; font-weight: 700;
      color: #92400E;
    }
    .voucher-code-row {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 12px; margin: 8px 0;
      background: #fff; border: 1px solid rgba(0,0,0,0.10);
      border-radius: 8px;
    }
    .voucher-code-chip {
      flex: 0 0 auto;
      font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
      font-size: 15px; font-weight: 700; letter-spacing: 0.05em;
      padding: 4px 10px; border-radius: 4px;
      background: #FEF3C7; color: #78350F;
      cursor: pointer; user-select: all;
      transition: background 120ms ease;
    }
    .voucher-code-chip:hover { background: #FDE68A; }
    .voucher-code-meta {
      flex: 1 1 auto; font-size: 13px; color: var(--text-mute);
    }
    .voucher-code-copy {
      background: transparent; border: 1px solid rgba(0,0,0,0.15);
      border-radius: 6px; padding: 4px 10px; cursor: pointer;
      font-size: 13px; color: var(--text);
    }
    .voucher-code-copy:hover { background: rgba(0,0,0,0.04); }

    /* removed (pk 2026-06-01): earned-stamps (.stamps-*) + loyalty page
       (#page-loyalty .loyalty-*) CSS — loyalty/points frontend unsurfaced
       (markup + JS removed from shop.html). Backend endpoints stay. */

    /* ── Phase 4.5: pawn loan customer pages ──────────────────────── */
    #page-pawn-check, #page-pawn-view, #page-pawn-error { padding: 48px 0; }
    .pawn-wrap { max-width: 720px; margin: 0 auto; }
    .pawn-wrap h1 {
      font-family: var(--display-font);
      font-size: clamp(24px, 3vw, 32px);
      font-weight: 700; margin: 0 0 8px 0; letter-spacing: -0.01em;
    }
    .pawn-blurb { color: var(--text-mute); margin: 0 0 28px 0; }
    .pawn-form .ec-field { margin-bottom: 16px; }
    .pawn-form .ec-field label {
      display: block; font-size: 13px; font-weight: 600;
      margin-bottom: 6px; color: var(--text);
    }
    .pawn-form input {
      width: 100%; padding: 12px 14px;
      border: 1px solid rgba(0,0,0,0.15); border-radius: 8px;
      font-size: 15px; font-family: inherit; background: #fff;
    }
    .pawn-form input:focus { outline: none; border-color: var(--accent); }
    .pawn-form .explainer {
      margin-top: 12px; font-size: 13px; color: var(--text-mute);
    }
    .pawn-sent {
      padding: 24px 22px; border-radius: 12px; text-align: center;
      background: rgba(16,185,129,0.06);
      border: 1px solid rgba(16,185,129,0.30);
    }
    .pawn-sent .ic { font-size: 32px; display: block; margin-bottom: 8px; color: #059669; }
    .pawn-sent .body { color: #064E3B; line-height: 1.5; margin: 0 0 16px 0; }
    .pawn-sent .resend {
      background: transparent; border: none; cursor: pointer;
      color: var(--accent); font-size: 14px; font-weight: 600;
      padding: 4px 8px; text-decoration: underline;
    }

    /* Status badges */
    .pawn-status-badge {
      display: inline-block; padding: 6px 14px; border-radius: 999px;
      font-size: 13px; font-weight: 700; letter-spacing: 0.02em;
    }
    .pawn-status-badge.active     { background: rgba(16,185,129,0.12); color: #047857; }
    .pawn-status-badge.paid_off   { background: #059669; color: #fff; }
    .pawn-status-badge.overdue    { background: rgba(245,158,11,0.15); color: #92400E; }
    .pawn-status-badge.forfeited  { background: #DC2626; color: #fff; }

    .pawn-forfeited-explainer {
      margin-top: 10px; padding: 12px 14px;
      background: rgba(220,38,38,0.06);
      border-left: 3px solid #DC2626;
      border-radius: 4px;
      font-size: 14px; color: #7F1D1D; line-height: 1.5;
    }

    /* Photo carousel — simple horizontal scroll */
    .pawn-photos {
      display: flex; gap: 12px; overflow-x: auto;
      padding: 8px 0; margin: 0 0 24px 0;
      scroll-snap-type: x mandatory;
      -webkit-overflow-scrolling: touch;
    }
    .pawn-photos .photo {
      flex: 0 0 auto;
      width: 240px; height: 180px;
      border-radius: 12px; overflow: hidden;
      background: rgba(0,0,0,0.04);
      scroll-snap-align: start;
    }
    .pawn-photos .photo img {
      width: 100%; height: 100%; object-fit: cover; display: block;
    }
    .pawn-photos .photo.placeholder {
      display: flex; align-items: center; justify-content: center;
      font-size: 48px; opacity: 0.4;
    }

    /* Loan summary grid */
    .pawn-summary {
      display: grid; grid-template-columns: 1fr;
      gap: 8px 24px; margin: 24px 0;
      padding: 18px 20px;
      background: rgba(0,0,0,0.02);
      border: 1px solid rgba(0,0,0,0.08);
      border-radius: 12px;
    }
    @media (min-width: 560px) {
      .pawn-summary { grid-template-columns: 1fr 1fr; }
    }
    .pawn-summary-row {
      display: flex; justify-content: space-between; align-items: baseline;
      padding: 4px 0; font-size: 14px;
    }
    .pawn-summary-row .key { color: var(--text-mute); }
    .pawn-summary-row .val { font-weight: 600; color: var(--text); }
    .pawn-summary hr {
      grid-column: 1 / -1;
      border: 0; border-top: 1px solid rgba(0,0,0,0.08);
      margin: 4px 0;
    }
    .pawn-summary .pawn-summary-row.total .val { font-size: 16px; color: var(--accent); }

    /* Progress bar */
    .pawn-progress { margin: 16px 0 24px 0; }
    .pawn-progress-bar {
      width: 100%; height: 10px; border-radius: 999px;
      background: rgba(0,0,0,0.06); overflow: hidden;
    }
    .pawn-progress-fill {
      height: 100%; width: 100%; background: var(--accent);
      /* Wave H7 (F-SHOP-011) — transform-based fill per DESIGN.md §7. */
      transform-origin: left; transform: scaleX(0);
      transition: transform 240ms ease;
    }
    .pawn-progress-fill.active     { background: #059669; }
    .pawn-progress-fill.paid_off   { background: #059669; }
    .pawn-progress-fill.overdue    { background: #F59E0B; }
    .pawn-progress-fill.forfeited  { background: #DC2626; }
    .pawn-progress-label {
      margin-top: 6px; font-size: 13px; color: var(--text-mute);
      text-align: right;
    }

    /* Next-due section */
    .pawn-next-due {
      margin: 24px 0; padding: 16px 18px;
      background: rgba(124,58,237,0.04);
      border: 1px solid rgba(124,58,237,0.20);
      border-radius: 12px;
    }
    .pawn-next-due h2, .pawn-next-due h3 {
      margin: 0 0 10px 0; font-size: 14px; font-weight: 700;
      text-transform: uppercase; letter-spacing: 0.04em;
      color: var(--text-mute);
    }
    .pawn-next-due .row {
      display: flex; align-items: center; gap: 10px;
      margin: 6px 0; font-size: 15px;
    }

    /* Schedule table */
    .pawn-schedule { margin: 24px 0; }
    .pawn-schedule h2, .pawn-schedule h3 {
      font-size: 15px; font-weight: 700; margin: 0 0 12px 0;
    }
    .pawn-schedule-list {
      border: 1px solid rgba(0,0,0,0.08);
      border-radius: 12px; overflow: hidden;
    }
    .pawn-installment {
      display: grid;
      grid-template-columns: 1fr 1.2fr 1fr 1fr;
      gap: 8px;
      padding: 12px 14px; font-size: 14px;
      border-bottom: 1px solid rgba(0,0,0,0.06);
    }
    .pawn-installment:last-child { border-bottom: 0; }
    .pawn-installment.paid    { background: rgba(16,185,129,0.04); }
    .pawn-installment.overdue { background: rgba(245,158,11,0.06); }
    .pawn-installment .label  { font-weight: 600; color: var(--text); }
    .pawn-installment .date,
    .pawn-installment .amount,
    .pawn-installment .marker { color: var(--text-mute); }
    .pawn-installment .marker.paid    { color: #047857; font-weight: 700; }
    .pawn-installment .marker.overdue { color: #B45309; font-weight: 700; }
    .pawn-installment .marker.grace   { color: var(--text-mute); font-style: italic; }

    /* Shop contact CTA */
    .pawn-contact {
      margin: 32px 0 0 0; padding: 18px 20px;
      background: rgba(0,0,0,0.03);
      border-radius: 12px; text-align: center;
    }
    .pawn-contact p { margin: 0 0 12px 0; color: var(--text-mute); font-size: 14px; }
    .pawn-contact .btn-primary { display: inline-flex; }
    .pawn-contact .phone-copy {
      display: inline-flex; align-items: center; gap: 8px;
      font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
      font-size: 16px; font-weight: 700; color: var(--text);
      background: #fff; border: 1px solid rgba(0,0,0,0.10);
      border-radius: 8px; padding: 8px 14px; cursor: pointer;
    }

    /* Token-error / no-loan page */
    .pawn-error-card {
      text-align: center; padding: 32px 24px;
      background: rgba(220,38,38,0.05);
      border: 1px solid rgba(220,38,38,0.25);
      border-radius: 12px;
    }
    .pawn-error-card .ic {
      font-size: 36px; color: #DC2626; display: block; margin-bottom: 12px;
    }
    .pawn-error-card h2 {
      margin: 0 0 8px 0; font-size: 18px; font-weight: 700;
    }
    .pawn-error-card p { margin: 0 0 16px 0; color: var(--text-mute); }

    .pawn-meta {
      font-size: 13px; color: var(--text-mute); margin-bottom: 16px;
    }

    /* ── Phase 6: content CMS — banners + articles ────────────────── */
    /* home_hero — large banner above the product grid */
    .p6-hero {
      position: relative; display: block;
      border-radius: 16px; overflow: hidden;
      min-height: 220px;
      background: rgba(0,0,0,0.04) center/cover no-repeat;
      text-decoration: none; color: #fff;
    }
    @media (min-width: 768px) { .p6-hero { min-height: 320px; } }
    /* pk 2026-06-01 — promotion banner crossfade: all active home_strip banners
       stack in one grid cell and fade between each other every 4s (JS toggles
       .is-active on #p6-hero-host .p6-hero-slide). Single banner → static. */
    #p6-hero-host { display: grid; }
    #p6-hero-host .p6-hero-slide {
      grid-area: 1 / 1;
      opacity: 0; transition: opacity 600ms ease;
      pointer-events: none;
    }
    #p6-hero-host .p6-hero-slide.is-active { opacity: 1; pointer-events: auto; }
    @media (prefers-reduced-motion: reduce) {
      #p6-hero-host .p6-hero-slide { transition: none; }
    }
    .p6-hero .p6-hero-overlay {
      position: absolute; inset: 0;
      display: flex; flex-direction: column; justify-content: flex-end;
      padding: 28px 32px;
      background: linear-gradient(to top, rgba(0,0,0,0.55), rgba(0,0,0,0) 60%);
    }
    .p6-hero .p6-hero-headline {
      font-family: var(--display-font);
      font-size: clamp(22px, 3vw, 34px); font-weight: 700;
      margin: 0 0 6px 0; letter-spacing: -0.01em;
    }
    .p6-hero .p6-hero-subtext {
      font-size: 15px; margin: 0; opacity: 0.92;
    }

    /* home_strip — row of up to 4 small banner tiles (legacy default) */
    .p6-strip {
      display: grid; grid-template-columns: 1fr; gap: 14px;
    }
    @media (min-width: 560px) { .p6-strip { grid-template-columns: repeat(2, 1fr); } }
    @media (min-width: 900px) { .p6-strip { grid-template-columns: repeat(4, 1fr); } }
    .p6-strip-tile {
      position: relative; display: block;
      border-radius: 12px; overflow: hidden;
      aspect-ratio: 3 / 2;
      background: rgba(0,0,0,0.04) center/cover no-repeat;
      text-decoration: none; color: #fff;
    }
    .p6-strip-tile .p6-strip-overlay {
      position: absolute; inset: 0;
      display: flex; align-items: flex-end;
      padding: 12px 14px;
      background: linear-gradient(to top, rgba(0,0,0,0.5), rgba(0,0,0,0) 65%);
      font-size: 14px; font-weight: 600;
    }

    /* ── pk 2026-05-31 — under-hero ARTICLE BENTO (.p6-article-bento) ──
       The ConWerse "Sneakers" reference: 4 article cards in an asymmetric grid
       — a LEAD card on the left spanning both rows, two stacked cards on the
       right, and a WIDE card across the full width below. Cards are the shared
       .article-card primitive. Desktop AND mobile (mobile = single column,
       lead first). Cite DESIGN.md §5.2 (bento) + §4.2 (card). Overrides the
       base .p6-strip auto-fit columns via explicit grid-template. */
    /* pk 2026-05-31: SAME bento on mobile as desktop (was a single column
       < 768px). grid-template-areas now apply at ALL widths; only sizing
       scales down on phones (smaller gap + cover min-heights). */
    .p6-strip.p6-article-bento {
      display: grid; gap: 12px;
      grid-template-columns: 1.3fr 1fr;
      grid-auto-rows: 1fr;
      /* lead | top-right
         lead | bottom-right
         wide | wide            */
      grid-template-areas:
        "lead topright"
        "lead bottomright"
        "wide wide";
    }
    .p6-strip.p6-article-bento .article-card { height: 100%; grid-area: topright; }  /* card #2 default */
    .p6-strip.p6-article-bento .article-card.bento-lead { grid-area: lead; }
    .p6-strip.p6-article-bento .article-card.bento-wide { grid-area: wide; }
    .p6-strip.p6-article-bento .article-card:nth-child(3) { grid-area: bottomright; }
    .p6-strip.p6-article-bento .article-card.bento-lead .cover { height: 100%; aspect-ratio: auto; min-height: 200px; }
    .p6-strip.p6-article-bento .article-card.bento-wide { display: grid; grid-template-columns: 1.4fr 1fr; }
    .p6-strip.p6-article-bento .article-card.bento-wide .cover { aspect-ratio: auto; height: 100%; min-height: 120px; }
    @media (min-width: 768px) {
      .p6-strip.p6-article-bento { gap: 16px; }
      .p6-strip.p6-article-bento .article-card.bento-lead .cover { min-height: 280px; }
      .p6-strip.p6-article-bento .article-card.bento-wide .cover { min-height: 180px; }
    }
    /* Very narrow phones (≤380px): keep the bento, just hide the excerpt in the
       smaller right-column cards so text doesn't overflow. */
    @media (max-width: 380px) {
      .p6-strip.p6-article-bento .article-card .body { padding: 10px 12px; }
      .p6-strip.p6-article-bento .article-card:not(.bento-lead) .excerpt { display: none; }
    }

    /* ── pk 2026-06-01 (item 4) — COUNT-ADAPTIVE article strip ──────────
       The fixed 3-row / 2-col bento above is sized for exactly 4 tiles. With
       fewer tiles the named areas (topright/bottomright/wide) stay empty but
       still occupy rows → a large empty band below the cards and a gap before
       the next section (#section-brands). renderBanners() stamps the host with
       data-count="N" (actual rendered tile count, 1–4); these rules pick a grid
       that fits EXACTLY N cards — no empty reserved cells, no trailing
       whitespace, so #section-brands sits directly beneath. data-count="4" =
       the default bento above (no override). Auto-flow collapse via explicit
       per-count grid-template (DESIGN.md §5.2 bento, §8.5 intrinsic responsive).
       Scope is .p6-strip.p6-article-bento only — the /articles SPAGE grid
       (.article-grid#articles-grid) is a different selector and is untouched. */

    /* 1 article — a single full-width card (NOT a quarter-width bento tile). */
    .p6-strip.p6-article-bento[data-count="1"] {
      grid-template-columns: 1fr;
      grid-auto-rows: auto;
      grid-template-areas: "topright";   /* lone card uses the default grid-area */
    }
    .p6-strip.p6-article-bento[data-count="1"] .article-card { grid-area: topright; }

    /* 2 articles — two equal cards side-by-side in a single row (no empty 2nd
       row, no wide row). Neutralise the lead's tall cover so both match. */
    .p6-strip.p6-article-bento[data-count="2"] {
      grid-template-columns: 1fr 1fr;
      grid-auto-rows: 1fr;
      grid-template-areas: "lead topright";
    }
    .p6-strip.p6-article-bento[data-count="2"] .article-card.bento-lead .cover {
      min-height: 0;
    }

    /* 3 articles — lead (tall, left, spans both rows) + two stacked on the right
       (no full-width wide row). This is the 4-tile bento minus the wide row. */
    .p6-strip.p6-article-bento[data-count="3"] {
      grid-template-columns: 1.3fr 1fr;
      grid-auto-rows: 1fr;
      grid-template-areas:
        "lead topright"
        "lead bottomright";
    }

    @media (min-width: 768px) {
      /* keep the desktop gap consistent with the 4-tile bento above */
      .p6-strip.p6-article-bento[data-count="2"],
      .p6-strip.p6-article-bento[data-count="3"] { gap: 16px; }
    }

    /* ── ConWerse r4 (2026-05-31) — 3-up category MOSAIC (.p6-mosaic) ──
       The PUMA/NIKE/STYLE-STACK cards in the ref: a lead tile + two stacked
       tiles, each with an overlay headline + a pill CTA. Reuses home_strip
       banner data. Cite DESIGN.md §5.2 (bento) + §4.5 (pill button). */
    .p6-strip.p6-mosaic {
      display: grid; gap: 16px;
      grid-template-columns: 1fr;
    }
    @media (min-width: 768px) {
      .p6-strip.p6-mosaic {
        grid-template-columns: 1.4fr 1fr;
        grid-auto-rows: 1fr;
      }
      /* Lead tile spans both rows on the left; the other two stack on the right. */
      .p6-strip.p6-mosaic .p6-strip-lead { grid-row: 1 / span 2; }
    }
    .p6-strip.p6-mosaic .p6-strip-tile {
      aspect-ratio: 4 / 3; border-radius: 18px; min-height: 200px;
    }
    @media (min-width: 768px) {
      .p6-strip.p6-mosaic .p6-strip-lead { aspect-ratio: auto; }
    }
    .p6-strip.p6-mosaic .p6-strip-overlay {
      flex-direction: column; align-items: flex-start; justify-content: flex-end;
      gap: 14px; padding: 22px 24px;
      background: linear-gradient(to top, rgba(0,0,0,0.58), rgba(0,0,0,0.05) 70%);
    }
    .p6-strip.p6-mosaic .p6-strip-title {
      font-family: var(--display-font);
      font-size: clamp(20px, 2.4vw, 30px); font-weight: 700;
      line-height: 1.15; letter-spacing: -0.01em;
      text-shadow: 0 2px 10px rgba(0,0,0,0.4); max-width: 16ch;
    }
    .p6-strip.p6-mosaic .p6-strip-pill {
      display: inline-flex; align-items: center;
      background: #fff; color: var(--text);
      font-size: 13px; font-weight: 600;
      padding: 10px 18px; border-radius: 999px;
      min-height: 40px; line-height: 1;
    }

    /* Articles list */
    #page-articles { padding: 48px 0; }
    #page-articles h1 {
      font-family: var(--display-font);
      font-size: clamp(26px, 4vw, 40px); font-weight: 700;
      margin: 0 0 24px 0; letter-spacing: -0.01em;
    }
    .article-grid {
      display: grid; grid-template-columns: 1fr; gap: 24px;
    }
    @media (min-width: 600px) { .article-grid { grid-template-columns: repeat(2, 1fr); } }
    @media (min-width: 960px) { .article-grid { grid-template-columns: repeat(3, 1fr); } }
    .article-card {
      display: flex; flex-direction: column;
      border: 1px solid rgba(0,0,0,0.08); border-radius: 14px;
      overflow: hidden; text-decoration: none; color: var(--text);
      transition: box-shadow 140ms ease, transform 140ms ease;
    }
    .article-card:hover {
      box-shadow: 0 8px 28px rgba(0,0,0,0.10);
      transform: translateY(-2px);
    }
    .article-card .cover {
      aspect-ratio: 16 / 9;
      background: rgba(0,0,0,0.05) center/cover no-repeat;
    }
    .article-card .cover.placeholder {
      display: flex; align-items: center; justify-content: center;
      font-size: 40px; opacity: 0.35;
    }
    .article-card .body { padding: 16px 18px; display: flex; flex-direction: column; gap: 6px; }
    .article-card .title {
      font-weight: 700; font-size: 17px; line-height: 1.35;
      letter-spacing: -0.01em;
    }
    .article-card .excerpt {
      font-size: 14px; color: var(--text-mute); line-height: 1.5;
    }
    .article-card .date {
      font-size: 12px; color: var(--text-mute); margin-top: 4px;
    }

    /* Article detail */
    #page-article { padding: 48px 0; }
    #page-article .article-detail { max-width: 720px; margin: 0 auto; }
    #page-article .article-cover {
      width: 100%; aspect-ratio: 16 / 9;
      border-radius: 16px; overflow: hidden; margin-bottom: 24px;
      background: rgba(0,0,0,0.05) center/cover no-repeat;
    }
    #page-article .article-title {
      font-family: var(--display-font);
      font-size: clamp(26px, 4vw, 40px); font-weight: 700;
      margin: 0 0 8px 0; letter-spacing: -0.01em; line-height: 1.2;
    }
    #page-article .article-date {
      font-size: 13px; color: var(--text-mute); margin-bottom: 24px;
    }
    /* Sanitized body — server allowlist is p/br/strong/em/ul/ol/li/a/h2/h3/blockquote/img */
    #page-article .article-body {
      font-size: 16px; line-height: 1.7; color: var(--text);
    }
    #page-article .article-body h2 {
      font-size: 22px; font-weight: 700; margin: 28px 0 10px 0;
      letter-spacing: -0.01em;
    }
    #page-article .article-body h3 {
      font-size: 18px; font-weight: 700; margin: 22px 0 8px 0;
    }
    #page-article .article-body p { margin: 0 0 16px 0; }
    #page-article .article-body ul,
    #page-article .article-body ol { margin: 0 0 16px 0; padding-left: 24px; }
    #page-article .article-body li { margin: 4px 0; }
    #page-article .article-body a { color: var(--accent); }
    #page-article .article-body img {
      max-width: 100%; height: auto; border-radius: 10px; margin: 8px 0;
    }
    /* pk 2026-06-03 — image size classes the article editor writes (img-sm/md/full);
       all bounded by the max-width:100% cap above so none overflow the 720px column. */
    #page-article .article-body img.img-sm { width: 40%; }
    #page-article .article-body img.img-md { width: 65%; }
    #page-article .article-body img.img-full { width: 100%; }
    #page-article .article-body blockquote {
      margin: 16px 0; padding: 8px 18px;
      border-left: 3px solid var(--accent);
      color: var(--text-mute); font-style: italic;
    }
    /* T4.3 — blog listing upgrade */
    .articles-search {
      width: 100%; max-width: 360px; font-family: inherit; font-size: 14px;
      padding: 10px 14px; border: 1px solid var(--border); border-radius: 999px;
      background: var(--bg); color: var(--text); outline: none; margin-bottom: 28px;
    }
    .articles-search:focus { border-color: var(--accent); }
    .article-featured {
      display: grid; grid-template-columns: 1fr; gap: 0;
      border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
      margin-bottom: 32px; background: #fff; cursor: pointer;
    }
    @media (min-width: 760px) { .article-featured { grid-template-columns: 1.25fr 1fr; } }
    .article-featured .cover { min-height: 210px; background: var(--bg-soft) center/cover no-repeat; }
    .article-featured .cover.placeholder { display: flex; align-items: center; justify-content: center; font-size: 40px; }
    .article-featured .body { padding: 28px; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
    .article-featured .tag {
      align-self: flex-start; font-size: 11px; font-weight: 700; letter-spacing: .06em;
      text-transform: uppercase; color: var(--accent); background: var(--bg-soft);
      padding: 4px 10px; border-radius: 6px;
    }
    .article-featured .title { font-family: var(--display-font); font-size: 24px; font-weight: 700; line-height: 1.25; }
    .article-featured .excerpt { font-size: 14px; color: var(--text-mute); line-height: 1.55; }
    .article-featured .meta { font-size: 12px; color: var(--text-mute); }
    .newsletter {
      margin-top: 44px; padding: 32px 24px; border-radius: 16px;
      background: var(--bg-soft); border: 1px solid var(--border); text-align: center;
    }
    .newsletter h2, .newsletter h3 { font-family: var(--display-font); font-size: 20px; margin: 0 0 6px; }
    .newsletter p { font-size: 13px; color: var(--text-mute); margin: 0 0 16px; }
    .newsletter form { display: flex; gap: 8px; max-width: 420px; margin: 0 auto; flex-wrap: wrap; }
    .newsletter input {
      flex: 1; min-width: 180px; font-family: inherit; font-size: 14px;
      padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px;
      background: #fff; color: var(--text); outline: none;
    }
    .newsletter input:focus { border-color: var(--accent); }
    /* T4.4 — article detail meta + share + related */
    .article-meta {
      display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
      font-size: 13px; color: var(--text-mute); margin-bottom: 22px;
    }
    .article-meta .dot { opacity: .5; }
    .article-share { display: flex; gap: 8px; flex-wrap: wrap; margin: 28px 0 8px; }
    .article-share a, .article-share button {
      display: inline-flex; align-items: center; gap: 6px;
      font-family: inherit; font-size: 12px; font-weight: 600;
      padding: 8px 14px; border-radius: 999px; cursor: pointer;
      border: 1px solid var(--border); background: #fff; color: var(--text);
    }
    .article-share a:hover, .article-share button:hover { border-color: var(--accent); }
    .article-related { max-width: 720px; margin: 44px auto 0; }
    .article-related h2, .article-related h3 { font-family: var(--display-font); font-size: 20px; margin: 0 0 16px; }
    .article-empty {
      padding: 48px 20px; text-align: center; color: var(--text-mute);
    }
    .article-empty .emoji { font-size: 40px; }

    /* ── A3 — customer account hub ──────────────────────────────────── */
    .acct-title {
      font-family: var(--display-font); font-size: clamp(26px, 4vw, 36px);
      font-weight: 700; margin: 0 0 20px 0; letter-spacing: -0.01em;
    }
    /* Webster 2026-06-01 (Rush "ฉัน" ref) — account-hub LANDING redesign:
       profile header → action-tile grid → settings list. The hub is a single
       vertical stack on every breakpoint (mobile-first; the ref is mobile),
       capped at a comfortable column width on desktop so it stays usable
       without stretching edge-to-edge. The legacy 3-bucket .acct-bucket /
       .acct-tab styles below are retained but no longer emitted by the markup
       (kept to avoid churn / unused-selector risk). */
    .acct-hub {
      display: flex; flex-direction: column; gap: 20px;
      margin-bottom: 24px;
    }
    @media (min-width: 900px) {
      .acct-hub { max-width: 560px; margin-left: auto; margin-right: auto; }
    }

    /* ── Webster 2026-06-01 — account SUB-PAGE navigation ────────────────
       pk: "every button go to the next page instead of showing below" +
       "remove the header and add back button — top left." Each tile / row
       opens its own full-screen view. The global site header (.nav) is
       dropped across the whole account flow (body.on-account) so the account
       stack reads as a self-contained screen, mirroring the PDP header-drop
       (DESIGN.md §1 atmosphere — the chrome is the frame, and here the back
       row IS the frame). The hub's .acct-back returns to the storefront;
       each sub-page's .acct-back returns to the hub. */
    body.on-account .nav { display: none; }

    /* Top-left back affordance. Extends the shared .pd-back text-link into a
       slightly stronger, pill-tappable control (it now owns the top edge in
       place of the header). 44px touch target per §8.2; explicit hover /
       active / focus-visible per CLAUDE.md (the base .pd-back lacked the
       latter two). Capped column width matches the hub on desktop so it
       lines up with the content stack. */
    .acct-back {
      align-self: flex-start;
      display: inline-flex; align-items: center; gap: 6px;
      min-height: 40px; padding: 8px 12px 8px 8px;
      margin: 0 0 4px -8px;          /* optical-align the arrow to the left edge */
      border-radius: 10px;
      color: var(--text-mute); font-size: 14px; font-weight: 600;
      text-decoration: none; cursor: pointer; background: transparent;
      transition: color var(--bk-dur-fast, 140ms) ease,
                  background-color var(--bk-dur-fast, 140ms) ease;
    }
    .acct-back::before { content: "←"; font-size: 16px; line-height: 1; }
    .acct-back:hover  { color: var(--accent); background: var(--bg-soft); }
    .acct-back:active { transform: translateY(1px); }
    .acct-back:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
      color: var(--accent);
    }
    body[data-template="luxury"] .acct-back { border-radius: 4px; letter-spacing: 0.01em; }

    /* Sub-page chrome — the back row + a Fraunces page title (§3.1 editorial
       display) + the existing #account-panel rendered below by the unchanged
       per-tab renderers. Single vertical stack capped to the hub width on
       desktop so a sub-page lands exactly where the hub content sat. */
    .acct-subpage {
      display: flex; flex-direction: column; gap: 14px;
    }
    /* The hub + sub-page toggle via the [hidden] attribute (set in
       _acctRenderActive). Their display:flex would otherwise override the UA
       [hidden]{display:none}, so re-assert it for both. */
    .acct-hub[hidden], .acct-subpage[hidden] { display: none !important; }
    @media (min-width: 900px) {
      .acct-subpage { max-width: 560px; margin-left: auto; margin-right: auto; }
    }
    .acct-subpage-title {
      font-family: var(--display-font);
      font-size: clamp(24px, 4.5vw, 32px); font-weight: 700;
      letter-spacing: -0.02em; line-height: 1.15; margin: 0;
      color: var(--text);
      overflow-wrap: anywhere;       /* §1 long-Thai-compound overflow guard */
    }
    body[data-template="luxury"] .acct-subpage-title { letter-spacing: 0.01em; }
    /* The panel sits directly under the title — drop the legacy top margin it
       carried when it lived below the hub. */
    .acct-subpage .acct-panel { margin-top: 2px; }
    /* Centre the add-address button row to the left (it leads the sub-page). */
    .acct-addr-add { justify-content: flex-start; }

    /* 1 · Profile header — elevated surface with an accent-tinted radial wash
       (DESIGN.md §2.6 accent · anti-generic layered tint, not a flat fill). */
    .acct-head {
      position: relative; display: flex; align-items: center; gap: 14px;
      padding: 20px 18px; border-radius: 18px;
      background:
        radial-gradient(120% 140% at 0% 0%,
          color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 60%),
        radial-gradient(120% 160% at 100% 0%,
          color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 55%),
        var(--bg-soft);
      border: 1px solid color-mix(in srgb, var(--accent) 16%, var(--border));
      box-shadow:
        0 1px 2px color-mix(in srgb, var(--accent) 10%, transparent),
        0 10px 24px -14px color-mix(in srgb, var(--accent) 28%, transparent);
    }
    body[data-template="luxury"] .acct-head { border-radius: 4px; }
    .acct-avatar {
      position: relative; flex-shrink: 0;
      width: 60px; height: 60px; border-radius: 50%;
      border: 2px solid color-mix(in srgb, var(--accent) 40%, #fff);
      background:
        linear-gradient(150deg,
          color-mix(in srgb, var(--accent) 88%, #000) 0%,
          var(--accent) 100%);
      display: flex; align-items: center; justify-content: center;
      cursor: pointer; padding: 0;
      box-shadow: 0 4px 14px -6px color-mix(in srgb, var(--accent) 55%, transparent);
      transition: transform 140ms cubic-bezier(.34,1.56,.64,1);
    }
    .acct-avatar:hover  { transform: scale(1.04); }
    .acct-avatar:active { transform: scale(0.97); }
    .acct-avatar:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 3px;
    }
    .acct-avatar-initial {
      font-family: var(--display-font);
      font-size: 24px; font-weight: 700; line-height: 1; color: #fff;
    }
    .acct-avatar-cam {
      position: absolute; right: -2px; bottom: -2px;
      width: 24px; height: 24px; border-radius: 50%;
      background: #fff; border: 1.5px solid color-mix(in srgb, var(--accent) 22%, var(--border));
      display: flex; align-items: center; justify-content: center;
      box-shadow: 0 2px 6px -2px rgba(15,23,42,0.35);
    }
    .acct-avatar-cam .bk-icon { width: 13px; height: 13px; color: var(--accent); }
    .acct-head-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
    .acct-head-phone {
      font-family: var(--display-font);
      font-size: 19px; font-weight: 700; letter-spacing: -0.01em;
      color: var(--text); line-height: 1.25;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .acct-head-add {
      align-self: flex-start;
      display: inline-flex; align-items: center; gap: 4px;
      background: none; border: none; padding: 3px 0; margin: 0;
      font: inherit; font-size: 13px; font-weight: 600;
      color: var(--accent); cursor: pointer; min-height: 24px;
      border-radius: 6px;
    }
    .acct-head-add .acct-chev { width: 14px; height: 14px; color: currentColor; opacity: 0.75; }
    .acct-head-add:hover  { text-decoration: underline; }
    .acct-head-add:active { opacity: 0.7; }
    .acct-head-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    .acct-head-gear {
      flex-shrink: 0; align-self: flex-start;
      width: 38px; height: 38px; border-radius: 12px;
      background: #fff; border: 1px solid var(--border);
      display: flex; align-items: center; justify-content: center; cursor: pointer;
      box-shadow: 0 2px 8px -4px rgba(15,23,42,0.25);
      transition: transform 140ms ease, background 140ms ease;
    }
    .acct-head-gear .bk-icon { width: 18px; height: 18px; color: var(--text-mute); }
    .acct-head-gear:hover  { background: var(--bg-soft); }
    .acct-head-gear:hover .bk-icon { color: var(--accent); }
    .acct-head-gear:active { transform: scale(0.94); }
    .acct-head-gear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    /* 2 · Action-tile grid — 2-up mobile, 3-up ≥640px. */
    .acct-tiles {
      list-style: none; margin: 0; padding: 0;
      display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
    }
    @media (min-width: 640px) { .acct-tiles { grid-template-columns: repeat(3, 1fr); } }
    .acct-tiles > li { display: flex; }
    .acct-tile {
      flex: 1; width: 100%;
      display: flex; flex-direction: column; align-items: center; gap: 10px;
      padding: 18px 10px; min-height: 92px;
      background: #fff; border: 1px solid var(--border); border-radius: 16px;
      cursor: pointer; font: inherit;
      box-shadow: 0 1px 2px rgba(15,23,42,0.04);
      transition: transform 140ms cubic-bezier(.34,1.56,.64,1),
                  box-shadow 140ms ease, border-color 140ms ease;
    }
    body[data-template="luxury"] .acct-tile { border-radius: 4px; background: var(--bg-soft); }
    .acct-tile-ic {
      width: 46px; height: 46px; border-radius: 14px;
      display: flex; align-items: center; justify-content: center;
      background: color-mix(in srgb, var(--accent) 12%, transparent);
      color: var(--accent);
    }
    .acct-tile-ic .bk-icon { width: 22px; height: 22px; }
    .acct-tile-label {
      font-size: 13px; font-weight: 600; color: var(--text);
      text-align: center; line-height: 1.3;
    }
    .acct-tile:hover {
      transform: translateY(-2px);
      border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
      box-shadow: 0 10px 22px -14px color-mix(in srgb, var(--accent) 45%, transparent);
    }
    .acct-tile:active { transform: translateY(0); }
    .acct-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    /* 3 · Settings list — chevron rows in one grouped card. */
    .acct-settings {
      list-style: none; margin: 0; padding: 0;
      background: #fff; border: 1px solid var(--border); border-radius: 16px;
      overflow: hidden;
      box-shadow: 0 1px 2px rgba(15,23,42,0.04);
    }
    body[data-template="luxury"] .acct-settings { border-radius: 4px; background: var(--bg-soft); }
    .acct-srow {
      width: 100%; box-sizing: border-box;
      display: flex; align-items: center; gap: 12px;
      padding: 14px 16px; min-height: 52px;
      background: none; border: none; cursor: pointer; font: inherit;
      color: var(--text); text-align: left; text-decoration: none;
      border-bottom: 1px solid var(--border);
      transition: background 120ms ease;
    }
    .acct-settings > li:last-child .acct-srow { border-bottom: none; }
    .acct-srow-ic { width: 19px; height: 19px; color: var(--text-mute); flex-shrink: 0; }
    .acct-srow-label { flex: 1; font-size: 14px; font-weight: 600; min-width: 0; }
    .acct-srow-value {
      font-size: 13px; font-weight: 600; color: var(--text-mute);
      white-space: nowrap;
    }
    .acct-chev { width: 16px; height: 16px; color: var(--text-mute); flex-shrink: 0; opacity: 0.7; }
    .acct-srow:hover  { background: var(--bg-soft); }
    .acct-srow:hover .acct-srow-ic { color: var(--accent); }
    .acct-srow:active { background: color-mix(in srgb, var(--accent) 7%, transparent); }
    .acct-srow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
    .acct-settings-sep { list-style: none; margin: 0; padding: 0; }
    .acct-settings-sep hr { border: 0; border-top: 1px solid var(--border); margin: 0; opacity: 0.6; }
    .acct-srow-danger .acct-srow-label,
    .acct-srow-danger .acct-srow-ic { color: #DC2626; }
    .acct-srow-danger:hover { background: #FEF2F2; }
    .acct-srow-danger:hover .acct-srow-ic { color: #DC2626; }
    .acct-srow-danger:focus-visible { outline-color: #DC2626; }

    .acct-bucket { display: flex; flex-direction: column; gap: 8px; }
    .acct-bucket-label {
      margin: 0 0 4px 0; padding: 0;
      font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
      text-transform: uppercase; color: var(--text-mute);
    }
    .acct-bucket-tabs {
      list-style: none; margin: 0; padding: 0;
      display: grid; grid-template-columns: 1fr; gap: 4px;
    }
    @media (min-width: 640px) and (max-width: 899px) {
      .acct-bucket-tabs { grid-template-columns: 1fr 1fr; }
    }
    /* pk 2026-06-01 (SF-22) — removed the dead .acct-tab / .acct-tab.active /
       .acct-tab-logout* block: the redesigned hub emits .acct-tile / .acct-srow
       (data-tab) and _acctRenderActive's '#acct-tabs .acct-tab' toggle matches
       nothing, so these ~30 lines styled no element. Deleted to avoid misleading
       the next editor. (.acct-bucket* above kept — separate selector group.) */

    /* Wave H10b — Recently Viewed list (acct-recent-*) + Reorder list
       (acct-reorder-*) row layouts. Both share the §3.6.3 tabular-nums
       discipline on price/total. */
    .acct-recent-list, .acct-reorder-list {
      display: flex; flex-direction: column; gap: 8px;
    }
    .acct-recent-row {
      display: flex; align-items: center; gap: 12px;
      padding: 10px 12px; border: 1px solid var(--border);
      border-radius: 10px; text-decoration: none; color: inherit;
      background: #fff; min-height: 56px;
    }
    .acct-recent-row:hover { background: var(--bg-soft); }
    .acct-recent-img {
      width: 56px; height: 56px; border-radius: 8px;
      background-size: cover; background-position: center;
      background-color: var(--bg-soft); flex-shrink: 0;
    }
    .acct-recent-meta {
      flex: 1; min-width: 0;
      display: flex; flex-direction: column; gap: 2px;
    }
    .acct-recent-name {
      font-size: 14px; font-weight: 600; color: var(--text);
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .acct-recent-price {
      /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
      font-size: 14px; color: var(--bk-ink, var(--ink)); font-weight: 700;
      font-variant-numeric: tabular-nums;
    }

    .acct-reorder-row {
      display: flex; align-items: center; gap: 12px;
      padding: 12px; border: 1px solid var(--border);
      border-radius: 10px; background: #fff;
    }
    .acct-reorder-meta { flex: 1; min-width: 0; }
    .acct-reorder-ref {
      font-family: ui-monospace, monospace; font-size: 13px;
      font-weight: 700; color: var(--text);
    }
    .acct-reorder-date {
      font-size: 12px; color: var(--text-mute); margin-top: 2px;
    }
    .acct-reorder-total {
      font-size: 14px; font-weight: 700; color: var(--accent);
      font-variant-numeric: tabular-nums;
    }
    @media (max-width: 480px) {
      .acct-reorder-row { flex-wrap: wrap; }
      .acct-reorder-row > .btn-text { width: 100%; justify-content: center; }
    }
    .acct-panel { min-height: 200px; }
    .acct-card {
      background: #fff; border: 1px solid var(--border);
      border-radius: var(--card-radius); padding: 20px; margin-bottom: 14px;
    }
    body[data-template="luxury"] .acct-card { background: var(--bg-soft); }
    .acct-row {
      display: flex; justify-content: space-between; gap: 16px;
      padding: 9px 0; font-size: 14px; border-bottom: 1px solid var(--border);
    }
    .acct-row:last-child { border-bottom: none; }
    .acct-row .k { color: var(--text-mute); }
    .acct-row .v { font-weight: 600; text-align: right; word-break: break-word; }
    .acct-field { margin-bottom: 13px; }
    .acct-field label {
      display: block; font-size: 12px; font-weight: 600;
      color: var(--text-mute); margin-bottom: 5px;
    }
    .acct-field input {
      width: 100%; box-sizing: border-box; font-family: inherit; font-size: 16px;
      padding: 10px 12px; border: 1.5px solid var(--border);
      border-radius: 10px; background: #fff; color: var(--text); outline: none;
    }
    .acct-field input:focus { border-color: var(--accent); }
    .acct-btn {
      /* 2026-06-02 (pk): 1.5px TRANSPARENT border (was `none`) so a filled
         .acct-btn and an outline .acct-btn-ghost occupy the IDENTICAL box in
         every theme — previously the ghost's 1.5px border made the two read as
         different sizes. box-sizing:border-box (global) keeps height at 44px. */
      border: 1.5px solid transparent; border-radius: 10px; cursor: pointer;
      background: var(--accent);
      color: #fff; font: inherit; font-size: 14px; font-weight: 700;
      padding: 11px 18px; min-height: 44px;
    }
    .acct-btn:active   { transform: translateY(1px); }
    .acct-btn:disabled { opacity: 0.55; cursor: default; }
    .acct-btn-ghost {
      background: none; border: 1.5px solid var(--border); color: var(--text);
    }
    .acct-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
    /* 2026-06-02 (pk): on the ORDER card only, the action buttons share the row
       equally so the 3 buttons (View items / Attach slip / Re-order) read as ONE
       size instead of width-by-text. Scoped to .acct-actions--order so it does
       NOT restyle the profile / address / wishlist / pawn action rows (e.g. the
       deliberately-compact "Add new address" button). min-width:auto (flex
       default) still prevents text clipping → they wrap on a narrow card. */
    .acct-actions--order > .acct-btn { flex: 1 1 0; }
    /* #5 (2026-06-01) — order line-items (purchase-history detail), lazy-loaded
       under each order card via shop.acctToggleItems. */
    .acct-order-items { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
    .acct-oi-row {
      display: flex; align-items: center; gap: 10px;
      padding: 8px; border-radius: 10px; background: var(--bg-soft);
    }
    /* #5b (2026-06-02, pk) — line item is a tap target into its PDP. Reset the
       <a> (inherit colour, no underline), signal tappability (cursor + hover +
       a chevron), and keep the whole row ≥44px (the 44px thumb guarantees it). */
    .acct-oi-row--link {
      text-decoration: none; color: inherit; cursor: pointer;
      transition: background 0.12s ease;
    }
    .acct-oi-row--link:hover { background: var(--border); }
    .acct-oi-row--link::after {
      content: '\203A'; flex-shrink: 0; margin-left: 2px;
      color: var(--text-mute); font-size: 18px; line-height: 1;
    }
    .acct-oi-thumb {
      width: 44px; height: 44px; flex-shrink: 0; border-radius: 8px;
      object-fit: cover; background: var(--border);
    }
    .acct-oi-thumb--ph { display: inline-block; }
    .acct-oi-meta { flex: 1; min-width: 0; }
    .acct-oi-name {
      font-size: 13px; font-weight: 600; color: var(--text);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .acct-oi-sub { font-size: 12px; color: var(--text-mute); margin-top: 2px; }
    .acct-oi-total { font-size: 13px; font-weight: 700; color: var(--text); flex-shrink: 0; }
    .acct-oi-loading, .acct-oi-empty {
      font-size: 13px; color: var(--text-mute); padding: 6px 2px;
    }
    .acct-err { color: #B91C1C; font-size: 13px; font-weight: 600; margin: 8px 0 0; }
    .acct-empty {
      text-align: center; color: var(--text-mute); font-size: 14px;
      padding: 36px 16px;
    }
    .acct-sub {
      font-size: 12px; font-weight: 700; letter-spacing: 0.07em;
      text-transform: uppercase; color: var(--text-mute); margin: 20px 0 10px;
    }
    /* order card */
    .acct-order-top {
      display: flex; justify-content: space-between; align-items: flex-start;
      gap: 12px; flex-wrap: wrap;
    }
    .acct-order-ref { font-weight: 700; font-size: 15px; }
    .acct-order-meta { font-size: 12px; color: var(--text-mute); margin-top: 2px; }
    .acct-order-total {
      font-weight: 700; color: var(--accent); margin-top: 8px;
      /* Wave H-6b (F-SHOP-006) — tabular-nums + right-align so the order
         total column stacks digit-true across the order list per §3.6.3. */
      font-variant-numeric: tabular-nums;
      text-align: right;
    }
    .os-badge {
      display: inline-block; font-size: 11px; font-weight: 700;
      padding: 4px 10px; border-radius: 999px; white-space: nowrap;
    }
    .os-badge.os-cart             { background: var(--bg-soft); color: var(--text-mute); }
    .os-badge.os-awaiting_payment { background: rgba(245,158,11,0.15); color: #92400E; }
    .os-badge.os-paid             { background: rgba(59,130,246,0.14); color: #1D4ED8; }
    .os-badge.os-shipped          { background: rgba(139,92,246,0.15); color: #6D28D9; }
    .os-badge.os-completed        { background: rgba(16,185,129,0.14); color: #047857; }
    .os-badge.os-cancelled        { background: rgba(0,0,0,0.06); color: var(--text-mute); }
    /* coupon row */
    .acct-coupon {
      display: flex; align-items: center; gap: 12px; padding: 11px 0;
      border-bottom: 1px solid var(--border);
    }
    .acct-coupon:last-child { border-bottom: none; }
    .acct-coupon .code {
      font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-weight: 700;
      font-size: 13px; letter-spacing: 0.05em; color: var(--accent);
      background: var(--bg-soft); padding: 5px 9px; border-radius: 7px;
      flex: 0 0 auto;
    }
    .acct-coupon .cn { flex: 1; font-size: 13px; color: var(--text); }
    .acct-coupon .cv { font-size: 13px; font-weight: 700; white-space: nowrap; }
    /* wave-2 placeholder */
    .acct-soon {
      text-align: center; padding: 40px 20px; color: var(--text-mute);
    }
    .acct-soon .pill {
      display: inline-block; font-size: 11px; font-weight: 700;
      letter-spacing: 0.08em; text-transform: uppercase;
      background: var(--bg-soft); color: var(--text-mute);
      padding: 5px 12px; border-radius: 999px; margin-bottom: 12px;
    }
    .acct-soon p { font-size: 14px; margin: 0; max-width: 320px; margin: 0 auto; line-height: 1.6; }
    /* A2 — checkout sign-in prompt */
    .co-signin {
      display: flex; align-items: center; justify-content: space-between;
      gap: 12px; flex-wrap: wrap;
      background: var(--bg-soft); border: 1px solid var(--border);
      border-radius: var(--card-radius); padding: 13px 16px; margin-bottom: 16px;
    }
    .co-signin span { font-size: 13px; color: var(--text); }
    .co-signin .co-signin-btn {
      flex: 0 0 auto; background: var(--accent); color: #fff; border: none;
      border-radius: 9px; font: inherit; font-size: 13px; font-weight: 700;
      padding: 9px 16px; cursor: pointer; min-height: 40px;
    }
    .co-signin .co-signin-btn:active { transform: translateY(1px); }
    .co-signin.is-in span { color: var(--text-mute); }
    /* Optional login-at-checkout prompt (Mr.C 2026-05-30) — benefit framing
       + guest escape. Stacks copy over a two-button action row; collapses to
       a column on narrow viewports. Guest checkout is the default — this is
       a nudge, never a gate. DESIGN.md §4.5 buttons · §8.2 ≥44px. */
    .co-signin.is-guest-prompt { align-items: flex-start; }
    .co-signin-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
    .co-signin-title { font-size: 14px; font-weight: 700; color: var(--text); }
    .co-signin-note  { font-size: 12px; color: var(--text-mute); }
    .co-signin-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
    .co-signin-guest {
      flex: 0 0 auto; background: transparent;
      border: 1.5px solid var(--border); border-radius: 9px;
      color: var(--text-mute); font: inherit; font-size: 13px; font-weight: 600;
      padding: 9px 14px; cursor: pointer; min-height: 40px;
    }
    .co-signin-guest:hover  { background: var(--bg); }
    .co-signin-guest:active { transform: translateY(1px); }
    @media (max-width: 480px) {
      .co-signin.is-guest-prompt { flex-direction: column; align-items: stretch; }
      .co-signin.is-guest-prompt .co-signin-actions { width: 100%; }
      .co-signin.is-guest-prompt .co-signin-btn,
      .co-signin.is-guest-prompt .co-signin-guest { flex: 1 1 0; }
    }

    /* ── A3-tail + A4 — order tracking, addresses, wishlist, etc. ────── */
    .acct-btn-danger { background: #DC2626; }
    .acct-tag {
      display: inline-block; font-size: 10px; font-weight: 700;
      letter-spacing: 0.06em; text-transform: uppercase;
      background: rgba(16,185,129,0.14); color: #047857;
      padding: 2px 7px; border-radius: 999px; vertical-align: middle;
    }
    .acct-ok  { color: #047857; font-weight: 700; font-size: 12px; }
    .acct-due { color: #92400E; font-weight: 700; font-size: 12px; }
    /* A4 — order lifecycle timeline (Phase 4.5-F F-11 enhanced).
       Each .os-step is now a <button> (was <div>) so each stage is
       focusable + announceable as a discrete tap target — tapping
       any step expands the per-order .os-expand-block below the
       timeline with timestamp summary + tracking + honest-scoping
       note (orders table persists only created_at + updated_at, not
       per-stage timestamps; per [[feedback-honest-scoping-caveat-as
       -comment]] the UI flags the gap explicitly). */
    .os-timeline {
      display: flex; align-items: flex-start; margin: 14px 0 4px;
    }
    .os-step {
      display: flex; flex-direction: column; align-items: center;
      gap: 5px; flex: 0 0 auto;
      /* Button reset — keeps the prior <div>-styled visual identical. */
      background: none; border: none; padding: 4px 2px;
      cursor: pointer; font: inherit; color: inherit;
    }
    .os-step:focus-visible .os-dot {
      outline: 2px solid var(--accent); outline-offset: 4px;
    }
    .os-dot {
      width: 13px; height: 13px; border-radius: 50%;
      background: #fff; border: 2px solid var(--border);
      transition: box-shadow var(--bk-dur-base) var(--bk-ease-out);
    }
    .os-step.done .os-dot    { background: var(--accent); border-color: var(--accent); }
    .os-step.current .os-dot { background: var(--accent); border-color: var(--accent);
      /* Wave H-1 (F-CUS-120) — pulse tint re-derives from --accent so the
         F-11 timeline current-stage ring picks up per-tenant + per-vertical
         brand bleed (DESIGN.md §2.5). Was hardcoded Vibrant brand-A. */
      box-shadow: 0 0 0 4px color-mix(in oklch, var(--accent) 18%, transparent);
      /* F-11 — gentle pulse on the current stage so it draws the eye.
         Respects prefers-reduced-motion (animation suppressed below). */
      animation: os-current-pulse 2.4s ease-in-out infinite;
    }
    @keyframes os-current-pulse {
      0%, 100% { box-shadow: 0 0 0 4px color-mix(in oklch, var(--accent) 18%, transparent); }
      50%      { box-shadow: 0 0 0 8px color-mix(in oklch, var(--accent) 10%, transparent); }
    }
    @media (prefers-reduced-motion: reduce) {
      .os-step.current .os-dot { animation: none; }
    }
    .os-lbl { font-size: 10px; font-weight: 600; color: var(--text-mute); text-align: center; max-width: 64px; }
    .os-step.current .os-lbl, .os-step.done .os-lbl { color: var(--text); }
    .os-line { flex: 1; height: 2px; background: var(--border); margin: 6px 2px 0; }
    .os-cancelled { margin: 12px 0 2px; }
    /* F-11 — expandable summary revealed on any os-step tap. The
       container handles its own [hidden] toggle via JS; the inner
       rows surface what the schema gives us (placed/updated) and
       inline-flag the per-stage-timestamp gap honestly. */
    .os-expand-block {
      margin-top: 12px; padding: 10px 12px;
      background: rgba(0,0,0,0.035); border-radius: 8px;
      font-size: 13px; line-height: 1.5;
    }
    .os-expand-block[hidden] { display: none; }
    .os-expand-row { color: var(--text); margin-bottom: 4px; }
    .os-expand-row:last-of-type { margin-bottom: 0; }
    .os-expand-note {
      margin-top: 8px; padding-top: 8px;
      border-top: 1px dashed var(--border);
      font-size: 11px; color: var(--text-mute);
      font-style: italic;
    }
    .acct-track {
      margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--border);
      font-size: 13px; color: var(--text-mute); line-height: 1.6;
    }
    .acct-track b { color: var(--text); font-weight: 700; }
    .acct-track a { color: var(--accent); font-weight: 600; white-space: nowrap; }
    /* B4 item-4 — payment-slip rejection notice on an order card */
    .acct-reject {
      margin-top: 12px; padding: 13px 15px; border-radius: 12px;
      background: rgba(220,38,38,0.07); border: 1px solid rgba(220,38,38,0.30);
    }
    .acct-reject-head {
      font-size: 14px; font-weight: 700; color: #B91C1C;
    }
    .acct-reject-reason { font-size: 13px; color: var(--text); margin-top: 6px; line-height: 1.5; }
    .acct-reject-reason b { font-weight: 700; }
    .acct-reject-help { font-size: 13px; color: var(--text-mute); margin-top: 4px; line-height: 1.5; }
    .acct-reject .acct-actions { margin-top: 10px; }
    .acct-addr-body { font-size: 13px; color: var(--text-mute); margin: 8px 0; line-height: 1.5; }
    /* wishlist */
    .acct-wl { display: flex; gap: 14px; align-items: flex-start; }
    .acct-wl.is-dead { opacity: 0.6; }
    .acct-wl-img {
      flex: 0 0 auto; width: 76px; height: 76px; border-radius: 10px;
      background: var(--bg-soft) center/cover no-repeat;
    }
    .acct-wl-body { flex: 1; min-width: 0; }
    .acct-wl-name { font-weight: 700; font-size: 14px; }
    /* v3-2 §2.7 — price text uses ink, not accent, for ≥4.5:1 contrast. */
    .acct-wl-price { font-weight: 700; color: var(--bk-ink, var(--ink)); margin: 6px 0; font-size: 14px; }
    /* notifications */
    .acct-notif { cursor: pointer; }
    .acct-notif.is-unread { border-left: 3px solid var(--accent); }
    .acct-notif-top { display: flex; justify-content: space-between; gap: 12px; }
    .acct-notif-title { font-weight: 700; font-size: 14px; }
    .acct-notif.is-unread .acct-notif-title::before {
      content: ''; display: inline-block; width: 7px; height: 7px;
      border-radius: 50%; background: var(--accent); margin-right: 7px;
      vertical-align: middle;
    }
    .acct-notif-body { font-size: 13px; color: var(--text-mute); margin-top: 5px; line-height: 1.5; }
    .acct-rw-stamps { font-size: 14px; font-weight: 600; margin: 6px 0; }

    /* ══ B3 — phone-width quality pass (≤767px) ═══════════════════════
       Placed last in this block so the iOS-zoom font-size override wins
       over the class-level input rules above by source order. */
    @media (max-width: 767px) {
      /* pk 2026-05-31 — top bar stays ONE line on mobile: logo · search ·
         cart/noti/profile. (Was: search dropped to a full-width row 2 — this
         older block out-ran r6's one-line rules on source order. Now nowrap +
         search flex:1 fills the middle so the icon group is pushed far right.) */
      .nav-inner   { flex-wrap: nowrap; gap: 10px; }
      .nav-search  { flex: 1 1 auto; min-width: 0; }
      .nav-search input { width: 100%; max-width: none; }
      .nav-brand   { font-size: 18px; }
      /* iOS Safari auto-zooms (and jump-scrolls) when a focused input's
         font is under 16px — pin every form control to 16px on phones. */
      .nav-search input,
      .ec-field input, .ec-field textarea,
      .co-coupon-input,
      .qty-stepper input,
      input, select, textarea { font-size: 16px; }

      /* ── Round 29 UAT-walk item 6: mobile responsive audit ─────────
         All audited at 375/390/414. */

      /* Tap targets — bring every header control to 44×44 (was 40)
         to clear the WCAG 2.5.5 target threshold. */
      /* pk 2026-06-03: all 3 topbar controls together (was cart+acct only → bell
         stayed 40px = mismatch on small phones). */
      .nav-cart, .nav-bell, .nav-acct-btn { width: 44px; height: 44px; font-size: 19px; }
      .lang-toggle button { padding: 12px 14px; min-height: 44px; }

      /* Container side padding — bump from 20px to 16px on phones so
         the product grid and section content don't feel cramped against
         the edge while leaving room for the 44px controls in the header. */
      .container { padding: 0 16px; }

      /* Hero — slim top/bottom padding on phones; the static benefits
         strip below was getting pushed below the fold on iPhone SE. */
      .hero { padding: 28px 0 36px 0; }
      .hero-inner { gap: 24px; }
      .hero-text p { margin-bottom: 20px; }

      /* Benefits strip — already 2-col via .benefits-grid; tighten the
         vertical rhythm on phones so the strip stays compact under the
         hero. (4-col only kicks in ≥760px so this is the phone-only
         shape.) */
      .benefits-strip .benefits-grid { padding: 24px 0; gap: 22px 12px; }
      .benefit-desc { max-width: 26ch; }

      /* Brand strip — pk 2026-06-01 (full-bleed image tiles): a slightly larger
         landscape image tile reads better as a photo than the old tiny square;
         ~150px wide → ~2.4 visible at 375 width while the marquee glides the
         rest past. The L/R gutter is zeroed below so the row runs edge-to-edge. */
      .brand-strip { padding: 24px 0; }
      .brand-strip-inner { gap: 12px; }
      .brand-item { width: 150px; height: 116px; padding: 0; }
      .brand-item .brand-overlay .name { font-size: 13px; }
      .brand-item.no-image .brand-overlay .name { font-size: 15px; }

      /* Categories — tighten gap on phones (2-up already from base). */
      .cat-grid { gap: 10px; }

      /* Product grid — 2-up already. pk 2026-06-01 — bumped 16px→20px
         ("เพิ่มระยะ product card ... ด้านซ้ายขวา นิดหน่อย"): a touch more
         breathing room between the two columns; still fits 2-up at 360px.
         This is the LATER-in-source mobile rule so it's the one that renders
         for .prod-grid on phones. */
      .prod-grid { gap: 20px; }

      /* PDP — Buy now / Add to bag / Buy via LINE all need full-tap
         comfort + safe vertical rhythm. The 2-col CTA grid is fine,
         but bump the row gap so the buttons don't crowd the qty
         stepper above. */
      .pd-cta-grid { gap: 8px; }
      .pd-buy-now  { padding-top: 14px; padding-bottom: 14px; }

      /* Cart — single-column layout already; ensure the cart-line image
         doesn't muscle out the meta column on narrow widths. (B3 set
         this, kept for clarity.) */
      .cart-line { gap: 10px; }

      /* Wave H10 (F-CUS-127) — supersedes the Wave H7 F-CUS-126 right-edge
         fade-mask on #acct-tabs (which H7 explicitly tagged as interim
         polish "until Wave H10 3-bucket regroup"). The 3-bucket .acct-hub
         grid stacks natively under <640px without any horizontal scroll,
         so the prior scrollbar-hiding hacks + mask-image are dead. */
    }
/* ═══════ Bookku Interaction Feedback Standard — kit (CSS half) ═══════
   Verbatim from feedback_standard_demo.html. See INTERACTION_FEEDBACK_STANDARD.md.
   Wave H-1 (F-CUS-132) — --brand-a/-b now derive from --accent so the kit
   participates in per-tenant + per-vertical brand bleed (DESIGN.md §2.5).
   The legacy Vibrant pair (#4361EE / #8B2FC9) survives only as fallback
   if --accent is undefined (which the storefront fetch always sets). */
:root {
  --brand-a: var(--accent, #4361EE);
  --brand-b: color-mix(in oklch, var(--accent, #4361EE) 80%, black);
  --ink: #1E293B;
  --line: #E5E0FF;
  /* Wave H-shop-rgba-cleanup — per Mr.F audit, 19 raw rgba(15,23,42,X)
     shadow sites outside :root. Token-ise so per-tenant brand bleed +
     future dark-mode can re-tint shadows in one place. Mirror admin_shop
     rgba-cleanup. Space-separated tuple for the rgba(R G B / a%) form. */
  --bk-shadow-color: 15 23 42;
  /* Wave H-shop-remainder (F-CUS-103) — motion design tokens per
     DESIGN.md §7 + §8.3. Three duration buckets + three easings so
     transitions read consistently across hover (fast) / press (base)
     / drawer-open (slow). Bespoke timings (haptic 80ms / hero-fade
     500ms) intentionally stay inline. */
  --bk-dur-fast:    140ms;   /* micro-feedback (hover/focus/press) — v3 P0 fix: was self-referential var(--bk-dur-fast) → invalid → killed ~25 transitions */
  --bk-dur-base:    200ms;   /* default property animation — v3 P0 fix: was self-referential */
  --bk-dur-slow:    300ms;   /* drawer / modal open */
  --bk-ease-out:    cubic-bezier(0.4, 0, 0.2, 1);     /* default exit curve */
  --bk-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* playful drawer slide */
  --bk-ease-in:     cubic-bezier(0.4, 0, 1, 1);       /* exit / dismiss */
}
/* Wave H4-shop — sprite consumer base class per DESIGN.md §11.0.2.
   Sprite ships at /icons.svg with 32 currentColor symbols.
   GAPS (v1 inventory missing — surfaced in Wave H4-shop handback):
     🎁 (gift/voucher — used in voucher tiles + cart voucher row + confirmation heading)
     🔥 (fire/hot — used in best-sellers heading + "N sold" badge)
     ⚡ (lightning/flash — used in flash sale label)
     📄 (document — used in article + voucher placeholders)
     📍 (pin/location — used in contact-row map link + social channel)
     🔗 (link — used in article copy-link button)
     ♪ (music — used as TikTok social glyph)
     ✦ (sparkle — used as AI assistant dot)
     📘 (Facebook) · 📷 (Instagram)  — social platform glyphs
   These need §11.0.2 amendment (sprite v2) before sweep continues. */
.bk-icon {
  width: 1em; height: 1em;
  display: inline-block; vertical-align: -0.125em;
  fill: currentColor; flex-shrink: 0;
}
/* Heart sprite is outline-only; .is-on state flips to filled. */
.pd-icon-btn.is-on .bk-icon, .pd-wlist.is-on .bk-icon { fill: currentColor; }
button:not(:disabled):active { transform: translateY(1px); }
.bk-btn {
  font-family: inherit; font-size: 13px; font-weight: 700;
  border: none; border-radius: 12px; padding: 11px 22px;
  cursor: pointer; color: #fff;
  background: var(--brand-a);
  transition: transform 90ms ease, box-shadow 160ms ease, opacity 160ms ease;
  box-shadow: 0 6px 16px -8px rgba(67,97,238,.7);
  display: inline-flex; align-items: center; gap: 8px;
}
.bk-btn:hover:not(:disabled) { box-shadow: 0 10px 22px -8px rgba(67,97,238,.85); }
.bk-btn:active:not(:disabled) { transform: translateY(2px) scale(.985); box-shadow: 0 3px 10px -6px rgba(67,97,238,.7); }
.bk-btn:disabled { cursor: progress; opacity: .82; }
.bk-btn--ghost {
  background: #fff; color: var(--ink);
  border: 1.5px solid var(--line); box-shadow: none;
}
.bk-btn--ghost:hover:not(:disabled) { border-color: #C4B5FD; }
.bk-spin {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.45); border-top-color: #fff;
  animation: bk-spin .6s linear infinite; flex-shrink: 0;
}
.bk-btn--ghost .bk-spin { border-color: rgba(0,0,0,.2); border-top-color: var(--brand-b); }
@keyframes bk-spin { to { transform: rotate(360deg); } }
#bk-progress {
  position: fixed; top: 0; left: 0; height: 3px; width: 100%;
  background: var(--brand-a);
  box-shadow: 0 0 8px rgba(124,58,237,.6);
  /* Wave H7 (F-SHOP-011) — transform-based fill per DESIGN.md §7. */
  transform-origin: left; transform: scaleX(0);
  transition: transform var(--bk-dur-base) ease-out, opacity var(--bk-dur-base) ease-out;
  z-index: 9999; pointer-events: none; opacity: 0;
}
#bk-progress.active { opacity: 1; }
#bk-toast {
  position: fixed; top: 20px; left: 50%;
  transform: translateX(-50%) translateY(-24px);
  display: flex; align-items: center; gap: 9px;
  padding: 12px 20px; border-radius: 14px;
  font-size: 13px; font-weight: 700;
  box-shadow: 0 14px 36px -10px rgba(var(--bk-shadow-color) / 32%);
  opacity: 0; pointer-events: none; z-index: 10000;
  transition: opacity 220ms ease, transform 260ms var(--bk-ease-spring);
  max-width: min(92vw, 460px);
}
#bk-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
#bk-toast.success { background: #ECFDF5; color: #047857; border: 1.5px solid #A7F3D0; }
#bk-toast.error   { background: #FEF2F2; color: #B91C1C; border: 1.5px solid #FECACA; }
#bk-toast.info    { background: #EFF3FF; color: #3749C8; border: 1.5px solid #C7D2FE; }
/* Wave 4 (2026-05-28) — added `warn` level for cautionary states that
   aren't outright errors (e.g. "session expiring soon"). Amber palette
   sits between success-green and error-red on the urgency spectrum. */
#bk-toast.warn    { background: #FFFBEB; color: #92400E; border: 1.5px solid #FDE68A; }
#bk-toast .bk-toast-ic { font-size: 15px; line-height: 1; }

/* LAYER 2b — bk.upload() modal: a blocking 0–100% upload pop-up. */
.bk-up-overlay {
  position: fixed; inset: 0; z-index: 10001;
  background: rgba(var(--bk-shadow-color) / 55%);
  display: flex; align-items: center; justify-content: center;
}
.bk-up-modal {
  background: #fff; border-radius: 18px; padding: 26px 30px;
  width: min(90vw, 360px); text-align: center; font-family: inherit;
  box-shadow: 0 24px 60px -12px rgba(var(--bk-shadow-color) / 40%);
}
.bk-up-title { font-size: 15px; font-weight: 800; color: var(--ink); }
.bk-up-sub   { font-size: 13px; font-weight: 600; color: #64748B; margin-top: 6px; }
.bk-up-track {
  height: 8px; background: #EEF2FF; border-radius: 99px;
  margin-top: 16px; overflow: hidden;
}
.bk-up-fill {
  height: 100%; width: 100%; border-radius: 99px;
  background: var(--brand-a);
  /* Wave H7 (F-SHOP-011) — transform-based fill per DESIGN.md §7. */
  transform-origin: left; transform: scaleX(0);
  transition: transform 180ms ease-out;
}
.bk-up-pct { font-size: 12px; font-weight: 800; color: var(--brand-b); margin-top: 8px; }

/* Bilingual <span class="lang-en/th"> toggle, keyed on html[lang]. Hoisted
   to the <head> (was a body <style>) so the boot skeleton — which paints
   before the main script + applyLang() run — already shows the right
   language at first paint, with no English flash for a TH shopper. */
.lang-th { display: none; }
html[lang="th"] .lang-th { display: inline; }
html[lang="th"] .lang-en { display: none; }

    .bk-cookie-banner {
      position: fixed; left: 16px; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px));
      z-index: 9999;
      max-width: 760px; margin: 0 auto;
      background: white;
      border: 1px solid #E5E7EB;
      border-radius: 14px;
      box-shadow: 0 20px 50px -10px rgba(var(--bk-shadow-color) / 25%), 0 8px 20px -10px rgba(37,99,235,0.10);
      padding: 16px 18px;
      display: none;
      flex-direction: column; gap: 12px;
      font-family: 'Plus Jakarta Sans', 'Noto Sans Thai', system-ui, -apple-system, sans-serif;
    }
    .bk-cookie-banner.is-visible { display: flex; }
    @media (min-width: 768px) {
      .bk-cookie-banner { flex-direction: row; align-items: center; gap: 18px; }
    }
    .bk-cookie-text { font-size: 13px; color: #334155; line-height: 1.5; flex: 1; }
    .bk-cookie-text a { color: #4F46E5; text-decoration: underline; text-underline-offset: 2px; }
    .bk-cookie-actions { display: flex; gap: 8px; flex-shrink: 0; }
    .bk-cookie-btn {
      padding: 10px 18px; border-radius: 10px; font-size: 13px; font-weight: 700;
      cursor: pointer; border: none; font-family: inherit;
      transition: transform var(--bk-dur-fast) var(--bk-ease-out), box-shadow var(--bk-dur-fast) var(--bk-ease-out);
    }
    .bk-cookie-btn-ghost { background: #F1F5F9; color: #475569; }
    .bk-cookie-btn-ghost:hover { background: #E2E8F0; }
    .bk-cookie-btn-primary {
      background: var(--brand-primary, #2563EB);
      color: white;
      box-shadow: 0 6px 16px -4px rgba(79,70,229,0.40);
    }
    .bk-cookie-btn-primary:hover { transform: translateY(-1px); box-shadow: 0 10px 22px -6px rgba(79,70,229,0.50); }
    /* F-15 (Wave 3, 2026-05-28): AI chat redesigned as a bottom-sheet
       drawer instead of the previous corner-popover panel. Bubble stays
       persistent bottom-right; tap → sheet slides up from screen bottom
       to ~80vh. Mobile: drag handle on top + drag-to-dismiss + backdrop-
       click-to-dismiss. Desktop: backdrop-click + close-X.

       V0a note (cited in commit body): the brief's "Tab 4 (AI chat) is a
       full-tab view" was empirically false — the AI chat was ALREADY a
       floating bubble + corner popover. Redesign target adjusted: convert
       the popover to a bottom-sheet drawer. */
    /* ConWerse r3 (2026-05-31): raise the launcher ~80px so it doesn't cover
       the profile/account control when the viewport is small/zoomed (pk). */
    .ai-chat { position: fixed; right: 24px; bottom: calc(96px + env(safe-area-inset-bottom, 0px)); z-index: 9000;
               display: none; font-family: var(--body-font, system-ui, sans-serif); }
    .ai-chat.enabled { display: block; }

    .ai-bubble {
      width: 48px; height: 48px; border-radius: 50%; border: none;
      background: var(--accent, var(--brand-primary, #2563EB));
      color: #fff; font-size: 26px; line-height: 1; cursor: pointer;
      /* F-J2-10 (Wave WS-Bundle-C) — shadow tinted via brand cascade per
         §6.1 + Wave H-shop-rgba-cleanup pattern (was legacy violet
         rgba(67,97,238) + rgba(139,47,201) hardcodes). */
      box-shadow: 0 8px 22px -6px rgba(var(--bk-shadow-color) / 32%);
      display: flex; align-items: center; justify-content: center;
      transition: transform 140ms ease, box-shadow 140ms ease;
      padding: 0; overflow: hidden;
    }
    .ai-bubble img {
      width: 30px; height: 30px; display: block;
      filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25));
    }
    .ai-bubble:hover  { box-shadow: 0 10px 26px -6px rgba(var(--bk-shadow-color) / 40%); }
    .ai-bubble:active { transform: translateY(1px) scale(0.97); }

    /* F-15 — backdrop fades in behind the sheet; click anywhere on the
       backdrop dismisses the drawer (matches the bk-plp-mobile-drawer
       click-outside-dismiss pattern). Mounted as a sibling of .ai-panel
       inside .ai-chat so a single open class controls both. */
    .ai-backdrop {
      position: fixed; inset: 0; background: rgba(var(--bk-shadow-color) / 45%);
      opacity: 0; pointer-events: none;
      transition: opacity 240ms ease;
      z-index: 1;
    }
    .ai-chat.open .ai-backdrop { opacity: 1; pointer-events: auto; }

    /* F-15 — bottom-sheet drawer. Mounted fixed at the bottom of the
       viewport, full-width on mobile, max-width 720 + centered on
       desktop. Slides up via transform translateY (100% → 0). Closed
       state keeps the DOM in place (display:flex always) — only the
       transform + pointer-events change. This is what persists chat
       state across open/close cycles per pk's brief. */
    .ai-panel {
      position: fixed; left: 0; right: 0; bottom: 0;
      width: 100%; max-width: 720px; margin: 0 auto;
      height: 80vh; max-height: 80vh;
      background: #fff;
      border-radius: 18px 18px 0 0;
      overflow: hidden;
      display: flex; flex-direction: column;
      box-shadow: 0 -24px 60px -12px rgba(var(--bk-shadow-color) / 32%);
      border: 1px solid #ECE9FB;
      transform: translateY(100%);
      transition: transform 280ms var(--bk-ease-spring);
      pointer-events: none;
      z-index: 2;
    }
    .ai-chat.open .ai-panel {
      transform: translateY(0);
      pointer-events: auto;
    }
    @media (prefers-reduced-motion: reduce) {
      .ai-panel { transition: none; }
      .ai-backdrop { transition: none; }
    }

    /* F-15 — drag handle at the top of the sheet (mobile drag-to-dismiss
       + visual affordance signaling "swipe me down"). Centered grabber
       bar; touch handlers wired in JS. */
    .ai-handle {
      flex: 0 0 auto; width: 100%; padding: 8px 0 4px 0;
      display: flex; justify-content: center;
      cursor: grab; touch-action: none;
      background: #fff;
    }
    .ai-handle:active { cursor: grabbing; }
    .ai-handle::before {
      content: ''; width: 40px; height: 4px; border-radius: 999px;
      background: #CBD5E1;
    }

    .ai-head {
      background: var(--accent, var(--brand-primary, #2563EB));
      color: #fff; padding: 13px 14px; display: flex; align-items: center; gap: 8px;
      flex: 0 0 auto;
    }
    .ai-head .ai-dot { font-size: 15px; }
    .ai-head .ai-title { font-weight: 700; font-size: 15px; flex: 1; }
    .ai-x {
      background: rgba(255,255,255,0.18); border: none; color: #fff;
      width: 26px; height: 26px; border-radius: 7px; font-size: 17px;
      line-height: 1; cursor: pointer; flex: 0 0 auto;
    }
    .ai-x:hover  { background: rgba(255,255,255,0.30); }
    .ai-x:active { transform: translateY(1px); }

    .ai-log {
      flex: 1 1 auto; overflow-y: auto; padding: 14px;
      display: flex; flex-direction: column; gap: 9px;
      background: #FAFAFC;
    }
    .ai-msg {
      max-width: 84%; padding: 9px 12px; border-radius: 14px;
      font-size: 14px; line-height: 1.5; white-space: pre-wrap;
      word-wrap: break-word;
    }
    .ai-msg-bot {
      align-self: flex-start; background: #fff; color: #1E293B;
      border: 1px solid #ECE9FB; border-bottom-left-radius: 4px;
    }
    .ai-msg-user {
      align-self: flex-end; color: #fff; border-bottom-right-radius: 4px;
      background: var(--accent, var(--brand-primary, #2563EB));
    }
    .ai-msg-typing {
      align-self: flex-start; background: #fff; border: 1px solid #ECE9FB;
      border-bottom-left-radius: 4px;
    }
    .ai-dots { display: inline-flex; gap: 4px; }
    .ai-dots i {
      width: 6px; height: 6px; border-radius: 50%;
      background: #B9B4D6; display: inline-block;
      animation: ai-bounce 1.2s infinite ease-in-out both;
    }
    .ai-dots i:nth-child(2) { animation-delay: 0.16s; }
    .ai-dots i:nth-child(3) { animation-delay: 0.32s; }
    @keyframes ai-bounce {
      0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
      40%           { transform: scale(1);   opacity: 1;   }
    }
    @media (prefers-reduced-motion: reduce) {
      .ai-dots i { animation: none; opacity: 0.7; }
      .ai-bubble { transition: none; }
    }

    .ai-form {
      flex: 0 0 auto; display: flex; gap: 8px;
      padding: 10px 10px calc(10px + env(safe-area-inset-bottom, 0px)) 10px;
      border-top: 1px solid #ECE9FB; background: #fff;
    }
    .ai-form input {
      flex: 1; border: 1.5px solid #E5E0FF; border-radius: 10px;
      padding: 9px 12px; font-size: 16px;            /* ≥16px — no iOS zoom */
      font-family: inherit; color: #1E293B; outline: none;
    }
    .ai-form input:focus { border-color: var(--brand-b, #8B2FC9); }
    .ai-form input:disabled { background: #F4F4F7; }
    .ai-send {
      /* Wave H-2 (F-CUS-133) — 42×~36 → 44×44 per WCAG 2.5.5 +
         DESIGN.md §8.2 touch-target minimum. */
      flex: 0 0 auto; width: 44px; height: 44px; border: none; border-radius: 10px;
      background: var(--accent, var(--brand-primary, #2563EB));
      color: #fff; font-size: 16px; cursor: pointer;
    }
    .ai-send:active   { transform: translateY(1px); }
    .ai-send:disabled { opacity: 0.55; cursor: default; }

    @media (max-width: 480px) {
      /* r3: keep the launcher clear of the bottom-tab bar + profile control. */
      .ai-chat { right: 16px; bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }
      /* Sheet keeps 80vh on mobile; only the bubble nudges in slightly. */
    }
    /* F-15 — when the bottom-sheet is open, hide the bubble (otherwise
       it stacks on top of the sheet and looks redundant). The drawer's
       built-in close-X + drag handle + backdrop click all replace it. */
    .ai-chat.open .ai-bubble {
      opacity: 0; pointer-events: none;
      transition: opacity var(--bk-dur-base) var(--bk-ease-out);
    }
    /* F-15 — body lock when drawer open: prevent the underlying page from
       scrolling under the sheet on mobile. */
    body.ai-chat-open { overflow: hidden; }
    /* Wave H7 (F-CUS-129) — PLP mobile drawer body-scroll-lock per DESIGN.md §4.4
       (modal/drawer prevents underlying scroll bleed on touch). Mirrors
       body.ai-chat-open above; toggled from the [data-bk-plp-drawer-open] /
       [data-bk-plp-drawer-close] click handlers in plpAttach. */
    body.bk-plp-drawer-open { overflow: hidden; touch-action: none; }
    /* Nav account control */
    .nav-account { position: relative; display: inline-flex; }
    .nav-acct-btn {
      width: 40px; height: 40px; border-radius: 999px; border: none;
      background: var(--bg-soft); cursor: pointer; font-size: 17px;
      display: inline-flex; align-items: center; justify-content: center;
      transition: background 160ms ease;
    }
    .nav-acct-btn:hover  { background: var(--border); }
    .nav-acct-btn:active { transform: translateY(1px); }
    .nav-acct-btn.is-in {
      background: var(--accent); color: #fff; font-weight: 700; font-size: 15px;
    }
    .nav-acct-menu {
      position: absolute; top: 48px; right: 0; z-index: 60;
      min-width: 190px; background: #fff; border: 1px solid var(--border);
      border-radius: 12px; padding: 6px; box-shadow: 0 16px 40px -12px rgba(var(--bk-shadow-color) / 28%);
    }
    .nav-acct-head {
      padding: 9px 12px 7px; font-size: 13px; font-weight: 700;
      color: var(--text); border-bottom: 1px solid var(--border);
      margin-bottom: 4px; word-break: break-word;
    }
    .nav-acct-item {
      display: block; width: 100%; text-align: left; background: none;
      border: none; cursor: pointer; padding: 10px 12px; border-radius: 8px;
      font: inherit; font-size: 14px; color: var(--text);
    }
    .nav-acct-item:hover  { background: var(--bg-soft); }
    .nav-acct-item:active { transform: translateY(1px); }

    /* Login modal */
    .auth-overlay {
      position: fixed; inset: 0; z-index: 10000; display: none;
      align-items: center; justify-content: center; padding: 16px;
      background: rgba(var(--bk-shadow-color) / 55%);
      font-family: var(--body-font, system-ui, sans-serif);
    }
    .auth-overlay.is-open { display: flex; }
    /* §11.2.2 #9 — body-scroll-lock while the modal is open (mirrors the
       shipped body.ai-chat-open / .bk-plp-drawer-open / .bk-notif-drawer-open
       precedents). Without it, a touch-drag on the dimmed backdrop scrolls the
       storefront/cart behind on mobile and the user loses their place. */
    body.auth-open { overflow: hidden; }
    .auth-modal {
      position: relative; width: min(400px, calc(100vw - 32px));
      max-height: calc(100vh - 48px); overflow-y: auto;
      background: #fff; border-radius: 18px; padding: 26px 24px 24px;
      /* §11.2.2 #2 — modal elevation derives its shadow tint from the tenant's
         --brand-primary. Was a fixed-neutral slate (rgba(--bk-shadow-color)), so
         the brand bleed stopped at the card edge (fails the §9.7 brand-bleed
         gate). --bk-elev-float (§6.1) is the canonical brand-tinted float token. */
      box-shadow: var(--bk-elev-float);
    }
    .auth-x {
      /* Wave H-shop-remainder (F-CUS-136) — bump 34×34 → 44×44 per §8.2
         + WCAG 2.5.5. */
      position: absolute; top: 8px; right: 8px;
      width: 44px; height: 44px; border-radius: 10px; border: none;
      background: var(--bg-soft); color: var(--text-mute);
      font-size: 19px; line-height: 1; cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .auth-x:hover  { background: var(--border); }
    .auth-x:active { transform: translateY(1px); }
    .auth-modal h2 {
      font-family: var(--display-font); font-size: 22px; font-weight: 700;
      margin: 0 0 6px 0; color: var(--text);
    }
    .auth-sub { font-size: 13px; color: var(--text-mute); margin: 0 0 18px 0; line-height: 1.5; }
    /* 2026-06-02 (pk) — "no payment slip" confirm card actions (reuses
       .auth-overlay/.auth-modal chrome). Cancel = outline, Confirm = accent;
       equal width, ≥44px tap (§8.2). */
    .co-cf-actions { display: flex; gap: 10px; margin-top: 20px; }
    .co-cf-btn {
      flex: 1 1 0; min-height: 46px; border-radius: 12px;
      font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
    }
    .co-cf-cancel { background: none; border: 1.5px solid var(--border); color: var(--text); }
    .co-cf-ok { background: var(--accent); color: #fff; border: 1.5px solid transparent; }
    .co-cf-btn:active { transform: translateY(1px); }
    /* Login-chooser tab switcher (Mr.C 2026-05-30) — phone/Google/email.
       Segmented pill; active tab lifts to the card surface + brand ink.
       DESIGN.md §4.5 button states · §8.2 ≥44px touch target. */
    .auth-tabs {
      display: flex; gap: 4px; margin: 0 0 18px 0; padding: 4px;
      background: var(--bg-soft); border-radius: 12px;
    }
    .auth-tab {
      flex: 1 1 0; min-height: 44px;
      border: none; border-radius: 9px; cursor: pointer;
      background: transparent; color: var(--text-mute);
      font: inherit; font-size: 14px; font-weight: 600; padding: 8px 6px;
      transition: background-color var(--bk-dur-exit, 140ms) var(--bk-ease-standard, ease),
                  color var(--bk-dur-exit, 140ms) var(--bk-ease-standard, ease);
    }
    .auth-tab:active { transform: translateY(1px); }
    .auth-tab.is-active {
      background: var(--bg); color: var(--brand-primary, var(--accent));
      box-shadow: 0 1px 3px rgba(var(--bk-shadow-color) / 14%);
    }
    /* pk 2026-06-01 (spec §B3 · §9.5) — grid-stack the 3 login panels into a
       single cell so the card height tracks the TALLEST panel (email-register)
       and never jumps when switching phone/google/email tabs. authShowTab()
       still toggles the [hidden] attribute; we override it to visibility:hidden
       (NOT display:none) so inactive panels keep their grid footprint and the
       cell always sizes to the tallest. Short panels (phone/google) get empty
       space below — intended per pk. Spacing-only: no brand-bleed change. */
    .auth-panels { display: grid; }
    .auth-tabpanel { grid-area: 1 / 1; min-width: 0; }
    .auth-tabpanel[hidden] { display: block; visibility: hidden; }
    .auth-field { margin-bottom: 14px; }
    .auth-field label {
      display: block; font-size: 12px; font-weight: 600;
      color: var(--text-mute); margin-bottom: 6px;
    }
    .auth-field input {
      width: 100%; box-sizing: border-box; font-family: inherit;
      font-size: 16px;   /* ≥16px — no iOS focus-zoom */
      padding: 11px 13px; border: 1.5px solid var(--border);
      border-radius: 10px; background: #fff; color: var(--text); outline: none;
    }
    .auth-field input:focus { border-color: var(--brand-primary, var(--accent)); }
    #auth-code { letter-spacing: 0.3em; font-weight: 700; text-align: center; }
    .auth-btn {
      /* Wave H-shop-remainder (F-CUS-136) — adopt --brand-primary v2 token
         per §11.2 customer auth atmosphere contract, falling back to
         legacy --accent so per-tenant brand bleed still cascades.
         min-height locked at 44px per §8.2 touch-target. */
      width: 100%; min-height: 44px;
      border: none; border-radius: 10px; cursor: pointer;
      background: var(--brand-primary, var(--accent)); color: #fff; font: inherit;
      font-size: 15px; font-weight: 700; padding: 12px;
    }
    .auth-btn:active   { transform: translateY(1px); }
    .auth-btn:disabled { opacity: 0.55; cursor: default; }
    .auth-or {
      display: flex; align-items: center; gap: 12px;
      margin: 16px 0; color: var(--text-mute); font-size: 12px;
    }
    .auth-or::before, .auth-or::after {
      content: ''; flex: 1; height: 1px; background: var(--border);
    }
    .auth-google {
      /* Wave H-shop-remainder (F-CUS-136) — min-height 44px lock. */
      width: 100%; min-height: 44px;
      border: 1.5px solid var(--border); border-radius: 10px;
      background: #fff; color: var(--text); font: inherit; font-size: 15px;
      font-weight: 600; padding: 11px; cursor: pointer;
      display: flex; align-items: center; justify-content: center; gap: 10px;
    }
    .auth-google:hover  { background: var(--bg-soft); }
    .auth-google:active { transform: translateY(1px); }
    .auth-google .g {
      font-weight: 800; font-size: 16px;
      font-family: Arial, sans-serif;
      background: conic-gradient(from -45deg, #EA4335 0 25%, #FBBC05 0 50%, #34A853 0 75%, #4285F4 0);
      -webkit-background-clip: text; background-clip: text; color: transparent;
    }
    .auth-otp-sent { font-size: 13px; color: var(--text-mute); margin: 0 0 16px 0; }
    .auth-otp-sent b { color: var(--text); }
    .auth-dev-hint {
      font-size: 12px; color: var(--text-mute); margin: 8px 0 0;
      padding: 7px 10px; background: var(--bg-soft); border-radius: 8px;
    }
    .auth-dev-hint b { color: var(--accent); letter-spacing: 0.15em; }
    .auth-error {
      font-size: 13px; font-weight: 600; color: #B91C1C;
      margin: 12px 0 0;
    }
    .auth-links { display: flex; justify-content: space-between; margin-top: 14px; gap: 12px; }
    /* Email tab register⇄login switch (Mr.C 2026-05-30) — group the prompt +
       link together (center) rather than space-between like the OTP links. */
    .auth-email-links { justify-content: center; align-items: center; gap: 6px; }
    .auth-email-switch-q { font-size: 13px; color: var(--text-mute); }
    .auth-link {
      /* Wave H-shop-remainder (F-CUS-136) — min-height 44px + brand-primary
         cascade per §11.2 + §8.2. */
      background: none; border: none; cursor: pointer;
      padding: 12px 10px; min-height: 44px;
      font: inherit; font-size: 13px; font-weight: 600;
      color: var(--brand-primary, var(--accent));
    }
    .auth-link:active { transform: translateY(1px); }
    /* pk 2026-05-31 (item 8): remove the floating "AI" text bar (#bk-fab) +
       its panel from the storefront. Markup + JS handlers kept intact so it's
       trivially reversible (delete this rule); just not shown. */
    #bk-fab, #bk-fab-panel { display: none !important; }
    /* pk 2026-06-01 FINAL: the WP-2 AI shopping-assistant bubble (.ai-chat) is
       KEPT on mobile — pk: "ส่วนที่คุณลบออกคือ bottom nav + ai chat เอากลับมา".
       (An earlier rule hid it on mobile; removed.) The "bottom bar" pk wanted
       gone was the FOOTER (hidden below), not this. */
    .bk-fab {
      position: fixed; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); right: 24px;
      width: 56px; height: 56px;
      border-radius: 50%;
      border: none;
      background: var(--brand-a, #4361EE);
      color: white;
      font-size: 14px; font-weight: 800;
      font-family: 'Plus Jakarta Sans', sans-serif;
      box-shadow: 0 10px 30px rgba(67, 97, 238, 0.35);
      cursor: pointer;
      z-index: 9000;
      display: flex; align-items: center; justify-content: center;
      transition: transform var(--bk-dur-base) var(--bk-ease-out), box-shadow var(--bk-dur-base) var(--bk-ease-out);
    }
    .bk-fab:hover {
      transform: translateY(-2px) scale(1.05);
      box-shadow: 0 14px 40px rgba(67, 97, 238, 0.45);
    }
    .bk-fab:focus-visible {
      outline: 3px solid var(--brand-primary, #2563EB);
      outline-offset: 3px;
    }
    .bk-fab[hidden] { display: none; }
    .bk-fab-panel {
      position: fixed; bottom: calc(96px + env(safe-area-inset-bottom, 0px)); right: 24px;
      width: 380px; max-width: calc(100vw - 32px);
      height: 480px; max-height: calc(100vh - 128px);
      background: white;
      border: 1px solid rgba(var(--bk-shadow-color) / 10%);
      border-radius: 16px;
      box-shadow: 0 24px 60px rgba(var(--bk-shadow-color) / 18%);
      z-index: 9001;
      display: flex; flex-direction: column;
      overflow: hidden;
      font-family: 'Plus Jakarta Sans', sans-serif;
    }
    .bk-fab-panel[hidden] { display: none; }
    .bk-fab-panel-head {
      flex-shrink: 0;
      display: flex; align-items: center; gap: 10px;
      padding: 12px 14px;
      background: var(--brand-a, #4361EE);
      color: white;
    }
    .bk-fab-panel-head .title { flex: 1; font-size: 13px; font-weight: 700; }
    .bk-fab-panel-head .quota { font-size: 11px; opacity: 0.85; }
    .bk-fab-panel-head .close {
      background: rgba(255, 255, 255, 0.18);
      border: none; color: white;
      width: 28px; height: 28px;
      border-radius: 50%;
      cursor: pointer;
      font-size: 16px;
      transition: background var(--bk-dur-fast) var(--bk-ease-out);
    }
    .bk-fab-panel-head .close:hover { background: rgba(255, 255, 255, 0.30); }
    .bk-fab-msgs {
      flex: 1; overflow-y: auto;
      padding: 12px 14px;
      background: #FBFAFE;
      display: flex; flex-direction: column; gap: 8px;
      font-size: 13px;
    }
    .bk-fab-msg { padding: 8px 12px; border-radius: 12px; max-width: 80%; line-height: 1.45; word-wrap: break-word; }
    .bk-fab-msg.user { align-self: flex-end; background: var(--accent, var(--brand-primary, #2563EB)); color: white; }
    .bk-fab-msg.assistant { align-self: flex-start; background: white; border: 1px solid rgba(var(--bk-shadow-color) / 10%); color: var(--brand-text, #1E293B); }
    .bk-fab-msg.error { align-self: flex-start; background: #FEE2E2; border: 1px solid #FECACA; color: #991B1B; }
    .bk-fab-msg.hint { align-self: center; background: transparent; color: var(--brand-text-mute, #64748B); font-size: 11px; max-width: 100%; text-align: center; }
    .bk-fab-input-row {
      flex-shrink: 0;
      display: flex; gap: 8px;
      padding: 10px 12px;
      border-top: 1px solid rgba(var(--bk-shadow-color) / 8%);
      background: white;
    }
    .bk-fab-input-row textarea {
      flex: 1; resize: none;
      padding: 8px 10px;
      border: 1px solid #E5E7EB;
      border-radius: 10px;
      font-size: 13px;
      font-family: 'Plus Jakarta Sans', sans-serif;
      max-height: 80px; min-height: 36px;
    }
    .bk-fab-input-row textarea:focus {
      outline: none;
      border-color: var(--brand-a, #4361EE);
      box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.10);
    }
    .bk-fab-input-row button {
      flex-shrink: 0;
      padding: 8px 14px;
      background: var(--accent, var(--brand-primary, #2563EB));
      color: white; border: none; border-radius: 10px;
      font-size: 13px; font-weight: 700;
      cursor: pointer;
      font-family: 'Plus Jakarta Sans', sans-serif;
      transition: opacity var(--bk-dur-fast) var(--bk-ease-out);
    }
    .bk-fab-input-row button:hover { opacity: 0.9; }
    .bk-fab-input-row button:disabled { opacity: 0.55; cursor: not-allowed; }
    @media (max-width: 560px) {
      .bk-fab-panel {
        bottom: calc(88px + env(safe-area-inset-bottom, 0px)); right: 16px; left: 16px;
        width: auto; height: calc(100vh - 116px);
      }
      .bk-fab { bottom: calc(18px + env(safe-area-inset-bottom, 0px)); right: 18px; width: 52px; height: 52px; }
    }

/* ═══ F-J2-06 (Wave WS-Bundle-C) — tabular-nums sweep across price columns ═══
   Per DESIGN.md §3.6.3 — numeric columns must align digit-by-digit. The
   existing F-SHOP-006 (Wave H-6b) rule at L1419-1420 covered .price + some
   compose selectors but not all price-rendering surfaces. This meta-rule
   sweeps the remainder. Listing each selector explicitly (no attribute or
   wildcard selector) so cascade specificity remains predictable.
   Webster F-J2-06 (Wave I) — verified across PLP / cart / checkout / order detail. */
.pd-price-current,
.pd-price-compare,
.pd-sticky-price,
.cf-price,
.pd-related-price,
.pd-variant-price-base,
.pd-variant-price-sale,
.line-total,
.cart-row.total strong,
.cart-row.discount span:last-child,
.flash-price,
.acct-recent-price,
.acct-wl-price,
.co-sticky-cta .co-sticky-total-value,
#co-total,
#co-voucher-discount-amount {
  font-variant-numeric: tabular-nums;
  /* pk 2026-06-01 — Bebas Neue on every price display in this sweep.
     The ฿ symbol falls through --price-font to --body-font (Latin-only
     face); tabular-nums above still applies to the fallback ฿ glyph. */
  font-family: var(--price-font);
}

/* ═══ Bebas Neue price coverage (pk 2026-06-01) — PRICE DISPLAYS ONLY ═══
   The F-J2-06 sweep above + the luxury price group (~L210) already carry
   --price-font. This rule extends Bebas Neue to the remaining price
   surfaces the sweep doesn't enumerate, so EVERY ฿ amount across the
   storefront reads in one consistent face:
     • product card    .prod-card .price (incl. flash/promo sale price),
                        .promo-price, .flash-strike, .promo-strike
                        (the struck "was" price is still a price → included)
     • flash card       .flash-strip .flash-card .price (+ its sale/struck)
     • PDP              .pd-price, #pd-price, #pd-price-compare
     • cart sidebar     #cart-subtotal, #cart-total, .cart-row strong
     • checkout         #co-subtotal, #co-shipping-cost, #co-discount-amount,
                        .co-amount strong, #co-amount-value
     • account / pawn   .acct-order-total, .acct-reorder-total,
                        .pawn-installment .amount, #pawn-view-next-amount
     • voucher          .voucher-price, #vd-price
   EXCLUDED (not prices): product names (even with numbers), descriptions /
   highlights, the flash COUNTDOWN clock, sold/stock/discount-% badges, and
   the order-reference ID (.conf-ref strong). The ฿ glyph falls through the
   --price-font chain to --body-font (Bebas Neue is Latin-only). */
.prod-card .price,
.prod-card .price .flash-price,
.prod-card .price .promo-price,
.promo-price,
.flash-strike,
.promo-strike,
.flash-strip .flash-card .price,
.flash-strip .flash-card .flash-price,
.flash-strip .flash-card .promo-price,
.flash-strip .flash-card .flash-strike,
.flash-strip .flash-card .promo-strike,
.pd-price,
#pd-price,
#pd-price-compare,
#cart-subtotal,
#cart-total,
.cart-row strong,
#co-subtotal,
#co-shipping-cost,
#co-discount-amount,
.co-amount strong,
#co-amount-value,
.acct-order-total,
.acct-reorder-total,
.pawn-installment .amount,
#pawn-view-next-amount,
.voucher-price,
#vd-price {
  font-family: var(--price-font);
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════
   v3 GLOBAL (Mr.A WAVE V3-1, 2026-05-30) — appended per DESIGN.md v3
   ═══════════════════════════════════════════════════════════════ */
/* §3.7 — measure & text-wrap (impeccable harvest; was 0 usage) */
h1, h2, h3, .hero h1, .pd-info h1 { text-wrap: balance; }
.pd-info .desc, p { text-wrap: pretty; }
.prod-card .title, .pd-info h1, .pd-info .desc { overflow-wrap: anywhere; } /* long Thai compound product names */
/* §8.3 — canonical reduced-motion block (universal; file had only scoped overrides) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   E3 (P1-2) — NAV NOTIFICATIONS BELL + DRAWER
   Bell mirrors .nav-cart geometry (40px circular, --bg-soft). Drawer is a
   right-side panel per DESIGN.md §4.4 — slide-in animates `transform` only
   (never width/left); the universal reduced-motion block above neutralizes
   it. Elevation/backdrop mirror the auth-overlay + ai-panel patterns.
   ═══════════════════════════════════════════════════════════════════ */
    .nav-bell {
      position: relative;
      width: 40px; height: 40px; border-radius: 999px;
      background: var(--bg-soft); border: none; cursor: pointer;
      color: var(--text);
      display: inline-flex; align-items: center; justify-content: center;
      transition: background 160ms ease;
    }
    .nav-bell:hover  { background: var(--border); }
    .nav-bell:active { transform: translateY(1px); }
    .nav-bell:focus-visible {
      outline: 3px solid var(--brand-primary-ring, var(--accent));
      outline-offset: 2px;
    }
    /* pk 2026-06-03 (EOD review) — 18px to match cart/account (867c321 equalised
       all three to 18px, but this later same-specificity rule kept the bell 20px). */
    .nav-bell .bk-icon { width: 18px; height: 18px; }
    .nav-bell .badge {
      position: absolute; top: -4px; right: -4px;
      min-width: 18px; height: 18px; padding: 0 5px;
      border-radius: 999px; background: var(--accent); color: #fff;
      font-size: 11px; font-weight: 700; line-height: 18px;
      text-align: center; pointer-events: none;
    }
    .nav-bell .badge.hidden { display: none; }
    @media (min-width: 768px) { .nav-bell { width: 44px; height: 44px; } }

    /* Drawer root = full-viewport overlay host (backdrop + panel). Hidden
       via the [hidden] attr (display:none) so closed state fully removes it. */
    .bk-notif-drawer-root[hidden] { display: none; }
    .bk-notif-drawer-root {
      position: fixed; inset: 0; z-index: 9990;
    }
    .bk-notif-backdrop {
      position: absolute; inset: 0;
      background: rgba(var(--bk-shadow-color) / 45%);
      animation: bk-fade-in var(--bk-dur-enter, 200ms) linear;
    }
    .bk-notif-drawer {
      position: absolute; top: 0; right: 0; bottom: 0;
      width: min(420px, 92vw);
      background: var(--bg);
      box-shadow: -16px 0 48px -12px rgba(var(--bk-shadow-color) / 32%);
      border-left: 1px solid var(--border);
      display: flex; flex-direction: column;
      animation: bk-slide-in-right var(--bk-dur-enter, 200ms) var(--bk-ease-spring);
    }
    .bk-notif-drawer-head {
      flex: 0 0 auto;
      display: flex; align-items: center; gap: 10px;
      padding: 16px 18px; border-bottom: 1px solid var(--border);
    }
    .bk-notif-drawer-title {
      font-family: var(--display-font, inherit);
      font-size: 16px; font-weight: 600; flex: 1 1 auto;
    }
    .bk-notif-drawer-mark {
      flex: 0 0 auto; background: none; border: none; cursor: pointer;
      color: var(--accent); font-size: 13px; font-weight: 600; padding: 6px 8px;
      border-radius: 8px;
      transition: background 160ms ease;
    }
    .bk-notif-drawer-mark:hover { background: var(--bg-soft); }
    .bk-notif-drawer-mark[hidden] { display: none; }
    .bk-notif-drawer-x {
      flex: 0 0 auto;
      width: 36px; height: 36px; border-radius: 8px;
      background: none; border: none; cursor: pointer; color: var(--text);
      display: inline-flex; align-items: center; justify-content: center;
      transition: background 160ms ease;
    }
    .bk-notif-drawer-x:hover  { background: var(--bg-soft); }
    .bk-notif-drawer-x:active { transform: translateY(1px); }
    .bk-notif-drawer-x .bk-icon { width: 20px; height: 20px; }
    .bk-notif-drawer-body {
      flex: 1 1 auto; overflow-y: auto;
      padding: 8px 0;
      -webkit-overflow-scrolling: touch;
    }
    .bk-notif-loading { padding: 16px 18px; }
    .bk-notif-empty { padding: 48px 24px; }

    /* Notification rows — full-width tap target; unread carries an accent
       left-rail + bolder title. */
    .bk-notif-item {
      width: 100%; text-align: left;
      display: flex; gap: 12px; align-items: flex-start;
      padding: 14px 18px; min-height: 44px;
      background: none; border: none; cursor: pointer;
      border-bottom: 1px solid var(--border);
      border-left: 3px solid transparent;
      transition: background 160ms ease;
    }
    .bk-notif-item:hover { background: var(--bg-soft); }
    .bk-notif-item.is-unread { border-left-color: var(--accent); background: var(--bg-soft); }
    .bk-notif-icon {
      flex: 0 0 auto;
      width: 32px; height: 32px; border-radius: 999px;
      background: var(--bg); border: 1px solid var(--border);
      display: inline-flex; align-items: center; justify-content: center;
      color: var(--text-mute);
    }
    .bk-notif-item.is-unread .bk-notif-icon { color: var(--accent); border-color: var(--accent); }
    .bk-notif-icon .bk-icon { width: 16px; height: 16px; }
    .bk-notif-main { flex: 1 1 auto; min-width: 0; }
    .bk-notif-row {
      display: flex; align-items: baseline; gap: 8px; justify-content: space-between;
    }
    .bk-notif-title { font-size: 14px; font-weight: 500; color: var(--text); }
    .bk-notif-item.is-unread .bk-notif-title { font-weight: 700; }
    .bk-notif-when { flex: 0 0 auto; font-size: 12px; color: var(--text-mute); }
    .bk-notif-body {
      display: block; margin-top: 3px;
      font-size: 13px; line-height: 1.45; color: var(--text-mute);
    }

    @keyframes bk-slide-in-right {
      from { transform: translateX(100%); }
      to   { transform: translateX(0); }
    }
    @keyframes bk-fade-in {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
    /* body-scroll-lock while the drawer is open (mirrors body.ai-chat-open /
       body.bk-plp-drawer-open). Prevents touch-drag bleed to the page behind. */
    body.bk-notif-drawer-open { overflow: hidden; }

    /* ════════════════════════════════════════════════════════════════════
       PLP + PDP MOBILE LAYOUT REDESIGN (Webster 2026-06-01, per pk REF 1/2)
       All rules below are MOBILE-FIRST or mobile-scoped (≤767px) and/or
       luxury-scoped; desktop PLP/PDP layout is untouched. Cites:
         §2.4 semantic danger (sale/sold-out), §2.6 brand accent (gold),
         §3.5 sentence-case, §4.0 radius scale, §4.5 pill/segmented + ≥44px,
         §4.14 overlay-clipping (ribbon/veil clip to the rounded cover),
         §6.1/§6.2 elevation (sticky bars = card tier), §8.2 touch targets.
       ════════════════════════════════════════════════════════════════════ */

    /* ── REF 1 · product-card discount ribbon (red angled corner) ──────────
       DEPRECATED pk 2026-06-01 (editorial card redesign): the "Promotion"
       ribbon is REMOVED — promo is now signalled by the red strike/price in the
       price row, on every viewport. The renderer no longer emits .prod-ribbon,
       so this rule (incl. the mobile display:block below) never matches; kept
       harmless to avoid touching unrelated selectors. */
    .prod-ribbon {
      display: none !important;
      /* pk 2026-06-01 — flat bar pinned TOP-LEFT (was a diagonal top-right
         corner ribbon); "Promotion" centered within the bar. Top-left corner
         matches the image's 16px radius; inner (bottom-right) corner softened.
         Lives inside .img (overflow:hidden) so it clips cleanly to the corner. */
      position: absolute; top: 0; left: 0; z-index: 3;
      padding: 5px 12px; text-align: center;
      background: var(--bk-danger, #B91C1C);
      color: #fff;
      font-size: 11px; font-weight: 800; line-height: 1.2;
      letter-spacing: 0.04em;
      border-top-left-radius: 16px;
      border-bottom-right-radius: 10px;
      box-shadow: 0 1px 3px rgba(0,0,0,0.22);
      pointer-events: none;
      display: none;   /* mobile-only ribbon (REF 1); desktop shows the struck price */
    }
    @media (max-width: 767px) {
      .prod-ribbon { display: block; }
    }

    /* ── REF 1 · sold-out veil (translucent gray cover + centered label) ───
       Supersedes the small .bk-chip-sold-out on the cover; that chip is hidden
       wherever a veil is present so they never double up. */
    .prod-soldout-veil {
      position: absolute; inset: 0; z-index: 4;
      /* #2 (2026-06-01, pk) — black scrim, shown on ALL viewports (was a gray
         mobile-only veil that left desktop sold-out cards with no overlay). */
      display: flex;
      align-items: center; justify-content: center;
      background: rgba(0, 0, 0, 0.55);
      pointer-events: none;
    }
    .prod-soldout-veil > span {
      background: rgba(0, 0, 0, 0.62); color: #fff;
      font-size: 12px; font-weight: 700; letter-spacing: 0.06em;
      /* #2 — no uppercase: render "Sold out" / "ติดจอง" as written. */
      padding: 6px 14px; border-radius: var(--bk-radius-pill, 999px);
    }
    /* #2 — the veil now shows on every viewport, so the corner chip would
       double up; suppress it whenever the veil is present (was mobile-only). */
    .prod-card .img:has(.prod-soldout-veil) ~ .bk-chip-sold-out { display: none; }
    /* #3 (2026-06-01) — "ติดจอง / Reserved" veil (a temporary HOLD: item is in
       someone's unconfirmed order). pk 2026-06-02: DROP the amber scrim — the
       orange "ไม่สวย" — and use the SAME black scrim/pill as sold-out. The veil
       element keeps BOTH classes (.prod-soldout-veil .prod-reserved-veil), so
       with no background override here it inherits sold-out's black. Only the
       label text differs ("ติดจอง" vs "Sold out"). */

    /* ── REF 1 · cart-icon button square on the price row (already present
       as .prod-act-cart) — on luxury keep it visually paired with the price.
       No structural change; this just keeps the icon gold-tinted on luxury so
       the add affordance reads against the champagne palette (§2.6). */
    body[data-template="luxury"] .prod-card .prod-act-cart {
      background: var(--accent); color: #fff; border-color: var(--accent);
    }
    body[data-template="luxury"] .prod-card .prod-act-cart .bk-icon { color: #1A1A1A; }   /* pk 2026-06-01: black cart icon — white was invisible on the light button */

    /* ── REF 1 · "ขายได้ N ชิ้น" units-sold line — emitted by _plpCardsHtml
       via the soldHtml slot; restyle to a calm muted caption (was the bold
       amber best-seller line). §2.3 muted ink. */
    .bk-plp-grid-cards .prod-card .prod-sold {
      margin-top: 5px; font-size: 11px; font-weight: 500;
      color: var(--text-mute); letter-spacing: 0;
    }
    .bk-plp-grid-cards .prod-card .prod-sold::before { content: none; }

    /* ── REF 1 · mobile tabs (หมวดหมู่ / แบรนด์) + chip rail ───────────────
       Hidden ≥768px (desktop keeps the sidebar). Tabs = a 2-segment control
       with a gold underline on the active tab; chip rail = horizontal-scroll
       pills. §4.5 segmented/pill + ≥44px tap, §2.6 accent. */
    .bk-plp-mobile-filters { display: none; }
    @media (max-width: 767px) {
      .bk-plp-mobile-filters {
        display: block;
        margin: 0 0 14px 0;
      }
      /* The legacy FILTERS button + drawer trigger are replaced by the chip
         rail on mobile (REF 1 has no FILTERS button). Drawer markup stays for
         no-JS safety but its open trigger is hidden. */
      .bk-plp-mobile-filter-btn { display: none; }

      .bk-plp-mtabs {
        display: flex; gap: 0;
        border-bottom: 1px solid var(--border);
        margin-bottom: 12px;
      }
      .bk-plp-mtab {
        appearance: none; background: none; border: 0; cursor: pointer;
        flex: 0 0 auto;
        font-family: var(--display-font);
        font-size: 16px; font-weight: 600; letter-spacing: 0.01em;
        color: var(--text-mute);
        padding: 8px 18px 12px; margin-bottom: -1px;
        min-height: 44px;
        border-bottom: 2px solid transparent;
        transition: color var(--bk-dur-fast, 160ms) ease,
                    border-color var(--bk-dur-fast, 160ms) ease;
      }
      .bk-plp-mtab:hover { color: var(--text); }
      .bk-plp-mtab.is-active {
        color: var(--text); border-bottom-color: var(--accent);
      }
      .bk-plp-mtab:focus-visible {
        outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px;
      }
      body[data-template="luxury"] .bk-plp-mtab {
        text-transform: uppercase; letter-spacing: 0.08em; font-size: 13px;
      }

      .bk-plp-chip-rail {
        display: flex; gap: 8px; flex-wrap: nowrap;
        overflow-x: auto; overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding: 2px 0 4px;
        scroll-snap-type: x proximity;
      }
      .bk-plp-chip-rail::-webkit-scrollbar { display: none; }
      .bk-plp-chip {
        appearance: none; cursor: pointer; flex: 0 0 auto;
        scroll-snap-align: start;
        min-height: 40px; padding: 8px 16px;
        border-radius: var(--bk-radius-pill, 999px);
        border: 1px solid var(--border);
        background: var(--bg);
        color: var(--text);
        font-size: 13px; font-weight: 600; white-space: nowrap;
        transition: background var(--bk-dur-fast, 160ms) ease,
                    color var(--bk-dur-fast, 160ms) ease,
                    border-color var(--bk-dur-fast, 160ms) ease,
                    transform 120ms ease;
      }
      .bk-plp-chip:hover { border-color: var(--text-mute); }
      .bk-plp-chip:active { transform: translateY(1px); }
      .bk-plp-chip:focus-visible {
        outline: 2px solid var(--accent); outline-offset: 2px;
      }
      /* pk 2026-06-01 — selected chip was a near-black accent FILL with
         near-black text on luxury (the luxury --accent resolves dark, and the
         old `body[data-template="luxury"] .is-active { color:#1A1A1A }`
         override painted dark text on it → a solid black blob with invisible
         label). Redesigned to a light brand-tinted fill + dark text + a clear
         2px accent border + accent-tinted lift, so "selected" is obvious and
         legible on every template (§2.5 brand bleed, §2.7 contrast floor,
         §4.5 selected state). Body text ≥4.5:1: --text on a 12%-accent tint. */
      .bk-plp-chip.is-active {
        background: color-mix(in oklch, var(--accent) 12%, var(--bg));
        color: var(--text);
        border-color: var(--accent); border-width: 2px;
        padding: 7px 15px;   /* compensate the +1px border so size holds */
        font-weight: 700;
        box-shadow: 0 2px 8px color-mix(in oklch, var(--accent) 22%, transparent);
      }
      /* "ลดราคา" on-sale toggle chip (pk 2026-06-01) — distinct sale-red active
         state (vs the brand-accent taxonomy chips) so the offer toggle reads as a
         different axis. DESIGN.md §1.2 danger token, §2.5 selected-state lift. */
      .bk-plp-chip-sale.is-active {
        background: var(--bk-danger, #B91C1C);
        color: #fff; border-color: var(--bk-danger, #B91C1C);
        box-shadow: 0 2px 8px color-mix(in oklch, var(--bk-danger, #B91C1C) 22%, transparent);
      }
      body[data-template="luxury"] .bk-plp-chip-sale.is-active { color: #fff; }

      /* pk 2026-06-01 — hide the PLP breadcrumb ("Shop › All products") on
         mobile to free product space. Scoped to #page-plp so the PDP
         breadcrumb (#pd-breadcrumb) is untouched. The #page-plp container keeps
         its 32px top padding (inline), so the tabs still sit close under the
         top bar like REF 1 without the crumb. */
      #page-plp .pd-breadcrumb { display: none; }
    }

    /* ── REF 2 · PDP spec table → card surface (label-left / value-right) ──
       The mini-spec table becomes a contained light-surface card on mobile
       (REF 2 item 5). §6.1 card elevation, §4.0 radius. Desktop keeps the
       borderless inline list. */
    @media (max-width: 767px) {
      .pd-mini-specs {
        border-top: none;
        background: var(--bg-soft);
        border: 1px solid var(--border);
        border-radius: var(--bk-radius-lg, 12px);
        padding: 4px 16px;
        margin-top: 20px;
      }
      .pd-mini-specs h2, .pd-mini-specs h3 {
        margin: 14px 0 4px 0;
      }
      .pd-mini-row {
        grid-template-columns: auto 1fr; gap: 16px;
        align-items: baseline;
      }
      .pd-mini-row .val { text-align: right; }
    }

    /* ── REF 2 · PDP price row — red sale price + struck compare + gold heart.
       The heart already lives in .pd-icon-row (top of the info column); on
       mobile pull it onto the price baseline so the row reads
       "price · struck · ♥" like REF 2 item 4. The PDP price for an on-sale
       product is painted red by _pdpPaintPriceStock via the flash/promo
       classes; this rule pins the current price to danger-red on mobile so a
       discounted PDP matches the card treatment (§2.4). */
    @media (max-width: 767px) {
      .pd-header-row { margin-bottom: 8px; }
      .pd-price-stack { align-items: baseline; }
      .pd-price-compare { font-size: 14px; }
      /* gold wishlist heart on luxury (§2.6) */
      body[data-template="luxury"] #pd-heart-btn { color: var(--accent); }
      body[data-template="luxury"] #pd-heart-btn svg { fill: none; stroke: currentColor; }
    }

    /* ── REF 2 · description see-more clamp + gold toggle (item 6) ──────────
       Clamp is applied by JS only when the text overflows (.pd-desc-clamp);
       the toggle is gold (§2.6), sentence-case (§3.5), ≥44px (§8.2). */
    .pd-desc-clamp {
      display: -webkit-box;
      -webkit-line-clamp: 4;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
    .pd-desc-toggle {
      appearance: none; background: none; border: 0; cursor: pointer;
      display: inline-flex; align-items: center; gap: 5px;
      margin-top: 10px; padding: 8px 0; min-height: 44px;
      color: var(--accent); font: inherit; font-weight: 600; font-size: 14px;
    }
    .pd-desc-toggle:hover { text-decoration: underline; }
    .pd-desc-toggle:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px;
    }
    .pd-desc-toggle .pd-desc-chevron {
      width: 16px; height: 16px;
      transition: transform var(--bk-dur-fast, 160ms) ease;
    }
    .pd-desc-toggle .pd-desc-toggle-less { display: none; }
    #pd-desc.is-open ~ #pd-desc-toggle .pd-desc-toggle-more { display: none; }
    #pd-desc.is-open ~ #pd-desc-toggle .pd-desc-toggle-less { display: inline; }
    #pd-desc.is-open ~ #pd-desc-toggle .pd-desc-chevron { transform: rotate(180deg); }

    /* ── REF 2 · related products → horizontal-scroll row on mobile (item 7).
       Reuses the existing .pd-related-card markup; on mobile the grid becomes
       a snap-scroll rail of fixed-width cards (card style matches the PLP grid
       card: square cover, name, red price). Desktop keeps the 2/4-col grid. */
    @media (max-width: 767px) {
      /* Edge-bleed to the screen edge. The mobile .container gutter is 16px
         (the ≤767px ".container { padding: 0 16px }" rule wins on source order
         over the luxury 28px longhand), so the bleed mirrors exactly 16px — no
         page-wide horizontal scroll. Same gutter on every template at mobile. */
      .pd-related-grid {
        display: flex; gap: 12px;
        grid-template-columns: none;
        overflow-x: auto; overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scroll-snap-type: x proximity;
        scrollbar-width: none;
        margin: 0 -16px; padding: 4px 16px 6px;
      }
      .pd-related-grid::-webkit-scrollbar { display: none; }
      .pd-related-card {
        flex: 0 0 46%; max-width: 46%;
        scroll-snap-align: start;
      }
      .pd-related-name {
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
        overflow: hidden; min-height: 2.6em;
      }
    }

    /* ── REF 2 · contact-mode sticky bottom CTA (yellow "ติดต่อเรา", item 8) ─
       Body-level fixed bar; shown only on mobile for purchase_mode='contact'
       products (body.on-product-contact). Champagne-gold fill, full-width,
       safe-area aware. §6.2 sticky = card elevation. Hidden by default; the
       on-product-contact gate reveals it and suppresses the cart-mode bar. */
    .pd-contact-sticky {
      position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
      display: none;
      padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
      background: var(--bg);
      border-top: 1px solid var(--border);
      box-shadow: var(--bk-elev-card, 0 -2px 12px rgba(0,0,0,0.08));
    }
    body.on-product.on-product-contact .pd-contact-sticky { display: block; }
    /* contact-mode suppresses the cart-mode sticky add-to-bag bar so the two
       can never stack on the bottom edge. */
    body.on-product-contact .pd-sticky-bar { display: none !important; }
    .pd-contact-sticky-cta {
      appearance: none; cursor: pointer;
      width: 100%; min-height: 52px;
      border: 1px solid var(--accent);
      border-radius: var(--bk-radius-md, 10px);
      background: var(--accent); color: #1A1A1A;
      font: inherit; font-weight: 700; font-size: 16px; letter-spacing: 0.01em;
      display: inline-flex; align-items: center; justify-content: center;
      transition: filter var(--bk-dur-fast, 160ms) ease, transform 120ms ease;
    }
    .pd-contact-sticky-cta:hover { filter: brightness(0.96); }
    .pd-contact-sticky-cta:active { transform: translateY(1px); }
    .pd-contact-sticky-cta:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    /* On luxury the accent may be blue (no tenant gold); pin the contact CTA to
       the champagne-gold token so REF 2's yellow button reads on the luxury
       palette regardless of --accent. §2.6. */
    body[data-template="luxury"] .pd-contact-sticky-cta {
      background: var(--lx-gold, #C9A961);
      border-color: var(--lx-gold, #C9A961);
      color: #1A1A1A;
    }
    /* When the contact sticky bar is visible, give the PDP bottom breathing
       room so the related rail / footer aren't hidden behind it. */
    @media (max-width: 767px) {
      body.on-product-contact #page-product .container { padding-bottom: 96px; }
    }

    /* ═══════════════════════════════════════════════════════════════════
       EDITORIAL MOBILE PDP (pk ref 2026-06-01) — ≤767px ONLY
       Hero image (rounded bottom) with floating back / share FABs + dots →
       wishlist heart row → brand-avatar + name + price row → product name →
       description → (kept) spec table + related → sticky black Add-to-Cart.
       Desktop PDP (≥768px) is untouched — every rule here is inside the
       max-width:767px guard. The reorder uses display:contents on .pd-grid
       and .pd-info so their children become direct flex items of the PDP
       .container and can be `order`-sequenced without any DOM move (desktop
       keeps the 2-col grid + right-rail info column intact).
       DESIGN.md §2.6 (gold accent), §3.5 (sentence-case CTA), §4 (radius),
       §6.4 (z-rungs / floating elevation), §8.2 (≥44px tap).
       ═══════════════════════════════════════════════════════════════════ */

    /* Base (all viewports): heart-row + brand-avatar are mobile-only chrome;
       hide on desktop so the right-column info layout is unchanged. */
    .pd-heart-row { display: none; }
    .pd-brand-avatar { display: none; }
    .pd-hero-fab { display: none; }

    @media (max-width: 767px) {
      /* ── Container becomes the single editorial column ─────────────────
         row + wrap (NOT column): full-width items (flex-basis:100%) force a
         line break so the page reads top-to-bottom, while the brand row +
         price can share ONE wrapped line. align-content:flex-start keeps the
         wrapped rows packed at the top (no vertical stretch gaps). */
      #page-product .container {
        display: flex;
        flex-flow: row wrap;
        align-content: flex-start;
        align-items: center;
        padding-left: 0;
        padding-right: 0;
        padding-top: 0;
      }
      /* pk 2026-06-01 BUGFIX — the PDP loading skeleton (#pd-skeleton, class
         "container") was stuck visible on mobile: this `display:flex` (author)
         overrode the UA `[hidden]{display:none}`, so `_pdpShowSkeleton(false)`
         setting skel.hidden=true couldn't hide it (desktop had no such rule →
         worked there). Make [hidden] win again — hides the leftover skeleton
         AND correctly hides the real content while the skeleton is up. */
      #page-product .container[hidden] { display: none !important; }
      /* Lift grid + info children up to the container so the whole PDP is one
         orderable flex column (description currently lives OUTSIDE .pd-grid, so
         contents-hoisting is the only way to interleave it with .pd-info's
         spec table without a DOM move). */
      #page-product .pd-grid { display: contents; }
      #page-product .pd-info { display: contents; }

      /* Hide the desktop PDP topbar back-link + breadcrumb on mobile — the new
         floating hero ‹ / ⋮ own the top, per pk (cut the old PDP topbar). */
      #page-product .pd-breadcrumb { display: none; }

      /* pk ref 2026-06-01 — drop the GLOBAL site header on the mobile PDP so the
         square gallery sits flush at the very top (ref has only the floating
         ‹ back / ⋮ share on the image, no site header). Tightly scoped:
         body.on-product (set on the product route by gotoPage) + ≤767px ONLY,
         so the header stays on desktop AND on every other mobile page.
         CAVEAT: this removes mobile search / cart / bell / profile access while
         on a PDP — the floating ‹ (back) and the sticky Add-to-Cart pill remain
         the navigation/conversion path; pk asked for this ref look. */
      body.on-product .nav { display: none; }

      /* ── 1 · Gallery — SQUARE (1:1), rounded, swipe + tap-to-zoom ──────
         pk ref 2026-06-01: a square hero with rounded corners (was a tall
         ~58vh full-bleed crop). The slides keep the existing scroll-snap swipe
         carousel + dots + tap-to-zoom (base .pd-gal-slide has cursor:zoom-in +
         the zoom handler is wired by pdpRenderGallery) — only the ratio + radius
         change here. The gallery gets a small side gutter so the rounded square
         reads as a card, not a full-bleed crop. */
      #page-product .pd-grid > div:first-child { order: 1; flex-basis: 100%; }
      .pd-gallery-mobile {
        margin: 0 16px;
        position: relative;
        border-radius: 0;
        overflow: visible;
      }
      /* 1:1 square slides, rounded corners kept. overflow:hidden on each slide
         clips the img to the radius; the strip clips horizontally so adjacent
         square slides don't peek past the rounded edge during a swipe. */
      .pd-gallery-mobile .pd-gal-strip {
        gap: 0;
        border-radius: 22px;
        overflow-x: auto; overflow-y: hidden;
      }
      .pd-gallery-mobile .pd-gal-slide {
        flex: 0 0 100%;
        aspect-ratio: 1 / 1;
        height: auto;
        max-height: none;
        border-radius: 22px;
        background: var(--bg-soft);
      }
      .pd-gallery-mobile .pd-gal-slide img { object-position: center; }

      /* Floating hero controls — translucent white circles, ≥44px tap. */
      .pd-hero-fab {
        display: inline-flex; align-items: center; justify-content: center;
        position: absolute; top: 14px; z-index: 6;
        width: 40px; height: 40px;
        border: none; border-radius: 999px;
        background: rgba(255, 255, 255, 0.86);
        -webkit-backdrop-filter: saturate(140%) blur(8px);
        backdrop-filter: saturate(140%) blur(8px);
        color: #1A1A1A; cursor: pointer;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16);
        /* ≥44px AAA tap via an invisible inset ring (§8.2). */
        transition: transform var(--bk-dur-fast, 160ms) ease,
                    background-color var(--bk-dur-fast, 160ms) ease;
      }
      .pd-hero-fab::after {
        content: ""; position: absolute; inset: -3px; border-radius: 999px;
      }
      .pd-hero-fab-back  { left: 14px; }
      .pd-hero-fab-share { right: 14px; }
      .pd-hero-fab:hover  { background: rgba(255, 255, 255, 0.96); }
      .pd-hero-fab:active { transform: scale(0.94); }
      .pd-hero-fab:focus-visible {
        outline: 2px solid var(--accent); outline-offset: 2px;
      }
      .pd-hero-fab svg { width: 20px; height: 20px; display: block; }
      .pd-hero-fab-back svg { margin-right: 1px; }

      /* Dots centred near the BOTTOM of the hero image (overlay, not below). */
      .pd-gallery-mobile .pd-gal-dots {
        position: absolute; left: 0; right: 0; bottom: 16px;
        margin-top: 0; padding: 0; z-index: 5;
      }
      .pd-gallery-mobile .pd-gal-dot {
        background: rgba(255, 255, 255, 0.55);
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
      }
      .pd-gallery-mobile .pd-gal-dot.active {
        background: #fff; opacity: 1;
      }
      /* Zoom hint sits clear of the back FAB. */
      .pd-gallery-mobile .pd-gal-zoom-hint { top: auto; bottom: 14px; right: 14px; }

      /* ── Gutter for everything BELOW the hero (hero stays full-bleed) ── */
      .pd-heart-row,
      #page-product .pd-header-row,
      #page-product .pd-info h1,
      #page-product .pd-price-stack,
      #page-product .pd-mini-specs,
      #page-product .pd-value-props,
      #page-product .pd-highlight,
      #page-product #pd-description-section,
      #page-product #pd-condition-section,
      #page-product #pd-specs-section,
      #page-product #pd-related-section {
        margin-left: 16px;
        margin-right: 16px;
      }
      /* Sections that bleed to the edge override the gutter (related rail). */
      #page-product #pd-related-section .pd-related-grid {
        margin-left: -16px; margin-right: -16px;
      }

      /* ── Wishlist heart — own row, right-aligned, just below the image.
         Grouped with the gallery (order:1) so it sits between the gallery and
         the name+price row; flex-basis:100% forces its own line. (Not in pk's
         text info-list, but it's not the brand/stars — pk asked to keep it.) */
      .pd-heart-row {
        display: flex; justify-content: flex-end;
        order: 1; flex-basis: 100%;
        margin-top: 10px; margin-bottom: 2px;
      }
      .pd-heart-mobile {
        min-width: 44px; min-height: 44px;
        color: var(--accent);
      }
      body[data-template="luxury"] .pd-heart-mobile { color: var(--accent); }
      .pd-heart-mobile svg { width: 22px; height: 22px; fill: none; stroke: currentColor; }
      .pd-heart-mobile.is-on svg { fill: currentColor; }
      /* The original in-flow heart/share icon cluster is redundant on mobile
         (share is now the hero ⋮, wishlist is the heart row). Hide it. */
      #page-product .pd-header-row .pd-icon-row { display: none; }

      /* ── 3 · Brand row REMOVED ─────────────────────────────────────────
         pk ref 2026-06-01: "Olive and Oaks + icon ข้ามส่วนนี้ไปเลย" — skip the
         brand avatar + name entirely on mobile. The whole .pd-header-row (which
         held only the brand-id + the already-hidden icon-row) collapses; the
         PRICE (which used to float onto this row) is kept and relocated to sit
         beside the product name below. No rating/stars exist in the markup.
         Desktop keeps .pd-header-row (the rule is inside the ≤767px guard). */
      #page-product .pd-header-row { display: none; }

      /* ── 2 · Product name (LEFT) + price (RIGHT) on one row ──────────
         The name + price share a single wrapped line: name flexes to fill,
         price is pinned to the right edge on the same baseline. Both carry
         order:2 so they pack together right under the gallery. */
      #page-product .pd-info h1 {
        order: 2; flex: 1 1 auto; min-width: 0;
        font-size: clamp(24px, 7vw, 30px);
        line-height: 1.18; letter-spacing: -0.02em;
        margin: 14px 0 0 16px;
      }
      #page-product .pd-price-stack {
        order: 2; flex: 0 0 auto;
        margin: 14px 16px 0 auto; padding-left: 12px;
        align-items: baseline;
        align-self: flex-start;
      }

      /* ── NO rating/stars — nothing rendered (none in markup) ───────── */

      /* ── 3 · Highlight (gold-rule line) under the name+price ───────── */
      #page-product #pd-highlight       { order: 3; flex-basis: 100%; }

      /* ── 4 · Quick specs table — MOVED ABOVE the description (pk) ───── */
      #page-product .pd-mini-specs      { order: 4; flex-basis: 100%; margin-top: 14px; }

      /* ── 5 · Description (muted) — now BELOW the spec table ─────────── */
      #page-product #pd-description-section {
        order: 5; flex-basis: 100%;
        border-top: none; padding-top: 0; margin-top: 14px;
      }
      #page-product #pd-description-section h2 {
        font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
        text-transform: uppercase; color: var(--text-mute);
        margin-bottom: 8px;
      }
      #page-product #pd-description-section .description {
        color: var(--text-mute); font-size: 15px; line-height: 1.7;
      }

      /* ── 6 · Related rail + other below-the-fold sections last ─────── */
      #page-product #pd-related-section  { order: 6; flex-basis: 100%; }
      /* Value-props / condition / verbose-spec keep their place after related
         (they were already below the description; preserve that ordering). */
      #page-product .pd-value-props      { order: 7; flex-basis: 100%; }
      #page-product #pd-condition-section { order: 8; flex-basis: 100%; }
      #page-product #pd-specs-section     { order: 9; flex-basis: 100%; }

      /* Hide the inline (in-page) purchase affordances on mobile — the sticky
         black Add-to-Cart pill (cart-mode) / gold ติดต่อเรา (contact-mode) own
         the conversion path at the bottom edge (ref item 7). qty stepper is
         dropped on mobile (the sticky CTA adds 1; users adjust qty in cart). */
      #page-product .pd-options,
      #page-product .pd-qty-row,
      #page-product .pd-cta-stack,
      #page-product #pd-contact-cta { display: none; }

      /* Bottom breathing room so the related rail / sections clear the sticky
         pill (cart-mode). Contact-mode keeps its own 96px rule above. */
      #page-product .container { padding-bottom: 92px; }

      /* ── 7 · Sticky bottom CTA → full-width BLACK rounded pill ──────
         The cart-mode #pd-sticky-bar is restyled into a single full-width
         ink pill. It is ALWAYS visible on the mobile PDP (the recap img/name/
         price are hidden; the bar is just the CTA), so drop the scroll-gated
         fade — pk's ref shows a persistent Add-to-Cart. The desktop
         scroll-reveal behavior (≥768px) is untouched. */
      /* Always-on cart pill, EXCEPT: contact-mode (gold pill owns the edge) and
         while the add-to-cart feedback drawer is up (it hides the bar so the
         two don't stack — preserve that). */
      body.on-product:not(.on-product-contact):not(.cf-drawer-active) .pd-sticky-bar {
        display: flex !important;
        opacity: 1 !important;
        transform: none !important;
      }
      .pd-sticky-bar {
        left: 0; right: 0; bottom: 0;
        gap: 0; padding: 10px 16px;
        padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
        background: transparent;
        border-top: none;
        box-shadow: none;
      }
      /* Hide the recap thumbnail / name / mirrored-price — the editorial PDP
         already shows them above; the sticky is purely the action pill. */
      .pd-sticky-bar .pd-sticky-img,
      .pd-sticky-bar .pd-sticky-meta { display: none; }
      /* The black pill must out-specify body[data-template="luxury"] .btn-primary
         (white-bg/accent-text on luxury, (0,2,1)); the body[data-template] prefix
         here lifts these to (0,3,1) so the ink fill wins on every template. */
      body[data-template] .pd-sticky-bar .pd-sticky-cta,
      .pd-sticky-bar .pd-sticky-cta {
        flex: 1 1 auto; width: 100%; margin: 0;
        min-height: 54px; padding: 0 20px;
        border-radius: 16px;
        background: #1A1A1A; color: #fff;
        border: 1px solid #1A1A1A;
        font-family: inherit;
        font-size: 15px; font-weight: 600; letter-spacing: 0.01em;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
        transform: none;
        transition: transform var(--bk-dur-fast, 160ms) ease,
                    background-color var(--bk-dur-fast, 160ms) ease,
                    box-shadow var(--bk-dur-fast, 160ms) ease;
      }
      body[data-template] .pd-sticky-bar .pd-sticky-cta:hover:not(:disabled),
      .pd-sticky-bar .pd-sticky-cta:hover:not(:disabled) {
        background: #000; transform: none; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.26);
      }
      .pd-sticky-bar .pd-sticky-cta:active:not(:disabled) { transform: translateY(1px); }
      body[data-template] .pd-sticky-bar .pd-sticky-cta:disabled,
      .pd-sticky-bar .pd-sticky-cta:disabled {
        background: var(--text-mute); border-color: var(--text-mute); color: #fff;
        opacity: 0.7; cursor: not-allowed; box-shadow: none;
      }
      .pd-sticky-bar .pd-sticky-cta:focus-visible {
        outline: 2px solid var(--accent); outline-offset: 2px;
      }
      .pd-sticky-bar .pd-sticky-cta .bk-icon { width: 18px; height: 18px; }
      /* Contact-mode pill becomes the same full-width shape (gold fill kept). */
      .pd-contact-sticky {
        background: transparent; border-top: none; box-shadow: none;
        padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
      }
      .pd-contact-sticky-cta { border-radius: 16px; min-height: 54px; }
    }

    /* ════════════════════════════════════════════════════════════════════
       PDP SHARE PANEL (Webster 2026-06-01, pk ref)
       Replaces the bare native-share with a proper share panel:
       bottom-sheet on mobile / centered popover on desktop. Mirrors the
       openContactSheet() overlay+sheet grammar; uses the §6.1 elevation
       tokens, §6.4 z-ladder (--z-modal), and the --bk-ease-spring drawer
       motion. Markup built in shop.html pdpShare().
       ──────────────────────────────────────────────────────────────────── */
    .pd-share-overlay {
      position: fixed; inset: 0;
      z-index: var(--z-modal, 1300);
      background: rgba(var(--bk-shadow-color, 15 23 42) / 45%);
      display: flex; align-items: flex-end; justify-content: center;
      animation: bk-fade-in var(--bk-dur-enter, 200ms) ease;
    }
    .pd-share-sheet-card {
      background: var(--bg, #fff);
      width: 100%; max-width: 480px;
      border-radius: 20px 20px 0 0;
      padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px));
      box-shadow: 0 -16px 48px -12px rgba(var(--bk-shadow-color, 15 23 42) / 28%);
      max-height: 84vh; overflow-y: auto;
      animation: pd-share-slide-up 280ms var(--bk-ease-spring, cubic-bezier(0.34, 1.56, 0.64, 1));
    }
    @keyframes pd-share-slide-up {
      from { transform: translateY(100%); }
      to   { transform: translateY(0); }
    }
    .pd-share-head {
      display: flex; align-items: center; justify-content: space-between;
      margin-bottom: 18px;
    }
    .pd-share-title {
      margin: 0; font-size: 16px; font-weight: 700;
      color: var(--text, #0F172A); letter-spacing: -0.01em;
    }
    .pd-share-close {
      width: 32px; height: 32px; border-radius: 50%;
      border: none; background: var(--bg-soft, #F3F4F6);
      color: var(--text-mute, #64748B); cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center;
      transition: background-color 160ms ease, transform 160ms var(--bk-ease-spring, ease);
    }
    .pd-share-close .bk-icon { width: 16px; height: 16px; }
    .pd-share-close:hover { background: var(--border, #E5E7EB); }
    .pd-share-close:active { transform: scale(0.92); }
    .pd-share-close:focus-visible {
      outline: 2px solid var(--accent, #2563EB); outline-offset: 2px;
    }
    /* Social row — round buttons, icon over a label. Wraps on desktop;
       scrolls horizontally on a narrow phone if it would overflow. */
    .pd-share-row {
      display: flex; gap: 14px;
      margin-bottom: 18px;
      overflow-x: auto; -webkit-overflow-scrolling: touch;
      scrollbar-width: thin;
      padding-bottom: 2px;
    }
    .pd-share-btn {
      flex: 0 0 auto;
      display: flex; flex-direction: column; align-items: center; gap: 7px;
      background: none; border: none; padding: 0; cursor: pointer;
      width: 60px;
    }
    .pd-share-glyph {
      width: 52px; height: 52px; border-radius: 50%;
      display: inline-flex; align-items: center; justify-content: center;
      color: #fff;
      box-shadow: var(--bk-elev-card);
      transition: transform 180ms var(--bk-ease-spring, ease),
                  box-shadow 180ms ease, filter 180ms ease;
    }
    .pd-share-glyph .bk-icon { width: 24px; height: 24px; }
    .pd-share-btn:hover .pd-share-glyph { transform: translateY(-2px); filter: brightness(1.06); }
    .pd-share-btn:active .pd-share-glyph { transform: translateY(0) scale(0.94); }
    .pd-share-btn:focus-visible { outline: none; }
    .pd-share-btn:focus-visible .pd-share-glyph {
      outline: 2px solid var(--accent, #2563EB); outline-offset: 2px;
    }
    .pd-share-name {
      font-size: 11px; font-weight: 600; color: var(--text-mute, #64748B);
      line-height: 1.2; white-space: nowrap;
    }
    /* Brand fills (official-ish). IG uses its signature gradient. */
    .pd-share-line      .pd-share-glyph { background: #06C755; }
    .pd-share-facebook  .pd-share-glyph { background: #1877F2; }
    .pd-share-instagram .pd-share-glyph {
      background: radial-gradient(circle at 30% 110%, #FEDA75 0%, #FA7E1E 24%, #D62976 48%, #962FBF 72%, #4F5BD5 100%);
    }
    .pd-share-x         .pd-share-glyph { background: #000; }
    .pd-share-whatsapp  .pd-share-glyph { background: #25D366; }
    .pd-share-more      .pd-share-glyph {
      background: var(--bg-soft, #F3F4F6); color: var(--text-mute, #64748B);
      box-shadow: inset 0 0 0 1px var(--border, #E5E7EB);
    }
    /* Copy-URL row. */
    .pd-share-copy-row {
      display: flex; gap: 8px; align-items: stretch;
    }
    .pd-share-url {
      flex: 1 1 auto; min-width: 0;
      padding: 0 12px; height: 42px;
      border: 1px solid var(--border, #E5E7EB); border-radius: 10px;
      background: var(--bg-soft, #FAFAFA);
      color: var(--text-mute, #64748B); font-size: 13px;
      text-overflow: ellipsis;
    }
    .pd-share-url:focus-visible {
      outline: 2px solid var(--accent, #2563EB); outline-offset: 1px;
    }
    .pd-share-copy-btn {
      flex: 0 0 auto; padding: 0 18px; height: 42px;
      border: none; border-radius: 10px;
      background: var(--accent, #2563EB); color: var(--brand-primary-ink, #fff);
      font-size: 13px; font-weight: 700; cursor: pointer;
      transition: transform 160ms var(--bk-ease-spring, ease), filter 160ms ease;
    }
    .pd-share-copy-btn:hover { filter: brightness(1.06); }
    .pd-share-copy-btn:active { transform: scale(0.96); }
    .pd-share-copy-btn:focus-visible {
      outline: 2px solid var(--accent, #2563EB); outline-offset: 2px;
    }
    @media (prefers-reduced-motion: reduce) {
      .pd-share-overlay,
      .pd-share-sheet-card { animation: none; }
      .pd-share-glyph,
      .pd-share-close,
      .pd-share-copy-btn { transition: none; }
    }
    /* Desktop (≥768px) — center the panel as a floating popover instead of
       a bottom-anchored sheet. */
    @media (min-width: 768px) {
      .pd-share-overlay { align-items: center; }
      .pd-share-sheet-card {
        border-radius: 18px;
        max-width: 420px;
        box-shadow: var(--bk-elev-float);
        animation: bk-fade-in var(--bk-dur-enter, 200ms) ease;
      }
      .pd-share-row { flex-wrap: wrap; justify-content: center; overflow-x: visible; }
    }


    /* ═══════════════════════════════════════════════════════════════════
       DARK-THEME CONTRAST SWEEP (pk 2026-06-02) — palette-restricted to
       yellow/white/grey/dark-grey/black (red reserved for wishlist+price).
       One pass over every storefront zone (workflow wer4c2dwk: 6 auditors
       + per-finding adversarial verify). ROOT fix: .nav bar stayed white
       → its (already-dark) children now render correctly. All rules are
       body[data-theme-mode="dark"]-scoped; light themes untouched.
       ═══════════════════════════════════════════════════════════════════ */
    body[data-theme-mode="dark"] .nav { background: rgba(15,15,15,0.92); }
    body[data-theme-mode="dark"] .catalog-strip { color: var(--bg); }
    body[data-theme-mode="dark"] .prod-card .prod-act-buy { color: var(--bg); }
    body[data-theme-mode="dark"] .flash-countdown {
  background: var(--bg-soft);
  color: var(--text-mute);
}
    body[data-theme-mode="dark"] .flash-countdown.ended { background: var(--bg-soft); color: var(--text-mute); }
    body[data-theme-mode="dark"] .prod-card .prod-sold { color: var(--text-mute); }
    body[data-theme-mode="dark"] .bk-plp-filter-dot { color: var(--bg); }
    body[data-theme-mode="dark"] .pd-gal-nav { background: var(--bg-soft); border-color: var(--border); color: var(--text); }
body[data-theme-mode="dark"] .pd-gal-nav:hover { background: var(--border); }
    body[data-theme-mode="dark"] .pd-opt-pill {
  background: var(--bg-soft);
  color: var(--text);
  border-color: var(--border);
}
    body[data-theme-mode="dark"] .pd-highlight {
  background: var(--bg-soft);
  border-left-color: var(--accent);
  color: var(--text);
}
    body[data-theme-mode="dark"] .pd-cond-dot { background: var(--border); }
body[data-theme-mode="dark"] .pd-cond-step.past .pd-cond-dot { background: var(--text-mute); }
body[data-theme-mode="dark"] .pd-cond-step.active .pd-cond-dot { background: var(--accent); box-shadow: 0 0 0 4px rgba(250, 204, 21, 0.25); }
body[data-theme-mode="dark"] .pd-cond-line { background: var(--border); }
    body[data-theme-mode="dark"] .pd-sticky-bar { background: var(--bg-soft); }
body[data-theme-mode="dark"] .pd-sticky-img { background: var(--border); }
    body[data-theme-mode="dark"] .pd-share-x .pd-share-glyph { background: var(--bg-soft); box-shadow: inset 0 0 0 1px var(--border); }
    body[data-theme-mode="dark"] .cart-line { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .bk-stepper { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .co-card { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .co-addr-card { background: var(--bg-soft); border-color: var(--border); }
body[data-theme-mode="dark"] .co-addr-card.is-selected { background: color-mix(in oklch, var(--accent) 8%, var(--bg-soft)); border-color: var(--accent); }
body[data-theme-mode="dark"] .co-addr-tag { background: color-mix(in oklch, var(--accent) 16%, var(--bg-soft)); }
    body[data-theme-mode="dark"] .co-pm-row { background: var(--bg-soft); }
    body[data-theme-mode="dark"] .co-coupon-chip { background: var(--bg-soft); border-color: var(--border); color: var(--accent); }
body[data-theme-mode="dark"] .co-coupon-chip:hover { background: color-mix(in oklch, var(--accent) 8%, var(--bg-soft)); border-color: var(--accent); }
    body[data-theme-mode="dark"] .co-coupon-chip .info {
  background: rgba(250, 204, 21, 0.15); /* #FACC15 var(--accent) tint, was purple rgba(124,58,237,0.15) */
  color: var(--accent);
}
    body[data-theme-mode="dark"] .co-coupon-applied {
  background: color-mix(in oklch, var(--accent) 8%, var(--bg-soft));
  border: 1px solid color-mix(in oklch, var(--accent) 35%, var(--border));
}
    body[data-theme-mode="dark"] .co-coupon-input:focus { box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 30%, transparent); }
    body[data-theme-mode="dark"] .cart-row.discount span:last-child { color: var(--accent); }
    body[data-theme-mode="dark"] .cart-line .flash-saved { color: var(--accent); }
    body[data-theme-mode="dark"] .co-acc-section.is-done .co-acc-num { background: var(--accent); color: var(--bg); }
    body[data-theme-mode="dark"] .co-acc-section.is-open .co-acc-num { color: var(--bg); }
    body[data-theme-mode="dark"] .cart-sticky-cta {
  background: rgba(26,26,26,0.96); /* --bg-soft #1A1A1A, keeps blur translucency */
  border-top-color: var(--border);
  box-shadow: 0 -2px 14px rgba(0,0,0,0.5);
}
    body[data-theme-mode="dark"] .acct-card { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .acct-recent-row { background: var(--bg-soft); }
    body[data-theme-mode="dark"] .acct-reorder-row { background: var(--bg-soft); }
    body[data-theme-mode="dark"] .ai-panel { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .ai-log { background: var(--bg); }
    body[data-theme-mode="dark"] .ai-msg-bot { background: var(--bg-soft); color: var(--text); border-color: var(--border); }
    body[data-theme-mode="dark"] .ai-msg-typing { background: var(--bg-soft); border-color: var(--border); }
body[data-theme-mode="dark"] .ai-msg-typing .ai-dots i { background: var(--text-mute); }
    body[data-theme-mode="dark"] .ai-handle { background: var(--bg-soft); }
    body[data-theme-mode="dark"] .ai-form { background: var(--bg-soft); border-top-color: var(--border); }
    body[data-theme-mode="dark"] .footer-social a { background: var(--bg-soft); border-color: var(--border); }
    body[data-theme-mode="dark"] .os-badge.os-awaiting_payment { background: var(--bg-soft); color: var(--accent); }
    body[data-theme-mode="dark"] .os-badge.os-paid { background: var(--bg-soft); color: var(--text); }
    body[data-theme-mode="dark"] .os-badge.os-shipped { background: var(--bg-soft); color: var(--text); }
    body[data-theme-mode="dark"] .os-badge.os-completed { background: var(--bg-soft); color: var(--accent); }
    body[data-theme-mode="dark"] .os-badge.os-cancelled { background: var(--bg-soft); color: var(--text-mute); border: 1px solid var(--border); }
    body[data-theme-mode="dark"] .acct-tag { background: var(--bg-soft); color: var(--accent); }
    body[data-theme-mode="dark"] .acct-ok { color: var(--accent); }
    body[data-theme-mode="dark"] .acct-due { color: var(--accent); }
    body[data-theme-mode="dark"] .os-dot { background: var(--bg-soft); }
    body[data-theme-mode="dark"] .os-expand-block { background: var(--bg-soft); }
    body[data-theme-mode="dark"] #bk-toast.success { background: var(--bg-soft); color: var(--accent); border: 1.5px solid var(--accent); }
    body[data-theme-mode="dark"] #bk-toast.error { background: var(--bg-soft); color: var(--text); border: 1.5px solid var(--border); }
    body[data-theme-mode="dark"] #bk-toast.info { background: var(--bg-soft); color: var(--text); border-color: var(--border); }
    body[data-theme-mode="dark"] #bk-toast.warn {
  background: var(--bg-soft);
  color: var(--accent);
  border-color: var(--accent);
}
    body[data-theme-mode="dark"] .bk-cookie-banner {
  background: var(--bg-soft);
  border-color: var(--border);
}
body[data-theme-mode="dark"] .bk-cookie-text { color: var(--text); }
body[data-theme-mode="dark"] .bk-cookie-text a { color: var(--accent); }
body[data-theme-mode="dark"] .bk-cookie-btn-ghost {
  background: var(--bg);
  color: var(--text);
}
body[data-theme-mode="dark"] .bk-cookie-btn-ghost:hover { background: var(--border); }
body[data-theme-mode="dark"] .bk-cookie-btn-primary {
  background: var(--accent);
  color: var(--bg);
}
    body[data-theme-mode="dark"] .article-share a,
body[data-theme-mode="dark"] .article-share button {
  background: var(--bg-soft);
  border-color: var(--border);
}

    /* ── DARK-THEME readability sweep #2 (pk 2026-06-02 "ตัวอักษรสีเทาอ่อนบนสีขาว
       อ่านไม่ออก") — light-gray/near-white text that lands on a surface that
       STAYED LIGHT in dark mode. Audit wj80mn8r2-style sweep w3uooadz9 (7/7
       adversarially confirmed; each verified the surface is genuinely light in
       dark + the fix is palette-legal + won't darken a dark-surface twin).
       Per-surface: flip the surface dark where it has no reason to be white, OR
       force the text dark where the surface deliberately stays light. ───────── */
    /* (1) in-menu language toggle sits on the WHITE .nav-acct-menu card → the
       inactive button (color #9CA3AF) was light-gray-on-white. Darken JUST the
       inactive one; :not(.active) leaves the active dark pill (its own rule)
       untouched (without it the active button would go dark-on-dark). */
    body[data-theme-mode="dark"][data-template] .nav-acct-menu .nav-acct-lang button:not(.active) { color: #1A1A1A; }
    /* (2) checkout sticky CTA bar: base background rgba(255,255,255,0.96) was
       never flipped (its cart-page twin .cart-sticky-cta was). Flip the surface
       dark — its label (--text-mute) + value (--ink, now near-white) then read. */
    body[data-theme-mode="dark"][data-template] .co-sticky-cta {
      background: rgba(26,26,26,0.96); border-top-color: var(--border);
      box-shadow: 0 -2px 14px rgba(0,0,0,0.5);
    }
    /* (3) slip-preview card hard-codes #fff; its title/checklist (--text, now
       near-white) went white-on-white. Flip the surface dark. */
    body[data-theme-mode="dark"][data-template] .bk-slip-preview { background: var(--bg-soft); border-color: var(--border); }
    /* (4) slip ERROR banner is a light-pink (#FEF2F2) surface; its reason
       (--text→near-white) + hints (--text-mute→light-gray) were unreadable on
       it. Darken to the banner's own existing title red #7F1D1D (the banner is a
       pre-existing semantic-red component — not new decorative red). */
    body[data-theme-mode="dark"][data-template] .bk-slip-banner-reason,
    body[data-theme-mode="dark"][data-template] .bk-slip-banner-hints { color: #7F1D1D; }
    /* (5) flash-card strike-through original price on the light flash-card bar →
       light-gray on light. Darken the strike to a readable mid-gray. */
    body[data-theme-mode="dark"][data-template] .flash-strip .flash-card .flash-card-bar .price .flash-strike,
    body[data-theme-mode="dark"][data-template] .flash-strip .flash-card .flash-card-bar .price .promo-strike { color: #6B7280; }
    /* (6) upsell modal hard-codes #fff; flip dark + its sub-text → muted. */
    body[data-theme-mode="dark"][data-template] .bk-up-modal { background: var(--bg-soft); }
    body[data-theme-mode="dark"][data-template] .bk-up-sub { color: var(--text-mute); }
    /* (7) Google sign-in button is a white pill; flip dark so its label reads. */
    body[data-theme-mode="dark"][data-template] .auth-google { background: var(--bg-soft); color: var(--text); border-color: var(--border); }
    body[data-theme-mode="dark"][data-template] .auth-google:hover { background: var(--border); }

    /* ── DARK-THEME CTA + countdown CLARITY (pk 2026-06-02: "ทำปุ่มชำระเงิน /
       ยืนยันสั่งซื้อ + countdown ให้ชัดขึ้น"). Both were near-invisible on the
       #0F0F0F page: (1) the .co-cta-standout primary pill hard-codes #1A1A1A
       (blends into the page); (2) the flash bigclock digits/labels inherit the
       luxury rules #2A2118 / #6B4F1F (dark-brown → invisible on dark) because the
       early dark rule (L181) LOST the source-order tie to luxury (L4523-4527).
       Appended at END-OF-FILE with [data-template] specificity so these WIN.
       DARK-THEME ONLY (gated on [data-theme-mode="dark"]) → light theme untouched.
       The no-slip confirm dialog uses .co-cf-btn (NOT .co-cta-standout) so it is
       UNAFFECTED — pk: that popup is already good. */
    /* (1) Primary conversion CTAs (ชำระเงิน / ยืนยันสั่งซื้อ — all .co-cta-standout
       + the mobile sticky-bar primary) → accent (yellow on the Bold palette) pill
       + dark text so they POP on the near-black page (was the #1A1A1A pill).
       Specificity: this rule = (0,3,1); it TIES luxury .co-cta-standout (0,3,1,
       L3747) and wins on source order (EOF). NOTE: the luxury [disabled] rule
       (L3782) is ALSO (0,3,1) → this active rule would tie-and-beat it on order,
       turning a DISABLED button solid-accent — so the dark [disabled] override
       below (0,4,1) is REQUIRED to keep disabled = muted (verify wmw3bas91). */
    body[data-theme-mode="dark"][data-template] .co-cta-standout,
    body[data-theme-mode="dark"][data-template] .co-sticky-cta .btn-primary,
    body[data-theme-mode="dark"][data-template] .cart-sticky-cta .btn-primary {
      background: var(--accent); color: #1A1A1A; border: none;
      box-shadow: 0 8px 22px -8px rgba(0,0,0,0.6);
    }
    body[data-theme-mode="dark"][data-template] .co-cta-standout:hover,
    body[data-theme-mode="dark"][data-template] .co-sticky-cta .btn-primary:hover,
    body[data-theme-mode="dark"][data-template] .cart-sticky-cta .btn-primary:hover {
      background: #FFD84D; color: #1A1A1A;
    }
    /* DISABLED CTA (e.g. #co-place before address+payment, or during busy()) →
       stays a muted dark-grey pill, NOT the active accent. [disabled] lifts these
       to (0,4,1)/(0,5,1) so they out-specify the active rule above. */
    body[data-theme-mode="dark"][data-template] .co-cta-standout[disabled],
    body[data-theme-mode="dark"][data-template] .co-sticky-cta .btn-primary[disabled],
    body[data-theme-mode="dark"][data-template] .cart-sticky-cta .btn-primary[disabled] {
      background: var(--bg-soft); color: var(--text-mute); box-shadow: none;
    }
    /* (2) Flash countdown → bright + readable. Digits + colon → accent yellow
       (beats luxury #2A2118/#B79A63); the HRS/MIN/SEC labels → light grey (were
       luxury #6B4F1F dark-brown = invisible on dark). (0,4,1) beats luxury (0,3,1). */
    body[data-theme-mode="dark"][data-template] .flash-bigclock .fc-num,
    body[data-theme-mode="dark"][data-template] .flash-bigclock .fc-colon,
    body[data-theme-mode="dark"][data-template] .flash-countdown-text { color: var(--accent); }
    body[data-theme-mode="dark"][data-template] .flash-bigclock .fc-label { color: #E5E7EB; }


    /* ── #1 (pk 2026-06-02): checkout "fill the required fields" → a CENTERED,
       clearly-designed popup (was a top toast). Reuses .auth-overlay (.is-open
       = dim backdrop + centered). Card works in light + dark. ───────────────── */
    .co-req-card {
      background: #fff; color: var(--ink, #1A1A1A);
      border-radius: 18px; padding: 26px 22px; max-width: 360px; width: 100%;
      text-align: center; box-shadow: 0 24px 60px -12px rgba(0,0,0,0.40);
      display: flex; flex-direction: column; align-items: center; gap: 14px;
      font-family: var(--body-font, system-ui, sans-serif);
    }
    .co-req-ico {
      width: 54px; height: 54px; border-radius: 999px; flex-shrink: 0;
      background: color-mix(in oklch, var(--accent) 16%, #fff);
      color: var(--accent); display: flex; align-items: center; justify-content: center;
    }
    .co-req-ico svg { width: 28px; height: 28px; }
    .co-req-msg { font-size: 16px; font-weight: 600; line-height: 1.5; margin: 0; }
    .co-req-ok { min-width: 150px; justify-content: center; }
    body[data-theme-mode="dark"][data-template] .co-req-card { background: var(--bg-soft); color: var(--text); }
    body[data-theme-mode="dark"][data-template] .co-req-ico  { background: rgba(250,204,21,0.18); }

    /* ── #2 (pk 2026-06-02): the add-to-cart confirmation popup (.cf-drawer) →
       BLACK in dark mode (base .cf-drawer-inner is #fff white). ─────────────── */
    body[data-theme-mode="dark"][data-template] .cf-drawer-inner {
      background: var(--bg-soft); color: var(--text);
      border-top: 1px solid var(--border);
      box-shadow: 0 -8px 30px -10px rgba(0,0,0,0.7);
    }
    body[data-theme-mode="dark"][data-template] .cf-drawer-inner .cf-name { color: var(--text); }
    body[data-theme-mode="dark"][data-template] .cf-drawer-inner .cf-variant,
    body[data-theme-mode="dark"][data-template] .cf-drawer-inner .cf-qty { color: var(--text-mute); }

    /* ── #4 (pk 2026-06-02): flash-card POP — the front card scales out briefly
       to make the price "feel big". Driven by _startFlashPopLoop() which rotates
       the rail one card at a time + pops the new first. Easing + 650ms keep it
       gentle (pk "อย่าให้เร็วเกินไป"). ──────────────────────────────────────── */
    @keyframes bk-flash-pop {
      0%   { transform: scale(1); }
      35%  { transform: scale(1.075); }
      100% { transform: scale(1); }
    }
    /* the popped card lifts ABOVE its neighbours so the scale-up reads as
       "popping out" rather than nudging the row. */
    .flash-card.bk-flash-pop { animation: bk-flash-pop 650ms cubic-bezier(0.22, 1, 0.36, 1); position: relative; z-index: 3; }
    @media (prefers-reduced-motion: reduce) { .flash-card.bk-flash-pop { animation: none; } }
    /* #4 recheck (pk 2026-06-02): the rail is overflow-x:auto → overflow-y
       computes to auto and CLIPS a vertical scale-up, so the pop was invisible.
       Give the rail top/bottom breathing room so the scaled card shows. */
    .flash-strip-cards { padding-top: 12px; padding-bottom: 12px; }
