/* ============================================================
   Dreame NAVO — IB Store
   Design language follows Dreame's own NAVO 「Golden Ring」:
   Deep Obsidian Black + Soft Mist Grey + polished gold accent.
   ============================================================ */

/* ---------- Fonts (self-hosted; no external requests / GDPR-safe) ----------
   Golos Text, not Inter. Paratype designed it with Cyrillic as a primary script
   rather than an adaptation, so Bulgarian is genuinely better set — and it is not
   the face every AI-generated UI defaults to. 104 KB across four subsets against
   Inter's 180 KB, with a wider 400-900 axis. */
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../assets/fonts/golos-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../assets/fonts/golos-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../assets/fonts/golos-latin.woff2') format('woff2');
  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;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../assets/fonts/golos-latin-ext.woff2') format('woff2');
  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;
}

/* ---------- Design tokens ---------- */
:root {
  /* Brand — sampled from Dreame's own NAVO deck */
  --obsidian:      #0A0A0B;
  --obsidian-2:    #131315;
  --obsidian-3:    #1C1C1F;
  --mist:          #F5F4F1;   /* "Soft Mist Grey" */
  --mist-2:        #EAE8E3;
  --white:         #FFFFFF;

  --gold:          #C9A464;   /* polished gold ring */
  --gold-bright:   #E8CFA0;
  --gold-deep:     #8A6A3B;
  /* Display gradient — for LARGE text only.
     Runs mid → light → mid: a highlight catching the middle, like light across
     polished metal. It deliberately never touches --gold-deep, which measures
     3.96:1 on obsidian — so the old dark→light ramp made the FIRST words of the
     headline the least legible ones. Minimum across this ramp is 8.46:1. */
  --gold-grad:     linear-gradient(100deg, var(--gold), var(--gold-bright) 55%, var(--gold));
  /* Surface gradient — for filled buttons/badges. Stays in the light half of the
     ramp so dark text keeps >8:1 contrast across the whole face, and reads like
     a polished metal surface rather than a diagonal wedge. */
  --gold-surface:  linear-gradient(180deg, var(--gold-bright), var(--gold));

  /* Text */
  --ink:           #16161A;
  --ink-2:         #56545166;
  --text-on-light:      #1A1A1D;
  --text-on-light-soft: #5C5A57;
  --text-on-dark:       #FFFFFF;
  --text-on-dark-soft:  #A8A5A0;

  /* Status — split by surface. The single-value pair failed AA on light grounds:
     #1FA463 measured 2.92:1 on mist and #D9534F 3.96:1 on white, both at 13.2px.
     The originals are kept for dark surfaces, where they already measure 6.17:1. */
  --success:       #1FA463;   /* on dark only */
  --alert:         #D9534F;   /* on dark only */
  --success-ink:   #157A48;   /* 4.62:1 on mist, 5.10:1 on white */
  --alert-ink:     #B3403C;   /* 5.02:1 on white, 4.55:1 on mist */

  /* Alpha ramps (pattern borrowed from Reolink's token set) */
  --b1: rgba(0,0,0,.04);  --b2: rgba(0,0,0,.08);  --b3: rgba(0,0,0,.16);
  --b5: rgba(0,0,0,.40);  --b7: rgba(0,0,0,.70);
  --w1: rgba(255,255,255,.06); --w2: rgba(255,255,255,.10);
  --w3: rgba(255,255,255,.18); --w5: rgba(255,255,255,.45);

  /* Fluid type scale */
  --step--1: clamp(0.82rem, 0.80rem + 0.10vw, 0.88rem);
  --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.13rem);
  --step-1:  clamp(1.20rem, 1.12rem + 0.42vw, 1.45rem);
  --step-2:  clamp(1.50rem, 1.32rem + 0.95vw, 2.15rem);
  --step-3:  clamp(1.90rem, 1.52rem + 1.90vw, 3.00rem);
  /* Retuned down from 4.25rem. The Display role only needs to clear Headline
     (--step-3) enough to read as primary — at 1440px that is 57px against 48px.
     The old top step made the headline 68px, which bought a louder hero at the
     cost of a 2.9:1 headline-to-body ratio; this holds the lead at 1.18:1 over
     a section heading while the body ratio stays near 2.2:1. */
  --step-4:  clamp(2.15rem, 1.60rem + 2.60vw, 3.55rem);

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

  /* Radius */
  --r-sm: 6px; --r-md: 12px; --r-lg: 20px; --r-xl: 28px; --r-pill: 999px;

  --shadow-md: 0 4px 24px rgba(0,0,0,.08);
  --shadow-lg: 0 18px 60px rgba(0,0,0,.14);

  --maxw: 1200px;
  --ease: cubic-bezier(.22,.61,.36,1);
}

/* ---------- Reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
/* Anchor jumps and keyboard focus both landed under the 64px sticky header. */
:target, [id] { scroll-margin-top: calc(64px + var(--s5)); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: 'Golos Text', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--text-on-light);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, picture, video { max-width: 100%; display: block; height: auto; }
h1, h2, h3, h4 { margin: 0 0 var(--s4); line-height: 1.14; font-weight: 700; letter-spacing: -0.02em; }
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }
p  { margin: 0 0 var(--s4); }
a  { color: inherit; }
/* ol included: .steps is an <ol> that renders its own numerals as .step__num,
   so native markers would double them up ("01  1."). */
ul, ol { margin: 0; padding: 0; list-style: none; }

:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; border-radius: var(--r-sm); }

.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--s5); }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- Sections ---------- */
.section { padding-block: clamp(var(--s8), 8vw, var(--s10)); }
.section--tight { padding-block: clamp(var(--s7), 5vw, var(--s8)); }
.section--light { background: var(--white); }
.section--mist  { background: var(--mist); }
.section--dark  { background: var(--obsidian); color: var(--text-on-dark); }
.section--dark h2, .section--dark h3 { color: var(--text-on-dark); }
.section--dark p { color: var(--text-on-dark-soft); }

.section-head { max-width: 62ch; margin-bottom: var(--s7); }
.section-head--center { margin-inline: auto; text-align: center; }

/* Solid, not gradient: at this size the bright end of the ramp washed out to
   ~1.6:1 on light sections. One colour per surface instead. */
.eyebrow {
  display: inline-block; font-size: var(--step--1); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; margin-bottom: var(--s3);
  color: var(--gold-deep);
}
.section--dark .eyebrow, .hero .eyebrow { color: var(--gold-bright); }
/* 1ch of Golos renders ~1.24 characters at this size, so 58ch lands ~72 per
   line. The one lead that overran was carrying an inline max-width:70ch (~87
   chars) that no stylesheet rule could reach; that override is gone. */
.lead { font-size: var(--step-1); color: var(--text-on-light-soft); max-width: 58ch; }
.section-head--center .lead { margin-inline: auto; }
.section--dark .lead { color: var(--text-on-dark-soft); }

/* ---------- Gold rule (the "ring" motif as a divider) ---------- */
.rule { height: 1px; border: 0; background: linear-gradient(90deg, transparent, var(--gold), transparent); margin: 0; }

/* ============================================================
   Announcement bar
   ============================================================ */
.announce {
  background: var(--obsidian-2); color: var(--text-on-dark);
  font-size: var(--step--1); text-align: center;
  padding: var(--s2) var(--s5); position: relative;
}
.announce strong { color: var(--gold-bright); font-weight: 600; }
.announce__close {
  position: absolute; inset-block: 0; right: var(--s3); background: none; border: 0;
  color: var(--w5); font-size: 20px; line-height: 1; cursor: pointer;
  /* 8px of side padding gave a 28px-wide target; 16px takes it to 44px. */
  padding-inline: var(--s4);
}
.announce__close:hover { color: var(--white); }

/* ============================================================
   Header — deliberately minimal (nav menus cost conversion)
   ============================================================ */
.header {
  position: sticky; top: 0; z-index: 60;
  background: rgba(10,10,11,.82);
  backdrop-filter: blur(14px) saturate(1.4);
  border-bottom: 1px solid var(--w1);
  color: var(--text-on-dark);
}
.header__inner { display: flex; align-items: center; gap: var(--s5); min-height: 64px; }
.brand {
  display: flex; align-items: center; gap: var(--s3);
  text-decoration: none; white-space: nowrap; flex: none;
  color: var(--text-on-dark);
}
.brand__mark { font-size: 15px; font-weight: 800; letter-spacing: .16em; }
.brand__navo { color: var(--gold-bright); }
/* Dreame makes these; we only sell them. "DREAMENAVO · IB STORE" read as one
   joint brand, so the seller stops being a suffix of the wordmark and becomes a
   discrete object — grammar and a hairline chip carry the relationship instead
   of a middot. */
/* 11px is a floor, not a preference: this is functional text inside a link, and
   letterspaced micro-labels are no exception. Tracking and padding are pulled in
   to pay for the extra width the larger size costs. */
.brand__seller {
  display: inline-flex; align-items: center; gap: .4em; position: relative;
  padding: 3px 7px; border: 1px solid var(--w2); border-radius: var(--r-sm);
  font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--white);
}
.brand__seller-pre { font-weight: 500; letter-spacing: .09em; color: var(--gold); }
/* The pre-label costs ~90px and the row is at its most crowded between 860px
   (where the nav is still shown) and ~1040px, where it pushes the CTA onto a
   second line. So it appears only above 1040px. Hidden visually but NOT removed:
   display:none would drop it from the accessible name too, leaving a screen
   reader with "DREAMENAVO IB Store" and restoring exactly the ambiguity this
   chip exists to remove. */
@media (max-width: 1040px) {
  .brand__seller-pre {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
}
/* Under ~440px the mark + chip + 24px gap exceed the row, and the header CTA
   wraps to two lines — the same failure the old "· IB STORE" suffix caused.
   Stacking gives the width back instead of shrinking the chip below the 11px
   floor, so phones keep the seller framing AND the pill keeps its 44px rhythm. */
@media (max-width: 440px) {
  .brand { flex-direction: column; align-items: flex-start; gap: 3px; }
}
/* Smallest phones: the stack alone no longer clears the CTA, so the wordmark
   gives up the last few px rather than the chip. */
@media (max-width: 380px) {
  .brand { gap: var(--s1); }
  .brand__mark { font-size: 13px; letter-spacing: .12em; }
}
.header__nav { display: flex; gap: var(--s5); margin-left: auto; font-size: var(--step--1); }
.header__nav a { text-decoration: none; color: var(--text-on-dark-soft); transition: color .2s var(--ease); }
.header__nav a:hover { color: var(--white); }
@media (max-width: 860px) { .header__nav { display: none; } }

/* ============================================================
   Buttons
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  font: inherit; font-weight: 650; text-decoration: none; cursor: pointer;
  padding: 14px 26px; border-radius: var(--r-pill); border: 1px solid transparent;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), background .18s var(--ease);
  line-height: 1.2;
}
/* Primary — must be the highest-contrast thing on screen */
.btn--primary {
  background: var(--gold-surface); color: #1A1408;
  box-shadow: 0 6px 20px rgba(201,164,100,.32);
}
.btn--primary:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(201,164,100,.44); }
.btn--ghost { background: transparent; border-color: var(--b3); color: var(--text-on-light); }
.section--dark .btn--ghost { border-color: var(--w3); color: var(--text-on-dark); }
.btn--ghost:hover { border-color: var(--gold); color: var(--gold-deep); }
.section--dark .btn--ghost:hover { color: var(--gold-bright); }
/* 44px floor: btn--sm carries actual buy actions (sticky bar, in-table CTAs),
   where 36px was below every touch-target guideline. */
.btn--sm { padding: 9px 18px; font-size: var(--step--1); min-height: 44px; }
.btn--block { width: 100%; }

/* ============================================================
   Hero — chooser, not a single product
   ============================================================ */
.hero {
  position: relative; background: var(--obsidian); color: var(--text-on-dark);
  padding-block: clamp(var(--s8), 9vw, 130px) clamp(var(--s8), 7vw, var(--s9));
  overflow: hidden;
}
/* Gold "ring" arcs — the deck's signature motif, drawn in CSS (no image weight) */
.hero__glow {
  position: absolute; pointer-events: none; border-radius: 50%;
  border: 1px solid transparent;
  background: conic-gradient(from 200deg, transparent 20%, rgba(201,164,100,.55), transparent 60%);
  mask: radial-gradient(circle, transparent 61%, #000 62%, #000 63%, transparent 64%);
  -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%, #000 63%, transparent 64%);
}
/* Pulled into frame. Previously both circles sat mostly off-canvas (one hung
   262px above and 230px past the right edge), so only fragments of arc showed
   and the motif read as a smudge rather than a ring. */
.hero__glow--1 { width: 1040px; aspect-ratio: 1; top: -46%; right: -4%;  opacity: .85; }
.hero__glow--2 { width: 720px;  aspect-ratio: 1; bottom: -30%; left: -8%; opacity: .45; }
.hero__inner { position: relative; z-index: 1; }
/* The hero centres its argument over the chooser band below it. */
.hero__head { text-align: center; }
/* Centred display type is fine; centred body copy is not. At 390px this paragraph
   runs six lines, and centring moves the left edge on every one of them. The
   headline stays centred, the prose goes ranged-left where it gets long. */
@media (max-width: 700px) {
  .hero__head .hero__sub { text-align: left; }
}
.hero__head .trust { justify-content: center; }
/* Carbonized from /impeccable live (typeset, variant 3 "Level" accepted).
   Neither size nor weight shouts: the display picks up 800 off the variable axis
   and the sub rises 10% and comes up from soft grey to full white, which carries
   the hierarchy on colour and space rather than on scale. The variant originally
   set the headline to --step-3, but that is also the h2 size, so the hero
   headline came out identical to every section heading; --step-4 was retuned
   instead, keeping the quiet body ratio while the hero stays primary.
   Size now comes from the global h1 rule — the role owns the token. */
.hero .eyebrow { letter-spacing: .16em; }
/* 24px, not the global 16px: under the headline a 16px gap reads as crowding */
.hero__title {
  font-weight: 800;
  letter-spacing: -0.024em; line-height: 1.14;
  max-width: 25ch; margin-inline: auto; margin-bottom: var(--s5);
}
.hero__title em {
  font-style: normal;
  background: var(--gold-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.hero__sub {
  font-size: calc(var(--step-1) * 1.1); line-height: 1.6;
  color: var(--text-on-dark); max-width: 52ch;
  margin-inline: auto; margin-bottom: var(--s6);
}

/* Trust row — sits above the fold, next to the CTAs */
/* Carbonized from /impeccable live (bolder, variant 1 accepted).
   The row previously opted out of every strong move the hero makes — smallest
   type, softest grey, no structure — while carrying the four facts that answer a
   first-time buyer's hesitation. It now joins the system's own gradient-divider
   motif and steps up to label type at full white. No new token was introduced. */
.trust {
  list-style: none; margin: var(--s6) 0 0; padding: var(--s5) 0 0;
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s7);
  /* the .rule divider motif, reused rather than invented */
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, var(--gold), rgba(201,164,100,.15)) 1;
}
.trust li {
  display: flex; align-items: center; gap: var(--s2);
  font-size: var(--step--1); font-weight: 600;
  letter-spacing: .10em; text-transform: uppercase;
  color: var(--text-on-dark);
}
.trust svg { flex: none; color: var(--gold); }
/* Four items free-wrapping at 375px landed 1-2-1, which reads as untidy rather
   than as a list. A fixed 2x2 is even and still fits the longest label.
   align-items:start so an uppercase label that wraps keeps its tick on line one. */
@media (max-width: 560px) {
  .trust { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3) var(--s4); }
  /* Uppercase is a desktop expression. In a 155px cell it pushed every one of the
     four labels onto a second line, doubling the row's height inside the hero for
     no legibility gain. Weight, white and the brass tick carry it here instead. */
  .trust li { text-transform: none; letter-spacing: .01em; align-items: start; }
  .trust svg { margin-top: 4px; }
}

/* Chooser cards */
/* A band, not three cards. Proximity and a hairline do the grouping the boxes
   were doing — which also stops the hero opening the page with the same card
   scaffold that three later sections already repeat. Explicit 3 columns rather
   than auto-fit: at ~780px auto-fit wrapped to 2 and left an orphan column
   carrying a divider on its left edge. */
.chooser {
  display: grid; gap: 0; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: clamp(var(--s7), 5vw, var(--s8));
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, rgba(201,164,100,.15), var(--gold), rgba(201,164,100,.15)) 1;
}
/* The column is a div, not one big link: it now holds two distinct actions —
   the name links to the full section, the button buys. A single card-swallowing
   anchor could not contain a nested <a>, and the band is where warm ad traffic
   should be able to buy without scrolling four screens first. */
.choice {
  display: flex; flex-direction: column; gap: var(--s3);
  padding: var(--s6) var(--s5);
  border-inline-start: 1px solid var(--w1);
  transition: box-shadow .22s var(--ease);
}
.choice:first-child { border-inline-start: 0; }
/* The column lights from its top edge instead of lifting: there is no box to
   lift, and a transform would drag the divider with it. */
.choice:hover { box-shadow: inset 0 2px 0 0 var(--gold); }
.choice__name a {
  color: inherit; text-decoration: none;
  /* The line box alone measured 24px — exactly the 2.5.8 AA floor, with nothing
     to spare. This is the column's secondary action, so it gets clear of it. */
  display: inline-block; padding-block: 4px;
}
.choice__name a:hover,
.choice__name a:focus-visible { color: var(--gold-bright); }
/* Pushed to the bottom of every column by .choice__desc's flex:1, so the three
   buttons align across the band regardless of description length.
   The glow is suppressed here on purpose. These keep the filled brass surface —
   the loudest treatment the system has — but three light sources in one row is
   what The Only Glow Rule exists to prevent: it would cost the in-section buy
   button the attention monopoly that makes the glow mean anything. Brass surface
   reads as a price list; brass light reads as one destination. */
.choice__buy {
  align-self: start; margin-top: var(--s2);
  box-shadow: none;
}
.choice__buy:hover, .choice__buy:focus-visible { box-shadow: none; }
@media (max-width: 700px) {
  .chooser { grid-template-columns: 1fr; }
  .choice { border-inline-start: 0; border-top: 1px solid var(--w1); padding-inline: 0; }
  .choice:first-child { border-top: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .choice, .choice__name { transition: none; }
}
/* The three renders came back with different black points — #101010, #0a0a0a and
   #000000 — against a #0A0A0B page, so as flat rectangles they would read as three
   faintly mismatched tones. Feathering the edges removes the boundary altogether,
   which is cheaper and more robust than trying to match the pixels. It also lets
   the hero's brass glow keep passing behind the band. */
.choice__shot {
  width: min(100%, 200px); height: auto; aspect-ratio: 1;
  object-fit: contain;
  margin-bottom: var(--s3);
  -webkit-mask-image: radial-gradient(closest-side circle at 50% 50%, #000 62%, transparent 97%);
  mask-image: radial-gradient(closest-side circle at 50% 50%, #000 62%, transparent 97%);
}
@media (max-width: 700px) {
  /* single column: the shot sits beside nothing, so cap it harder */
  .choice__shot { width: min(100%, 164px); }
}

/* ---------- Hero photographic ground ----------
   Desktop only, and deliberately: below 900px `cover` crops a 16:9 frame down to
   a ~17% vertical slice that communicates nothing, and this traffic is mobile-heavy
   paid social where the bytes matter more than the mood. Phones keep flat obsidian
   and never fetch the file — a CSS background inside the media query, not a
   <picture>, because a <picture> fallback src downloads regardless. */
.hero__bg { display: none; }
@media (min-width: 900px) {
  .hero__bg {
    display: block; position: absolute; inset: 0; z-index: 0;
    background: url('../assets/img/hero-night.webp?v=1') center / cover no-repeat;
  }
  @media (min-width: 1500px) {
    .hero__bg { background-image: url('../assets/img/hero-night-2600.webp?v=1'); }
  }
  /* The scrim is measured, not guessed. The lit parapet peaks at luma 213 and the
     doorway at 246, and both land under centred display type. At 82% obsidian the
     brightest point resolves to RGB(46,46,46): white holds 12.6:1 and the soft-grey
     sub-head 5.5:1, while the doorway still reads as a warm glow rather than being
     flattened to black. */
  .hero__bg::after {
    content: ""; position: absolute; inset: 0;
    background: rgba(10, 10, 11, .82);
  }
}

/* Not an eyebrow. This is a scan key — it lets someone flick down three columns
   and find their situation without reading sentences — so it has a real job and
   should look like it has one. Tracked uppercase was the page's default costume
   for "label" (21 elements wore it across 5 classes, and this was nearly
   indistinguishable from .eyebrow at the same size and weight). Colour and
   position carry the hierarchy now; the letterspacing was carrying nothing. */
.choice__where {
  font-size: var(--step--1); font-weight: 650;
  letter-spacing: .01em; color: var(--gold);
}
.choice__name { font-weight: 700; font-size: var(--step-1); color: var(--white); letter-spacing: -.01em; transition: color .22s var(--ease); }
.choice__desc { font-size: var(--step--1); color: var(--text-on-dark-soft); flex: 1; }
.choice__price { font-weight: 700; font-size: var(--step-1); color: var(--white); }
.choice__price span { font-weight: 400; font-size: var(--step--1); color: var(--text-on-dark-soft); }

/* ============================================================
   Claim strip ("Why NAVO")
   ============================================================ */
/* 4 items: 1 → 2 → 4 columns. auto-fit would give 3 + an orphan at tablet widths. */
.claims { display: grid; gap: var(--s5); grid-template-columns: 1fr; }
@media (min-width: 560px)  { .claims { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .claims { grid-template-columns: repeat(4, 1fr); } }
.claim { display: flex; flex-direction: column; gap: var(--s2); }
.claim__icon {
  width: 44px; height: 44px; display: grid; place-items: center; margin-bottom: var(--s2);
  border-radius: var(--r-md); background: rgba(201,164,100,.14); color: var(--gold-deep);
}
.section--dark .claim__icon { background: rgba(201,164,100,.16); color: var(--gold-bright); }
.claim__title { font-weight: 650; }
.claim__body { font-size: var(--step--1); color: var(--text-on-light-soft); margin: 0; }
.section--dark .claim__body { color: var(--text-on-dark-soft); }

/* ============================================================
   Product blocks
   ============================================================ */
.product { display: grid; gap: clamp(var(--s6), 5vw, var(--s8)); align-items: center; }
@media (min-width: 900px) {
  .product { grid-template-columns: 1.05fr .95fr; }
  .product--flip .product__media { order: 2; }
}
.product__media { position: relative; }

/* Pin the product shot while the feature list scrolls past it, so the section
   never becomes a wall of text with the visual left behind.
   - align-self:start is REQUIRED: the grid default (stretch) makes the item
     full-height, and a full-height item can never stick.
   - min-height guard: on short viewports there isn't room to pin anything
     without the image overflowing, so it just scrolls normally. */
@media (min-width: 900px) and (min-height: 640px) {
  .product { align-items: start; }
  .product__media {
    position: sticky;
    top: calc(64px + var(--s5));   /* clears the sticky header */
    align-self: start;
  }
  .product__media img { max-height: calc(100vh - 64px - var(--s7)); object-fit: contain; }
}
.product__media img {
  width: 100%; border-radius: var(--r-xl);
  background: var(--mist);            /* renders sit on light bg — matches their own backdrop */
  box-shadow: var(--shadow-md);
}
.section--dark .product__media img { box-shadow: 0 20px 60px rgba(0,0,0,.5); }

/* --s5, not --s4: with the category eyebrow gone the badges sit directly on the
   product name and 16px was too tight under a --step-2 heading. */
.badge-row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s5); }
.badge {
  font-size: var(--step--1); font-weight: 600; padding: 5px 12px; border-radius: var(--r-pill);
  background: rgba(201,164,100,.16); border: 1px solid rgba(201,164,100,.32);
  /* Brass Deep over a 16% brass tint measured 4.06-4.41:1. The tint alone
     carries the brass; the text takes ink so the badge clears AA. */
  color: var(--text-on-light);
}
.section--dark .badge { color: var(--gold-bright); }
/* Flat, not gradient — 12px letterspaced caps needs one predictable background.
   The `.section--dark .badge--award` selector is REQUIRED, not redundant: the
   `.section--dark .badge` rule above is 2 classes and would otherwise win the
   colour, painting gold text on this gold background (1:1 — invisible). */
.badge--award,
.section--dark .badge--award {
  background: var(--gold-bright); color: #16110A;
  border-color: transparent; font-weight: 700;
}

.product__title { margin-bottom: var(--s3); }
.product__tag { font-size: var(--step-1); color: var(--text-on-light-soft); margin-bottom: var(--s5); }
.section--dark .product__tag { color: var(--text-on-dark-soft); }

/* Feature list with gold ticks */
.features { display: grid; gap: var(--s3); margin-bottom: var(--s6); }
.features li { display: grid; grid-template-columns: 22px 1fr; gap: var(--s3); align-items: start; font-size: var(--step-0); }
.features svg { margin-top: 6px; color: var(--gold); flex: none; }
.features b { font-weight: 650; }

/* Price + buy */
.buy { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4) var(--s5); }
.price { display: flex; flex-direction: column; gap: 2px; }
.price__eur { font-size: var(--step-2); font-weight: 750; letter-spacing: -.02em; line-height: 1; }
/* --success-ink on light (4.62:1); the brighter --success is kept for dark. */
.price__note { font-size: var(--step--1); color: var(--success-ink); font-weight: 600; }
.section--dark .price__note { color: var(--success); }

/* Mini spec strip under each product */

/* ============================================================
   Media + text block (the workhorse — used many times)
   ============================================================ */
.mediablock { display: grid; gap: clamp(var(--s5), 4vw, var(--s7)); align-items: center; }
@media (min-width: 860px) {
  .mediablock { grid-template-columns: 1fr 1fr; }
  .mediablock--flip .mediablock__media { order: 2; }
}
.mediablock + .mediablock { margin-top: clamp(var(--s7), 6vw, var(--s9)); }
/* One authored moment instead of six identical 50/50 splits. The flagship's
   coverage claim is the only place on the page where the image IS the argument —
   a top-down yard with the detection zones drawn on it — so it gets the full
   measure and the stats read as a band beneath it rather than a column beside it. */
@media (min-width: 860px) {
  .mediablock--wide { grid-template-columns: 1fr; gap: var(--s6); }
  .mediablock--wide .mediablock__body { max-width: 62ch; }
  .mediablock--wide .stats {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s5); margin-top: var(--s6);
    border-top: 1px solid transparent;
    border-image: linear-gradient(90deg, var(--gold), rgba(201,164,100,.15)) 1;
    padding-top: var(--s5);
  }
}
.mediablock__media img,
.mediablock__media video { width: 100%; border-radius: var(--r-lg); box-shadow: var(--shadow-md); display: block; }
.section--dark .mediablock__media img,
.section--dark .mediablock__media video { box-shadow: 0 16px 50px rgba(0,0,0,.5); }
/* Video loads nothing until scrolled into view (main.js swaps data-src -> src).
   Reserve the box so there is no layout shift while it is still empty. */
.mediablock__media video { aspect-ratio: 16 / 9; background: var(--obsidian-2); object-fit: cover; }
.mediablock__body h3 { margin-bottom: var(--s3); }

/* Stat cluster */
.stats { display: flex; flex-wrap: wrap; gap: var(--s6); margin: var(--s5) 0 0; }
/* On light surfaces the bright end of the ramp is invisible, so the gradient is
   reserved for dark sections and light ones get a solid deep gold. */
.stat__num {
  font-size: var(--step-3); font-weight: 750; line-height: 1; letter-spacing: -.03em;
  color: var(--gold-deep);
}
.section--dark .stat__num {
  /* Brass Light first, and not decoration: the base rule above sets Brass Deep
     for light surfaces, and that colour is what paints if background-clip:text
     doesn't. On obsidian Brass Deep is 3.96:1 — the exact failure the Two
     Brasses Rule exists to prevent. The fallback has to be legible too. */
  color: var(--gold-bright);
  background: var(--gold-grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.stat__label { font-size: var(--step--1); color: var(--text-on-light-soft); }
.section--dark .stat__label { color: var(--text-on-dark-soft); }

/* ============================================================
   Comparison tables
   ============================================================ */
/* The comparison tables are the only argument this page has that competitors
   cannot copy, and at 375px the wider one showed ONLY our own column — 445px of
   comparison sat off-screen behind a swipe signalled by a 2px scrollbar hairline.
   Three things fix it without restructuring the markup: the row-label column
   stays pinned so you always know which attribute you are reading, a right-edge
   fade shows there is more, and a hint names the gesture until you use it. */
.tablewrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r-lg);
  position: relative;
}
/* The buy buttons in here name their model, because the column header that
   would otherwise identify them sits ~950px up the table and scrolls out of
   view sideways. Keep the label on one line — wrapped, "Купи Care SE 1" reads
   as two separate things. The cells have room: 134px of label in a 166px cell. */
.tablewrap .btn { white-space: nowrap; }
/* Fade only while there is something still hidden to the right. */
.tablewrap[data-scrollable="true"] { mask-image: linear-gradient(90deg, #000 88%, transparent 100%); }
.tablewrap[data-scrolled="end"]    { mask-image: none; }

.table-hint {
  display: none; align-items: center; gap: var(--s2);
  font-size: var(--step--1); color: var(--text-on-light-soft);
  margin: 0 0 var(--s3);
}
.section--dark .table-hint { color: var(--text-on-dark-soft); }
.table-hint svg { color: var(--gold); flex: none; animation: hint-nudge 1.8s var(--ease) infinite; }
.tablewrap-group[data-scrollable="true"] .table-hint { display: flex; }
.tablewrap-group[data-scrolled="end"] .table-hint { display: none; }
@keyframes hint-nudge { 0%,100% { transform: translateX(0); } 50% { transform: translateX(4px); } }
@media (prefers-reduced-motion: reduce) { .table-hint svg { animation: none; } }

/* Pinned row-label column */
.tablewrap tbody th,
.tablewrap thead th:first-child {
  position: sticky; left: 0; z-index: 2;
  background: var(--white);
}
.section--mist .tablewrap tbody th { background: var(--mist); }
.tablewrap thead th:first-child { z-index: 3; background: var(--mist); }
.section--dark .tablewrap tbody th { background: var(--obsidian-2); }
.section--dark .tablewrap thead th:first-child { background: var(--obsidian-3); }
/* hairline so scrolled cells visibly pass underneath rather than colliding */
.tablewrap tbody th::after,
.tablewrap thead th:first-child::after {
  content: ''; position: absolute; inset-block: 0; right: 0; width: 1px;
  background: var(--b2);
}
.section--dark .tablewrap tbody th::after,
.section--dark .tablewrap thead th:first-child::after { background: var(--w2); }
table { border-collapse: collapse; width: 100%; min-width: 640px; font-size: var(--step--1); background: var(--white); }
.section--dark table { background: var(--obsidian-2); }
/* padding-inline keeps the first character clear of .tablewrap's rounded corner */
/* The source attribution now lives OUTSIDE the scroll container as .table-source:
   inside <caption> it was clipped by overflow-x and dimmed by the edge mask, which
   made the page's best trust asset its least readable text. A visually-hidden
   <caption> stays in the markup so the table keeps an accessible name. */
.table-source {
  font-size: var(--step--1); color: var(--text-on-light-soft);
  margin: 0 0 var(--s4); max-width: 56ch;
}
.section--dark .table-source { color: var(--text-on-dark-soft); }
.section--dark caption { color: var(--text-on-dark-soft); }
th, td { padding: var(--s3) var(--s4); text-align: left; border-bottom: 1px solid var(--b2); vertical-align: top; }
.section--dark th, .section--dark td { border-bottom-color: var(--w2); }
thead th { font-weight: 650; white-space: nowrap; background: var(--mist); }
.section--dark thead th { background: var(--obsidian-3); color: var(--white); }
tbody th { font-weight: 500; color: var(--text-on-light-soft); white-space: nowrap; }
.section--dark tbody th { color: var(--text-on-dark-soft); }
/* Our column highlighted */
.col-ours { background: rgba(201,164,100,.10); font-weight: 650; }
/* was Brass Deep on a 22% tint = 4.22:1 */
thead .col-ours { background: rgba(201,164,100,.22); color: var(--text-on-light); }
.section--dark thead .col-ours { color: var(--gold-bright); background: rgba(201,164,100,.16); }
/* 13 cells carry the honesty argument; #D9534F measured 3.96:1 on white. */
td.is-no { color: var(--alert-ink); }
.section--dark td.is-no { color: var(--alert); }

/* ============================================================
   Before/after night-vision slider
   ============================================================ */
/* 16:9 — the shape of real camera footage. A square reads as a photograph. */
.compare {
  position: relative; border-radius: var(--r-lg); overflow: hidden;
  touch-action: pan-y; cursor: ew-resize; user-select: none; background: var(--obsidian-2);
  aspect-ratio: 16 / 9;
}
/* Required caption whenever the frames are generated rather than real camera
   output. Presenting a simulated frame as a sample is a misleading commercial
   practice under EU law — do not remove this to tidy up the layout. */
.compare-note {
  font-size: var(--step--1); color: var(--text-on-light-soft);
  margin: var(--s3) 0 0; font-style: italic;
}
.section--dark .compare-note { color: var(--text-on-dark-soft); }
.compare img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.compare__after { clip-path: inset(0 0 0 var(--pos, 50%)); }
.compare__handle {
  position: absolute; inset-block: 0; left: var(--pos, 50%); width: 2px;
  background: rgba(255,255,255,.9); transform: translateX(-1px); pointer-events: none;
}
/* Split sun|moon disc — pattern borrowed from Dreame's own Elite 1 InCam slider.
   It states what the two sides are without needing a caption.
   REQUIRED DOM: .compare__grip must be a CHILD of .compare__handle. As a sibling
   it positions against .compare and freezes at the centre instead of tracking --pos. */
.compare__grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  width: 76px; height: 76px; border-radius: 50%; background: #fff;
  display: grid; grid-template-columns: 1fr 1fr; align-items: center;
  color: #1A1408; box-shadow: 0 4px 22px rgba(0,0,0,.4);
}
.compare__grip svg { justify-self: center; }
/* the hairline that continues the wipe line through the disc */
.compare__grip::after {
  content: ''; position: absolute; left: 50%; top: 12px; bottom: 12px;
  width: 1px; background: #1A1408; transform: translateX(-.5px);
}
.compare__label {
  position: absolute; bottom: var(--s4); font-size: var(--step--1); font-weight: 600;
  padding: 5px 12px; border-radius: var(--r-pill); background: var(--b7); color: #fff; pointer-events: none;
}
.compare__label--l { left: var(--s4); }
.compare__label--r { right: var(--s4); }

/* ============================================================
   Steps (installation)
   ============================================================ */
/* The numeral is the least informative thing in a step, so it doesn't get to
   command the eye: it demotes to a brass counter in the .eyebrow idiom and the
   step verb leads. On desktop the section head anchors left in its own column
   rather than sitting above four columns that dilute it. */
.steps-head { max-width: 46ch; margin-inline: auto; text-align: center; margin-bottom: clamp(var(--s7), 5vw, var(--s8)); }
.steps-head h2 { margin-bottom: var(--s4); }
.steps-head .lead { margin: 0 auto; }
@media (min-width: 1000px) {
  .steps-head { text-align: left; margin-inline: 0; }
  .steps-head .lead { margin-inline: 0; }
}

/* Block, not grid, below the split: a sticky grid item can only travel inside
   its own row, so as a row-1 grid child the visual unpinned the moment its own
   height scrolled by. As a block child it can ride the full section. */
.steps-layout { display: block; }
.steps-layout > .steps-visual { margin-bottom: var(--s6); }
@media (min-width: 1000px) {
  .steps-layout {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    column-gap: clamp(var(--s7), 6vw, var(--s9));
    align-items: start;
  }
  .steps-layout > .steps-visual { margin-bottom: 0; }
}

/* The visual pins while its column of steps scrolls past and swaps frames.
   Sticky top clears the sticky header (64px desktop, ~112px once the brand
   stacks below 440px). */
.steps-visual { position: sticky; top: 128px; }
/* Below the split the steps scroll *under* the pinned panel, so it needs an
   opaque full-bleed backdrop and a hairline edge — otherwise step copy peeks
   out around the image's rounded corners and reads as a rendering fault. */
@media (max-width: 999px) {
  .steps-visual {
    z-index: 2;
    background: var(--white);
    margin-inline: calc(var(--s5) * -1);
    padding: var(--s2) var(--s5) var(--s4);
    border-bottom: 1px solid var(--b2);
  }
  /* Without this the step copy is sliced mid-glyph at the panel edge. Let it
     fade in from under the hairline instead. */
  .steps-visual::after {
    content: ""; position: absolute; left: 0; right: 0; top: 100%;
    height: var(--s5); pointer-events: none;
    background: linear-gradient(var(--white), rgba(255, 255, 255, 0));
  }
}
@media (min-width: 1000px) { .steps-visual { top: 88px; } }
.steps-visual__stack {
  position: relative;
  aspect-ratio: 5 / 4;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--mist);
}
@media (min-width: 1000px) { .steps-visual__stack { aspect-ratio: 4 / 5; } }
.steps-visual__frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .55s var(--ease);
}
.steps-visual__frame.is-active { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .steps-visual__frame { transition: none; } }

/* One column at every width: the steps are read one at a time against the
   pinned visual, so a 2-up grid would break the pairing. Desktop gets a wide
   beat so the sticky frame has travel to hold through. */
.steps { display: grid; gap: var(--s6); grid-template-columns: 1fr; }
@media (min-width: 1000px) { .steps { gap: clamp(var(--s8), 7vw, var(--s10)); } }

/* Everything but the current step recedes. Kept above the 4.5:1 floor: the
   dimmed body still computes 4.63:1 on white. */
.step { transition: opacity .4s var(--ease); }
@media (min-width: 700px) {
  .step { opacity: .45; }
  .step.is-active { opacity: 1; }
  .step.is-active .step__rule { border-top-color: var(--gold); }
}
@media (prefers-reduced-motion: reduce) { .step { transition: none; } }
.no-js .step { opacity: 1; }

/* Each step opens on a hairline carrying its number; the rule spans the column
   so the short measure below reads as deliberate, and it turns brass on the
   step the pinned frame is currently showing. */
.step__rule {
  border-top: 1px solid var(--b2);
  padding-top: var(--s3); margin-bottom: var(--s3);
  transition: border-color .4s var(--ease);
}
.step__num {
  font-size: var(--step-0); font-weight: 750; letter-spacing: .06em; line-height: 1;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--gold-deep);   /* steps sit on a white section — solid, not gradient */
}
.steps__note {
  margin: clamp(var(--s6), 4vw, var(--s7)) 0 0;
  font-size: var(--step--1); color: var(--text-on-light-soft);
}
/* h3, not h4: the markup is <h3>, so the old h4 rule matched nothing and the
   headings inherited the global --step-2, shouting over their own copy. */
.step h3 { font-size: var(--step-1); font-weight: 700; letter-spacing: -0.015em; margin: 0 0 var(--s2); text-wrap: balance; }
.step p { font-size: var(--step--1); line-height: 1.55; color: var(--text-on-light-soft); margin: 0; max-width: 42ch; }

/* ============================================================
   FAQ accordion
   ============================================================ */
.faq { max-width: 820px; margin-inline: auto; }
.faq details { border-bottom: 1px solid var(--b2); }
.section--dark .faq details { border-bottom-color: var(--w2); }
.faq summary {
  cursor: pointer; list-style: none; padding: var(--s5) var(--s6) var(--s5) 0;
  font-weight: 650; position: relative; font-size: var(--step-0);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ''; position: absolute; right: var(--s2); top: 50%;
  width: 11px; height: 11px; border-right: 2px solid var(--gold); border-bottom: 2px solid var(--gold);
  transform: translateY(-70%) rotate(45deg); transition: transform .25s var(--ease);
}
.faq details[open] summary::after { transform: translateY(-30%) rotate(225deg); }
.faq details p { margin: 0 0 var(--s5); color: var(--text-on-light-soft); max-width: 68ch; }
.section--dark .faq details p { color: var(--text-on-dark-soft); }

/* ============================================================
   Final CTA
   ============================================================ */
.finale { text-align: center; }
/* Subgrid, not three independent columns. As flex columns each card sized its own
   rows, so "Navo Elite 1 OutCam" wrapping to two lines pushed that card's place,
   price and button below the others' — the prices and buy buttons never shared a
   line. The cards now span the parent's four row tracks, so name, place, price and
   button each sit on one baseline across all three. */
.finale__grid {
  display: grid; gap: var(--s4); margin-top: var(--s7);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto auto auto;
}
.finale__card {
  padding: var(--s5); border-radius: var(--r-lg);
  background: var(--obsidian-2); border: 1px solid var(--w2); text-align: left;
  display: grid; grid-row: span 4; grid-template-rows: subgrid;
  row-gap: var(--s3);
}
/* h3, not h4: the old selector matched nothing, so these headings kept the global
   16px heading margin on top of the row gap. */
.finale__card h3 { margin: 0; color: var(--white); align-self: start; }
.finale__card .price__eur { font-size: var(--step-1); align-self: end; }
.finale__card .btn { align-self: end; }
/* Below the 3-up threshold the cards stack, so shared row tracks stop meaning
   anything — and one column of 12 tracks would stretch them oddly. */
@media (max-width: 700px) {
  .finale__grid { grid-template-columns: 1fr; grid-template-rows: none; }
  .finale__card { grid-row: auto; grid-template-rows: none; display: flex; flex-direction: column; gap: var(--s3); }
  .finale__card .btn { margin-top: auto; }
}
/* Where subgrid is unavailable the buttons at least share a bottom edge, which is
   the half of the misalignment that reads worst. */
@supports not (grid-template-rows: subgrid) {
  .finale__card { display: flex; flex-direction: column; gap: var(--s3); }
  .finale__card .btn { margin-top: auto; }
}

/* ============================================================
   Footer
   ============================================================ */
.footer { background: var(--obsidian); color: var(--text-on-dark-soft); padding-block: var(--s8) var(--s6); font-size: var(--step--1); }
.footer__grid { display: grid; gap: var(--s6); grid-template-columns: 1fr; margin-bottom: var(--s7); }
@media (min-width: 560px)  { .footer__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .footer__grid { grid-template-columns: repeat(4, 1fr); } }
.footer h4 { color: var(--white); font-size: var(--step--1); letter-spacing: .1em; text-transform: uppercase; margin: 0 0 var(--s3); }
/* 16px of line-box is not a tap target. Inline-block plus vertical padding takes
   these to 32px, clearing the 24x24 AA minimum with room for a thumb, without
   respacing the whole footer. */
.footer a {
  color: var(--text-on-dark-soft); text-decoration: none;
  display: inline-block; padding-block: 8px;
}
.footer li + li { margin-top: 0; }
.footer a:hover { color: var(--gold-bright); }
.footer__bottom { border-top: 1px solid var(--w1); padding-top: var(--s5); display: flex; flex-wrap: wrap; gap: var(--s4); justify-content: space-between; }
/* #6E6B66 on obsidian measured 3.73:1 — the only true low-contrast hit the
   detector and the review both flagged. --text-on-dark-soft is 8.06:1.
   max-width tightened from 70ch: it was rendering ~87 chars per line. */
.footer__note { max-width: 62ch; color: var(--text-on-dark-soft); }

/* ============================================================
   Sticky mobile buy bar
   ============================================================ */
.stickybuy {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 70;
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s4);
  padding-bottom: max(var(--s3), env(safe-area-inset-bottom));
  background: rgba(10,10,11,.94); backdrop-filter: blur(14px);
  border-top: 1px solid var(--w2); color: var(--text-on-dark);
  transform: translateY(110%); transition: transform .32s var(--ease);
}
/* The consent banner is fixed to the same edge, and it is ~211px tall on a phone.
   Stacking the two would hand 34% of a 375px screen to fixed furniture, so the
   banner simply owns the bottom edge until it is answered. The page still has
   twelve in-flow buy links meanwhile — this bar is an accelerator, not the only
   route out. */
:root[data-consent="pending"] .stickybuy { display: none; }
.stickybuy.is-visible { transform: translateY(0); }
.stickybuy__info { min-width: 0; flex: 1; }
.stickybuy__name { font-size: var(--step--1); font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stickybuy__price { font-size: var(--step--1); color: var(--gold-bright); }
@media (min-width: 861px) { .stickybuy { display: none; } }

/* ============================================================
   Cookie consent — the only gate on the Meta pixel
   ============================================================ */
.consent {
  /* Above the buy bar (70) and the header (60): until this is answered it is the
     one thing on the page that must not be obscured. */
  position: fixed; inset-inline: 0; bottom: 0; z-index: 80;
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s4);
  padding: var(--s4);
  padding-bottom: max(var(--s4), env(safe-area-inset-bottom));
  background: rgba(10,10,11,.97); backdrop-filter: blur(14px);
  border-top: 1px solid var(--w2); color: var(--text-on-dark);
  box-shadow: 0 -18px 60px rgba(0,0,0,.42);
}
.consent[hidden] { display: none; }
.consent__body { flex: 1 1 34ch; min-width: 0; }
.consent__title { margin: 0 0 2px; font-size: var(--step-0); font-weight: 700; letter-spacing: -.01em; }
.consent__text { margin: 0; font-size: var(--step--1); line-height: 1.55; color: var(--text-on-dark-soft); }
.consent__text a { color: var(--gold-bright); text-underline-offset: 3px; }

.consent__actions { display: flex; gap: var(--s3); flex: 0 0 auto; }
/* Equal visual weight, and "Отказвам" comes first. Making accept the louder
   button is the standard consent dark pattern, and consent collected that way is
   not freely given — i.e. not valid consent, which would defeat the point of
   having the gate at all. Same padding, same font, same size: only the fill
   differs, and both fills are high-contrast against the obsidian bar. */
.consent__btn { flex: 1 1 auto; min-width: 124px; }
.consent__btn--deny  { background: var(--mist); color: var(--text-on-light); }
.consent__btn--allow { background: var(--gold-surface); color: #1A1408; }
.consent__btn:hover { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) {
  .consent__btn:hover { transform: none; }
}
/* Narrow phones: the two buttons take the full width side by side rather than
   stacking, so neither is reachable-by-thumb in a way the other is not. */
@media (max-width: 560px) {
  .consent { gap: var(--s3); }
  .consent__actions { width: 100%; }
}

/* ============================================================
   Scroll reveal — CSS-driven, IntersectionObserver adds .is-in
   ============================================================ */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal.is-in { opacity: 1; transform: none; }

/* Side-by-side siblings all cross the threshold on the same frame, so three
   .choice columns and four .claim cards were arriving as one block — the
   entrance read as a page repaint rather than a sequence. They cascade now.
   The duration is shortened to absorb the stagger, so the whole group still
   finishes inside the 0.7s the standalone blocks take. */
.chooser .choice,
.claims .claim { transition-duration: .48s; }
.chooser .choice:nth-child(2) { transition-delay: .07s; }
.chooser .choice:nth-child(3) { transition-delay: .14s; }
.claims .claim:nth-child(2) { transition-delay: .06s; }
.claims .claim:nth-child(3) { transition-delay: .12s; }
.claims .claim:nth-child(4) { transition-delay: .18s; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  /* Explicit: the stagger selectors outrank bare .reveal on specificity, so the
     shorthand reset above would not have cleared their delays on its own. */
  .chooser .choice, .claims .claim { transition-delay: 0s; transition-duration: 0s; }
  .btn, .choice, .compare__handle { transition: none !important; }
}
/* If JS never runs, never hide content */
.no-js .reveal { opacity: 1; transform: none; }
/* Both the video src and its poster are attached by JS now, so without JS the
   element is an empty 16/9 box. The <noscript> still beside it carries the frame
   instead; hide the dead player so they don't stack. */
.no-js .mediablock__media video { display: none; }
