/* ===========================================================================
   Single-product mattress page
   Direction: human-approachable — palette bound VERBATIM from
   `tools directions --id human-approachable`. Its posture asks for a sans
   display with strong weight contrast, so display = Bricolage Grotesque
   (self-hosted, Vietnamese subsets included) against Be Vietnam Pro for body.
   Derived tones use oklch() in the same hue families. No invented hex.
   =========================================================================== */

/* --- display face: variable, self-hosted, Vietnamese covered ------------- */
@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('fonts/BricolageGrotesque-vietnamese.woff2') format('woff2');
  font-weight: 400 800; font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('fonts/BricolageGrotesque-latin-ext.woff2') format('woff2');
  font-weight: 400 800; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('fonts/BricolageGrotesque-latin.woff2') format('woff2');
  font-weight: 400 800; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ---- human-approachable, verbatim ----------------------------------- */
  /* --bg is the ONE departure from the verbatim binding: the direction ships a
     cool near-white oklch(98% 0.004 240); the brief asked for porcelain, so the
     page now sits on a warm-neutral white (#FAF9F6 = oklch(98.2% 0.0041 91)).
     Everything else below is still the direction's own values. */
  --bg:      oklch(98.2% 0.004 91);
  --surface: oklch(100% 0 0);
  --fg:      oklch(20% 0.018 88);
  --muted:   oklch(50% 0.016 88);
  --border:  oklch(90% 0.008 88);
  --accent:  oklch(56% 0.12 170);

  --font-display: 'Bricolage Grotesque', 'Avenir Next', system-ui, sans-serif;
  --font-body:    'Be Vietnam Pro', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;

  /* ---- derived ---------------------------------------------------------- */
  --accent-dark: oklch(46% 0.11 170);
  --accent-tint: oklch(96% 0.022 170);
  --accent-line: oklch(84% 0.05 170);
  --deep:        oklch(26% 0.035 210);   /* dark panel, same cool family */
  --deep-2:      oklch(33% 0.04 210);
  --on-deep:     oklch(97% 0.005 240);
  --on-deep-mid: oklch(78% 0.015 240);
  /* The ONE panel tone. Measured in OKLab, the pale set had six members doing
     the work of two: --bg↔--cream was ΔE 1.77, --beige↔--sand 1.59,
     --bg↔--surface 1.84 — all under the ~2 threshold where the eye stops
     telling two colours apart. --sand is the only pale tone that clears the
     page decisively (ΔE 4.28), so it became the single panel colour and
     --cream / --beige were dropped rather than kept as near-duplicates. */
  /* Lifted 94% -> 96%: at 94% the panel measured ΔE 4.28 from the page, which
     reads as a heavy grey-beige slab. 96% lands at ΔE 2.50 — soft but still
     above the ~2 threshold where a band stops registering. 96.5% was tested and
     rejected at ΔE 1.96: the band visibly disappears. This is the floor. */
  --sand:        oklch(96% 0.014 85);
  --sale:        oklch(55% 0.19 25);
  --star:        oklch(78% 0.14 85);
  --ok:          oklch(56% 0.12 170);

  --r:   14px;
  --r-l: 20px;
  --pad: 20px;
  --max: 1200px;
  --nav: 64px;

  /* ---- motion system: one easing, one philosophy ----------------------- */
  --e-out: cubic-bezier(.16, 1, .3, 1);   /* rise and settle */
  --e-io:  cubic-bezier(.4, 0, .2, 1);
  --t-1: .45s;
  --t-2: .7s;
  --t-3: 1.05s;
}
::selection { background: var(--accent); color: oklch(100% 0 0); }
@media (min-width: 768px)  { :root { --pad: 32px; } }
@media (min-width: 1180px) { :root { --pad: 44px; } }

/* ===== base ============================================================= */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; color: var(--fg);
  font-family: var(--font-body); font-size: 17px; line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  /* Flat porcelain. The beige wash that used to fade over the first screen was
     only ΔE 2.77 from the page at its strongest and ΔE 1.59 from --sand — a
     third pale tone nobody could name. Removed rather than tuned. */
  background-color: var(--bg);
}
img { display: block; max-width: 100%; height: auto; }
svg { display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul { margin: 0; padding: 0; list-style: none; }
p { margin: 0; }
h1, h2, h3, h4 {
  margin: 0; font-family: var(--font-display); font-weight: 700;
  line-height: 1.04; letter-spacing: -0.035em; color: var(--fg);
}
h1 { font-size: clamp(42px, 8.4vw, 96px); }
h2 { font-size: clamp(30px, 4.6vw, 54px); letter-spacing: -0.03em; }
h3 { font-size: clamp(19px, 2vw, 25px); letter-spacing: -0.02em; line-height: 1.2; }
.num { font-family: var(--font-display); font-variant-numeric: tabular-nums; }
:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--pad); }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip { position: absolute; left: 12px; top: -60px; z-index: 300; padding: 11px 18px; background: var(--fg); color: var(--surface); border-radius: var(--r); transition: top .2s; }
.skip:focus { top: 12px; }

.eyebrow {
  display: inline-block; font-family: var(--font-display); font-size: 12px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
}
.eyebrow--on { color: var(--on-deep-mid); }
.lead { max-width: 58ch; font-size: clamp(17px, 1.7vw, 20px); color: var(--muted); }

.sec { padding-block: clamp(64px, 9vw, 128px); }
.sec__head { margin-bottom: clamp(32px, 5vw, 60px); }
.sec__head .eyebrow { margin-bottom: 16px; }
.sec__head h2 { max-width: 34ch; }
.sec__head .lead { margin-top: 20px; }
/* Second clause of a two-part heading, tinted. Own line via display:block so the
   break is structural, not a <br> that shifts with the font. Not italic — <span>
   carries no emphasis semantics, and the colour already does the separating. */
.h2__k { display: block; color: var(--accent-dark); }
/* A narrow heading alone in a wide container reads as unfinished, so when the
   head has supporting copy it becomes an editorial two-column split and uses
   the full measure. :has() keeps lead-less heads single-column. */
@media (min-width: 920px) {
  .sec__head:has(.lead) {
    display: grid; grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
    column-gap: clamp(40px, 6vw, 88px); align-items: end;
  }
  .sec__head:has(.lead) .eyebrow { grid-column: 1 / -1; }
  .sec__head:has(.lead) h2 { max-width: 20ch; }
  .sec__head:has(.lead) .lead { margin-top: 0; padding-bottom: 8px; }
}
.sec--deep { background: var(--deep); color: var(--on-deep); }
.sec--deep h2, .sec--deep h3 { color: var(--on-deep); }
.sec--deep .lead { color: var(--on-deep-mid); }
/* Both were separate tones once (--accent-tint mint, --sand warm). The mint was
   the only cool hue among five warm surfaces and read as an intrusion, so both
   collapsed onto the single panel tone. Kept as two class names because the
   markup uses both; they are deliberately identical, not a copy-paste slip. */
.sec--tint,
.sec--sand { background: var(--sand); }

/* ===== buttons ========================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 52px; padding: 14px 28px; border-radius: 99px;
  font-family: var(--font-display); font-size: 16px; font-weight: 600;
  border: 1.5px solid transparent; white-space: nowrap;
  transition: transform .14s, background .16s, box-shadow .16s, border-color .16s, color .16s;
}
/* --accent-dark, not --accent: white on --accent measures 4.34:1, under the 4.5
   AA needs for a 16px/600 label, and a button label does not qualify for the
   large-text exemption. --accent-dark takes it to 6.54:1 while keeping the fill
   6.18:1 clear of the page, so the button still reads as a solid object. The
   promise pill on the trust bar keeps --accent because its label is 19px/700
   (large text, 3:1) — same hue, different tier, both compliant. */
.btn--buy {
  background: var(--accent-dark); color: oklch(100% 0 0);
  box-shadow: 0 1px 2px oklch(40% 0.10 170 / .32), 0 10px 24px -10px oklch(40% 0.10 170 / .55);
}
.btn--buy:hover { background: oklch(40% 0.10 170); box-shadow: 0 2px 4px oklch(36% 0.09 170 / .36), 0 16px 32px -12px oklch(36% 0.09 170 / .6); transform: translateY(-1px); }
.btn--buy:active { transform: translateY(1px); }
.btn--line { border-color: var(--border); background: var(--surface); }
.btn--line:hover { border-color: var(--fg); }
.btn--onDeep { background: var(--on-deep); color: var(--deep); }
.btn--onDeep:hover { background: oklch(88% 0.01 240); }
.btn--ghostDeep { border-color: oklch(100% 0 0 / .3); color: var(--on-deep); }
.btn--ghostDeep:hover { border-color: var(--on-deep); }
.btn--sm { min-height: 44px; padding: 11px 20px; font-size: 15px; }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; transform: none; }
.btn[data-s="busy"] { color: transparent; pointer-events: none; position: relative; }
.btn[data-s="busy"]::after {
  content: ""; position: absolute; width: 17px; height: 17px; border-radius: 99px;
  border: 2px solid oklch(100% 0 0); border-top-color: transparent; animation: spin .6s linear infinite;
}
.btn[data-s="done"] { background: var(--accent-dark); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ===== nav ============================================================== */
.nav {
  position: sticky; top: 0; z-index: 100; min-height: var(--nav);
  background: color-mix(in oklab, var(--bg) 85%, transparent); backdrop-filter: saturate(1.7) blur(14px);
  border-bottom: 1px solid transparent; transition: border-color .2s;
}
.nav--stuck { border-bottom-color: var(--border); }
.nav .wrap { display: flex; align-items: center; gap: 20px; min-height: var(--nav); }
.mark { font-family: var(--font-display); font-size: 22px; font-weight: 800; letter-spacing: -0.05em; }
.mark i { font-style: normal; color: var(--accent); }
.nav__links { display: none; gap: 2px; margin-left: 12px; }
.nav__links a { padding: 9px 13px; font-size: 15.5px; color: var(--muted); border-radius: 99px; }
.nav__links a:hover { color: var(--fg); background: oklch(20% 0.02 240 / .05); }
.nav__end { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.nav__cartN {
  min-width: 22px; height: 22px; display: none; place-items: center; padding: 0 6px;
  font-family: var(--font-display); font-size: 12px; font-weight: 700;
  background: var(--accent); color: oklch(100% 0 0); border-radius: 99px;
}
.nav__cartN[data-on="true"] { display: grid; }
.burger { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-left: -8px; border-radius: 99px; }
.burger:hover { background: oklch(20% 0.02 240 / .05); }
@media (min-width: 940px) { .nav__links { display: flex; } .burger { display: none; } }

/* ===== hero ============================================================= */
.hero { padding-block: clamp(28px, 5vw, 56px) clamp(48px, 7vw, 92px); overflow: hidden; }
.hero__grid { display: grid; gap: clamp(30px, 5vw, 56px); align-items: center; }

@media (min-width: 940px) { .hero__grid { grid-template-columns: 1.02fr .98fr; } }
/* The hero headline breaks onto two authored lines, so it is capped smaller
   than the global h1 scale — at 96px the second line outgrew the hero column
   and wrapped into a third line. */
/* 5.4vw, not 6vw: with the text column narrowed to give the image room, 6vw
   pushed "Thức dậy khỏe." past the column at ~940px and broke the authored
   two-line headline into three. */
.hero h1 { margin-top: 18px; font-size: clamp(40px, 6vw, 76px); text-wrap: balance; }
.hero h1 i { font-style: normal; color: var(--accent); }
.hero h1 br { line-height: 0; }
.hero__lead { margin-top: 22px; }
.hero__rating { display: flex; align-items: center; gap: 10px; margin-top: 26px; font-size: 15px; color: var(--muted); }
.hero__price { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 8px 14px; margin-top: 26px; }
.hero__now { font-family: var(--font-display); font-size: clamp(32px, 3.6vw, 44px); font-weight: 700; letter-spacing: -0.035em; line-height: 1; }
.hero__was { font-size: 17px; color: var(--muted); text-decoration: line-through; }
.hero__off { padding: 4px 11px; font-family: var(--font-display); font-size: 13px; font-weight: 700; color: oklch(100% 0 0); background: var(--sale); border-radius: 99px; }
.hero__from { width: 100%; font-size: 14px; color: var(--muted); }
.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }
.hero__ship { margin-top: 18px; font-size: 14.5px; color: var(--muted); }
/* radius + clipping live on the frame, not the img, so the scroll-driven
   drift is contained inside the rounded shape */
.hero__media { position: relative; border-radius: var(--r-l); overflow: hidden; }
.hero__media img { width: 100%; height: 100%; aspect-ratio: 1; object-fit: cover; }
/* the trial badge pins to one corner, fully inside the image, on a real surface */
.hero__badge {
  position: absolute; right: 18px; bottom: 18px; z-index: 2;
  display: grid; gap: 1px; padding: 14px 18px; text-align: center;
  background: color-mix(in oklab, var(--surface) 93%, transparent); backdrop-filter: blur(8px);
  border-radius: var(--r); box-shadow: 0 8px 26px -8px oklch(20% 0.02 240 / .35);
}
.hero__badge b { font-family: var(--font-display); font-size: 26px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; }
.hero__badge span { font-size: 12px; color: var(--muted); }

/* --- cặp ảnh tự đổi ---------------------------------------------------
   Hai ảnh xếp chồng trong một khung. Phân vai rõ để không tranh property:
   wrapper .swap giữ transform + entry animation, hai <img> chỉ giữ opacity.
   aspect-ratio nằm trên wrapper vì ảnh position:absolute không còn đóng góp
   chiều cao cho khung — thiếu nó là hero sập về 0. */
/* pan-y: cuộn dọc vẫn thuộc về trang, chỉ vuốt ngang mới là đổi ảnh — không
   thì trên cảm ứng người dùng vuốt xuống lại vô tình đổi ảnh */
.swap { position: relative; display: block; aspect-ratio: 1; touch-action: pan-y; user-select: none; }
.swap__i { -webkit-user-drag: none; }
/* control nằm ngoài .swap nên không bị heroDrift kéo theo */
.swap__i { position: absolute; inset: 0; opacity: 0; }
.swap__i.is-on { opacity: 1; }
/* Vòng đổi do CSS chạy, không phải setTimeout — xem lý do đo được trong
   pdp.js. Một chu kỳ = 2 ảnh, nên --swap-t/2 là thời gian mỗi ảnh hiện.
   7s (3.5s/ảnh) thay cho 5.2s/ảnh của bản timer: đủ lâu để đọc, không lâu
   tới mức tưởng trang bị treo.
   Fade chiếm 8% chu kỳ (~0.56s) — hai ảnh fade ngược nhau cùng lúc nên đây
   là crossfade thật, không phải nháy trắng ở giữa. */
[data-swap] { --swap-t: 7s; --dot-ring: color-mix(in oklab, var(--fg) 55%, transparent); }
@keyframes swapFade {
  0%, 42%  { opacity: 1; }
  50%, 92% { opacity: 0; }
  100%     { opacity: 1; }
}
/* Dấu tròn chạy CÙNG bộ keyframes và cùng độ lệch nửa chu kỳ với ảnh, nên nó
   không thể lệch pha — không có JS nào phải theo dõi xem ảnh nào đang hiện. */
@keyframes dotCycle {
  0%, 42%  { background: var(--accent-dark); border-color: transparent;      transform: scale(1.35); }
  50%, 92% { background: transparent;        border-color: var(--dot-ring);  transform: scale(1); }
  100%     { background: var(--accent-dark); border-color: transparent;      transform: scale(1.35); }
}
/* auto và paused đều CÒN animation (paused chỉ đóng băng); manual thì tắt hẳn,
   vì `animation-play-state: paused` vẫn ghi đè opacity nên .is-on sẽ vô hiệu */
[data-swap].is-live:not([data-mode="manual"]) .swap__i,
[data-swap].is-live:not([data-mode="manual"]) .swap__dot::after {
  animation-duration: var(--swap-t);
  animation-timing-function: var(--e-io);
  animation-iteration-count: infinite;
}
[data-swap].is-live:not([data-mode="manual"]) .swap__i          { animation-name: swapFade; }
[data-swap].is-live:not([data-mode="manual"]) .swap__dot::after { animation-name: dotCycle; }
/* phần tử sau lệch nửa chu kỳ — dùng `+` thay vì :nth-child để không phụ thuộc
   việc còn phần tử nào khác nằm cùng cấp */
[data-swap].is-live:not([data-mode="manual"]) .swap__i   + .swap__i          { animation-delay: calc(var(--swap-t) / -2); }
[data-swap].is-live:not([data-mode="manual"]) .swap__dot + .swap__dot::after { animation-delay: calc(var(--swap-t) / -2); }
[data-swap][data-mode="paused"] .swap__i,
[data-swap][data-mode="paused"] .swap__dot::after { animation-play-state: paused; }
[data-swap][data-mode="manual"] .swap__i {
  animation: none;
  transition: opacity 420ms var(--e-out);   /* tự chọn thì đổi nhanh hơn vòng tự động */
}

/* --- cụm điều khiển: gộp một góc, luôn hiện -----------------------------
   Bản trước nút dừng chỉ hiện khi hover — sai với cảm ứng và sai với a11y
   (control ẩn tới khi hover thì không ai biết nó có). Dots cũng phải luôn
   hiện, vì chúng chính là thứ cho biết "có 2 ảnh, vuốt được". */
.swap__ui {
  position: absolute; left: 18px; bottom: 18px; z-index: 3;
  display: flex; align-items: center; gap: 2px; padding: 3px 5px 3px 3px;
  background: color-mix(in oklab, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px); border-radius: 999px;
  box-shadow: 0 6px 20px -8px oklch(20% 0.02 240 / .34);
}
.swap__stop {
  display: grid; place-items: center; width: 30px; height: 30px; padding: 0;
  color: var(--fg); border-radius: 999px; cursor: pointer;
  transition: background var(--t-1);
}
.swap__stop svg { width: 14px; height: 14px; }
.swap__stop:hover { background: color-mix(in oklab, var(--fg) 8%, transparent); }
.swap__dots { display: flex; align-items: center; }
/* ô bấm 30×30 (≥ chiều cao cụm) nhưng dấu tròn chỉ 9px — "hình tròn nhỏ nhỏ"
   mà ngón tay vẫn trúng, không phải mục tiêu 9px */
.swap__dot { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; cursor: pointer; }
.swap__dot::after {
  content: ""; width: 10px; height: 10px; border-radius: 999px;
  background: transparent; border: 1.5px solid var(--dot-ring);
  transition: background var(--t-1), border-color var(--t-1), transform var(--t-1) var(--e-out);
}
.swap__dot[aria-pressed="true"]::after {
  background: var(--accent-dark); border-color: transparent; transform: scale(1.35);
}
.swap__stop:focus-visible, .swap__dot:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 1px; }

@media (prefers-reduced-motion: reduce) {
  /* không vòng nào — JS chuyển sang manual lúc khởi tạo nên dot 1 sáng đúng.
     Nút dừng bị ẩn: không có gì đang chạy để mà dừng. */
  [data-swap] .swap__i { animation: none; }
  .swap__stop { display: none; }
}

/* stars */
.stars {
  --rate: 5;
  --m: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 1l2.6 5.6 6.1.7-4.5 4.2 1.2 6L10 14.7 4.6 17.5l1.2-6L1.3 7.3l6.1-.7z'/></svg>");
  position: relative; display: inline-block; flex: 0 0 auto;
  width: 80px; height: 16px; background: var(--border);
  -webkit-mask-image: var(--m); mask-image: var(--m);
  -webkit-mask-size: 16px 16px; mask-size: 16px 16px;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
}
.stars::after { content: ""; position: absolute; inset: 0 auto 0 0; width: calc(var(--rate) / 5 * 100%); background: var(--star); }

/* ===== trust: thin promise bar =========================================
   Measured off ru9.vn own bar (`.information-bar-section`): 68px tall,
   padding 16px 0, no borders, 36x36 icon, 16px/400 label, one flex row centred,
   background rgb(255,251,233) — a pale cream, matched by --cream. No border,
   same as theirs: the band separates from the cool --bg by hue, not luminance
   (both sit at ~98% lightness).

   An earlier version promoted the trial to a filled accent pill and left the
   other two bare, which read as one finished item beside two unstyled ones.
   All three now share ONE recipe — tinted icon chip + label with its key token
   in accent — so the emphasis belongs to the band as a whole instead of to a
   single cell. Ranking inside the row comes from reading order, not colour. */
.trust {
  background: var(--sand);
  border-block: 1px solid color-mix(in oklab, var(--accent) 12%, var(--border));
}
/* Mobile: three fixed columns with the icon above a wrapping label. Letting the
   items sit side by side and wrap freely put each one on its own line — a 156px
   band, taller than anything this bar has been. Fixed columns keep it to 3
   equal cells and ~96px, and equal cells are also the clearest way to show the
   three promises rank the same. */
.trust ul {
  display: grid; grid-template-columns: repeat(3, 1fr);
  align-items: start; gap: 0; padding-block: 12px;
}
.trust li {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 4px 6px; text-align: center;
  font-size: 13.5px; color: var(--fg);
}
/* the one repeated unit: same size, same tint, same ring on all three.
   The tint alone is nearly invisible on this band — a 13% accent wash over
   white measured 1.04:1 against --sand, because both land at ~96% lightness.
   What makes the chip a chip is the ring at FULL accent: 3.2:1 against the
   band, i.e. it clears 1.4.11's 3:1 for a graphical object on its own. */
.trust__ico {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 99px;
  background: color-mix(in oklab, var(--accent) 16%, var(--surface));
  box-shadow: inset 0 0 0 1.5px var(--accent);
  color: var(--accent-dark);
}
.trust__ico svg { width: 17px; height: 17px; }
.trust__t {
  font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em;
  line-height: 1.3; text-wrap: balance;
}
/* the accent lands on the value in every item — 100 / 10 / Miễn phí — so the
   colour is a consistent role, not a decoration on one cell */
.trust__t b { font-weight: 700; color: var(--accent-dark); }

/* 700: below this the three labels side by side overflow and wrap; above it
   they fit on one line with room to spare (652px of track for 558px of item). */
@media (min-width: 700px) {
  .trust ul { display: flex; align-items: center; justify-content: center; gap: 6px; }
  .trust li { flex-direction: row; gap: 10px; padding: 5px 10px; font-size: 15px; text-align: left; }
  .trust__t { white-space: nowrap; }
}
/* 880: three even columns with hairline dividers — what makes the trio read as
   one designed strip rather than three things that happen to be near each other. */
@media (min-width: 880px) {
  .trust ul { display: grid; grid-template-columns: repeat(3, auto); justify-content: center; gap: 0; }
  .trust li { font-size: 16px; padding: 4px clamp(20px, 3.4vw, 42px); }
  .trust li + li { border-left: 1px solid color-mix(in oklab, var(--fg) 13%, var(--sand)); }
  .trust__ico { width: 38px; height: 38px; }
  .trust__ico svg { width: 21px; height: 21px; }
}

/* ===== feature rows: big image one side, headline + 3 labelled points
   the other, alternating direction. Structural pattern only — tokens, type
   and copy stay ours. ==================================================== */
.frow { display: grid; gap: 32px; align-items: center; }
@media (min-width: 900px) {
  /* 4-6 with the copy first, 6-4 when the image leads — the image keeps 60%
     either way. Media is already first in the DOM on flipped rows, so no
     `order` juggling is needed. */
  .frow { grid-template-columns: minmax(0, 4fr) minmax(0, 6fr); gap: clamp(40px, 5vw, 76px); }
  .frow--flip { grid-template-columns: minmax(0, 6fr) minmax(0, 4fr); }
}
.frow__copy { min-width: 0; }
.frow h2 { font-size: clamp(26px, 2.9vw, 40px); text-wrap: balance; }
.frow__copy .lead { margin-top: 16px; }
.frow__pts { display: grid; gap: 24px; margin-top: clamp(26px, 3vw, 36px); }
/* 44px -> 28px: the column was sized for the 44px chip that used to sit here.
   With the chip gone a 26px icon floated in it, leaving 25px of dead space
   before the heading — measured, not guessed. 28px puts the real gap at ~16px. */
.frow__pt {
  display: grid; grid-template-columns: 28px minmax(0, 1fr);
  column-gap: 16px; align-items: start;
}
.frow__pt h3 { font-size: clamp(16px, 1.5vw, 19px); }
/* real SVG icons in a tinted tile — replaces the emoji, which sat on
   inconsistent baselines and rendered at different sizes per platform */
/* No chip, no accent. Measured: the green appeared 10 times as a solid fill, of
   which only 4 were clickable — five of the rest were these chips. With the fill
   and the accent colour gone the icon carries itself and the green is left to
   mean one thing: this is an action. */
.frow__ico {
  grid-row: 1 / span 2; display: grid; place-items: center;
  width: 28px; height: 28px; color: var(--fg);
}
.frow__ico svg { width: 26px; height: 26px; }
.frow__link { margin-top: clamp(22px, 3vw, 30px); }
.frow__pt p { margin-top: 6px; font-size: 15px; color: var(--muted); }
.frow__copy > .btn { margin-top: clamp(24px, 3vw, 32px); }
.frow__media { position: relative; min-width: 0; border-radius: var(--r-l); overflow: hidden; }
.frow__media img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.frow--whole .frow__media img { height: auto; aspect-ratio: auto; max-height: none; }
@media (min-width: 900px) {
  .frow__media img { aspect-ratio: 1; max-height: clamp(400px, 40vw, 560px); }
  /* a row carrying only two points leaves a ~220px vertical void against a
     560px image; shorten the image rather than pad the copy with filler */
  .frow--tight .frow__media img { max-height: clamp(330px, 30vw, 430px); }
  /* opt-in: giữ NGUYÊN ảnh, 0% bị cắt. Dùng cho hàng có ảnh ngang — ép nó vào
     khung vuông thì mất hai bên. height:auto để chính ảnh quyết định chiều cao,
     nên đúng ở mọi bề rộng và tự đúng nếu sau này đổi ảnh khác tỉ lệ. */
  .frow--whole .frow__media img { height: auto; aspect-ratio: auto; max-height: none; }
}
/* badge pinned to one corner, fully inside the image, on a real surface */
.frow__badge {
  position: absolute; right: 18px; bottom: 18px; z-index: 2;
  display: grid; gap: 1px; padding: 13px 17px; text-align: center;
  background: oklch(100% 0 0 / .93); backdrop-filter: blur(8px);
  border-radius: var(--r); box-shadow: 0 8px 26px -8px oklch(20% 0.02 240 / .35);
}
.frow__badge b { font-family: var(--font-display); font-size: 24px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; }
.frow__badge span { font-size: 12px; color: var(--muted); }

/* ===== vs old mattress ================================================= */
.vs { display: grid; gap: 14px; }
@media (min-width: 780px) { .vs { grid-template-columns: 1fr 1fr; gap: 20px; } }
.vs__col { padding: 26px; border-radius: var(--r-l); }
.vs__col--old { background: oklch(100% 0 0 / .06); border: 1px dashed oklch(100% 0 0 / .26); }
.vs__col--new { background: var(--on-deep); color: var(--deep); box-shadow: 0 16px 40px -18px oklch(0% 0 0 / .5); }
.vs__col--new h3 { color: var(--deep); }
.vs__tag { font-family: var(--font-display); font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
.vs__col--old .vs__tag { color: var(--on-deep-mid); }
.vs__col--new .vs__tag { color: var(--muted); }
.vs__col h3 { margin: 10px 0 18px; }
.vs__col li { position: relative; padding: 9px 0 9px 28px; font-size: 15.5px; }
.vs__col li + li { border-top: 1px solid oklch(50% 0.018 240 / .18); }
.vs__col--old li { color: var(--on-deep-mid); }
.vs__col li::before {
  content: ""; position: absolute; left: 0; top: 17px; width: 16px; height: 16px; border-radius: 99px;
}
.vs__col--old li::before { background: oklch(100% 0 0 / .14); box-shadow: inset 0 0 0 1.5px oklch(100% 0 0 / .34); }
.vs__col--new li::before { background: var(--accent); }
.vs__col--new li::after {
  content: ""; position: absolute; left: 4.5px; top: 22px; width: 7px; height: 4px;
  border-left: 2px solid oklch(100% 0 0); border-bottom: 2px solid oklch(100% 0 0); rotate: -45deg;
}

/* ===== trial =========================================================== */
/* --- 100 đêm ngủ thử ---------------------------------------------------
   Đầu khối là 2 cột riêng (tiêu đề+dẫn nhập | thẻ cam kết), KHÔNG dùng
   .sec__head vì rule đó tách tiêu đề và lead thành 2 cột khi có .lead — ở đây
   lead phải nằm ngay dưới tiêu đề. */
.trial__top { display: grid; gap: clamp(24px, 3vw, 40px); margin-bottom: clamp(38px, 4.6vw, 62px); }
@media (min-width: 900px) {
  .trial__top { grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr); align-items: center; }
}
.trial__head .eyebrow { display: block; margin-bottom: 14px; }
.trial__head h2 { max-width: 18ch; }
.trial__hl { color: var(--accent-dark); }
.trial__head .lead { margin-top: 16px; max-width: 46ch; }

.trial__vow {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(16px, 2vw, 24px); align-items: center;
  padding: clamp(18px, 2.2vw, 26px); background: var(--sand); border-radius: var(--r-l);
}
.trial__vow p { font-size: 15.5px; color: var(--muted); }
/* Khiên: bo góc bất đối xứng chứ KHÔNG dùng clip-path: path() — path() nhận toạ
   độ px cố định nên không co giãn theo clamp(). */
.trial__shield {
  display: grid; place-items: center; gap: 3px; flex: 0 0 auto;
  width: clamp(62px, 5.6vw, 76px); aspect-ratio: .9;
  background: var(--deep); color: var(--on-deep);
  border-radius: 14px 14px 46% 46% / 14px 14px 62% 62%;
}
.trial__shield b { font-family: var(--font-display); font-size: clamp(17px, 1.7vw, 21px); font-weight: 800; line-height: 1; }
.trial__shield svg { width: 14px; height: 14px; }

.trial {
  --tdisc: clamp(68px, 7.4vw, 104px);
  --tgap: clamp(18px, 2.4vw, 34px);
  display: grid; gap: clamp(28px, 3vw, 36px) var(--tgap);
  margin: 0; padding: 0; list-style: none;
}
@media (min-width: 560px) and (max-width: 859px) { .trial { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 860px) { .trial { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.trial__s { position: relative; min-width: 0; }
.trial__ico {
  display: grid; place-items: center;
  width: var(--tdisc); height: var(--tdisc); border-radius: 999px;
  background: var(--sand); color: var(--accent-dark);
}
.trial__ico svg { width: 42%; height: 42%; }
.trial__n {
  display: block; margin-top: clamp(15px, 1.7vw, 23px);
  font-family: var(--font-display); font-size: 14px; font-weight: 700; color: var(--accent-dark);
}
.trial__s h3 { margin-top: 5px; font-size: clamp(17px, 1.5vw, 20px); line-height: 1.22; }
.trial__s p { margin-top: 10px; font-size: 14.5px; color: var(--muted); }

/* Đường nối chỉ có nghĩa khi 4 bước nằm CÙNG một hàng — dưới 860px chúng xếp
   2 cột hoặc 1 cột nên đường ngang sẽ trỏ vào chỗ trống. */
.trial__link { display: none; }
@media (min-width: 860px) {
  .trial__link {
    display: block; position: absolute; z-index: 1; height: 26px;
    top: calc(var(--tdisc) / 2); translate: 0 -50%;
    left: calc(var(--tdisc) + 12px);
    /* right âm để vươn qua khe grid tới sát đĩa của bước kế tiếp */
    right: calc(12px - var(--tgap));
  }
  .trial__link::before {
    content: ""; position: absolute; inset-inline: 0; top: 50%;
    border-top: 2px dotted color-mix(in oklab, var(--fg) 26%, transparent);
  }
  .trial__chev {
    position: absolute; left: 50%; top: 50%; translate: -50% -50%;
    display: grid; place-items: center; width: 26px; height: 26px;
    border-radius: 999px; background: var(--accent-dark); color: oklch(100% 0 0);
  }
  .trial__chev svg { width: 12px; height: 12px; }
}
.trial__n { font-family: var(--font-display); font-size: 12px; font-weight: 700; letter-spacing: .12em; color: var(--accent-dark); }
.trial__s h3 { margin-top: 12px; font-size: 19px; }
.trial__s p { margin-top: 8px; font-size: 15px; color: var(--muted); }

/* ===== reviews: tall portrait + tagged review cards ==================== */
/* one section runs to a wider measure: a tall image plus three readable cards
   does not fit inside the 1200px column the rest of the page uses */
.wrap--wide { max-width: 1440px; }
.rev { display: grid; gap: clamp(28px, 4vw, 44px); }
.rev > * { min-width: 0; }
.rev__media { position: relative; border-radius: var(--r-l); overflow: hidden; }
/* stacked, the image is a wide crop with a ceiling — at 4/3 across a 1100px
   column it rendered 792px tall and swallowed the section */
.rev__media img { width: 100%; height: auto; }
.rev__body .eyebrow { display: block; margin-bottom: 14px; }
/* smaller than the page's default h2: it sits beside cards, not above a whole
   section, and the accent line needs to stay a highlight rather than a shout */
.rev__body h2 { max-width: 22ch; font-size: clamp(26px, 3.1vw, 42px); }
.rev__body h2 i { font-style: normal; color: var(--fg); opacity: .55; }
.rev__agg { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 18px; font-size: 15px; color: var(--muted); }
.rev__agg b { font-family: var(--font-display); font-weight: 700; color: var(--fg); }

.rev__cards { display: grid; gap: 14px; margin-top: clamp(26px, 3vw, 34px); }
.rc {
  display: grid; gap: 13px; align-content: start; min-width: 0;
  padding: 20px; margin: 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: 0 2px 6px -3px oklch(20% 0.02 240 / .1);
}
.rc__tag {
  justify-self: start; padding: 5px 11px;
  font-family: var(--font-display); font-size: 12.5px; font-weight: 600;
  color: var(--on-deep); background: var(--deep); border-radius: 8px;
}
/* the star row reuses the .stars mask at track colour only: it marks where a
   rating goes without asserting one. The card's own "chờ dữ liệu" caption
   keeps it from reading as a genuine zero-star review. */
.rc__stars {
  --m: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 1l2.6 5.6 6.1.7-4.5 4.2 1.2 6L10 14.7 4.6 17.5l1.2-6L1.3 7.3l6.1-.7z'/></svg>");
  display: block; width: 80px; height: 16px; background: var(--border);
  -webkit-mask-image: var(--m); mask-image: var(--m);
  -webkit-mask-size: 16px 16px; mask-size: 16px 16px;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
}
.rc__skel { display: grid; gap: 9px; }
.rc__skel span { height: 11px; border-radius: 99px; background: var(--bg); border: 1px dashed var(--border); }
.rc__by {
  display: grid; gap: 3px; padding-top: 13px; border-top: 1px solid var(--border);
  font-size: 14px; font-weight: 600; color: var(--fg);
}
.rc__by span { font-family: var(--font-display); font-size: 11.5px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }

@media (min-width: 620px) {
  .rev__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* three cards in two columns leaves a lone card beside dead space — let the
     last one run the full width instead of hanging */
  .rc:nth-child(3):last-child { grid-column: 1 / -1; }
}
@media (min-width: 980px) {
  .rev__cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
  .rc:nth-child(3):last-child { grid-column: auto; }
}
@media (min-width: 1200px) {
  /* the image only goes beside the cards once three of them stay readable.
     It stretches to the copy's full height rather than floating at 288px in a
     tall empty column, so the crop is genuinely tall and the row edges line up. */
  .rev { grid-template-columns: minmax(0, 11fr) minmax(0, 13fr); gap: clamp(36px, 3.6vw, 56px); align-items: center; }
  /* Ảnh KHÔNG bị cắt ở bất kỳ bề rộng nào — xem quy tắc ở .rev__media img.
     Đánh đổi đã chọn có ý thức: ảnh không cao bằng cột chữ nữa (ảnh nguồn là
     ảnh ngang 1402×1122), nên có dải trống dưới ảnh trên trang sản phẩm.
     Người dùng chọn "giữ nguyên chi tiết" thay vì "lấp kín cột".
     Kiểm tự động: RECON/check-crop.mjs (phải ra 0–100% cả x và y). */
  /* không override gì ở đây: ảnh giữ tỉ lệ gốc ở MỌI bề rộng, 0% bị cắt */
}

/* ===== note: honest empty-state callout (restored — it sat inside the deleted
   buy-block boundary but is still used by the reviews section) ============ */
.note {
  display: flex; gap: 11px; max-width: 68ch; margin-top: 34px; padding: 15px 17px;
  background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--star);
  border-radius: var(--r); font-size: 14px; color: var(--muted);
}
.note svg { flex: 0 0 auto; margin-top: 3px; color: var(--star); }
.sec--deep .note { background: oklch(100% 0 0 / .07); border-color: oklch(100% 0 0 / .18); color: var(--on-deep-mid); }

/* ===== faq ============================================================= */
.faq { border-top: 1px solid var(--border); }
.faq__i { border-bottom: 1px solid var(--border); }
.faq__q {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 22px 0; text-align: left;
  font-family: var(--font-display); font-size: clamp(17px, 1.7vw, 20px); font-weight: 600; letter-spacing: -0.02em;
}
.faq__q svg { flex: 0 0 auto; color: var(--muted); transition: transform .22s, color .22s; }
.faq__q[aria-expanded="true"] svg { transform: rotate(45deg); color: var(--accent); }
.faq__a { padding-bottom: 22px; max-width: 70ch; font-size: 16px; color: var(--muted); }
.faq__a[hidden] { display: none; }

/* ===== sticky buy bar ================================================== */
.bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  display: flex; align-items: center; gap: 16px;
  padding: 12px var(--pad); background: oklch(100% 0 0 / .93);
  backdrop-filter: blur(14px); border-top: 1px solid var(--border);
  translate: 0 110%; transition: translate .3s cubic-bezier(.2,.8,.3,1);
}
.bar[data-on="true"] { translate: 0 0; }
.bar__i { display: none; align-items: center; gap: 12px; min-width: 0; }
.bar__i img { width: 46px; height: 46px; object-fit: cover; border-radius: 9px; }
.bar__t { min-width: 0; }
.bar__t b { display: block; font-family: var(--font-display); font-size: 15px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar__t span { font-size: 13px; color: var(--muted); }
.bar__p { margin-left: auto; font-family: var(--font-display); font-size: 19px; font-weight: 700; letter-spacing: -0.02em; white-space: nowrap; }
.bar .btn { flex: 0 0 auto; }
@media (min-width: 700px) { .bar__i { display: flex; } }
@media (max-width: 699px) { .bar { gap: 10px; } .bar__p { margin-left: 0; } .bar .btn { flex: 1; } }

/* ===== footer ========================================================== */
.foot { background: var(--deep); color: var(--on-deep); padding-block: 60px 30px; padding-bottom: 96px; }
.foot a { color: var(--on-deep-mid); }
.foot a:hover { color: var(--on-deep); }
.foot__g { display: grid; gap: 30px; }
@media (min-width: 760px) { .foot__g { grid-template-columns: 1.7fr 1fr 1fr; gap: 44px; } }
.foot .mark { color: var(--on-deep); }
.foot p { font-size: 14.5px; color: var(--on-deep-mid); }
.foot__t { margin-bottom: 12px; font-family: var(--font-display); font-size: 12px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: oklch(100% 0 0 / .5); }
.foot li { padding: 4px 0; font-size: 14.5px; }
.foot__b { margin-top: 42px; padding-top: 20px; border-top: 1px solid oklch(100% 0 0 / .16); font-size: 13px; color: oklch(100% 0 0 / .5); }
.news { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; max-width: 420px; }
.news input {
  flex: 1 1 180px; min-height: 50px; padding: 12px 16px; font: inherit; font-size: 15.5px;
  color: var(--on-deep); background: oklch(100% 0 0 / .08);
  border: 1px solid oklch(100% 0 0 / .22); border-radius: 99px;
}
.news input::placeholder { color: oklch(100% 0 0 / .45); }
.news input[aria-invalid="true"] { border-color: var(--star); }
.news__m { flex: 1 0 100%; min-height: 16px; font-size: 13.5px; }
.news__m[data-s="ok"] { color: oklch(82% 0.11 170); }
.news__m[data-s="err"] { color: var(--star); }

/* ===== drawer ========================================================== */
.drawer {
  position: fixed; inset: 0 auto 0 0; z-index: 200; width: min(86vw, 340px);
  background: var(--surface); translate: -100% 0; visibility: hidden;
  transition: translate .3s cubic-bezier(.2,.8,.3,1), visibility 0s linear .3s;
  display: flex; flex-direction: column;
}
.drawer[data-open="true"] { translate: 0 0; visibility: visible; transition: translate .3s cubic-bezier(.2,.8,.3,1), visibility 0s; }
.drawer__t { display: flex; align-items: center; justify-content: space-between; padding: 14px var(--pad); border-bottom: 1px solid var(--border); }
.drawer__b { padding: 12px var(--pad) 30px; }
.drawer__b a { display: block; padding: 14px 0; font-family: var(--font-display); font-size: 17px; font-weight: 600; border-bottom: 1px solid var(--border); }
.scrim { position: fixed; inset: 0; z-index: 190; background: oklch(26% 0.035 210 / .5); opacity: 0; visibility: hidden; transition: opacity .3s, visibility .3s; }
.scrim[data-open="true"] { opacity: 1; visibility: visible; }
[inert] { pointer-events: none; }
body[data-lock="true"] { overflow: hidden; }

/* ===== image states ==================================================== */
.ph { position: relative; }
.ph.is-loading::before {
  content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit;
  background: linear-gradient(100deg, var(--accent-tint) 20%, oklch(92% 0.03 170) 40%, var(--accent-tint) 60%);
  background-size: 240% 100%; animation: sh 1.2s linear infinite;
}
@keyframes sh { from { background-position: 180% 0; } to { background-position: -80% 0; } }
.ph.is-loading img { opacity: 0; }
.ph img { transition: opacity .3s; }
.ph.is-broken::before {
  content: "Không tải được ảnh"; position: absolute; inset: 0; z-index: 1;
  display: grid; place-items: center; padding: 10px; text-align: center;
  font-size: 13px; color: var(--muted); background: var(--bg);
  border: 1px dashed var(--border); border-radius: inherit;
}

/* ===== reveal (staggerable) ============================================
   Hidden initial states are scoped to html.js — set by an inline script — so
   that if the JS never runs, every section is simply visible instead of
   permanently transparent.
   ====================================================================== */
.js .rv {
  opacity: 0; transform: translateY(22px);
  transition: opacity var(--t-2) var(--e-out), transform var(--t-2) var(--e-out);
  transition-delay: calc(var(--i, 0) * 85ms);
}
.js .rv[data-seen="true"] { opacity: 1; transform: none; }

/* ========================================================================
   Motion
   Everything below is additive polish and is switched off wholesale by
   prefers-reduced-motion at the end of this block.
   ======================================================================== */

/* --- hero entrance: words rise, then the supporting lines follow -------- */
.hero h1 .w { display: inline-block; will-change: transform, opacity; }
@keyframes wordUp {
  from { opacity: 0; transform: translateY(.55em); filter: blur(7px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
@keyframes riseIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
@keyframes mediaIn {
  from { opacity: 0; transform: scale(1.07); }
  to   { opacity: 1; transform: none; }
}
@keyframes badgePop {
  0%   { opacity: 0; transform: translateY(10px) scale(.92); }
  100% { opacity: 1; transform: none; }
}
.hero h1 .w { animation: wordUp .85s var(--e-out) both; animation-delay: calc(120ms + var(--i, 0) * 62ms); }
.hero__col > *:not(h1) { animation: riseIn var(--t-2) var(--e-out) both; }
.hero__col > .eyebrow      { animation-delay: 60ms; }
.hero__col > .hero__lead   { animation-delay: 560ms; }
.hero__col > .hero__rating { animation-delay: 640ms; }
.hero__col > .hero__price  { animation-delay: 710ms; }
.hero__col > .hero__cta    { animation-delay: 790ms; }
.hero__col > .hero__ship   { animation-delay: 860ms; }
.hero__media > img, .hero__media > .swap { animation: mediaIn var(--t-3) var(--e-out) both; animation-delay: 180ms; }
.hero__badge { animation: badgePop var(--t-2) var(--e-out) both; animation-delay: 1s; }

/* --- hero parallax, scroll-linked where the browser supports it -------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes heroDrift { to { transform: translateY(-4%) scale(1.05); } }
    .hero__media > img, .hero__media > .swap {
      animation: mediaIn var(--t-3) var(--e-out) both, heroDrift linear both;
      animation-delay: 180ms, 0s;
      animation-timeline: auto, view();
      animation-range: normal, entry 0% exit 100%;
    }
  }
}

/* --- nav shows its wordmark settling in -------------------------------- */
.nav .wrap > * { animation: riseIn var(--t-1) var(--e-out) both; }
.nav .wrap > *:nth-child(2) { animation-delay: 40ms; }
.nav .wrap > *:nth-child(3) { animation-delay: 90ms; }
.nav .wrap > *:nth-child(4) { animation-delay: 140ms; }

/* --- trust strip: cells rise in sequence on first view ---------------- */
.js .trust li { opacity: 0; transform: translateY(14px); transition: opacity var(--t-2) var(--e-out), transform var(--t-2) var(--e-out); }
.js .trust[data-seen="true"] li { opacity: 1; transform: none; }
.trust li:nth-child(2) { transition-delay: 90ms; }
.trust li:nth-child(3) { transition-delay: 180ms; }

/* --- firmness pin travels to its position ------------------------------ */
.js .firm__bar { --p: 0%; }
.js .firm__bar[data-seen="true"] { --p: 85%; }
.firm__bar[data-seen="true"] .firm__flag { opacity: 1; }

/* --- gallery: View Transitions for the main image swap ---------------- */
#gal-main { view-transition-name: galmain; }
::view-transition-old(galmain), ::view-transition-new(galmain) { animation-duration: .4s; }

/* --- vs columns: the "after" column lifts in ------------------------- */
.js .vs__col { opacity: 0; transform: translateY(20px); transition: opacity var(--t-2) var(--e-out), transform var(--t-2) var(--e-out); }
.js .vs[data-seen="true"] .vs__col { opacity: 1; transform: none; }
.vs[data-seen="true"] .vs__col--new { transition-delay: 140ms; }
.vs__col li { transition: transform var(--t-1) var(--e-out); }
.vs__col--new li:hover { transform: translateX(4px); }

/* --- CTA arrow nudge --------------------------------------------------- */
.btn svg { transition: transform var(--t-1) var(--e-out); }
.btn:hover svg { transform: translateX(3px); }

/* --- add-to-cart success ring ----------------------------------------- */
@keyframes ring {
  from { box-shadow: 0 0 0 0 oklch(56% 0.12 170 / .45); }
  to   { box-shadow: 0 0 0 16px oklch(56% 0.12 170 / 0); }
}
.btn[data-s="done"] { animation: ring .7s var(--e-out); }

/* --- counters hold their box while animating -------------------------- */
[data-to] { font-variant-numeric: tabular-nums; }

/* --- anchor targets clear the sticky nav ------------------------------- */
:where(#cau-tao, #do-cung, #mua, #ngu-thu) { scroll-margin-top: calc(var(--nav) + 12px); }

/* The firmness value sits at 85%, so a flag centred on the pin runs past the
   right end of a narrow bar. Below 700px anchor it to the bar's right edge and
   drop the pointer — the pin directly beneath still carries the connection.
   Placed after the `left: var(--p)` rule so it wins on source order. */
@media (max-width: 700px) {
  .firm__flag { left: auto; right: 0; translate: 0; }
  .firm__flag::after { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rv, .trust li, .lay__stack .lay__row, .vs__col { opacity: 1; transform: none; transition: none; }
  .firm__flag { opacity: 1; }
  .firm__bar { --p: 85%; }
  .hero h1 .w, .hero__col > *, .hero__media > img, .hero__media > .swap, .hero__badge, .nav .wrap > *,   * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
