/* ==========================================================================
   Promio — Anchor DS tokens + base layer
   Source: docs/design-system.md (Appendix — CSS tokens) + §01 Foundations.
   Every wireframe in docs/wireframe/ links to this file.

   THIS BUILD carries **palette D · Promio Warm Teal** (agreed 2026-08-03) and the
   **Plus Jakarta Sans + JetBrains Mono** pairing. Every deviation from the original
   Anchor is listed in docs/design-system.md, under "## Promio deviations".
   Tokens marked [+] are additions beyond the Anchor Appendix.
   ========================================================================== */

/* ==========================================================================
   @font-face — SELF-HOSTED, no CDN. These are the assets shipped to the Worker.
   Variable woff2, subset to latin + latin-ext + vietnamese, with the full OpenType
   layout feature set preserved (so "tnum" still works). Source: github.com/google/fonts
   (OFL 1.1), subset with fontTools. Two files, ~98 KB in total.
   ========================================================================== */

@font-face {
  font-family: "Plus Jakarta Sans";
  src: url("/assets/fonts/plus-jakarta-sans-var.woff2") format("woff2");
  font-weight: 200 800;          /* variable wght — covers 400/500/600/700 */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/jetbrains-mono-var.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* --- Deep Teal — foundation (Anchor §01.1, palette D values) ------------ */
  --teal-900: #023F47;
  --teal-800: #046B76;   /* primary brand: topbar, links, Dark buttons */
  --teal-700: #0B818C;
  --teal-600: #1A9AA4;
  --teal-500: #57B7BE;
  --teal-300: #C2E9EC;   /* AA fix: inactive nav on the topbar */
  --teal-100: #D6EFF0;

  /* --- Signal Green — INTERACTION ONLY (Anchor §00.2) --------------------- */
  --green-700: #009A5C;  /* focus ring — 3.64:1 on white, 3.11:1 on the app background */
  --green-600: #00BD70;  /* Primary button hover */
  --green-500: #00D47E;  /* Primary button fill, active nav underline, ring on dark surfaces */
  --green-100: #C9F6E1;  /* Ghost button hover fill */

  /* --- Neutrals — warm bone (palette D: hue 43° instead of cold-blue 206°) - */
  --ink-900: #161A17;
  --ink-700: #3D4642;
  --ink-500: #666B64;    /* AA fix: 4.66:1 on the app background (was #6A6F68 = 4.40) */
  --ink-400: #6E6A5E;    /* AA fix: 4.62:1 on the app background (was #8A989D = 2.41) */
  --gray-200: #E2DCD0;   /* dividers and decorative rules ONLY — never control borders */
  --gray-100: #F0EDE6;   /* app background — warm bone */
  --gray-50:  #F8F6F1;
  --white:    #FFFFFF;

  /* [+] the border of real controls (input, select, textarea, Secondary button).
     gray-200 only reaches 1.37:1 on white — it fails WCAG 1.4.11 (3:1 required). */
  --border-control: #928C7B;   /* 3.35:1 on white, 3.11:1 on gray-50 */

  /* --- State — there is no "success" token by design (Anchor §00.1) ------- */
  --state-neutral:        #61665F;  --state-neutral-bg:   #F5F3EE;
  --state-attention:      #9A4A05;  --state-attention-bg: #FCEBD4;
  --state-attention-mark: #C96A0B;
  --state-tracked:        #1A5FCC;  --state-tracked-bg:   #E4EEFB;
  --state-linked:         #046B76;  --state-linked-bg:    #D6EFF0;
  --state-fault:          #C4123C;  --state-fault-bg:     #FDE7ED;
  --state-absent:         #6E6A5E;

  /* --- Typography --------------------------------------------------------- */
  /* Plus Jakarta Sans: has "tnum" (verified with fontTools) and a complete
     diacritics set. JetBrains Mono: dotted zero, tailed l, serifed I — 0/O and
     1/l/I cannot be confused in a coupon code. See font-lab.html. */
  --font-sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --fs-metric: 26px; --fs-h1: 28px; --fs-h2: 20px; --fs-h3: 16px;
  --fs-body: 16px; --fs-dense: 14px; --fs-small: 13px; --fs-caption: 11px;
  --fs-mono: 13px; --fs-mono-sm: 12px;

  /* [+] palette D: discount figures and the store-page H1 grow — they are what
     people came to read. Dense 14/20 is unchanged, so table rows stay at 36px. */
  --fs-offer: 30px;   --lh-offer: 34px;
  --fs-store-h1: 26px; --lh-store-h1: 33px;

  /* [+] line-height split out of the §01.2 table (previously only as "size / LH") */
  --lh-metric: 32px; --lh-h1: 36px; --lh-h2: 28px; --lh-h3: 24px;
  --lh-body: 25px; --lh-dense: 20px; --lh-small: 20px; --lh-caption: 14px;
  --lh-mono: 20px; --lh-mono-sm: 18px;

  /* [+] weight + tracking split out (§01.2 describes them in prose) */
  --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;
  --tracking-tight: -0.01em;   /* Metric, H1 */
  --tracking-caption: 0.08em;  /* Caption, badge, chip, column header */

  /* --- Spacing (4px grid) -------------------------------------------------- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px;

  /* --- Density (Anchor §01.3) --------------------------------------------- */
  --row-h: 36px;          /* table row — Compact */
  /* [+] CHANGED from the Appendix: the Anchor lists --row-pad-y: 10px, but 10 + 20
     (Dense 14/20 line-height) + 10 = 40px, which cannot produce the 36px row §01.3
     claims. The 36px figure is kept (a visible promise: ~18 rows per screen) and the
     padding drops to 8px: 8 + 20 + 8 = 36. */
  --row-pad-y: 8px;
  --row-pad-x: 12px;
  --card-pad: 20px;       /* Comfortable */
  --drawer-pad: 24px;

  /* --- Radius ------------------------------------------------------------- */
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-full: 999px;

  /* --- Elevation ----------------------------------------------------------
     [+] DEVIATION from §01.4 ("cards have NO shadow"): the app background and white
     cards only separate by 1.23:1, so banning shadows flattens the whole page. A soft
     warm-tinted shadow is allowed on cards. See design-system.md → "## Promio deviations". */
  --shadow-sm: 0 1px 2px rgba(64,48,20,.06);
  --shadow-md: 0 4px 12px rgba(64,48,20,.08);
  --shadow-lg: 0 12px 32px rgba(64,48,20,.16);
  --card-shadow:       0 1px 2px rgba(64,48,20,.05), 0 8px 22px -10px rgba(64,48,20,.24);
  --card-shadow-hover: 0 2px 4px rgba(64,48,20,.07), 0 14px 30px -12px rgba(64,48,20,.32);

  /* [+] topbar/sidebar background — a very light vertical gradient whose brightest
     point is still teal-800, so teal-300 nav keeps its 4.80:1 */
  --topbar-bg: linear-gradient(180deg, var(--teal-800) 0%, #035A64 100%);

  /* --- Motion ------------------------------------------------------------- */
  --ease: cubic-bezier(.4,0,.2,1);
  --t-fast: 120ms; --t-base: 200ms;

  /* --- Focus -------------------------------------------------------------- */
  --focus-ring: 2px solid var(--green-700);
  --focus-offset: 2px;
  /* [+] AA fix: green-700 on teal-800 is only 1.71:1. On dark teal surfaces use
     green-500 (3.18:1) — still green, so it keeps meaning "interactive". */
  --focus-ring-dark: 2px solid var(--green-500);

  /* [+] Layout — the Anchor has no layout tokens; Promio needs them for the public pages */
  --container: 1080px;        /* frontstore */
  --container-wide: 1280px;   /* admin */
  --container-pad: 24px;
  --topbar-h: 56px;
  --sidebar-w: 240px;         /* admin nav (Anchor §03.2 has no sidebar) */
  --tap-min: 44px;            /* minimum touch target */
}

/* ==========================================================================
   Reset + base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }
/* the reset above beats the UA margin:auto that centers a modal <dialog>, pinning it
   to the top-left corner. Restore it so showModal() dialogs stay centered. */
dialog:modal { margin: auto; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink-900);
  background: var(--gray-100);
  /* Anchor §01.2: every figure is tabular. Plus Jakarta Sans defaults to proportional
     figures, so "tnum" has to be enabled for real, not just declared as font-variant-numeric. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
ul, ol { list-style: none; }
a { color: var(--teal-800); text-decoration: none; }
a:hover { text-decoration: underline; }

*:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
/* AA fix: on dark teal surfaces switch to a green-500 ring (3.18:1 vs 1.71:1) */
.topbar *:focus-visible,
.admin-side *:focus-visible,
.sitefoot *:focus-visible { outline: var(--focus-ring-dark); }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ==========================================================================
   Typography scale (Anchor §01.2)
   ========================================================================== */

.t-metric { font-size: var(--fs-metric); line-height: var(--lh-metric); font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); }
.t-h1, h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); }
.t-h2, h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-bold); }
.t-h3, h3 { font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-semibold); }
h4 { font-size: var(--fs-dense); line-height: var(--lh-dense); font-weight: var(--fw-semibold); }
.t-body { font-size: var(--fs-body); line-height: var(--lh-body); }
.t-dense { font-size: var(--fs-dense); line-height: var(--lh-dense); }
.t-small { font-size: var(--fs-small); line-height: var(--lh-small); }
.t-caption {
  font-size: var(--fs-caption); line-height: var(--lh-caption);
  font-weight: var(--fw-medium); letter-spacing: var(--tracking-caption);
  text-transform: uppercase;
}
.t-mono { font-family: var(--font-mono); font-size: var(--fs-mono); line-height: var(--lh-mono); }
.t-mono-sm { font-family: var(--font-mono); font-size: var(--fs-mono-sm); line-height: var(--lh-mono-sm); }

.muted  { color: var(--ink-500); }
.dim    { color: var(--ink-400); }
.num    { font-variant-numeric: tabular-nums; }
.absent { color: var(--state-absent); font-style: italic; }   /* §03.4 */

/* ==========================================================================
   Layout
   ========================================================================== */

.wrap { max-width: var(--container); margin: 0 auto; padding: 0 var(--container-pad); }
.wrap-wide { max-width: var(--container-wide); margin: 0 auto; padding: 0 var(--container-pad); }

.stack   { display: flex; flex-direction: column; gap: var(--sp-6); }
.stack-3 { display: flex; flex-direction: column; gap: var(--sp-3); }
.stack-4 { display: flex; flex-direction: column; gap: var(--sp-4); }
.row     { display: flex; align-items: center; gap: var(--sp-3); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); }
.spacer  { flex: 1; }

.section { padding: var(--sp-8) 0; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-4); }

/* 2-column store page — collapses to one column below 900px (spec §4.2) */
.layout-2col { display: grid; grid-template-columns: 300px 1fr; gap: var(--sp-6); align-items: start; }
.layout-2col > .col-side { position: sticky; top: var(--sp-6); }

@media (max-width: 900px) {
  .layout-2col { grid-template-columns: 1fr; }
  .layout-2col > .col-side { position: static; }
}

/* Card — white, r-lg, soft warm-tinted shadow (an intentional §01.4 DEVIATION) */
.card { background: var(--white); border-radius: var(--r-lg); padding: var(--card-pad);
  box-shadow: var(--card-shadow); }
.card-flush { padding: 0; overflow: hidden; }

.divider { height: 1px; background: var(--gray-50); border: 0; }
.divider-strong { height: 1px; background: var(--gray-200); border: 0; }

/* ==========================================================================
   02.1 Button — Primary / Dark / Secondary / Ghost
   Green-500 appears here only (where things are clickable), on ≤10% of the screen.
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  font-size: var(--fs-body); line-height: 1; font-weight: var(--fw-medium);
  padding: 11px 22px; border: 1px solid transparent; border-radius: var(--r-md);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn-sm { padding: 7px 14px; font-size: var(--fs-small); border-radius: var(--r-sm); }

.btn-primary   { background: var(--green-500); color: var(--ink-900); }
.btn-primary:hover { background: var(--green-600); }
.btn-dark      { background: var(--teal-800); color: var(--white); }
.btn-dark:hover{ background: var(--teal-700); }
.btn-secondary { background: var(--white); border-color: var(--border-control); color: var(--ink-900); }
.btn-secondary:hover { border-color: var(--ink-700); }
.btn-ghost     { background: transparent; color: var(--teal-800); }
.btn-ghost:hover { background: var(--teal-100); }
.btn[disabled], .btn.is-disabled { background: var(--gray-200); color: var(--ink-400); cursor: default; transform: none; }

/* icon-only — still keeps a 44px touch area on touch devices via ::after */
.btn-icon { width: 32px; height: 32px; padding: 0; border-radius: var(--r-sm); position: relative; }
@media (pointer: coarse) {
  .btn-icon::after { content: ""; position: absolute; inset: 50% auto auto 50%;
    width: var(--tap-min); height: var(--tap-min); transform: translate(-50%, -50%); }
}

/* ==========================================================================
   02.3 Badge — mapped 1:1 to state-*. There is NO green badge (§00.1).
   ========================================================================== */

.badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-full); white-space: nowrap;
}
.badge-neutral   { color: var(--state-neutral);   background: var(--state-neutral-bg); }
.badge-attention { color: var(--state-attention); background: var(--state-attention-bg); }
.badge-tracked   { color: var(--state-tracked);   background: var(--state-tracked-bg); }
.badge-linked    { color: var(--state-linked);    background: var(--state-linked-bg); }
.badge-fault     { color: var(--state-fault);     background: var(--state-fault-bg); }

/* 02.4 Chip — metadata about how to read the data */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-full);
  background: var(--teal-100); color: var(--teal-800);
}

/* ==========================================================================
   02.2 Input + form field (Comfortable)
   ========================================================================== */

.input, .select, .textarea {
  width: 100%; background: var(--white); color: var(--ink-900);
  border: 1px solid var(--border-control); border-radius: var(--r-sm);   /* AA 1.4.11 */
  padding: 11px 14px; font-size: var(--fs-body); line-height: var(--lh-body);
  transition: border-color var(--t-fast) var(--ease);
}
.textarea { min-height: 96px; resize: vertical; }
.select { appearance: none; padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-500) 50%),
                    linear-gradient(135deg, var(--ink-500) 50%, transparent 50%);
  background-position: calc(100% - 18px) 20px, calc(100% - 13px) 20px;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.input::placeholder, .textarea::placeholder { color: var(--ink-400); }
.input:focus, .select:focus, .textarea:focus {
  outline: 0; border-color: var(--teal-800);
  box-shadow: 0 0 0 3px rgba(0,154,92,.25);   /* ring green-700 @25% */
}
.input-mono { font-family: var(--font-mono); font-size: var(--fs-mono); }

.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-500); }
.field-label .req { color: var(--state-fault); }
.field-help { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-400); }
.field.is-error .input, .field.is-error .select, .field.is-error .textarea { border-color: var(--state-fault); }
.field.is-error .field-help { color: var(--state-fault); }
/* Not an error — the save is allowed — but the reader has to see it. */
.field-help.is-warn { color: var(--state-attention); }

/* search input — 16px icon 12px from the left, 36px left padding (§02.2) */
.search { position: relative; }
.search .input { padding-left: 36px; }
.search .search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--ink-400); font-size: 16px; line-height: 1;
  pointer-events: none;
}

/* checkbox row */
.check { display: flex; align-items: flex-start; gap: var(--sp-2); }
.check input { width: 18px; height: 18px; margin-top: 3px; accent-color: var(--green-700); }
.check span { font-size: var(--fs-body); }

/* ==========================================================================
   03.3 Copy field — signature pattern
   Mono 13px ink-700 · 24px Ghost copy button ALWAYS visible · ✓ stays 1200ms ·
   aria-live="polite" announces "Copied" · truncates in the MIDDLE (8 leading + 6 trailing), not at the end.
   ========================================================================== */

.copy-field { display: inline-flex; align-items: center; gap: var(--sp-2); max-width: 100%; }
.copy-field > code {
  font-family: var(--font-mono); font-size: var(--fs-mono); line-height: var(--lh-mono);
  color: var(--ink-700); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cf-btn {
  flex: 0 0 auto; width: 24px; height: 24px; display: inline-flex;
  align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--teal-800);
  border-radius: var(--r-sm); cursor: pointer; font-size: 14px; line-height: 1;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.cf-btn:hover { background: var(--teal-100); }
.cf-btn.is-copied { color: var(--green-700); }
/* the copy icon is inline SVG; ✓ only appears after copying. Switched by class, not by
   innerHTML — keeps the handler cheap and avoids rebuilding the DOM. */
.cf-btn .cf-ok { display: none; font-size: 14px; line-height: 1; }
.cf-btn.is-copied .cf-ico { display: none; }
.cf-btn.is-copied .cf-ok { display: inline; }
.cf-ico { display: block; }
.search-icon svg { display: block; }

/* boxed variant — used for the coupon code on the reveal page and the blog coupon box */
.code-box {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  background: var(--gray-50); border: 1px dashed var(--border-control);
  border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4);
}
.code-box .copy-field > code { color: var(--ink-900); letter-spacing: .04em; }
.code-box .cf-btn { width: 32px; height: 32px; }

/* ==========================================================================
   02.9 Banner — full width, r-md, 3px left border
   ========================================================================== */

.banner {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
  font-size: var(--fs-small); line-height: var(--lh-small); border-left: 3px solid;
}
.banner-icon { flex: 0 0 16px; line-height: var(--lh-small); }
.banner-info      { background: var(--state-tracked-bg);   border-color: var(--state-tracked);        color: var(--ink-700); }
.banner-attention { background: var(--state-attention-bg); border-color: var(--state-attention-mark); color: var(--ink-700); }
.banner-fault     { background: var(--state-fault-bg);     border-color: var(--state-fault);          color: var(--ink-700); }

/* ==========================================================================
   02.5 Table — Compact 36px for EVERY admin table
   ========================================================================== */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tbl { width: 100%; border-collapse: collapse; background: var(--white); font-size: var(--fs-dense); }
.tbl th {
  position: sticky; top: 0; z-index: 1; background: var(--white); text-align: left;
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase; color: var(--ink-400);
  padding: var(--row-pad-y) var(--row-pad-x); border-bottom: 1px solid var(--gray-200);
  white-space: nowrap;
}
/* Rows are exactly 36px: badges and copy fields are inline-flex with `vertical-align:
   middle`, so the line box swells to ~21.5px and 8+8 vertical padding would give ~37.5px.
   Dropping the vertical padding and letting the cell's `height` + `vertical-align: middle`
   centre the content locks the row at 36px regardless of font. `--row-pad-y` still applies to <th>. */
.tbl td {
  height: var(--row-h); padding: 0 var(--row-pad-x);
  border-bottom: 1px solid var(--gray-50); color: var(--ink-700);
  line-height: var(--lh-dense); vertical-align: middle;
  white-space: nowrap;                 /* Compact: cells never wrap, the table scrolls horizontally */
}
/* long text cells: truncate at the end + keep the full value in title (not IDs, so end-truncation is fine) */
.tbl .cell-name { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
.tbl td .t-strong { color: var(--ink-900); font-weight: var(--fw-medium); }
.tbl tbody tr:hover { background: var(--gray-50); }
.tbl tbody tr.is-selected { background: var(--gray-50); }
.tbl tbody tr.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--green-500); }
.tbl .col-num { text-align: right; font-variant-numeric: tabular-nums; }
.tbl .col-drag { width: 32px; color: var(--ink-400); cursor: grab; text-align: center; }
/* Everything inside a cell has to fit the 20px content box, otherwise the row grows past 36px.
   The real touch area is widened with ::after on touch devices (see @media pointer: coarse). */
.tbl .cf-btn { width: 20px; height: 20px; font-size: 13px; }

.act-btn {
  position: relative; width: 20px; height: 20px; padding: 0; margin-left: 2px;
  display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
  border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-500);
  font-size: 13px; line-height: 1; cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.act-btn:hover { background: var(--gray-200); color: var(--teal-800); }
.act-btn.danger:hover { background: var(--state-fault-bg); color: var(--state-fault); }
@media (pointer: coarse) {
  .act-btn::after { content: ""; position: absolute; inset: 50% auto auto 50%;
    width: var(--tap-min); height: var(--tap-min); transform: translate(-50%, -50%); }
}
/* inline-flex sitting on the baseline leaves a gap underneath → the row grows past 36px */
.tbl .badge, .tbl .copy-field, .tbl .chip { vertical-align: middle; }
.tbl td > .badge, .tbl td > .copy-field { display: inline-flex; }

/* the actions column sticks to the right edge: a 10-column table is always wider than the
   admin's 992px viewport, and the actions must stay reachable without scrolling all the way */
.tbl .col-actions {
  width: 1%; white-space: nowrap; text-align: right;
  position: sticky; right: 0; background: var(--white);
  border-left: 1px solid var(--gray-200);
}
.tbl th.col-actions { z-index: 2; }
.tbl tbody tr:hover .col-actions,
.tbl tbody tr.is-selected .col-actions { background: var(--gray-50); }

/* 02.13 Pagination */
.pagination {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap; margin-top: var(--sp-3);
  font-size: var(--fs-small); color: var(--ink-500);
}

/* ==========================================================================
   Tabs (segmented) — used for the All / Verified / Codes / Deals tabs
   ========================================================================== */

.tabs { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border: 0; border-radius: var(--r-sm); background: transparent;
  font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-500); cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.tab:hover { background: var(--gray-50); }
.tab[aria-selected="true"] { background: var(--teal-800); color: var(--white); }
.tab .n { font-variant-numeric: tabular-nums; opacity: .75; }

/* ==========================================================================
   Accordion — About store / How to apply / FAQs
   ========================================================================== */

.acc { background: var(--white); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--card-shadow); }
.acc-item + .acc-item { border-top: 1px solid var(--gray-50); }
.acc-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  background: none; border: 0; cursor: pointer; text-align: left;
  padding: var(--card-pad); font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-semibold);
}
.acc-head .caret { color: var(--ink-400); transition: transform var(--t-fast) var(--ease); font-size: 12px; }
.acc-head[aria-expanded="true"] .caret { transform: rotate(180deg); }
.acc-body { padding: 0 var(--card-pad) var(--card-pad); color: var(--ink-700); }
.acc-head[aria-expanded="false"] + .acc-body { display: none; }

/* prose — HTML content entered by the admin (about, how to apply, posts) */
/* The body is arbitrary admin HTML, so it can hold a run with no break opportunity — a bare
   URL, or a table flattened into one paragraph. Left to wrap normally that run widens the
   document and the whole page zooms out on a phone. */
.prose { color: var(--ink-700); font-size: var(--fs-body); line-height: var(--lh-body); overflow-wrap: anywhere; }
.prose > * + * { margin-top: var(--sp-3); }
.prose h2 { color: var(--ink-900); margin-top: var(--sp-6); }
.prose h3 { color: var(--ink-900); margin-top: var(--sp-5); }
.prose ul { list-style: disc; padding-left: 20px; }
.prose ol { list-style: decimal; padding-left: 20px; }
.prose li + li { margin-top: var(--sp-1); }
.prose strong { color: var(--ink-900); font-weight: var(--fw-semibold); }
/* A comparison table is wider than a phone whatever we do, so it carries its own sideways
   scroll rather than setting the width of the page. display:block is what makes the
   overflow apply — a table box ignores it. */
.prose table {
  display: block; overflow-x: auto; width: max-content; max-width: 100%;
  border-collapse: collapse; font-size: var(--fs-dense); line-height: var(--lh-dense);
}
/* The table scrolls, so cells no longer need the page-saving break and can wrap on words. */
.prose th, .prose td {
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--gray-200); text-align: left;
  overflow-wrap: normal;
}
.prose th { background: var(--gray-50); color: var(--ink-900); font-weight: var(--fw-semibold); }

/* ==========================================================================
   03.2 Topbar — 56px, teal-800
   ========================================================================== */

.topbar { background: var(--topbar-bg); }
.topbar .bar { display: flex; align-items: center; gap: var(--sp-8); height: var(--topbar-h); }
.wordmark { flex: 0 0 auto; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--white); font-size: 18px; font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); }
.wordmark:hover { text-decoration: none; }
/* The nav takes whatever width the bar has left and scrolls inside itself. Without this a
   long brand or a fifth link widens the document, and the whole page scrolls sideways. */
.topnav {
  display: flex; gap: var(--sp-6); flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
.topnav::-webkit-scrollbar { display: none; }
.topnav a {
  flex: 0 0 auto; white-space: nowrap;
  color: var(--teal-300); font-size: var(--fs-dense); line-height: var(--topbar-h);
  border-bottom: 2px solid transparent; text-decoration: none;
}
.topnav a:hover { color: var(--white); text-decoration: none; }
.topnav a[aria-current="page"] { color: var(--white); border-bottom-color: var(--green-500); }

/* site.js adds these on the side that still has links out of view. */
.topnav.fade-r { mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.topnav.fade-l { mask-image: linear-gradient(to left, #000 calc(100% - 24px), transparent); }
.topnav.fade-l.fade-r {
  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}

@media (max-width: 640px) {
  .topbar .bar { gap: var(--sp-4); }
  .topnav { gap: var(--sp-5); }
}

/* 02.11 Empty state */
.empty { max-width: 400px; margin: 0 auto; text-align: center; padding: var(--sp-10) 0; }
.empty p { color: var(--ink-500); font-size: var(--fs-small); margin-top: var(--sp-2); }

/* ==========================================================================
   Image placeholders (wireframe) — R2 assets take their place
   ========================================================================== */

.ph {
  background: var(--gray-50); border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-400); font-size: var(--fs-mono-sm); font-family: var(--font-mono);
  text-align: center; overflow: hidden;
}
.logo-tile {
  background: var(--teal-100); color: var(--teal-800); border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); flex: 0 0 auto;
}

/* rating — stars use teal-600/gray-200, NOT state colours (this is not a state) */
.rating { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-small); color: var(--ink-500); }
.rating .stars { color: var(--teal-600); letter-spacing: 1px; }
.rating .stars .off { color: var(--gray-200); }
.rating .score { color: var(--ink-900); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }


/* ==========================================================================
   PROMIO — components shared across pages
   (built from the Anchor components above; no new colours or spacing)
   ========================================================================== */

/* --- the search bar under the topbar ------------------------------------- */
.searchbar { padding: var(--sp-5) 0 var(--sp-2); }
.searchbar .search { max-width: 560px; margin: 0 auto; }

/* --- breadcrumb ---------------------------------------------------------- */
.breadcrumb { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
  padding: var(--sp-4) 0; font-size: var(--fs-small); color: var(--ink-400); }
.breadcrumb a { color: var(--ink-500); }
.breadcrumb [aria-current] { color: var(--ink-700); }

/* --- auto-fitting grid --------------------------------------------------- */
.grid-auto { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.grid-auto-sm { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.grid-auto-lg { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-4 { display: grid; gap: var(--sp-4); grid-template-columns: repeat(4, 1fr); }
@media (max-width: 860px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .grid-4 { grid-template-columns: 1fr; } }

/* --- OFFER CARD (spec §4.2) ---------------------------------------------
   left = large offer_label (teal-800) · middle = badge + name + description ·
   right = Get Code (Primary green) / Get Deal (Dark teal)
   ------------------------------------------------------------------------ */
.offer {
  display: flex; align-items: center; gap: var(--sp-4);
  background: var(--white); border-radius: var(--r-lg); padding: 18px var(--card-pad);
  box-shadow: var(--card-shadow);
  transition: box-shadow var(--t-base) var(--ease);
}
.offer:hover { box-shadow: var(--card-shadow-hover); }
.offer-label { flex: 0 0 96px; width: 96px; text-align: center; }
.offer-label .big {
  display: block; font-size: var(--fs-offer); line-height: var(--lh-offer); font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight); color: var(--teal-800); font-variant-numeric: tabular-nums;
}
.offer-label .big.is-long { font-size: var(--fs-h2); line-height: 26px; }
.offer-label .sub {
  display: block; font-size: var(--fs-caption); line-height: var(--lh-caption);
  font-weight: var(--fw-medium); letter-spacing: var(--tracking-caption);
  text-transform: uppercase; color: var(--ink-400); margin-top: 2px;
}
.offer-mid { flex: 1 1 auto; min-width: 0; }
.offer-mid .badges { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.offer-mid h3 { color: var(--ink-900); }
.offer-mid p { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-500); margin-top: 2px; }
.offer-act { flex: 0 0 auto; display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
.offer-act .terms { font-size: var(--fs-caption); color: var(--ink-400); text-align: center; }

/* Get code peek: the code sits in a dashed box BEHIND the button, which covers all but its
   tail, so the offer reads as "a real code is waiting" before anyone clicks. Hover slides
   the button off it. The code is decorative here (aria-hidden) — the usable hand-over is
   the ?offer= reveal, which is also where the affiliate link lives. */
.get-code { position: relative; display: block; width: 188px; height: 42px; }
.get-code .peek {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end;
  padding-right: 9px; overflow: hidden; white-space: nowrap;
  /* the sliver is a teaser, not a control: no selecting it, and clicks fall to the card */
  user-select: none; pointer-events: none;
  border: 2px dashed var(--green-600); border-radius: var(--r-md);
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: .08em;
  text-transform: uppercase; color: var(--teal-800);
}
/* left/right rather than width, so the button can be slid without resizing its label */
.get-code .btn {
  position: absolute; top: 0; bottom: 0; left: 0; right: 32px; padding: 0;
  transition: left var(--t-fast) var(--ease), right var(--t-fast) var(--ease);
}
.get-code:hover .btn, .get-code:focus-within .btn { left: -10px; right: 46px; }

@media (max-width: 600px) {
  .offer { flex-wrap: wrap; gap: var(--sp-3); }
  .offer-label { flex-basis: 84px; width: 84px; text-align: left; }
  .offer-label .big { font-size: var(--fs-metric); line-height: 30px; }
  .offer-act { flex: 1 1 100%; }
  /* direct child only — .get-code .btn is absolutely placed and must keep left/right */
  .offer-act > .btn { width: 100%; }
  .get-code { width: 100%; }
}

/* --- STORE CARD (home "Popular stores", category listing) ---------------- */
.store-card {
  display: flex; flex-direction: column; gap: var(--sp-2);
  background: var(--white); border-radius: var(--r-lg); padding: var(--card-pad);
  box-shadow: var(--card-shadow);
  transition: box-shadow var(--t-base) var(--ease);
}
.store-card:hover { box-shadow: var(--card-shadow-hover); text-decoration: none; }
.store-card .logo-tile { width: 48px; height: 48px; font-size: var(--fs-h3); }
.store-card h3 { color: var(--ink-900); }
.store-card .count { font-size: var(--fs-small); color: var(--ink-500); font-variant-numeric: tabular-nums; }
.store-card .more { font-size: var(--fs-small); color: var(--teal-800); margin-top: auto; padding-top: var(--sp-2); }

/* --- DEAL CARD (product deal, "Deals of Today") -------------------------- */
.deal-card { display: flex; flex-direction: column; background: var(--white); border-radius: var(--r-lg); padding: var(--sp-4); box-shadow: var(--card-shadow); }
.deal-card .ph { height: 140px; margin-bottom: var(--sp-3); }
.deal-card h4 { color: var(--ink-900); }
.deal-card .price { display: flex; align-items: baseline; gap: var(--sp-2); margin: var(--sp-2) 0 var(--sp-3); }
.deal-card .price .new { font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--teal-800); font-variant-numeric: tabular-nums; }
.deal-card .price .old { font-size: var(--fs-small); color: var(--ink-400); text-decoration: line-through; font-variant-numeric: tabular-nums; }
.deal-card .price .save { font-size: var(--fs-caption); color: var(--ink-500); font-variant-numeric: tabular-nums; }
.deal-card .btn { margin-top: auto; }

/* --- POST CARD (blog teaser) --------------------------------------------- */
.post-card { display: flex; flex-direction: column; background: var(--white); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--card-shadow); }
.post-card .ph { height: 148px; border-radius: 0; }
.post-card .body { padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); flex: 1; }
.post-card h3 { color: var(--ink-900); }
.post-card .meta { font-size: var(--fs-small); color: var(--ink-400); margin-top: auto; }
.post-row { display: flex; gap: var(--sp-3); background: var(--white); border-radius: var(--r-lg); padding: var(--sp-3); box-shadow: var(--card-shadow); }
.post-row .ph { flex: 0 0 72px; width: 72px; height: 72px; }

/* --- footer site ---------------------------------------------------------- */
.sitefoot { background: var(--teal-900); color: var(--teal-300); margin-top: var(--sp-10); padding: var(--sp-8) 0; }
.sitefoot .cols { display: grid; gap: var(--sp-6); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.sitefoot h4 { color: var(--white); font-size: var(--fs-caption); line-height: var(--lh-caption);
  letter-spacing: var(--tracking-caption); text-transform: uppercase; font-weight: var(--fw-medium); margin-bottom: var(--sp-3); }
.sitefoot a { color: var(--teal-300); font-size: var(--fs-small); line-height: 26px; display: block; }
.sitefoot a:hover { color: var(--white); }
.sitefoot .legal { margin-top: var(--sp-6); padding-top: var(--sp-4);
  border-top: 1px solid rgba(157,194,197,.24); font-size: var(--fs-small); color: var(--teal-300); }
.sitefoot .foot-about { margin-top: var(--sp-6); max-width: 46ch; font-size: var(--fs-small);
  line-height: 22px; color: var(--teal-300); }
.sitefoot .social-row { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.sitefoot .social-link { display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 999px; background: rgba(157,194,197,.12); color: var(--teal-300); }
.sitefoot .social-link:hover { background: var(--white); color: var(--teal-900); }

/* A 26px row is a fine mouse target but a poor thumb one. Two columns pay for the taller
   rows, so the footer does not get longer than it already is on a phone. */
@media (max-width: 640px) {
  .sitefoot .cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5); }
  .sitefoot a { display: flex; align-items: center; min-height: 44px; line-height: 20px; }
  .sitefoot .social-row a { min-height: 0; }
}

/* ==========================================================================
   ADMIN shell — sidebar + toolbar (spec §5)
   Anchor §03.2 prescribes a topbar and no sidebar; the admin has 5 nav groups, so it
   uses a teal-800 sidebar with active = 3px green-500 left border (borrowed from §02.5).
   ========================================================================== */

.admin { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
/* the sidebar keeps a flat teal-800 fill: it spans the full page height, so a gradient would stretch */
.admin-side { background: var(--teal-800); min-height: 100vh; }
.admin-side-inner { position: sticky; top: 0; padding: var(--sp-5) 0; max-height: 100vh; overflow-y: auto; }
.admin-side .wordmark { padding: 0 var(--sp-5) var(--sp-5); display: block; }
.admin-nav-group { padding: var(--sp-4) var(--sp-5) 6px; color: var(--teal-300);
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase; }
.admin-nav a {
  display: block; padding: 9px var(--sp-5); color: var(--teal-300); font-size: var(--fs-dense);
  border-left: 3px solid transparent; text-decoration: none;
}
.admin-nav a:hover { color: var(--white); background: rgba(1,51,58,.4); text-decoration: none; }
.admin-nav a[aria-current="page"] { color: var(--white); border-left-color: var(--green-500); background: rgba(1,51,58,.55); }
.admin-nav a .sub { padding-left: var(--sp-3); }

.admin-main { min-width: 0; background: var(--gray-100); padding: var(--sp-6); }
.admin-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-5); }
.toolbar { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.toolbar .search { flex: 1 1 260px; max-width: 320px; }
.toolbar .select { width: auto; min-width: 150px; }

.form-grid { display: grid; gap: var(--sp-5); grid-template-columns: 1fr 1fr; }
.form-grid .span-2 { grid-column: 1 / -1; }
.form-actions { display: flex; gap: var(--sp-3); align-items: center; }

@media (max-width: 900px) {
  .admin { grid-template-columns: 1fr; }
  .admin-side { min-height: 0; }
  .admin-side-inner { position: static; max-height: none; padding-bottom: var(--sp-3); }
  .admin-side .admin-nav { display: flex; flex-wrap: wrap; }
  .admin-nav-group { display: none; }
  .admin-nav a { border-left: 0; border-bottom: 3px solid transparent; }
  .admin-nav a[aria-current="page"] { border-left: 0; border-bottom-color: var(--green-500); }
  .form-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Motion preference
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  * { transition-property: opacity; transform: none !important; }
}

/* ==========================================================================
   PROMIO — component theo trang
   Ported from the inline <style> blocks of docs/wireframe/*.html. The CSP `style-src 'self'`
   forbids both inline <style> and style= attributes, so every page-specific value has to
   become a real class here.
   ========================================================================== */

/* The home hero sits closer to the breadcrumb than a normal section does. */
.section--tight { padding-top: var(--sp-4); }

/* --- home banner carousel (the home page hero) ------------------------------------
   object-fit: CONTAIN, never cover: the image is artwork the site owner laid out, and
   cropping it is how the words they centred get cut in half. The teal-800 behind the
   letterboxing keeps the hero reading as one block.
   aspect-ratio comes from the WIDEST banner and applies to every slide, so switching slides
   never changes the height of the largest element on the page.
   The gap to the breadcrumb is a MARGIN here, not the padding .section--tight gives the
   other pages: box-sizing is border-box site-wide, so padding on the element that carries
   aspect-ratio makes the image area that much shorter than the ratio asks for — 16px of it,
   which letterboxes even a banner cut to the exact frame (measured 86% fill at 390px). The
   nav buttons and dots are positioned against this same box, so keeping the ratio here is
   what keeps them centred on the artwork. */
.banner-carousel { position: relative; aspect-ratio: var(--bc-ratio, 3); margin-top: var(--sp-4); }
/* No `scroll-behavior: smooth` here on purpose: site.js asks for the animation explicitly
   and falls back to an instant jump when the browser will not animate, which a CSS rule
   would silently override back into a no-op.
   The track fills the frame by ABSOLUTE POSITIONING, not `height: 100%`. WebKit does not
   treat a height that comes from `aspect-ratio` as definite for a percentage-height child:
   the chain track→slide→img each asking for 100% resolved against the viewport instead, so
   Safari rendered the hero as tall as the whole screen with the artwork a strip in the middle
   (measured 342×844 at a 390×844 viewport, 14% fill) while Chromium was correct. Out-of-flow
   children also cannot feed their height back into the frame, which is what keeps
   `aspect-ratio` in charge of it. */
.bc-track { position: absolute; inset: 0; display: flex; overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain; scrollbar-width: none; border-radius: var(--r-lg); }
.bc-track::-webkit-scrollbar { display: none; }
.bc-slide { flex: 0 0 100%; position: relative; scroll-snap-align: center; }
.bc-slide > a, .bc-slide > div { position: absolute; inset: 0; display: block; }
.bc-slide img { width: 100%; height: 100%; object-fit: contain; display: block;
  background: var(--teal-800); border-radius: var(--r-lg); }
/* Controls are white-on-dark, never green: green stays reserved for Get Code. */
.bc-nav { position: absolute; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px; border: 0; border-radius: var(--r-full); cursor: pointer;
  background: rgba(2, 63, 71, .55); color: var(--white); font-size: 20px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: background var(--t-fast) var(--ease); }
.bc-nav:hover { background: var(--teal-800); }
.bc-prev { left: var(--sp-3); }
.bc-next { right: var(--sp-3); }
.bc-dots { position: absolute; left: 0; right: 0; bottom: var(--sp-3);
  display: flex; justify-content: center; gap: var(--sp-2); }
.bc-dot { width: 8px; height: 8px; padding: 0; border: 0; border-radius: var(--r-full);
  background: rgba(255, 255, 255, .5); cursor: pointer; }
.bc-dot.is-current { background: var(--white); width: 20px; }

/* --- store tile compact, Popular stores (home.html:20-26) ------------------ */
.store-tile { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  background: var(--white); border-radius: var(--r-lg); padding: var(--sp-4); text-align: center;
  box-shadow: var(--card-shadow); transition: box-shadow var(--t-base) var(--ease); }
.store-tile:hover { box-shadow: var(--card-shadow-hover); text-decoration: none; }
.store-tile .logo-tile { width: 56px; height: 56px; font-size: var(--fs-h3); }
.store-tile .logo-img { width: 56px; height: 56px; border-radius: var(--r-md); object-fit: cover; }
.store-tile .name { font-size: var(--fs-dense); font-weight: var(--fw-semibold); color: var(--ink-900); }
.store-tile .count { font-size: var(--fs-caption); color: var(--ink-500); font-variant-numeric: tabular-nums; }

/* --- home page category list (home.html:28-33) --------------------------- */
.cat-list { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.cat-list a { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  background: var(--white); border-radius: var(--r-sm); padding: 10px var(--sp-3);
  font-size: var(--fs-dense); color: var(--ink-900); }
.cat-list a:hover { background: var(--gray-50); text-decoration: none; }
.cat-list .n { color: var(--ink-400); font-size: var(--fs-small); font-variant-numeric: tabular-nums; }

/* --- store page: identity + summary (store.html:10-21) ------------------- */
.store-id { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
.store-id .logo-tile { width: 64px; height: 64px; font-size: 22px; }
.store-id .logo-img { width: 64px; height: 64px; border-radius: var(--r-md); object-fit: cover; }
.store-id h1 { font-size: var(--fs-store-h1); line-height: var(--lh-store-h1); }
.store-id .rating { margin-top: 4px; }
.summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3);
  margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--gray-50); }
.summary .n { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-bold);
  color: var(--ink-900); font-variant-numeric: tabular-nums; }
.summary .l { font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase; color: var(--ink-400); }
.faq-q { font-weight: var(--fw-semibold); color: var(--ink-900); }
.faq-a { margin-top: var(--sp-1); }
.faq-list > li + li { margin-top: var(--sp-4); }
/* replaces style="margin-top:var(--sp-3)" at store.html:83 */
.store-side-desc { margin-top: var(--sp-3); }
/* sidebar hand-off — full width so it reads as the card's primary action */
.store-cta { display: flex; width: 100%; margin-top: var(--sp-4); }
/* dl Store info — thay cho style="display:grid;grid-template-columns:110px 1fr" (store.html:105) */
.store-meta { display: grid; grid-template-columns: 110px 1fr; row-gap: var(--sp-2); }
.store-meta dt { color: var(--ink-500); }
.card-label { margin-bottom: var(--sp-3); }
.offer-count { margin-top: 4px; }

/* --- reveal (store-reveal.html:19-25) -------------------------------------
   One <dialog>, two shapes. It ships with `open`, and the UA stylesheet makes a
   non-modal open dialog position:absolute — undone below so the no-JS shape is an
   ordinary card in the offer column. site.js re-opens it with showModal(), and :modal
   then supplies the centred overlay + backdrop, matching the reference site.
   -------------------------------------------------------------------------- */
.reveal { border: 1px solid var(--teal-100); position: relative; }
.reveal:not(:modal) { position: static; width: auto; height: auto; margin: 0; }
.reveal:modal {
  max-width: 620px; width: calc(100% - 2 * var(--sp-4)); margin: auto;
  box-shadow: 0 12px 40px rgba(22, 26, 23, .18);
}
.reveal::backdrop { background: rgba(22, 26, 23, .45); }
/* sits over the card padding, so the header text keeps the full column width */
.reveal-close {
  position: absolute; top: 10px; right: 12px; width: 32px; height: 32px;
  border: none; background: transparent; border-radius: var(--r-sm);
  font-size: 22px; line-height: 1; color: var(--ink-500); cursor: pointer;
}
.reveal-close:hover { background: var(--teal-100); color: var(--ink-900); }
.reveal-top { display: flex; gap: var(--sp-4); align-items: flex-start; padding-right: 28px; }
.reveal-top .offer-label { flex: 0 0 96px; }
.reveal-top .reveal-copy { flex: 1; min-width: 0; }
.reveal-top .badges { display: flex; gap: 6px; margin-bottom: 6px; }
.reveal .code-box { margin-top: var(--sp-4); }
.reveal .banner { margin-top: var(--sp-4); }
.reveal-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; margin-top: var(--sp-4); }
.code-box .copy-field > code { font-size: var(--fs-mono); letter-spacing: .12em; text-transform: uppercase; }

/* --- trang category (category.html:9-21) --------------------------------- */
.cat-head { background: var(--white); border-radius: var(--r-lg); padding: var(--sp-6);
  box-shadow: var(--card-shadow); }
.cat-head h1 { margin-bottom: var(--sp-3); }
.cat-stats { display: flex; flex-wrap: wrap; gap: var(--sp-6); margin-top: var(--sp-4);
  padding-top: var(--sp-4); border-top: 1px solid var(--gray-50); }
.cat-stats .n { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; }
.cat-stats .l { font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caption); text-transform: uppercase; color: var(--ink-400); }
.store-card .head { display: flex; gap: var(--sp-3); align-items: center; }
.store-card .logo-img { width: 48px; height: 48px; border-radius: var(--r-md); object-fit: cover; flex: 0 0 auto; }
.rel-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.rel-list a { background: var(--white); border-radius: var(--r-full); padding: 7px 14px;
  font-size: var(--fs-small); color: var(--ink-900); }
.rel-list a:hover { background: var(--gray-50); text-decoration: none; }

/* --- post detail (blog-detail.html:9-32) --------------------------------- */
.article { max-width: 720px; margin: 0 auto; }             /* ~70 characters per line */
.article .cover { height: 320px; border-radius: var(--r-lg); margin-bottom: var(--sp-6);
  width: 100%; object-fit: cover; }
.article h1 { margin-bottom: var(--sp-3); }
.article-head { margin-bottom: var(--sp-6); }
.article-head h1 { margin-top: var(--sp-3); }
.post-meta { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center;
  font-size: var(--fs-small); color: var(--ink-500); }
.article-body { background: var(--white); border-radius: var(--r-lg); padding: var(--sp-8);
  box-shadow: var(--card-shadow); }
.post-row .body { min-width: 0; }
.post-row h4 { margin-top: 6px; }
.post-row .date { margin-top: 4px; }
.post-card .cover-img, .post-row .cover-img { object-fit: cover; }
.post-card .cover-img { width: 100%; height: 148px; }
.post-row .cover-img { flex: 0 0 72px; width: 72px; height: 72px; border-radius: var(--r-md); }
.deal-card .cover-img { width: 100%; height: 140px; border-radius: var(--r-md);
  object-fit: cover; margin-bottom: var(--sp-3); }

/* the in-post coupon box — only rendered when posts.store_id is set */
.coupon-box { border: 1px solid var(--teal-100); border-radius: var(--r-lg);
  padding: var(--sp-5); margin: var(--sp-8) 0; background: var(--white); }
.coupon-box .cb-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.coupon-box .cb-head .logo-tile, .coupon-box .cb-head .logo-img { width: 44px; height: 44px;
  font-size: var(--fs-dense); border-radius: var(--r-md); object-fit: cover; flex: 0 0 auto; }
.coupon-box .cb-head .cb-title { flex: 1; min-width: 0; }
.coupon-box .cb-row { display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-top: 1px solid var(--gray-50); }
.coupon-box .cb-label { flex: 0 0 72px; font-size: var(--fs-h2); line-height: var(--lh-h2);
  font-weight: var(--fw-bold); color: var(--teal-800); font-variant-numeric: tabular-nums; }
.coupon-box .cb-label.is-long { font-size: var(--fs-h3); }
.coupon-box .cb-mid { flex: 1; min-width: 0; }
.coupon-box .cb-mid .n { font-size: var(--fs-dense); font-weight: var(--fw-semibold); color: var(--ink-900); }
.coupon-box .cb-all { display: block; margin-top: var(--sp-3); font-size: var(--fs-small); }
@media (max-width: 600px) {
  .article-body { padding: var(--sp-5); }
  .coupon-box .cb-row { flex-wrap: wrap; }
  .coupon-box .cb-row .btn { width: 100%; }
}

/* --- spacing utilities replacing the style= attributes scattered in the wireframes --- */
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mb-4 { margin-bottom: var(--sp-4); }
.w-auto { width: auto; }

/* --- deal listing page --------------------------------------------------- */
.deal-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }

/* ==========================================================================
   ADMIN — component theo trang (admin-list.html:10-15, admin-form.html:10-35)
   ========================================================================== */

.admin-card { background: var(--white); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--card-shadow); }
.tbl .col-type { width: 96px; }
.tbl .store-cell { color: var(--ink-900); }
.tbl .drag { font-size: 14px; }
.tbl .col-actions { padding-left: var(--sp-4); }
.tbl .thumb { width: 28px; height: 28px; border-radius: var(--r-sm); object-fit: cover; }

.form-wrap { max-width: 880px; }
.form-wrap .card + .card { margin-top: var(--sp-6); }
.card-title { display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin-bottom: var(--sp-5); }

/* rich text block — Tiptap mounts into .rt-body */
/* NO overflow here — an ancestor with overflow becomes the scroll container for a sticky
   child, which would pin .rt-bar to a box that never scrolls. The corners are rounded on
   the first and last child instead. */
.rt { border: 1px solid var(--gray-200); border-radius: var(--r-sm); }
.rt-bar { display: flex; flex-wrap: wrap; gap: var(--sp-1); padding: 6px;
  background: var(--gray-50); border-bottom: 1px solid var(--gray-200);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  position: sticky; top: 0; z-index: 2; }
.rt-bar button { min-width: 30px; height: 30px; padding: 0 8px; border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--ink-700); font-size: var(--fs-small); cursor: pointer; }
.rt-bar button:hover { background: var(--white); }
.rt-bar button.is-active { background: var(--white); color: var(--teal-800); font-weight: var(--fw-semibold); }
.rt-bar .sep { width: 1px; background: var(--gray-200); margin: 4px 4px; }
.rt-body { min-height: 180px; padding: var(--sp-4); background: var(--white); }
.rt-body:focus-visible { outline-offset: -2px; }
.rt-body img { max-width: 100%; border-radius: var(--r-sm); }
/* Tiptap marks the cells a column/row command will act on; without a fill the editor gives
   no sign of what is about to change. */
.rt-body .selectedCell { background: var(--teal-100); }
.rt-foot { display: flex; justify-content: space-between; padding: 6px var(--sp-3);
  border-top: 1px solid var(--gray-50); font-size: var(--fs-caption); color: var(--ink-400);
  border-radius: 0 0 var(--r-sm) var(--r-sm); }

/* Upload feedback lives in the toolbar, not .rt-foot: the foot sits at the end of a body
   that can be thousands of pixels tall, so on a long post it is off screen exactly when
   the editor pastes or drops an image. */
.rt-status { margin-left: auto; align-self: center; flex: 0 0 auto; white-space: nowrap;
  padding: 0 var(--sp-2); font-size: var(--fs-caption); color: var(--ink-500); }
.rt-status:empty { padding: 0; }
.rt-status-fault { color: var(--state-fault); }

/* Must come AFTER the .rt-bar rule above — a media query adds no specificity, so the
   earlier flex-wrap would otherwise win. */
@media (max-width: 900px) {
  .rt-bar { flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none; }
  .rt-bar::-webkit-scrollbar { display: none; }
  .rt-status { position: sticky; right: 0; background: var(--gray-50); }
}

/* bottom-sticky action bar — top border, NO shadow (a shadow would fall over the scrolling content) */
.form-bar { position: sticky; bottom: 0; z-index: 3; margin-top: var(--sp-6);
  background: var(--white); border-top: 1px solid var(--gray-200);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  padding: var(--sp-4) var(--card-pad);
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.type-pill { margin-left: auto; display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-small); color: var(--ink-500); }
/* checkbox help text has to wrap, otherwise the label runs straight into the description */
.check .field-help { display: block; margin-top: 2px; }
/* the sticky bar covers the end of the form unless the form reserves room */
.form-wrap { padding-bottom: var(--sp-2); }

/* admin sign-in screen */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--gray-100); padding: var(--sp-6); }
.login-card { width: 100%; max-width: 380px; }
.login-card h1 { font-size: var(--fs-h2); line-height: var(--lh-h2); margin-bottom: var(--sp-2); }
.login-card .sub { color: var(--ink-500); font-size: var(--fs-small); margin-bottom: var(--sp-5); }
.login-card .btn { width: 100%; margin-top: var(--sp-4); }

/* image upload + FAQ repeater */
.upload-field { display: flex; gap: var(--sp-3); align-items: flex-start; }
.upload-field .preview { width: 72px; height: 72px; border-radius: var(--r-md); object-fit: cover;
  background: var(--gray-50); flex: 0 0 auto; }
.upload-field .ph { width: 72px; height: 72px; flex: 0 0 auto; font-size: var(--fs-caption); }
.faq-item { border: 1px solid var(--gray-200); border-radius: var(--r-sm); padding: var(--sp-3);
  display: flex; flex-direction: column; gap: var(--sp-2); }
.faq-item + .faq-item { margin-top: var(--sp-3); }
.faq-item .faq-item-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }

/* banner screens — the list thumb keeps the real 3:1-ish shape, since a square crop of a
   banner shows almost nothing of it */
.tbl .thumb-wide { width: 64px; height: 28px; border-radius: var(--r-sm); object-fit: cover;
  background: var(--gray-50); }
/* The preview frame is the home page frame: the real aspect ratio, contain (never crop),
   teal behind the letterboxing. The dashed box is the middle 60% — text placed outside it
   is what stops being readable on a phone. */
.banner-preview { position: relative; width: 100%; max-width: 520px; background: var(--teal-800);
  border-radius: var(--r-md); overflow: hidden; }
.banner-preview img { width: 100%; height: 100%; object-fit: contain; display: block; }
.bp-safe { position: absolute; inset: 20%; border: 1px dashed rgba(255,255,255,.55);
  border-radius: var(--r-sm); pointer-events: none; }

/* confirmation modal — the native <dialog> handles the focus trap and Escape */
dialog.confirm { border: 0; border-radius: var(--r-lg); padding: var(--card-pad); max-width: 420px;
  box-shadow: var(--shadow-lg); color: var(--ink-900); background: var(--white); }
dialog.confirm::backdrop { background: rgba(22, 26, 23, .45); }
dialog.confirm h3 { margin-bottom: var(--sp-2); }
dialog.confirm p { font-size: var(--fs-small); color: var(--ink-500); }
dialog.confirm .actions { display: flex; gap: var(--sp-3); justify-content: flex-end; margin-top: var(--sp-5); }

.hint { font-size: var(--fs-small); color: var(--ink-400); margin-top: var(--sp-2); }
.is-dragging { opacity: .6; }
