/* ============================================================
   VoltzHub v2 — Storefront design system
   Clean white • one accent (violet) • rounded cards • soft shadows
   ============================================================ */

/* ============================================================
   RESPONSIVE ARCHITECTURE — BREAKPOINT CONTRACTS  (read first)

   There are TWO responsive systems here, and they are only loosely
   coupled. Before changing any breakpoint, know which one you are in.

   1) CSS media queries — this file plus 13 page-level <style> blocks.
   2) JS app mode — detectAppMode() in assets/js/ui.js stamps `app-mode`
      on <html> and EIGHT pages then render a COMPLETELY DIFFERENT DOM
      through separate renderers (.ah-* home, .apc-* category, .ap-*
      product, .aa-* account, .ac-* cart, .a2-* auth).

   ── CONTRACT 1: 760px is mirrored in JS and MUST stay in sync ──
   `760` appears in assets/js/ui.js (the app-mode media query and its
   change listener) AND in this file. They are the same threshold
   expressed twice. Changing one without the other desynchronises the
   class-based and query-based halves of app mode. Nothing enforces it.

   ── CONTRACT 2: the 768px blocks deliberately EXCEED 760 ──
   Some blocks are written `max-width: 768px` but target APP-DOM classes
   (.ah-*, .apc-*, .ap-*), including the 44x44 touch targets. They only
   work because 768 > 760, so they fully cover the app band. Their
   761-768 portion is inert by design. Narrowing any of them to 720
   would silently break app-DOM sizing across the whole 721-760 band.

   ── CONTRACT 3: narrow-width desktop rules are NOT dead code ──
   At <=760 the default is app mode, so desktop markup (.pgrid, .pd2,
   .dorder, .catx, .cart2-item ...) does not exist and those rules look
   dead. They are not: `?app=0` persists in localStorage indefinitely
   and forces the desktop DOM at any width. Deleting them breaks that
   opt-out, which has no UI to clear it.

   ── CONTRACT 4: max-width blocks STACK ──
   Every block here is unbounded below, so a 390px phone matches the
   1000, 900, 800, 768, 760 AND 720 blocks at once. Four pairs resolve
   by SOURCE ORDER at equal specificity — .catx-grid, .pgrid,
   .hcat-grid, .pd2. Reordering them inverts the layout.

   ── CONTRACT 5: app mode is not just "small" ──
   An installed PWA is app mode at ANY width, including 1400px, where
   no max-width block applies. Resizing a browser window does not
   reproduce this; test standalone separately.

   ── One-off breakpoints (single page, no app-mode coupling) ──
   420 variant picker · 520 assistant · 560 notif sheet / app / order
   receipt · 800 contact · 820 auth + order · 860 cart
   ============================================================ */
:root {
  /* VoltzHub original blue identity */
  --brand: #1a3fb5;
  --brand-dark: #1533a0;
  --brand-soft: #eef2ff;
  --brand-grad: linear-gradient(135deg, #1533a0, #4f6df5);
  --ink: #111827;
  /* Alias of --ink. 85 declarations across the storefront and the admin panel
     were written as var(--ink-1) against a token that was never defined, so
     every one of them was an invalid declaration the browser discarded — the
     element silently inherited its parent's colour instead. Defining the alias
     here repairs all 85 at once, in the one :root both bundles consume.
     🛑 It is an ALIAS, not a new colour. --ink-1 must always equal --ink; if
        they ever diverge, every one of those 85 sites shifts at once. */
  --ink-1: var(--ink);
  --ink-2: #4b5563;
  /* Muted supporting text. Was #9ca3af, which measured 2.54:1 on white — below
     the WCAG AA floor for normal text (4.5:1) and below even the large-text
     floor (3:1). It carries review counts, dates, metadata, helper text and
     empty-state copy across 375 declarations, so it was the single largest
     readability defect in the product.
     #6b7280 measures 4.83:1 on white and 4.56:1 on --bg-2, the only two
     surfaces it is ever painted on. It is the next step up the same grey ramp
     the palette already uses (--ink #111827 / --ink-2 #4b5563), so no new
     colour family is introduced and the muted-grey character is preserved. */
  --ink-3: #6b7280;
  --line: #e5e7eb;
  --bg: #ffffff;
  --bg-2: #f8f8fb;
  --green: #16a34a;
  --green-soft: #ecfdf5;
  --amber: #f59e0b;
  --red: #dc2626;
  --red-soft: #fef2f2;
  --radius: 16px;
  --radius-sm: 10px;
  /* Identifier font — order numbers, refs, ticket numbers.
     Deliberately the SAME UI font as the rest of the app, not a monospace:
     identifiers should read as native interface text, not as code. Plus Jakarta
     Sans is already loaded at 400/600/700/800, so the 600 used for identifiers
     is a real face — nothing is synthetically emboldened.
     One hook for all identifier typography; see the REFERENCE-NUMBER block at
     the end of this file and the twin at the end of admin/admin.css. */
  --font-id: var(--font);
  --shadow: 0 1px 2px rgba(17, 24, 39, .05), 0 8px 24px rgba(17, 24, 39, .06);
  --shadow-lg: 0 4px 12px rgba(17, 24, 39, .08), 0 16px 48px rgba(17, 24, 39, .12);
  --font: "Plus Jakarta Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; overflow-y: scroll; }
body { font-family: var(--font); color: var(--ink); background: var(--bg); line-height: 1.55; font-size: 15px; }
img { max-width: 100%; display: block; }
/* safety net: any image with both a fixed width & height covers instead of stretching */
img[width][height] { object-fit: cover; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; font-size: 14px; }

.wrap { max-width: 1200px; margin: 0 auto; padding: 0 16px; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none; border-radius: 12px; font-weight: 700; font-size: 14px;
  padding: 11px 20px; transition: .18s; white-space: nowrap;
}
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-dark); transform: translateY(-1px); box-shadow: 0 6px 18px rgba(26, 63, 181, .35); }
.btn-outline { background: #fff; color: var(--ink); border: 1.5px solid var(--line); }
.btn-outline:hover { border-color: var(--brand); color: var(--brand); }
.btn-soft { background: var(--brand-soft); color: var(--brand); }
.btn-soft:hover { background: #e9defc; }
.btn-green { background: #22c55e; color: #fff; }
.btn-green:hover { background: #16a34a; }
.btn-lg { padding: 14px 28px; font-size: 15px; border-radius: 14px; }
.btn-sm { padding: 7px 14px; font-size: 13px; border-radius: 10px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none !important; box-shadow: none !important; }

/* ---------- header ---------- */
.topbar { background: var(--brand-grad); color: #fff; font-size: 12.5px; font-weight: 600; text-align: center; padding: 7px 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.header { position: sticky; top: 0; z-index: 50; background: rgba(255, 255, 255, .92); backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); }
.header-in { display: flex; align-items: center; gap: 14px; height: 66px; }
.logo { display: flex; align-items: center; gap: 9px; font-weight: 800; font-size: 20px; letter-spacing: -.4px; flex-shrink: 0; }
.logo-mark { width: 36px; height: 36px; display: grid; place-items: center; background: none; }
.logo-mark svg { width: 34px; height: 34px; }
.logo b { color: var(--ink); } .logo b span { color: var(--brand); }

/* `safe center` is the load-bearing part: a plain `center` that overflows spills
   equally over BOTH neighbours — the logo on the left, the currency + icons on the
   right — which is exactly the collision this replaces. `safe` falls back to
   start-alignment the moment content stops fitting; browsers that do not know the
   keyword keep the plain `center` on the line above. overflow-x then keeps any
   remaining overflow scrollable rather than painted over the logo, matching how
   `.navrow-in` already handles the same problem. */
.hnav { display: flex; align-items: center; justify-content: center; justify-content: safe center; gap: 2px; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.hnav::-webkit-scrollbar { display: none; }
.hnav a { padding: 8px 15px; border-radius: 999px; font-size: 13.5px; font-weight: 600; color: var(--ink-2); white-space: nowrap; transition: .14s; }
.hnav a:hover { color: var(--brand); }
.hnav a.active { background: var(--bg-2); color: var(--ink); }
.hnav-right { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.hnav-right a { padding: 8px 13px; border-radius: 999px; font-size: 13.5px; font-weight: 600; color: var(--ink-2); white-space: nowrap; transition: .14s; }
.hnav-right a:hover { color: var(--brand); }
.hnav-right:empty { display: none; }
.search { position: relative; }
.search input { width: 100%; padding: 11px 16px 11px 42px; border: 1.5px solid var(--line); border-radius: 999px; background: var(--bg-2); outline: none; transition: .18s; }
.search input:focus { border-color: var(--brand); background: #fff; box-shadow: 0 0 0 4px var(--brand-soft); }
.search .s-ico { position: absolute; left: 15px; top: 0; bottom: 0; color: var(--ink-3); display: flex; align-items: center; pointer-events: none; }
.search-drop { position: absolute; top: calc(100% + 8px); left: 0; right: 0; background: #fff; border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow-lg); overflow: hidden; display: none; max-height: 380px; overflow-y: auto; z-index: 60; }
.search-drop.open { display: block; }
.search-item { display: flex; gap: 10px; align-items: center; padding: 10px 14px; transition: .12s; }
.search-item:hover { background: var(--bg-2); }
.search-item img { width: 44px; height: 34px; border-radius: 8px; object-fit: cover; }
.search-item .si-n { font-weight: 700; font-size: 13.5px; }
.search-item .si-p { font-size: 12.5px; color: var(--brand); font-weight: 700; }

.hactions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.hbtn { position: relative; width: 42px; height: 42px; border-radius: 12px; border: none; background: transparent; display: grid; place-items: center; color: var(--ink-2); transition: .15s; }
.hbtn:hover { background: var(--bg-2); color: var(--brand); }
.cart-badge { position: absolute; top: 3px; right: 3px; background: var(--brand); color: #fff; font-size: 10.5px; font-weight: 800; min-width: 17px; height: 17px; padding: 0 4px; border-radius: 999px; display: grid; place-items: center; }
.hbtn-dot { position: absolute; top: 7px; right: 8px; width: 9px; height: 9px; border-radius: 50%; background: #ef4444; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(239,68,68,.35); }
#notifBell svg { width: 21px; height: 21px; display: block; transform: translateY(.5px); }
#notifBell { padding: 0; }

/* ===== Notifications panel (website + app) ===== */
.nsheet-ov { position: fixed; inset: 0; z-index: 3000; background: rgba(15,23,42,.42); backdrop-filter: blur(2px); opacity: 0; transition: opacity .24s; display: flex; justify-content: flex-end; align-items: flex-start; }
.nsheet-ov.open { opacity: 1; }
.nsheet { width: 380px; max-width: 92vw; max-height: 82vh; margin: 74px 18px 0 0; background: #fff; border: 1px solid var(--line); border-radius: 18px; box-shadow: 0 24px 60px rgba(15,23,42,.28); display: flex; flex-direction: column; overflow: hidden; transform: translateY(-10px) scale(.98); opacity: 0; transition: .24s cubic-bezier(.2,.8,.2,1); }
.nsheet.open { transform: none; opacity: 1; }
.nsheet-h { display: flex; align-items: center; justify-content: space-between; padding: 15px 18px; border-bottom: 1px solid var(--line); }
.nsheet-h b { font-size: 16px; }
.nsheet-x { width: 32px; height: 32px; border: none; background: var(--bg-2); border-radius: 9px; cursor: pointer; color: var(--ink-2); font-size: 15px; display: grid; place-items: center; }
.nsheet-x:hover { background: #eef0f4; }
.nsheet-b { overflow-y: auto; padding: 6px; }
.nitem { display: flex; gap: 12px; align-items: flex-start; padding: 12px 12px; border-radius: 12px; text-decoration: none; color: inherit; transition: background .12s; }
.nitem + .nitem { border-top: 1px solid var(--line); border-radius: 0; }
a.nitem:hover { background: var(--bg-2); }
.nitem .ic { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 11px; display: grid; place-items: center; font-size: 19px; }
.nitem .tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.nitem .tx b { font-size: 14px; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.nitem .tx > span { font-size: 12.5px; color: var(--ink-2); line-height: 1.4; white-space: pre-wrap; overflow-wrap: anywhere; }
.nitem .tx .t { font-size: 11px; color: var(--ink-3, #94a3b8); margin-top: 2px; }
.nempty { text-align: center; padding: 42px 24px; display: flex; flex-direction: column; align-items: center; gap: 7px; }
.nempty .e { font-size: 34px; }
.nempty b { font-size: 15px; }
.nempty span { font-size: 13px; color: var(--ink-2); }
@media (max-width: 560px) {
  .nsheet-ov { align-items: center; justify-content: center; padding: 20px; }
  .nsheet { width: 100%; max-width: 400px; margin: 0; max-height: 76vh; border-radius: 22px; box-shadow: 0 30px 70px rgba(15,23,42,.35); transform: translateY(14px) scale(.97); }
}

/* ===== App search overlay ===== */
.aserch-ov { position: fixed; inset: 0; z-index: 3200; background: #fff; display: flex; flex-direction: column; transform: translateY(100%); transition: transform .24s cubic-bezier(.2,.8,.2,1); }
.aserch-ov.open { transform: none; }
.aserch-top { display: flex; align-items: center; gap: 10px; padding: calc(12px + env(safe-area-inset-top)) 14px 12px; border-bottom: 1px solid var(--line); }
.aserch-back { width: 40px; height: 40px; border-radius: 12px; background: var(--bg-2); display: grid; place-items: center; border: none; cursor: pointer; color: var(--ink-1); flex-shrink: 0; }
.aserch-field { flex: 1; display: flex; align-items: center; gap: 8px; background: var(--bg-2); border-radius: 12px; padding: 0 13px; height: 44px; margin: 0; }
.aserch-field svg { width: 18px; height: 18px; color: var(--ink-3); flex-shrink: 0; }
.aserch-field input { flex: 1; border: none; background: none; outline: none; font: inherit; font-size: 14px; color: var(--ink-1); }
/* Same field focus treatment as .apc-search, and for the same structural
   reason: the input is `border: none; background: none`, so the WRAPPER
   (.aserch-field — grey pill, radius 12px, 44px tall, icon + input) is the
   thing the user sees as the field. A halo on the input would ring an
   invisible inner box. This is the full-screen search that slides up from the
   bottom nav in app mode, the last search control still falling through to the
   global accessibility outline. Only the halo is reused — .aserch-field has no
   border to recolour and adding one would change its 44px height. */
/* 🛑 PRODUCTS SEARCH HAS NO FOCUS INDICATOR — deliberate product decision.
   Same rule as the other two Products searches: the full-screen product search
   must look pixel-identical focused and unfocused.
   ⚠️ WCAG 2.4.7 failure on this field only, requested explicitly. */
.aserch-field:focus-within,
.aserch-field input:focus,
.aserch-field input:focus-visible {
  outline: none;
  box-shadow: none;
  transition: none;
}
.aserch-body { flex: 1; overflow-y: auto; padding: 6px 0 calc(14px + env(safe-area-inset-bottom)); }
.aserch-item { display: flex; align-items: center; gap: 12px; padding: 11px 16px; text-decoration: none; color: inherit; }
.aserch-item:active { background: var(--bg-2); }
.aserch-item img { width: 46px; height: 46px; border-radius: 11px; object-fit: cover; background: var(--brand-soft); flex-shrink: 0; }
.aserch-item .n { font-size: 13.5px; font-weight: 700; color: var(--ink-1); }
.aserch-item .p { font-size: 12px; color: var(--brand); font-weight: 800; margin-top: 2px; }
.aserch-empty { text-align: center; color: var(--ink-3); padding: 54px 24px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.aserch-empty .e { font-size: 38px; }
.aserch-empty span { font-size: 13.5px; }
.aserch-track { display: inline-block; margin-top: 6px; font-size: 13px; font-weight: 700; color: var(--brand); background: var(--brand-soft); padding: 9px 16px; border-radius: 12px; text-decoration: none; }

/* ===== Variant picker (centered modal, quick-add for multi-option products) ===== */
.vpick-ov { position: fixed; inset: 0; z-index: 3300; background: rgba(15,23,42,.5); backdrop-filter: blur(3px); opacity: 0; transition: opacity .2s; display: flex; align-items: center; justify-content: center; padding: 20px; }
.vpick-ov.open { opacity: 1; }
.vpick { position: relative; width: 100%; max-width: 440px; background: #fff; border-radius: 24px; padding: 24px 22px 22px; box-shadow: 0 30px 70px rgba(15,23,42,.35); transform: translateY(14px) scale(.97); opacity: 0; transition: .26s cubic-bezier(.2,.8,.2,1); max-height: 88vh; overflow-y: auto; }
.vpick-ov.open .vpick { transform: none; opacity: 1; }
.vpick-x { position: absolute; top: 15px; right: 15px; width: 32px; height: 32px; border: none; background: var(--bg-2); border-radius: 50%; color: var(--ink-2); cursor: pointer; display: grid; place-items: center; }
.vpick-x:hover { background: #eceef3; color: var(--ink-1); }
.vpick-head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; padding-right: 34px; }
.vpick-img { width: 54px; height: 54px; border-radius: 14px; overflow: hidden; background: var(--brand-soft); flex-shrink: 0; }
.vpick-img img { width: 100%; height: 100%; object-fit: cover; }
.vpick-ttl small { font-size: 11.5px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase; color: var(--brand); display: block; margin-bottom: 3px; }
.vpick-ttl b { font-size: 18px; font-weight: 800; letter-spacing: -.3px; line-height: 1.15; }
.vpick-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.vpick-opt { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; background: #fff; border: 1.5px solid var(--line); border-radius: 16px; padding: 14px 14px 13px; cursor: pointer; text-align: left; transition: .16s; }
.vpick-opt:hover, .vpick-opt:active { border-color: var(--brand); background: var(--brand-soft); transform: translateY(-2px); box-shadow: 0 8px 20px rgba(37,99,235,.14); }
.vpick-opt .ol { font-size: 13.5px; font-weight: 800; color: var(--ink-1); }
/* 🛑 The label is the ONLY variable-height thing in the option, and everything
   below it (price, stock, add) is pushed down one line per extra wrapped line.
   Two options sitting side by side in the grid therefore showed their prices at
   different heights whenever their labels wrapped differently — 41px vs 60px vs
   79px measured, i.e. exactly one .ol line-height apart each time.

   `margin-bottom: auto` makes the LABEL absorb the option's spare vertical
   space instead of the price group inheriting the offset. Grid rows already
   stretch every option in a row to the same height, so the price/stock/add
   block now lands at an identical distance from the bottom for all of them.

   Chosen over clamping .ol to a fixed line count because that would either
   truncate long plan names or reserve empty space in options whose labels all
   fit on one line — changing the option height. This reserves nothing and
   leaves the TALLEST option pixel-identical (its spare space is already 0), so
   only the short-label options move, and only to line up with it. */
.vpick-opt .ol { margin-bottom: auto; }
.vpick-opt .op { font-size: 17px; font-weight: 800; color: var(--brand); margin-top: 4px; }
/* 🛑 Second cause, independent of the label. .op holds the price and — only on
   SALE variants — a nested .ow strike-through at a smaller font. As inline
   content that second span altered .op's line box, so a discounted option's
   price sat ~2px off a full-price one, and at the narrow 2-column width .ow
   wrapped onto its own line and threw it out by 15px.
   Making .op a baseline flex row pins both spans to one non-wrapping line, so
   .op has the same height whether or not the variant is on sale. Baseline (not
   center) keeps the strike-through sitting on the price's baseline exactly as
   the inline version did, so nothing moves horizontally or visually. */
.vpick-opt .op { display: flex; align-items: baseline; white-space: nowrap; }
.vpick-opt .ow { font-size: 11.5px; color: var(--ink-3); text-decoration: line-through; margin-left: 6px; font-weight: 600; }
.vpick-opt .os { font-size: 10.5px; font-weight: 700; color: #16a34a; margin-top: 2px; }
.vpick-opt .os.lo { color: #b45309; }

/* currency pill for app-mode gradient headers */
.hdr-cur { display: inline-flex; align-items: center; gap: 5px; height: 34px; padding: 0 13px; border-radius: 11px; background: rgba(255,255,255,.18); border: none; color: #fff; font: inherit; font-weight: 800; font-size: 12.5px; cursor: pointer; flex-shrink: 0; }
.hdr-cur svg { width: 12px; height: 12px; opacity: .85; }

/* ===== Currency picker (centered modal) ===== */
.curpick-ov { position: fixed; inset: 0; z-index: 3300; background: rgba(15,23,42,.5); backdrop-filter: blur(3px); opacity: 0; transition: opacity .2s; display: flex; align-items: center; justify-content: center; padding: 20px; }
.curpick-ov.open { opacity: 1; }
.curpick { width: 100%; max-width: 380px; background: #fff; border-radius: 24px; box-shadow: 0 30px 70px rgba(15,23,42,.35); transform: translateY(14px) scale(.97); opacity: 0; transition: .26s cubic-bezier(.2,.8,.2,1); max-height: 82vh; display: flex; flex-direction: column; overflow: hidden; }
.curpick-ov.open .curpick { transform: none; opacity: 1; }
.curpick-h { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px 14px; border-bottom: 1px solid var(--line); }
.curpick-h b { font-size: 15.5px; }
.curpick-x { width: 32px; height: 32px; border: none; background: var(--bg-2, #f4f5f8); border-radius: 50%; color: var(--ink-2); font-size: 14px; cursor: pointer; display: grid; place-items: center; }
.curpick-b { overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.curpick-opt { display: flex; align-items: center; gap: 12px; width: 100%; border: 1.5px solid transparent; background: none; border-radius: 12px; padding: 13px 14px; font: inherit; font-size: 14px; font-weight: 700; color: var(--ink-1); cursor: pointer; text-align: left; transition: .12s; }
.curpick-opt:hover { background: var(--bg-2, #f4f5f8); }
.curpick-opt.on { color: var(--brand); background: var(--brand-soft); border-color: var(--brand); }
.curpick-opt .c { min-width: 42px; }
.curpick-opt .s { flex: 1; color: var(--ink-3); font-weight: 600; }
.curpick-opt .ck { color: var(--brand); font-weight: 800; }
.vpick-opt .oadd { margin-top: 10px; width: 100%; text-align: center; background: var(--brand); color: #fff; font-size: 11.5px; font-weight: 800; padding: 8px 0; border-radius: 10px; opacity: .92; }
.vpick-opt:hover .oadd { opacity: 1; }
/* One-off: the variant-picker sheet, the narrowest breakpoint in the codebase.
   UI-generated markup, so it is present in BOTH app and web renders. */
/* One-off: the variant-picker sheet, the narrowest breakpoint in the codebase.
   UI-generated markup, so it is present in BOTH app and web renders.

   🛑 Below 420 the option turns into a WRAPPING ROW, so the price is misaligned
   by a different mechanism than the column layout above: label, price, stock
   and add all share one flex line, and the label's WIDTH decides where the line
   breaks. A short label left the price on line 1, a longer one pushed it to
   line 2 or 3 — measured at 19 / 41 / 60px within the same sheet.

   `flex-basis: 100%` gives the label its own line unconditionally, so the price
   group always begins on the next line and every option resolves to the same
   offset. `margin-bottom: 0` cancels the column-layout rule above, which would
   otherwise beat `align-items: center` and top-align the label on its line. */
@media (max-width: 420px) { .vpick-grid { grid-template-columns: 1fr; } .vpick-opt { flex-direction: row; align-items: center; flex-wrap: wrap; } .vpick-opt .ol { flex-basis: 100%; margin-bottom: 0; } .vpick-opt .oadd { width: auto; margin: 0 0 0 auto; padding: 8px 14px; } }
/* custom currency dropdown (replaces the native select) */
.cur-dd { position: relative; }
.cur-btn { display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--line); background: var(--bg-2, #f4f5f8); border-radius: 999px; padding: 8px 12px 8px 15px; font: inherit; font-weight: 700; font-size: 13px; color: var(--ink-1); cursor: pointer; transition: border-color .15s, box-shadow .15s, background-color .15s; }
.cur-btn svg { width: 12px; height: 12px; color: var(--ink-3); transition: transform .18s; }
.cur-btn:hover { border-color: var(--brand); background: #fff; }
.cur-dd.open .cur-btn { border-color: var(--brand); background: #fff; box-shadow: 0 0 0 3px var(--brand-soft); }
.cur-dd.open .cur-btn svg { transform: rotate(180deg); }
.cur-menu { position: absolute; top: calc(100% + 8px); right: 0; min-width: 156px; background: #fff; border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 16px 40px rgba(15,23,42,.16); padding: 6px; opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity .16s, transform .16s, visibility .16s; z-index: 300; max-height: 320px; overflow-y: auto; }
.cur-dd.open .cur-menu { opacity: 1; visibility: visible; transform: none; }
.cur-opt { display: flex; align-items: center; gap: 10px; width: 100%; border: none; background: none; border-radius: 10px; padding: 9px 11px; font: inherit; font-size: 13px; font-weight: 700; color: var(--ink-1); cursor: pointer; text-align: left; transition: background .12s; }
.cur-opt:hover { background: var(--bg-2, #f4f5f8); }
.cur-opt.on { color: var(--brand); background: var(--brand-soft); }
.cur-opt .c { min-width: 36px; }
.cur-opt .s { flex: 1; color: var(--ink-3); font-weight: 600; }
.cur-opt .ck { color: var(--brand); font-weight: 800; }
.cur-select { -webkit-appearance: none; -moz-appearance: none; appearance: none; border: 1px solid var(--line); background-color: var(--bg-2, #f4f5f8); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; border-radius: 12px; padding: 10px 32px 10px 14px; font-weight: 700; font-size: 13px; color: var(--ink-1); outline: none; cursor: pointer; }

.navrow { border-top: 1px solid #f1f2f6; }
.navrow-in { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; }
.navrow-in::-webkit-scrollbar { display: none; }
.navlink { padding: 10px 14px; font-size: 13.5px; font-weight: 600; color: var(--ink-2); border-radius: 10px 10px 0 0; white-space: nowrap; }
.navlink:hover, .navlink.active { color: var(--brand); }

.hamb { display: none; }
.mobile-menu { display: none; position: fixed; inset: 0; z-index: 100; }
.mobile-menu.open { display: block; }
.mm-back { position: absolute; inset: 0; background: rgba(17, 24, 39, .45); }
.mm-panel { position: absolute; top: 0; left: 0; bottom: 0; width: min(320px, 85vw); background: #fff; padding: 20px; overflow-y: auto; animation: slideIn .22s ease; }
@keyframes slideIn { from { transform: translateX(-100%); } to { transform: translateX(0); } }
.mm-panel a { display: block; padding: 13px 12px; font-weight: 600; border-radius: 12px; }
.mm-panel a:hover { background: var(--brand-soft); color: var(--brand); }

/* ---------- hero slider ---------- */
.hero { margin-top: 18px; }
.slider { position: relative; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.slides { display: flex; transition: transform .55s cubic-bezier(.4, 0, .2, 1); }
.slide { min-width: 100%; }
.slide picture { display: block; width: 100%; }
/* 🛑 The hero image and its loading skeleton MUST be sized by the SAME rule.
   The skeleton used to be a bare `height:300px` block that matched neither
   breakpoint (desktop hero is ~500px, mobile ~231px), so the first paint was
   always the wrong size and the banner visibly jumped when it arrived.
   Keep .hero-skel on every aspect-ratio rule for .slide img — that pairing is
   what guarantees the reserved box is exact and CLS stays at 0. */
.slide img, .hero-skel { width: 100%; aspect-ratio: 1440/600; display: block; }
.slide img { object-fit: cover; }
.hero-skel { border-radius: inherit; }
/* fade the artwork in once decoded. Opt-in via .fade, which initSlider() adds:
   if JS never runs the images stay fully visible instead of invisible. */
.slider.fade .slide img { opacity: 0; transition: opacity .32s ease; }
.slider.fade .slide img.rdy { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .slider.fade .slide img { transition: none; } }
.dots { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); display: flex; gap: 7px; }
.dot { width: 8px; height: 8px; border-radius: 99px; background: rgba(255, 255, 255, .55); border: none; transition: .25s; padding: 0; }
.dot.on { width: 24px; background: #fff; }
.snav { position: absolute; top: 50%; transform: translateY(-50%); width: 38px; height: 38px; border-radius: 99px; border: none; background: rgba(255, 255, 255, .85); color: var(--ink); font-size: 17px; display: grid; place-items: center; opacity: 0; transition: .2s; }
.slider:hover .snav { opacity: 1; }
.snav.prev { left: 12px; } .snav.next { right: 12px; }

/* ===== MOBILE HERO — immersive banner (≤768px only; desktop untouched) =====
   SIZED TO THE REAL ARTWORK. The live banners are 1280×720 (16:9) for BOTH the
   desktop image and the `imgM` phone source, so this frame stays near 16:9. A
   portrait frame here would pad a wide image with huge empty bars (which is
   exactly what a 4:5 attempt did), and cropping to fill is not acceptable.
   16/10 gives the banner more presence than the original 16/9 while leaving
   only a few px of backdrop above and below.

   For a genuinely tall, immersive hero, upload a PORTRAIT phone banner in
   Admin → Home page: the `imgM` field already exists and is served through
   <source media="(max-width:720px)">. Then this ratio can go portrait.

   `object-fit: contain` guarantees artwork is never cropped, whatever ratio is
   uploaded; the backdrop shows only where a banner is off-ratio.

   `aspect-ratio` + `min-height` reserve the full box before the image loads, so
   there is no layout shift while it downloads.

   The `body .ah …` duplicates are REQUIRED, not redundant: index.html's own
   <style> block sets `.ah .slide img { aspect-ratio: 16/9 }` / `.ah .hero` /
   `.ah .dots` and is parsed AFTER this file, so an equal-specificity selector
   would lose on source order. `body` lifts these above it. */
@media (max-width: 768px) {
  .hero,
  body .ah .hero { margin: 16px -8px 22px; }   /* -8 cancels half of .wrap's 16px side padding */

  .slider,
  body .ah .slider { border-radius: 18px; box-shadow: 0 12px 28px rgba(17, 24, 39, .16); }

  /* BOX METRICS — image and skeleton together, so the space reserved before the
     image loads is byte-for-byte the space it ends up occupying. */
  .slider .slide img, .hero-skel,
  body .ah .slider .slide img, body .ah .hero-skel {
    aspect-ratio: 1 / 1;       /* square hero. NOTE: the live banners are still
                                  1280x720 (16:9), so `contain` below letterboxes them
                                  until 1200x1200 art is uploaded to imgM. */
    object-position: center;   /* explicit, so a future edit cannot drift it */
    /* min-height:210px and max-height:46vh REMOVED — both existed only to make
       the old 7:5 box workable. With 16:9 a 338px-wide phone computes 190px tall,
       so a 210px floor would force the box taller than the ratio and reintroduce
       the very bars this change removes. */
    display: block;
  }
  /* PAINTING — image only; the skeleton keeps its shimmer gradient. */
  .slider .slide img,
  body .ah .slider .slide img {
    object-fit: contain;       /* never crop — bars are the deliberate, temporary
                                  cost until square artwork replaces the 16:9 files */
    background: #0f1729;
  }

  /* dots: larger, centred, clear of the artwork */
  .dots,
  body .ah .dots { bottom: 12px; gap: 10px; }
  .dot { width: 9px; height: 9px; position: relative; }
  .dot.on { width: 26px; }
  /* Touch target without changing the dot's visual size. Vertical inset goes to
     -18px so the pad clears 44px (9 + 18 + 18 = 45). The HORIZONTAL inset stays
     -8px on purpose: the dots sit 10px apart, so a 44px-wide pad would swallow
     its neighbours and make the outer dots unreachable. 25x45 is the widest
     honest target here; going wider needs more spacing, i.e. a redesign. */
  .dot::after { content: ""; position: absolute; inset: -18px -8px; }
}

/* ---------- sections ---------- */
.section { margin: 42px 0; }
.sec-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 18px; gap: 12px; }
.sec-head h2 { font-size: 22px; font-weight: 800; letter-spacing: -.4px; }
.sec-head .see-all { color: var(--brand); font-weight: 700; font-size: 13.5px; white-space: nowrap; }
.sec-head .see-all:hover { text-decoration: underline; }

/* home categories — modern icon-chip tiles */
.hcat-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; }
.hcat { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; background: var(--bg-2); border: none; border-radius: 16px; padding: 18px 10px; transition: .18s; }
.hcat .ic { width: 48px; height: 48px; flex-shrink: 0; border-radius: 15px; background: var(--brand-soft); color: var(--brand); display: grid; place-items: center; transition: .18s; }
.hcat .ic svg { width: 23px; height: 23px; }
.hcat .ic .cc-emoji { font-size: 22px; }
.hcat b { font-size: 12.5px; letter-spacing: -.2px; line-height: 1.25; }
.hcat:hover { background: var(--brand-soft); transform: translateY(-3px); }
.hcat:hover .ic { background: var(--brand-grad); color: #fff; transform: scale(1.06); }

/* product grid & cards */
.pgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.pcard { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; transition: .22s; position: relative; }
.pcard:hover { box-shadow: var(--shadow-lg); transform: translateY(-4px); border-color: transparent; }
.pcard-img { position: relative; overflow: hidden; }
.pcard-img img { width: 100%; aspect-ratio: 4/3; object-fit: cover; transition: .35s; }
.pcard:hover .pcard-img img { transform: scale(1.05); }
.badge { position: absolute; top: 10px; left: 10px; background: var(--red); color: #fff; font-size: 11.5px; font-weight: 800; padding: 4px 9px; border-radius: 8px; }
.badge.oos { background: #6b7280; }
.pcard-body { padding: 13px 14px 15px; display: flex; flex-direction: column; flex: 1; gap: 4px; }
.pc-cat { font-size: 11px; font-weight: 700; color: var(--brand); text-transform: uppercase; letter-spacing: .6px; }
.pc-name { font-weight: 700; font-size: 14.5px; line-height: 1.3; }
.pc-desc { font-size: 12.5px; color: var(--ink-2); display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; white-space: normal; word-break: break-word; }
.pc-rate { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-2); margin-top: 2px; }
.stars { color: var(--amber); font-size: 12px; letter-spacing: 1px; }
.pc-foot { margin-top: auto; padding-top: 10px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pc-price { display: flex; flex-direction: column; }
.pc-price .from { font-size: 10.5px; color: var(--ink-3); font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
.pc-price .now { font-weight: 800; font-size: 16px; color: var(--ink); }
.pc-price .old { font-size: 12px; color: var(--ink-3); text-decoration: line-through; font-weight: 600; }

/* ============================================================
   HOMEPAGE PRODUCT CARD — compact, browse-optimised variant
   ------------------------------------------------------------
   🛑 SCOPED TO #home ON PURPOSE. UI.productCard() is ONE component shared by
      four surfaces: index.html (.pgrid and .prow), products.html (#grid) and
      the "More in this category" strip on product.html. `#home` is the id of
      index.html's <main> and exists nowhere else, so every rule below applies
      to the homepage alone — the shop listing and the product page keep the
      description and the original proportions untouched. Editing the shared
      template instead would have stripped descriptions from all four.
   ============================================================ */

/* 1 — the description does not render on the homepage. `display: none` removes
   it from the box tree entirely, so no height is reserved for it. */
#home .pc-desc { display: none; }

/* 1b — NO RATING ROW, NO RESERVED SPACE.
   .pc-rate is rendered conditionally (`r.count ? … : ""`), so a product with
   zero reviews emits no element at all and the title simply moves up.
   🛑 An earlier version reserved a 23px invisible spacer here (an always-present
      .pc-desc re-shown via :has(), sized to match the rating row) so every card
      shared one baseline. That is exactly the reserved blank area that reads as
      a placeholder under unrated products — do not re-add it. Prices and buttons
      stay aligned the simple way instead: .pc-foot is pinned to the bottom of
      the card (see 2 below), which needs no reservation anywhere above it. */

/* 1c — the price block is the OTHER variable: `From` and the struck-through
   original are optional lines above .now, so an on-sale card was taller than a
   flat-priced one and its button sat lower. Fixing the block height and
   bottom-aligning it means .now always lands on the same baseline, and the
   button — centred in .pc-foot — lands with it. 40px holds the tallest case
   (old 12px x 1.35 = 16.2 + now 19px x 1.15 = 21.9 = 38.1). */
#home .pc-price { height: 40px; justify-content: flex-end; }
#home .pc-price .from, #home .pc-price .old { line-height: 1.35; }
#home .pc-price .now { line-height: 1.15; }

/* 2 — content sits top-down; the footer is pinned to the bottom.
   🛑 `margin-top: auto` on .pc-foot is what keeps prices and buttons aligned
      across a row WITHOUT reserving a rating row above them. A card with no
      reviews just has one fewer element and its title moves up naturally; the
      surplus lands below the content, where the price already is.
      Do not swap this back to `justify-content: space-between` — that spreads
      the surplus between the groups, which pushes the title down and only
      looked right because a spacer was faking a rating row on every card. */
#home .pcard-body { padding: 14px 15px 15px; gap: 8px; }
#home .pc-foot { margin-top: auto; padding-top: 12px; }

/* 3 — category demoted to a label. It was --brand blue at 11px/700, which put
   it in direct competition with the product name for first fixation. Muted ink
   with wider tracking reads as a category tag instead of a headline. */
#home .pc-cat { font-size: 10.5px; font-weight: 700; color: var(--ink-3); letter-spacing: .7px; }

/* 4 — the name is the hero, and is the reason heights stay equal.
   🛑 `height: 2.56em` (2 lines x 1.28 line-height) is load-bearing: it reserves
      exactly two lines whether the name needs one or two, so a long title can
      never push the card taller and a short one leaves no ragged gap. em-based
      so it tracks font-size if that is ever changed. */
#home .pc-name { font-size: 18px; font-weight: 800; line-height: 1.28; letter-spacing: -.45px;
                 color: var(--ink); height: 2.56em; margin-bottom: 2px; display: -webkit-box;
                 -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* 5 — rating promoted from metadata to a trust signal, sitting directly under
   the title where it fills the space the description used to occupy. Stars up
   12 -> 15px (+25%), numerals up 12 -> 13.5px and weighted.
   NOTE: the score and the count share one text node in the template, so they
   cannot be weighted differently without a markup wrapper. Both are set at 700
   against amber stars rather than one bold / one light. */
#home .pc-rate { display: flex; align-items: center; gap: 7px; font-size: 13.5px;
                 font-weight: 700; color: var(--ink); margin-top: 0; }
#home .pc-rate .stars { font-size: 15px; letter-spacing: 2px; color: var(--amber); }

/* 6 — price anchors the lower half; FROM stays a whisper above it. */
#home .pc-price .now { font-size: 19px; font-weight: 800; letter-spacing: -.6px; color: var(--ink); }
#home .pc-price .from { font-size: 10px; letter-spacing: .5px; }

/* 7 — CTA: taller, wider, softer corner, optically centred against the price. */
#home .pc-foot .btn { padding: 9px 17px; border-radius: 12px; font-size: 13px; }

/* 8 — hover: 4px lift was enough to read as a jump. 2px plus a deeper shadow
   and a border that brightens instead of vanishing is the restrained version. */
#home .pcard { transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1),
                           transform .28s cubic-bezier(.4, 0, .2, 1),
                           border-color .28s cubic-bezier(.4, 0, .2, 1); }
#home .pcard:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); border-color: #dfe3ea; }

/* horizontal product row */
.prow { display: grid; grid-auto-flow: column; grid-auto-columns: calc((100% - 48px) / 4); gap: 16px; overflow-x: auto; padding-bottom: 8px; scroll-snap-type: x mandatory; scrollbar-width: thin; }
.prow > * { scroll-snap-align: start; }

/* trust badges */
.trust { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.trust-card { display: flex; gap: 14px; align-items: flex-start; background: var(--bg-2); border-radius: var(--radius); padding: 20px; }
.trust-ico { width: 46px; height: 46px; flex-shrink: 0; border-radius: 14px; background: #fff; box-shadow: var(--shadow); display: grid; place-items: center; font-size: 22px; }
.trust-card b { display: block; font-size: 14.5px; margin-bottom: 2px; }
.trust-card p { font-size: 13px; color: var(--ink-2); }

/* custom order CTA */
.cta-block { background: var(--brand-grad); border-radius: 20px; padding: 44px 28px; text-align: center; color: #fff; position: relative; overflow: hidden; }
.cta-block::before { content: ""; position: absolute; width: 300px; height: 300px; border-radius: 50%; background: rgba(255, 255, 255, .08); top: -140px; right: -80px; }
.cta-block h2 { font-size: 26px; font-weight: 800; letter-spacing: -.5px; margin-bottom: 8px; }
.cta-block p { opacity: .92; max-width: 520px; margin: 0 auto 22px; }
.cta-btns { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.btn-white { background: #fff; color: var(--brand); }
.btn-white:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(0, 0, 0, .18); }
.btn-wa { background: #22c55e; color: #fff; }
.btn-wa:hover { background: #16a34a; transform: translateY(-1px); }

/* ---------- footer ---------- */
/* light footer — original VoltzHub theme */
.footer { margin-top: 60px; background: var(--bg-2); color: var(--ink-2); border-top: 1px solid var(--line); }
.footer-in { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: 32px; padding: 46px 0 36px; }
.footer h4 { color: var(--ink); font-size: 14px; margin-bottom: 14px; letter-spacing: .3px; }
.footer a { display: block; font-size: 13.5px; padding: 4px 0; color: var(--ink-2); }
.footer a:hover { color: var(--brand); }
.footer p { font-size: 13.5px; color: var(--ink-2); }
.f-logo { display: flex; align-items: center; gap: 9px; font-weight: 800; font-size: 19px; color: var(--ink); margin-bottom: 12px; }
.socials { display: flex; gap: 10px; margin-top: 14px; }
.socials a { width: 38px; height: 38px; border-radius: 10px; background: #fff; border: 1px solid var(--line); color: var(--ink-2); display: grid; place-items: center; padding: 0; transition: .15s; }
.socials a svg { width: 17px; height: 17px; }
.socials a:hover { background: var(--brand); border-color: var(--brand); color: #fff; transform: translateY(-2px); }
.pay-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.pay-badges span { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 5px 11px; font-size: 12px; font-weight: 600; color: var(--ink-2); }
.footer-bottom { border-top: 1px solid var(--line); padding: 20px 0; font-size: 13px; color: var(--ink-3); display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.footer-bottom .fb-pay { color: var(--ink-2); }
@media (max-width: 720px) { .footer-bottom { flex-direction: column; text-align: center; gap: 8px; } }

/* shop layout: category sidebar (desktop) / slide-in drawer (mobile) */
.shop-layout { display: grid; grid-template-columns: 250px 1fr; gap: 22px; align-items: start; margin-bottom: 60px; }
.shop-layout > * { min-width: 0; }
.side-cats { position: sticky; top: 96px; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 10px; box-shadow: var(--shadow); }
.side-cats h4 { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 1.3px; color: var(--ink-3); padding: 10px 12px 6px; }
.scat { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 11px; font-weight: 600; font-size: 13.5px; color: var(--ink-2); transition: .13s; }
.scat .sc-i { width: 30px; height: 30px; flex-shrink: 0; border-radius: 9px; background: var(--bg-2); display: grid; place-items: center; color: var(--brand); transition: .13s; }
.scat .sc-i svg { width: 16px; height: 16px; }
.scat .sc-i .cc-emoji { font-size: 15px; }
.scat .cnt { margin-left: auto; font-size: 11px; font-weight: 700; color: var(--ink-3); }
.scat:hover { background: var(--bg-2); color: var(--ink); }
.scat.on { background: var(--brand-soft); color: var(--brand); }
.scat.on .sc-i { background: var(--brand); color: #fff; }
.mm-panel .scat { display: flex; align-items: center; padding: 11px 12px; }
/* mobile: swipeable category pills instead of the sidebar */
.cat-pills { display: none; }
.cpill { display: inline-flex; align-items: center; gap: 7px; padding: 9px 15px; border-radius: 999px; border: 1.5px solid var(--line); background: #fff; font-size: 12.5px; font-weight: 700; color: var(--ink-2); white-space: nowrap; flex-shrink: 0; transition: .15s; }
.cpill svg { width: 14px; height: 14px; }
.cpill .cc-emoji { font-size: 13px; }
.cpill.on { background: var(--brand-grad); border-color: transparent; color: #fff; box-shadow: 0 4px 14px rgba(26, 63, 181, .3); }
@media (max-width: 900px) {
  .shop-layout { grid-template-columns: 1fr; }
  .side-cats { display: none; }
  /* column-wise category grid: all categories visible, no swiping */
  .cat-pills { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 14px; }
  .cpill { padding: 11px 14px; font-size: 12.5px; border-radius: 13px; justify-content: flex-start; min-width: 0; }
  .cpill svg { width: 15px; height: 15px; flex-shrink: 0; }
  .cpill .cp-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* contact page */
.contact-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 44px 0 30px; }
.ccard { border: 1px solid var(--line); border-radius: 20px; padding: 28px 24px; background: #fff; display: flex; flex-direction: column; align-items: flex-start; transition: .2s; }
.ccard:hover { border-color: var(--brand); box-shadow: var(--shadow-lg); transform: translateY(-4px); }
.ccard .ci { width: 50px; height: 50px; border-radius: 99px; background: var(--brand-soft); color: var(--brand); display: grid; place-items: center; margin-bottom: 16px; }
.ccard .ci svg { width: 22px; height: 22px; }
.ccard b { font-size: 16.5px; margin-bottom: 5px; }
.ccard p { font-size: 13.5px; color: var(--ink-2); }
.ccard .cv { font-size: 13px; font-weight: 700; color: var(--brand); margin-top: 8px; word-break: break-all; }
.ccard .btn { margin-top: 16px; }
.contact-note { background: var(--bg-2); border-radius: 16px; padding: 18px 20px; font-size: 13.5px; color: var(--ink-2); margin-bottom: 80px; display: flex; gap: 10px; align-items: flex-start; }
/* One-off: contact page only. 800 is used nowhere else and .contact-grid
   appears on no other page, so this block has no overlap with anything. */
@media (max-width: 800px) { .contact-grid { grid-template-columns: 1fr; gap: 12px; margin-top: 26px; } .contact-note { margin-bottom: 50px; } }

/* category showcase cards — clean white cards with subtle color accents */
.catx-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin: 36px 0 90px; }
.catx { display: block; position: relative; border-radius: 20px; padding: 20px; border: 1px solid var(--line); background: #fff; overflow: hidden; box-shadow: 0 1px 2px rgba(17, 24, 39, .04), 0 4px 14px rgba(17, 24, 39, .05); transition: .22s cubic-bezier(.2, .8, .3, 1); }
.catx::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: linear-gradient(90deg, var(--c1), var(--c2)); opacity: 0; transition: .22s; }
.catx:hover { transform: translateY(-4px); border-color: transparent; box-shadow: 0 16px 40px rgba(17, 24, 39, .1); }
.catx:hover::before { opacity: 1; }
.cx-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 15px; }
.cx-ic { width: 46px; height: 46px; border-radius: 14px; background: linear-gradient(135deg, var(--c1), var(--c2)); color: #fff; display: grid; place-items: center; box-shadow: 0 6px 16px var(--sh); transition: .22s; }
.cx-ic svg { width: 22px; height: 22px; }
.cx-ic .cc-emoji { font-size: 21px; }
.catx:hover .cx-ic { transform: rotate(-6deg) scale(1.07); }
.cx-count { font-size: 11px; font-weight: 800; background: var(--bg-2); border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; color: var(--ink-2); white-space: nowrap; }
.catx > b { display: block; font-size: 16.5px; font-weight: 800; letter-spacing: -.3px; color: var(--ink); }
.catx > p { font-size: 12.5px; color: var(--ink-2); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cx-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; }
.cx-thumbs { display: flex; align-items: center; }
.cx-thumbs img, .cx-more { width: 30px; height: 30px; border-radius: 99px; border: 2px solid #fff; object-fit: cover; margin-left: -9px; box-shadow: 0 1px 4px rgba(17, 24, 39, .12); }
.cx-thumbs img:first-child { margin-left: 0; }
.cx-more { background: var(--ink); color: #fff; display: grid; place-items: center; font-size: 9.5px; font-weight: 800; }
.cx-go { width: 36px; height: 36px; border-radius: 99px; background: var(--bg-2); border: 1px solid var(--line); display: grid; place-items: center; color: var(--c1); font-size: 15px; font-weight: 700; transition: .22s; }
.catx:hover .cx-go { background: linear-gradient(135deg, var(--c1), var(--c2)); border-color: transparent; color: #fff; transform: rotate(-45deg); }
@media (max-width: 1000px) { .catx-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px) {
  .catx-grid { gap: 10px; margin: 24px 0 60px; }
  .catx { padding: 14px; border-radius: 16px; }
  .cx-ic { width: 38px; height: 38px; border-radius: 12px; }
  .cx-ic svg { width: 18px; height: 18px; }
  .catx > b { font-size: 14px; }
  .catx > p { font-size: 11px; }
  .cx-foot { margin-top: 12px; }
  .cx-thumbs img, .cx-more { width: 25px; height: 25px; margin-left: -8px; }
  .cx-go { width: 30px; height: 30px; font-size: 13px; }
}

/* VoltzHub category catalog — editorial numbered rail (Shop + Categories pages) */
.eyebrow { font-size: 12px; font-weight: 800; letter-spacing: 2px; color: var(--brand); text-transform: uppercase; }
.cat-intro { margin: 54px 0 8px; }
.cat-intro h1 { font-size: 40px; font-weight: 800; letter-spacing: -1.2px; margin: 8px 0 12px; }
.cat-intro h1 em { font-style: normal; background: var(--brand-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.cat-intro p { color: var(--ink-2); font-size: 15px; max-width: 560px; }
.cat-rail { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 980px; margin: 36px auto 90px; }
.catr { display: flex; align-items: center; gap: 16px; padding: 18px 20px; border: 1px solid var(--line); border-radius: 18px; background: #fff; position: relative; overflow: hidden; transition: .2s; }
.catr:hover { border-color: var(--brand); transform: translateY(-2px); box-shadow: 0 10px 26px rgba(26, 63, 181, .1); }
.catr::before { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, var(--brand-soft), transparent 65%); opacity: 0; transition: .25s; }
.catr > * { position: relative; }
.catr .ic { width: 54px; height: 54px; flex-shrink: 0; border-radius: 18px; background: var(--brand-soft); color: var(--brand); display: grid; place-items: center; transition: .25s; }
.catr .ic svg { width: 25px; height: 25px; }
.catr .ic .cc-emoji { font-size: 24px; }
.catr b { font-size: 17px; font-weight: 800; letter-spacing: -.3px; transition: transform .25s; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.catr .arrow { margin-left: auto; width: 40px; height: 40px; flex-shrink: 0; border-radius: 99px; border: 1.5px solid var(--line); display: grid; place-items: center; color: var(--ink-3); font-size: 16px; transition: .25s; }
.catr:hover::before { opacity: 1; }
.catr:hover .ic { background: var(--brand-grad); color: #fff; transform: rotate(-6deg) scale(1.06); }
.catr:hover b { transform: translateX(6px); }
.catr:hover .arrow { background: var(--brand); border-color: var(--brand); color: #fff; transform: rotate(-45deg); }
@media (max-width: 720px) {
  .cat-intro { margin: 24px 0 4px; }
  .cat-intro h1 { font-size: 28px; }
  .cat-rail { margin: 22px auto 60px; gap: 9px; }
  .catr { gap: 10px; padding: 12px 12px; border-radius: 14px; }
  .catr .ic { width: 38px; height: 38px; border-radius: 12px; }
  .catr .ic svg { width: 19px; height: 19px; }
  .catr b { font-size: 13.5px; }
  .catr .arrow { display: none; }
}

/* mobile bottom navigation */
.bottom-nav { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 95; background: rgba(255, 255, 255, .97); backdrop-filter: blur(12px); border-top: 1px solid var(--line); padding: 6px 4px calc(6px + env(safe-area-inset-bottom)); box-shadow: 0 -4px 20px rgba(17, 24, 39, .07); }
.bnav-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 5px 2px; font-size: 10.5px; font-weight: 700; color: var(--ink-3); border-radius: 10px; position: relative; transition: .15s; }
.bnav-item svg { width: 22px; height: 22px; display: block; }
.bnav-item.on { color: var(--brand); }
.bnav-item.on::before { content: ""; position: absolute; top: -7px; width: 26px; height: 3px; border-radius: 0 0 4px 4px; background: var(--brand); }
.bnav-item:active { transform: scale(.93); }
.bnav-badge { position: absolute; top: 0; right: calc(50% - 20px); background: var(--brand); color: #fff; font-size: 9.5px; font-weight: 800; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; display: grid; place-items: center; }

/* WhatsApp float */
.wa-float { position: fixed; right: 18px; bottom: 18px; z-index: 90; width: 56px; height: 56px; border-radius: 999px; background: #22c55e; display: grid; place-items: center; box-shadow: 0 8px 24px rgba(34, 197, 94, .45); transition: .2s; }
.wa-float:hover { transform: scale(1.08); }
.wa-float svg { width: 30px; height: 30px; position: relative; z-index: 1; }
.wa-float::before { content: ""; position: absolute; inset: 0; border-radius: 999px; box-shadow: 0 0 0 0 rgba(34,197,94,.5); animation: waPulse 2.6s infinite; }
@keyframes waPulse { 0% { box-shadow: 0 0 0 0 rgba(34,197,94,.45); } 70% { box-shadow: 0 0 0 16px rgba(34,197,94,0); } 100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); } }
/* ===== store assistant chatbot ===== */
.vh-bot-launch { position: fixed; right: 18px; bottom: 94px; z-index: 92; width: 58px; height: 58px; border-radius: 50%; border: none; cursor: pointer; background: var(--brand-grad, linear-gradient(135deg,#6d28d9,#2563eb)); color: #fff; display: grid; place-items: center; box-shadow: 0 10px 26px rgba(37,99,235,.45); }
.vh-bot-launch:hover { transform: scale(1.07); }
.vh-bot-launch svg { width: 30px; height: 30px; }
.vh-bot-launch::before { content: ""; position: absolute; inset: 0; border-radius: 50%; box-shadow: 0 0 0 0 rgba(37,99,235,.5); animation: vhpulse 2.6s infinite; }
@keyframes vhpulse { 0% { box-shadow: 0 0 0 0 rgba(37,99,235,.45); } 70% { box-shadow: 0 0 0 16px rgba(37,99,235,0); } 100% { box-shadow: 0 0 0 0 rgba(37,99,235,0); } }
.vh-bot-ping { position: absolute; top: 0; right: 0; width: 14px; height: 14px; border-radius: 50%; background: #22c55e; border: 2px solid #fff; z-index: 1; }
.vh-bot { position: fixed; right: 18px; bottom: 84px; z-index: 96; width: 372px; max-width: calc(100vw - 28px); height: 560px; max-height: calc(100vh - 130px); background: #fff; border-radius: 20px; box-shadow: 0 24px 60px rgba(17,24,39,.30); display: flex; flex-direction: column; overflow: hidden; opacity: 0; transform: translateY(16px) scale(.98); pointer-events: none; transition: .22s; }
.vh-bot.open { opacity: 1; transform: none; pointer-events: auto; }
/* header: 64–72px including padding — compact enough to leave the conversation
   the room, tall enough for a 38px avatar + two lines of identity */
.vh-bot-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 13px 15px; min-height: 66px; box-sizing: border-box; background: var(--brand-grad, linear-gradient(135deg,#6d28d9,#2563eb)); color: #fff; flex-shrink: 0; }
.vh-bot-acts { display: flex; align-items: center; gap: 7px; flex-shrink: 0; }
.vh-bot-id { display: flex; align-items: center; gap: 10px; }
.vh-bot-av { width: 38px; height: 38px; border-radius: 50%; background: rgba(255,255,255,.2); display: grid; place-items: center; }
.vh-bot-av svg { width: 23px; height: 23px; }
.vh-bot-id b { display: block; font-size: 14.5px; line-height: 1.2; }
.vh-bot-status { font-size: 11px; opacity: .92; display: flex; align-items: center; gap: 5px; }
.vh-live { width: 7px; height: 7px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 0 0 rgba(74,222,128,.7); animation: vhlive 1.8s infinite; }
@keyframes vhlive { 0% { box-shadow: 0 0 0 0 rgba(74,222,128,.6); } 70% { box-shadow: 0 0 0 6px rgba(74,222,128,0); } 100% { box-shadow: 0 0 0 0 rgba(74,222,128,0); } }
.vh-bot-x { background: rgba(255,255,255,.16); border: none; color: #fff; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; flex-shrink: 0; display: grid; place-items: center; padding: 0; }
.vh-bot-x svg { display: block; }
.vh-bot-x:hover { background: rgba(255,255,255,.3); }
/* 🛑 `min-height: 0` is load-bearing: as a flex child it would otherwise refuse
   to shrink below its content and the SHEET would grow instead of the list
   scrolling. Same rule as the admin queue cards. */
.vh-bot-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; scroll-behavior: smooth; padding: 18px 16px 24px; background: linear-gradient(180deg, #f7f8fc 0%, #eceff6 100%); display: flex; flex-direction: column; gap: 14px; }

/* ---- welcome / empty state ---- */
.vh-wel { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 5px; padding: 14px 6px 4px; animation: vhpop .34s cubic-bezier(.2,.8,.3,1); }
.vh-wel-av { width: 62px; height: 62px; border-radius: 20px; background: var(--brand-grad, linear-gradient(135deg,#6d28d9,#2563eb)); color: #fff; display: grid; place-items: center; box-shadow: 0 12px 28px rgba(37,99,235,.34); margin-bottom: 4px; }
.vh-wel-av svg { width: 34px; height: 34px; }
.vh-wel h4 { font-size: 18px; font-weight: 800; margin: 0; color: var(--ink-1); letter-spacing: -.3px; }
.vh-wel p { font-size: 13px; line-height: 1.55; color: var(--ink-3); margin: 0; max-width: 30ch; }
.vh-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 12px 2px 2px; }
.vh-chip { display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 999px; padding: 9px 14px; font-size: 12.5px; font-weight: 700; color: var(--ink-1); text-decoration: none; cursor: pointer; transition: .16s; box-shadow: 0 2px 8px rgba(17,24,39,.05); font-family: inherit; }
.vh-chip:hover { border-color: var(--brand); color: var(--brand); transform: translateY(-1px); box-shadow: 0 8px 18px rgba(37,99,235,.15); }
.vh-chip.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.vh-chip.primary:hover { color: #fff; filter: brightness(1.06); }
.vh-bot-body::-webkit-scrollbar { width: 6px; }
.vh-bot-body::-webkit-scrollbar-thumb { background: rgba(17,24,39,.16); border-radius: 99px; }
.vh-row { display: flex; align-items: flex-end; gap: 8px; align-self: flex-start; max-width: 92%; animation: vhpop .28s cubic-bezier(.2,.8,.3,1); }
.vh-ava { width: 30px; height: 30px; border-radius: 50%; background: var(--brand-grad, linear-gradient(135deg,#6d28d9,#2563eb)); color: #fff; display: grid; place-items: center; flex-shrink: 0; box-shadow: 0 3px 8px rgba(37,99,235,.32); }
.vh-ava svg { width: 18px; height: 18px; }
.vh-msg { font-size: 13.5px; line-height: 1.5; padding: 11px 14px; border-radius: 18px; }
.vh-row .vh-msg { animation: none; }
.vh-msg.bot { background: #fff; color: var(--ink-1); border-bottom-left-radius: 6px; box-shadow: 0 3px 12px rgba(17,24,39,.07); }
.vh-msg.bot b { color: var(--brand); font-weight: 800; }
.vh-msg.user { align-self: flex-end; max-width: 86%; background: var(--brand-grad, linear-gradient(135deg,#6d28d9,#2563eb)); color: #fff; border-bottom-right-radius: 6px; font-weight: 600; box-shadow: 0 5px 16px rgba(37,99,235,.32); animation: vhpop .28s cubic-bezier(.2,.8,.3,1); }
.vh-indent { padding-left: 38px; }
.vh-plist { display: flex; flex-direction: column; gap: 8px; align-self: stretch; }
.vh-opts { display: flex; flex-direction: column; gap: 8px; align-self: stretch; animation: vhpop .32s cubic-bezier(.2,.8,.3,1); }
.vh-opt { display: flex; align-items: center; gap: 11px; text-align: left; background: #fff; border: 1px solid var(--line); border-radius: 14px; padding: 9px 12px; cursor: pointer; transition: .16s; text-decoration: none; box-shadow: 0 2px 8px rgba(17,24,39,.05); }
.vh-opt:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: 0 8px 18px rgba(37,99,235,.15); }
.vh-opt-ic { width: 34px; height: 34px; border-radius: 10px; background: var(--brand-soft); color: var(--brand); display: grid; place-items: center; font-size: 17px; flex-shrink: 0; }
.vh-opt-ic svg { width: 18px; height: 18px; }
.vh-opt-l { flex: 1; min-width: 0; font-weight: 700; font-size: 13.5px; color: var(--ink-1); }
.vh-opt-ar { color: var(--ink-3); font-size: 20px; line-height: 1; transition: .16s; }
.vh-opt:hover .vh-opt-ar { color: var(--brand); transform: translateX(3px); }
.vh-bot-prod { display: flex; align-items: center; gap: 12px; background: #fff; border: 1px solid var(--line); border-radius: 15px; padding: 9px; text-decoration: none; transition: .16s; box-shadow: 0 2px 8px rgba(17,24,39,.05); animation: vhpop .32s cubic-bezier(.2,.8,.3,1); }
.vh-bot-prod:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: 0 10px 22px rgba(37,99,235,.15); }
.vh-bot-prod img { width: 48px; height: 48px; border-radius: 11px; object-fit: cover; flex-shrink: 0; border: 1px solid var(--line); }
.vh-bot-prod-i { flex: 1; min-width: 0; }
.vh-bot-prod-i b { display: block; font-size: 13px; color: var(--ink-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 3px; }
.vh-bot-prod-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.vh-bot-prod-meta .price { font-size: 13px; color: var(--brand); font-weight: 800; }
.vh-prod-rate { font-size: 11px; font-weight: 700; color: #b7791f; }
.vh-prod-tag { font-size: 10px; font-weight: 800; letter-spacing: .2px; color: #15803d; background: #dcfce7; padding: 2px 7px; border-radius: 999px; }
.vh-prod-tag.oos { color: #b45309; background: #fef3c7; }
.vh-bot-prod-btn { flex-shrink: 0; font-size: 12px; font-weight: 800; color: #fff; background: var(--brand); padding: 8px 14px; border-radius: 10px; white-space: nowrap; align-self: center; }
.vh-bot-foot { padding: 9px 14px; text-align: center; font-size: 10.5px; font-weight: 600; color: var(--ink-3); background: #fff; border-top: 1px solid var(--line); display: flex; align-items: center; justify-content: center; gap: 5px; }
.vh-bot-foot .vh-lock { font-size: 11px; }
.vh-typing { align-self: flex-start; display: flex; gap: 4px; padding: 14px; background: #fff; border-radius: 18px; border-bottom-left-radius: 6px; box-shadow: 0 3px 12px rgba(17,24,39,.07); animation: vhpop .28s cubic-bezier(.2,.8,.3,1); }
.vh-typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); animation: vhblink 1.2s infinite; }
.vh-typing i:nth-child(2) { animation-delay: .2s; }
.vh-typing i:nth-child(3) { animation-delay: .4s; }
@keyframes vhblink { 0%, 60%, 100% { opacity: .3; } 30% { opacity: 1; } }
@keyframes vhpop { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* ============================================================
   ASSISTANT — RESPONSIVE TIERS
     > 1024px   desktop floating widget (unchanged)
     ≤ 1024px   tablet: centred panel, wider than desktop
     ≤ 768px    mobile: full-screen native sheet

   🛑 The sheet must NOT simply overlay the bottom-nav. Measured at 375×812:
      the panel ran to y=811 while .bottom-nav starts at y=743 — 59px of the
      conversation (and the whole footer) sat on top of the nav, which also
      made the nav unusable. `html.vh-bot-open` hides .bottom-nav and .wa-float
      for the duration instead; that is what a full-screen sheet should do, and
      it is why the class is toggled on <html> rather than solved with z-index.
   ============================================================ */
@media (max-width: 900px) {
  .vh-bot-launch { bottom: 150px; right: 14px; }
}
/* launcher: animate out instead of display:none, so it can slide back in */
.vh-bot-launch { transition: transform .24s cubic-bezier(.2,.8,.3,1), opacity .18s ease; }
.vh-bot-launch.vh-hidden { transform: scale(.35) translateY(14px); opacity: 0; pointer-events: none; }
html.vh-bot-open .bottom-nav,
html.vh-bot-open .wa-float { opacity: 0; pointer-events: none; transition: opacity .18s ease; }

/* ---- tablet: centred panel ---- */
@media (min-width: 769px) and (max-width: 1024px) {
  .vh-bot {
    left: 50%; right: auto; bottom: 96px;
    width: 440px; max-width: calc(100vw - 40px);
    height: min(720px, 84vh); max-height: none;
    transform: translateX(-50%) translateY(16px) scale(.98);
  }
  .vh-bot.open { transform: translateX(-50%); }
}

/* ---- mobile: full-screen sheet ---- */
@media (max-width: 768px) {
  .vh-bot-launch { width: 52px; height: 52px; }
  .vh-bot-launch svg { width: 27px; height: 27px; }
  .vh-bot {
    inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    width: auto; max-width: none; height: auto; max-height: none;
    border-radius: 0;
    /* slide up from the bottom edge — a sheet, not a shrinking popover */
    transform: translateY(100%); opacity: 1;
    transition: transform .3s cubic-bezier(.32,.72,0,1), opacity .2s ease;
  }
  .vh-bot.open { transform: none; }
  /* safe areas live on the CHROME, not the sheet, so the scroll area still
     runs edge to edge behind the notch / home indicator */
  .vh-bot-head { padding-top: calc(13px + env(safe-area-inset-top)); }
  .vh-bot-foot { padding-bottom: calc(9px + env(safe-area-inset-bottom)); }
  .vh-bot-body { padding: 18px 16px 24px; gap: 14px; }
  .vh-row { max-width: 80%; }
  .vh-msg { font-size: 14px; padding: 12px 15px; }
  .vh-msg.user { max-width: 78%; }
  .vh-indent { padding-left: 40px; }
}
@media (prefers-reduced-motion: reduce) {
  .vh-bot, .vh-bot-launch { transition-duration: .01ms; }
}

/* ===== APP MODE — installed-PWA gets a distinct, native-feeling skin ===== */
.app-mode .topbar { display: none; }
/* SINGLE SOURCE OF TRUTH for footer visibility — and it is FAIL-SAFE.
   The desktop footer is never shown in app mode. Mobile navigation is provided
   by .bottom-nav + the hamburger menu.

   Two layers, deliberately:
   1) The narrow-screen rule below needs NO JavaScript. If a page's script is
      slow, throws, or hasn't run yet (account.html awaits network data before
      it renders, so it is the most exposed), the footer is ALREADY hidden.
      Hiding is the default; showing requires an explicit opt-out.
   2) `.app-mode` covers the installed PWA at any width (e.g. tablet).

   The `?app=0` "view desktop site" opt-out still works: detectAppMode() adds
   `web-mode` to <html>, which releases the rule below. */
@media (max-width: 760px) {
  html:not(.web-mode) .footer { display: none; }
}
.app-mode .footer { display: none; }
.app-mode .header { padding-top: env(safe-area-inset-top); }
.app-mode .bottom-nav { display: flex; padding: 8px 6px calc(10px + env(safe-area-inset-bottom)); border-top: 0.5px solid var(--line); box-shadow: 0 -6px 22px rgba(17,24,39,.07); }
.app-mode .bnav-item { font-size: 9.5px; gap: 3px; color: #9aa4b6; font-weight: 600; }
.app-mode .bnav-item svg { width: 22px; height: 22px; }
.app-mode .bnav-item.on { color: var(--brand); }
.app-mode .bnav-item.on svg { stroke-width: 2.3; }

/* ---------- product page (modern) ---------- */
.crumbs { font-size: 12.5px; color: var(--ink-3); margin: 18px 0; }
.crumbs a:hover { color: var(--brand); }
.qty { display: inline-flex; align-items: center; border: 1.5px solid var(--line); border-radius: 12px; overflow: hidden; }
.qty button { width: 40px; height: 44px; border: none; background: #fff; font-size: 18px; font-weight: 700; color: var(--ink-2); }
.qty button:hover { background: var(--bg-2); color: var(--brand); }
.qty span { width: 44px; text-align: center; font-weight: 800; }

.pd2 { display: grid; grid-template-columns: 1.05fr 1fr; gap: 44px; align-items: start; margin-bottom: 20px; }
.pd2-gallery { position: sticky; top: 96px; }
.pd2-main { position: relative; border-radius: 24px; overflow: hidden; box-shadow: var(--shadow); background: var(--bg-2); border: 1px solid var(--line); }
.pd2-main img { width: 100%; aspect-ratio: 4/3; object-fit: contain; display: block; padding: 14px; }
.pd2-off { position: absolute; top: 14px; left: 14px; background: var(--red); color: #fff; font-size: 12.5px; font-weight: 800; padding: 6px 12px; border-radius: 999px; box-shadow: 0 4px 14px rgba(220, 38, 38, .4); }
.pd2-thumbs { display: flex; gap: 10px; margin-top: 12px; }
.pd2-thumbs img { width: 68px; height: 52px; object-fit: contain; background: var(--bg-2); border: 1px solid var(--line); padding: 4px; border-radius: 12px; cursor: pointer; opacity: .55; transition: .18s; outline: 2px solid transparent; outline-offset: 2px; }
.pd2-thumbs img.on { opacity: 1; outline-color: var(--brand); }

.pd2-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.pd2-cat { background: var(--brand-soft); color: var(--brand); font-size: 11px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase; padding: 6px 12px; border-radius: 999px; }
/* Rating — a trust line under the title, NOT a tag. It sits between h1 and
   .pd2-short so the product name stays the single primary focus and the rating
   reads as the first supporting signal, ahead of the price. Deliberately has no
   pill: a chip would put it back in the same visual class as the category and
   stock tags, which is what made it read as "just another tag". Reuses the
   storefront's existing .stars treatment (var(--amber)) so the glyphs match the
   review summary further down the page. */
.pd2-trust { display: flex; align-items: center; gap: 9px; margin: 0 0 14px; }
.pd2-trust .stars { font-size: 15px; letter-spacing: 2.5px; }
.pd2-trust .t-avg { font-size: 15px; font-weight: 800; letter-spacing: -.2px; font-variant-numeric: tabular-nums; }
.pd2-trust .t-cnt { font-size: 13px; font-weight: 600; color: var(--ink-3); }
.pd2-stock { background: var(--green-soft); color: var(--green); font-size: 12px; font-weight: 700; padding: 6px 12px; border-radius: 999px; }
.pd2 h1 { font-size: 32px; font-weight: 800; letter-spacing: -.9px; line-height: 1.12; margin-bottom: 10px; }
.pd2-short { color: var(--ink-2); font-size: 15px; margin-bottom: 18px; }
.pd2-price { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; margin-bottom: 20px; }
.pd2-price .now { font-size: 34px; font-weight: 800; letter-spacing: -1px; background: var(--brand-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.pd2-price .old { font-size: 17px; color: var(--ink-3); text-decoration: line-through; font-weight: 600; }
.pd2-price .save { background: var(--green-soft); color: var(--green); font-size: 12px; font-weight: 800; padding: 4px 11px; border-radius: 999px; }

.pd2-label { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; color: var(--ink-3); margin-bottom: 10px; }
.plan-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; }
.plan { position: relative; border: 1.5px solid var(--line); border-radius: 16px; padding: 13px 15px; cursor: pointer; background: #fff; transition: .16s; }
.plan:hover { border-color: #b9c8f5; transform: translateY(-1px); }
.plan.on { border-color: var(--brand); background: var(--brand-soft); box-shadow: 0 6px 18px rgba(26, 63, 181, .14); }
.plan.oos { opacity: .45; cursor: not-allowed; border-style: dashed; }
.plan .pl-check { position: absolute; top: 10px; right: 10px; width: 20px; height: 20px; border-radius: 99px; background: var(--brand); color: #fff; font-size: 11px; display: none; place-items: center; }
.plan.on .pl-check { display: grid; }
.plan .pl-l { font-weight: 700; font-size: 13.5px; line-height: 1.25; padding-right: 20px; }
.plan .pl-p { font-weight: 800; font-size: 15px; margin-top: 6px; }
.plan .pl-p .was { font-size: 11.5px; color: var(--ink-3); text-decoration: line-through; font-weight: 600; margin-left: 6px; }
.plan .pl-s { font-size: 11px; color: var(--ink-3); font-weight: 600; margin-top: 2px; }
.plan .pl-s.low { color: var(--amber); }

.pd2-actions { display: flex; gap: 10px; align-items: stretch; margin-bottom: 18px; flex-wrap: wrap; }
.pd2-benefits { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 8px; }
.benefit { background: var(--bg-2); border-radius: 14px; padding: 12px 10px; text-align: center; }
.benefit .b-i { font-size: 20px; margin-bottom: 4px; }
.benefit b { display: block; font-size: 12px; }
.benefit span { font-size: 10.5px; color: var(--ink-3); font-weight: 600; }

.acc { border: 1px solid var(--line); border-radius: 16px; margin-bottom: 10px; overflow: hidden; background: #fff; }
.acc-head { display: flex; align-items: center; gap: 10px; width: 100%; border: none; background: #fff; padding: 15px 18px; font-weight: 800; font-size: 14.5px; text-align: left; }
.acc-head .a-ic { transition: .25s; margin-left: auto; color: var(--ink-3); }
.acc.open .acc-head .a-ic { transform: rotate(180deg); color: var(--brand); }
.acc-body { display: none; padding: 0 18px 16px; font-size: 14px; color: var(--ink-2); white-space: pre-line; line-height: 1.65; }
.acc.open .acc-body { display: block; }

.pd2-reviews { margin: 44px 0 10px; }
.rev-layout { display: grid; grid-template-columns: 280px 1fr; gap: 22px; align-items: start; }
.rev-summary { background: var(--bg-2); border-radius: 20px; padding: 24px; text-align: center; position: sticky; top: 96px; }
.rev-summary .big { font-size: 44px; font-weight: 800; letter-spacing: -1.5px; line-height: 1; }
.rev-summary .stars { font-size: 16px; margin: 6px 0 2px; }
.rev-summary .cnt { font-size: 12.5px; color: var(--ink-3); font-weight: 600; }
.rev-bar { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--ink-3); font-weight: 700; margin-top: 7px; }
.rev-bar .track { flex: 1; height: 6px; background: #e5e7eb; border-radius: 9px; overflow: hidden; }
.rev-bar .fill { height: 100%; background: var(--amber); border-radius: 9px; }
.rev-card { border: 1px solid var(--line); border-radius: 16px; padding: 16px 18px; margin-bottom: 12px; background: #fff; }

/* "explore this category" card on the product page */
.cat-explore { display: flex; align-items: center; gap: 15px; margin-top: 14px; padding: 17px 18px; border-radius: 18px; background: linear-gradient(120deg, var(--brand-soft), #fff 70%); border: 1.5px solid #d6def8; transition: .2s; }
.cat-explore .ce-ic { width: 50px; height: 50px; flex-shrink: 0; border-radius: 15px; background: var(--brand-grad); color: #fff; display: grid; place-items: center; box-shadow: 0 6px 16px rgba(26, 63, 181, .3); transition: .2s; }
.cat-explore .ce-ic svg { width: 23px; height: 23px; }
.cat-explore .ce-ic .cc-emoji { font-size: 22px; }
.cat-explore b { display: block; font-size: 15px; }
.cat-explore span.ce-sub { font-size: 12.5px; color: var(--ink-2); }
.cat-explore .ce-arrow { margin-left: auto; width: 36px; height: 36px; flex-shrink: 0; border-radius: 99px; background: #fff; border: 1.5px solid var(--line); display: grid; place-items: center; color: var(--brand); font-size: 15px; transition: .2s; }
.cat-explore:hover { border-color: var(--brand); box-shadow: 0 10px 26px rgba(26, 63, 181, .14); transform: translateY(-2px); }
.cat-explore:hover .ce-ic { transform: rotate(-6deg) scale(1.05); }
.cat-explore:hover .ce-arrow { background: var(--brand); border-color: var(--brand); color: #fff; transform: rotate(-45deg); }

.pd2-cat:hover { background: var(--brand); color: #fff; }

.pd2-stickybar { display: none; position: fixed; left: 10px; right: 10px; bottom: 82px; z-index: 94; background: rgba(255, 255, 255, .97); backdrop-filter: blur(10px); border: 1px solid var(--line); border-radius: 18px; box-shadow: var(--shadow-lg); padding: 10px 12px 10px 16px; align-items: center; gap: 12px; }
.pd2-stickybar .sb-p { line-height: 1.15; }
.pd2-stickybar .sb-p .l { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .6px; color: var(--ink-3); }
.pd2-stickybar .sb-p .v { font-size: 17px; font-weight: 800; color: var(--brand); }
.pd2-stickybar .btn { flex: 1; }

@media (max-width: 900px) {
  .pd2 { grid-template-columns: 1fr; gap: 22px; }
  .pd2-gallery { position: static; }
  .rev-layout { grid-template-columns: 1fr; }
  .rev-summary { position: static; }
}
@media (max-width: 720px) {
  .pd2 h1 { font-size: 23px; }
  .pd2-price .now { font-size: 27px; }
  .pd2-main { border-radius: 18px; }
  .plan-grid { grid-template-columns: 1fr 1fr; }
  .pd2-stickybar { display: flex; }
  body.pd2-page { padding-bottom: 150px; }
}

/* reviews */
.review { border-bottom: 1px solid var(--line); padding: 16px 0; }
.review:last-child { border: none; }
.rev-head { display: flex; align-items: center; gap: 10px; margin-bottom: 5px; }
.avatar { width: 38px; height: 38px; border-radius: 99px; background: var(--brand-soft); color: var(--brand); font-weight: 800; display: grid; place-items: center; font-size: 14px; }
.rev-head b { font-size: 14px; display: block; }
.rev-head time { font-size: 12px; color: var(--ink-3); }
.review p { font-size: 14px; color: var(--ink-2); white-space: normal; }
.rate-input { display: flex; gap: 4px; font-size: 26px; cursor: pointer; color: #d1d5db; }
.rate-input span.on { color: var(--amber); }

/* ---------- forms ---------- */
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 11px 14px; border: 1.5px solid var(--line); border-radius: 12px; outline: none; transition: .15s; background: #fff;
}
.field input:focus, .field select:focus, .field textarea:focus,
/* These three <select>s render as field BOXES (own border, radius, background),
   so they belong on the field treatment, not the global ring — same rule, same
   declaration, nothing duplicated. .cur-select is appearance:none, .toolbar and
   .apc-bar selects are the sort dropdowns. */
.toolbar select:focus, .apc-bar select:focus, .cur-select:focus { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-soft); }
.field .hint { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
.msg { border-radius: 12px; padding: 11px 14px; font-size: 13.5px; font-weight: 600; margin: 10px 0; display: none; }
.msg.err { display: block; background: var(--red-soft); color: var(--red); }
.msg.ok { display: block; background: var(--green-soft); color: var(--green); }

/* ---------- cart / checkout ---------- */
.page-title { font-size: 26px; font-weight: 800; letter-spacing: -.5px; margin: 26px 0 20px; }
.cart-layout { display: grid; grid-template-columns: 1.7fr 1fr; gap: 24px; align-items: start; }
.card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
.card-pad { padding: 20px; }
.cart-item { display: flex; gap: 14px; padding: 16px 20px; border-bottom: 1px solid var(--line); align-items: center; }
.cart-item:last-child { border: none; }
.cart-item img { width: 108px; height: 84px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
.ci-info { flex: 1; min-width: 0; }
.ci-info b { display: block; font-size: 14px; }
.ci-info .ci-v { font-size: 12.5px; color: var(--ink-2); }
.ci-price { font-weight: 800; white-space: nowrap; }
.ci-remove { border: none; background: none; color: var(--ink-3); font-size: 13px; padding: 4px; }
.ci-remove:hover { color: var(--red); }
.sum-row { display: flex; justify-content: space-between; font-size: 14px; padding: 6px 0; color: var(--ink-2); }
.sum-row.total { font-size: 17px; font-weight: 800; color: var(--ink); border-top: 1px dashed var(--line); margin-top: 8px; padding-top: 12px; }
.sum-row .disc { color: var(--green); font-weight: 700; }
.coupon-row { display: flex; gap: 8px; margin: 12px 0; }
.coupon-row input { flex: 1; padding: 10px 13px; border: 1.5px solid var(--line); border-radius: 11px; outline: none; text-transform: uppercase; font-weight: 700; letter-spacing: .5px; }
/* Same focus treatment as .field input:focus above — border + the 4px
   --brand-soft halo. This rule previously set only the border colour, so the
   cart coupon field focused to a bare dark ring while every other form field
   on the storefront showed the soft light-blue one. Reusing the identical
   declaration rather than inventing a variant keeps the four self-styled field
   scopes (.search, .field, .coupon-row, .a2-inp) on one treatment. */
.coupon-row input:focus { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-soft); }

.steps { display: flex; gap: 0; margin: 22px 0; counter-reset: step; }
.step { flex: 1; text-align: center; position: relative; font-size: 12.5px; font-weight: 700; color: var(--ink-3); }
.step::before { counter-increment: step; content: counter(step); display: grid; place-items: center; width: 34px; height: 34px; margin: 0 auto 6px; border-radius: 99px; background: var(--bg-2); border: 2px solid var(--line); font-size: 13px; }
.step.on { color: var(--brand); }
.step.on::before { background: var(--brand); border-color: var(--brand); color: #fff; }
.step.done { color: var(--green); }
.step.done::before { background: var(--green); border-color: var(--green); color: #fff; content: "✓"; }
.step::after { content: ""; position: absolute; top: 17px; left: calc(50% + 24px); right: calc(-50% + 24px); height: 2px; background: var(--line); }
.step:last-child::after { display: none; }

.bank { border: 1.5px solid var(--line); border-radius: 14px; padding: 15px 16px; margin-bottom: 10px; cursor: pointer; transition: .15s; display: flex; gap: 13px; align-items: flex-start; }
.bank:hover { border-color: #b9c8f5; }
.bank.on { border-color: var(--brand); background: var(--brand-soft); }
.bank-logo { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 16px; flex-shrink: 0; }
.bank b { font-size: 14.5px; }
.bank .bk-l { font-size: 13px; color: var(--ink-2); }
.bank .bk-n { font-family: ui-monospace, Consolas, monospace; font-weight: 700; font-size: 13.5px; margin-top: 2px; }
.copy-btn { border: none; background: var(--brand-soft); color: var(--brand); font-size: 11px; font-weight: 800; border-radius: 7px; padding: 3px 8px; margin-left: 6px; }

.upload-zone { border: 2px dashed var(--line); border-radius: 14px; padding: 26px; text-align: center; cursor: pointer; transition: .15s; color: var(--ink-2); }
.upload-zone:hover, .upload-zone.drag { border-color: var(--brand); background: var(--brand-soft); }
.upload-zone .uz-ico { font-size: 30px; margin-bottom: 6px; }
.upload-preview { margin-top: 12px; display: none; }
.upload-preview img { max-height: 180px; border-radius: 12px; border: 1px solid var(--line); margin: 0 auto; }
.upload-preview .up-name { font-size: 13px; font-weight: 700; margin-top: 8px; text-align: center; }

/* order status pills */
.pill { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 800; border-radius: 999px; padding: 4px 12px; }
.pill.pending { background: #fef3c7; color: #b45309; }
.pill.inprogress { background: #dbeafe; color: #1d4ed8; }
.pill.completed { background: #dcfce7; color: #15803d; }
.pill.cancelled { background: #f3f4f6; color: #6b7280; }
.pill.refunded { background: var(--red-soft); color: var(--red); }

/* order confirmation */
.confirm-hero { text-align: center; padding: 46px 16px 26px; }
.confirm-hero .big-ico { width: 84px; height: 84px; margin: 0 auto 18px; border-radius: 99px; background: var(--green-soft); display: grid; place-items: center; font-size: 40px; animation: pop .4s ease; }
@keyframes pop { 0% { transform: scale(.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
.order-no { font-family: ui-monospace, Consolas, monospace; font-size: 22px; font-weight: 800; color: var(--brand); background: var(--brand-soft); display: inline-block; padding: 8px 20px; border-radius: 12px; margin: 10px 0; }

.deliver-box { background: #0f0d1a; color: #d1fae5; border-radius: 14px; padding: 18px; font-family: ui-monospace, Consolas, monospace; font-size: 13.5px; white-space: pre-wrap; word-break: break-word; }
.deliver-box .db-title { color: #34d399; font-weight: 800; font-family: var(--font); margin-bottom: 8px; display: flex; align-items: center; gap: 7px; }

/* account */
.acct-layout { display: grid; grid-template-columns: 250px 1fr; gap: 24px; align-items: start; margin: 26px 0; }
.acct-nav a { display: flex; gap: 10px; align-items: center; padding: 12px 15px; border-radius: 12px; font-weight: 600; font-size: 14px; color: var(--ink-2); }
.acct-nav a.on, .acct-nav a:hover { background: var(--brand-soft); color: var(--brand); }
.order-row { display: flex; align-items: center; gap: 14px; padding: 15px 18px; border-bottom: 1px solid var(--line); cursor: pointer; transition: .12s; flex-wrap: wrap; }
.order-row:hover { background: var(--bg-2); }
.order-row:last-child { border: none; }
.order-row .on-no { font-family: ui-monospace, Consolas, monospace; font-weight: 800; font-size: 13.5px; }
.order-row .on-meta { font-size: 12.5px; color: var(--ink-3); }
.order-row .on-total { margin-left: auto; font-weight: 800; }

/* account dashboard */
.dash-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-bottom: 22px; }
.stat-card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); }
.stat-card .sc-ico { width: 42px; height: 42px; border-radius: 99px; background: var(--brand-soft); display: grid; place-items: center; font-size: 19px; margin-bottom: 12px; }
.stat-card .sc-n { font-size: 26px; font-weight: 800; letter-spacing: -.5px; line-height: 1.1; }
.stat-card .sc-l { font-size: 12.5px; color: var(--ink-2); font-weight: 600; margin-top: 2px; }
.dash-grid { display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr); gap: 18px; align-items: start; }
.dash-grid > * { min-width: 0; }
.dash-orders-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 20px 20px 6px; }
.dash-orders-head h2 { font-size: 19px; font-weight: 800; letter-spacing: -.3px; }
.dorder { display: flex; align-items: center; gap: 14px; margin: 12px 16px; padding: 15px 16px; min-height: 96px; border: 1px solid var(--line); border-radius: 14px; cursor: pointer; transition: .15s; }
.dorder:hover { border-color: var(--brand); box-shadow: var(--shadow); }
.dorder .do-ico { width: 42px; height: 42px; flex-shrink: 0; border-radius: 99px; background: var(--brand-soft); display: grid; place-items: center; font-size: 18px; }
.dorder .do-main { flex: 1; min-width: 0; }
.dorder .do-no { font-family: ui-monospace, Consolas, monospace; font-weight: 800; font-size: 13.5px; display: inline-block; margin-right: 8px; }
.dorder .do-items { font-size: 12.5px; color: var(--ink-2); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dorder .do-right { text-align: right; flex-shrink: 0; }
.dorder .do-total { font-weight: 800; font-size: 14.5px; }
.dorder .do-date { font-size: 11.5px; color: var(--ink-3); }
.dorder .do-arrow { color: var(--ink-3); font-size: 17px; }
.profile-card { text-align: left; }
.profile-card .pc-top { display: flex; align-items: center; gap: 13px; padding-bottom: 16px; border-bottom: 1px solid var(--line); margin-bottom: 6px; }
.profile-card .pc-top b { font-size: 15.5px; display: block; }
.profile-card .pc-top span { font-size: 12.5px; color: var(--ink-3); word-break: break-all; }
.pr-row { display: flex; justify-content: space-between; gap: 12px; padding: 11px 0; border-bottom: 1px solid #f1f2f6; font-size: 13px; }
.pr-row:last-of-type { border-bottom: none; }
.pr-row .k { font-size: 10.5px; font-weight: 800; letter-spacing: 1px; color: var(--ink-3); text-transform: uppercase; padding-top: 2px; flex-shrink: 0; }
.pr-row .v { font-weight: 600; text-align: right; word-break: break-word; }
@media (max-width: 1000px) { .dash-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 720px) {
  .dash-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .stat-card { padding: 15px; }
  .stat-card .sc-ico { width: 36px; height: 36px; font-size: 17px; margin-bottom: 8px; }
  .stat-card .sc-n { font-size: 22px; }
  /* order rows: order number on one line, no cramped arrow, tidy spacing */
  .dorder { margin: 10px 0; gap: 11px; padding: 13px 14px; min-height: 88px; align-items: center; }
  .dorder .do-ico { width: 36px; height: 36px; font-size: 15px; }
  .dorder .do-no { white-space: nowrap; font-size: 12px; margin-right: 6px; }
  .dorder .do-items { font-size: 12px; }
  .dorder .do-total { font-size: 13.5px; }
  .dorder .do-date { font-size: 11px; }
  .dorder .do-arrow { display: none; }
}

/* CMS page */
.cms { max-width: 780px; margin: 30px auto 60px; }
.cms h1 { font-size: 30px; font-weight: 800; letter-spacing: -.6px; margin-bottom: 18px; }
.cms h2 { font-size: 22px; margin: 26px 0 10px; }
.cms h3 { font-size: 17px; margin: 20px 0 8px; }
.cms p, .cms li { color: var(--ink-2); font-size: 15px; margin-bottom: 10px; }
.cms ul, .cms ol { padding-left: 22px; margin-bottom: 14px; }
.cms img { border-radius: 14px; }

/* toolbar (category page) */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 20px; }
/* `height` pins the select to the input's natural 41px. Both already share this
   rule's padding and border, but a native <select> has a taller intrinsic
   content box, so the two sat 2px apart while side by side in the toolbar.
   41px is the height the input already renders at — nothing is resized, the
   select is simply brought onto it. Padding, border, radius and typography are
   untouched, and the select stays a native control. */
.toolbar select, .toolbar input { padding: 10px 13px; border: 1.5px solid var(--line); border-radius: 11px; outline: none; background: #fff; height: 41px; }
.toolbar input { flex: 1; min-width: 160px; }
/* Same focus treatment as .field / .coupon-row / .ac-coupon / .a2-inp — the
   storefront's one field-focus style, reused verbatim. This is the DESKTOP
   Products search (#q, the only .toolbar input in the storefront); the app-mode
   one is .apc-search in products.html.

   It had no :focus rule of its own, and the `outline: none` on the base rule
   above could not save it: `.toolbar input` and the global `input:focus-visible`
   are BOTH (0,1,1), and the global rule is declared later in this same file, so
   it won on source order and painted the hard ring. Giving the field its own
   :focus style plus the (0,2,1) opt-out below settles it by weight instead.

   Scoped to `input`, not `.toolbar select` — the Sort dropdown is not a text
   field and deliberately keeps the global ring. */
/* 🛑 PRODUCTS SEARCH HAS NO FOCUS INDICATOR — deliberate product decision.
   The desktop Products search must look pixel-identical focused and unfocused:
   no outline, no halo, no border-colour change. `border-color` is pinned back
   to --line so the 1.5px border stays exactly as it renders unfocused, which
   also keeps the box dimensions untouched. Scoped to `input` only — the Sort
   `select` beside it is not a Products search and keeps the global ring.
   ⚠️ WCAG 2.4.7 failure on this field only, requested explicitly. */
.toolbar input:focus, .toolbar input:focus-visible {
  outline: none;
  box-shadow: none;
  border-color: var(--line);
}
.chip { border: 1.5px solid var(--line); background: #fff; border-radius: 999px; padding: 7px 15px; font-size: 13px; font-weight: 700; color: var(--ink-2); white-space: nowrap; }
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }

/* skeletons */
.skel { background: linear-gradient(90deg, #f1f2f6 25%, #e9eaf0 50%, #f1f2f6 75%); background-size: 200% 100%; animation: shim 1.3s infinite; border-radius: var(--radius); }
@keyframes shim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.skel-card { height: 300px; }

/* empty states */
.empty { text-align: center; padding: 56px 16px; color: var(--ink-2); }
.empty .e-ico { font-size: 46px; margin-bottom: 12px; }
.empty b { display: block; font-size: 17px; color: var(--ink); margin-bottom: 5px; }

/* toast */
.toast { position: fixed; bottom: 88px; left: 50%; transform: translateX(-50%) translateY(20px); background: #111827; color: #fff; padding: 12px 22px; border-radius: 12px; font-weight: 700; font-size: 13.5px; opacity: 0; pointer-events: none; transition: .25s; z-index: 200; box-shadow: var(--shadow-lg); max-width: 90vw; text-align: center; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* modal */
.modal { position: fixed; inset: 0; z-index: 150; display: none; align-items: center; justify-content: center; padding: 16px; }
.modal.open { display: flex; }
.modal-back { position: absolute; inset: 0; background: rgba(17, 24, 39, .5); }
.modal-box { position: relative; background: #fff; border-radius: 18px; max-width: 560px; width: 100%; max-height: 88vh; overflow-y: auto; padding: 24px; box-shadow: var(--shadow-lg); animation: pop .25s ease; }
.modal-x { position: absolute; top: 14px; right: 14px; border: none; background: var(--bg-2); width: 32px; height: 32px; border-radius: 99px; font-size: 15px; color: var(--ink-2); }

/* auth */
.auth-box { max-width: 420px; margin: 44px auto 70px; }
.auth-tabs { display: flex; background: var(--bg-2); border-radius: 13px; padding: 4px; margin-bottom: 22px; }
.auth-tabs button { flex: 1; border: none; background: none; padding: 10px; font-weight: 700; font-size: 14px; border-radius: 10px; color: var(--ink-2); }
.auth-tabs button.on { background: #fff; color: var(--brand); box-shadow: var(--shadow); }

/* desktop header: logo left · nav pills center · icons right;
   search opens as a dropdown panel from the magnifier icon */
@media (min-width: 721px) {
  .header-in { position: relative; }
  .header-in .logo { flex: 0 0 auto; }
  .header-in .hactions { flex: 0 0 auto; }
  .search { display: none; position: absolute; top: calc(100% + 10px); left: 50%; transform: translateX(-50%); width: min(620px, 94%); background: #fff; border: 1px solid var(--line); border-radius: 18px; box-shadow: var(--shadow-lg); padding: 10px; z-index: 70; }
  .search.desk-open { display: block; }
  .search .search-drop { position: static; margin-top: 8px; box-shadow: none; border: none; border-top: 1px solid var(--line); border-radius: 0; }
}

/* Collapse the header to the hamburger at 900px, not 720px.
   Measured on the live site at 1280px: logo 132px + nav pills 396px +
   actions 265px + two 14px gaps + the .wrap 16px side padding needs ~853px of
   viewport. Between 721px and that figure the nav had nowhere to go and
   overlapped the logo and the currency pill. The 720px block below also drives
   the bottom nav, the product grids and the footer, so it cannot simply be moved
   up — this band-limited block collapses the header and nothing else. The
   magnifier, currency, bell, cart and account controls all live in .hactions and
   stay put; the hidden links are already mirrored in #mobileMenu. */
@media (min-width: 721px) and (max-width: 900px) {
  .hnav, .hnav-right { display: none; }
  .hamb { display: grid; }
  /* .hnav carried `flex: 1`, so hiding it removes the only thing pushing the
     icons to the right edge — without this they bunch up against the logo. */
  .hactions { margin-left: auto; }
}

/* ---------- responsive ---------- */
@media (max-width: 1000px) {
  .pgrid { grid-template-columns: repeat(3, 1fr); }
  .hcat-grid { grid-template-columns: repeat(4, 1fr); }
  .prow { grid-auto-columns: calc((100% - 32px) / 3); }
  .footer-in { grid-template-columns: 1fr 1fr; }
  .pd { grid-template-columns: 1fr; gap: 24px; }
  .acct-layout { grid-template-columns: 1fr; }
  .acct-nav { display: flex; overflow-x: auto; gap: 6px; }
  .acct-nav a { white-space: nowrap; }
}
@media (max-width: 720px) {
  body { padding-bottom: 74px; }
  .bottom-nav { display: flex; }
  .wa-float { bottom: 86px; right: 14px; width: 50px; height: 50px; }
  .wa-float svg { width: 26px; height: 26px; }
  .toast { bottom: 140px; }
  .footer { margin-bottom: 0; }
  .pgrid { grid-template-columns: repeat(2, 1fr); gap: 11px; }
  .hcat-grid { grid-template-columns: repeat(3, 1fr); gap: 9px; }
  .hcat { padding: 14px 6px; border-radius: 14px; gap: 8px; }
  .hcat .ic { width: 42px; height: 42px; border-radius: 13px; }
  .hcat .ic svg { width: 20px; height: 20px; }
  .hcat b { font-size: 11.5px; }
  .prow { grid-auto-columns: calc((100% - 11px) / 2); gap: 11px; }
  .trust { grid-template-columns: 1fr; }
  .cart-layout { grid-template-columns: 1fr; }
  .cart-item img { width: 84px; height: 66px; }
  .footer-in { grid-template-columns: 1fr; gap: 26px; }
  /* top bar: one short line, extra items hidden instead of wrapping */
  .topbar { font-size: 11.5px; padding: 6px 10px; }
  .topbar .tb-extra { display: none; }

  /* header: single row; tapping the magnifier reveals a full-width search row */
  .header-in { flex-wrap: wrap; height: auto; padding: 10px 16px 12px; row-gap: 10px; gap: 8px; }
  .search { display: none; flex: 1 1 100%; order: 10; max-width: none; margin: 0; }
  .search.m-open { display: block; }
  .search input { padding: 10px 15px 10px 40px; font-size: 13.5px; }
  .hactions { margin-left: auto; gap: 3px; }
  .hnav, .hnav-right { display: none; }
  .hamb { display: grid; }
  /* compact header so the currency switcher fits on phones */
  .logo { font-size: 17px; gap: 7px; }
  .logo-mark { width: 32px; height: 32px; border-radius: 9px; }
  .logo-mark svg { width: 17px; height: 17px; }
  .hbtn { width: 37px; height: 37px; }
  .hactions .cur-select { display: inline-block; padding: 6px 26px 6px 11px; font-size: 11.5px; background-position: right 8px center; }
  .hactions .cur-btn { padding: 7px 9px 7px 12px; font-size: 12px; }
  .navrow { display: none; }
  .sec-head h2 { font-size: 18px; }
  .section { margin: 30px 0; }
  .pd-info h1 { font-size: 21px; }
  /* NOTE: a `.slide img { aspect-ratio: 1/1 }` rule lived here and was dead code —
     `.slider .slide img` in the ≤768px block above is more specific (0,2,1 vs
     0,1,1) and wins regardless of source order, so 1/1 never applied at any
     width. Removed: a second, conflicting hero ratio in the file is exactly how
     the placeholder and the banner drift apart again. */
  .cta-block h2 { font-size: 21px; }
  .hero { margin-top: 12px; }
}

/* ============================================================
   REFERENCE-NUMBER LEGIBILITY  (typography only)
   Storefront twin of the block at the end of admin/admin.css — admin.css is
   not loaded on the public site, so the rule has to exist in both. Keep in sync.
   Order references render in the app's own UI font at 600 with .015em tracking:
   compact, native to the interface, with just a hint of air between characters.
   Deliberately NOT monospace — that read as code and forced the IDs wide.

   Delivered credentials (.vh-cred) and bank/payment TIDs stay monospace on
   purpose: those really are opaque strings a customer transcribes character by
   character, which is the case monospace genuinely serves.

   🛑 Presentation only. No text is altered — the value stays exactly as
      stored, so copy, tracking lookup and validation are untouched. Never
      "fix" legibility by inserting spaces into the string itself.
   ============================================================ */
.order-no,
.order-row .on-no,
.dorder .do-no,
.vh-no,
.ord-stat .no,
.ord-row-m .no,
.aa-ot .no {
  font-family: var(--font-id);
  font-weight: 600;
  letter-spacing: .015em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}
/* the customer-facing ones sit in lists, not headings — nudge up to match admin */
.order-row .on-no, .dorder .do-no, .vh-no { font-size: 13.5px; }
/* the big tracking-page number keeps its display size */
.order-no { font-size: 22px; font-weight: 600; }

/* ============================================================
   PAGE TRANSITIONS  (progressive enhancement)
   Cross-document view transitions give same-origin navigations a native-feeling
   cross-fade with no router and no JS. Browsers without support simply ignore
   the at-rule and navigate exactly as before — there is no fallback to write.

   Deliberately a short cross-fade rather than a slide: this is a multi-page
   site, so a directional slide would need to know whether the navigation was a
   push or a pop, and getting that wrong reads worse than no animation at all.
   ============================================================ */
@view-transition { navigation: auto; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .18s; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}

/* ============================================================
   REVIEW CARD  (UI.reviewCard — one component, every surface)
   Each block is rendered only when its field has a value, so an empty
   field collapses completely rather than leaving a blank label behind.
   ============================================================ */
/* One surface divided by hairlines — NOT nested boxes. The old card stacked a
   bordered product tile inside a bordered card, which is what made it read as
   several widgets glued together. Here rating / quote / product / reviewer are
   four bands of the same sheet, separated by 1px rules. */
.tc { gap: 0; }

/* 1 — RATING BAND. Stars and the numeric score lead; the date is pushed right
   and demoted to the lightest ink on the card so it stops competing. */
.tc-rating { display: flex; align-items: center; gap: 11px; padding-bottom: 4px; }
.tc-score { font-size: 19px; font-weight: 800; letter-spacing: -.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.tc-when { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--ink-3); white-space: nowrap; }

/* 2 — QUOTE. The hero, and the fix for the card's worst problem.
   🛑 `flex: 1` + `align-items: center` is what kills the empty area. Cards in a
      .prow stretch to the tallest sibling, so a two-word review used to sit
      pinned to the top with a void beneath it. The quote block now claims that
      free space and centres its content inside it, so a short review lands in
      the optical middle of the card and reads as deliberate.
   🛑 The mark sits BESIDE the text, not above it. As a block it cost ~60px of
      pure decoration — the very space this section was trying to reclaim. As a
      flex sibling it costs 13px of width and nothing vertically. */
.tc-quote { margin: 0; flex: 1; display: flex; align-items: center; gap: 13px; padding: 18px 0; }
.tc-quote::before { content: "\201C"; flex-shrink: 0; align-self: flex-start; margin-top: 3px;
                    font: 700 40px/.62 Georgia, "Times New Roman", serif; color: #dee1e9; }
.tc-quote .rev-text { flex: 1; min-width: 0; }

/* 3 — REVIEWED PRODUCT. Identity only: icon, name, category, go. The rating and
   the Best seller / Featured badge were removed — the card already states its
   rating in the header, and repeating it inside the panel made the reader parse
   the same fact twice and wonder which one applied to what. The badge was
   product marketing inside someone else's opinion.
   🛑 .tc-pbody height matches .tc-thumb exactly, so the two columns are
      optically level and the panel height stays constant whether or not a
      product has a category. Both remaining fields are single-line. */
/* Internal rules are lighter than --line: at #e5e7eb they read as structural
   borders and chopped the card into three boxed regions. #eef0f5 divides
   without dominating, which is what makes the bands feel like one sheet. */
.tc-plink { margin-top: auto; padding-top: 20px; border-top: 1px solid #eef0f5; }
.tc-plabel { display: block; margin-bottom: 12px; font-size: 10.5px; font-weight: 800;
             letter-spacing: 1.1px; text-transform: uppercase; color: var(--ink-3); }
.tc-prod { display: flex; align-items: center; gap: 14px; padding: 14px; border-radius: 18px;
           background: var(--bg-2); border: 1px solid transparent; text-decoration: none;
           transition: background .18s ease, border-color .18s ease; }
.tc-prod:hover { background: #fff; border-color: var(--line); }
.tc-thumb { width: 64px; height: 64px; border-radius: 16px; object-fit: cover; flex-shrink: 0; background: #fff; }
.tc-pbody { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center;
            gap: 3px; height: 64px; overflow: hidden; }
.tc-pname { font-size: 15px; font-weight: 800; color: var(--ink); letter-spacing: -.25px;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tc-cat { font-size: 13px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Circular affordance rather than a bare glyph — it reads as a button, which is
   what makes the whole panel feel clickable without adding any label text. */
.tc-go { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%;
         background: #eceef4; color: var(--ink-2); font-size: 15px; line-height: 1; flex-shrink: 0;
         transition: background .18s ease, color .18s ease, transform .18s ease; }
.tc-prod:hover .tc-go { background: var(--brand); color: #fff; transform: translateX(2px); }

/* 4 — REVIEWER FOOTER. Second rule closes the card. */
.tc-by { display: flex; align-items: center; gap: 13px; margin-top: 20px; padding-top: 20px; border-top: 1px solid #eef0f5; }
.tc-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tc-who b { font-size: 14.5px; font-weight: 800; letter-spacing: -.3px; color: var(--ink); line-height: 1.25; }
.tc-verified { font-size: 12px; font-weight: 600; color: var(--green); letter-spacing: -.1px; }
.tc-guest { font-size: 12px; font-weight: 600; color: var(--ink-3); letter-spacing: -.1px; }
.rc-status { margin-left: auto; font-size: 10.5px; font-weight: 800; text-transform: capitalize;
             border-radius: 999px; padding: 2px 9px; background: var(--bg-2); color: var(--ink-3); }
.rc-status.approved { background: var(--green-soft); color: var(--green); }
.rc-status.pending { background: #fff7ed; color: #c2410c; }

/* ============================================================
   CHECKOUT APP HEADER
   The gradient bar cart/product/account use, so checkout finally matches the
   rest of the app. Values copied from .ac-top in cart.html on purpose — one
   design language, not a second one.

   🛑 Visibility is decided by CSS ALONE, never by JS. The header markup is in
      checkout.html from the first byte; this block reveals it. An earlier
      version injected the header with JS after load, so the browser painted
      the DESKTOP layout first and then swapped — a visible flash on every
      load. Same fail-safe shape as the footer rule: hidden is the default,
      showing requires the explicit ?app=0 opt-out (which adds .web-mode).
   ============================================================ */
.ck-page .ck-top { display: none; }

@media (max-width: 760px) {
  html:not(.web-mode) .ck-page .ck-top { display: flex; }
  html:not(.web-mode) .ck-page .header,
  html:not(.web-mode) .ck-page .topbar { display: none !important; }
  html:not(.web-mode) .ck-page .page-title,
  html:not(.web-mode) .ck-page .co-sub { display: none; }
}
/* installed PWA at any width */
.app-mode .ck-page .ck-top { display: flex; }
.app-mode .ck-page .header, .app-mode .ck-page .topbar { display: none !important; }
.app-mode .ck-page .page-title, .app-mode .ck-page .co-sub { display: none; }

.ck-page .ck-top {
  align-items: center; gap: 12px;
  padding: calc(14px + env(safe-area-inset-top)) 16px 18px;
  background: var(--brand-grad, linear-gradient(135deg, #1a3fb5, #4f6df5));
  color: #fff; border-radius: 0 0 22px 22px;
  box-shadow: 0 8px 22px rgba(37, 99, 235, .22);
}
.ck-page .ck-ico {
  width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0;
  background: rgba(255, 255, 255, .18); border: none; color: #fff;
  display: grid; place-items: center; cursor: pointer; transition: background .15s;
}
.ck-page .ck-ico:active { background: rgba(255, 255, 255, .3); }
.ck-page .ck-ico svg { width: 20px; height: 20px; }
.ck-page .ck-top b { display: block; font-size: 18px; font-weight: 800; color: #fff; line-height: 1.2; }
.ck-page .ck-top span { display: block; font-size: 12px; color: rgba(255, 255, 255, .85); margin-top: 2px; }

/* ---- BREATHING ROOM ----
   The cards ran flush to the header and to each other, which is what read as
   congested. Space the stack, and give the cards a comfortable inner padding
   instead of the 24px desktop value squeezed into a phone width. */
@media (max-width: 760px) {
  html:not(.web-mode) .ck-page .wrap { padding-top: 18px; padding-left: 14px; padding-right: 14px; }
  html:not(.web-mode) .ck-page .cart-layout { gap: 16px; }
  html:not(.web-mode) .ck-page .co-card { padding: 18px 16px; margin-bottom: 16px; border-radius: 18px; }
  html:not(.web-mode) .ck-page .co-head { margin-bottom: 16px; }
  html:not(.web-mode) .ck-page .co-card .field { margin-bottom: 15px; }
  html:not(.web-mode) .ck-page .pay-tile { padding: 15px 16px; }
  html:not(.web-mode) .ck-page .pay-grid { gap: 11px; }
}

/* ============================================================
   TOUCH TARGETS — phones only (max-width: 768px)

   🛑 EVERY rule here adds INVISIBLE hit area. Nothing may change a control's
      painted size, colour, radius, type or position — only the region that
      responds to a tap. If a change is visible, it does not belong in here.

   The controls below were each measured under Apple's 44x44 minimum on a
   375px viewport (hit area probed with elementFromPoint, not assumed from
   the box). They live in per-page <style> blocks (index/products/product/
   categories) but the fix sits here because store.css loads on every page.

   Only the DEFICIENT dimension is expanded. Icon buttons get 44x44; wide
   controls that are merely short get height only, so a pad never reaches
   sideways into the control next to it.
   Desktop (>768px) never sees this block.
   ============================================================ */
@media (max-width: 768px) {
  /* the pad needs a positioned host. `relative` moves and resizes nothing —
     and these four do not set `position` themselves, so nothing is overridden.
     (.ah-bell, .apc-ico and .snav are already positioned; left alone.) */
  .ah-av, .ah-sec a, .ah-card .add, .apc-card .add, .ap-ico, .acp-back { position: relative; }

  /* 44x44 icon buttons: avatar, bell, add-to-cart, back/share/cart icons */
  .ah-av::after, .ah-bell::after,
  .ah-card .add::after, .apc-card .add::after,
  .ap-ico::after, .apc-ico::after, .acp-back::after,
  .snav::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }

  /* "See all" section links are 37x19 text links at the end of their row —
     free space above, below and to the right, so a full pad is safe here. */
  .ah-sec a::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 100%; min-width: 44px; height: 44px;
  }

  /* .ah-av carries `overflow: hidden` to crop its <img> to the circle — which
     also clipped the pad back to 40x40 (measured: the pad had no effect until
     this rule existed). Move the crop onto the image itself: it already fills
     the 40px circle at object-fit:cover, so the result is pixel-identical and
     the pad can now escape the box.
     🛑 `body` prefix is required — index.html's <style> is parsed AFTER this
        file, so a bare `.ah-av` here loses to the page's `overflow: hidden`. */
  body .ah-av { overflow: visible; }
  body .ah-av img { border-radius: 50%; }

  /* The catalog search field is a 21px-tall input inside a 44px pill. The
     border and background belong to .apc-search, so stretching the input to
     the pill it already sits in is invisible and doubles the tappable height. */
  .apc-search input { align-self: stretch; }
}

/* ============================================================
   KEYBOARD FOCUS VISIBILITY

   🛑 :focus-visible ONLY — never :focus. A mouse click must look exactly as it
      always has; the ring appears only for keyboard/AT navigation.

   Previously this block listed only six form controls — the ones that set
   `outline: none` with no replacement. Everything else (buttons, links, product
   and category cards, icon buttons, nav, cart, notification, chatbot and
   checkout controls) fell back to the BROWSER DEFAULT ring, which differs per
   engine, is often invisible against tinted or brand-filled surfaces, and does
   not follow the component's border-radius. The rule below replaces that with
   one consistent, brand-coloured ring for every interactive element.

   Controls that strip the ring but DO replace it keep their own treatment and
   are deliberately left alone (their border + brand shadow IS the indicator):
     .search input        -> border + brand shadow  (store.css)
     .field input/select  -> border + brand shadow  (store.css)
     .coupon-row input    -> border + brand shadow  (store.css)   cart, desktop
     .ac-coupon input     -> border + brand shadow  (cart.html)   cart, app mode
     .a2-inp input        -> border + brand shadow  (auth.html)
     .toolbar input       -> border + brand shadow  (store.css)   products, desktop
     .apc-search          -> brand shadow on the WRAPPER via :focus-within
                             (products.html) — the input itself is border:none
                             and transparent, so the wrapper IS the field
                             (products, app mode)

   Ring matches the admin design system (outline 2px brand / offset 2px).
   `outline` is painted outside the box and NEVER participates in layout, so
   nothing moves, reflows or changes size. Modern engines follow the element's
   own border-radius, so the ring is round on pills and square on squares with
   no per-component rules.
   ============================================================ */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* 🛑 Text fields that ALREADY draw their own focus treatment opt out of the
   global ring — otherwise they render TWO blue indicators at once.

   Each scope below owns a `:focus` rule (not `:focus-visible`) that sets
   `border-color: var(--brand)` plus a 4px
   `box-shadow: 0 0 0 4px var(--brand-soft)` halo:
       .search input:focus      store.css:104
       .field   input/select/textarea:focus   store.css:914
       .coupon-row input:focus  store.css:939
       .a2-inp  input:focus     auth.html:39
   Because those use `:focus`, a KEYBOARD focus matches them AND the global
   `input:focus-visible` rule, so the field painted its own border+halo and the
   global rule added an outline outside it — the double ring.

   This removes only the outline, so the field keeps the exact focus appearance
   it had before the global rule existed. It is NOT an accessibility loss: the
   border+halo is the visible indicator, and it is present for mouse and
   keyboard alike. Scoped by wrapper class, so it is deliberately narrow —
   fields with NO focus styling of their own (the app-mode .ap-form textarea,
   the #avZoom range slider, .apc-search / .apc-bar) are not listed and keep
   the global ring as their only indicator.

   Specificity: each is (0,2,1) against the global rule's (0,1,1), so this wins
   on weight, not on source order. `.a2-inp` and `.ac-coupon` are declared here
   rather than in auth.html / cart.html because neither page's own rule sets
   `outline` — there is nothing there to override.

   🛑 The CART HAS TWO COUPON INPUTS, one per renderer, and BOTH must be listed:
   `.coupon-row input` is the desktop DOM, `.ac-coupon input` is the app-mode
   DOM (cart.html branches on UI.isAppMode()). Fixing only one leaves the other
   falling through to the global outline — which is exactly what happened once
   already. Same trap applies to any other component with split renderers. */
.search input:focus-visible,
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible,
.coupon-row input:focus-visible,
.a2-inp input:focus-visible,
.ac-coupon input:focus-visible,
.ap-form textarea:focus-visible,
.toolbar select:focus-visible, .apc-bar select:focus-visible, .cur-select:focus-visible {
  outline: none;
}
/* The three Products search fields (.apc-search, .toolbar input, .aserch-field)
   used to be listed above too. They now carry dedicated no-indicator rules that
   already set `outline: none` at the same specificity, so listing them here as
   well was a duplicate declaration with no effect. Removed — each is suppressed
   at exactly one place: .toolbar/.aserch-field above in this file,
   .apc-search in products.html. */

/* 🛑 The ONLY exception, and it is about clipping, not styling. An outline with
   a positive offset is painted outside the element's border box, so an ANCESTOR
   with `overflow: hidden` crops it. Each control below sits flush against such
   an ancestor (.pcard, .qty, .ac-qty, .ap-qty, .acc, .curpick, .nsheet,
   .search-drop, .slider, .pay-details), so its ring would be cut off on the
   flush edges. Pulling the offset inside keeps the full ring visible.
   Same colour, same width — only the offset differs, so there is one focus
   treatment, not two. Every control here has a light background, so a brand
   ring drawn just inside its edge stays clearly visible; brand-FILLED controls
   (.btn-primary, .ah-card .add, .ap-add …) are deliberately not in this list —
   an inset ring would vanish into their own fill. */
.pcard-img:focus-visible,
.qty button:focus-visible,
.ac-qty button:focus-visible,
.ap-qty button:focus-visible,
.acc-head:focus-visible,
.curpick-opt:focus-visible,
.nitem:focus-visible,
.search-drop a:focus-visible,
.slide:focus-visible,
.pay-details .copy-ic:focus-visible {
  outline-offset: -2px;
}

/* ============================================================
   DELIVERED CREDENTIALS CARD  (.cred-*)
   Shared by both order-page renderers (web + app mode) so the two
   cannot drift. Mint surface + green border marks it as the one
   "your goods are here" moment on the page; every colour is an
   existing token (--green, --green-soft, --line, --ink-*).
   ============================================================ */
.cred-card { background: var(--green-soft); border: 1px solid #bbf7d0; border-radius: 16px; padding: 20px; margin-bottom: 22px; }
.cred-head { display: flex; align-items: flex-start; gap: 13px; margin-bottom: 14px; }
.cred-ic { width: 42px; height: 42px; border-radius: 50%; background: #fff; color: var(--green); display: grid; place-items: center; flex-shrink: 0; }
.cred-ic svg { width: 21px; height: 21px; }
.cred-head b { display: block; font-size: 16px; font-weight: 800; color: var(--ink-1); }
.cred-head span { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 2px; }
/* pre-wrap + break-word: credentials arrive as multi-line text and must never
   overflow the card or collapse their line breaks. */
.cred-box { background: #fff; border: 1px solid #d1fae5; border-radius: 12px; padding: 14px 15px; font-family: ui-monospace, Consolas, monospace; font-size: 13.5px; line-height: 1.65; color: var(--ink-1); white-space: pre-wrap; word-break: break-word; }
.cred-box.masked { color: var(--ink-3); letter-spacing: 1px; }
.cred-actions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 13px; }
.cred-btn { display: inline-flex; align-items: center; gap: 7px; background: #fff; border: 1px solid #bbf7d0; border-radius: 999px; padding: 9px 15px; font-size: 12.5px; font-weight: 700; color: var(--ink-1); cursor: pointer; text-decoration: none; transition: .15s; }
.cred-btn:hover { border-color: var(--green); color: var(--green); }
.cred-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.cred-btn.ok { border-color: var(--green); color: var(--green); }
@media (max-width: 720px) {
  .cred-card { padding: 16px; }
  .cred-btn { flex: 1 1 auto; justify-content: center; }
}

/* ============================================================
   TOUCH TARGET SIZE  (WCAG 2.5.5 / Apple HIG: 44x44 minimum)

   Icon-only controls are drawn at 27-42px because that is the right VISUAL
   size — the problem is only that the tappable region matched the icon. Each
   control below keeps its exact painted size and gains an invisible centred
   ::after that carries the hit area out to 44x44.

   🛑 NOTHING VISUAL CHANGES. The pseudo-element has no background, border or
      content, so it is never painted. It is a child of the control, so taps on
      it still resolve to the control. `position: absolute` keeps it out of
      layout entirely — no reflow, no size change, no shift in spacing or
      alignment, on desktop or mobile.
   🛑 `position: relative` is added ONLY to controls that are currently static.
      The seven that are already absolutely positioned (.vpick-x, .modal-x,
      .tmodal-x, .prof-av-btn, .snav, .a2-eye) are left alone — they already
      establish a containing block, and re-anchoring them would move them.
   ============================================================ */
.aa-back, .aa-hback, .ord-back, .ac-ico, .ck-page .ck-ico, .aserch-back,
.nsheet-x, .curpick-x, .ci2-trash, .copy-ic, .vh-bot-x, .cx-go {
  position: relative;
}

.hbtn::after, .ah-bell::after, .aa-back::after, .aa-hback::after,
.ord-back::after, .ap-ico::after, .apc-ico::after, .acp-back::after,
.ac-ico::after, .ck-page .ck-ico::after, .aserch-back::after,
.nsheet-x::after, .vpick-x::after, .curpick-x::after, .modal-x::after,
.tmodal-x::after, .ci2-trash::after, .copy-ic::after, .vh-bot-x::after,
.prof-av-btn::after, .snav::after, .cx-go::after,
.ah-card .add::after, .apc-card .add::after, .a2-eye::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
}

/* 🛑 The one control where a full 44px would OVERLAP its neighbour. In the
      mobile header .hactions drops to `gap: 3px` and .hbtn to 37px, so two
      buttons expanded to 44 would each reach 3.5px toward the other and steal
      4px of each other's taps — a worse bug than the one being fixed. Width is
      therefore capped at 37 + 3 = 40px, exactly filling the gap and no more;
      height still reaches the full 44px because the header is a single row
      with nothing above or below to collide with. */
@media (max-width: 720px) {
  .hbtn::after { width: 40px; }
}

/* ------------------------------------------------------------
   QUANTITY STEPPERS — the three controls a centred pseudo could NOT fix.

   Each pill carries `overflow: hidden` to clip its buttons' square corners to
   the pill radius. A centred 44px pseudo grows in ALL FOUR directions, so on a
   30x32 button it pushes 7px past the left/right pill edge and 6px past the
   top/bottom — straight into the clip, which discards exactly the area that was
   supposed to become tappable. It measured as fixed and was not.

   The fix uses the two axes differently, because they are not symmetrical:

   VERTICAL — the pseudo cannot exceed the pill, so the pill must genuinely be
   44px tall. That is the one real dimension change, applied only where the pill
   is short (.ap-qty 40->44, .ac-qty 32->44). .qty is already 44 and untouched.

   HORIZONTAL — the <span> holding the number is inert dead space INSIDE the
   pill. Anchoring each pseudo to its button's OUTER edge grows it inward across
   that span, so it never reaches a pill edge and `overflow: hidden` never sees
   it. No pill gets wider.
   🛑 The two hit areas must not meet. Measured: .qty leaves 36px clear,
      .ap-qty 6px. .ac-qty's pill was 86px and two 44px targets need 88, so its
      span grows 26->28px — the only width change in this fix, and the minimum
      that makes the arithmetic work.
   ------------------------------------------------------------ */
.qty button, .ap-qty button, .ac-qty button { position: relative; }

.qty button::after, .ap-qty button::after, .ac-qty button::after {
  content: "";
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
}
.qty button:first-child::after,
.ap-qty button:first-child::after,
.ac-qty button:first-child::after { left: 0; }

.qty button:last-child::after,
.ap-qty button:last-child::after,
.ac-qty button:last-child::after { right: 0; }
