/* ============================================================
   D'ART — shared design system
   Brand: D'ART Brand Guideline 2026 (official team doc; supersedes the
   prior "D'ART Universe" indigo/gold). Concept: An Elegant, Quiet, Real
   Vietnam — "quiet luxury." SAGE-LED rebalance (2026-07-29): green is the
   dominant surface (bands, buttons, table headers, headings); warm cream is
   the paper; brown + gold are intelligent accents. Contextual page
   backgrounds (warm cream / light sage / caramel) chosen per page to flatter
   the products shown. Type: Lora + Montserrat.
   Old indigo/gold token names are kept (remapped to forest green) so page
   markup is untouched. Ports to Shopify as assets/dart-universe.css.
   ============================================================ */

:root {
  /* Palette — D'ART Brand Guideline 2026 PRIMARY (exact guide hexes, locked) */
  --cocoa: #3F2F25;       /* Basalt brown  — guide exact (Nâu Đất Bazan; accent / ink) */
  --forest: #2F533C;      /* Cocoa Verde   — guide exact (primary green; buttons/bands/headers) */
  --paper-exact: #FFF8EC; /* Morning Dew   — guide exact (near-white cream; true paper) */
  /* Working green shades derived from Cocoa Verde (kept per Tommy) */
  --forest-deep: #22402D; /* darker Cocoa Verde — headings, dark bands, hovers */
  --sage: #6E7A63;        /* Xanh Lá Cacao — secondary blocks / labels / tags */
  --sage-deep: #55604C;
  --sage-pale: #CBD3C0;   /* pale sage — hairline borders, tints on cream */

  /* SECONDARY palette — Brand Guideline 2026 (Daily Gifts / Seasonal only; exact hexes) */
  --almond: #E3883E;      /* Toasted Almond */
  --raspberry: #E26189;   /* Raspberry Sorbet */
  --matcha: #6DB651;      /* Matcha Cream */
  --sky: #53B8E9;         /* Fresh Sky */
  --taro: #965BA5;        /* Taro */

  /* Contextual page backgrounds */
  --paper: #FFF8EC;       /* Morning Dew cream — guide exact; default page bg */
  --cream-2: #E5D9C1;     /* deeper cream — panels / alt sections */
  --surface-sage: #DFE4D3;/* light sage — bg for colourful-product pages (Shop, Gifts) */
  --caramel: #CBB48E;     /* warm tan — premium product-hero blocks (sparing) */

  --porcelain: var(--paper);     /* alias: default page bg */
  --porcelain-2: var(--cream-2); /* alias: panels / alt sections */
  --indigo: var(--forest);       /* alias: primary brand / actions */
  --indigo-ink: var(--forest-deep); /* alias: headings + dark bands */
  --toile: #9AA48C;       /* muted sage-grey — dashed borders, separators */
  --mist: var(--sage-pale);/* alias: light sage — chips, tracks, B2B tags */
  --gold: #C7A233;        /* 18K foil accent — fills */
  --gold-deep: #715710;   /* antique foil — eyebrow/label text (AA ≥4.5 on cream + sage) */
  --crimson: #9C3B29;     /* [verify] flags — warm brick; AA on cream AND cream-2 panels */
  --white: #FCF9F2;       /* warm ivory — card surfaces + text on dark bands */
  --ink: #2C2A24;      /* body text — warm near-black, ~11:1 on cream */
  --ink-soft: #635948; /* secondary text — warm taupe; darkened 2026-08-09 so it
                          still clears AA (4.5:1) on the --cream-2 panels, not just on paper */
  --cream: var(--cream-2);
  --line: #D8CDB6;     /* warm border on cream */

  /* Type — Brand Guideline 2026 */
  --serif: "Lora", Georgia, "Times New Roman", serif;
  --sans: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* Small-label floor (vault Rule 24). Every uppercase eyebrow, tag, pill,
     category kicker and caption uses this one token, so the whole ladder moves
     with a single edit instead of drifting apart across 600 lines. 12px is the
     floor for text a person has to read on screen — do not go below it.
     Deliberately exempt: the .brand wordmark's "· CHOCOLATE ·" (logotype, and
     only a fallback behind the real logo image) and the numeral inside a 26px
     map pin, which is a badge digit rather than reading text. */
  --fs-label: .75rem; /* 12px */

  /* Spacing scale (4/8) */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px; --s10: 128px;

  /* Layout */
  --maxw: 1320px;
  --pad: 24px;
  --radius: 4px;
  --ease: cubic-bezier(.22,.61,.36,1);
}

@media (min-width: 768px)  { :root { --pad: 32px; } }
@media (min-width: 1024px) { :root { --pad: 64px; } }
@media (min-width: 1440px) { :root { --pad: 80px; } }

/* Reset */
* { margin: 0; padding: 0; box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  font-family: var(--sans);
  color: var(--ink);
  background: var(--porcelain);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
/* ⚠️ The `hidden` attribute is only honoured by the UA stylesheet, so ANY author
   rule that sets `display` silently defeats it. `.event-card` was `display:grid`,
   so the Events filters set hidden=true on three cards and all four stayed on
   screen — the control tested "live" by property and was dead to a human.
   (Found 2026-08-10; the 08-09 check asserted the property, not the pixels.)
   This guard is why `!important` is correct here: nothing should ever out-rank
   an explicit instruction to hide an element. */
[hidden] { display: none !important; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }

/* Contextual page backgrounds (sage-led system) — chosen per page/section
   to flatter what's shown. Default body = warm cream (--porcelain). */
body.bg-sage { background: var(--surface-sage); } /* colourful-product pages: Shop, Gifts */
.section--sage { background: var(--surface-sage); }   /* light-sage section on a cream page */
.section--caramel { background: var(--caramel); }     /* premium product-hero band — sparing */
.section--caramel h2, .section--caramel h3 { color: #3A2A12; }
.section--caramel .eyebrow { color: #5C3E1B; }
.section--cream2 { background: var(--cream-2); }      /* subtle warm-cream panel section */

/* Layout helpers */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.container { width: 100%; max-width: var(--maxw); margin: 0 auto; padding-inline: var(--pad); }
/* ─── Vertical rhythm ────────────────────────────────────────────────────────
   ⭐ Rewritten 2026-08-19. Defined ONCE here, as two variables, so the whole
   site's vertical spacing can be tuned in one edit instead of hunting literals
   through 26 pages — the same reason the type scale lives in one place.

   ⚠️ What was wrong: the pages were simply too tall, and the space between
   sections was not consistent. Because a junction is the SUM of two paddings,
   mixing .section (112px) and .section--tight (64px) produced three different
   gaps — 224px, 176px and 128px — with no rule saying which appeared where.
   Measured across all 26 pages, adjacent sections were 80px to 593px apart.

   The fix keeps the two-tier model, since a hero genuinely needs less air than
   a full band, but tightens both and narrows the spread to 108 / 130 / 152px.
   The clamp still collapses the whole scale on small screens. */
:root {
  --sec-y:       clamp(40px, 5.5vw, 76px);   /* full section  → 152px junction */
  --sec-y-tight: clamp(30px, 4vw,   54px);   /* hero / sub    → 108px junction */
}
.section { padding-block: var(--sec-y); }
.section--tight { padding-block: var(--sec-y-tight); }

/* Typography */
h1, h2, h3, h4 { font-family: var(--serif); font-weight: 500; line-height: 1.08; color: var(--indigo-ink); letter-spacing: .5px; }
.h-hero { font-size: clamp(2.75rem, 7vw, 5.5rem); line-height: 1.02; }
.h-1 { font-size: clamp(2rem, 4.5vw, 3.25rem); }
.h-2 { font-size: clamp(1.6rem, 3vw, 2.25rem); }
.lead { font-size: clamp(1.05rem, 1.6vw, 1.3rem); color: var(--ink-soft); max-width: 60ch; }
p { max-width: 68ch; }

.eyebrow {
  font-family: var(--sans); font-weight: 600; font-size: var(--fs-label);
  letter-spacing: .22em; text-transform: uppercase; color: var(--gold-deep);
}
.eyebrow--indigo { color: var(--indigo); }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--sans); font-weight: 600; font-size: .92rem;
  letter-spacing: .04em; padding: 15px 30px; border-radius: var(--radius);
  border: 1.5px solid transparent; transition: all .22s var(--ease);
  min-height: 48px; text-transform: uppercase;
}
.btn--primary { background: var(--indigo); color: var(--white); }
.btn--primary:hover { background: var(--indigo-ink); transform: translateY(-1px); }
.btn--gold { background: var(--gold); color: var(--indigo-ink); }
.btn--gold:hover { background: var(--gold-deep); }
.btn--outline { background: transparent; color: var(--indigo); border-color: var(--indigo); }
.btn--outline:hover { background: var(--indigo); color: var(--white); }
/* The gold rule under a ghost link is drawn with text-decoration, not border-bottom.
   A border sits at the bottom of the BOX, so padding the link out to a 44px tap
   target would drag the rule away from the text; an underline stays welded to the
   baseline however tall the box gets. */
.btn--ghost { display: inline-flex; align-items: center; min-height: 44px; padding: 0; text-transform: none; letter-spacing: 0; border: 0; border-radius: 0; color: var(--indigo); font-weight: 600;
  text-decoration: underline; text-decoration-color: var(--gold-deep); text-decoration-thickness: 1.5px; text-underline-offset: 4px; }
.btn--ghost:hover { color: var(--gold-deep); }
/* On the dark bands the ghost link's default --indigo is Cocoa Verde on Cocoa
   Verde — the same dark-on-dark failure Round 15 found on the Journey outro
   heading. craft.html was patching it with an inline style="color:var(--gold)";
   this makes it a rule so the next dark-band link cannot ship invisible. */
.journey .btn--ghost, .section--deep .btn--ghost { color: var(--gold); }
.journey .btn--ghost:hover, .section--deep .btn--ghost:hover { color: var(--white); }

:focus-visible { outline: 3px solid var(--gold-deep); outline-offset: 2px; }

/* Placeholder image box — labeled + dimensioned so the team knows what to make */
.imgph {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center; background:
    repeating-linear-gradient(45deg, var(--porcelain-2) 0 12px, #E7DFCF 12px 24px);
  border: 1.5px dashed var(--toile); color: var(--indigo); border-radius: var(--radius);
  min-height: 180px; padding: 20px; overflow: hidden;
}
.imgph b { font-family: var(--sans); font-weight: 600; font-size: .82rem; letter-spacing: .04em; }
.imgph span { font-size: var(--fs-label); color: var(--ink-soft); }
.imgph svg { width: 26px; height: 26px; opacity: .5; }

/* ---------- Announcement bar ---------- */
.announce {
  background: var(--indigo-ink); color: #EDE6D8; text-align: center;
  font-size: .8rem; letter-spacing: .03em; padding: 9px 16px;
}
.announce b { color: var(--gold); font-weight: 600; }

/* ---------- Header ---------- */
/* ⚠️ Written as rgba() because it needs alpha, which is why it survived every
   hex-based colour sweep: it was still rgba(237,227,207,.92) — #EDE3CF, the
   Round 11 paper colour retired 2026-07-29 — until Round 20. Any future colour
   audit must grep rgb( and rgba( as well as the hex. */
.site-header { position: sticky; top: 0; z-index: 100; background: rgba(255,248,236,.92); backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); }
.nav { display: flex; align-items: center; justify-content: space-between; gap: 24px; min-height: 72px; }
/* Wordmark — matches Brand Guideline 2026 lockup: bold Lora "D'ART" in Basalt
   brown over spaced "• CHOCOLATE •" */
.brand { font-family: var(--serif); font-weight: 600; font-size: 1.6rem; letter-spacing: .12em; color: var(--cocoa); line-height: 1; text-align: center; }
.brand small { display: block; font-family: var(--sans); font-weight: 600; font-size: .46rem; letter-spacing: .42em; color: var(--cocoa); margin-top: 3px; padding-left: .42em; opacity: .92; }
.brand small::before { content: "• "; }
.brand small::after { content: " •"; }
.nav-links { display: none; align-items: center; gap: 16px; }
/* ⚠️ nowrap is required, not cosmetic. Adding the dropdown carets pushed the
   nav past its line and EVERY multi-word label wrapped to two lines —
   'New Collection', 'World of D'Art', 'B2B Solution'. uxcheck did not catch
   it because wrapping PREVENTS overflow, so the page measured clean while
   looking broken. Found by looking at the render. (2026-08-10) */
.nav-links a { white-space: nowrap; display: inline-flex; align-items: center; min-height: 44px; font-size: .9rem; font-weight: 500; letter-spacing: .04em; color: var(--ink); padding: 6px 0; position: relative; }
/* bottom: 4px, not 0 — the link is now a 44px tap target, so a rule pinned to the
   box bottom would float well clear of the word it belongs to. */
.nav-links a::after { content: ""; position: absolute; left: 0; bottom: 4px; width: 0; height: 1.5px; background: var(--gold-deep); transition: width .22s var(--ease); }
.nav-links a:hover::after { width: 100%; }
.nav-utils { display: flex; align-items: center; gap: 18px; }
.nav-utils a { display: inline-flex; align-items: center; gap: 6px; font-size: .82rem; font-weight: 500; }
.lang { color: var(--ink-soft); }
.lang b { color: var(--indigo); }
.icon-btn { background: none; border: 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px; color: var(--ink); font-size: .82rem; min-width: 44px; min-height: 44px; }
.icon-btn svg { width: 20px; height: 20px; }
.burger { display: inline-flex; align-items: center; justify-content: center; background: none; border: 0; min-width: 44px; min-height: 44px; }
.burger svg { width: 26px; height: 26px; color: var(--indigo); }

/* Mobile drawer */
.drawer { position: fixed; inset: 0 0 0 auto; width: min(84vw, 360px); background: var(--white); z-index: 200; transform: translateX(100%); transition: transform .3s var(--ease); box-shadow: -8px 0 40px rgba(46,59,46,.15); padding: 24px; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; }
.drawer.open { transform: translateX(0); }
.drawer a { padding: 14px 8px; font-size: 1.05rem; border-bottom: 1px solid var(--line); color: var(--indigo-ink); }
.drawer-close { align-self: flex-end; background: none; border: 0; font-size: 1.6rem; color: var(--indigo); margin-bottom: 8px; min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.scrim { position: fixed; inset: 0; background: rgba(46,59,46,.5); z-index: 150; opacity: 0; visibility: hidden; transition: opacity .3s; }
.scrim.open { opacity: 1; visibility: visible; }

/* ---------- Dropdown navigation (added 2026-08-10) ----------
   The nav exposed 7 pages; the site has 20 destinations. Interior pages —
   Our Craft, Sustainability, the four B2B sub-pages, Visit Us, FAQ — were
   reachable only from the footer or from within another page.

   Hover AND :focus-within, so a keyboard user tabbing to the parent link opens
   the same menu a mouse user sees. No separate toggle button: each parent is a
   real page, and adding a 44px caret button per item would have cost ~200px of
   header width. Touch is handled in dart-nav.js (first tap opens, second
   follows), because :hover is unreliable on a touch screen. */
.nav-item { position: relative; display: flex; align-items: center; }
.nav-caret { width: 13px; height: 13px; margin-left: 4px; flex: none; transition: transform .2s var(--ease); }
.nav-item:hover .nav-caret,
.nav-item:focus-within .nav-caret,
.nav-item.is-open .nav-caret { transform: rotate(180deg); }

.nav-menu {
  position: absolute; top: 100%; left: -14px; z-index: 200;
  min-width: 232px; padding: 8px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 4px 14px 4px 14px;
  box-shadow: 0 18px 40px rgba(46,59,46,.16);
  display: grid; gap: 2px;
  /* visibility (not display:none) so the panel can transition, and so the
     links stay out of the tab order while closed. */
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .18s var(--ease), transform .18s var(--ease), visibility .18s;
}
.nav-item:hover .nav-menu,
.nav-item:focus-within .nav-menu,
.nav-item.is-open .nav-menu { opacity: 1; visibility: visible; transform: translateY(0); }
/* ⚠️ Escape needs this, and it must stay AFTER the three rules above. The panel
   is held open by :focus-within, so while the parent link still has focus no
   amount of class removal will close it. `.is-closed` is set by dart-nav.js on
   Escape and cleared as soon as the pointer or focus leaves the item.
   Equal specificity to :focus-within (0,3,0) — source order is what wins. */
.nav-item.is-closed .nav-menu { opacity: 0; visibility: hidden; transform: translateY(-6px); }
/* Bridges the gap between the parent link and the panel, so the menu does not
   snap shut as the pointer travels down into it. */
.nav-item::after { content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 10px; }

.nav-menu a {
  display: block; min-height: 44px; padding: 11px 14px;
  font-size: .9rem; font-weight: 500; letter-spacing: .02em;
  color: var(--ink); border-radius: 3px; white-space: nowrap;
}
.nav-menu a:hover, .nav-menu a:focus-visible { background: var(--porcelain-2); color: var(--indigo-ink); }
/* The parent's animated gold underline is a top-level affordance; inside the
   panel the background swap does that job and the rule just adds noise. */
.nav-menu a::after { display: none; }

/* ---------- Drawer groups (added 2026-08-10) ----------
   Below 1024px the drawer is the ONLY navigation, so it carries every
   destination, grouped under non-clickable headings. */
/* No border on the group itself: `.drawer a` already draws one under every
   link, so a group border stacked a second rule right beneath the last link of
   each group. Spacing separates the groups instead. */
.drawer-group { display: grid; gap: 2px; padding: 6px 0 14px; }
.drawer-group a:last-child { border-bottom: 0; }
.drawer-heading {
  font-family: var(--sans); font-size: var(--fs-label); font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--gold-deep);
  padding: 4px 0 6px;
}
.drawer-group a { padding-left: 12px; font-size: .95rem; }
.drawer-top { font-weight: 600; }

/* ⚠️ Raised from 1024px to 1100px on 2026-08-10. Seven items plus dropdown
   carets need 931px beside the logo and the utility icons; at 1024 the header
   has 896px, and it overflowed by 19px. Below 1100 the drawer takes over — and
   the drawer now carries every destination grouped, so nothing is lost.
   Measured, not guessed: 1024 TOO WIDE, 1100 fits with 41px to spare. */
@media (min-width: 1100px) {
  .nav-links { display: flex; }
  .burger { display: none; }
}

/* Small phones: keep the header on one line (no horizontal overflow) */
@media (max-width: 480px) {
  .nav { gap: 10px; }
  .nav-utils { gap: 10px; }
  .lang { display: none; }              /* VI toggle not live yet; restore at wider widths */
  .brand { font-size: 1.3rem; letter-spacing: .1em; }
}

/* ---------- Hero — centered copy band + big 16:9 animation stage ---------- */
.hero { background: var(--paper); padding-block: clamp(28px, 4vw, 52px) clamp(40px, 6vw, 72px); }
/* Copy sits BELOW the stage again (Tommy, 2026-08-12 — video back to the top),
   so this top margin is once more the gap between the video and the copy. */
.hero-copy {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 18px; max-width: 780px; margin: clamp(28px, 4vw, 44px) auto 0;
}
.hero-copy .lead { max-width: 58ch; }
.hero-cta { display: flex; align-items: center; justify-content: center; gap: 24px; flex-wrap: wrap; margin-top: 4px; }

/* 16:9 stage for the hero video/animation — within page margins, leaf-radius corners */
.hero-stage {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  /* keep the whole 16:9 frame within the viewport: cap width so height never
     exceeds the space under the announce bar + header (~180px), shrinking on
     short/wide screens instead of running off the bottom */
  max-width: calc((100vh - 180px) * 16 / 9);
  max-width: calc((100svh - 180px) * 16 / 9);
  margin-inline: auto;
  border-radius: 44px 10px 44px 10px; overflow: hidden; background: var(--forest);
}
.hero-stage .imgph { position: absolute; inset: 0; height: 100%; min-height: 0; border: 0; border-radius: 0; background: var(--forest); }
.hero-stage .imgph b, .hero-stage .imgph span { color: var(--white); }
.hero-stage .imgph svg { color: var(--white); opacity: .7; }
/* the animation/video/embed drops straight in and fills the stage */
.hero-stage video, .hero-stage img, .hero-stage iframe { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; }

/* ---------- Brand logo (image with text-wordmark fallback) ---------- */
.brand-img { height: 64px; width: auto; display: block; }
.brand-img ~ .brand-txt { display: none; }   /* hide text when the logo image loads */
@media (max-width: 480px) { .brand-img { height: 50px; } }
/* header wordmark (text fallback) enlarged to match the bigger logo */
.site-header .brand-txt { font-size: 2.05rem; }
.site-header .brand-txt small { font-size: .58rem; }

/* ---------- Customer segments ---------- */
.seg-head { display: flex; flex-direction: column; gap: 10px; margin-bottom: 40px; }
.seg-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
.seg {
  border: 1px solid var(--line); border-radius: 20px 6px 20px 6px; padding: 26px 24px;
  background: var(--white); display: flex; flex-direction: column; gap: 8px;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease), border-color .22s;
}
.seg:hover { transform: translateY(-3px); box-shadow: 0 14px 30px rgba(46,59,46,.09); border-color: var(--mist); }
.seg h3 { font-size: 1.5rem; }
.seg p { font-size: .9rem; color: var(--ink-soft); }
.seg .seg-cta { margin-top: auto; font-size: .8rem; font-weight: 600; letter-spacing: .04em; color: var(--indigo); text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
.seg .seg-cta::after { content: "→"; transition: transform .2s var(--ease); }
.seg:hover .seg-cta::after { transform: translateX(4px); }
@media (min-width: 640px)  { .seg-grid { grid-template-columns: repeat(2, 1fr); } }
/* 5-up only from 1280. At 1024 five tracks are ~179px, which is narrower than the
   min-content of a one-word heading like "Traceability" — a grid item defaults to
   min-width:auto, so the item refuses to shrink and pushes the whole page into
   horizontal scroll. The min-width:0 below is the belt to that braces. */
@media (min-width: 1024px) { .seg-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1280px) { .seg-grid { grid-template-columns: repeat(5, 1fr); } }
/* The World of D'art hub holds 7 cards. On the 5-up ladder that lands as 5 + 2,
   which reads as an unfinished row; 4-up gives a balanced 4 + 3. */
/* 4 columns held 7 cards as 4+3, one gap. The Press card came out 2026-08-10
   (no coverage or awards yet), leaving 6 — which is two clean rows of 3.
   Restore 4 columns if a 7th hub card ever returns. */
@media (min-width: 1024px) { .seg-grid--hub { grid-template-columns: repeat(3, 1fr); } }
.seg-grid > * { min-width: 0; }

/* ---- Photo-headed seg card (Round 21, Tommy: "add visuals, some pages are
   really boring"). The image bleeds to the card edge; text keeps its padding.
   Ratio is 3/2 — every candidate frame is a photograph of people or product,
   and 16/9 crops heads on the card widths these grids produce. */
.seg--photo { padding: 0; overflow: hidden; }
.seg--photo img { width: 100%; height: auto; aspect-ratio: 3/2; object-fit: cover; display: block; }
.seg--photo .seg-body { padding: 20px 24px 24px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
/* Sustainability pillars: exactly three, so hold 3-up from tablet and never
   enter the 5-up ladder (three cards in five tracks strands two empties). */
.pillar-grid { grid-template-columns: 1fr; }
@media (min-width: 760px) { .pillar-grid { grid-template-columns: repeat(3, 1fr) !important; } }

/* ---------- Category grid ---------- */
.cat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
/* Organic "leaf" radius echoes the hero arc (big rounds on the TL/BR diagonal) */
.cat { position: relative; border-radius: 44px 10px 44px 10px; overflow: hidden; aspect-ratio: 3/4; display: block; transition: transform .22s var(--ease), box-shadow .22s var(--ease); }
.cat:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(46,59,46,.13); }
.cat .imgph { position: absolute; inset: 0; height: 100%; min-height: 0; border-radius: 0; }
.cat-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: var(--paper); padding: 6%; }
/* ⚠️ .cat-img is `contain` + padding because it was built for the Fairy
   ARTWORK — a transparent PNG that must sit inside the tile, not bleed to its
   edges. A photograph given the same treatment floats letterboxed in a cream
   box. Photographs get their own modifier and fill the tile. (2026-08-10) */
.cat-img--photo { object-fit: cover; padding: 0; background: var(--mist); }
/* The scrim has to carry white text over an image nobody has chosen yet — staff
   will be uploading these, and a pale product shot would wash the old
   transparent→.72 gradient out completely (visible today on the Gifts
   placeholders). Deeper, starting higher, plus a shadow as the safety net. */
.cat-label { position: absolute; left: 0; right: 0; bottom: 0; padding: 20px; background: linear-gradient(to top, rgba(34,64,45,.94) 0%, rgba(34,64,45,.78) 50%, transparent 100%); color: var(--white); text-shadow: 0 1px 3px rgba(0,0,0,.45); font-family: var(--serif); font-size: 1.45rem; z-index: 2; transition: padding .22s var(--ease); }
.cat:hover .cat-label { padding-bottom: 28px; }
@media (min-width: 900px) { .cat-grid { grid-template-columns: repeat(4, 1fr); } }

/* Fairy emblem — Signature series only (Brand Guideline 2026: "The Fairy
   Vietnam", premium tier). Interim line-art mark; official Cô Tiên D'Art
   illustration replaces the card image (see placeholder brief). */
.cat--signature .fairy-badge {
  position: absolute; top: 12px; left: 12px; z-index: 3;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 13px 6px 8px; border-radius: 999px;
  background: rgba(46,59,46,.82); border: 1px solid var(--gold);
  backdrop-filter: blur(4px);
}
.cat--signature .fairy-badge svg { width: 22px; height: 22px; color: var(--gold); flex: none; }
.cat--signature .fairy-badge span {
  font-family: var(--sans); font-weight: 600; font-size: var(--fs-label);
  letter-spacing: .13em; text-transform: uppercase; color: var(--gold);
}

/* ---------- Journey teaser ---------- */
.journey { background: var(--indigo-ink); color: var(--porcelain); }
.journey-grid { display: grid; gap: 40px; align-items: center; }
/* The map art is 1024×1536 portrait — left uncapped it makes the band roughly
   twice the height of the text beside it, leaving a large dead area bottom-right.
   Capping the height keeps the two columns closer to balanced. */
/* ⚠️ max-height was 620px. The text column beside it is only 322–401px on every
   one of the EIGHT pages that use this band, and align-items:center splits the
   difference — so each page carried 109–149px of dead green above the eyebrow
   and the same again below the CTA, plus 112px of section padding on top of
   that. Round 19 saw it on Business Solutions as "~250px of dead green"; it was
   never a Business Solutions bug, it was this one number. 470px puts the map
   within ~70px of the tallest text column, which centring can absorb without
   reading as a hole. Measured across all eight pages, not eyeballed on one. */
.journey-map { max-height: 470px; width: auto; max-width: 100%; justify-self: center; border: 1px solid rgba(234,227,216,.25); border-radius: var(--radius); background: var(--porcelain); }
.journey h2 { color: var(--white); }
.journey .lead { color: #DCE1D2; }
.journey .eyebrow { color: var(--gold); }
@media (min-width: 900px) { .journey-grid { grid-template-columns: 1fr 1.1fr; } }

/* ---------- Featured products ---------- */
.prod-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px 16px; }
.prod { display: flex; flex-direction: column; gap: 12px; }
.prod .imgph, .prod > img { aspect-ratio: 1/1; min-height: 0; border-radius: 30px 8px 30px 8px; transition: transform .22s var(--ease), box-shadow .22s var(--ease); }
.prod:hover .imgph, .prod:hover > img { transform: translateY(-3px); box-shadow: 0 14px 30px rgba(46,59,46,.10); }
.prod h3 { font-family: var(--sans); font-weight: 600; font-size: .98rem; color: var(--ink); letter-spacing: 0; }
.prod .price { font-family: var(--serif); font-size: 1.2rem; color: var(--indigo); }
.prod .price small { font-family: var(--sans); font-size: .78rem; color: var(--ink-soft); }
.flag { font-size: var(--fs-label); color: var(--crimson); font-weight: 600; letter-spacing: .03em; }
@media (min-width: 900px) { .prod-grid { grid-template-columns: repeat(4, 1fr); } }
/* Gifts shows its whole range in one grid (2026-08-12). Three columns, not the
   usual four: five products across four tracks strands a single card alone on
   row two, and gift photography wants the larger frame. */
@media (min-width: 900px) { .prod-grid--gifts { grid-template-columns: repeat(3, 1fr); } }

/* ---------- Fill-in slot — values staff supply (added 2026-08-10) ----------
   `.flag` and `.slot` are siblings and mean different things. `.flag` says
   "this number is on the page but nobody has confirmed it". `.slot` says
   "there is no number here yet and one is owed". Every price and every hard
   spec on this site is now the second kind: the figure is not in the markup
   at all, so there is nothing unverified to accidentally publish (Rule 15).

   ⚠️ Named `.slot`, NOT `.fill` — `.progress-bar .fill` is the cart's free-delivery
   bar and a generic `.fill` rule silently forced a min-width onto it (2026-08-10).

   ⚠️ NEVER type a number into a slot to "see how it looks". The moment a
   figure sits inside one it is a published claim again, and it will be missed
   at review precisely because the slot around it looks deliberate.

   Where the value actually comes from at the Shopify port:
     · product prices   → Shopify product records (never hand-typed into HTML)
     · event prices/times, delivery threshold, gift wrap, B2B specs
                        → theme section settings, editable in Shopify admin

   The slot is deliberately visible. An empty gap reads as a rendering fault;
   a dotted rule reads as a form field, which is what it is. */
.slot {
  display: inline-block;
  min-width: 5.5ch;
  border-bottom: 2px dotted var(--sage);
  /* Sit the rule below the text baseline so it never collides with descenders
     in the unit that follows it. */
  transform: translateY(-.12em);
  /* A zero-height inline-block collapses and the rule disappears; the
     zero-width space holds the line box open without adding visible text. */
  content: "";
}
.slot::before { content: "\200B"; }        /* zero-width space — see above */
.slot--wide { min-width: 8ch; }            /* four-figure totals, hamper prices */
.slot--narrow { min-width: 3.5ch; }        /* inline inside a sentence */
/* The unit stays REAL TEXT, never a CSS ::after — generated content is skipped
   by text extraction and read inconsistently by screen readers, and the
   currency is the one part of this that must survive both. */
.slot-unit { font-family: var(--sans); font-size: .82em; color: var(--ink-soft); letter-spacing: .04em; }
/* On dark green bands the sage rule falls to ~1.9:1 and vanishes. */
.band--deep .slot, .section--deep .slot, .cta-band .slot, .announce .slot { border-bottom-color: var(--sage-pale); }
.band--deep .slot-unit, .section--deep .slot-unit, .cta-band .slot-unit { color: var(--paper-soft, #E8E2D4); }
/* The announcement bar is indigo-ink at .8rem — the unit has to hold its own
   contrast there rather than inherit the cream-page taupe. */
.announce .slot-unit { color: #EDE6D8; }

/* ---------- Hero video loop (added 2026-08-10) ----------
   Fills the 16:9 stage that previously held a placeholder box. object-fit
   covers so the frame is always full regardless of stage aspect. */
.hero-stage .hero-video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: inherit;
}
/* The still the hero settles on once the clip has played through (2026-08-11).
   Sits above the video and cross-fades in, so the video's own last frame is
   never visible as a freeze — it reads as an ending, not a stall.
   Height comes from the .hero-stage rule (100%), NOT from aspect-ratio, so the
   Round 18 height:auto trap does not apply here. */
.hero-stage .hero-endframe {
  opacity: 0; pointer-events: none;
  transition: opacity .9s var(--ease);
}
.hero-stage.is-ended .hero-endframe { opacity: 1; }

/* Someone who has asked their OS to reduce motion should get the poster frame,
   not an autoplaying loop. The still is a real photograph and says the same
   thing, so nothing is lost. */
@media (prefers-reduced-motion: reduce) {
  .hero-stage .hero-video { display: none; }
  .hero-stage { background-image: url("img/hero-poster.jpg"); background-size: cover; background-position: center; }
}

/* ---------- Seed to bar (added 2026-08-10) ----------
   The vertical-integration story on Home. Six steps that must read as a
   SEQUENCE, not a gallery — hence an <ol> and the numbered captions.
   Scrolls horizontally on phones rather than stacking into six tall cards
   that nobody thumbs past; becomes a grid from 720px. */
.seed { background: var(--porcelain-2); }
.seed-head { display: grid; gap: 18px; align-items: end; margin-bottom: clamp(28px, 4vw, 44px); }
@media (min-width: 900px) { .seed-head { grid-template-columns: 1.05fr .95fr; gap: 48px; } }

.seed-track {
  list-style: none; display: grid; gap: 18px;
  grid-auto-flow: column; grid-auto-columns: 78%;
  overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: 10px; margin-inline: calc(var(--pad) * -1); padding-inline: var(--pad);
  scrollbar-width: thin;
}
.seed-step { scroll-snap-align: start; }
@media (min-width: 720px) {
  /* Enough room for a real grid — drop the horizontal scroller entirely so no
     content sits off-screen on a device that has the width for it. */
  .seed-track { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr);
                overflow: visible; margin-inline: 0; padding-inline: 0; }
}
/* ⚠️ Deliberately STAYS at 3 columns on desktop — 2 rows of 3, not 1 row of 6.
   Six tracks at 1440 gives each photograph 182px, which sliced faces in half
   and cut the apron logo. These are portraits of people; a filmstrip crop
   destroys the exact thing they were added for. (Measured 2026-08-10.) */

.seed-step figure { margin: 0; }
.seed-step img {
  width: 100%;
  /* ⚠️ height:auto is REQUIRED, not tidiness. The width/height HTML attributes
     (there for CLS) are presentational hints that map to the CSS `height`
     property, and `aspect-ratio` only computes a height when height is `auto`.
     Without this every image rendered at its full natural height — 1070px
     tall in a 182px column. Found by measuring, not by looking. */
  height: auto;
  aspect-ratio: 4/5; object-fit: cover;
  border-radius: 24px 6px 24px 6px; background: var(--mist);
}
.seed-step figcaption { margin-top: 12px; font-size: .88rem; color: var(--ink-soft); line-height: 1.5; }
.seed-step figcaption b {
  display: block; font-family: var(--sans); font-size: var(--fs-label);
  font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--indigo); margin-bottom: 5px;
}
/* Marks a caption fact still waiting on Tommy. Same warm brick as .flag so the
   site has ONE colour meaning "unconfirmed", not two. */
.name-slot { color: var(--crimson); font-weight: 600; }

.seed-proof { display: grid; gap: clamp(24px, 4vw, 48px); align-items: center; margin-top: clamp(36px, 5vw, 64px); }
@media (min-width: 900px) { .seed-proof { grid-template-columns: 1.1fr .9fr; } }
.seed-seq { margin: 0; }
.seed-seq img { width: 100%; height: auto; border-radius: var(--radius); background: var(--white); }
.seed-seq figcaption { margin-top: 12px; font-size: .86rem; color: var(--ink-soft); font-style: italic; }
.seed-proof h3 { margin-bottom: 14px; }   /* heading sat welded to its first line */
.seed-proof p + p { margin-top: 12px; }
.seed-proof p { color: var(--ink-soft); }

/* ---------- Quantity stepper (added 2026-08-10) ----------
   Sits alongside "Choose your bundle", which picks WHICH pack; this picks HOW
   MANY of it. Both are needed and they are not the same control.
   44px targets per WCAG 2.5.8 — uxcheck.mjs fails the build below that. */
.qty-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.qty { display: inline-flex; align-items: stretch; border: 1.5px solid var(--indigo); border-radius: var(--radius); overflow: hidden; }
.qty button {
  width: 44px; min-height: 48px; border: 0; background: transparent;
  color: var(--indigo); font-size: 1.15rem; line-height: 1;
  transition: background .18s var(--ease);
}
.qty button:hover:not(:disabled) { background: var(--sage-pale); }
.qty button:disabled { opacity: .35; cursor: not-allowed; }
.qty input {
  width: 54px; min-height: 48px; border: 0; border-inline: 1.5px solid var(--indigo);
  text-align: center; font-family: var(--sans); font-weight: 600; font-size: 1rem;
  color: var(--ink); background: var(--white);
  -moz-appearance: textfield;
}
.qty input::-webkit-outer-spin-button, .qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty-label { display: block; font-size: var(--fs-label); font-weight: 600; letter-spacing: .04em; color: var(--ink-soft); margin-bottom: 6px; }

/* ---------- Reviews (added 2026-08-10) ----------
   Ships with an HONEST empty state. There are no reviews yet, so there is no
   rating, no star row and no count — inventing any of those would be exactly
   the fabricated social proof Rule 15 exists to prevent. A real summary block
   replaces this when a review app is wired at the Shopify port. */
.reviews-empty {
  border: 1px dashed var(--toile); border-radius: var(--radius);
  padding: clamp(24px, 4vw, 40px); text-align: center;
}
.reviews-empty h3 { font-size: 1.25rem; margin-bottom: 8px; }
.reviews-empty p { color: var(--ink-soft); max-width: 46ch; margin: 0 auto 18px; font-size: .95rem; }

/* ---------- Product origin story (added 2026-08-10) ----------
   The PDP was one line of copy then four accordions. This is the structure for
   the story; every FACT inside it is a slot, because nobody here has stood on
   the farm. Staff fill the slots, the shape is already right. */
.origin { display: grid; gap: clamp(24px, 4vw, 48px); align-items: start; }
@media (min-width: 900px) { .origin { grid-template-columns: 1fr 1fr; } }
.origin-facts { margin-top: 20px; border-top: 1px solid var(--line); }
.origin-facts > div { display: grid; grid-template-columns: minmax(96px, 30%) 1fr; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.origin-facts dt { font-size: var(--fs-label); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--gold-deep); align-self: center; }
.origin-facts dd { margin: 0; color: var(--ink); align-self: center; }

/* ---------- Sample-data notice (added 2026-08-10) ----------
   Cart, Checkout and Account keep real figures because they demonstrate a
   CALCULATION — blank them and there is no arithmetic left to review. That is
   the one exception to the blanked-prices rule, so it has to be stated on the
   page rather than understood from context: a reader who lands on Cart and sees
   "Gift wrap +50,000 VND" would otherwise take it for a confirmed price.
   Deliberately not `.callout` — that component means "warning" on this site. */
.sample-note {
  display: flex; gap: 10px; align-items: flex-start;
  border: 1px dashed var(--toile); border-radius: var(--radius);
  padding: 12px 16px; margin-bottom: 20px;
  font-size: .84rem; color: var(--ink-soft); background: transparent;
}
.sample-note b { color: var(--crimson); font-weight: 600; }

/* ---------- Certifications / partners ---------- */
/* ⚠️ `.certs` is a certifications band on Home but a CREAM PANEL UTILITY on ten
   other pages — the Events booking detail, biz-oem #specs, Account, Press, Our
   Story, Sustainability and the four B2B pages all borrow it purely for this
   background. Changing this one line therefore repaints eleven pages. Round 20
   flipped it to --paper to break the touching cream bands on Home and silently
   flattened the Events booking panel into the listing above it. Reverted, and
   fixed where the problem actually is: Home's band alone (.certs--paper). */
.certs { background: var(--porcelain-2); }
/* Home only: .seed directly above is porcelain-2 too, and two touching bands of
   the same cream read as one band with 166px of dead air in the middle rather
   than as a section break. Nothing else on the site has that adjacency —
   verified by flipping the shared rule and running pagecheck.mjs, which named
   Home as the only TOUCH-BAND page. */
.certs--paper { background: var(--paper); }

/* ⚠️ The FOOTER is porcelain-2 as well, so any porcelain-2 band that ends a page
   runs straight into it behind a 1px hairline and the two read as one 900px
   block of cream. Four pages did this — Account, Events, Our Story and
   Sustainability — and it went unseen for twenty rounds because the footer is a
   sibling of <main>, not of the last <section>, so a DOM-sibling check cannot
   reach it. pagecheck.mjs now measures adjacency from the boxes instead.
   Paper is not the fix here: these panels sit on PAPER content, so paper would
   just move the collision upward. Light sage is the third surface the brand
   guideline already provides, and it differs from both neighbours. */
.certs--sage { background: var(--surface-sage); }
.cert-row { display: flex; flex-wrap: wrap; gap: 16px 40px; align-items: center; }
.cert-badge { display: inline-flex; align-items: center; gap: 10px; font-weight: 600; letter-spacing: .04em; color: var(--indigo-ink); }
.cert-badge span { display: inline-grid; place-items: center; width: 44px; height: 44px; border: 1.5px solid var(--indigo); border-radius: 50%; font-family: var(--serif); font-size: .8rem; }
/* The real ISOCERT marks are the only genuine proof in this band; sized against
   four large partner placeholders they read as an afterthought. */
.cert-logo { height: clamp(92px, 11vw, 124px); width: auto; display: block; }
/* The strip now lives INSIDE .cert-row (a wrapping flex), pushed right, so the
   certifications and the partner area read as one balanced line rather than a
   short row of real proof sitting above a field of empty boxes. It keeps its
   own grid so real logos still lay out in columns once supplied. */
.partner-strip { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-left: auto; }
@media (min-width: 700px) { .partner-strip { grid-template-columns: repeat(4, 1fr); } }
/* While the strip holds only the launch slot, don't stretch it across four
   tracks — a lone quarter-width hole looks like a broken image. Written with
   :has() rather than a modifier class so the strip returns to four columns by
   itself the moment staff add real logos, with nothing to remember to undo.
   Degrades harmlessly to the four-column rule where :has() is unsupported. */
.partner-strip:has(> :only-child) { grid-template-columns: minmax(0, 340px); }
.imgph--slot { min-height: 88px; }

/* ---------- Events band ---------- */
.events { position: relative; overflow: hidden; }
.events-grid { display: grid; gap: 32px; align-items: center; }
.events .imgph { min-height: 260px; height: 100%; }
@media (min-width: 900px) { .events-grid { grid-template-columns: 1.1fr 1fr; } }

/* ---------- Newsletter ---------- */
.news { background: var(--cocoa); color: var(--white); text-align: center; }
.news h2 { color: var(--white); }
.news form { display: flex; gap: 10px; max-width: 480px; margin: 24px auto 0; flex-wrap: wrap; justify-content: center; }
.news input { flex: 1 1 220px; min-height: 48px; padding: 0 18px; border: 1.5px solid rgba(255,255,255,.35); background: rgba(255,255,255,.08); color: var(--white); border-radius: var(--radius); font-size: .95rem; }
.news input::placeholder { color: rgba(255,255,255,.6); }

/* ---------- Footer ---------- */
.site-footer { background: var(--porcelain-2); border-top: 1px solid var(--line); padding-block: clamp(48px, 6vw, 80px) 32px; }
.foot-grid { display: grid; grid-template-columns: 1fr; gap: 36px; }
.foot-col h3 { font-family: var(--sans); font-weight: 600; font-size: .78rem; letter-spacing: .16em; text-transform: uppercase; color: var(--gold-deep); margin-bottom: 14px; }
.foot-col a, .foot-col p { display: block; font-size: .9rem; color: var(--ink-soft); padding: 5px 0; max-width: none; }
.foot-col a:hover { color: var(--indigo); }
/* One wordmark, one colour. The footer used to render the TEXT fallback in
   Cocoa Verde while the header rendered the real logo PNG in Basalt, so the
   logo appeared in two different colours on every page. The footer now uses the
   same image; the text fallback behind it is Basalt to match. (Round 20) */
.foot-brand .brand { color: var(--cocoa); text-align: left; }
.foot-brand .brand small { padding-left: 0; }
.foot-brand .brand-img { height: 54px; }
.foot-bottom { display: flex; flex-wrap: wrap; gap: 16px; justify-content: space-between; align-items: center; margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--line); font-size: .78rem; color: var(--ink-soft); }
.pay { display: flex; gap: 8px; flex-wrap: wrap; }
.pay span { border: 1px solid var(--line); border-radius: 3px; padding: 4px 9px; font-size: var(--fs-label); font-weight: 600; letter-spacing: .04em; color: var(--ink-soft); background: var(--white); }
@media (min-width: 640px)  { .foot-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .foot-grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; } }

/* ============================================================
   SHOP LISTING + PRODUCT DETAIL (PDP)
   ============================================================ */

/* Breadcrumb */
/* ⚠️ padding-BLOCK only, never the `padding` shorthand. 21 of 25 pages write
   the crumb as `class="crumb container"`, so the shorthand's implied
   padding-inline:0 beat .container's padding-inline (same specificity, later in
   the file) and the breadcrumb hung 80px out into the left margin — misaligned
   with the content column on every page except the three that happened to nest
   the crumb INSIDE a .container (shop, product, seasonal). Found in the Round
   24 visual pass; measured at the painted text edge, not the element box,
   because the two markup patterns give the same box and different text. */
.crumb { font-size: .8rem; color: var(--ink-soft); padding-block: 20px 4px; display: flex; gap: 8px; flex-wrap: wrap; }
.crumb a:hover { color: var(--indigo); }
/* --toile is a BORDER grey: as text on cream it is 2.5:1, well under AA. */
.crumb span { color: var(--sage-deep); }
.crumb b { color: var(--ink); font-weight: 500; }

/* Panel label — names which record an in-page panel is currently showing.
   Replaces the second breadcrumb on the Events booking panel (Round 20): a
   breadcrumb claims you moved page, and a duplicate Breadcrumb landmark shows
   up twice in a screen reader's landmark list. */
.panel-label { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.panel-label span { font-family: var(--sans); font-weight: 600; font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase; color: var(--gold-deep); }
.panel-label b { font-family: var(--serif); font-weight: 500; font-size: 1.05rem; color: var(--ink); }

/* Shop header */
.shop-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px; padding-bottom: 28px; border-bottom: 1px solid var(--line); margin-bottom: 32px; }
.shop-head .count { font-size: .85rem; color: var(--ink-soft); }
.sortbox { display: inline-flex; align-items: center; gap: 8px; font-size: .82rem; color: var(--ink-soft); }
.sortbox select { font-family: var(--sans); font-size: .85rem; padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); color: var(--ink); min-height: 44px; }

/* Shop layout: filters + grid */
.shop-layout { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: start; }
.filters { display: grid; gap: 26px; }
.filter-group { border-bottom: 1px solid var(--line); padding-bottom: 20px; }
/* The Dietary group used to be last and killed its own rule inline. With it gone
   (2026-08-10) the rule is dropped structurally, so it stays correct whichever
   group ends up last. */
.filter-group:last-of-type { border-bottom: 0; }
.filter-title { font-family: var(--sans); font-weight: 600; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase; color: var(--indigo-ink); margin-bottom: 14px; }
.filter-list { display: grid; gap: 10px; }
/* The label is the tap target, so it carries the 44px height — the box itself
   stays 18px, which is what the eye expects a checkbox to look like. */
.filter-list label { display: flex; align-items: center; gap: 12px; font-size: .9rem; color: var(--ink); cursor: pointer; min-height: 44px; }
.filter-group.is-disabled .filter-title, .filter-group.is-disabled .filter-list { opacity: .4; }
.filter-group.is-disabled .filter-list { pointer-events: none; }

/* A control that exists but cannot work yet, rendered as a non-focusable <span>
   rather than a dead <a href="#">. Used by Visit Us's "Get Directions" while the
   store addresses are still pending. Honest beats broken. */
.btn.is-inert {
  opacity: .45; cursor: not-allowed; pointer-events: none;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Knowledge Center articles that are planned but unwritten (2026-08-12). Not
   dimmed to .45 like an inert button — the card still has to be readable, since
   the topic list is the useful part. It simply stops pretending to be a link,
   and says why. */
.article-card.is-soon { cursor: default; }
.article-card.is-soon:hover { transform: none; box-shadow: none; }
.soon-badge {
  display: inline-block; margin-bottom: 8px;
  padding: 3px 10px; border-radius: 999px;
  background: var(--cream-2); color: var(--ink-soft);
  font-family: var(--sans); font-size: var(--fs-label);
  letter-spacing: .08em; text-transform: uppercase; font-weight: 600;
}
.filter-note { font-size: .8rem; font-style: italic; color: var(--ink-soft); margin-top: 10px; }
.filter-help { border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; background: var(--white); display: grid; gap: 4px; }
.filter-help h2 { font-size: 1.15rem; line-height: 1.3; margin-bottom: 4px; } /* global h1–h4 line-height 1.08 is too tight once this wraps */
.filter-help p { font-size: .88rem; color: var(--ink-soft); margin-bottom: 6px; }
.filter-help .btn--ghost { font-size: .88rem; } /* keep the inherited 44px min-height */
.filter-help-note { font-size: var(--fs-label); font-style: italic; margin: 10px 0 0; }

/* Checkout — optional VAT invoice block (VN e-invoice) */
.vat-toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: 600; color: var(--ink); }
.vat-toggle input { width: 18px; height: 18px; accent-color: var(--indigo); }
.vat-fields { margin-top: 16px; padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--porcelain-2); display: grid; gap: 14px; }
.vat-fields legend { font-family: var(--serif); font-size: 1.1rem; color: var(--indigo-ink); padding: 0 6px; }
.vat-fields [lang="vi"], .vat-toggle [lang="vi"] { color: var(--ink-soft); font-weight: 400; }

/* Footer — real store contacts */
.foot-store { font-style: normal; display: block; padding: 4px 0 12px; font-size: .82rem; line-height: 1.5; color: var(--ink-soft); }
.foot-store b { display: block; color: var(--ink); font-weight: 600; }
/* The street line is its own block so the phone number does not run straight on
   from it ("…P. Bến Thành(+84) 969 123 220"). The links stay inline so Hà Nội's
   two numbers sit together on one line, separated by the · in the markup. */
.foot-store .st { display: block; }
.foot-store a { display: inline; padding: 0; color: var(--indigo); }
.foot-store a:hover { text-decoration: underline; }
.chip { border: 1px solid var(--line); background: var(--white); border-radius: 999px; padding: 8px 16px; font-size: .82rem; font-weight: 500; color: var(--ink); cursor: pointer; min-height: 44px; min-width: 44px; transition: all .18s var(--ease); }
.chip:hover, .chip[aria-pressed="true"] { border-color: var(--indigo); background: var(--indigo); color: var(--white); }

.shop-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px 16px; }
.prod .prod-cat { font-size: var(--fs-label); letter-spacing: .14em; text-transform: uppercase; color: var(--gold-deep); font-weight: 600; }

/* auto-fit, not repeat(3, 1fr) — 2026-08-12. With three fixed tracks a filter
   that returns two products left a visibly empty third cell and shrank the
   photos to 278px, so a narrow result read as a broken page rather than a
   deliberate one. auto-fit collapses the unused track, so two results become
   two large cards filling the row. At the full nine products the container is
   868px and minmax(240px,1fr) still resolves to three columns, so the default
   view is unchanged. */
@media (min-width: 700px) {
  .shop-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
  /* ⚠️ auto-fit alone overcorrects: a filter returning ONE product gave it the
     whole 868px row as a single 868×868 card, which is worse than the gap it
     was fixing. The cap keeps a lone result a sensible size. At nine products
     the tracks are 279px and this never applies. Measured, not guessed. */
  .shop-grid .prod { max-width: 430px; }
}
@media (min-width: 1024px) {
  .shop-layout { grid-template-columns: 248px 1fr; gap: 44px; }
  .filters { position: sticky; top: 96px; }
}

.shop-empty {
  grid-column: 1 / -1;      /* span the results columns — it lives inside #grid */
  margin: 0; padding: 40px 0; text-align: center; color: var(--ink-soft);
  font-size: 1rem;
}
.shop-empty a { color: var(--indigo); font-weight: 600; text-decoration: underline; }

/* ---------- PDP ---------- */
.pdp-grid { display: grid; grid-template-columns: 1fr; gap: 36px; align-items: start; padding-top: 12px; }
@media (min-width: 900px) { .pdp-grid { grid-template-columns: 1fr 1fr; gap: 56px; } .pdp-info { position: sticky; top: 96px; } }
.gallery-main { aspect-ratio: 1/1; }
.gallery-thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 12px; }
.gallery-thumbs .imgph { aspect-ratio: 1/1; min-height: 0; }
.pdp-info { display: flex; flex-direction: column; gap: 18px; }
.pdp-info h1 { font-size: clamp(2rem, 4vw, 3rem); }
.pdp-price { font-family: var(--serif); font-size: 1.9rem; color: var(--indigo); }
.pdp-price small { font-family: var(--sans); font-size: .9rem; color: var(--ink-soft); }
.bundle-title { font-size: .78rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--indigo-ink); }
.bundle { display: flex; gap: 10px; flex-wrap: wrap; }
.bundle button { border: 1.5px solid var(--line); background: var(--white); border-radius: var(--radius); padding: 12px 22px; font-weight: 600; font-size: .9rem; color: var(--ink); min-height: 48px; transition: all .18s var(--ease); }
.bundle button[aria-pressed="true"] { border-color: var(--indigo); background: var(--porcelain-2); color: var(--indigo); box-shadow: inset 0 0 0 1px var(--indigo); }
.pdp-info .btn--primary { align-self: flex-start; }

.accordion { border-top: 1px solid var(--line); margin-top: 6px; }
.acc-item { border-bottom: 1px solid var(--line); }
.acc-btn { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; background: none; border: 0; padding: 18px 2px; font-family: var(--sans); font-weight: 600; font-size: .95rem; color: var(--indigo-ink); text-align: left; min-height: 44px; }
.acc-btn .plus { font-size: 1.3rem; color: var(--gold-deep); transition: transform .22s var(--ease); }
.acc-btn[aria-expanded="true"] .plus { transform: rotate(45deg); }
.acc-panel { max-height: 0; overflow: hidden; transition: max-height .28s var(--ease); }
.acc-panel .acc-inner { padding: 0 2px 20px; color: var(--ink-soft); font-size: .92rem; }

/* ============================================================
   GIFTS + BUSINESS SOLUTIONS
   ============================================================ */

/* Page hero (no split media) — used by Gifts & Business Solutions */
.page-hero { text-align: center; }
.page-hero .container { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.page-hero .lead { max-width: 60ch; }
.page-hero .hero-cta { justify-content: center; }
.page-hero--indigo { background: var(--indigo-ink); color: var(--porcelain); }
.page-hero--indigo h1, .page-hero--indigo .h-hero { color: var(--white); }
.page-hero--indigo .lead { color: #DCE1D2; }
.page-hero--indigo .eyebrow { color: var(--gold); }

/* Gift services band (reuses events-grid); gift-note chip */
.gift-note { display: inline-flex; align-items: center; gap: 8px; background: var(--porcelain-2); border: 1px solid var(--line); border-radius: 999px; padding: 8px 16px; font-size: .85rem; color: var(--indigo-ink); }

/* Business Solutions — B2B register (line-art / navy, spec-forward) */
.biz-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }
.biz-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 30px 28px; background: var(--white); display: flex; flex-direction: column; gap: 14px; transition: box-shadow .22s var(--ease), border-color .22s; }
.biz-card:hover { box-shadow: 0 16px 34px rgba(46,59,46,.10); border-color: var(--mist); }
.biz-card h3 { font-size: 1.7rem; }
.biz-card .who { font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; color: var(--gold-deep); font-weight: 600; }
.biz-bullets { list-style: none; display: grid; gap: 8px; margin: 4px 0; }
.biz-bullets li { position: relative; padding-left: 22px; font-size: .92rem; color: var(--ink-soft); }
.biz-bullets li::before { content: ""; position: absolute; left: 4px; top: 8px; width: 8px; height: 8px; border: 1.5px solid var(--indigo); transform: rotate(45deg); }
.biz-card .btn { align-self: flex-start; margin-top: auto; }
@media (min-width: 860px) { .biz-grid { grid-template-columns: repeat(2, 1fr); } }

/* Spec table (OEM / manufacturing) */
.spec-table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.spec-table th, .spec-table td { text-align: left; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.spec-table th { font-family: var(--sans); font-weight: 600; color: var(--indigo-ink); width: 40%; letter-spacing: .02em; }
.spec-table td { color: var(--ink-soft); }
.spec-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); }

/* ============================================================
   FORMS (shared)
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.field label { font-size: .82rem; font-weight: 600; color: var(--indigo-ink); }
.field input, .field select, .field textarea { min-height: 48px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); font-family: var(--sans); font-size: .95rem; background: var(--white); color: var(--ink); width: 100%; }
.field textarea { min-height: 120px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--indigo); outline-offset: 0; }

/* ---------- Form controls in the brand ----------
   Left native, a checkbox and a <select> render in the OS's own blue-grey
   chrome, which is the one place a carefully built page still looks like a
   template. These redraw them in Cocoa Verde. The SVG tick and chevron are
   inline data URIs — no external request, so they survive the port intact
   (# must be written %23 inside a data URI). */
input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; flex: none; margin: 0;
  border: 1.5px solid var(--toile); border-radius: 3px;
  background: var(--white); cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:hover, input[type="radio"]:hover { border-color: var(--indigo); }
input[type="checkbox"]:checked, input[type="radio"]:checked { background-color: var(--indigo); border-color: var(--indigo); }
input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23FCF9F2' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
  background-size: 13px; background-position: center; background-repeat: no-repeat;
}
input[type="radio"]:checked {
  background-image: radial-gradient(circle, var(--white) 0 3.5px, transparent 3.6px);
}
select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%232F533C' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 14px;
  padding-right: 40px;
}
.form-row { display: grid; gap: 0 16px; }
@media (min-width: 620px) { .form-row--2 { grid-template-columns: 1fr 1fr; } }

/* ============================================================
   EVENTS & WORKSHOPS
   ============================================================ */
.event-list { display: grid; gap: 16px; }
.event-card { display: grid; grid-template-columns: 1fr; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--white); transition: box-shadow .22s var(--ease); }
.event-card:hover { box-shadow: 0 14px 30px rgba(46,59,46,.09); }
.event-card .imgph { min-height: 160px; border-radius: 0; }
.event-body { padding: 22px 24px; display: flex; flex-direction: column; gap: 8px; }
.event-body h3 { font-size: 1.5rem; }
.event-body .meta { font-size: .88rem; color: var(--ink-soft); }
.event-body .price-tag { font-family: var(--serif); font-size: 1.2rem; color: var(--indigo); }
.event-body .btn { align-self: flex-start; margin-top: 6px; }
@media (min-width: 720px) { .event-card { grid-template-columns: 240px 1fr; align-items: stretch; } .event-card .imgph { min-height: 100%; } }

/* ---- Round 21 events rebuild: "bigger boxes, two side by side, with a
   description". --tall stacks photo over body (the old layout squeezed the
   photo into a 240px side column, which is why the cards read as thin), and
   the list holds two tracks from 900px instead of one long column. */
.event-list--2up { display: grid; gap: 20px; grid-template-columns: 1fr; }
@media (min-width: 900px) { .event-list--2up { grid-template-columns: repeat(2, 1fr); } }
.event-card--tall { grid-template-columns: 1fr !important; }
.event-card--tall .imgph { aspect-ratio: 16/9; min-height: 0; }
.event-card--tall .event-body { display: flex; flex-direction: column; gap: 10px; padding: 22px 24px 24px; }
.event-card--tall .event-body .btn { align-self: flex-start; margin-top: auto; }
.event-desc { font-size: .95rem; color: var(--ink); max-width: 60ch; }

/* ---- Month calendar (events page). Quiet-luxury calendar: paper cells,
   hairline grid, gold session chips. Wide content scrolls its own container
   on small screens — the page never scrolls sideways. */
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; max-width: 420px; margin-bottom: 14px; }
.cal-head b { font-family: var(--serif); font-size: 1.25rem; font-weight: 600; color: var(--indigo-ink); }
.cal-nav { width: 44px; height: 44px; border: 1px solid var(--line); border-radius: 50%; background: var(--white); color: var(--indigo); font-size: 1.1rem; cursor: pointer; transition: all .18s var(--ease); }
.cal-nav:hover { background: var(--indigo); color: var(--white); border-color: var(--indigo); }
.cal-scroll { overflow-x: auto; }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(88px, 1fr)); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--line); gap: 1px; min-width: 640px; }
.cal-dow { background: var(--forest); color: var(--white); font-family: var(--sans); font-size: var(--fs-label); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; padding: 8px 10px; }
.cal-day { background: var(--white); min-height: 96px; padding: 6px 8px 8px; display: flex; flex-direction: column; gap: 4px; }
.cal-day--void { background: var(--paper); }
.cal-num { font-family: var(--serif); font-size: .9rem; color: var(--ink-soft); }
.cal-ev { border: 0; text-align: left; font-family: var(--sans); font-size: .72rem; line-height: 1.35; background: var(--gold); color: #2E240A; border-radius: 4px; padding: 6px 8px; min-height: 44px; cursor: pointer; transition: background .15s var(--ease), transform .15s var(--ease); }
.cal-ev b { font-weight: 700; }
.cal-ev small { display: block; opacity: .75; font-size: .68rem; }
.cal-ev:hover { background: var(--forest); color: var(--white); transform: translateY(-1px); }

/* Round 22 (Tommy: "should it be a square? there is no picture that will look
   good for that size") — the panel photo is square again. The column's spare
   height is spent on a Good-to-know card that FLEX-GROWS, so the two columns
   end level without stretching the photo. (The Round 21 stretch attempt is
   gone; its aspect-ratio collision lesson lives on in the git history.) */
#detail .pdp-grid { align-items: stretch; }
.panel-side { display: flex; flex-direction: column; gap: 18px; }
.panel-side .gallery-main { aspect-ratio: 1/1; height: auto; }
.good-to-know { flex: 1; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); padding: 20px 22px; }
.good-to-know h3 { font-family: var(--sans); font-weight: 600; font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase; color: var(--gold-deep); margin-bottom: 12px; }
.good-to-know dl { display: flex; flex-direction: column; gap: 9px; }
.good-to-know dl > div { display: flex; justify-content: space-between; gap: 14px; align-items: baseline; border-bottom: 1px dashed var(--sage-pale); padding-bottom: 8px; }
.good-to-know dl > div:last-child { border-bottom: 0; }
.good-to-know dt { font-weight: 600; font-size: .85rem; color: var(--ink); }
.good-to-know dd { font-size: .85rem; color: var(--ink-soft); text-align: right; }
.good-to-know dd a { color: var(--indigo); text-decoration: underline; display: inline-flex; align-items: center; min-height: 44px; }

/* ---- Group booking: per-guest name fields ---- */
.guest-names { border: 1px dashed var(--toile); border-radius: var(--radius); padding: 14px 16px 16px; margin-top: 4px; }
.guest-names-note { font-size: .82rem; color: var(--ink-soft); margin-bottom: 10px; }
.guest-names-grid { display: grid; gap: 10px 14px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .guest-names-grid { grid-template-columns: 1fr 1fr; } }
.event-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 28px; }

/* ============================================================
   CART
   ============================================================ */
.cart-layout { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: start; }
.progress-bar { background: var(--porcelain-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 16px; font-size: .85rem; color: var(--indigo-ink); margin-bottom: 20px; }
.progress-bar .track { height: 6px; background: var(--mist); border-radius: 999px; margin-top: 8px; overflow: hidden; }
.progress-bar .fill { height: 100%; background: var(--gold-deep); width: 20%; }
.cart-item { display: grid; grid-template-columns: 84px 1fr auto; gap: 16px; align-items: center; padding: 18px 0; border-bottom: 1px solid var(--line); }
.cart-item .imgph { width: 84px; height: 84px; min-height: 0; }
.cart-item h3 { font-family: var(--sans); font-weight: 600; font-size: .98rem; color: var(--ink); letter-spacing: 0; }
.cart-item .sub { font-size: .82rem; color: var(--ink-soft); }
.cart-item .remove { background: none; border: 0; color: var(--ink-soft); font-size: .8rem; text-decoration: underline; cursor: pointer; padding: 4px 8px; min-height: 44px; }
.qty { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: var(--radius); margin-top: 8px; }
.qty button { width: 44px; height: 44px; background: none; border: 0; font-size: 1.1rem; color: var(--indigo); }
.qty span { min-width: 34px; text-align: center; font-variant-numeric: tabular-nums; font-size: .9rem; }
.cart-item .line-price { font-family: var(--serif); font-size: 1.1rem; color: var(--indigo); white-space: nowrap; }
.summary { border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 24px; background: var(--porcelain-2); }
.summary h3 { font-family: var(--sans); font-weight: 600; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase; color: var(--indigo-ink); margin-bottom: 16px; }
.summary .row { display: flex; justify-content: space-between; padding: 8px 0; font-size: .92rem; color: var(--ink-soft); }
.summary .row.total { border-top: 1px solid var(--line); margin-top: 8px; padding-top: 14px; font-family: var(--serif); font-size: 1.3rem; color: var(--indigo); }
.summary .btn { width: 100%; justify-content: center; margin-top: 16px; }
.giftwrap { display: flex; align-items: flex-start; gap: 10px; padding: 14px 0; border-bottom: 1px solid var(--line); font-size: .88rem; color: var(--ink); }
.giftwrap input { width: 18px; height: 18px; margin-top: 3px; accent-color: var(--indigo); }
@media (min-width: 900px) { .cart-layout { grid-template-columns: 1fr 360px; gap: 48px; } .summary { position: sticky; top: 96px; } }

/* ============================================================
   CHECKOUT
   ============================================================ */
.steps { display: flex; gap: 8px; margin-bottom: 32px; flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: 10px; font-size: .88rem; color: var(--ink-soft); }
.step .n { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; border: 1.5px solid var(--line); font-size: .82rem; font-weight: 600; }
.step.active { color: var(--indigo); font-weight: 600; }
.step.active .n { background: var(--indigo); color: var(--white); border-color: var(--indigo); }
.step .arrow { color: var(--sage-deep); margin: 0 4px; }
.pay-methods { display: grid; gap: 10px; margin-top: 8px; }
.pay-opt { display: flex; align-items: center; gap: 12px; border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; cursor: pointer; font-size: .92rem; min-height: 52px; }
.pay-opt input { width: 18px; height: 18px; accent-color: var(--indigo); }
.pay-opt[data-sel="true"] { border-color: var(--indigo); background: var(--porcelain-2); }

/* ============================================================
   MY ACCOUNT
   ============================================================ */
.auth-wrap { max-width: 440px; margin: 0 auto; }
.auth-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 32px 28px; background: var(--white); }
.auth-tabs { display: flex; gap: 4px; margin-bottom: 24px; border-bottom: 1px solid var(--line); }
.auth-tabs button { flex: 1; background: none; border: 0; padding: 12px; font-family: var(--sans); font-weight: 600; font-size: .95rem; color: var(--ink-soft); border-bottom: 2px solid transparent; cursor: pointer; min-height: 44px; }
.auth-tabs button[aria-selected="true"] { color: var(--indigo); border-bottom-color: var(--gold-deep); }
.oauth-btn { width: 100%; justify-content: center; margin-top: 4px; border: 1.5px solid var(--line); background: var(--white); color: var(--ink); }
.auth-or { text-align: center; font-size: .8rem; color: var(--ink-soft); margin: 16px 0; position: relative; }
.dash { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: start; }
/* Without this the order table's min-content width (~341px) becomes the grid
   track's automatic minimum, so on a 375px phone the whole dashboard is wider
   than the screen. .spec-wrap already has overflow-x:auto — it just could never
   act on it, because a grid item defaults to min-width:auto and will not shrink
   below its content. */
.dash > * { min-width: 0; }
.dash-side { border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; background: var(--white); }
.dash-side .who { font-weight: 600; color: var(--indigo-ink); }
.dash-side .email { font-size: .82rem; color: var(--ink-soft); margin-bottom: 14px; }
.dash-nav { display: grid; gap: 2px; }
.dash-nav a { padding: 10px 12px; border-radius: var(--radius); font-size: .92rem; color: var(--ink); min-height: 40px; display: flex; align-items: center; }
.dash-nav a.active, .dash-nav a:hover { background: var(--porcelain-2); color: var(--indigo); }
.order-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.order-table th { text-align: left; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); padding: 10px 12px; border-bottom: 1px solid var(--line); }
.order-table td { padding: 14px 12px; border-bottom: 1px solid var(--line); }
.pill { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: var(--fs-label); font-weight: 600; }
.pill.done { background: #E3F1E8; color: #1E7A44; }
.pill.transit { background: var(--mist); color: var(--indigo); }
@media (min-width: 860px) { .dash { grid-template-columns: 240px 1fr; } }

/* ============================================================
   STORY / CRAFT / INGREDIENTS
   ============================================================ */
.team-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
.team { text-align: center; display: flex; flex-direction: column; gap: 6px; }
.team .imgph { aspect-ratio: 1/1; min-height: 0; border-radius: 50%; margin-bottom: 8px; }
.team h3 { font-size: 1.3rem; }
.team .role { font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; color: var(--gold-deep); font-weight: 600; }
@media (min-width: 800px) { .team-grid { grid-template-columns: repeat(4, 1fr); } }

/* ---------- Craft page — film, process photography, bonbons (2026-08-10) ---- */
.craft-hero { margin: 0; }
.craft-hero img {
  width: 100%; height: auto;           /* height:auto — see the .seed-step note */
  aspect-ratio: 21/9; object-fit: cover;
  border-radius: var(--radius); background: var(--mist);
}
/* 21:9 was chosen for a face-centred candid. The Round 20 hero is a mould
   draining chocolate, where the LENGTH of the strands is the whole image —
   21:9 sliced it off. 16:9 keeps the drop. */
.craft-hero--16x9 img { aspect-ratio: 16/9; }

/* Wide page banner — Our Story and Sustainability. Replaced 1320×560 / 1320×520
   placeholders, so it keeps that shape rather than the photos' native 16:9 or
   3:2, which would run ~700px tall and push the page's real content below the
   fold. Both photographs are of PEOPLE and a 2.4:1 crop of a 16:9 frame cuts
   top and bottom, so each page sets --focal to keep the faces in. */
/* ⚠️ margin-BLOCK only. This is a <figure class="container">, and `margin: 0`
   — written to kill the figure's UA margin-inline:40px — also killed
   .container's `margin: 0 auto`, so the 1320px box sat flush LEFT in the
   viewport: both hero photos rendered 60px left of the content column with a
   200px gutter on the right. Second instance this round of the same bug shape
   (see .crumb): a class combined with .container must never use the `margin`
   or `padding` SHORTHAND, because the implied inline value silently beats
   .container's own. Found in the Round 24 visual pass. */
.story-hero { margin-block: 0; }
.story-hero img {
  width: 100%; height: auto;
  aspect-ratio: 1320/545; object-fit: cover; object-position: 50% var(--focal, 38%);
  border-radius: var(--radius); background: var(--mist);
}
.craft-hero figcaption { margin-top: 10px; font-size: .86rem; color: var(--ink-soft); font-style: italic; }

.film {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius); background: var(--forest-deep);
  aspect-ratio: 16/9;
}

/* Where a step has no truthful photograph yet. Deliberately looks like the
   `.slot` price fields — same dashed language, same meaning: something is owed
   here and nobody has invented it. */
.shot-slot {
  display: grid; place-content: center; gap: 4px; text-align: center;
  /* 16/9 not 4/3 — three full-height empty boxes in a row read as damage
     rather than as a to-do list. Still reserves honest space. */
  aspect-ratio: 16/9; border: 1px dashed var(--toile); border-radius: var(--radius);
  padding: 12px; color: var(--ink-soft);
  /* ⚠️ align-self, or a grid row taller than the box stretches it. On craft.html
     this slot shares a row with a 976px process step and was being pulled to
     833px — a dashed box that size reads as a broken page, not as a to-do.
     aspect-ratio alone does not prevent it: it sets the shape, and the stretch
     overrides the height. Measured 2026-08-19. */
  align-self: start;
}
.shot-slot b { font-size: var(--fs-label); letter-spacing: .1em; text-transform: uppercase; color: var(--crimson); }
.shot-slot span { font-size: .82rem; }

/* Gifts lifestyle shot — replaced an .imgph placeholder, so it inherits the
   same slot in the two-column band. height:auto for the aspect-ratio reason
   documented on .seed-step img. */
.gift-shot {
  width: 100%; height: auto; aspect-ratio: 4/3; object-fit: cover;
  border-radius: 30px 8px 30px 8px; background: var(--mist);
}

/* B2B capability grid. Shares the bonbon-grid card language deliberately —
   same site, same vocabulary — but 4-up and allowed to drop to 2 columns,
   since the fourth cell is a shot slot rather than a photograph. */
.cap-grid { display: grid; gap: clamp(18px, 3vw, 26px); }
@media (min-width: 620px)  { .cap-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .cap-grid { grid-template-columns: repeat(4, 1fr); } }
.cap-grid figure { margin: 0; }
.cap-grid img, .cap-grid .shot-slot {
  width: 100%; height: auto; aspect-ratio: 4/3; object-fit: cover;
  border-radius: 24px 6px 24px 6px;
}
.cap-grid img { background: var(--mist); }
.cap-grid figcaption { margin-top: 12px; font-size: .88rem; color: var(--ink-soft); line-height: 1.55; }
.cap-grid figcaption b {
  display: block; font-family: var(--sans); font-size: var(--fs-label); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--indigo); margin-bottom: 5px;
}

.bonbon-grid { display: grid; gap: clamp(18px, 3vw, 28px); }
@media (min-width: 800px) { .bonbon-grid { grid-template-columns: repeat(3, 1fr); } }
.bonbon-grid figure { margin: 0; }
.bonbon-grid img {
  width: 100%; height: auto; aspect-ratio: 4/3; object-fit: cover;
  border-radius: 24px 6px 24px 6px; background: var(--mist);
}
.bonbon-grid figcaption { margin-top: 12px; font-size: .9rem; color: var(--ink-soft); line-height: 1.55; }
.bonbon-grid figcaption b {
  display: block; font-family: var(--sans); font-size: var(--fs-label); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--indigo); margin-bottom: 5px;
}

.process { display: grid; gap: 18px; }
@media (min-width: 900px) { .process { grid-template-columns: repeat(2, 1fr); } }
/* Third column holds the photograph. It was a two-column text card before the
   process gained imagery on 2026-08-10. */
.process-step { display: grid; grid-template-columns: 56px 1fr; gap: 20px; align-items: start; padding: 22px 24px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); }
.process-step > img, .process-step > .shot-slot {
  grid-column: 1 / -1; width: 100%; height: auto;
  aspect-ratio: 4/3; object-fit: cover; border-radius: var(--radius);
}
/* ⚠️ Only the IMG gets the tint. Applying it to .shot-slot too put --ink-soft
   text on sage at 4.46:1 — just under AA, and caught by the harness rather
   than by eye. The slot sits on the card's own white. */
.process-step > img { background: var(--mist); }
@media (min-width: 900px) {
  .process-step--wide { grid-column: 1 / -1; }
  /* ⚠️ The placeholder must be listed alongside the img. The wide step spans
     the full grid, so at the default 4/3 an empty dashed box came out
     1110x833 — a wall, not a to-do note. The rule was written for the
     photograph and the slot that stands in for it was forgotten. */
  .process-step--wide > img,
  .process-step--wide > .shot-slot { aspect-ratio: 21/9; }
}
.process-step .num { width: 48px; height: 48px; border-radius: 50%; border: 1.5px solid var(--indigo); color: var(--indigo); display: grid; place-items: center; font-family: var(--serif); font-size: 1.4rem; }
.process-step h3 { font-size: 1.4rem; margin-bottom: 6px; }
.process-step p { font-size: .92rem; color: var(--ink-soft); }
@media (min-width: 720px) { .process { grid-template-columns: repeat(2, 1fr); } }

/* Cacao & Ingredients — hard B2C / B2B divide */
.divide-band { text-align: center; padding: 20px; }
.divide-band .tag { display: inline-block; padding: 6px 18px; border-radius: 999px; font-size: var(--fs-label); font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
.tag--retail { background: var(--gold); color: var(--indigo-ink); }
.tag--b2b { background: var(--mist); color: var(--indigo-ink); }
.b2b-half { background: var(--indigo-ink); color: var(--porcelain); }
.b2b-half h2, .b2b-half h3 { color: var(--white); }
.b2b-half .lead { color: #DCE1D2; }
.b2b-half .eyebrow { color: var(--gold); }
.b2b-half .imgph { background: repeating-linear-gradient(45deg, #34412F 0 12px, #2A3527 12px 24px); border-color: var(--toile); color: var(--mist); }
.b2b-half .imgph b { color: var(--mist); }
.b2b-half .spec-wrap { background: rgba(255,255,255,.04); border-color: rgba(234,227,216,.3); }
.b2b-half .spec-table th { color: var(--white); }
.b2b-half .spec-table td { color: #DCE1D2; }
.b2b-half .spec-table th, .b2b-half .spec-table td { border-bottom-color: rgba(234,227,216,.2); }

/* ============================================================
   KNOWLEDGE CENTER · VISIT · PRESS · CONTACT
   ============================================================ */
.kb-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }
.article-card { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--white); transition: box-shadow .22s var(--ease); }
.article-card:hover { box-shadow: 0 14px 30px rgba(46,59,46,.09); }
.article-card .imgph { min-height: 160px; border-radius: 0; }
.article-card .body { padding: 20px 22px; display: flex; flex-direction: column; gap: 8px; }
.article-card .kb-cat { font-size: var(--fs-label); letter-spacing: .14em; text-transform: uppercase; color: var(--gold-deep); font-weight: 600; }
.article-card h3 { font-size: 1.4rem; }
.article-card p { font-size: .9rem; color: var(--ink-soft); }
.article-card .read { font-size: .82rem; font-weight: 600; color: var(--indigo); margin-top: auto; }
@media (min-width: 640px) { .kb-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .kb-grid { grid-template-columns: repeat(3, 1fr); } }

.loc-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }
.loc-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--white); }
.loc-card .imgph { min-height: 180px; border-radius: 0; }
.loc-card .body { padding: 22px 24px; display: flex; flex-direction: column; gap: 6px; }
.loc-card h3 { font-size: 1.5rem; }
.loc-card .addr { font-size: .92rem; color: var(--ink-soft); }
/* ⚠️ Each part on its own line. Filling these cards in on 2026-08-12 reproduced
   the exact footer bug Round 15 fixed — inline children ran the phone straight
   on from the street and the hours straight on from the phone:
   "…P. Bến Thành, Hồ Chí Minh(+84) 969 123 220Opening hours [pending]".
   Same defect, second location; that is what makes it worth a rule rather than
   a one-off patch. */
.loc-card .addr > * { display: block; }
.loc-card .addr > * + * { margin-top: 4px; }
.loc-card .addr a { color: var(--indigo); font-weight: 600; }
.loc-card .tagline { font-size: var(--fs-label); letter-spacing: .1em; text-transform: uppercase; font-weight: 600; }
.loc-card .btn { align-self: flex-start; margin-top: 8px; }
@media (min-width: 800px) { .loc-grid { grid-template-columns: repeat(2, 1fr); } }

.press-quote { border-left: 3px solid var(--gold-deep); padding: 6px 0 6px 22px; margin: 0; }
.press-quote p { font-family: var(--serif); font-size: 1.5rem; color: var(--indigo-ink); line-height: 1.3; }
.press-quote cite { display: block; margin-top: 10px; font-family: var(--sans); font-style: normal; font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; color: var(--gold-deep); }
.press-quotes { display: grid; gap: 32px; }
@media (min-width: 800px) { .press-quotes { grid-template-columns: 1fr 1fr; } }

.contact-grid { display: grid; grid-template-columns: 1fr; gap: 40px; align-items: start; }
@media (min-width: 860px) { .contact-grid { grid-template-columns: 1.2fr 1fr; } }
.contact-info .row { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); font-size: .92rem; }
.contact-info .row b { color: var(--indigo-ink); min-width: 84px; display: inline-block; }

/* Motion */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ---- Knowledge Center article template (Round 21). One page serves all six
   drafts via ?a=, the same shape as product.html — Shopify's blog template
   replaces it at the port. Reading measure capped at 70ch. */
.article-wrap { max-width: 820px; }
.article-body h2 { font-size: 1.45rem; margin: 30px 0 10px; color: var(--indigo-ink); }
.article-body p { margin: 0 0 14px; max-width: 70ch; font-size: 1.02rem; line-height: 1.75; }
.article-body a { color: var(--indigo); text-decoration: underline; }

/* ---- Visit Us interactive map (Round 22). Leaflet container + brand pins.
   The gold dot with a forest ring reads as D'art against CARTO's pale tiles. */
/* Store locator layout (Round 24). The map's SHAPE is what frames the country
   — see the note in visit-us.html. Portrait, so fitBounds lands on zoom 6 and
   Vietnam fills the frame instead of the old landscape frame's 51° of Asia.
   Beside the cards on desktop, so the extra height costs no page length. */
/* ⚠️ minmax(0,1fr), not the implicit `auto` column. The map div's only child is
   the position:absolute offline fallback, so as a grid item with an auto-sized
   column it has no in-flow content to size against and collapsed to 2px wide
   when stacked — max-width alone cannot rescue a track that measured itself
   from nothing. Caught by reading the map's own bounds, not by a screenshot. */
.loc-layout { display: grid; gap: 28px; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1000px) {
  .loc-layout { grid-template-columns: 420px minmax(0, 1fr); align-items: start; }
}
.loc-map { height: clamp(600px, 62vw, 700px); border: 1px solid var(--line); border-radius: var(--radius); background: var(--cream-2); position: relative; z-index: 0; }
/* Stacked (below the two-column breakpoint) the map must NOT run the full
   container width: a 704px-wide frame re-widens the longitude span to 15°+ and
   Thailand comes back. Capped and centred so the frame stays portrait at every
   width, which is the whole mechanism here. */
/* ⚠️ width:100% is REQUIRED here, not belt-and-braces. `margin-inline:auto` on
   a grid item cancels the default justify-self:stretch — the auto margins
   absorb the free space and the box falls back to shrink-to-fit, which for this
   div is ZERO because its only child is position:absolute. It rendered 2px
   wide. max-width caps a width; it never supplies one. */
@media (max-width: 999.98px) { .loc-map { width: 100%; max-width: 460px; margin-inline: auto; } }
.loc-map-fallback { position: absolute; inset: 0; display: grid; place-content: center; color: var(--ink-soft); font-size: .9rem; padding: 20px; text-align: center; }
.loc-pin span { display: block; width: 22px; height: 22px; border-radius: 50%; background: var(--gold); border: 3px solid var(--forest); box-shadow: 0 0 0 2px rgba(255,255,255,.85), 0 2px 6px rgba(0,0,0,.3); }
.leaflet-popup-content { font-family: var(--sans); font-size: .85rem; line-height: 1.5; }
.leaflet-popup-content b { font-family: var(--serif); font-size: 1rem; color: var(--indigo-ink); }
.leaflet-popup-content a { color: var(--indigo); text-decoration: underline; }

/* ---- Calendar colour key + typed chips (Round 22). One colour per
   experience, drawn from the locked brand set — the secondary palette stays
   reserved for Daily Gifts / Seasonal as the guideline requires. */
.cal-legend { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 12px; font-size: .8rem; color: var(--ink-soft); }
.cal-legend span { display: inline-flex; align-items: center; gap: 7px; }
.cal-key { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.cal-key--tasting, .cal-ev--tasting { background: var(--gold); color: #2E240A; }
.cal-key--tour, .cal-ev--tour { background: var(--sage-deep); color: var(--white); }
.cal-key--workshop, .cal-ev--workshop { background: var(--cocoa); color: var(--white); }
.cal-ev--tour small, .cal-ev--workshop small { opacity: .85; }
.cal-ev--tour:hover, .cal-ev--workshop:hover { background: var(--forest); color: var(--white); }

/* ---- Per-event session picker in the booking panel ---- */
.field-label { display: block; font-weight: 600; font-size: .85rem; margin-bottom: 8px; }
.session-pick { display: flex; flex-wrap: wrap; gap: 8px; }
.session-chip { border: 1px solid var(--line); background: var(--white); border-radius: 999px; padding: 9px 15px; min-height: 44px; font-family: var(--sans); font-size: .82rem; color: var(--ink); cursor: pointer; transition: all .18s var(--ease); }
.session-chip:hover { border-color: var(--indigo); }
.session-chip[aria-pressed="true"] { background: var(--indigo); border-color: var(--indigo); color: var(--white); }
/* Leaflet ships 30px zoom buttons; 44px is the tap floor this site holds
   everywhere else. Styled rather than exempted — controls persist, so unlike
   the tiles they CAN be fixed from our stylesheet. */
.leaflet-control-zoom a { width: 44px !important; height: 44px !important; line-height: 44px !important; font-size: 1.15rem; }
.session-enquiry { font-size: .82rem; color: var(--ink-soft); margin-top: 10px; }
.session-enquiry a { color: var(--indigo); text-decoration: underline; display: inline-flex; align-items: center; min-height: 44px; }
#dt[readonly] { background: var(--paper); color: var(--indigo-ink); font-weight: 600; cursor: default; }

/* ============================================================
   ILLUSTRATION LAYER (Round 23)
   Filled by dart-ill.js from ill-manifest.js. A slot with no
   generated file keeps its .imgph placeholder and none of this
   applies — so the site is never mid-batch broken.
   ============================================================ */

/* A placeholder that has been filled. Keeps the page's own layout
   modifier (.gallery-main, grid sizing) and drops the dashed hatch. */
.ill-fig {
  display: block; position: relative; overflow: hidden;
  border: 0; border-radius: var(--radius); background: none;
  aspect-ratio: var(--ill-ar, 16/9); min-height: 0; padding: 0;
}
.ill-img { display: block; width: 100%; height: 100%; }

/* Toile line art is never cropped — engraved linework loses its
   composition the moment a frame cuts into it, and the cream ground
   IS the artwork (the prompts specify cream, not transparent), so
   letterboxing is invisible rather than something to design around. */
/* ⚠️ No background on either the frame or the image. build_ill.py lifts the
   flat cream paper out of the line art and ships it ink-on-transparent, so the
   art sits on whatever surface is behind it. Painting --paper here put a cream
   rectangle back — and the B2B cards are #FCF9F2, not --paper #FFF8EC, so it
   read as a pale box around every icon. (Caught in the card render, 2026-08-16.) */
.ill-img--contain { object-fit: contain; background: none; }
/* Photoreal mood shots are backdrops and do crop. */
.ill-img--cover { object-fit: cover; }

/* Spot icons on B2B capability cards. Absent until generated —
   the cards keep their existing diamond bullets meanwhile. Once any icon on
   the page is live, the not-yet-generated ones RESERVE their space (see
   dart-ill.js) so the headings in a row stay level through a partial batch. */
.biz-card-ico { display: none; }
.ill-icons-live .biz-card-ico { display: block; width: 56px; aspect-ratio: 1/1; margin: 0 0 10px; }
.ill-icons-live .biz-card-ico:not(.ill-on) { visibility: hidden; }
.biz-card-ico.ill-on {
  display: block; width: 56px; aspect-ratio: 1/1;
  margin: 0 0 10px; background: none; border: 0; border-radius: 0;
}
.biz-card-ico .ill-img { width: 56px; height: 56px; }

/* Ornamental section bands. Purely decorative (alt=""), so they are
   simply not there until a file exists. */
.ill-divider { display: none; }
.ill-divider.ill-on {
  display: block; width: 100%; max-width: 760px; margin: 0 auto;
  aspect-ratio: var(--ill-ar, 1600/140); background: none;
  border: 0; border-radius: 0; opacity: .85;
}
@media (max-width: 640px) { .ill-divider.ill-on { max-width: 90%; } }

/* Card thumbs keep the band height the CARD defines, not the record's
   aspect ratio — otherwise a half-generated grid goes ragged, with the
   two illustrated cards taller than the four still waiting. Contain
   letterboxes the art onto cream instead of cropping it. */
.article-card .ill-fig { aspect-ratio: auto; height: 160px; }
.event-card:not(.event-card--tall) .ill-fig { aspect-ratio: auto; min-height: 160px; height: 100%; }

/* Mood bands — the photoreal layer. Rounded, full-container, purely atmospheric.
   These carry no product claim, which is the only reason a photograph is used
   here rather than the toile that carries the rest of the site. */
/* ⚠️ The aspect-ratio override is the point of this class, not the rounding.
   The manifest gives every mood shot 16/9 — which is the ART's true shape and
   correct to reserve while it loads — but a 16/9 band across a 1152px container
   is 648px tall, i.e. a full-bleed hero. These are meant to be a closing breath
   at the foot of a text page, not the loudest thing on it. object-fit:cover
   means the shallower frame simply crops, costing nothing. */
.mood-band.ill-fig { aspect-ratio: 1320/360; border-radius: var(--radius); overflow: hidden; }
.mood-band .ill-img--cover { border-radius: var(--radius); }

/* Local Delicacy photograph — sits under the delicacy line in each journey
   chapter. Shallow, so it reads as an illustration of the sentence above it
   rather than as a second hero inside an already long chapter. */
.delicacy-shot.ill-fig { aspect-ratio: 16/9; border-radius: var(--radius); overflow: hidden; margin-top: 12px; }
.delicacy-shot .ill-img--cover { border-radius: var(--radius); }

/* Province landscape — the establishing shot at the top of each journey
   chapter, between the prose and the region film. Deeper than the delicacy
   photo below it: this one is setting the scene, that one is illustrating a
   sentence, and giving them the same weight flattened the chapter. */
.province-shot.ill-fig { aspect-ratio: 16/9; border-radius: var(--radius); overflow: hidden; margin: 18px 0; }
.province-shot .ill-img--cover { border-radius: var(--radius); }
