/* ============================================================
   KHAMACO v2 — components.css
   Section + nav + footer + mobile styles. Pairs with base.css.
   ============================================================ */

/* ---------- Header / Nav ---------- */
.site-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease);
  border-bottom: 1px solid transparent;
}
/* Solid bone — NO backdrop-filter blur (blur repaints every scroll frame → jank).
   Opaque rather than 0.96: at 4% transparency the body copy scrolling underneath
   showed through as ghost text behind the nav, which reads as a rendering fault. */
.site-header.scrolled {
  background: var(--bone);
  border-bottom-color: var(--hairline);
}
.nav {
  max-width: 1440px; margin-inline: auto; padding: 1.05rem var(--gutter);
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
}
.brand { display: inline-flex; align-items: baseline; gap: 0.55rem; }
.brand__mark { font-family: var(--f-display); font-weight: 500; font-size: 1.35rem; letter-spacing: -0.01em; color: var(--ink); }
.brand__sub { font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--olive); }
.nav__links { display: flex; align-items: center; gap: 1.9rem; }
.nav__link { font-size: 0.92rem; font-weight: 450; color: var(--ink); position: relative; padding-block: 0.3rem; white-space: nowrap; }
.nav__link::after { content: ""; position: absolute; left: 0; bottom: 0; width: 0; height: 1.5px; background: var(--amber); transition: width 0.3s var(--ease); }
.nav__link:hover::after { width: 100%; }
.nav__right { display: flex; align-items: center; gap: 1.1rem; }
.lang-switch { display: inline-flex; font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.05em; border: 1px solid var(--hairline); border-radius: 999px; overflow: hidden; }
.lang-switch a { padding: 0.32rem 0.7rem; color: var(--ink-soft); transition: background 0.25s, color 0.25s; }
.lang-switch a.active { background: var(--amber); color: #fff; }
.nav__toggle { display: none; width: 44px; height: 44px; align-items: center; justify-content: center; flex-direction: column; gap: 5px; }
.nav__toggle span { width: 22px; height: 2px; background: var(--ink); transition: transform 0.3s var(--ease), opacity 0.2s; transform-origin: center; }
.nav__toggle.active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle.active span:nth-child(2) { opacity: 0; }
.nav__toggle.active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Header over dark hero (not yet scrolled) → light text for contrast.
   The transparent header has no surface of its own, so its legibility was entirely at
   the mercy of whatever the hero photo put behind it (brand__sub measured 1.79:1 and
   the inactive lang-switch 2.11:1 over the pale pineapple hero). This scrim is the
   header's own guarantee — it fades out completely by the bottom edge, so the hero
   still reads as full-bleed. */
.site-header::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 190%;
  background: linear-gradient(180deg, rgba(30,22,16,0.68) 0%, rgba(30,22,16,0.34) 55%, rgba(30,22,16,0) 100%);
  pointer-events: none; z-index: -1;
  transition: opacity 0.35s var(--ease);
}
/* fades out as the solid bone header fades in, so there is no pop at the handover */
.site-header.scrolled::before, .light-top .site-header::before { opacity: 0; }
.site-header:not(.scrolled) .brand__mark { color: var(--bone-on-dark); }
.site-header:not(.scrolled) .brand__sub { color: var(--mango); }
.site-header:not(.scrolled) .nav__link { color: var(--bone-on-dark); }
.site-header:not(.scrolled) .nav__toggle span { background: var(--bone-on-dark); }
.site-header:not(.scrolled) .lang-switch { border-color: var(--hairline-dark); }
/* measured 4.16–4.84:1 over the hero scrim in --bone-on-dark-soft, i.e. failing AA on
   some pages; the active tab is already distinguished by its amber pill, so the
   inactive one can take the brighter tone without losing the state cue */
.site-header:not(.scrolled) .lang-switch a:not(.active) { color: var(--bone-on-dark); }

/* Pages whose TOP section is a light band (no dark hero) — e.g. Contact.
   Body carries .light-top so the not-yet-scrolled header shows a solid bone bg
   + DARK text/marks instead of the light-on-light that made nav/brand invisible. */
.light-top .site-header:not(.scrolled) { background: rgba(247, 242, 233, 0.96); border-bottom-color: var(--hairline); }
.light-top .site-header:not(.scrolled) .brand__mark { color: var(--ink); }
.light-top .site-header:not(.scrolled) .brand__sub { color: var(--olive); }
.light-top .site-header:not(.scrolled) .nav__link { color: var(--ink); }
.light-top .site-header:not(.scrolled) .nav__toggle span { background: var(--ink); }
.light-top .site-header:not(.scrolled) .lang-switch { border-color: var(--hairline); }
.light-top .site-header:not(.scrolled) .lang-switch a:not(.active) { color: var(--ink-soft); }

/* Desktop crunch zone: the hamburger only takes over at <=1024 (that boundary is a
   contract with main.js `isDesktop`, so it must not move). Between 1025 and 1180 the
   full nav + lang-switch + CTA no longer fit, so links wrapped to two lines and the
   lang-switch slid under the CTA. Tighten spacing and drop the brand descender instead
   of moving the breakpoint. */
@media (min-width: 1025px) and (max-width: 1180px) {
  .nav { gap: 1rem; }
  .nav__links { gap: 1.15rem; }
  .nav__right { gap: 0.7rem; }
  .nav__link { font-size: 0.86rem; }
  .nav__right .btn-primary { padding: 0.72rem 1.05rem; font-size: 0.88rem; }
  .brand__sub { display: none; }
}

/* mobile menu */
.mobile-menu { position: fixed; inset: 0; z-index: 99; background: var(--bone); padding: 6rem var(--gutter) 2rem; transform: translateY(-100%); transition: transform 0.5s var(--ease), visibility 0.5s; visibility: hidden; display: flex; flex-direction: column; gap: 0.4rem; }
.mobile-menu.open { transform: translateY(0); visibility: visible; }
/* Never in the tab order on desktop (its toggle is hidden there) */
@media (min-width: 1025px) { .mobile-menu { display: none; } }
.mobile-menu a { font-family: var(--f-display); font-size: 1.7rem; font-weight: 400; padding: 0.7rem 0; border-bottom: 1px solid var(--hairline); }

/* ---------- Hero ---------- */
.hero { position: relative; min-height: 100svh; display: flex; align-items: flex-end; overflow: hidden; background: var(--espresso); }
.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
/* Scrim strength is a CONTRAST requirement, not a mood knob: hero copy is
   --bone-on-dark(-soft) and must clear AA over the brightest pixel the photo can
   put behind it (backlit mango/pineapple ~ #E8C060). The old mid-stop of 0.2 left
   .hero__sub at ~2.6:1 on the soft-dried pineapple hero. Floor is now 0.55 anywhere
   text can land, deepening to 0.86 at the baseline where the copy block actually sits. */
.hero__scrim { position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(30,22,16,0.62) 0%, rgba(30,22,16,0.55) 30%, rgba(30,22,16,0.74) 62%, rgba(30,22,16,0.86) 100%),
              linear-gradient(90deg, rgba(30,22,16,0.72) 0%, rgba(30,22,16,0.34) 55%, rgba(30,22,16,0.06) 100%); }
.hero__sweep { position: absolute; inset: -20% -10% auto -10%; height: 60%; z-index: 2; pointer-events: none;
  background: radial-gradient(120% 80% at 20% 0%, rgba(242,169,59,0.22), rgba(242,169,59,0) 60%); }
.hero__inner { position: relative; z-index: 3; width: 100%; max-width: 1440px; margin-inline: auto; padding: 6rem var(--gutter) clamp(3.25rem, 7vh, 6rem); }
.hero__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.7rem; max-width: 62rem; }
.hero h1 { color: var(--bone-on-dark); font-size: clamp(2.15rem, 1.4rem + 2.75vw, 4rem); font-weight: 350; }
.hero h1 .em-on-dark { color: var(--mango); }
.hero__sub { color: var(--bone-on-dark-soft); font-size: clamp(1.05rem, 0.95rem + 0.6vw, 1.3rem); max-width: 54ch; line-height: 1.55; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-top: 0.5rem; }
.hero__facts { display: flex; flex-wrap: wrap; gap: 2.4rem; margin-top: 1rem; padding-top: 1.6rem; border-top: 1px solid var(--hairline-dark); }
.fact { display: flex; flex-direction: column; gap: 0.15rem; }
.fact__num { font-family: var(--f-display); font-size: clamp(2rem, 1.4rem + 2vw, 3rem); font-weight: 380; color: var(--mango); line-height: 1; font-feature-settings: 'lnum' 1, 'tnum' 1; }
.fact__label { font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--bone-on-dark-soft); }
.hero__scroll { position: absolute; bottom: 1.5rem; left: 50%; transform: translateX(-50%); z-index: 3; font-family: var(--f-mono); font-size: 0.66rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--bone-on-dark-soft); opacity: 0.7; }
/* Hero entrance — H1 is the LCP element: transform-only (opacity stays 1) so LCP
   is counted at first paint. `both` fill applies the from-state before first frame. */
@media (prefers-reduced-motion: no-preference) {
  .hero h1 { animation: heroRise 1s var(--ease) both; will-change: transform; }
  .hero .eyebrow { animation: heroFade 0.8s var(--ease) 0.05s both; }
  .hero__sub { animation: heroFade 0.9s var(--ease) 0.18s both; }
  .hero__cta, .hero .btn-sub { animation: heroFade 0.9s var(--ease) 0.3s both; }
  .hero__facts { animation: heroFade 0.9s var(--ease) 0.42s both; }
}
@keyframes heroRise { from { transform: translateY(24px); } to { transform: translateY(0); } }
@keyframes heroFade { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* ---------- Cert strip ---------- */
.certs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-lg); overflow: hidden; }
.cert { background: var(--bone); padding: 1.8rem 1.5rem; display: flex; flex-direction: column; align-items: flex-start; gap: 0.7rem; }
.cert__badge { width: 44px; height: 44px; color: var(--amber); }
.cert__name { font-family: var(--f-display); font-size: 1.15rem; font-weight: 500; }
.cert__desc { font-size: 0.82rem; color: var(--ink-soft); line-height: 1.45; }

/* ---------- Pillars (3-col) ---------- */
.pillars { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2.2rem; }
.pillar { position: relative; padding-top: 1.6rem; border-top: 2px solid var(--amber); }
.pillar__k { font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--olive); }
.pillar h3 { margin: 0.6rem 0 0.7rem; }

/* ---------- Category cards (PinnedFields on desktop) ---------- */
.cats { display: grid; gap: 1.5rem; grid-template-columns: repeat(3, 1fr); }
.cat-card { position: relative; border-radius: var(--r-lg); overflow: hidden; background: var(--chestnut); min-height: 460px; display: flex; flex-direction: column; justify-content: flex-end; isolation: isolate; }
.cat-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; transition: transform 0.8s var(--ease); }
/* The old ramp stayed at alpha .05 until 30% and only reached .9 at the very bottom,
   so .cat-card__count (mango, sitting at the TOP of the body block) measured 1.68:1
   over a bright card photo. Deepen from mid-card so the whole body block is covered. */
/* Deepened again: pixel sampling put .cat-card__count at 2.35–3.35:1 on the three
   category cards at 1440/1920 (it only passed at 390, where the shorter card drops the
   text further down the ramp). The dark stop now starts before the body block begins
   instead of at 68%, so the copy sits on the deep end at every height. */
.cat-card::after { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(30,22,16,0.10) 0%, rgba(30,22,16,0.42) 30%, rgba(30,22,16,0.90) 55%, rgba(30,22,16,0.97) 100%); }
.cat-card:hover img { transform: scale(1.05); }
/* The card-level gradient could not be trusted to cover the copy: it is anchored to the
   CARD, while the text block's position depends on how tall the card ends up, so at
   390px the count line rode up into the bright part of the photo and measured 1.85:1.
   This scrim is anchored to the TEXT BLOCK, so the copy carries its own darkness at any
   card height, over any photo. */
.cat-card__body { padding: 1.8rem; color: var(--bone-on-dark); position: relative; z-index: 1; }
.cat-card__body::before {
  content: ""; position: absolute; inset: -3.5rem 0 0 0; z-index: -1;
  background: linear-gradient(180deg, rgba(30,22,16,0) 0%, rgba(30,22,16,0.74) 32%, rgba(30,22,16,0.94) 60%, rgba(30,22,16,0.98) 100%);
  pointer-events: none;
}
.cat-card__count { font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--mango); }
.cat-card h3 { color: var(--bone-on-dark); margin: 0.5rem 0 0.4rem; }
.cat-card__link { display: inline-flex; align-items: center; gap: 0.5em; margin-top: 1rem; font-weight: 500; color: var(--mango); font-size: 0.92rem; }

/* ---------- Product cards (category / hub grids) ---------- */
.prod-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.3rem; }
.prod-card { background: var(--bone); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: 1.7rem; display: flex; flex-direction: column; transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease), border-color 0.25s; }
.prod-card:hover { transform: translateY(-3px); box-shadow: 0 14px 34px -20px rgba(30,22,16,0.4); }
.prod-card__tag { font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--amber-text); margin-bottom: 0.7rem; }
.prod-card h3 { margin-bottom: 0.5rem; }
.prod-card p { font-size: 0.9rem; color: var(--ink-soft); flex: 1; margin: 0; }
.prod-card__use { margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid var(--hairline); font-family: var(--f-mono); font-size: 0.7rem; color: var(--olive); letter-spacing: 0.02em; text-transform: uppercase; }
.prod-card--soft { border-top: 3px solid var(--olive); }

/* Photographed variant — the buyer is sourcing a physical good, so the SKU grids
   lead with the product, not a text block. Padding moves onto __body so the image
   can go edge-to-edge inside the card's radius. */
.prod-card--photo { padding: 0; overflow: hidden; }
.prod-card--photo:hover { border-color: rgba(168,102,15,0.45); }
.prod-card__media { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--paper); }
.prod-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.75s var(--ease); }
.prod-card--photo:hover .prod-card__media img { transform: scale(1.055); }
/* keeps the tag legible over any photo without dimming the product itself */
.prod-card__media .prod-card__tag {
  position: absolute; left: 0.85rem; top: 0.85rem; margin: 0; z-index: 2;
  background: rgba(30,22,16,0.82); color: var(--mango);
  padding: 0.34rem 0.62rem; border-radius: 999px; backdrop-filter: blur(3px);
}
.prod-card__body { padding: 1.35rem 1.45rem 1.5rem; display: flex; flex-direction: column; flex: 1; }
.prod-card__body h3 { margin-bottom: 0.45rem; }
@media (prefers-reduced-motion: reduce) { .prod-card--photo:hover .prod-card__media img { transform: none; } }
.prod-card--freeze { border-top: 3px solid var(--amber); }

/* ---------- Comparison table (GEO gold) ---------- */
/* The table was capped at 60rem, the same measure as the body copy above it. But a
   comparison table is not prose: a comfortable reading measure is what FORCES the
   wrapping here, so nearly every cell ran to two lines. Prose keeps its measure (set
   inline on the intro block); the table gets the full container, and on wide screens
   reaches a little past it. overflow-x:auto still catches anything narrower. */
.cmp-wrap { overflow-x: auto; border: 1px solid var(--hairline); border-radius: var(--r-lg); max-width: none; }
@media (min-width: 1200px) {
  .cmp-wrap {
    width: calc(100% + 2 * clamp(0rem, 2.5vw, 3rem));
    margin-inline: calc(-1 * clamp(0rem, 2.5vw, 3rem));
  }
}
.cmp { width: 100%; border-collapse: collapse; min-width: 560px; font-size: 0.95rem; }
.cmp th, .cmp td { text-align: left; padding: 1rem 1.1rem; border-bottom: 1px solid var(--hairline); vertical-align: top; }
.cmp thead th { font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--olive); background: var(--paper); }
/* the criterion labels are short ("Quy trình", "Kết cấu") — 26% was dead space taken
   from the two columns that actually carry the wrapping text */
.cmp tbody th { font-weight: 500; color: var(--ink); width: 20%; }
.cmp td { color: var(--ink-soft); }
/* These two tints LABEL the columns of the soft-dried vs freeze-dried comparison —
   olive for the slow-dried side, amber for the freeze-dried side. They only mean
   something on that table, so they must not be borrowed by any other one. */
.cmp .col-soft { background: rgba(110,115,85,0.05); }
.cmp .col-freeze { background: var(--amber-tint); }
/* Buyer-guide tables (condition → recommendation → application) instead emphasise the
   single ANSWER column, so the eye lands on the recommendation and the other two
   columns stay quiet. One consistent tone, no stripe that comes and goes. */
.cmp .col-answer { background: rgba(168,102,15,0.07); }
.cmp thead th.col-answer { background: var(--amber-tint); color: var(--amber-text); }
.cmp tr:last-child td, .cmp tbody tr:last-child th, .cmp tbody tr:last-child td { border-bottom: 0; }
/* On a DARK band (mango "Which format" guide) the table's ink/ink-soft cell text
   and hairline borders were dark-on-dark. Float it as a bone card so the light-theme
   cell colours read correctly. Home §4 table is on a light band → unaffected. */
.on-dark .cmp-wrap { background: var(--bone); }

/* ---------- Mango spotlight ---------- */
.mango-hero { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(2rem,5vw,4.5rem); align-items: center; }

/* ---------- Stacked composition: photo on top, changing detail beneath ----------
   The two-column version put a swapping photo beside a block of static text, so the
   six formats were named but never explained. Stacking puts the product first and
   hands the space directly under it to information that changes with the photo. */
.mango-hero--stack { display: block; }
.mango-intro { max-width: 46rem; margin-bottom: clamp(1.8rem, 3vw, 2.6rem); }
.mango-hero--stack .mango-figure { aspect-ratio: 21 / 9; }

.mango-detail {
  display: grid;                 /* every item shares one cell … */
  margin-top: clamp(1.6rem, 3vw, 2.2rem);
  border-top: 1px solid var(--hairline-dark-strong);
  padding-top: clamp(1.4rem, 2.5vw, 1.9rem);
}
.mango-detail__item {
  grid-area: 1 / 1;              /* … so the panel is as tall as its LONGEST entry and
                                    swapping never reflows the page (CLS stays 0) */
  display: grid; gap: 0.5rem; align-content: start;
  max-width: 62rem;
  opacity: 0; visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.45s var(--ease), transform 0.55s var(--ease), visibility 0s linear 0.45s;
}
.mango-detail__item.is-active {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 0.5s var(--ease) 0.1s, transform 0.6s var(--ease) 0.1s, visibility 0s;
}
.mango-detail__k { font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--mango); }
.mango-detail__item h3 { color: var(--bone-on-dark); margin: 0; }
.mango-detail__item p { color: var(--bone-on-dark-soft); margin: 0; max-width: 58ch; }
.mango-detail__use { font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--olive-on-dark); margin-top: 0.2rem; }

.mango-hero--stack .sku-grid { grid-template-columns: repeat(3, 1fr); margin-top: clamp(1.6rem, 3vw, 2.2rem); }
/* Pause control — WCAG 2.2.2 requires a way to stop content that moves by itself. */
.mango-pause {
  display: inline-flex; align-items: center; gap: 0.55em;
  margin-top: 1rem; padding: 0.55rem 0.9rem;
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--bone-on-dark-soft);
  border: 1px solid var(--hairline-dark-strong); border-radius: 999px;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.mango-pause:hover { color: var(--mango); border-color: var(--mango); }
.mango-pause__ico { width: 9px; height: 11px; flex: none; background: currentColor; clip-path: polygon(0 0, 35% 0, 35% 100%, 0 100%, 0 0, 65% 0, 100% 0, 100% 100%, 65% 100%, 65% 0); }
/* pressed = rotation is stopped, so the icon becomes "play" */
.mango-pause[aria-pressed="true"] .mango-pause__ico { clip-path: polygon(0 0, 100% 50%, 0 100%); }

.mango-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem 2.5rem; margin-top: clamp(2rem, 4vw, 2.8rem); }
.mango-foot > div:last-child { display: flex; flex-direction: column; align-items: flex-start; }

@media (max-width: 860px) {
  .mango-hero--stack .mango-figure { aspect-ratio: 4 / 3; }
  .mango-hero--stack .sku-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .mango-hero--stack .sku-grid { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .mango-detail__item { transition: opacity 0.2s linear, visibility 0s linear 0.2s; transform: none; }
  .mango-detail__item.is-active { transition: opacity 0.2s linear, visibility 0s; transform: none; }
}
.mango-figure { position: relative; border-radius: var(--r-lg); overflow: hidden; aspect-ratio: 4/3; }
.mango-figure img { width: 100%; height: 100%; object-fit: cover; object-position: center; }

/* Cross-fading format stack. Slides are absolutely stacked so the figure keeps one
   fixed box — no reflow, no CLS as they swap. The incoming slide settles from a hair
   of scale so the change reads as a turn of the hand rather than a flat dissolve. */
.mango-figure[data-mango-stack] { isolation: isolate; }
.mango-figure__slide {
  position: absolute; inset: 0;
  opacity: 0; transform: scale(1.04);
  transition: opacity 0.9s var(--ease), transform 1.6s var(--ease);
}
.mango-figure__slide.is-active { opacity: 1; transform: scale(1); z-index: 1; }
.mango-torch { z-index: 2; }

/* The chips are now controls: they report which format is on screen and can drive it. */
.sku { cursor: pointer; transition: border-color 0.3s var(--ease), background 0.3s var(--ease), transform 0.3s var(--ease); }
.sku::before { transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease); }
.sku:hover, .sku:focus-visible { border-color: var(--mango); transform: translateY(-1px); }
.sku.is-current { border-color: var(--mango); background: rgba(242,169,59,0.09); }
.sku.is-current::before { transform: scale(1.5); box-shadow: 0 0 0 4px rgba(242,169,59,0.18); }
@media (prefers-reduced-motion: reduce) {
  .mango-figure__slide { transition: opacity 0.25s linear; transform: none; }
  .mango-figure__slide.is-active { transform: none; }
  .sku:hover, .sku:focus-visible { transform: none; }
}
.mango-torch { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(circle at var(--tx,50%) var(--ty,45%), rgba(242,169,59,0.28), rgba(30,22,16,0) 45%); mix-blend-mode: screen; }
.sku-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.8rem; margin-top: 1.6rem; }
.sku { padding: 0.9rem 1.1rem; border: 1px solid var(--hairline-dark); border-radius: var(--r-md); font-size: 0.9rem; display: flex; align-items: center; gap: 0.6em; }
.sku::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--mango); flex: none; }

/* ---------- Factories / stat cards ---------- */
.factory-grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: clamp(2rem,4vw,3.5rem); align-items: center; }
.factory-media { position: relative; border-radius: var(--r-lg); overflow: hidden; aspect-ratio: 16/11; }
.factory-media img { width: 100%; height: 100%; object-fit: cover; }
.stat-cards { display: grid; grid-template-columns: repeat(2,1fr); gap: 1rem; margin-top: 1.8rem; }
.stat-card { border: 1px solid var(--hairline); border-radius: var(--r-md); padding: 1.3rem; }
.stat-card .n { display: block; font-family: var(--f-display); font-size: 2.2rem; font-weight: 380; color: var(--amber); line-height: 1; }
.stat-card .l { display: block; font-size: 0.82rem; color: var(--ink-soft); margin-top: 0.5rem; }
/* amber is 3.88:1 on espresso — stat figures on a dark band step up to mango */
.on-dark .stat-card { border-color: var(--hairline-dark-strong); }
.on-dark .stat-card .n { color: var(--mango); }
.on-dark .stat-card .l { color: var(--bone-on-dark-soft); }

/* ---------- Process (numbered steps) ---------- */
.process-map { position: relative; margin-top: 2.5rem; }
.process-steps { display: grid; grid-template-columns: repeat(5, 1fr); gap: 1.5rem; }
.pstep { text-align: left; padding-top: 1rem; border-top: 1px solid var(--hairline-dark); }
.pstep__n { font-family: var(--f-mono); font-size: 0.8rem; color: var(--mango); letter-spacing: 0.1em; }
.pstep h3 { font-family: var(--f-display); font-size: 1.05rem; font-weight: 500; margin: 0.5rem 0 0.4rem; color: var(--bone-on-dark); letter-spacing: -0.005em; }
.pstep p { font-size: 0.83rem; color: var(--bone-on-dark-soft); }

/* ---------- Lab / quality ---------- */
.spec-list { display: grid; grid-template-columns: repeat(2,1fr); gap: 1.3rem 2.5rem; margin-top: 1.5rem; }
.spec { border-top: 1px solid var(--hairline); padding-top: 0.9rem; }
.spec dt { font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--olive); }
.spec dd { font-family: var(--f-display); font-size: 1.4rem; font-weight: 400; margin-top: 0.3rem; }

/* ---------- Private label (stacking deck) ---------- */
.deck { display: grid; grid-template-columns: repeat(3,1fr); gap: 1.3rem; }
.deck-card { background: var(--paper); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: 1.8rem; }
.deck-card__ico { width: 40px; height: 40px; color: var(--amber); margin-bottom: 1rem; }
.deck-card h3 { margin-bottom: 0.5rem; }

/* ---------- Global reach ---------- */
.reach { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: clamp(2rem,4vw,3.5rem); align-items: center; }
.reach-map { width: 100%; display: block; }
/* legacy abstract-diagram classes — kept so an un-rebuilt page still renders */
.reach-map .land { fill: var(--chestnut); }
.reach-map .dot { fill: var(--mango); }
.reach-map .arc { fill: none; stroke: var(--mango); stroke-width: 1.2; opacity: 0.55; }

/* ---------- Real world map (built by _build/build-reach-map.mjs) ---------- */
.rm-land path { fill: var(--chestnut); }
/* stroke-width is in viewBox units and the map renders at ~0.56 scale, so 2.6 here is
   about 1.5px on screen */
.rm-route { fill: none; stroke: var(--mango); stroke-width: 2.6; opacity: 0.8; stroke-linecap: round; }
.rm-dot { fill: var(--mango); }
.rm-origin { fill: var(--mango); }
.rm-origin-halo { fill: none; stroke: var(--mango); stroke-width: 1.2; opacity: 0.45; }
/* font-size is in viewBox units too: the map is a 1000-unit canvas drawn ~560px wide,
   so 20 here lands at ~11px on screen. Authoring it as 11 made the labels 6px. */
.rm-label {
  font-family: var(--f-mono); font-size: 20px; font-weight: 500; letter-spacing: 0.07em;
  fill: var(--bone-on-dark);            /* 15.17:1 on espresso */
  paint-order: stroke; stroke: rgba(30,22,16,0.9); stroke-width: 5px; stroke-linejoin: round;
}
.rm-label--origin { fill: var(--mango); font-weight: 600; }

/* Entry: routes draw (handled in JS via stroke-dashoffset), pins and labels arrive as
   each route lands. Transform-only + opacity so nothing triggers layout. */
.rm-dot, .rm-label { opacity: 0; transition: opacity 0.5s var(--ease), transform 0.5s var(--ease); }
.rm-dot { transform-box: fill-box; transform-origin: center; transform: scale(0.2); }
.rm-label { transform: translateY(4px); }
.rm-dot.is-in, .rm-label.is-in { opacity: 1; transform: none; }
.rm-label--origin { opacity: 1; transform: none; }   /* origin is never staggered */

/* Origin keeps a slow pulse once the routes are live — the one moving thing in the
   section, so it reads as the source rather than just another pin. */
.reach-map.is-live .rm-origin-halo {
  transform-box: fill-box; transform-origin: center;
  animation: rmPulse 3.2s var(--ease) infinite;
}
@keyframes rmPulse {
  0%   { transform: scale(1);   opacity: 0.5; }
  70%  { transform: scale(2.1); opacity: 0; }
  100% { transform: scale(2.1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .rm-dot, .rm-label { opacity: 1; transform: none; transition: none; }
  .reach-map.is-live .rm-origin-halo { animation: none; }
}
@media (max-width: 860px) {
  /* one column: the map is full-container-width but the container is narrower, so the
     scale factor drops further and the type has to grow again to stay readable */
  .rm-label { font-size: 30px; letter-spacing: 0.05em; }
  .rm-route { stroke-width: 3.4; }
}
.markets { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.4rem; }
.market-chip { font-family: var(--f-mono); font-size: 0.78rem; letter-spacing: 0.04em; padding: 0.4rem 0.85rem; border: 1px solid var(--hairline-dark); border-radius: 999px; color: var(--bone-on-dark); }

/* ---------- Why-us (fan accordion grid) ---------- */
.reasons { display: grid; grid-template-columns: repeat(3,1fr); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-lg); overflow: hidden; }
/* 4-up hairline grid (About certifications = 4 items — even row, no orphan cell).
   Reuses .reason children; distinct from .reasons which stays 3-col for the 6-item home grid. */
.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-lg); overflow: hidden; }
@media (max-width: 900px) { .grid-4 { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 520px) { .grid-4 { grid-template-columns: 1fr; } }
.reason { background: var(--bone); padding: 1.7rem; }
.reason__ico { width: 34px; height: 34px; color: var(--amber); margin-bottom: 0.9rem; }
.reason h3 { font-family: var(--f-display); font-size: 1.12rem; font-weight: 500; margin-bottom: 0.4rem; letter-spacing: -0.005em; }
.reason p { font-size: 0.88rem; color: var(--ink-soft); }

/* ---------- Marquee (proof strip, mobile-safe) ---------- */
.marquee { overflow: hidden; border-block: 1px solid var(--hairline); padding-block: 1.1rem; }
.marquee__track { display: inline-flex; gap: 3rem; white-space: nowrap; animation: marquee 38s linear infinite; will-change: transform; }
.marquee__item { font-family: var(--f-mono); font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); display: inline-flex; align-items: center; gap: 0.8em; }
.marquee__item::after { content: "—"; color: var(--olive); }
@keyframes marquee { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .marquee__track { animation: none; } }
/* set by the IntersectionObserver in main.js once the element scrolls out of view —
   an infinite animation off-screen is pure main-thread cost */
.marquee__track.is-parked, .reach-map.is-parked .rm-origin-halo { animation-play-state: paused; }

/* ---------- FAQ (collapsing list) ---------- */
.faq { max-width: 820px; margin-inline: auto; }
.faq-item { border-bottom: 1px solid var(--hairline); }
.faq-q { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding: 1.4rem 0; text-align: left; font-family: var(--f-display); font-size: clamp(1.1rem,1rem+0.5vw,1.35rem); font-weight: 460; }
.faq-q__ico { flex: none; width: 22px; height: 22px; color: var(--amber); transition: transform 0.35s var(--ease); }
.faq-item.open .faq-q__ico { transform: rotate(45deg); }
.faq-a { overflow: hidden; height: 0; transition: height 0.4s var(--ease); }
.faq-a__inner { padding-bottom: 1.5rem; color: var(--ink-soft); max-width: 68ch; }

/* ---------- RFQ band + form ---------- */
.rfq { background: var(--espresso); position: relative; overflow: hidden; }
.rfq__edge { position: absolute; top: 0; right: 0; width: 42%; height: 100%; z-index: 0; opacity: 0.5; }
.rfq__edge img { width: 100%; height: 100%; object-fit: cover; }
.rfq__edge::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, var(--espresso) 8%, rgba(30,22,16,0.3) 100%); }
.rfq__inner { position: relative; z-index: 1; display: grid; grid-template-columns: 0.95fr 1.05fr; gap: clamp(2rem,5vw,4.5rem); align-items: start; }
.form-card { background: var(--bone); border-radius: var(--r-lg); padding: clamp(1.5rem,3vw,2.4rem); }
/* Light island inside the dark .rfq.on-dark band — keep its own text dark-on-light,
   overriding the inherited .on-dark {h*, p, .muted} colours (else the success-panel
   heading + helper text render light-on-bone = invisible). */
.form-card h1, .form-card h2, .form-card h3 { color: var(--ink); }
.form-card p, .form-card .muted { color: var(--ink-soft); }
.form-card label, .form-card .field-label { color: var(--ink); }
.form-step__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; gap: 1rem; }
.form-step__label { font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--olive); }

/* Progress rail — replaces the bare "Step 1 of 2" text so the wizard reads as a
   wizard. Two segments; the active one fills amber. aria-hidden because the
   textual "Step N of 2" beside it already carries the same state for AT. */
.form-progress { display: flex; gap: 0.4rem; margin-bottom: 1.6rem; }
.form-progress__seg { flex: 1; height: 3px; border-radius: 999px; background: var(--hairline); overflow: hidden; }
.form-progress__seg::after { content: ""; display: block; height: 100%; width: 0; background: var(--amber); transition: width 0.5s var(--ease); }
.form-progress__seg.is-done::after, .form-progress__seg.is-active::after { width: 100%; }

.field { margin-bottom: 1.15rem; position: relative; }
.field label, .field .field-label { display: block; font-size: 0.82rem; font-weight: 500; margin-bottom: 0.45rem; color: var(--ink); }
.field .req { color: var(--amber-text); margin-left: 0.15em; }
.field input, .field select, .field textarea {
  width: 100%; padding: 0.92rem 1rem; border: 1px solid var(--hairline);
  border-radius: var(--r-md); background: #fff; font: inherit; color: var(--ink);
  transition: border-color 0.22s var(--ease), box-shadow 0.22s var(--ease), background 0.22s;
}
.field input::placeholder, .field textarea::placeholder { color: #8d8375; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: rgba(42,32,24,0.3); }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--amber); outline: none; box-shadow: 0 0 0 3px rgba(168,102,15,0.28); }
/* select needs its own arrow once appearance is normalised across browsers */
.field select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%235A4C3E' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 1rem center; padding-right: 2.5rem; }
.field input[aria-invalid="true"], .field select[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: #b23b2e; box-shadow: 0 0 0 3px rgba(178,59,46,0.18); }
/* valid-state tick, but only after the field has been touched (JS adds .is-valid) */
.field.is-valid input, .field.is-valid select { border-color: #4f7a46; }
.field.is-valid::after {
  content: ""; position: absolute; right: 0.95rem; bottom: 1.05rem; width: 15px; height: 15px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3 8.5L6.5 12L13 4.5' stroke='%234f7a46' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center/contain;
  pointer-events: none;
}
.field.is-valid select ~ .fld-tick, .field.is-valid:has(select)::after { right: 2.4rem; }

/* Submit busy state — prevents the double-submit that a slow mail handoff invites */
.btn[data-busy="true"] { pointer-events: none; opacity: 0.72; }
.btn[data-busy="true"] .btn__arrow { display: none; }
.btn__spin { display: none; width: 1em; height: 1em; border: 2px solid rgba(255,255,255,0.45); border-top-color: #fff; border-radius: 50%; }
.btn[data-busy="true"] .btn__spin { display: inline-block; animation: btnSpin 0.7s linear infinite; }
@keyframes btnSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn[data-busy="true"] .btn__spin { animation-duration: 2s; } }
.field-err { color: #9e2f24; font-size: 0.78rem; margin-top: 0.4rem; display: flex; align-items: center; gap: 0.4em; font-weight: 500; }
.field-err::before { content: "!"; display: inline-flex; align-items: center; justify-content: center; width: 1.15em; height: 1.15em; border-radius: 50%; background: #b23b2e; color: #fff; font-size: 0.72em; flex: none; }
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip-check { position: relative; }
.chip-check input { position: absolute; opacity: 0; pointer-events: none; }
.chip-check span {
  display: inline-flex; align-items: center; gap: 0.45em;
  padding: 0.6rem 1rem; border: 1px solid var(--hairline); border-radius: 999px;
  font-size: 0.86rem; background: #fff; cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s, border-color 0.2s, transform 0.2s var(--ease);
}
/* the dot becomes a tick on select — gives the chip a state change you can feel */
.chip-check span::before {
  content: ""; width: 14px; height: 14px; border-radius: 50%; flex: none;
  border: 1.5px solid rgba(42,32,24,0.28); transition: all 0.2s var(--ease);
}
.chip-check:hover span { border-color: rgba(168,102,15,0.5); transform: translateY(-1px); }
.chip-check input:checked + span { background: var(--amber); color: #fff; border-color: var(--amber); }
.chip-check input:checked + span::before {
  border-color: #fff; background: #fff no-repeat center/9px 9px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3 8.5L6.5 12L13 4.5' stroke='%23A8660F' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* keyboard focus must be visible on the SPAN — the real input is offscreen */
.chip-check input:focus-visible + span { outline: 2px solid var(--amber); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .chip-check:hover span { transform: none; } }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-success { display: none; text-align: center; padding: 2rem 1rem; }
.form-success.show { display: block; }
.form-success .tick { width: 54px; height: 54px; color: var(--amber); margin: 0 auto 1rem; }

/* ---------- Footer ---------- */
.footer { background: var(--espresso); color: var(--bone-on-dark-soft); padding-top: var(--sp-12); border-top: 1px solid var(--hairline-dark); }
.footer__grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: 2.5rem; }
.footer h3 { font-family: var(--f-mono); font-size: 0.74rem; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--mango); margin-bottom: 1rem; }
.footer a { color: var(--bone-on-dark-soft); font-size: 0.9rem; line-height: 2; transition: color 0.2s; }
.footer a:hover { color: var(--mango); }
.footer__brand .brand__mark { color: var(--bone-on-dark); }
.footer__addr { font-size: 0.85rem; line-height: 1.7; margin-bottom: 0.8rem; }
.footer__bottom { border-top: 1px solid var(--hairline-dark); margin-top: var(--sp-8); padding-block: 1.6rem; display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; font-size: 0.8rem; }

/* ---------- Mobile sticky CTA bar ---------- */
.sticky-cta { position: fixed; bottom: 0; left: 0; right: 0; z-index: 90; display: none; grid-template-columns: 1.4fr 1fr 1fr; gap: 1px; background: var(--hairline); border-top: 1px solid var(--hairline); }
.sticky-cta a { background: var(--bone); padding: 0.85rem 0.5rem; display: flex; flex-direction: column; align-items: center; gap: 0.2rem; font-size: 0.72rem; font-weight: 500; color: var(--ink); }
.sticky-cta a.primary { background: var(--amber); color: #fff; }
.sticky-cta svg { width: 20px; height: 20px; }

/* ---------- Reveal (fail-visible base) ---------- */
.reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }
.no-js .reveal, .reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1 !important; transform: none !important; } }

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 1024px) {
  /* Nav → hamburger from 1024 down (fixes 861–1024 nav overflow) */
  .nav__links, .nav__right .lang-switch, .nav__right .btn-primary { display: none; }
  .nav__toggle { display: flex; }
  .sticky-cta { display: grid; }
  body { padding-bottom: 62px; }
  /* Hero: content-height under the fixed header — no empty dark band on tablet */
  .hero { align-items: flex-start; min-height: auto; }
  .hero__inner { padding-top: 6.75rem; padding-bottom: clamp(3rem, 7vh, 5rem); }
  .mango-hero, .factory-grid, .reach, .rfq__inner { grid-template-columns: 1fr; }
  .factory-media { order: -1; }
  .rfq__edge { display: none; }
  .process-steps { grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
  .prod-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 860px) {
  .pillars, .cats, .deck { grid-template-columns: 1fr; }
  .certs, .reasons { grid-template-columns: repeat(2, 1fr); }
  .sku-grid, .stat-cards, .spec-list { grid-template-columns: 1fr; }
  .footer__grid { grid-template-columns: 1fr 1fr; }
  .cat-card { min-height: 360px; }
  .process-steps { grid-template-columns: repeat(2, 1fr); }
  .footer a { display: inline-block; padding: 0.3rem 0; line-height: 1.5; }  /* ≥44px tap */
}
@media (max-width: 600px) {
  .process-steps { grid-template-columns: 1fr; }
  .data-band__item { white-space: normal; }              /* stop clipping "Markets…" */
  .data-band__row { justify-content: flex-start; gap: 0.4rem 1.2rem; }
}
@media (max-width: 520px) {
  .certs, .reasons, .footer__grid, .prod-grid { grid-template-columns: 1fr; }
  .hero__facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
}

/* ---------- Real brand mark ----------
   Replaces the typographic wordmark we had been setting. Sized by height so the
   intrinsic 1668x689 ratio drives the width and nothing reflows while it loads. */
.brand__logo { height: 40px; width: auto; display: block; }
.brand__logo--footer { height: 52px; margin-bottom: 0.9rem; }
@media (max-width: 1180px) { .brand__logo { height: 34px; } }
@media (max-width: 860px)  { .brand__logo { height: 32px; } .brand__logo--footer { height: 46px; } }

/* ============================================================
   SKU DETAIL PAGE  (/products/<method>/<fruit>/ — mould, Đợt 0)
   Appended, never edited above this line: 18 EN + 18 VI pages inherit these rules,
   so a change to an existing selector would ripple into 36 pages at once.
   ============================================================ */

/* ---------- Breadcrumb ----------
   The hub pages set their crumb inline on .eyebrow, which is display:inline-flex and
   therefore will not wrap between items — at 390px a four-level crumb pushed the hero
   sideways. This is an <ol> so assistive tech gets the hierarchy, and it wraps. */
.crumbs__list {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem 0.55rem;
  font-family: var(--f-mono); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase;
}
.crumbs__list li { display: inline-flex; align-items: center; gap: 0.55rem; }
.crumbs__list li + li::before { content: "/"; opacity: 0.5; }
.crumbs__list a { color: var(--amber-text); padding-block: 0.3rem; }
.crumbs__list a:hover { text-decoration: underline; text-underline-offset: 3px; }
.crumbs__list [aria-current] { color: var(--ink-soft); }
.on-dark .crumbs__list a { color: var(--mango); }
.on-dark .crumbs__list [aria-current] { color: var(--bone-on-dark-soft); }
/* Reserve two lines of crumb on narrow screens.
   JBMono is wider than every OS mono fallback, so below ~860px the crumb renders on one
   line before the font arrives and two lines after — pushing the H1, the CTAs and the
   whole data-band down 21px. Measured 0.074 CLS on the English page while the rest of
   the site sits at 0. Preloading JBMono was not enough: under 4x CPU throttling the swap
   still lands after first paint. A `size-adjust` fallback would fix the metrics on a
   desktop that happens to have Consolas and be wrong on Android, where the fallback is a
   different font again. Reserving the box does not care which font wins.
   ⚠️ Holds for a crumb of one or two lines. A third line shifts again — keep the last
   crumb short enough that `measure-sku-page.mjs` reports crumbH <= 51 at 390px. */
@media (max-width: 860px) { .crumbs__list { min-height: 3.2rem; align-content: flex-start; } }

/* ---------- Photo evidence gallery ----------
   A sourcing buyer scrolls a SKU page looking for proof the goods exist. So: one
   wide band of product filling the frame, then four tiles that each answer a
   different question (one piece up close · how it presents loose · who handles it).
   Aspect ratios are fixed per slot rather than per photo, so width/height are known
   before the image arrives and the grid never reflows — CLS stays at 0. */
.sku-gallery { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(0.75rem, 1.3vw, 1.15rem); }
.sku-gallery figure { margin: 0; }
.sku-gallery__frame { position: relative; overflow: hidden; border-radius: var(--r-lg); background: var(--paper); aspect-ratio: 4 / 3; }
.sku-gallery__frame img { width: 100%; height: 100%; object-fit: cover; }
.sku-gallery figure.is-lead { grid-column: span 4; }
.sku-gallery figure.is-lead .sku-gallery__frame { aspect-ratio: 21 / 9; }
.sku-gallery figcaption {
  font-family: var(--f-mono); font-size: 0.68rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--olive); margin-top: 0.6rem;
}
.on-dark .sku-gallery figcaption { color: var(--mango); }
@media (max-width: 860px) {
  .sku-gallery { grid-template-columns: repeat(2, 1fr); }
  .sku-gallery figure.is-lead { grid-column: span 2; }
  .sku-gallery figure.is-lead .sku-gallery__frame { aspect-ratio: 16 / 9; }
}
@media (max-width: 520px) {
  .sku-gallery { grid-template-columns: 1fr; }
  .sku-gallery figure.is-lead { grid-column: span 1; }
  .sku-gallery figure.is-lead .sku-gallery__frame { aspect-ratio: 3 / 2; }
}

/* ---------- Spec sheet with the values still open ----------
   The client has not confirmed moisture, shelf life, pack format or lead time, and the
   figures on their old site contradict this one (MOQ 400 kg there, 500 kg here). So the
   page declares the FIELD NAMES and leaves the values to the quote. Two reasons this is
   a component and not a paragraph of apology: it reads as a document rather than as an
   unfinished page, and filling it in later is a text swap plus deleting one attribute —
   `<dd data-pending>On your quote</dd>` becomes `<dd>12 months</dd>` — with no markup
   or CSS change. Nothing has to be rebuilt when the client answers. */
.specsheet { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 clamp(1.5rem, 3.5vw, 3.5rem); }
.specsheet__row { display: flex; align-items: baseline; gap: 0.7rem; padding: 0.8rem 0; border-top: 1px solid var(--hairline); }
.specsheet dt {
  display: flex; align-items: baseline; gap: 0.7rem; flex: 1; min-width: 0;
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink);
}
/* dotted leader: the row reads as a form waiting for an entry, not as missing content */
.specsheet dt::after { content: ""; flex: 1; min-width: 1.5rem; border-bottom: 1px dotted var(--hairline); transform: translateY(-0.3em); }
.specsheet dd { margin: 0; flex: none; font-size: 0.92rem; color: var(--ink); }
.specsheet dd[data-pending] {
  font-family: var(--f-mono); font-size: 0.68rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--olive);
}
.on-dark .specsheet dd[data-pending] { color: var(--mango); }
.on-dark .specsheet__row { border-color: var(--hairline-dark); }
.on-dark .specsheet dt::after { border-color: var(--hairline-dark); }
@media (max-width: 700px) { .specsheet { grid-template-columns: 1fr; } }

/* ---------- Cross-link chips (light bands) ----------
   .market-chip is dark-band only (its border is --hairline-dark). These are links, not
   labels, so they also carry a 44px hit box. */
.link-chips { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.3rem; }
.link-chip {
  display: inline-flex; align-items: center; gap: 0.55em; min-height: 44px;
  padding: 0.5rem 1.1rem; border: 1px solid var(--hairline); border-radius: 999px;
  background: var(--bone); color: var(--ink); font-size: 0.9rem; font-weight: 500;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease), transform 0.2s var(--ease);
}
.link-chip:hover { border-color: var(--amber); color: var(--amber-text); transform: translateY(-2px); }
.link-chip__arrow { color: var(--amber-text); transition: transform 0.25s var(--ease); }
.link-chip:hover .link-chip__arrow { transform: translateX(3px); }
.on-dark .link-chip { background: transparent; border-color: var(--hairline-dark-strong); color: var(--bone-on-dark); }
.on-dark .link-chip:hover { border-color: var(--mango); color: var(--mango); }
.on-dark .link-chip__arrow { color: var(--mango); }

/* ---------- Product card as a whole-card link ----------
   The hub grids use <article> because nothing there is clickable yet. On a SKU page the
   related cards ARE the navigation, so the card itself is the <a>: one tab stop and one
   hit area instead of a card with a small link hiding at the bottom of it. */
.prod-card--link { color: inherit; }
.prod-card__go {
  margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid var(--hairline);
  font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--amber-text);
  display: inline-flex; align-items: center; gap: 0.5em;
}
.prod-card--link:hover .prod-card__go { color: var(--amber-hover); }
.prod-card--link:hover .prod-card__go .btn__arrow { transform: translateX(3px); }

/* ---------- Category card → detail page ----------
   The grids were inert <article> blocks with 32 detail pages behind them and no way in.
   The link is a real anchor (not a stretched overlay on the whole card) so its purpose
   is announced on its own, and the card lifts with it on hover. */
.prod-card__more {
  display: inline-flex; align-items: center; gap: 0.35em;
  margin-top: 1rem; padding-top: 0.9rem;
  border-top: 1px solid var(--hairline);
  font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--amber-text);
  transition: gap 0.25s var(--ease), color 0.25s var(--ease);
}
.prod-card--photo .prod-card__more { margin-top: auto; }
.prod-card__more:hover { gap: 0.7em; color: var(--amber-hover); }
.prod-card:has(.prod-card__more:hover) { border-color: rgba(168,102,15,0.45); }
@media (prefers-reduced-motion: reduce) { .prod-card__more:hover { gap: 0.35em; } }
