/* ==========================================================================
   iMagnet · layout and components
   Every color and size comes from tokens.css. Nothing hardcoded.
   Written with CSS logical properties throughout so the same rules serve
   Hebrew RTL, Arabic RTL and English LTR without a second stylesheet.
   ========================================================================== */

/* --- Reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  /* No browser or WordPress/Hello-Elementor default may flash a coloured
     highlight when a finger taps brand chrome. -webkit-tap-highlight-color is
     inherited, so setting it transparent here clears it for every element —
     the header logo, the opening mark/disc, the stage, every link and button.
     This is the fix for the owner's "coloured circle appears around the logo
     on tap" on the live site: the disc was cleared last round, but the header
     logo (which plays the opening on tap) still carried the device default. */
  -webkit-tap-highlight-color: transparent;
}
body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--f-he);
  font-size: var(--t-body);
  font-weight: var(--fw-light);
  line-height: 1.85;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, p, figure { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; }

/* HELLO ELEMENTOR'S reset.css IS A STARTER SKIN, NOT A RESET. Neutralised
   2026-08-18. It ships, in the parent theme this store is a child of:
       a                                   { color: #c36 }
       a:hover, a:active                   { color: #336 }
       [type=button], [type=submit], button{ border: 1px solid #c36; color: #c36 }
       ...:focus, ...:hover                { background-color: #c36; color: #fff }
   #c36 is rgb(204, 51, 102), and it is the pink in the owner's two photographs:
   the credit-card trigger in the first, the menu's close button in the second.
   It walked between them because ON A PHONE A TAP IS :focus, and :focus stays
   until something else is tapped.
   Our components all answer :hover, so hover was covered by accident. :focus
   never was. Neither was the border: measured on the live checkout, five of the
   nine buttons were carrying a 1px magenta border at rest, including both
   wallet buttons and the credit-card trigger.
   Written in the SAME selector shape as the rules it answers, so it wins on
   order alone and any component with a class still outranks it, and touching
   only the three properties that leak. No padding, no radius, no geometry, so
   nothing moves.
   This replaces an earlier attempt that named four controls one by one (see the
   gallery focus ring in woo-brand.css). A list can only ever cover what somebody
   remembered; this covers every button on the site, including tomorrow's.

   AND THE REASON OUR EXISTING RESET NEVER HELD: `button { color: inherit }` a
   few lines above is specificity (0,0,1), while Hello writes the SAME rule as
   `[type=button], [type=submit], button`, and an attribute selector counts at
   class level. So for every button carrying type="button" or type="submit" —
   which is every button this theme prints, including .nav-toggle and
   .pay__trigger — Hello's (0,1,0) outranked our (0,0,1) and the magenta text
   colour won. Matching the selector shape is what makes this hold. */
[type="button"], [type="submit"], button { border-color: transparent; color: inherit; }
[type="button"]:focus, [type="button"]:hover,
[type="submit"]:focus, [type="submit"]:hover,
button:focus, button:hover {
  background-color: transparent;
  color: inherit;
}
a:hover, a:active { color: inherit; }

:focus-visible {
  outline: 2px solid var(--c-ring);
  outline-offset: 3px;
}
/* The ring's colour is inherited, not chosen per component: --c-ring is the deep
   green at :root (12.95:1 on ivory) and flips to gold inside anything painted on
   the deep green (8.23:1 there). Before this, the ring was the gold everywhere
   and measured 1.57:1 on ivory — under the 3:1 WCAG 2.4.11 asks of a focus
   indicator, so keyboard focus was effectively invisible across most of the
   store. Listing the dark containers here rather than each control inside them
   means a control added to the footer or the header tomorrow is correct with no
   further thought. */
.band-dark,
.topbar,
.site-header { --c-ring: var(--c-accent); }
/* A ring is shown ONLY for genuine keyboard focus (:focus-visible above, kept
   for WCAG 2.4.7). Any focus left by a pointer press — a mouse click or a tap —
   gets no ring, globally, so no UA/theme default can leak a coloured outline
   onto the opening's interactive elements. This is the desktop half of the
   owner's "ring around the logo": the header logo's click is preventDefault-ed
   to play the opening, so focus stays on it, and without this the gold accent
   ring drew a rectangle around the logo on a plain click. Higher specificity
   than a bare a:focus / button:focus theme default, so it wins the reset. */
:focus:not(:focus-visible) { outline: none; }

/* --- Primitives ----------------------------------------------------------- */
.container {
  max-inline-size: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--s-7); }
.section--tight { padding-block: var(--s-6); }
/* Pulls a section up toward the one above it. Used on the collection so it sits
   closer to the value strip. */
.section--flush-top { padding-block-start: var(--s-5); }

.band-dark {
  background: var(--c-primary);
  color: var(--c-on-dark);
}

/* A green band that carries a jungle-and-gold scene instead of a flat fill, so
   the section reads as part of the same world as the hero rather than a slab of
   colour. The foliage is a real photograph; a flat near-black-green scrim rides
   over it so the ivory copy keeps its contrast (the scrim is darker than the
   base green, so text is at least as legible as it was, verified on rendered
   pixels). The scene stays subordinate: present as depth and light, never
   loud enough to fight the words. */
.band-dark--scene {
  /* The same transparent foliage frame and the same green as the top banner, so
     every green scene band across the site reads as one identical treatment. */
  background:
    url(../../assets/web/scene-shop-frame.webp) center / cover no-repeat,
    #0b2013;
}

/* --- Scene banners on interior pages (visual audit 2026-07-23) -------------
   A generated jungle-and-gold scene behind a page's opening title, so no
   interior page opens on bare ivory. scene-shop / scene-story each carry a
   dark ramp baked into the RIGHT of the frame (the RTL copy side), so only a
   light overall wash is added here and contrast is measured on real pixels. */
.page-hero {
  display: grid;
  align-content: center;
  min-block-size: 340px;
  /* Run full-bleed up under the fixed header (cancelling the body's top pad),
     then pad the copy back down so it clears the header. Same move the home
     hero makes, so the header floats on the banner and its logo/pills flip to
     the on-dark treatment via the is-dark marker the script reads. */
  margin-block-start: calc(-1 * var(--chrome-h, 76px));
  padding-block: calc(var(--chrome-h, 76px) + var(--s-6)) var(--s-7);
  background: center / cover no-repeat var(--c-primary);
  color: var(--c-on-dark);
  border-block-end: 1px solid var(--c-line-on-dark);
}
/* The banner opens over the header, so the header starts in on-dark mode; the
   script then flips --np back to 1 as the ivory grid scrolls under the bar. */
html:has(.page-hero) { --np: 0; }
.page-hero .eyebrow { color: var(--c-accent); }
.page-hero .title { margin-block-end: var(--s-3); }
.page-hero .lede { color: var(--c-on-scene-muted); max-inline-size: 54ch; }
/* The shop banner is a transparent foliage frame laid over the solid brand
   green (the green is the CSS colour, not baked into the art), with the copy
   centred in the clean middle the leaves frame. */
.page-hero--shop {
  background-image: url(../../assets/web/scene-shop-frame.webp);
  /* Matched to the green the lower scene band actually renders (side-1 under
     its 0.18 scrim), so the two green bands on the page read as one colour. */
  background-color: #0b2013;
  text-align: center;
}
.page-hero--shop .lede { margin-inline: auto; }
/* UNUSED as of P4, kept only so a future banner-style story page has its
   treatment ready. The APPROVED story page has no .page-hero at all: it is copy
   on the ivory page framed by .foliage-rail down each outer edge (see the Story
   page block further down). Worth stating, because the P4 brief was written
   believing this rule was the story page's hero and sent the contrast check
   after a scrim that nothing renders. If you are measuring the story page,
   measure the rails against the LINE BOXES of the copy, not this. */
.page-hero--story { background-image: linear-gradient(to left, rgba(10, 22, 14, 0.62), rgba(10, 22, 14, 0.16) 55%, rgba(10, 22, 14, 0.04)), url(../../assets/web/scene-story.webp); background-position: center 40%; }
[dir="ltr"] .band-dark--scene { background-position: center; }

/* The muted copy on a scene band is over a photograph, not a flat fill, so it
   takes the on-scene muted value (0.78) the hero already uses, never the flat
   0.62 which is calibrated for solid green and drops below the 4.5:1 floor on
   the lit gold of the scene. Measured worst case on the band: 5.15:1. */
.band-dark--scene .lede,
.band-dark--scene .story__body { color: var(--c-on-scene-muted); }
/* On a phone the scene band showed no foliage at all: scene-shop-frame is a
   foliage FRAME with a clear centre, and a narrow viewport with `cover` crops
   straight to that empty middle, so the leaves at the edges are lost. On the
   phone, use the full foliage scene (the same image the story-page hero uses)
   under a flat dark scrim instead — the plants are actually visible, and the
   scrim keeps the muted ivory copy legible over the lit leaves. The scrim is
   0.78, not lighter: measured on the rendered pixels, the copy sits over a
   blown-out leaf highlight (~rgb(252,256,243)), and only a scrim this dark keeps
   the 0.78-muted copy clear of the 4.5:1 floor there (worst 5.6:1; a 0.62 scrim
   failed at 3.4:1). The foliage still reads clearly as texture underneath. */
@media (max-width: 900px) {
  .band-dark--scene {
    background:
      linear-gradient(rgba(10, 22, 14, 0.78), rgba(10, 22, 14, 0.78)),
      url(../../assets/web/scene-story.webp) center / cover no-repeat,
      #0b2013;
  }
}

/* Gold fails contrast on ivory, so the eyebrow is green on light and gold
   only on the deep green, where it measures 8.2:1. */
.eyebrow {
  font-size: var(--t-eyebrow);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  color: var(--c-primary);
  margin-block-end: var(--s-4);
}
/* Every deep-green surface, not only .band-dark. The hero paints the same
   green from its own rule, so an eyebrow there was #14301F on #14301F: a
   1:1 ratio and literally invisible. Gold on the green measures 8.2:1. */
.band-dark .eyebrow,
.hero .eyebrow { color: var(--c-accent); }

/* The brand wordmark. Always the Latin display face, mixed case (never the
   eyebrow's uppercase), so MaxVolmer reads as a name, not a shout. */
.brand {
  font-family: var(--f-la);
  text-transform: none;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* When the brand IS the eyebrow (hero, collection), it keeps the eyebrow colour
   but drops the uppercase and heavy tracking, which suit the sans, not a serif. */
.eyebrow--brand {
  font-family: var(--f-la);
  text-transform: none;
  letter-spacing: 0.05em;
  font-size: 1.05rem;
}

/* Skip to content. Standard 5568 asks that a keyboard user be able to pass the
   navigation, and the store ships an accessibility statement, so this is a
   requirement rather than a nicety. Off screen until it takes focus. */
.skip-link {
  position: fixed;
  inset-block-start: var(--s-2);
  inset-inline-start: var(--s-2);
  z-index: 50;
  padding: 0.7rem 1.3rem;
  background: var(--c-accent);
  color: var(--c-primary);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  border-radius: var(--radius-sm);
  translate: 0 -400%;
}
.skip-link:focus { translate: 0 0; }

/* Visually hidden, still read aloud. Carries state that color alone implies. */
.vh {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

/* Latin scent names. Never uppercase the brand name with this class. */
.latin {
  font-family: var(--f-la);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-latin);
  text-transform: uppercase;
}

.title {
  font-size: var(--t-title);
  font-weight: var(--fw-light);
  line-height: 1.25;
}

.lede {
  font-size: var(--t-subtitle);
  font-weight: var(--fw-light);
  line-height: 1.7;
  color: var(--c-muted-text);
}
.band-dark .lede { color: var(--c-on-dark-muted); }

.rule {
  border: 0;
  border-block-start: 1px solid var(--c-hairline);
  margin: 0;
}
.band-dark .rule { border-block-start-color: var(--c-line-on-dark); }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.95rem 2.1rem;
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  letter-spacing: 0.04em;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease),
              border-color 0.3s var(--ease);
}
.btn--gold {
  background: var(--c-accent);
  color: var(--c-primary);
}
.btn--gold:hover,
.btn--gold:focus { background: var(--c-accent-soft); }

.btn--ghost {
  background: transparent;
  color: var(--c-accent);
  border-color: var(--c-line-on-dark);
}
.btn--ghost:hover,
.btn--ghost:focus {
  border-color: var(--c-accent);
  background: rgba(var(--rgb-accent), 0.08);
}

.btn--ink {
  background: var(--c-primary);
  color: var(--c-on-dark);
}
.btn--ink:hover,
.btn--ink:focus { background: var(--c-primary-soft); }

/* Secondary action on ivory. Gold text on ivory fails contrast, so the
   light-background outline button uses the green ink instead. */
.btn--outline {
  background: transparent;
  color: var(--c-primary);
  border-color: var(--c-primary);
}
.btn--outline:hover,
.btn--outline:focus { background: var(--c-primary); color: var(--c-on-dark); }

/* --- The arch ------------------------------------------------------------- */
/* The signature form. Taken from the logo mark: two uprights joined by a
   semicircular crown. Used in exactly two places, hairline only, never
   filled: the hero frame and the scent card. */
.arch {
  position: relative;
  border: 1px solid var(--c-arch-on-dark);
  background: rgba(244, 237, 225, 0.035);
  border-radius: var(--arch-crown) var(--arch-crown) var(--radius) var(--radius);
  display: grid;
  place-items: end center;
  overflow: hidden;
  transition: border-color 0.5s var(--ease);
}
.arch::before {
  /* light held inside the arch. The brand promise is a presence that stays
     in the air, so the bottle sits in a glow rather than on a gradient. */
  content: "";
  position: absolute;
  inset-block-end: 12%;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: 82%;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgba(var(--rgb-accent), 0.20) 0%, transparent 68%);
  pointer-events: none;
}
/* The interior deepens toward its floor. Enki is near-white glass, and on the
   flat ivory it had almost nothing to sit against: the card read as a hole
   rather than as a bottle. A vertical wash gives every bottle a floor to stand
   on, costs no request, and applies to all six cards equally so the grid still
   reads as one set.
   Measured on rendered screenshots rather than judged by eye: the interior was
   flat to within 1.2 luminance points before this and runs 8.3 points deeper
   at the floor after it. 0.16 was tried and rejected. It separates the bottle
   better still, but it turns the arch into a grey-green panel and this arch is
   specified as hairline only, never filled. */
.arch--light {
  border-color: rgba(var(--rgb-accent), 0.45);
  background: linear-gradient(180deg,
    rgba(var(--rgb-primary), 0.012) 0%,
    rgba(var(--rgb-primary), 0.10) 100%);
}
.arch--light::before {
  background: radial-gradient(circle, rgba(var(--rgb-accent), 0.18) 0%, transparent 68%);
}

.arch__bottle {
  position: relative;
  inline-size: 76%;
  margin-block-end: 8%;
  filter: drop-shadow(0 22px 34px rgba(var(--rgb-primary), 0.30));
}


/* A boxed shot is a 1000 square while the arch is portrait, so it cannot use
   the bottle's sizing: at 76% of the arch width a square composition renders
   the bottle inside it tiny. It sits wider and lower instead. */
.arch__pack {
  position: relative;
  inline-size: 94%;
  margin-block-end: 12%;
  filter: drop-shadow(0 18px 28px rgba(var(--rgb-primary), 0.26));
}

/* Placeholder for packshots still in transit. Every product now has real
   photography, so this survives only for a future product added before its
   shoot lands. */
.arch__pending {
  position: relative;
  display: grid;
  justify-items: center;
  gap: var(--s-3);
  margin-block-end: 26%;
  text-align: center;
}
.arch__pending img { inline-size: 96px; opacity: 0.26; }
.arch__pending span {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  color: var(--c-muted-text);
}

/* --- Header --------------------------------------------------------------- */
/* Deep green, and on the home page it runs straight into the hero so the
   two read as one field. */
/* The header is fixed now, so the document has to make room for it or every
   page but the home page starts underneath it. --chrome-h is measured at
   runtime rather than written down, because the preview flag counts toward it
   and will not exist in the theme. The fallback is the header's own height at
   its usual size, so a page with the script blocked is merely a few pixels off
   rather than broken. The opening cancels this padding on purpose: running
   under the header is the whole point of a transparent one. */
body { padding-block-start: var(--chrome-h, 76px); }

/* Fixed and transparent, so the opening frame is a full screen of photograph
   with the brand floating on it rather than a photograph with a green bar
   stapled above it. It earns a background only once there is content behind it
   to separate from.
   backdrop-filter is the part doing the work: over foliage a flat translucent
   panel turns leaf detail into noise behind the nav, and the blur turns it back
   into a surface. -webkit- prefix included because Safari still wants it. */
.site-header {
  position: fixed;
  /* Below the preview flag, which is also fixed and paints above this. At 0 the
     two overlapped and the flag covered the top half of the header: on desktop
     they cleared each other by luck, and at 390px the flag wraps to two lines
     and swallowed the nav entirely. --flag-h is 0 in the theme, where the flag
     does not exist, so this line costs production nothing. */
  inset-block-start: var(--flag-h, 0px);
  inset-inline: 0;
  z-index: 100;
  color: var(--c-on-dark);
  /* The bar itself is never a surface on desktop: no fill, no hairline, no
     frame. The only surfaces in the header are the pills, so the photograph
     runs unbroken behind it.
     That moves a real problem onto the pills rather than removing it. A bar
     used to be solid here because a transparent one measured 1.53:1 over the
     ivory sections, and ivory links on light glass over ivory is the same
     failure. --np is the answer: see the nav pill below. */
  background: transparent;
  border-block-end: 0;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease);
}

/* "What is under the header right now."
   0 means a dark region is behind it, so the mark can be the light one and the
   pills can stay a thin smoked glass that lets the scene through. 1 means it is
   over something light, so the mark flips to the dark one and the pills bring
   their own darkness, because ivory links on light glass over ivory is ivory on
   ivory. Everything in the bar reads this one number.
   The script keeps it current as you scroll past each section. These two are
   the value before it runs and the value it falls back to: light is the safe
   default because pills that carry their own darkness are legible on anything,
   and a page that opens on the photograph says so up front.
   Both of these sit on the root, which is also where the script writes, so the
   live value simply overrides them as an inline style. On body they would win
   against it for everything inside body, which is the whole page. */
:root { --np: 1; }
/* Static defaults for the fixed-chrome heights. site.js measures the exact values
   at runtime and overrides these inline on <html>, but a permanent strip now ships
   in the theme (the announcement ticker) and on every preview page (the approval
   flag), so the first paint — and any no-JS visitor — must already lay out under
   both the strip and the header: without these the header would render at top:0
   beneath the fixed strip and the body padding would be short by the strip's
   height. The strip is one --t-meta line inside var(--s-2) padding top and bottom;
   76px is the header's resting height, the value the rest of the file falls back
   to. */
:root {
  --header-h: 76px;
  /* 2.5rem = 40px, the strip's height, and it closes exactly:
       2 x var(--s-2) padding            = 2 x 0.5rem            = 1.0000rem
       one --t-meta line at line-height 1.85 = 0.8125 x 1.85     = 1.5031rem
                                                          total  = 2.5031rem
     The TEXT line box sets it. Not the icon: at 1.15em the icon is 0.9344rem
     (~15px), well inside the ~24px text line, so it never governs — worth being
     exact about, because the next person to tighten the strip's line-height will
     trust this comment, and a plausible-but-wrong derivation is how the earlier
     2.25rem (4px short, leaving the first paint that much out until site.js
     measured the real value) survived as long as it did.
     Kept in rem, not px, because every input above is root-relative, so the
     strip genuinely scales with a visitor's text size and a px default would
     drift the moment anyone changed it. Verified identical for every promise at
     360/390/430/768/1440/2560 — they share one grid cell, so that is structural
     rather than lucky. */
  --flag-h: 2.5rem;
  --chrome-h: calc(76px + 2.5rem);
}
html:has(.journey) { --np: 0; }
/* Opt in only where a full-bleed dark scene runs underneath. :has() is load
   bearing and also chosen for how it fails: a browser without it keeps the
   solid header, which is legible on every page. */
/* The preview flag is position:static above a fixed header, so the page needs
   to be pushed down by the header's own height or the first section hides
   under it. Only pages that are not the journey need this: the journey is
   deliberately full-bleed under the header. */
/* Three columns rather than space-between, because the nav has to be centred on
   the bar and not merely placed between two groups: with space-between it drifts
   whenever the cart label or the language segment changes width, and it would
   move again the day a third link is added. The outer columns are equal and
   flexible, the nav is its own width, so the centre is the centre.
   In RTL the first column is the right one, which is why the toggle sits first
   in the markup: on a phone it wants the far side from the cart, and on desktop
   it is not there at all and the logo takes the column. */
.site-header__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s-4);
  padding-block: var(--s-3);
}
.site-header__logo {
  justify-self: start;
  position: relative;
  display: inline-flex;
}
.site-header__logo img { block-size: 52px; inline-size: auto; display: block; }
/* The second mark stacks on the first and the two cross fade on --np, the same
   number that darkens the pills. The first one is what sizes the link, so it
   stays in flow and only the second is lifted out of it. */
.site-header__logo img + img {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
}
.logo--on-dark { opacity: calc(1 - var(--np, 1)); }
.logo--on-light { opacity: var(--np, 1); }
.site-header__logo img { transition: opacity 0.3s var(--ease); }

/* The links sit on their own surface rather than directly on the photograph.
   Glass and not a solid fill: the bar is over foliage that is meant to be seen,
   so the pill lifts the text off it without cutting a black hole in the scene.
   It reads as almost nothing over a dark plate, which is the intent, and it is
   what carries the links' contrast once the bar behind it went transparent. */
.nav {
  position: relative;
  display: flex;
  align-items: center;
  justify-self: center;
  gap: var(--s-5);
  font-size: var(--t-small);
  padding: 0.7rem var(--s-5);
  border-radius: 999px;
  /* The brand green, not a smoked black. Glass here means tinted and blurred,
     and the tint is the brand's own colour so the bar belongs to the palette
     rather than reading as a grey panel dropped on the photograph.
     It is a tint and not a veil of ivory: a light veil brightened the leaf
     behind the links and pushed the gold of the active one to 4.48:1, under
     the floor. */
  background-color: rgba(20, 48, 31, 0.62);
  border: 1px solid rgba(244, 237, 225, 0.12);
  backdrop-filter: blur(16px) saturate(115%);
  -webkit-backdrop-filter: blur(16px) saturate(115%);
}
/* The rest of the darkness, for when there is nothing dark behind the pill. It
   is a layer rather than a second colour on the element because a background
   cannot cross fade and this has to: the tone changes while you scroll past a
   section edge, and a step there is visible. */
.nav::before,
.lang-switch::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgba(20, 48, 31, 0.92);
  opacity: var(--np, 1);
  transition: opacity 0.3s var(--ease);
  pointer-events: none;
}
/* Positioned, so they paint above the layer rather than under it. */
.nav > *,
.lang-switch > * { position: relative; }
.nav a {
  padding-block: var(--s-1);
  border-block-end: 1px solid transparent;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.nav a:hover, .nav a[aria-current="page"] {
  color: var(--c-accent);
  border-block-end-color: var(--c-accent);
}

/* --- The brands dropdown -------------------------------------------------
   "מותגים" stopped being a landing link on 2026-09-08 (home/shell copy brief,
   section 2): it is a disclosure now, so a visitor reaches any brand page from
   any page rather than through the home page's shelf. The panel ships `hidden`
   in the markup — a closed disclosure must be out of the tab order, and a panel
   merely translated off screen is not — so everything below only styles the
   OPEN state; Task 3's script removes the attribute and flips aria-expanded.

   The button is styled as one of the nav's own links (same size, same hover
   rule) rather than as a control, because in the bar it IS one of the four
   items and a button that looks like a button would break the row. */
.brands-menu { position: relative; display: inline-flex; }
.brands-menu__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4ch;
  padding: var(--s-1) 0;
  background: none;
  border: 0;
  border-block-end: 1px solid transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.brands-menu__btn:hover,
.brands-menu__btn[aria-expanded="true"] {
  color: var(--c-accent);
  border-block-end-color: var(--c-accent);
}
/* The chevron is drawn rather than added to the markup: it is decoration, and a
   glyph in the markup would be one more thing both renderers have to keep
   identical. It rotates on the open state, which is the affordance. */
.brands-menu__btn::after {
  content: "";
  inline-size: 0.42em;
  block-size: 0.42em;
  border-inline-end: 1.5px solid currentColor;
  border-block-end: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform 0.25s var(--ease);
}
.brands-menu__btn[aria-expanded="true"]::after { transform: translateY(0.05em) rotate(-135deg); }

/* On desktop the panel hangs under the header on its own surface — the same
   tinted glass the nav pill is made of, so it reads as part of the bar rather
   than as a white card dropped on the photograph. Centred on the button in both
   reading directions with a logical inset pair, so it needs no RTL variant. */
.brands-menu__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--s-3));
  inset-inline-start: 50%;
  transform: translateX(50%);
  z-index: 120;                 /* above the header's own pills, under the mini cart */
  display: grid;
  gap: var(--s-1);
  min-inline-size: 13rem;
  padding: var(--s-3);
  border-radius: var(--radius-sm);
  background: rgba(20, 48, 31, 0.97);
  border: 1px solid rgba(244, 237, 225, 0.12);
  backdrop-filter: blur(18px) saturate(115%);
  -webkit-backdrop-filter: blur(18px) saturate(115%);
  box-shadow: 0 18px 40px rgba(var(--rgb-primary), 0.28);
  color: var(--c-on-dark);
  text-align: start;
  white-space: nowrap;
}
[dir="ltr"] .brands-menu__panel { transform: translateX(-50%); }
.brands-menu__panel[hidden] { display: none; }
.brands-menu__panel a {
  padding: var(--s-1) var(--s-2);
  border-radius: var(--radius-sm);
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}
.brands-menu__panel a:hover,
.brands-menu__panel a:focus { background: rgba(var(--rgb-accent), 0.14); color: var(--c-accent); }
/* "לכל המותגים" is the shelf, not a brand: a hairline says so without a second
   heading in a four-item panel. :last-child rather than a class, so the rule
   holds as brands are added and never needs a markup change. */
.brands-menu__panel a:last-child {
  margin-block-start: var(--s-1);
  padding-block-start: var(--s-2);
  border-block-start: 1px solid var(--c-line-on-dark);
  color: var(--c-on-dark-muted);
}
.brands-menu__panel a:last-child:hover { color: var(--c-accent); }

.site-header__end {
  display: flex;
  align-items: center;
  justify-self: end;
  gap: var(--s-3);
  font-size: var(--t-small);
}

/* The search button. Glass like the language pill beside it rather than gold
   like the cart: the cart is the one ACTION on this bar and stays the only
   filled thing on it. 40px is a real target on a phone, where this control has
   to survive next to the logo and the cart at 360px. */
.search-toggle {
  display: grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  padding: 0;
  border-radius: 999px;
  background: none;
  border: 0;
  color: var(--c-on-scene-muted);
  cursor: pointer;
  transition: color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.search-toggle svg { inline-size: 20px; block-size: 20px; display: block; }
.search-toggle:hover,
.search-toggle:focus,
.search-toggle[aria-expanded="true"] {
  color: var(--c-on-dark);
  background: rgba(10, 22, 14, calc(0.18 + var(--np, 1) * 0.3));
}

/* A switch, so it has to look like one thing with two halves rather than like
   two links that happen to be adjacent. The filled half is where you are and
   the empty half is the one tap that changes it. */
.lang-switch {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  /* Same two layers as the nav pill, and for the same reason. */
  background-color: rgba(10, 22, 14, 0.34);
  background-image: linear-gradient(
    rgba(10, 22, 14, calc(var(--np, 1) * 0.62)),
    rgba(10, 22, 14, calc(var(--np, 1) * 0.62)));
  border: 1px solid rgba(244, 237, 225, 0.12);
  backdrop-filter: blur(16px) saturate(115%);
  -webkit-backdrop-filter: blur(16px) saturate(115%);
}
.lang-switch__seg {
  display: grid;
  place-items: center;
  min-inline-size: 34px;
  block-size: 28px;
  padding-inline: 0.5rem;
  border-radius: 999px;
  font-size: var(--t-meta);
  letter-spacing: 0.06em;
  /* The on-scene 78%, never the flat 62%. This bar is over a photograph and the
     flat value measured 4.30:1 on a lit leaf, under the 4.5:1 floor. */
  color: var(--c-on-scene-muted);
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease);
}
.lang-switch__seg.is-on {
  background: var(--c-accent);
  color: var(--c-primary);
  font-weight: var(--fw-medium);
}
.lang-switch__seg:not(.is-on):hover { color: var(--c-on-dark); }

/* The one filled control on the bar. Gold, pill, and the only thing here that
   is not glass, because it is the only thing here that is an action. */
.cart-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.62rem 1.3rem;
  border-radius: 999px;
  background: var(--c-accent);
  color: var(--c-primary);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  letter-spacing: 0.04em;
  white-space: nowrap;
  transition: background-color 0.3s var(--ease);
}
.cart-btn:hover,
.cart-btn:focus { background: var(--c-accent-soft); }
/* The cart sits at the far side of the button, which in RTL means last in the
   markup. It is the object the button is about, so it is the larger of the two
   things in there and the word is the caption. */
.cart-btn__cart {
  position: relative;
  display: block;
  flex: none;
  inline-size: 24px;
  block-size: 24px;
}
.cart-btn__icon {
  inline-size: 24px;
  block-size: 24px;
  display: block;
}
/* On the cart, not next to it. Pulled up and out so it reads as a badge stuck
   on the corner rather than as part of the drawing. */
.cart-btn__count {
  position: absolute;
  inset-block-start: -7px;
  inset-inline-end: -8px;
  display: grid;
  place-items: center;
  min-inline-size: 17px;
  block-size: 17px;
  padding-inline: 3px;
  border-radius: 999px;
  background: var(--c-primary);
  color: var(--c-accent);
  font-size: 11px;
  line-height: 1;
  font-weight: var(--fw-medium);
}

/* Below 900px the nav was simply display:none, which left a phone with no
   navigation at all: six links unreachable, and the only way to any of them
   was the one CTA in the hero. This is the missing half of that rule. */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  inline-size: 44px;                /* a real 44px target, not a 24px icon */
  block-size: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  inline-size: 24px;
  block-size: 2px;
  margin-inline: auto;
  border-radius: var(--radius-sm);
  background: var(--c-on-dark);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --- Hero ----------------------------------------------------------------- */
/* One full-bleed photograph that the copy and the bottle stand inside. It was
   a 360px framed arch first, and that was wrong: a whole generated scene was
   showing through a 48px strip down each side of the bottle, which reads as a
   texture rather than as a place. The plates are now generated with a
   deliberate empty half and the copy sits in it. */
.hero {
  position: relative;
  /* isolation: isolate used to sit here, sealing the plate/scrim/copy into one
     stacking unit below .journey__intro. P1 (task 8, E3-E5 plan) needs the
     copy to read ABOVE the intro from the first frame while the plate and
     scrim stay BELOW it exactly as before, and one sealed unit cannot do
     both — isolation removed so .hero__copy's own z-index (below) can win
     against .journey__intro's, while .hero__plate/.hero::after/.hero__figure
     keep the z-index they always had and stay under the intro.
     isolation: auto !important defends that removal on the LIVE site: the
     Elementor global kit still serves a stale sheet (post-11.css) carrying
     the old .hero{isolation:isolate}, which re-seals the unit and hides the
     copy (live QA 08-09, critical #1). Until Elementor is retired or its
     kit regenerated, this line must win over any stray legacy rule. */
  isolation: auto !important;
  display: grid;
  align-items: center;
  min-block-size: min(80vh, 780px);
  background: var(--c-primary);
  color: var(--c-on-dark);
  padding-block: var(--s-6) var(--s-7);
  overflow: hidden;
}
/* The wrapper exists only to carry the art direction, so it must not become a
   box between .hero and the image it positions. */
.hero picture { display: contents; }
.hero__plate {
  position: absolute;
  inset: 0;
  z-index: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Anchored to the empty half, not centred. When the viewport is proportionally
     narrower than the plate, cover crops horizontally, and centring would walk
     the foliage into the copy at exactly the widths nobody tests. Anchoring
     here keeps the empty half whole and takes the crop out of the foliage,
     where it costs a narrower band behind the bottle and nothing else. */
  object-position: 100% 50%;
}
/* Hebrew and Arabic both set the copy on the right, which is where the plate's
   empty half already is. English arrives in early August and flips the layout,
   and one mirror handles it: object-position resolves before the transform, so
   the crop still keeps the empty half and the mirror simply moves it to the
   left, under the copy. Foliage has no handedness, so nothing looks reversed. */
[dir="ltr"] .hero__plate { transform: scaleX(-1); }

/* Contrast here is a legal requirement, not a preference, so it is enforced in
   CSS rather than left to whatever the generator produced. Measured on the
   shipped plate by check-plate-deadzone.mjs: the wide plate's copy zone is
   already 15.87:1 for solid ivory and 6.63:1 for the 62% muted colour with no
   scrim at all, so on desktop this is insurance for odd viewport ratios. On
   the phone plate it is load bearing: the muted colour measures 3.01:1 bare
   and 4.80:1 under the scrim. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to left,
    rgba(10, 22, 14, 0.55) 0%,
    rgba(10, 22, 14, 0.34) 34%,
    rgba(10, 22, 14, 0) 66%);
}
[dir="ltr"] .hero::after {
  background: linear-gradient(to right,
    rgba(10, 22, 14, 0.55) 0%,
    rgba(10, 22, 14, 0.34) 34%,
    rgba(10, 22, 14, 0) 66%);
}

.hero__grid {
  position: relative;
  display: grid;
  grid-template-columns: 1.35fr 0.65fr;
  align-items: center;
  gap: clamp(2rem, 6vw, 5rem);
}
/* No z-index of its own (P1, task 8): with isolation gone from .hero above,
   giving the GRID a z-index would re-seal it into one unit again, the same
   problem one level down. Its two children carry their own z-index instead,
   so .hero__copy can sit above .journey__intro while .hero__figure stays
   below it, unchanged. */
/* P1 (task 8, E3-E5 plan): the copy's own z-index, high enough to clear
   .journey__intro's 5 now that nothing above it seals it away. Harmless
   outside the journey too (reduced motion / no-JS hides the intro outright),
   so this is unconditional rather than scoped to .journey. */
.hero__copy {
  position: relative;
  z-index: 6;
}
.hero__title {
  font-size: var(--t-display);
  font-weight: var(--fw-light);
  line-height: 1.14;
  margin-block-end: var(--s-4);
  text-wrap: balance;
}
.hero__sub {
  font-size: var(--t-subtitle);
  /* Not --c-on-dark-muted. This line sits on a photograph now, and the flat
     background value fails the contrast floor on a phone. See tokens.css. */
  color: var(--c-on-scene-muted);
  max-inline-size: 40ch;
  margin-block-end: var(--s-5);
}
/* Two hero actions: the featured bottle (gold) and the full collection
   (ghost, which is built for the dark scene). They wrap on a narrow phone. */
.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
/* z-index 2, same level .hero__grid used to give it via isolation — stays
   under .journey__intro (5) exactly as before; only the copy above escaped. */
.hero__figure { position: relative; z-index: 2; justify-self: center; margin: 0; }
/* Free standing in the scene rather than inside a frame. The shadow is black
   and not the green used on ivory cards, because here it falls on foliage:
   its job is separating the glass from the leaves behind it, not implying a
   floor the photograph does not contain. */
.hero__bottle {
  display: block;
  /* 300 rather than 340. The opening rests the bottle at 0.92 of this, so what
     is actually on screen goes from 313px to 276px: present, and no longer
     competing with the foliage for the picture.
     The vw arm is untouched. It only takes over below about 480px, which is a
     width where the phone rules have replaced this sizing anyway. */
  inline-size: min(300px, 62vw);
  block-size: auto;
  filter: drop-shadow(0 26px 46px rgba(0, 0, 0, 0.60));
}

/* --- The opening ---------------------------------------------------------- */
/* A full screen of foliage with the mark standing in it, which resolves into
   the hero as you scroll: the leaves recede, the mark grows and dissolves, the
   photograph underneath takes over, and the copy and the bottle rise into it.
   Built by wrapping the hero rather than rebuilding it, deliberately. The hero
   is the finished state, its contrast is measured on rendered pixels, and this
   only decides when it becomes visible. That also means the reduced-motion
   path is not a second design to maintain: it is the hero, unwrapped.
   One number crosses the JS/CSS seam, --p, the scroll progress from 0 to 1.
   Everything else is a band expressed here, so the timing is tunable in the
   stylesheet by whoever is looking at it rather than buried in a script. */
.journey {
  position: relative;
  background: var(--c-primary);
  /* Scrub distance is the part beyond one screen. 130vh means the opening
     takes a little over one full flick on a phone. */
  block-size: calc(100vh + 130vh);
  block-size: calc(100svh + 130vh);
  margin-block-start: calc(-1 * var(--chrome-h, 76px));   /* sit under the fixed header */
}
.journey__stage {
  position: sticky;
  inset-block-start: 0;
  block-size: 100vh;
  block-size: 100svh;                 /* svh so mobile browser chrome cannot jump the stage */
  overflow: hidden;
}
/* The header floats over the photograph, so the photograph has to give it
   something to sit on. This plate's upper left is its brightest area, which is
   exactly where the cart and language links sit, and a translucent bar plus a
   blur was not enough: the nav measured below the 4.5:1 floor there.
   Sits above the intro rather than under it so it holds at every point of the
   opening, not only once the plate has dissolved. It stops short of the header's
   own height plus a soft tail, so it reads as light falling off rather than as
   a band. */
.journey__stage::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 8;
  block-size: calc(var(--chrome-h, 76px) + 72px);
  background: linear-gradient(to bottom,
    rgba(10, 22, 14, 0.78) 0%,
    rgba(10, 22, 14, 0.62) 46%,
    rgba(10, 22, 14, 0) 100%);
  pointer-events: none;
}
.journey .hero {
  block-size: 100%;
  min-block-size: 0;
  /* Centre the copy in the screen the visitor sees, not in the whole stage. The
     stage is a full 100svh and runs up under the fixed header, but the base
     .hero centres its content in that whole height, so the header's 132px counts
     as empty space above the copy and the copy is pulled up into it: measured,
     the eyebrow sat 76px below the header with 240px of void under the button,
     top heavy enough that the eyebrow lined up with the logo.
     Offsetting the top padding by the chrome height moves the centre of the
     padding box down to the middle of [chrome, stage bottom], which is the
     middle of the visible area. The symmetric s-5 is breathing room on each side
     of that region. --chrome-h is measured and includes the preview flag, so
     this follows the flag out in production and re-centres on the header alone,
     with the 92px fallback covering the first paint before the measure lands. */
  padding-block: calc(var(--chrome-h, 92px) + var(--s-5)) var(--s-5);
}

/* The opening hero is locked to one screen (the sticky stage), so on a short
   desktop viewport its copy and bottle scale down to fit rather than clipping the
   bottle base and the buttons at the fold. The added announcement ticker makes
   the chrome taller, which is what tightened this on laptop-height screens. The
   bottle becomes height-driven (inline-size auto) so it keeps its aspect ratio. */
@media (min-width: 901px) and (max-height: 820px) {
  .journey .hero__title { font-size: clamp(1.85rem, 4.6vh, var(--t-display)); }
  .journey .hero__sub { margin-block-end: var(--s-4); }
  .journey .hero__bottle { inline-size: auto; block-size: min(46vh, 466px); }
}

/* The site opens as one photograph with the mark in it and nothing else. The
   header, and the preview strip above it, arrive on the same gesture that takes
   the mark away: the chrome assembles itself as the brand dissolves, instead of
   sitting over the picture from the first frame.
   The band starts where the mark starts leaving, 0.24, and completes before the
   mark is fully gone at 0.46, so the two read as one handover rather than as a
   gap with nothing on screen.
   Opacity only, never display or visibility: --chrome-h is measured from these
   two elements, and the negative pull that lets the photograph run under the
   header is derived from it. Taking them out of flow would move the thing they
   sit on and the opening would jump at the moment it is meant to settle.
   Scoped to .has-journey, so reduced motion and a failed script get the header
   they always had, fully visible, immediately. */
html.has-journey .site-header,
html.has-journey .preview-flag,
html.has-journey .topbar {
  opacity: clamp(0, calc((var(--p, 0) - 0.24) / 0.22), 1);
}
/* A keyboard reaches the nav before a scroll does. Following focus into a
   header that cannot be seen is worse than showing it early, so focus brings it
   back whatever the scroll position. */
html.has-journey .site-header:focus-within { opacity: 1; }

.journey__intro {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  overflow: hidden;
  pointer-events: none;
  /* Holds full until 0.24, gone by 0.54, so the mark has the screen to itself
     before anything starts resolving. */
  opacity: clamp(0, calc((0.54 - var(--p, 0)) / 0.30), 1);
}
/* With the film running the opening is one continuous camera move that ends on
   the hero, so the intro must NOT dissolve halfway: it plays the whole scrub and
   hands over at the very end. The last frame matches the hero plate to a mean
   of 5.2/255, so that handover is a crossfade between two near-identical
   pictures and reads as nothing at all. Ending on the real plate rather than on
   the canvas is deliberate: the resting hero is the one whose contrast is
   measured, and it stays that way. */
html.has-film .journey__intro {
  opacity: clamp(0, calc((0.90 - var(--p, 0)) / 0.12), 1);
}
.journey__intro picture { display: contents; }
/* display:contents hands the picture's children to the parent's layout, and a
   <source> is one of those children. In a block box that costs nothing, so this
   went unnoticed. In this grid it became a second row: two auto rows, the free
   space split between them by the default stretch, and the mark pushed 115px
   below the centre place-items asked for. It was landing below its own pocket,
   the ring of shadow that exists to keep gold legible on lit leaves, which is
   the part that made this worth chasing rather than a matter of taste.
   Found by the press target, which is centred by the box model and therefore
   did not inherit the same mistake: 230 against 345 in one measurement. */
.journey__intro picture > source,
.hero picture > source { display: none; }
.journey__intro img.journey__plate {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* A slow push in. The leaves come toward you as they dissolve, which is what
     makes the hero underneath feel like it was always further back. */
  transform: scale(calc(1 + var(--p, 0) * 0.14));
}
/* The plate keeps its CSS push even once the film is running. Cancelling it
   looked tidier and was worse: .has-film lands mid scroll, so the plate would
   snap back from its zoom while the canvas was still fading in over it. It is
   fully covered by then anyway, so there is nothing to gain and a visible jump
   to lose. */

/* The camera move. Covers the stage exactly as the plate does, and is painted
   only once the script has frames to paint, so it never flashes empty over the
   plate it is standing on. */
.journey__film {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  display: block;
  opacity: 0;
  transition: opacity 240ms linear;
}
html.has-film .journey__film { opacity: 1; }
/* Gold on foliage is the one contrast case here that no plate solves, because
   every plate has lit leaf edges somewhere. So the mark carries its own pocket
   of shadow rather than depending on where the leaves happened to fall. */
.journey__pocket {
  position: absolute;
  inset: 0;
  background: radial-gradient(46% 46% at 50% 50%,
    rgba(10, 22, 14, 0.82) 0%,
    rgba(10, 22, 14, 0.58) 52%,
    rgba(10, 22, 14, 0) 100%);
}
/* The pocket exists to hold the mark, so it has to leave with the mark. It used
   to get that for free from the intro dissolving at 0.54; now that the intro
   stays for the whole move, a shadow with nothing in it would ride the camera
   all the way down. Same band as the mark, deliberately. */
html.has-film .journey__pocket {
  opacity: clamp(0, calc((0.42 - var(--p, 0)) / 0.24), 1);
}
.journey__mark {
  position: relative;
  z-index: 2;
  /* Sized for a full screen rather than for a slot. At 340px it read as a logo
     dropped on a photograph; the opening is the one moment the brand gets the
     whole screen, and the mark should take it. */
  inline-size: min(36vw, 440px);
  block-size: auto;
  /* The mark gets the screen to itself first. It holds at full opacity while it
     grows, and only once it has grown does it start to go, which is also when
     the camera starts moving and the chrome arrives. Three things on one beat
     rather than a logo that is already dissolving before it has been read.
     Leaves a touch before the plate does, so the screen is foliage alone for a
     moment rather than cutting from logo straight to copy. */
  opacity: clamp(0, calc((0.46 - var(--p, 0)) / 0.22), 1);
  /* Starts clearly under full size and grows through the whole opaque beat, so
     the entrance reads as "the mark grows in" rather than a logo that is already
     at full size and merely dissolves. 0.66 at rest -> ~1.02 by 0.24 (still
     fully opaque, the moment it lands at full) -> ~1.35 as it fades out by 0.46. */
  transform: scale(calc(0.66 + var(--p, 0) * 1.5));
}

/* P1 contrast fix (task 8, E3-E5 plan). The copy now paints ABOVE the intro
   instead of waiting behind it (see .hero__copy's z-index), and
   check-hero-contrast.mjs measured it against what the intro actually is —
   a photograph plus a moving gold mark — and found ratios as low as 1.00:1,
   over the mark's own gold at 1440. The resting hero's own scrim
   (.hero::after) does not help: it sits UNDER the intro, and the intro's
   opacity is what is being measured through here. No single crop of "a
   photograph and a moving logo" is reliably dark enough on its own, so this
   is a flat scrim across the whole intro, in CSS, never the image — the
   store's own rule for exactly this situation.
   Last child of .journey__intro, so it paints over the plate, film and mark
   alike without a z-index number to keep in step with theirs as they change.
   It rides the SAME opacity .journey__intro already fades on, rather than a
   second timeline: as the intro dissolves toward the resting hero, this goes
   with it, and the resting hero (already measured at 15.87:1 / 6.63:1 with
   no scrim at all) is exactly as bright as it always was.
   NOT a descendant of .hero__copy: check-hero-contrast.mjs's harness hides
   .hero__copy to measure what is behind it, and a scrim living inside the
   very element being hidden would be hidden right along with it — which is
   what actually happened on the first pass at this fix (a pseudo-element on
   .hero__copy itself, measured, found unchanged, because it was invisible
   the moment the harness ran). This lives on the intro instead, which the
   harness never touches, so it measures as what it actually is: the thing
   the copy sits on. */
.journey__scrim {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: rgba(8, 18, 12, 0.78);
}

/* The opening is driven by scrolling and by nothing else — there is no press-to-
   play control any more (it was removed along with the phone auto-play, so the
   whole opening has one driver, the scroll). The stage therefore shows no
   pointer: it is not a click target.
   The bottle sits underneath the film during the scrub and is held
   non-interactive until the hero has settled (.is-done), so a stray click
   during the scrub cannot land on an image that is not yet in its place.
   The copy is NOT held here any more (P1, task 8): it is visible and above
   the film from the first frame (see .hero__copy's z-index and opacity
   below), so its link has to be usable from the first frame too — a visible
   "לכל הבשמים" button a visitor cannot click would be worse than the button
   being hidden. */
html.has-journey .journey:not(.is-done) .hero__figure { pointer-events: none; }

/* The bottle arrives last, on its own now (P1, task 8): the copy used to
   share this exact band so the two moved as one, but the copy no longer
   animates in at all — see .hero__copy immediately below. */
.journey .hero__figure {
  --r: clamp(0, calc((var(--p, 0) - 0.46) / 0.30), 1);
  opacity: var(--r);
  transform: translateY(calc((1 - var(--r)) * 26px));
}
/* P1 (task 8, E3-E5 plan): the title, subtitle and CTA read from the first
   frame at both 390 and 1440, full stop — no --r, no scroll-linked opacity.
   The home page gets 75% of landings and lost 60% of them before anyone saw
   a headline; the fix is that the words are simply there, the same way they
   would be with reduced motion or no JS. The intro (foliage, mark, film)
   still plays over the plate behind this and still dissolves on scroll
   exactly as before — only the two are no longer racing for the same pixels,
   because the copy now paints above the intro's z-index instead of under it. */
.journey .hero__copy {
  opacity: 1;
  transform: none;
}
/* Later with the film, because the camera is still travelling at 0.46 and the
   bottle riding a moving frame reads as a prop on someone else's footage.
   It starts where the handover above ends rather than under it. The intro is a
   layer above the hero, so the bottle rising while the film is still opaque
   would rise invisibly: the first cut of this had the band at 0.74 and the
   whole hero appeared in one jump in the last few percent, which is the pop
   this timing exists to avoid.
   Finishes at 0.90, not 1.0, and that is the whole point of this change. The
   opening used to assemble on the very last pixel of the scrub: measured frame
   by frame, both clips are dead still from about 0.88 on, diff from the last
   frame under 1/255, yet the bottle only reached full size at 1.0. So the one
   moment the hero was whole was the moment the section released, and on a phone,
   which is flicked rather than nudged, you went straight past it. Ending the
   rise at 0.90 leaves 0.90 to 1.0 as a hold: the finished hero, pinned, while
   there is still scroll left to rest on it. */
html.has-film .journey .hero__figure {
  --r: clamp(0, calc((var(--p, 0) - 0.78) / 0.12), 1);
}
/* The bottle is not a third line of copy, so it stops sharing the copy's move.
   It comes up from below the frame, slightly small, and reaches full size as it
   arrives, which reads as the object being set down in the pocket the camera
   opened rather than being switched on in place.
   It starts a little before the copy and travels further, so it leads and the
   words settle around it.
   The origin is the base of the bottle, not its centre: growing from the centre
   makes glass inflate, growing from the foot makes it stand up. */
html.has-film .journey .hero__figure {
  /* Rises 0.76 to 0.90, a touch ahead of the copy, and comes to rest where the
     camera does. It is the element the owner could not see on a phone, because
     it finished at 1.0 with no scroll left to hold it. It now finishes at 0.90
     and is held, at full size, through the last tenth of the scrub. It still
     starts behind the intro, which is opaque until 0.78 and gone by 0.90, so it
     spends its first sliver hidden and then emerges rising as the film clears
     rather than popping in once it has. */
  --r: clamp(0, calc((var(--p, 0) - 0.76) / 0.14), 1);
  transform-origin: 50% 100%;
  /* Starts smaller and comes to rest below full size, so the arrival is a real
     change of scale and the bottle sits in the scene rather than filling it.
     0.70 to 0.92, not 0.86 to 1: the travel is more than twice what it was and
     the resting size leaves the foliage room to be the picture. */
  transform:
    translateY(calc((1 - var(--r)) * 96px))
    scale(calc(0.70 + var(--r) * 0.22));
}
/* The load-time rise animation would fight the scroll reveal for the same
   properties, and it has nothing to add here: the journey is the entrance. */
.journey .rise { animation: none; opacity: 1; transform: none; }

/* Without JS there is no --p, so every band would sit at its zero. The bottle
   would open mid-rise with nothing to finish the animation (the copy carries
   no such risk any more: since P1 its opacity is a flat 1, not --r-driven, so
   a --p that never arrives cannot leave it stranded either). The journey
   therefore only exists once a script has confirmed it can drive it.
   Everything above is scoped to that class; this is the state before it. */
/* The negative pull is deliberately NOT reset here. It was at first, and that
   left the transparent header floating over the bare strip of body background
   that the padding had opened, with nothing behind its links: measured at
   1.53:1 for every pixel of the bar. A transparent header needs the dark
   photograph underneath it in every state, not only when the opening runs. */
html:not(.has-journey) .journey { block-size: auto; }
html:not(.has-journey) .journey__stage { position: static; block-size: auto; }
html:not(.has-journey) .journey__intro { display: none; }
html:not(.has-journey) .journey .hero { block-size: auto; min-block-size: min(80vh, 780px); }
html:not(.has-journey) .journey .hero__figure { --r: 1; }

/* Reduced motion lands on exactly the same place as no JS: the finished hero,
   with its measured contrast, and no scroll hijack. The script does not add
   .has-journey when the query matches, so this needs no rules of its own. */

/* --- Value strip ---------------------------------------------------------- */
.values {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-block: 1px solid var(--c-hairline);
}
.values__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--s-5) var(--s-4);
  text-align: center;
  font-size: var(--t-small);
  color: var(--c-text);
}
.values__item + .values__item { border-inline-start: 1px solid var(--c-hairline); }
/* The four store reassurances carry the same line-icon language as the product
   page trust bar: check-circle, delivery, padlock, gift box. */
.values__icon {
  inline-size: 26px;
  block-size: 26px;
  fill: none;
  stroke: var(--c-primary);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  margin-block-end: var(--s-3);
}
.values__item strong {
  display: block;
  font-weight: var(--fw-medium);
  margin-block-end: var(--s-1);
}
.values__item span { color: var(--c-muted-text); }
/* Four marks wrap to a 2x2 on a phone. The single-row inline dividers would
   then land in the wrong places, so the odd cells drop their start border and
   the second row gains a top border. */
@media (max-width: 720px) {
  .values { grid-template-columns: 1fr 1fr; }
  .values__item:nth-child(odd) { border-inline-start: 0; }
  .values__item:nth-child(n+3) { border-block-start: 1px solid var(--c-hairline); }
}

/* --- Section head --------------------------------------------------------- */
.section-head {
  max-inline-size: 62ch;
  margin-block-end: var(--s-6);
}
.section-head--center {
  margin-inline: auto;
  text-align: center;
}
.section-head .title { margin-block-end: var(--s-3); }

/* --- Scent grid ----------------------------------------------------------- */
.scents {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
/* A column, so the price rule can be pinned to the bottom. Hook lines differ
   in length between scents, and with plain blocks a two-line hook pushed that
   card's price 25px below its neighbours' and broke the row's baseline. */
.scent {
  position: relative; /* the stretched-link ::after and the quick-add button both anchor to this box (fix round 1) */
  display: flex;
  flex-direction: column;
  block-size: 100%;
}
.scent .arch {
  aspect-ratio: 3 / 3.9;
  margin-block-end: var(--s-4);
}

/* The signature interaction: the bottle is pulled up toward the crown of
   the arch. The brand is called iMagnet. */
@media (prefers-reduced-motion: no-preference) {
  .scent .arch__bottle { transition: transform 0.55s var(--ease), opacity 0.45s var(--ease); }
  .scent .arch__box { transition: opacity 0.45s var(--ease); }
  /* Two focus selectors, not one: imagnet_shop_card()'s cards (fix round 1)
     no longer have <a class="scent"> — .scent is a plain div and the anchor
     moved to the nested .scent__link — while imagnet_related_card() and the
     home brand shelf still render <a class="scent"> directly. :focus-visible
     covers those; :has(.scent__link:focus-visible) covers the shop/brand
     cards, keyboard-tabbing onto the (now nested) product-name link. */
  .scent:hover .arch__bottle,
  .scent:focus-visible .arch__bottle,
  .scent:has(.scent__link:focus-visible) .arch__bottle { transform: translateY(-14px); }
}
.scent:hover .arch--light { border-color: var(--c-accent); }

/* Hovering reveals the box the bottle comes in. Savour and Pour Homme are the
   same olive at card size, and the box is what separates them: a different
   colour each, and five of six carry a readable name. It is also the gifting
   proof, which is this store's commercial angle.
   This rides the gesture that already lifts the bottle instead of adding a
   second signature: the bottle is pulled away and what is under it is the box.
   Two deliberate choices. It is a background rather than an <img>, so a screen
   reader hears each product once instead of twice. And it lives inside a hover
   query, so a phone never downloads 221KB of boxes it has no way to reveal.
   Which is also the limit: on a phone this does nothing at all. */
/* display:none on a touch device, so the box is never even fetched there.
   Paired with loading="lazy" this keeps 221KB of boxes off a phone that has no
   way to reveal them. It was first built as a background-image driven by a
   custom property, which was wrong: a relative url() inside a custom property
   resolves against the stylesheet consuming the var(), not the document
   declaring it, so ../assets/ resolved from css/ and pointed at
   site-preview/assets, which does not exist. An <img> src has no such trap. */
.arch__box { display: none; }

@media (hover: hover) and (pointer: fine) {
  .scent .arch__box {
    display: block;
    position: absolute;
    inset-block-end: 10%;
    /* Centred with inset-inline + auto margins, never with
       inset-inline-start:50% plus translate:-50%. That idiom shifts twice in
       the same direction under RTL and threw the box almost entirely outside
       the arch, where overflow:hidden ate it. The same trap is documented for
       .info__bubble further down this file. */
    inset-inline: 0;
    margin-inline: auto;
    inline-size: 94%;
    opacity: 0;
    pointer-events: none;
  }
  .scent:hover .arch__box,
  .scent:focus-visible .arch__box,
  .scent:has(.scent__link:focus-visible) .arch__box { opacity: 1; }
  /* :not(:only-child) — the bottle only fades out when there is a box to fade
     IN behind it. A card whose arch holds the bottle alone (the related-scent
     strip on the product page, and any live product with a featured image but
     no gift-box gallery shot) would otherwise hover to an empty arch. */
  .scent:hover .arch__bottle:not(:only-child),
  .scent:focus-visible .arch__bottle:not(:only-child),
  .scent:has(.scent__link:focus-visible) .arch__bottle:not(:only-child) { opacity: 0; }
}

/* The stretched-link pattern (fix round 1). imagnet_shop_card() no longer
   wraps the whole card in <a class="scent"> — the quick-add <form>/<button>
   was nested inside that anchor, interactive-inside-interactive, invalid by
   the HTML content model and a real double-default-action risk (form submit
   AND anchor navigate racing on one click, worse with JS blocked). The real
   anchor is now `.scent__link`, wrapping only the product name; this ::after
   stretches it (position:absolute, inset:0) to cover the FULL `.scent` box,
   so the whole card is still one click to the product page. z-index:1 —
   under the quick-add button below — so a click that lands on the button
   hits the button and never this overlay underneath it.
   `.scent__link` itself stays position:static on purpose — an absolutely
   positioned ::after skips a static ancestor when resolving its containing
   block, so `inset:0` reaches all the way up to `.scent` (position:relative)
   and stretches to the FULL card. Positioning `.scent__link` itself would
   shrink the ::after back down to just the name's own box. */
.scent__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* The quick add-to-cart control (P4, task 2). `.scent__media` wraps the arch
   ONLY — never the arch itself — so `.arch__bottle:not(:only-child)` above
   still counts just the bottle/box pair and cannot be tricked into treating
   a card with no box as one that has one. The button sits on top, outside
   `.arch`'s own overflow:hidden, so it is never clipped.
   Visible and tappable at EVERY width, on purpose: 89% of this store's
   traffic is mobile, where hover does not exist, so unlike `.arch__box`
   above this is never gated behind `@media (hover: hover)`.
   z-index:2 — deliberately above `.scent__link::after` (z-index:1) — is
   what makes a press on this button land on the button and not the
   stretched link underneath it; being outside that link's own DOM subtree
   is what stops it from ever navigating in the first place. */
.scent__media { position: relative; }
.scent__add {
  position: absolute;
  z-index: 2;
  inset-block-end: var(--s-3);
  inset-inline-end: var(--s-3);
  margin: 0;
}
.scent__add-btn {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: 50%;
  border: 1px solid var(--c-arch-on-dark);
  background: var(--c-primary);
  color: var(--c-on-dark);
  cursor: pointer;
  transition: background-color 0.25s var(--ease), transform 0.2s var(--ease);
}
.scent__add-btn svg { inline-size: 1.1rem; block-size: 1.1rem; }
.scent__add-btn:hover,
.scent__add-btn:focus-visible { background: var(--c-accent); }
.scent__add-btn:active { transform: scale(0.94); }
.scent__add-btn.is-busy { opacity: 0.72; cursor: progress; }

.scent__cat {
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  color: var(--c-muted-text);
  margin-block-end: var(--s-1);
}
/* The brand line sits above each scent name, in the brand green rather than the
   muted gender line, so the card reads brand then product. It is a dedicated
   line because more brands than MaxVolmer may join the shelf later. */
.scent__brand {
  font-size: var(--t-meta);
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  color: var(--c-primary);
  margin-block-end: var(--s-2);
}
.scent__name {
  font-size: 1.05rem;
  color: var(--c-primary);
  margin-block-end: var(--s-2);
}
.scent__hook {
  font-size: var(--t-small);
  line-height: 1.7;
  margin-block-end: var(--s-3);
  text-wrap: pretty;
}
.scent__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-block-start: auto;   /* pins the price rule to the card's bottom edge */
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--c-hairline);
}
.scent__price {
  font-size: 1.15rem;
  font-weight: var(--fw-book);
  color: var(--c-primary);
}
.scent__family { font-size: var(--t-meta); color: var(--c-muted-text); }

/* --- Story ---------------------------------------------------------------- */
.story__inner {
  max-inline-size: 60ch;
  margin-inline: auto;
  text-align: center;
}
.story__inner .title { margin-block-end: var(--s-4); }
.story__body {
  color: var(--c-muted-text);
  font-size: var(--t-subtitle);
  margin-block-end: var(--s-5);
  text-wrap: pretty;
}
/* On the deep green the body flips to the on-dark muted ink. The home page's
   #story teaser is a dark band and reads the same as before; the story page
   adds light sections, where the ivory-on-ivory default would be invisible. */
.band-dark .story__body { color: var(--c-on-dark-muted); }
.story__inner .btn { display: inline-flex; }

/* Story page: tropical foliage rails down both outer edges.
   iMagnet jungle foliage, colour-matched to the site leaves and rebuilt as a
   vertically seamless mirror loop (transparent PNGs in ../assets/story-page/).
   The rails sit ABOVE the flat section colours and BELOW the text, so the leaves
   spill from the green bands onto the ivory sections as one continuous motif
   down the scroll. They reach UP behind the transparent fixed header so the
   foliage begins at the very top of the page, not below the menu. */
.page-story main { position: relative; }
.foliage-rail {
  position: absolute;
  inset-block-start: calc(-1 * var(--chrome-h, 76px));  /* start at the page top, behind the header */
  inset-block-end: 0;
  inline-size: clamp(72px, 15vw, 280px);   /* full opacity at every size, incl. mobile */
  z-index: 0;
  pointer-events: none;
  background-repeat: repeat-y;
  background-size: 100% auto;
}
/* WebP, not the PNG masters. The masters are 806x5376 RGBA at ~4.95 MB EACH, and
   the rail renders at most 280px wide (56px on a phone), so shipping them made
   the story page a 10.3 MB cold load on mobile for two decorative tiles: 98% of
   the page's weight, at a 14x oversample. Re-encoded by
   scripts/build-rail-webp.mjs, which mirrors the top half rather than resizing
   the whole tile, because these are exact vertical mirrors and that symmetry is
   what makes repeat-y seamless. Phones take a 160px cut instead (below). */
.foliage-rail--left  { left: 0;  background-image: url(../../assets/story-page/foliage-rail-left.webp);  background-position: left top; }
.foliage-rail--right { right: 0; background-image: url(../../assets/story-page/foliage-rail-right.webp); background-position: right top; }

/* The copy stays centred and its width tracks the viewport, so it always sits
   in the clear centre the two rails frame and never touches a leaf, on mobile
   or desktop. 58vw leaves ~21vw of clear gutter each side; the leaves reach at
   most ~12vw in, so there is always daylight between the words and the plants. */
.page-story .container { position: relative; z-index: 1; }
.page-story .page-head,
.page-story .story__inner {
  max-inline-size: min(56ch, 58vw);
  margin-inline: auto;
  text-align: center;
}
.page-story .page-head .lede { max-inline-size: min(52ch, 56vw); margin-inline: auto; }

/* Bold words inside the running copy carry the full ink (not the muted body
   colour) so the emphasis actually pops and guides the eye. */
.story__body strong,
.lede strong { font-weight: var(--fw-medium); color: var(--c-text); }
.band-dark .story__body strong,
.band-dark .lede strong { color: var(--c-on-dark); font-weight: var(--fw-medium); }

/* On phones the leaves stay at full strength but slim, and the copy narrows to
   a centred column with clear daylight on both sides. */
@media (max-width: 720px) {
  /* Owner, 2026-07-28: the leaves should read bigger on a phone. 56px -> 88px.
     The constraint is not taste, it is the 5568 contrast floor: the rails sit
     BEHIND the copy (z-index 0 against the container's 1), so a leaf that
     reaches under a line of text stops that text sitting on flat ivory and the
     measured worst pixel moves off the background it was cleared against. The
     copy column therefore narrows in step, from 56vw to 54vw.
     88px was tried first and gave the leaves the presence he asked for, but at
     50vw the h1 broke into six two-word lines on a 390px phone, which is a worse
     page than the one that started with thinner leaves. 72px is a visible +29%
     and costs the copy almost nothing. Re-measured on rendered pixels at
     320/360/390/430: every worst pixel still the flat ivory. */
  .foliage-rail { inline-size: 72px; }
  /* A 56px rail has no use for a 560px tile. The phone cut is 160px (still ~2.9x
     for any DPR) and about a seventh of the bytes, which matters most exactly
     where the connection is worst. */
  .foliage-rail--left  { background-image: url(../../assets/story-page/foliage-rail-left-sm.webp); }
  .foliage-rail--right { background-image: url(../../assets/story-page/foliage-rail-right-sm.webp); }
  .page-story .page-head,
  .page-story .story__inner,
  .page-story .page-head .lede { max-inline-size: 54vw; }
}

/* The scene-frame bands (home story teaser, shop hero + closing CTA) carry the
   leaves baked into the background at the section edges. On phones the cover
   crop pulls those leaves inward, so the copy takes extra side room to keep
   clear daylight between the words and the plants. */
@media (max-width: 720px) {
  .band-dark--scene .container,
  .page-hero .container { padding-inline: 18vw; }
}

/* On the story page the side rails run up behind the header, so the logo (at the
   container's right edge) would otherwise sit under the right rail's leaves.
   Nudge it inward, tied to the rail's own reach (~15vw), capped so it never
   drifts too far on wide screens where the leaves are already clear. */
.page-story .site-header__logo { margin-inline-start: clamp(0px, 11vw, 130px); }

/* --- Gifting -------------------------------------------------------------- */
.gift {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(2rem, 6vw, 5rem);
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  padding: clamp(2rem, 5vw, 4rem);
}
.gift__title { margin-block-end: var(--s-3); }
.gift__body { color: var(--c-muted-text); margin-block-end: var(--s-5); }
.gift__figure .arch {
  inline-size: min(280px, 70vw);
  aspect-ratio: 3 / 3.6;
  margin-inline: auto;
}

/* --- Closing + footer ----------------------------------------------------- */
.closing {
  text-align: center;
  padding-block: var(--s-7);
}
.closing .title { margin-block-end: var(--s-5); }

/* Every page ends with a dark band directly above this dark footer, and two
   green blocks in a row read as one undifferentiated field: the closing call
   to action was being swallowed. A gold hairline is the only separator this
   palette needs. */
.site-footer {
  padding-block: var(--s-5) var(--s-4);
  border-block-start: 1px solid var(--c-line-on-dark);
}
/* Four columns since 2026-09-08 (home/shell copy brief, section 13): the brand,
   then קנייה / מותגים / שירות ומידע. The brands links used to be a tail on the
   information list, which is where a visitor looking for a brand page would
   never think to look. */
.site-footer__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: var(--s-5);
  padding-block-end: var(--s-4);
}
/* The service line (brief section 13). Sits between the columns and the
   copyright, on its own hairline, so the offer of help is a statement rather
   than a fifth column of links. The button is .btn--ghost because this band is
   dark: gold text on the green, which is the one outline treatment that holds
   contrast here. */
.site-footer__service {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block: var(--s-4);
  border-block-start: 1px solid var(--c-line-on-dark);
}
.site-footer__service-text {
  font-size: var(--t-small);
  color: var(--c-on-dark);
  margin: 0;
}
.site-footer__logo img { block-size: 56px; inline-size: auto; margin-block-end: var(--s-2); }
.site-footer__tag {
  font-size: var(--t-small);
  color: var(--c-on-dark-muted);
  max-inline-size: 32ch;
}
/* The social row sits under the brand: line icons that pick up the muted value
   and warm to gold on hover, the same affordance as the link lists. */
.site-footer__social {
  display: flex;
  gap: var(--s-3);
  list-style: none;
  padding: 0;
  margin-block-start: var(--s-3);
}
.site-footer__social a {
  display: inline-flex;
  color: var(--c-on-dark-muted);
  transition: color .15s ease;
}
.site-footer__social a:hover { color: var(--c-accent); }
.site-footer__social svg {
  inline-size: 20px;
  block-size: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* WhatsApp reads clearest as its solid glyph, so it opts out of the stroke
   treatment the monoline icons share. */
.site-footer__social svg.is-solid { fill: currentColor; stroke: none; }
.site-footer h3 {
  font-size: var(--t-meta);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  color: var(--c-accent);
  margin: 0 0 var(--s-3);
}
.site-footer li { margin-block-end: var(--s-2); font-size: var(--t-small); }
.site-footer li a:hover { color: var(--c-accent); }
.site-footer__base {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--c-line-on-dark);
  font-size: var(--t-meta);
  color: var(--c-on-dark-muted);
}
.site-footer__by { display: inline-flex; align-items: center; gap: 0.6ch; }
.site-footer__webside {
  block-size: 15px;
  inline-size: auto;
  display: inline-block;
  vertical-align: middle;
  opacity: 0.85;
}

/* --- Shop page ------------------------------------------------------------ */
.page-head {
  padding-block: var(--s-6) var(--s-5);
  border-block-end: 1px solid var(--c-hairline);
}
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding-block: var(--s-5);
}
.filter {
  padding: 0.5rem 1.25rem;
  font-size: var(--t-small);
  border: 1px solid var(--c-hairline);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  transition: all 0.3s var(--ease);
}
.filter:hover { border-color: var(--c-accent); }
/* Two selectors for one state. In the static preview a filter is a <button>, so
   it says aria-pressed; on the live store each filter is a real destination (a
   product-category archive, or the shop with the category's query), so it is an
   <a> and says aria-current="page" — aria-pressed belongs to toggle buttons, not
   to navigation. Both must look like the approved pressed chip. */
.filter[aria-pressed="true"],
.filter[aria-current="page"] {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: var(--c-on-dark);
}
/* The filter row is a <nav> on the live store, so it needs the anchor reset the
   preview's <button> got for free. */
a.filter { text-decoration: none; color: inherit; display: inline-block; }
a.filter[aria-current="page"] { color: var(--c-on-dark); }

/* --- Product page --------------------------------------------------------- */
.crumbs {
  padding-block: var(--s-4);
  font-size: var(--t-meta);
  color: var(--c-muted-text);
}
.crumbs a:hover { color: var(--c-primary); }

.product {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 6vw, 5rem);
  padding-block: var(--s-5) var(--s-6);
  align-items: start;
}

/* Related products. A green band closes the page, and the cards run in a
   compact variant so the strip supports the product rather than competing
   with it: shorter arch, no hook line, smaller type. */
.related-band { padding-block: var(--s-6) var(--s-4); }
.scents--compact {
  gap: var(--s-4);
  /* Narrower than the container as well as shorter, so the strip reads as a
     footnote to the product rather than a second shop page. */
  max-inline-size: 860px;
  margin-inline: auto;
}
.scents--compact .arch {
  aspect-ratio: 3 / 2.5;
  margin-block-end: var(--s-3);
}
.scents--compact .scent__name { font-size: 0.95rem; }
.scents--compact .scent__price { font-size: 1.05rem; }
.scents--compact .arch__pending img { inline-size: 60px; }
.scents--compact .arch__pending { margin-block-end: 16%; gap: var(--s-2); }

/* Scent cards sitting on the deep green need their own ink. */
.band-dark .scent__name { color: var(--c-on-dark); }
.band-dark .scent__cat,
.band-dark .scent__family,
.band-dark .arch__pending span { color: var(--c-on-dark-muted); }
.band-dark .scent__price { color: var(--c-accent); }
.band-dark .scent__foot { border-block-start-color: var(--c-line-on-dark); }
/* The info column is much taller than the media column, so on desktop the
   media sticks and the right side stops being empty on the way down.
   .product uses align-items:start, which is what lets sticky work here.

   The catch is laptop screens. A stuck element taller than the viewport can
   never be scrolled to the bottom of, so the image gets clipped and stays
   clipped. So the whole media column is capped against the viewport height
   and shrinks to fit rather than being cut. The factor 0.66 is derived from
   what the column actually stacks: the arch at its 3/3.8 ratio, its 16px
   margin, and the thumbnail strip. On a tall desktop 100% wins and nothing
   changes. */
@media (min-width: 901px) {
  .product__media {
    position: sticky;
    inset-block-start: var(--s-5);
    inline-size: min(100%, calc((100vh - 5rem) * 0.66));
    inline-size: min(100%, calc((100dvh - 5rem) * 0.66));
    margin-inline: auto;
  }
}
.product__media .arch {
  aspect-ratio: 3 / 3.8;
  margin-block-end: var(--s-3);
}

/* --- Product gallery ------------------------------------------------------
   The arch becomes the viewport for a horizontal scroll-snap track. The swipe
   on a phone is then the browser's own, which is what makes it smooth on iOS
   Safari, and the arrows and thumbnails are enhancements layered on top. The
   track is laid over the arch's glow rather than replacing the arch, so the
   frame, its hairline and its interior wash are all unchanged. */
.gallery__track {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
  border-radius: inherit;
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  display: grid;
  place-items: end center;   /* the bottle stands on the arch floor, as before */
  min-inline-size: 0;
}
/* The close-up is landscape, so it centres rather than standing. */
.gallery__slide--center { place-items: center; }
.gallery__detail {
  position: relative;
  inline-size: 90%;
  max-block-size: 72%;
  object-fit: contain;
  filter: drop-shadow(0 16px 26px rgba(var(--rgb-primary), 0.22));
}

/* The lifestyle slide: the real bottle inside a generated jungle-and-gold
   scene. Unlike the packshot slides it is a full photograph, so it fills the
   arch edge to edge and inherits its rounded crown, rather than floating on the
   light interior. The scene carries its own light, so it needs none of the
   arch's glow. */
.gallery__slide--scene { place-items: stretch; }
.gallery__scene {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* Circular controls pinned to the arch's vertical centre, where its sides are
   straight and full width so the round radius never clips them. Green fill with
   an ivory chevron reads on the light interior at any slide. Hidden until the
   script marks the gallery ready, and desktop only: a phone swipes. */
/* Relative wrapper around the arch only, so the arrows can be positioned
   against the image's own box and sit just outside it without the arch's
   overflow:hidden clipping them. */
.gallery-frame { position: relative; }
.gallery__arrow {
  position: absolute;
  inset-block-start: 50%;
  translate: 0 -50%;
  z-index: 2;
  display: none;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--c-primary);
  color: var(--c-on-dark);
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(var(--rgb-primary), 0.30);
  transition: background-color 0.25s var(--ease);
}
.gallery__arrow:hover,
.gallery__arrow:focus { background: var(--c-primary-soft); }
.gallery__arrow svg { inline-size: 42%; block-size: 42%; }
/* Outside the image, not on top of it. The owner asked for this directly: an
   arrow sitting on the photograph hides a good part of it. The arrows now live
   in .gallery-frame, a relative wrapper around the arch that does NOT clip, so
   they can sit just beyond the arch's edges in the column's own breathing room.
   2.75rem is the arrow's own size, plus a small gap. */
.gallery__arrow--prev { inset-inline-start: calc(-1 * (2.75rem + 0.6rem)); }   /* just past the right edge in RTL */
.gallery__arrow--next { inset-inline-end: calc(-1 * (2.75rem + 0.6rem)); }     /* just past the left edge in RTL  */
@media (min-width: 901px) {
  /* Sibling of the arch now, revealed once the gallery marks itself ready. */
  .gallery.is-ready ~ .gallery__arrow { display: grid; }
}

/* Thumbnails, now selectors rather than decoration. One per slide, the active
   one carries a green border, and the image inside is decorative because the
   button names itself. */
.product__thumbs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-2);
}
.product__thumb {
  aspect-ratio: 4 / 3;   /* square thumbs cost too much of the capped height */
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  background: rgba(var(--rgb-primary), 0.035);
  padding: 0;
  overflow: hidden;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: border-color 0.25s var(--ease);
}
.product__thumb img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
}
.product__thumb:hover { border-color: var(--c-accent); }
.product__thumb.is-active { border-color: var(--c-primary); }

/* The H1 holds the display name AND the qualifying terms, so the heading
   Google reads carries "בושם", the concentration and the size, while the
   page still leads with the wordmark. */
.product__name {
  margin-block-end: var(--s-2);
  font-weight: var(--fw-light);
}
.product__name-latin {
  display: block;
  font-size: clamp(2rem, 4vw, 2.9rem);
  color: var(--c-primary);
  line-height: 1.1;
}
.product__name-sub {
  display: block;
  margin-block-start: var(--s-2);
  font-family: var(--f-he);
  font-size: var(--t-small);
  font-weight: var(--fw-light);
  letter-spacing: normal;
  text-transform: none;
  color: var(--c-muted-text);
}
.product__cat { font-size: var(--t-meta); letter-spacing: 0.1em; color: var(--c-muted-text); }
.product__hook {
  font-size: var(--t-subtitle);
  line-height: 1.7;
  margin-block: var(--s-4);
  text-wrap: pretty;
}
.product__price {
  font-size: 1.9rem;
  font-weight: var(--fw-book);
  color: var(--c-primary);
  margin-block-end: var(--s-1);
}
.product__vat { font-size: var(--t-meta); color: var(--c-muted-text); margin-block-end: var(--s-4); }

.product__buy {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-block-end: var(--s-4);
}
.product__buy .btn { flex: 1 1 12rem; justify-content: center; }

/* --- Sticky buy bar ------------------------------------------------------
   Once the price and add-to-cart have scrolled past, they follow the reader at
   the foot of the screen. Deep green so it reads as a layer over the ivory page
   rather than part of it, and so the gold button keeps the contrast it has
   everywhere else (ivory on this green measures 12.26:1, gold 8.2:1).
   z-index sits BELOW the header (100) and the announcement strip (101): this is
   the bottom of the screen, but a phone's URL bar collapsing can bring the two
   close, and the chrome should win.
   It is translated out rather than display:none so it can slide, and
   `visibility` follows so nothing inside is tabbable while it is away. */
.buybar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 99;
  background: var(--c-primary);
  color: var(--c-on-dark);
  border-block-start: 1px solid var(--c-line-on-dark);
  padding-block: var(--s-3);
  /* Clear of a phone's home indicator. */
  padding-block-end: calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  visibility: hidden;
}
.buybar.is-on { transform: none; visibility: visible; }
@media (prefers-reduced-motion: no-preference) {
  .buybar { transition: transform 0.35s var(--ease), visibility 0.35s var(--ease); }
}
.buybar__inner {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
/* The name and price share a block that takes the free space, so the stepper and
   the button stay put at the far end whatever the name's length. */
.buybar__id {
  flex: 1 1 auto;
  min-inline-size: 0;             /* lets a long name ellipsize instead of pushing */
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
.buybar__name {
  font-size: var(--t-meta);
  /* The FLAT-green muted (62%, 5.6:1), not the on-scene one (78%). The bar is a
     solid fill, not a photograph, and the two values exist precisely so a flat
     surface is not given the scene's compensation. */
  color: var(--c-on-dark-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.buybar__price {
  font-size: 1.15rem;
  color: var(--c-on-dark);
}
/* The stepper inverts onto the dark bar: its own borders and glyphs are drawn
   for the ivory page. */
.buybar .qty { border-color: var(--c-line-on-dark); flex: none; }
.buybar .qty__btn { color: var(--c-on-dark); }
.buybar .qty__btn:hover,
.buybar .qty__btn:focus { background: rgba(255, 255, 255, 0.09); }
.buybar .qty__input { color: var(--c-on-dark); border-inline-color: var(--c-line-on-dark); }
.buybar .btn { flex: 0 1 14rem; justify-content: center; }
/* Scoped by .buybar (0,2,0) on purpose, not written as a bare .buybar__form
   (0,1,0). In the theme this form also carries WooCommerce's conventional `cart`
   class, and the design's own `.cart` rule — the CART PAGE's two-column grid with
   a ~64px gap — is later in this file, so at equal specificity it would win and
   split the stepper from the button. Exactly the collision woo-brand.css already
   documents for .product__buy.cart. */
.buybar .buybar__form {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  grid-template-columns: none;
  padding-block: 0;
}

@media (max-width: 560px) {
  /* On a phone the button is what matters; the stepper shrinks to its glyphs and
     the name line is dropped so the price and the button both keep their size.
     Every row item also gets `min-inline-size: 0`. Without it a flex item will
     not shrink below its own min-content, and the row here is price + stepper +
     button: on a 360px phone their combined min-content exceeded the container,
     so the row OVERFLOWED instead of fitting, pushing the minus button and the
     price off the side of the screen and slicing the quantity to a sliver that
     reads as a dot. In the theme there is an extra flex level (the real
     add-to-cart <form> wrapping the stepper and the button), which has the same
     default and has to be told the same thing. */
  .buybar { padding-block: var(--s-2); padding-block-end: calc(var(--s-2) + env(safe-area-inset-bottom, 0px)); }
  .buybar__inner { gap: var(--s-2); }
  .buybar__name { display: none; }
  .buybar__id { flex: 0 0 auto; min-inline-size: 0; }
  .buybar .buybar__form { flex: 1 1 auto; min-inline-size: 0; gap: var(--s-2); }
  .buybar .qty__btn { inline-size: 2rem; }
  /* Wider than the buttons so a two-digit quantity still fits. */
  .buybar .qty__input { inline-size: 2.5rem; }
  .buybar .btn { flex: 1 1 auto; min-inline-size: 0; padding-inline: var(--s-2); white-space: nowrap; }
}

/* Quantity stepper. Sized so the two buttons clear a 24px touch target and
   the whole control matches the add-to-cart height beside it. */
.qty {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--c-primary);
  border-radius: var(--radius-sm);
  flex: none;
}
.qty__btn {
  inline-size: 2.75rem;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--c-primary);
  font-size: 1.15rem;
  line-height: 1;
  transition: background-color 0.25s var(--ease);
}
.qty__btn:hover,
.qty__btn:focus { background: rgba(var(--rgb-primary), 0.07); }
.qty__input {
  inline-size: 3rem;
  border: 0;
  border-inline: 1px solid var(--c-hairline);
  background: transparent;
  text-align: center;
  font: inherit;
  font-size: var(--t-small);
  color: var(--c-text);
  /* An input carries browser padding and a min width of its own, and a number
     input additionally reserves room for a spinner on engines that ignore
     `appearance: textfield`. On a narrow control that is enough to squeeze the
     digit down to a smudge. Reset all three so the width declared above is the
     width the digit actually gets. */
  padding: 0;
  min-inline-size: 0;
  -moz-appearance: textfield;
  appearance: textfield;
}
.qty__input::-webkit-outer-spin-button,
.qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* The three reassurances. The owner asked for these to carry weight, so
   they are a bordered three-up with icons rather than a thin caption row. */
.trust {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--c-hairline);
  background: var(--c-surface);
  border-radius: var(--radius);
  overflow: hidden;                 /* so the inner dividers stop at the rounded corners */
  margin-block-end: var(--s-5);
}
.trust__item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-3);
  font-size: var(--t-meta);
  line-height: 1.45;
  color: var(--c-text);
}
.trust__item + .trust__item { border-inline-start: 1px solid var(--c-hairline); }
.trust__item svg {
  inline-size: 20px;
  block-size: 20px;
  flex: none;
  stroke: var(--c-primary);
  fill: none;
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* K7 (G7, task 5): the free-shipping line, a sibling of .trust rather than a
   fourth grid cell — .trust is a fixed three-up (repeat(3, 1fr)) and a fourth
   item there would either overflow the row or need the grid rebuilt. */
.trust-shipping {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  text-align: center;
  margin-block-end: var(--s-5);
}

.product__desc { margin-block-end: var(--s-5); text-wrap: pretty; }
.product__sample { margin-block: var(--s-4); padding: var(--s-4); border: 1px solid currentColor; border-radius: 8px; }
.product__sample p { margin-block: 0 .5em; }
.product__sample p:last-child { margin-block-end: 0; }

/* Fragrance pyramid. Head, heart, base genuinely are a sequence in
   perfumery, so the ordered structure carries real information. */
.pyramid__intro {
  font-size: var(--t-small);
  color: var(--c-muted-text);
  margin-block-end: var(--s-4);
}

/* Drawn as an actual pyramid. Each row owns its own band, so the bands can
   never drift out of line with their text the way a single background
   shape would. The three tints ascend with depth, which is real: the base
   is what is still on the skin hours later. A 2px gap does the separating,
   never a stroke. */
.pyramid { margin-block-end: var(--s-5); display: grid; gap: 2px; }
.pyramid__row {
  display: grid;
  grid-template-columns: 104px 3.5rem 1fr;
  gap: var(--s-3);
  align-items: center;
}
.pyramid__band {
  block-size: 34px;
  background: var(--c-primary);
}
.pyramid__row:nth-child(1) .pyramid__band {
  clip-path: polygon(50% 0, 66% 100%, 34% 100%);
  opacity: 0.22;
}
.pyramid__row:nth-child(2) .pyramid__band {
  clip-path: polygon(34% 0, 66% 0, 83% 100%, 17% 100%);
  opacity: 0.45;
}
.pyramid__row:nth-child(3) .pyramid__band {
  clip-path: polygon(17% 0, 83% 0, 100% 100%, 0 100%);
  opacity: 0.78;
}
.pyramid__label {
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  color: var(--c-primary);   /* was gold: 2.1:1 on ivory, fails as text */
  font-weight: var(--fw-medium);
  display: inline-flex;
  align-items: center;
}
.pyramid__notes { font-size: var(--t-small); }

.spec {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  letter-spacing: 0.04em;
  margin-block-end: var(--s-5);
}

details.faq {
  border-block-end: 1px solid var(--c-hairline);
}
details.faq summary {
  cursor: pointer;
  padding-block: var(--s-3);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
}
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after {
  content: "+";
  color: var(--c-primary);   /* a control affordance needs 3:1; gold gives 2.1 */
  font-size: 1.2rem;
  line-height: 1;
}
details.faq[open] summary::after { content: "\2212"; }
details.faq p {
  padding-block-end: var(--s-4);
  font-size: var(--t-small);
  color: var(--c-muted-text);
  max-inline-size: 66ch;
}

/* --- Info bubble ---------------------------------------------------------- */
/* Opens on hover for mouse and on focus for keyboard and touch, so one
   component covers all three without JavaScript. */
.info {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
  margin-inline-start: var(--s-2);
}
.info__btn {
  inline-size: 18px;
  block-size: 18px;
  border: 1px solid var(--c-muted);
  border-radius: 50%;
  background: transparent;
  color: var(--c-muted-text);
  font-family: var(--f-la);
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: help;
  padding: 0;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.info__btn:hover { border-color: var(--c-primary); color: var(--c-primary); }

/* Anchored by its inline-start edge, not centred. Centring with
   inset-inline-start:50% plus a physical translate double-shifts in RTL and
   pushes the bubble off the left edge. Anchoring makes it open along the
   reading direction and stay inside the text column in both directions. */
.info__bubble {
  position: absolute;
  inset-block-start: calc(100% + 10px);
  inset-inline-start: 0;
  inline-size: min(288px, 74vw);
  padding: var(--s-3) var(--s-3);
  background: var(--c-primary);
  color: var(--c-on-dark);
  border-radius: 3px;
  font-size: var(--t-meta);
  font-weight: var(--fw-light);
  line-height: 1.65;
  text-align: start;
  letter-spacing: normal;
  text-transform: none;
  opacity: 0;
  visibility: hidden;
  translate: 0 4px;
  transition: opacity 0.22s var(--ease), translate 0.22s var(--ease),
              visibility 0.22s;
  z-index: 5;
}
.info__bubble::before {
  content: "";
  position: absolute;
  inset-block-end: 100%;
  inset-inline-start: 4px;
  border: 5px solid transparent;
  border-block-end-color: var(--c-primary);
}
.info:hover .info__bubble,
.info:focus-within .info__bubble {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
}

/* --- Performance metrics -------------------------------------------------- */
/* Longevity and sillage are single ratios against a limit, so each is a
   meter, not a chart. One hue: the fill is the brand green, the track a
   lighter step of the same green. The number is always printed as text, so
   no value is reachable only by colour. */
.metrics {
  border-block: 1px solid var(--c-hairline);
  padding-block: var(--s-4);
  margin-block-end: var(--s-5);
  display: grid;
  gap: var(--s-4);
}

.metric__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-block-end: var(--s-2);
}
.metric__label {
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  display: inline-flex;
  align-items: center;
}
.metric__value {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  font-variant-numeric: tabular-nums;
}
.metric__value b {
  font-weight: var(--fw-medium);
  color: var(--c-text);
  font-size: var(--t-small);
}

.meter {
  block-size: 6px;
  border-radius: 3px;
  background: rgba(var(--rgb-primary), 0.12);
  overflow: hidden;
}
.meter__fill {
  display: block;
  block-size: 100%;
  background: var(--c-primary);
  border-start-end-radius: 4px;
  border-end-end-radius: 4px;
}
.metric__note {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  margin-block-start: var(--s-2);
}

/* Categorical rows: season, time of day, gender. Nominal categories, so
   every active chip shares one colour rather than a ramp, and the label
   text is always present so state never rests on colour alone. */
.metric--chips .metric__head { margin-block-end: var(--s-3); }
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.34rem 0.85rem;
  border: 1px solid var(--c-hairline);
  border-radius: 999px;
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  background: transparent;
}
.chip.is-on {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: var(--c-on-dark);
}
/* Icons inherit currentColor, so they invert with the chip automatically.
   Hairline strokes only, no fills: at 14px a filled glyph turns to mud. */
.chip svg {
  inline-size: 14px;
  block-size: 14px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

/* --- Section head, secondary ---------------------------------------------
   A left-anchored eyebrow + title used above a form column or a story band,
   lighter than .page-head so it groups a subsection rather than opening a page. */
.subhead { margin-block-end: var(--s-5); }
.subhead .title { font-size: var(--t-subtitle); margin-block-start: var(--s-2); }

/* --- FAQ page ------------------------------------------------------------- */
/* Reuses the product page's details.faq accordion, grouped into headed sets. */
.faq-groups { max-inline-size: 760px; margin-inline: auto; }
.faq-group + .faq-group { margin-block-start: var(--s-6); }
.faq-group > .eyebrow { margin-block-end: var(--s-3); }
/* A hairline over the first item so each set reads as a bounded list, matching
   the bottom border every row already carries. */
.faq-group .faq:first-of-type { border-block-start: 1px solid var(--c-hairline); }
/* The homepage FAQ teaser: a short set of questions that ends in a link to the
   full knowledge centre, centred under the accordion. */
.faq-home__cta { text-align: center; margin-block-start: var(--s-5); }

/* --- Home bands (2026-09-08 home/shell copy brief) ------------------------
   Five small additions, all of them reusing what the page already has rather
   than introducing a second card language: the hero's trust line, the brand
   card's visible CTA, the browse-by-mood rows, the assurance band and the
   closing band's lede. Everything logical (inline-start/end, block-start/end)
   so the RTL page and a later LTR one need one set of rules. */

/* The hero's trust line, under the two actions. Same on-scene muted ink as
   .hero__sub — it sits on the same photograph and the flat on-dark value
   fails the contrast floor there (see tokens.css). */
.hero__trust {
  margin-block-start: var(--s-4);
  font-size: var(--t-meta);
  letter-spacing: 0.04em;
  color: var(--c-on-scene-muted);
}

/* The brand card's CTA (brief §5). The card is still one click through
   .scent__link's stretched ::after; this is the visible affordance the brief
   asks for. margin-block-start:auto pins it to the card's bottom edge the way
   .scent__foot does on a product card, so a two-line headline in one card
   cannot leave its neighbour's CTA floating. */
.scent__cta {
  margin-block-start: auto;
  padding-block-start: var(--s-3);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
}
.scent__cta .scent__link {
  color: var(--c-primary);
  text-decoration: underline;
  text-underline-offset: 0.35em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--c-hairline);
}
.scent__cta .scent__link:hover,
.scent__cta .scent__link:focus-visible { text-decoration-color: currentColor; }
/* A brand card carries no price rule, so its headline gets the room the
   product card spends on .scent__foot. */
.scent--brand .scent__name { margin-block-end: var(--s-2); }

/* A band's own closing action, centred under its grid (the bestsellers shelf's
   "לכל הבשמים"). Deliberately not .faq-home__cta with a second name: this one
   sits under a grid rather than an accordion and wants the wider gap. */
.section-cta { text-align: center; margin-block-start: var(--s-6); }

/* "בחירה לפי אופי" (brief §7). Four text cards, no photography: the mood is
   the promise, and a stock image would be an invented fact about a scent.
   Four across on desktop, two on a tablet, one on a phone — the same
   breakpoints .values uses, so the two strips step together down the page. */
.moods {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1rem, 2.2vw, 1.75rem);
}
.mood {
  position: relative;   /* .mood__link::after stretches to this box */
  display: flex;
  flex-direction: column;
  padding: var(--s-5);
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  transition: border-color 0.25s var(--ease);
}
.mood:hover { border-color: var(--c-primary); }
/* The focus ring belongs to the whole card, not to the two words inside it:
   the stretched link is what a keyboard lands on, and an outline drawn around
   the CTA text alone reads as a stray underline on a card-sized target. */
.mood:has(.mood__link:focus-visible) {
  outline: 2px solid var(--c-primary);
  outline-offset: 3px;
}
.mood__title {
  font-size: 1.05rem;
  color: var(--c-primary);
  margin-block-end: var(--s-2);
}
.mood__line {
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--c-muted-text);
  margin-block-end: var(--s-4);
  text-wrap: pretty;
}
.mood__cta {
  margin-block-start: auto;
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
}
.mood__link { color: var(--c-primary); }
.mood__link::after {
  content: "";
  position: absolute;
  inset: 0;
}
@media (max-width: 900px) { .moods { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .moods { grid-template-columns: 1fr; } }

/* "מקוריות ושירות" (brief §9): a quiet centred band, the same measure the
   story teaser reads at, with its two page links on one row. */
.assurance {
  max-inline-size: 60ch;
  margin-inline: auto;
  text-align: center;
}
.assurance .title { margin-block-end: var(--s-4); }
.assurance__body {
  color: var(--c-muted-text);
  font-size: var(--t-subtitle);
  line-height: 1.7;
  margin-block-end: var(--s-5);
  text-wrap: pretty;
}
.assurance__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-3) var(--s-5);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
}
.assurance__link {
  color: var(--c-primary);
  text-decoration: underline;
  text-underline-offset: 0.35em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--c-hairline);
}
.assurance__link:hover,
.assurance__link:focus-visible { text-decoration-color: currentColor; }

/* The closing band's lede (brief §12). shop.html's own closing band already
   sets these three values inline; this is the rule that makes the home's
   match it without a second inline style. */
.closing .lede {
  max-inline-size: 52ch;
  margin-inline: auto;
  margin-block-end: var(--s-5);
}

/* --- Forms (contact, checkout) -------------------------------------------
   Fields sit on white so they read as an input against the ivory page. The
   focus ring is a legal requirement under 5568, not decoration, so it is a
   green border plus a soft halo that shows on any field. Every control clears
   a 48px touch target. All spacing logical, so the grid holds in He, Ar and
   later En with no second rule. */
.form { display: grid; gap: var(--s-4); }
.field { display: grid; gap: var(--s-2); }
.field > label {
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);
}
.field input,
.field textarea,
.field select {
  font: inherit;
  font-size: var(--t-body);
  color: var(--c-text);
  background: var(--c-surface);
  /* --c-muted, not --c-hairline. A hairline is 1.30:1 on the white field and
     1.10:1 against the ivory page, and WCAG 1.4.11 asks 3:1 for the boundary of
     a control you are expected to find and click into. --c-muted measures
     3.82:1 on the field and 3.47:1 against the page. It is the token marked
     NON-TEXT ONLY, which is exactly what a border is. Dividers elsewhere keep
     the hairline: they carry no interaction. */
  border: 1px solid var(--c-muted);
  border-radius: var(--radius-sm);
  padding: 0.8rem 1rem;
  min-block-size: 3rem;
  inline-size: 100%;
  transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.field textarea { min-block-size: 8.5rem; resize: vertical; line-height: 1.6; }
/* The label carries the meaning, so the placeholder is a hint and can be the
   muted-text value, which still measures 5.4:1 rather than the 3.5:1 muted. */
.field input::placeholder,
.field textarea::placeholder { color: var(--c-muted-text); opacity: 1; }
.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(var(--rgb-primary), 0.12);
}
.field--wide { grid-column: 1 / -1; }
.field__hint { font-size: var(--t-meta); color: var(--c-muted-text); }
/* The visible "required" marker. Quiet, but present: the `required` attribute
   alone is announced to a screen reader and invisible to everyone else, so a
   sighted visitor would only find out which fields matter by failing. */
.field__req {
  font-size: var(--t-meta);
  font-weight: var(--fw-book);
  color: var(--c-muted-text);
  letter-spacing: 0;
  text-transform: none;
}
/* The FAQ group labels are real headings now (h2), so the eyebrow treatment has
   to survive a heading's own font-size and margins. */
h2.eyebrow { font-size: var(--t-eyebrow); }

/* What a submitted form says back. Two states, one shape: a panel with a tinted
   inline-start edge, sitting as the form's first row so the form's own grid gap
   spaces it. The live theme prints whichever applies after a submit; the preview
   ships both hidden so the design is approvable, the same convention the cart's
   empty state uses. Colour is never the only signal — each state carries words,
   and the error state also marks its fields with aria-invalid. */
.form__notice {
  grid-column: 1 / -1;
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  border-inline-start: 3px solid var(--c-primary);
  border-radius: var(--radius-sm);
  padding: var(--s-3) var(--s-4);
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--c-text);
}
.form__notice--error { border-inline-start-color: var(--c-danger); }
/* A field the server rejected. The ring is the danger colour rather than the
   focus gold, so an invalid field reads as invalid even before it is focused. */
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--c-danger); }

/* --- Contact page --------------------------------------------------------- */
.contact {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(2rem, 6vw, 5rem);
  padding-block: var(--s-5) var(--s-6);
  align-items: start;
}
/* `minmax(0, 1fr)` and not the implicit `auto`. Corrected 2026-08-19.
   An implicit grid track is `auto`, whose MINIMUM is the widest item's
   min-content, and one of these cards carries an unbreakable string: the
   Instagram address, 281px of it. The track grew to 393px inside a 350px
   wrapper, so all four cards hung 43px past the page and the contact page
   scrolled sideways on a phone. `min-inline-size: 0` on the card's body did not
   help, because the string is what has no smaller size, not the box around it.
   Measured live at 360: 53px of sideways scroll, and 23px at 390.

   This half stops the TRACK exceeding its container. The value's own
   overflow-wrap below is the other half, and both are needed: cap the track
   alone and the text spills out of the card instead of off the page. */
.contact-methods { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
/* A method is a card; when its value exists later (a wa.me link, a tel:) the
   whole card becomes the anchor and lights its border on hover. Inert now,
   because there is no real number to point at yet. */
.contact-method {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
}
a.contact-method { transition: border-color 0.25s var(--ease); }
a.contact-method:hover { border-color: var(--c-accent); }
.contact-method__icon {
  flex: none;
  inline-size: 46px; block-size: 46px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(var(--rgb-primary), 0.06);
}
.contact-method__icon svg {
  inline-size: 22px; block-size: 22px;
  stroke: var(--c-primary); fill: none;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.contact-method__body { display: grid; gap: 2px; min-inline-size: 0; }
.contact-method__label {
  font-size: var(--t-meta);
  letter-spacing: 0.04em;
  color: var(--c-muted-text);
}
/* `anywhere` rather than `break-word`, because these values are exactly the
   strings that have no break opportunity in them: an address like
   info@imagnet.co.il and a URL like https://instagram.com/imagnet.co.il. It also
   feeds back into the track sizing above, since `anywhere` is the one value that
   also lowers the element's min-content contribution. */
.contact-method__value {
  font-size: var(--t-body);
  color: var(--c-text);
  font-weight: var(--fw-medium);
  overflow-wrap: anywhere;
}
/* The value that has not been filled yet reads plainly as pending, not as a
   real detail a visitor could act on. */
.contact-method__value--pending { color: var(--c-muted-text); font-weight: var(--fw-book); }

/* --- Content page (legal shells: terms, privacy, shipping, accessibility) -
   The frame only. The four pages carry no real copy: a plugin injects the
   legal text into .prose later. .prose therefore styles arbitrary injected
   headings, paragraphs and lists for a comfortable RTL read, and the preview
   shows a marked-empty placeholder so the shell does not read as broken. */
.content-page { padding-block: var(--s-5) var(--s-7); }
.content-page .container { max-inline-size: 820px; }
.content-page__title {
  font-size: var(--t-title);
  font-weight: var(--fw-light);
  line-height: 1.2;
  margin-block-end: var(--s-3);
}
.content-page__meta { font-size: var(--t-meta); color: var(--c-muted-text); margin-block-end: var(--s-5); }
.prose { color: var(--c-text); }
.prose > * + * { margin-block-start: var(--s-4); }
.prose h2 {
  font-size: var(--t-subtitle);
  font-weight: var(--fw-medium);
  line-height: 1.3;
  color: var(--c-primary);
  margin-block-start: var(--s-5);
}
.prose h3 { font-size: var(--t-body); font-weight: var(--fw-medium); margin-block-start: var(--s-4); }
.prose p, .prose li { font-size: var(--t-body); line-height: 1.8; max-inline-size: 68ch; }
.prose ul, .prose ol { padding-inline-start: var(--s-4); display: grid; gap: var(--s-2); }
.prose a { color: var(--c-primary); text-decoration: underline; text-underline-offset: 3px; }
.prose__placeholder {
  border: 1px dashed var(--c-hairline);
  border-radius: var(--radius-sm);
  padding: var(--s-5);
  color: var(--c-muted-text);
  font-size: var(--t-small);
  line-height: 1.7;
  text-align: center;
}

/* --- Cart + checkout (static mockups) ------------------------------------
   Non-functional on purpose: their job is to prove WooCommerce's own Cart and
   Checkout blocks can be styled to this design, and to be the CSS target at
   migration. So every surface is a real brand surface, nothing is invented. */
.cart {
  display: grid;
  grid-template-columns: 1.5fr 0.7fr;
  gap: clamp(2rem, 5vw, 4rem);
  padding-block: var(--s-5) var(--s-6);
  align-items: start;
}
.cart__lines { border-block-start: 1px solid var(--c-hairline); }
.cart-line {
  display: grid;
  grid-template-columns: 84px 1fr auto;
  gap: var(--s-4);
  align-items: center;
  padding-block: var(--s-4);
  border-block-end: 1px solid var(--c-hairline);
}
.cart-line__media {
  inline-size: 84px;
  aspect-ratio: 3 / 4;
  background: rgba(var(--rgb-primary), 0.035);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  display: grid; place-items: center; overflow: hidden;
}
.cart-line__media img { inline-size: 78%; block-size: 78%; object-fit: contain; }
.cart-line__body { display: grid; gap: var(--s-2); min-inline-size: 0; }
/* The stepper is inline-flex, but as a grid item it would stretch to the whole
   column under the default justify-self:stretch. Anchor it to its intrinsic
   width at the inline start (the right, in RTL). */
.cart-line__body .qty { justify-self: start; }
.cart-line__title { font-size: var(--t-body); }
.cart-line__meta { font-size: var(--t-meta); color: var(--c-muted-text); }
.cart-line__end { display: grid; justify-items: end; gap: var(--s-2); text-align: end; }
.cart-line__price { font-size: var(--t-body); font-weight: var(--fw-medium); white-space: nowrap; }
.cart-line__remove {
  background: none; border: 0; padding: 0;
  color: var(--c-muted-text);
  font-size: var(--t-meta);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cart-line__remove:hover { color: var(--c-primary); }

/* The empty state. Shipped in the markup as a hidden variant so the owner can
   see it and the WordPress build has the target, without a second page. */
.cart-empty {
  text-align: center;
  padding-block: var(--s-7);
  border-block: 1px solid var(--c-hairline);
}
.cart-empty p { color: var(--c-muted-text); margin-block: var(--s-3) var(--s-5); }
.is-hidden { display: none; }

/* --- Mini cart, the add-to-cart answer -----------------------------------
   WooCommerce's stock confirmation is a notice injected at the TOP of the page.
   It reflows the layout on every add, it does not belong to this design, and on
   a phone it lands off screen when the tap came from the sticky buy bar, so the
   reader presses "הוסיפו לעגלה" and sees nothing happen at all. This panel
   answers where the eye already is.

   One shell, two behaviours, and that split is deliberate. In the preview the
   body holds sample lines, which is what the owner approves. In the theme the
   body and the header count are registered WooCommerce FRAGMENTS, so the same
   markup carries the live cart and WooCommerce itself refreshes it on every add.
   Nothing about the design is duplicated between the two.

   Without JS none of this runs: the form posts exactly as before and
   WooCommerce shows its own notice, branded in woo-brand.css. The panel is a
   progressive layer over a path that already worked, never a replacement for it.

   z-index 200 clears the header (100), the announcement strip (101) and the
   sticky buy bar (99) — a dialog that a fixed strip can cross is not a dialog. */
.minicart {
  position: fixed;
  inset: 0;
  z-index: 200;
}
.minicart[hidden] { display: none; }

.minicart__scrim {
  position: absolute;
  inset: 0;
  background: rgba(var(--rgb-primary), 0.42);
  opacity: 0;
  transition: opacity 0.28s var(--ease);
}

/* inset-inline-end resolves to the LEFT in RTL, which is the side the cart
   button sits on in the header. The panel comes out from under its own trigger
   rather than from the opposite edge of the screen. */
/* Flex column, not `grid-template-rows: auto 1fr auto`. The panel gained a
   fourth child on 2026-09-08 (the add-confirmation line, which ships hidden),
   and a fixed three-row template silently mis-assigns rows the moment the
   number of rendered children changes: with the confirmation hidden the foot
   would land in the 1fr row and the scrolling body in an auto one, so the
   panel would push its own footer off screen. A column with one growing child
   states the intent instead of encoding a count. */
.minicart__panel {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: min(25.5rem, 92vw);
  background: var(--c-bg);
  border-inline-start: 1px solid var(--c-hairline);
  box-shadow: -18px 0 48px rgba(var(--rgb-primary), 0.14);
  display: flex;
  flex-direction: column;
  transform: translateX(calc(100% * var(--minicart-dir, -1)));
  transition: transform 0.34s var(--ease);
  max-block-size: 100%;
}
[dir="ltr"] .minicart__panel { --minicart-dir: 1; }

.minicart.is-open .minicart__scrim { opacity: 1; }
.minicart.is-open .minicart__panel { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
  .minicart__scrim,
  .minicart__panel { transition: none; }
}

.minicart__head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-4) var(--s-3);
  border-block-end: 1px solid var(--c-hairline);
}
.minicart__title {
  font-size: var(--t-subtitle);
  font-weight: var(--fw-light);
}
.minicart__close {
  background: none;
  border: 0;
  padding: var(--s-2);
  margin: calc(var(--s-2) * -1);
  color: var(--c-muted-text);
  cursor: pointer;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
}
.minicart__close:hover { color: var(--c-primary); }
.minicart__close svg { inline-size: 1.25rem; block-size: 1.25rem; }

/* The add confirmation (2026-09-08 brief). The panel's title is its permanent
   name now, so this line is what says an add just happened. Hidden at rest and
   revealed by Task 3's script; the `hidden` attribute is what keeps it out of
   the flow, and this only says what it looks like when it is there. */
.minicart__confirm {
  flex: none;
  margin: 0;
  padding: var(--s-2) var(--s-4);
  background: rgba(var(--rgb-accent), 0.12);
  border-block-end: 1px solid var(--c-hairline);
  color: var(--c-primary);
  font-size: var(--t-small);
  text-align: center;
}
.minicart__confirm[hidden] { display: none; }

/* The scrolling region. flex:1 takes what is left after the head, the
   confirmation and the foot; min-block-size:0 is what lets a flex item actually
   shrink and scroll instead of pushing the footer off the panel. */
.minicart__body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-block-size: 0;
  padding-inline: var(--s-4);
}

/* The lines reuse .cart-line wholesale, only tighter: same media plate, same
   type, same rules. One definition of what a cart row looks like. */
/* One row, at every width, and this is an override of a deliberate rule rather
   than a disagreement with it. Below 560px the CART PAGE drops the price under
   the title on purpose, because there a line also carries a quantity stepper and
   a remove control, and all four fight for 390px. The mini cart's line has
   neither: it is a confirmation, not an editor. So the constraint that rule
   exists to solve is absent here, and inheriting it only costs vertical space in
   a panel where height is how many items the reader can see at once.

   Measured before deciding: at 390 the panel is 359 wide and the row needs about
   237, so one row fits with room. grid-template-areas has to be cleared as well
   as the columns re-declared — the named areas from that media query define
   their own two-column structure, and leaving them set while asking for three
   columns is how a "fixed" grid keeps rendering the old shape. */
.minicart__body .cart-line {
  grid-template-columns: 60px 1fr auto;
  grid-template-areas: none;
  gap: var(--s-3);
  padding-block: var(--s-3);
}
.minicart__body .cart-line__media { inline-size: 60px; grid-area: auto; align-self: center; }
.minicart__body .cart-line__body { grid-area: auto; }
.minicart__body .cart-line__end {
  grid-area: auto;
  justify-items: end;
  text-align: end;
}
.minicart__body .cart-line:last-child { border-block-end: 0; }

/* The empty state carries a way out now (2026-09-08 brief): a panel that only
   says "empty" is a dead end on the one screen where the reader has already
   shown intent. */
.minicart__empty {
  display: grid;
  justify-items: center;
  gap: var(--s-4);
  text-align: center;
  color: var(--c-muted-text);
  padding-block: var(--s-6);
  font-size: var(--t-small);
}
.minicart__empty-text { max-inline-size: 26ch; }

.minicart__foot {
  flex: none;
  padding: var(--s-4);
  border-block-start: 1px solid var(--c-hairline);
  background: var(--c-surface);
  display: grid;
  gap: var(--s-3);
}
.minicart__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--t-body);
}
.minicart__total strong { font-weight: var(--fw-medium); }
.minicart__foot .btn { inline-size: 100%; justify-content: center; }
.minicart__note {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  text-align: center;
}
.minicart__cont {
  background: none;
  border: 0;
  padding: 0;
  color: var(--c-muted-text);
  font: inherit;
  font-size: var(--t-small);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.minicart__cont:hover { color: var(--c-primary); }

/* --- The search overlay ---------------------------------------------------
   The header's search opens a modal rather than a dropdown, because the thing
   it shows is a LIST of products with a brand and a family on each row, and a
   list like that under a 76px bar is a scrolling sliver on a phone. Centred
   rather than side-anchored (the one place it departs from the mini cart's
   drawer): search has no side of the bar it belongs to, and a reader typing
   should be looking at the middle of the screen.

   Everything else is the mini cart's own vocabulary on purpose — the same
   scrim, the same surface, the same hairline, the same close button — so the
   two panels read as one system rather than as two designs.

   z-index 200 matches the mini cart: it clears the header (100), the top strip
   (101) and the sticky buy bar (99). A dialog a fixed strip can cross is not a
   dialog. The two are never open at once (Task 3 closes one to open the other),
   so they do not need to be ordered against each other.

   INERT IN THIS TASK. It ships `hidden` and nothing binds it until Task 3, so
   the open state below is what the script will reveal, not what anyone can
   reach today. */
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
}
.search-overlay[hidden] { display: none; }

.search-overlay__scrim {
  position: absolute;
  inset: 0;
  background: rgba(var(--rgb-primary), 0.42);
  opacity: 0;
  transition: opacity 0.28s var(--ease);
}
.search-overlay.is-open .search-overlay__scrim { opacity: 1; }

/* Pinned near the top rather than vertically centred: the panel grows downward
   as results arrive, and a centred box would jump on every keystroke. */
.search-overlay__panel {
  position: absolute;
  inset-block-start: min(12vh, 7rem);
  inset-inline: 0;
  inline-size: min(38rem, 92vw);
  margin-inline: auto;
  max-block-size: min(70vh, 34rem);
  display: flex;
  flex-direction: column;
  background: var(--c-bg);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  box-shadow: 0 24px 60px rgba(var(--rgb-primary), 0.22);
  opacity: 0;
  transform: translateY(-0.75rem);
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease);
}
.search-overlay.is-open .search-overlay__panel { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .search-overlay__scrim,
  .search-overlay__panel { transition: none; }
}

.search-overlay__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-block-end: 1px solid var(--c-hairline);
}
.search-overlay__ico {
  flex: none;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  color: var(--c-muted-text);
}
/* No border and no fill of its own: the panel's head IS the field, so a second
   box inside it would be a box in a box. The caret and the placeholder carry
   the affordance. */
.search-overlay__input {
  flex: 1 1 auto;
  min-inline-size: 0;
  padding: var(--s-2) 0;
  background: none;
  border: 0;
  color: var(--c-primary);
  font: inherit;
  font-size: var(--t-body);
}
.search-overlay__input::placeholder { color: var(--c-muted-text); }
/* Safari draws its own clear button inside type="search" and it lands on the
   wrong side under RTL. The close control beside it already does that job. */
.search-overlay__input::-webkit-search-cancel-button { display: none; }

.search-overlay__close {
  flex: none;
  background: none;
  border: 0;
  padding: var(--s-2);
  margin: calc(var(--s-2) * -1);
  color: var(--c-muted-text);
  cursor: pointer;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
}
.search-overlay__close:hover { color: var(--c-primary); }
.search-overlay__close svg { inline-size: 1.25rem; block-size: 1.25rem; }

.search-overlay__results {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  margin: 0;
  padding: 0;
}
.search-overlay__results:empty { display: none; }
.search-overlay__results li { border-block-end: 1px solid var(--c-hairline); }
.search-overlay__results li:last-child { border-block-end: 0; }
.search-overlay__results a {
  display: grid;
  gap: 2px;
  padding: var(--s-3) var(--s-4);
  transition: background-color 0.2s var(--ease);
}
.search-overlay__results a:hover,
.search-overlay__results a:focus-visible { background: var(--c-surface); }
.search-overlay__result-name { font-size: var(--t-body); }
.search-overlay__result-meta { font-size: var(--t-meta); color: var(--c-muted-text); }
/* The no-results line. The wording lives on the list's data-empty attribute
   (approved copy belongs in the markup, not in the script), and the script
   prints it into this element; styled here so it reads as an answer rather
   than as a failed row. */
.search-overlay__empty {
  padding: var(--s-5) var(--s-4);
  text-align: center;
  color: var(--c-muted-text);
  font-size: var(--t-small);
}

/* The free-shipping progress line (G8/K8, task 5): a track filled toward the
   threshold, replaced outright by the "done" line once the order clears it —
   never shown together, so the reader never sees a full bar beside a line
   that already says the same thing. Shared by the mini cart and the cart
   page's own summary (scripts/lib/chrome.mjs `shippingProgress()`, inc/
   render-cart.php `imagnet_shipping_progress_html()`), so one rule styles
   both; `.summary` gets its own bottom margin below since the bar sits
   between the summary title and its rows there, not in a foot with its own
   gap like the mini cart's. */
.shipping-progress { display: grid; gap: var(--s-2); }
.shipping-progress__label,
.shipping-progress__done {
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  text-align: center;
}
.shipping-progress__done { color: var(--c-primary); font-weight: var(--fw-medium); }
.shipping-progress__track {
  block-size: 6px;
  border-radius: 999px;
  background: var(--c-hairline);
  overflow: hidden;
}
.shipping-progress__fill {
  block-size: 100%;
  background: var(--c-accent);
  border-radius: inherit;
  transition: inline-size 0.3s ease;
}
.summary .shipping-progress { margin-block-end: var(--s-4); }

/* While the panel is open the page behind it must not scroll. Set on <html> by
   the script so it survives a body that other code also writes to. */
.is-minicart-open { overflow: hidden; }

/* The add-to-cart button while its request is in flight. The label stays put
   (a button that changes width mid-press moves under the finger); only the
   surface dims, and the control is disabled so a double tap cannot add twice. */
.btn.is-busy { opacity: 0.72; cursor: progress; }

/* Order summary, shared by cart and checkout. Sticks beside a tall column on
   desktop and drops under it on a phone. */
.summary {
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  padding: var(--s-5);
}
@media (min-width: 901px) {
  .summary { position: sticky; inset-block-start: var(--s-5); }
}
.summary__title { font-size: var(--t-subtitle); font-weight: var(--fw-light); margin-block-end: var(--s-4); }
.summary__row {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block: var(--s-2);
  font-size: var(--t-small);
  color: var(--c-muted-text);
}
.summary__row span:last-child { color: var(--c-text); }
.summary__row--total {
  border-block-start: 1px solid var(--c-hairline);
  margin-block-start: var(--s-2);
  padding-block-start: var(--s-4);
  font-size: var(--t-body);
  color: var(--c-text);
  font-weight: var(--fw-medium);
}
.summary .btn { inline-size: 100%; justify-content: center; margin-block-start: var(--s-4); }
.summary__note { font-size: var(--t-meta); color: var(--c-muted-text); text-align: center; margin-block-start: var(--s-3); }

/* --- Cart sticky bar (P15, task 6) ----------------------------------------
   The cart page's own version of the sticky buy bar above (.buybar): the
   subtotal and the checkout button follow the reader at the foot of a phone
   screen until the real summary card — which already carries the very same
   subtotal and the very same button — scrolls into view
   (initCartStickyBar() in site.js, watching .summary in the preview and
   WooCommerce's own .cart-collaterals in the live theme). Same dark surface,
   z-index and motion as .buybar, so a reader who has seen one on a product
   page recognises the other; no shared class with it, because this bar
   carries no quantity stepper and coupling markup that happens to look
   alike would let an unrelated buybar change break this one by accident.

   Hidden outright on desktop, not just translated off screen: the summary
   column already sits beside the lines there, always in view, so a desktop
   reader should find nothing here at all — the same display:none-by-default,
   shown-only-under-900px pattern .nav-toggle already uses above. */
.cart-stickybar {
  display: none;
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 99;
  background: var(--c-primary);
  color: var(--c-on-dark);
  border-block-start: 1px solid var(--c-line-on-dark);
  padding-block: var(--s-3);
  /* Clear of a phone's home indicator. */
  padding-block-end: calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  visibility: hidden;
}
.cart-stickybar.is-on { transform: none; visibility: visible; }
@media (prefers-reduced-motion: no-preference) {
  .cart-stickybar { transition: transform 0.35s var(--ease), visibility 0.35s var(--ease); }
}
@media (max-width: 900px) {
  .cart-stickybar { display: flex; }
}
.cart-stickybar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.cart-stickybar__sub {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
.cart-stickybar__label {
  font-size: var(--t-meta);
  /* The FLAT-green muted (62%, 5.6:1), the same token .buybar__name uses on
     this same solid fill. */
  color: var(--c-on-dark-muted);
}
.cart-stickybar__price { font-size: 1.15rem; color: var(--c-on-dark); }
.cart-stickybar .btn { flex: 0 1 14rem; justify-content: center; }
@media (max-width: 560px) {
  .cart-stickybar .btn { flex: 1 1 auto; min-inline-size: 0; padding-inline: var(--s-2); white-space: nowrap; }
}

/* Checkout: a form of stacked sections beside the order summary. */
.checkout {
  display: grid;
  grid-template-columns: 1.25fr 0.75fr;
  gap: clamp(2rem, 5vw, 4rem);
  padding-block: var(--s-5) var(--s-6);
  align-items: start;
}
.checkout__form { display: grid; gap: var(--s-6); }
.checkout__section > h2 { font-size: var(--t-subtitle); font-weight: var(--fw-light); margin-block-end: var(--s-4); }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }

/* A chosen option in a mockup (a shipping method, the payment gateway). Reads
   as selectable without being a live control. */
.option {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  background: var(--c-surface);
  font-size: var(--t-small);
}
.option + .option { margin-block-start: var(--s-2); }
.option.is-on { border-color: var(--c-primary); box-shadow: inset 0 0 0 1px var(--c-primary); }
.option__dot {
  flex: none;
  inline-size: 18px; block-size: 18px;
  border-radius: 50%;
  border: 1px solid var(--c-muted);
  display: grid; place-items: center;
}
.option.is-on .option__dot { border-color: var(--c-primary); }
.option.is-on .option__dot::after {
  content: "";
  inline-size: 9px; block-size: 9px;
  border-radius: 50%;
  background: var(--c-primary);
}
.option__body { display: grid; gap: 2px; min-inline-size: 0; }
.option__meta { font-size: var(--t-meta); color: var(--c-muted-text); }
.option__price { margin-inline-start: auto; font-weight: var(--fw-medium); white-space: nowrap; }

/* --- Gift wrap add-on (checkout) ------------------------------------------
   Was a static mockup: three wrap slots shown in a chosen state plus a
   handwritten-greeting field, none of it wired to anything. It is now the real
   control, and it replaces WooCommerce's order-notes box on the live checkout.

   Two changes the owner locked on 2026-07-30, both narrowing the mockup to what
   the business can actually honour today:
     - the three wrap slots are gone. They were image placeholders waiting on
       real wrap photography, and shipping them live would have put three
       identical glyphs labelled "עטיפה 1/2/3" on a paying checkout. Their
       styles (.giftwrap__wraps, .wrap*) went with them rather than sitting here
       unreferenced; git holds them for the day the photos exist.
     - the greeting no longer promises handwriting, only a card.

   NO JAVASCRIPT. The panel is revealed by the checkbox's own :checked state
   through a sibling selector, which is why the checkbox is a sibling of the
   panel rather than nested inside the label. That matters more here than
   anywhere else on the site: this is the checkout, the gateway lands on it in
   days, and a reader with a blocked script still has to be able to ask for a
   gift wrap. `display: none` (not `visibility` or a transform) is deliberate —
   a collapsed panel must be out of the tab order and unread by a screen reader,
   not merely invisible. */
.giftwrap { display: grid; gap: var(--s-3); }
/* The checkbox itself is visually replaced by .option's dot, but it stays in the
   layout, focusable, and 1px rather than display:none — a hidden input cannot be
   focused, and the focus ring is drawn on the label below. */
.giftwrap__check {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.giftwrap__toggle { cursor: pointer; }
.giftwrap__check:focus-visible + .giftwrap__toggle {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
}
/* A checkbox, so the mark is a tick rather than .option__dot's filled circle. */
.giftwrap__toggle .option__dot { border-radius: var(--radius-sm); }
.giftwrap__check:checked + .giftwrap__toggle {
  border-color: var(--c-primary);
  box-shadow: inset 0 0 0 1px var(--c-primary);
}
.giftwrap__check:checked + .giftwrap__toggle .option__dot {
  border-color: var(--c-primary);
  background: var(--c-primary);
}
/* The tick. PHYSICAL border sides on purpose: this is a rotated glyph, not text,
   so it has no reading direction to follow. With the logical pair
   (border-inline-end) the kept sides flip in RTL and the same -45deg rotation
   draws the tick backwards. */
.giftwrap__check:checked + .giftwrap__toggle .option__dot::after {
  content: "";
  width: 9px; height: 5px;
  border: 2px solid var(--c-on-dark);
  border-top: 0; border-right: 0;
  border-radius: 1px;
  transform: rotate(-45deg) translate(1px, -1px);
}
.giftwrap__panel { display: none; }
.giftwrap__check:checked ~ .giftwrap__panel {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  border-inline-start: 3px solid var(--c-accent);
  border-radius: var(--radius-sm);
}

/* --- The payment area (checkout) -----------------------------------------
   Added 2026-08-18 for the move to Tranzila Hosted Fields, which brings the
   card fields INTO the checkout page and removes the second screen.

   THIS BLOCK IS THE ONE DEFINITION. The static mockup (build-static-pages.mjs)
   and the live WooCommerce checkout print the same `pay__*` class names, so both
   are styled from here rather than from two sets that drift apart. site.css is
   copied verbatim into the theme by build-wp-theme.mjs, so the live page gets
   these exact rules. woo-brand.css only bridges WooCommerce's own wrappers.

   ORDER: wallets, divider, card. The owner chose it on 2026-08-18, and the
   measurement backs it: the only order ever paid in this store (188) was paid
   with Apple Pay, so the proven path goes first.  */
/* THE CONTAINER IS THE STATE, and it is deliberately NOT a grid.
   Live, `.pay-box` is WooCommerce's own payment box, whose children include a
   <style> tag the gateway prints and a <fieldset> we are forbidden to reparent
   (it holds Tranzila's iframes). So the vertical rhythm is done with margins on
   the children rather than with a gap on the parent: a gap would put a fixed
   space between the credit row and the panel it opens, and the two have to meet
   flush to read as one control. The row that opens the panel is the one child
   with no margin under it, and that is the whole trick. */
.pay-box > * { margin-block-end: var(--s-4); }
.pay-box > .pay__trigger { margin-block-end: 0; }
.pay-box > :last-child { margin-block-end: 0; }

/* `.pay` now holds only the wallets and the divider. Everything below the
   divider is a direct child of `.pay-box`, because live it has to be: the card
   panel is the gateway's fieldset and it sits where the gateway put it. */
.pay { display: grid; gap: var(--s-4); }

/* WALLETS SIT IN ONE ROW, and that follows from the device, not from taste.
   Apple Pay only ever appears on an Apple device, Google Pay only on Android,
   and bit appears everywhere. So a real reader sees TWO buttons at most, never
   three, and two side by side is a row rather than a stack that pushes the card
   option below the fold.
   flex, not grid: when only bit qualifies (most desktops) it takes the full
   width on its own instead of leaving a hole where a wallet would have been. */
.pay__wallets { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.pay__wallets > .pay__wallet { flex: 1 1 10rem; }
.pay__wallet {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  /* A FIXED height, not a minimum, and the three marks are centred inside it.
     Each network publishes artwork of a different natural height (Apple's is a
     plate, Google's and bit's are bare wordmarks), so a min-height let the
     content decide and the row came out 54 / 50 / 48px: three buttons that
     should read as one control, visibly not lining up.
     52px also clears the 48px floor that Israeli standard 5568 inherits from
     WCAG 2.5.5, which matters most on the control that takes the money. */
  block-size: 52px;
  padding: 0 1.4rem;
  inline-size: 100%;
  font: inherit;
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.25s var(--ease), border-color 0.25s var(--ease);
}
/* Every wallet is hidden until its own availability check passes. A button that
   cannot pay is worse than a missing one: it is a dead end on the money screen.
   The static mockup overrides this so the layout can be seen and approved. */
.pay__wallet[hidden] { display: none; }
.pay__wallet-name { line-height: 1; }

/* THE MARKS ARE THE PAYMENT NETWORKS' OWN ARTWORK.
   Sized by height and never by width, so each keeps the proportions its owner
   published: all three are trademarks whose shape may not be altered. A common
   cap height is also what makes three logos from three companies read as one
   row rather than as three pasted images.

   The per-mark heights differ because the artwork differs in what it includes.
   Apple's mark is a PLATE with the lockup inside it, so it needs to be taller to
   put the same amount of ink on the button as a bare wordmark does. */
.pay__mark { block-size: 22px; inline-size: auto; flex: none; display: block; }
/* Apple's is a plate with padding built into the artwork, so it has to run
   taller than bit's bare wordmark to put the same amount of ink on the button.
   Matched by eye against bit, not by number. */
.pay__mark--apple { block-size: 36px; }
.pay__mark--bit { block-size: 26px; }

/* Apple and Google both make button appearance a CONDITION of using the
   service, and both supply the art: Apple's black-plate/white-lockup and
   Google's colored primary lockup are each the light-button variant their own
   guidelines allow. All three marks in the markup are the official artwork
   read from data/payment-marks.mjs. */
/* All three marks are Latin lockups whose internal order their owners fix. The
   page is RTL and RTL reorders flex children, so the button's own writing
   direction is pinned back to LTR: without this the Apple lockup rendered as
   "Pay " instead of " Pay". */
.pay__wallet { direction: ltr; }

/* Apple publishes the Apple Pay Mark as a WHITE plate carrying the black
   Pay lockup. Rather than recolour a trademark to suit a black button, the
   button is matched to the mark, so the plate disappears into it and the lockup
   sits on the button exactly as Apple draws it. This is Apple's own light button
   style, and the hairline is what keeps a white control from dissolving into the
   ivory page behind it. */
.pay__wallet--apple {
  background: #FFF;
  color: #000;
  border-color: rgba(0, 0, 0, 0.14);
}
.pay__wallet--apple:hover,
.pay__wallet--apple:focus { background: #F2F2F2; }

/* Google's light button: white plate, colored primary lockup, hairline like
   Apple's. Restored 2026-09-10: Tranzila's SDK now exposes chargeGpay (spec F2). */
.pay__wallet--gpay {
  background: #FFF;
  color: #000;
  border-color: rgba(0, 0, 0, 0.14);
}
.pay__wallet--gpay:hover,
.pay__wallet--gpay:focus { background: #F2F2F2; }
.pay__mark--gpay { block-size: 24px; }
/* bit's own dark teal, sampled from the logo the owner supplied. The mark is a
   raster that carries that same plate behind the wordmark, so matching the
   button to it makes the plate vanish and leaves only the cyan wordmark. Any
   other button colour would show a visible rectangle behind the logo, which is
   the same trap the brand tokens hit with the green behind the iMagnet mark. */
.pay__wallet--bit {
  background: #00353B;
  color: #39EDF6;
}
.pay__wallet--bit:hover,
.pay__wallet--bit:focus { background: #004A53; }
.pay__wallet:focus-visible {
  outline: 2px solid var(--c-ring);
  outline-offset: 2px;
}
/* Mid-charge. The button is also disabled in the DOM; this is what makes that
   visible, because a control that ignores a tap without looking any different
   reads as a broken page and earns a second tap. */
.pay__wallet.is-busy,
.pay__wallet:disabled { opacity: 0.55; cursor: progress; }

/* The divider. A rule with the words sitting in it, not a line and a caption. */
.pay__or {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-meta);
  color: var(--c-muted-text);
  margin: 0;
}
.pay__or::before,
.pay__or::after {
  content: "";
  block-size: 1px;
  background: var(--c-hairline);
}

/* Task 8 (spec F2): the checkout's country field is pinned to IL and kept in
   the form (Woo needs its value for tax/shipping zones) but never shown. */
.imagnet-hidden-field { display: none; }

/* The 3DS note under the payment box (spec F2). Sits like the other summary
   notes, just spaced off the payment box above it. */
.pay__note { margin-block-start: var(--s-3); }

/* --- Credit card, folded behind one row ----------------------------------
   The card fields open on a click instead of sitting open under the wallets.
   Most readers here pay with a wallet, and empty card fields under the button
   they were about to press is noise on the screen that decides.

   🔴 IT IS FOLDED, NOT HIDDEN, AND THE DIFFERENCE IS THE WHOLE MECHANISM.
   `display: none` was the first version, and it would have shipped a bug. The
   card fields are Tranzila <iframe>s, and an iframe whose ancestor is display:none
   gets a zero-size layout viewport; a hosted field created inside one can come
   up with no dimensions and no way to recover. So the panel keeps its layout box
   and is folded to zero height instead. The iframe inside is laid out at its full
   width the entire time and never has to re-measure.

   `visibility: hidden` is what does the accessibility work the display switch
   used to do: it takes the fields out of the tab order AND out of the
   accessibility tree, so a keyboard or screen-reader user does not walk into a
   panel nobody opened. It is the one property here that is not cosmetic.

   ⛔ AND THE OTHER THING THIS SHAPE BUYS. The live page cannot wrap the
   gateway's fieldset in anything: moving a node that contains an iframe reloads
   it. So the state lives on the CONTAINER (`.pay-box`, which is WooCommerce's own
   payment box live and the mockup's wrapper here) and reaches the panel by
   descent. Nothing has to be reparented for this to work, on either page. */
.pay__trigger {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  inline-size: 100%;
  min-block-size: 52px;
  padding: 0 var(--s-3);
  border: 1px solid var(--c-muted);
  border-radius: var(--radius-sm);
  background: var(--c-surface);
  font: inherit;
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);
  text-align: start;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}
.pay__trigger:hover { border-color: var(--c-primary); }
.pay__trigger:focus-visible {
  outline: 2px solid var(--c-ring);
  outline-offset: 2px;
}
.pay__trigger-icon { inline-size: 22px; block-size: 22px; flex: none; color: var(--c-muted-text); }
.pay__trigger-text { margin-inline-end: auto; }
.pay__trigger-chev {
  inline-size: 18px; block-size: 18px; flex: none;
  color: var(--c-muted-text);
  transition: transform 0.25s var(--ease);
}
/* Open: the chevron points up, and the row squares off into the panel below so
   the two read as one object rather than a row with a box under it. */
.pay-open .pay__trigger {
  border-color: var(--c-primary);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
  border-block-end-color: transparent;
}
.pay-open .pay__trigger .pay__trigger-chev { transform: rotate(180deg); }

/* Folded. Every box property is zeroed here and restored in the open state, so
   a collapsed panel contributes no height, no border and no gap of its own.
   min-inline-size: 0 is for the live page specifically: the panel there is a
   <fieldset>, and a fieldset's default min-inline-size is min-content, which
   would push the checkout column wider than the card number inside it. */
.pay__card {
  block-size: 0;
  min-block-size: 0;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  visibility: hidden;
}
.pay-open .pay__card {
  display: grid;
  margin-block-end: var(--s-4);
  gap: var(--s-4);
  block-size: auto;
  overflow: visible;
  visibility: visible;
  padding: var(--s-4);
  border: 1px solid var(--c-primary);
  border-block-start: 0;
  border-end-start-radius: var(--radius-sm);
  border-end-end-radius: var(--radius-sm);
}
/* TWO COLUMNS, BECAUSE THE GATEWAY ASKED FOR TWO.
   Tranzila prints WooCommerce's own row classes: `form-row-wide` for the card
   number and the ID, and `form-row-first` + `form-row-last` for the expiry and
   CVV, which are meant to sit side by side. Under a single-column grid that
   pairing is lost and the form becomes four stacked boxes for what is really
   three questions.
   ⚠️ woo-brand.css gives those two rows `inline-size: calc(50% - …)` for Woo's
   float layout. Inside a grid the track is already half, so that width would
   halve it again and leave a CVV box a quarter of the form wide. It is undone
   here, at higher specificity, rather than edited there, because on every other
   Woo form the float version is still correct. */
.pay__card > .form-row { display: grid; gap: var(--s-2); }
.pay-open .pay__card { grid-template-columns: 1fr 1fr; }
.pay-open .pay__card > .form-row { margin: 0; inline-size: auto; }
.pay-open .pay__card > .form-row-wide { grid-column: 1 / -1; }

/* WooCommerce ends its card form with a float-clearing div. Under grid it is not
   clearing anything, it is an empty row adding a gap. */
.pay-open .pay__card > .clear { display: none; }

/* The gateway's own error line under a field, which it fills as the reader
   types. Same treatment as .pay__err: no height while empty, so the form does
   not jump every time a digit lands. */
.pay__card [id^="errors_for_"] {
  font-size: var(--t-meta);
  color: var(--c-danger);
}
.pay__card [id^="errors_for_"]:empty { display: none; }

/* --- bit's sheet ---------------------------------------------------------
   Tranzila's SDK draws the bit payment sheet as a fixed overlay with inline
   styles, at z-index 1000000, and with NO way to close it. A reader who changes
   their mind is left with the checkout behind a panel they cannot dismiss, on
   the screen that takes the money.
   This is the backdrop that goes underneath it: it dims the page so the sheet
   reads as a layer rather than as a rendering fault, and it carries the close
   control the SDK does not provide. Deliberately one step below the SDK's own
   z-index, so their sheet stays on top of it. */
.pay__sheet {
  position: fixed;
  inset: 0;
  z-index: 999999;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.pay__sheet-close {
  margin-block-start: max(1rem, env(safe-area-inset-top));
  min-block-size: 48px;
  padding: 0 1.6rem;
  border: 0;
  border-radius: 999px;
  background: var(--c-surface);
  font: inherit;
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);
  cursor: pointer;
}
.pay__sheet-close:focus-visible {
  outline: 2px solid var(--c-ring);
  outline-offset: 2px;
}

/* Tranzila's 3DS challenge (thostedf.js triggerChallenge) is a fixed
   div#hf-challenge-cont appended to body with inline z-index 9999. Our own
   fixed bars (.buybar, .cart-stickybar at 99, .site-header at 100) sit below
   it; this rule only guards against a future z-index creeping past it and
   keeps the page from scrolling under the sheet. */
#hf-challenge-cont { z-index: 10000 !important; }
body:has(#hf-challenge-cont) { overflow: hidden; }

/* A card field's caption is a <span>, not a <label>, and that is on purpose.
   The control it names is an <iframe> served by Tranzila, and `for` may only
   point at a labelable element: pointing it at the wrapper <div> is invalid HTML
   and buys a screen-reader user nothing at all. So the caption is a span, and
   the wrapper carries role="group" + aria-labelledby, which does announce.
   Styled to match `.field > label` exactly, because a reader must not be able to
   tell that the card fields are built differently from the address fields. */
.pay__label {
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);
}

/* A hosted field is an <iframe> served by Tranzila, so it cannot be styled from
   this stylesheet: the type inside it is set through the `styles` option passed
   to TzlaHostedFields.create(). What this rule owns is the BOX around it, and it
   deliberately mirrors `.field input` value for value, so a card field and an
   address field are visibly the same control. If .field input changes, this
   changes with it. */
.pay__field {
  display: flex;
  align-items: center;
  background: var(--c-surface);
  border: 1px solid var(--c-muted);
  border-radius: var(--radius-sm);
  padding: 0 1rem;
  min-block-size: 3rem;
  transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.pay__field iframe {
  inline-size: 100%;
  block-size: 100%;
  min-block-size: 0;
  border: 0;
  display: block;
  background: transparent;
}
/* Tranzila puts these two classes on the container itself as the reader types.
   Valid is deliberately quiet: a green flash on every field is noise. Invalid
   uses the system's one error colour, and never as the only signal, because the
   gateway also writes the message. */
.pay__field.hosted-fields-valid { border-color: var(--c-primary); }
.pay__field.hosted-fields-invalid { border-color: var(--c-danger); }
.pay__field:focus-within {
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(var(--rgb-primary), 0.12);
}

/* The error line a field can carry. Empty until the gateway fills it, and it
   holds no space while empty so the form does not jump as it validates. */
.pay__err {
  font-size: var(--t-meta);
  color: var(--c-danger);
  margin: 0;
}
.pay__err:empty { display: none; }

.pay__secure {
  display: flex;
  align-items: start;
  gap: var(--s-2);
  font-size: var(--t-meta);
  line-height: 1.7;
  color: var(--c-muted-text);
  margin: 0;
}
.pay__secure svg { inline-size: 16px; block-size: 16px; flex: none; margin-block-start: 0.2em; }

/* --- Forms + mockups, responsive ----------------------------------------- */
/* minmax(0, 1fr) AND NOT 1fr, on every one of these. Fixed 2026-08-18.
   `1fr` is shorthand for `minmax(auto, 1fr)`, and that `auto` minimum is the
   item's min-content: a single-column grid written as `1fr` therefore refuses to
   shrink below whatever its widest content needs, and simply overflows the
   viewport instead. In the mockup that never showed, because the mockup's own
   fields are narrow. On the live checkout it did: `.checkout` also matches
   WooCommerce's real <form class="checkout woocommerce-checkout">, whose billing
   block measures 368px at its minimum, so the column stayed 368px wide inside a
   315px page and the whole checkout scrolled sideways on every phone. Measured
   at 390px: document 398px against a 375px viewport, 48 elements out of bounds;
   with minmax(0, 1fr), 375 against 375 and none.
   minmax(0, …) lets the track go to zero and the content wrap, which is what a
   one-column mobile layout means. */
@media (max-width: 900px) {
  .contact,
  .cart,
  .checkout { grid-template-columns: minmax(0, 1fr); }
  .field-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 560px) {
  /* The price and remove control drop under the title rather than fighting the
     name for width at 390. media | body across two rows. */
  .cart-line {
    grid-template-columns: 68px 1fr;
    grid-template-areas: "media body" "media end";
    column-gap: var(--s-3);
    row-gap: var(--s-3);
  }
  .cart-line__media { grid-area: media; inline-size: 68px; align-self: start; }
  .cart-line__body { grid-area: body; }
  .cart-line__end {
    grid-area: end;
    justify-items: start;
    text-align: start;
    flex-direction: row;
  }
}

/* --- Preview-only note ---------------------------------------------------- */
/* Fixed, because the header above it is fixed and a static flag would scroll
   out from under it and leave a gap. Preview-only scaffolding: it does not
   exist in the theme, which is exactly why the space it takes is measured at
   runtime into --chrome-h rather than written into a rule as a number. */
.preview-flag {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 101;
  /* The brand green itself, not the step up from it. The strip is meant to read
     as the brand's own colour above the bar rather than as a lighter band. */
  background: var(--c-primary);
  color: var(--c-on-dark-muted);
  font-size: var(--t-meta);
  text-align: center;
  padding-block: var(--s-2);
  letter-spacing: 0.04em;
}
.preview-flag strong { color: var(--c-accent); font-weight: var(--fw-medium); }

/* --- The top strip (permanent, static) ----------------------------------- */
/* Plays the same structural role the preview flag does: a fixed strip above the
   header that --flag-h / --chrome-h are measured from (see site.js), so the
   header sits under it and the home opening runs under both.

   STATIC SINCE 2026-09-08 (home/shell copy brief, section 1). It used to hold a
   six-promise rotator in a stacked grid cell, an aria-hidden viewport, a .vh
   mirror list and a WCAG 2.2.2 pause button; all of that went with the rotation,
   because nothing here auto-updates any more. What is left is one line: a gold
   line icon, the free-shipping threshold, and a small link to the shipping page.

   THE HEIGHT IS THE CONTRACT, and it is unchanged at 2.5rem: two var(--s-2)
   paddings plus one --t-meta line. site.js measures the real value into
   --flag-h/--chrome-h, but the :root fallback near the top of this file states
   that arithmetic, so the first paint and any no-JS visitor lay out correctly.
   Keep the padding on the line itself, as the old .topbar__viewport had it. */
.topbar {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 101;                 /* above the header (100), exactly like the flag */
  background: var(--c-primary);
  color: var(--c-on-dark);
  overflow: hidden;
  font-size: var(--t-meta);
  letter-spacing: 0.03em;
}
.topbar__inner {
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The line wraps as a unit on a narrow phone rather than breaking mid-sentence:
   the message and its link are one statement, and a link stranded on its own row
   under a truncated promise reads as two. */
.topbar__note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin: 0;
  padding-block: var(--s-2);
  padding-inline: var(--s-4);
  text-align: center;
}
/* Gold, underlined, and deliberately not a button: it is a footnote to the
   promise beside it, not a second call to action competing with the cart. */
.topbar__link {
  color: var(--c-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.topbar__link:hover { color: var(--c-accent-soft); }
/* A gold line icon leads the strip and sets its visual rhythm. */
.topbar__ico {
  inline-size: 1.15em;
  block-size: 1.15em;
  flex: none;
  fill: none;
  stroke: var(--c-accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Load-in -------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .rise {
    opacity: 0;
    transform: translateY(18px);
    animation: rise 0.9s var(--ease) forwards;
  }
  .rise--2 { animation-delay: 0.12s; }
  .rise--3 { animation-delay: 0.24s; }
  @keyframes rise {
    to { opacity: 1; transform: none; }
  }
}

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 900px) {
  .nav-toggle { display: flex; }

  /* The whole bar becomes one pill on a phone, with the toggle on one side, the
     logo centred and the cart on the other. The toggle is first in the markup,
     so in RTL it takes the right hand column with no reordering.
     The centre column is the flexible one here, the opposite of desktop, so the
     logo stays centred while the two ends size to their content. */
  .site-header__inner {
    grid-template-columns: auto 1fr auto;
    gap: var(--s-2);
    border-radius: 999px;
    padding: var(--s-2) var(--s-3);
    margin-block: var(--s-2);
    background-color: rgba(244, 237, 225, 0.07);
    /* The brand green, the same tint the desktop nav pill carries, not the
       near-black it used to be: over an ivory section the bar reads as green
       rather than as a black bar dropped on white. */
    background-image: linear-gradient(
      rgba(20, 48, 31, calc(var(--np, 1) * 0.88)),
      rgba(20, 48, 31, calc(var(--np, 1) * 0.88)));
    border: 1px solid rgba(244, 237, 225, 0.12);
    backdrop-filter: blur(16px) saturate(115%);
    -webkit-backdrop-filter: blur(16px) saturate(115%);
  }
  .site-header__logo { justify-self: center; }
  .site-header__logo img { block-size: 40px; }
  /* The mark stays the light cut on a phone, always, and does not cross fade the
     way it does on desktop. The bar here is a pill with a dark fill of its own:
     the photograph shows through it over the hero, and the dark green covers it
     over every ivory section, so the mark is on dark in all of them. The desktop
     switch exists because there the logo sits on the bare bar and meets ivory
     directly; here it would swap in the dark cut over an ivory section and land
     it on the pill's own dark green, dark on dark, which is the one pairing the
     whole tone system is there to avoid. So it is pinned, not switched. */
  .logo--on-dark { opacity: 1; }
  .logo--on-light { opacity: 0; }
  /* Four controls do not fit across 390px without the logo losing the middle,
     so the cart keeps its icon and its count and drops the word. The accessible
     name is on the anchor, so nothing is lost to a screen reader. */
  .cart-btn { padding: 0.45rem 0.7rem; gap: var(--s-1); }
  .cart-btn__label { display: none; }
  .lang-switch__seg {
    min-inline-size: 27px;
    block-size: 24px;
    padding-inline: 0.3rem;
  }
  .nav-toggle { inline-size: 40px; block-size: 40px; }
  /* The drawer hangs off the fixed header, so it is fixed too and starts where
     the header ends. The inline offset is logical, so it opens correctly under
     Hebrew and Arabic now and under English in August without a second rule. */
  .nav.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    position: fixed;
    inset-block-start: var(--header-h, 76px);
    inset-inline: 0;
    padding: var(--s-4) var(--s-5) var(--s-5);
    /* The brand green, like the desktop nav pill, not a near-black panel: the
       drawer belongs to the palette rather than reading as black on the white
       page behind it. Near-opaque so the content under it stays covered, and
       the cream links measure 12.3:1 on this green. */
    background: rgba(20, 48, 31, 0.98);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-block-end: 1px solid var(--c-line-on-dark);
    font-size: var(--t-body);
  }
  .nav.is-open a {
    padding-block: var(--s-3);
    border-block-end: 1px solid rgba(var(--rgb-accent), 0.12);
  }
  .nav.is-open a:last-child { border-block-end: 0; }

  .hero__grid,
  .gift,
  .product { grid-template-columns: 1fr; }
  .hero__figure { order: -1; }

  /* On a phone the opening's stage is exactly one screen and it clips, so the
     hero has to FIT that screen rather than merely start in it. It did not:
     the bottle ran to the bottom edge of the plate and the call to action was
     cut off below the fold entirely, which is not a small crop, it is the one
     thing the hero is for.
     The bottle is therefore capped in svh rather than in px or vw. Its height
     is what competes with the copy for the screen, so height is what has to be
     bounded, and bounding it in the same unit as the stage means it holds on a
     short phone in landscape and on a tall one without a second guess. */
  /* The hero's own vertical padding plus the grid's came to about 317px inside
     an 844px screen, and the bottle's row is what paid for it: it collapsed to
     nothing while the copy took everything. Inside the opening the grid owns
     the spacing, because it is the thing that has to clear the fixed header. */
  /* A longer scrub than the desktop's 130vh. The reveal now finishes at 0.90
     and holds to 1.0, and that hold is a tenth of the scrub: a tenth of 130vh
     is about a finger flick, gone in one gesture. A tenth of this is a real
     stretch of screen with the finished hero pinned in it, which on a phone is
     the difference between resting on the bottle and never landing on it.
     It also slows the camera itself, which reads as a heavier, more deliberate
     push on a small screen. */
  .journey {
    block-size: calc(100vh + 190vh);
    block-size: calc(100svh + 190vh);
  }
  html.has-journey .journey .hero { padding-block: 0; }
  html.has-journey .journey .hero__grid {
    /* The screen, stated outright, in the same unit the stage is built from.
       100% and stretch were both tried first and both left this at its content
       height, 597 inside an 844 screen, because the hero is itself a grid whose
       row is auto: the row sized to this and this asked for the row. Naming the
       height rather than deriving it ends that, and the inner 1fr row below can
       finally resolve, which is what gives the bottle its space back. */
    block-size: 100svh;
    /* The copy takes the height it needs and the bottle takes what is left,
       rather than the bottle taking a share and the copy taking its chances.
       Capping the bottle in svh instead was measured on four phones and the
       button was still cut off on all four, by 12px to 118px: any fixed share
       is a guess about a copy block whose height depends on how the Hebrew
       wraps at that width. This asks the question the other way round and there
       is nothing left to guess.
       If the copy alone ever exceeds the screen the bottle collapses to nothing
       and the words survive, which is the right way for this to fail. */
    grid-template-rows: minmax(0, 1fr) auto;
    /* The hero grid centres its items on a wide screen, which sizes each one to
       its own content. Here the figure's only content is the bottle and the
       bottle is out of flow, so centring gave a row 267px tall holding a box
       0px tall. Stretch is what hands the row's height to the figure. */
    align-items: stretch;
    gap: var(--s-3);
    /* Clears the fixed header at the top, and lifts the group off the bottom
       edge so the bottle stands in the scene the way it does on a wide screen
       instead of sitting on the frame. */
    padding-block: calc(var(--chrome-h, 76px) + var(--s-2)) var(--s-5);
  }
  html.has-journey .journey .hero__figure {
    position: relative;
    min-block-size: 0;          /* or the row refuses to shrink under its image */
    /* Stretch, because the bottle inside it is out of flow: the figure has no
       content left to be as wide as, and centring a box with no width gives a
       box with no width. That rendered the hero with the button finally in
       place and no bottle at all. */
    justify-self: stretch;
  }
  /* Taken out of flow on purpose. A percentage height on an image inside a 1fr
     row is circular, the row sizes to the image and the image to the row, and
     the browser resolves it by falling back to the file's intrinsic 1400px:
     the measured need went UP from 834 to 911 doing it the obvious way.
     Absolute against the figure resolves against a box the grid has already
     given a definite height, so the picture fits the space instead of defining
     it. contain keeps the glass in proportion. */
  html.has-journey .journey .hero__bottle {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    max-inline-size: none;
    object-fit: contain;
  }

  /* The scrim runs bottom up on a phone, because that is where the copy is.
     It was load bearing under the old 4:5 plate: 3.01:1 bare, 4.80:1 under
     this, against a 4.5:1 legal floor. The plate is now the frame the phone's
     camera move comes to rest on, whose lower half is genuinely dark rather
     than merely darker, and the same copy measures 16.22:1 worst pixel. So this
     is margin now rather than the thing carrying the floor. It stays: it costs
     nothing visible on an already dark plate, and it is what keeps the floor
     met if the plate is ever recut. */
  .hero::after,
  [dir="ltr"] .hero::after {
    background: linear-gradient(to top,
      rgba(10, 22, 14, 0.62) 0%,
      rgba(10, 22, 14, 0.55) 42%,
      rgba(10, 22, 14, 0) 74%);
  }
  .scents { grid-template-columns: repeat(2, 1fr); }
  /* The brand block takes the full width and the three link columns share the
     row under it. Four columns at 900px gave each list about 150px, which the
     longer service labels ("משלוחים והחזרות") wrap in the middle of. */
  .site-footer__grid { grid-template-columns: repeat(3, 1fr); gap: var(--s-4); }
  .site-footer__brand { grid-column: 1 / -1; }
  .site-footer__service { justify-content: flex-start; }
  .nav { display: none; }

  /* In the drawer the dropdown is not a dropdown: there is no bar to hang a
     panel under, so it becomes an open/close group in the flow, indented under
     its own button. The button keeps the drawer's own link rhythm. */
  .nav.is-open .brands-menu { display: block; }
  .nav.is-open .brands-menu__btn {
    inline-size: 100%;
    justify-content: space-between;
    padding-block: var(--s-3);
    border-block-end: 1px solid rgba(var(--rgb-accent), 0.12);
    font-size: var(--t-body);
  }
  /* :not([hidden]) is load bearing, not tidiness. `.nav.is-open
     .brands-menu__panel` is (0,3,0) and `.brands-menu__panel[hidden]` up at
     the desktop rule is (0,2,0), so a bare selector here WINS against the
     hidden rule: on a phone the panel rendered open while still carrying the
     `hidden` attribute and aria-expanded="false", and Task 3's toggle would
     have been a no-op on the one breakpoint that matters most here. Adding
     :not([hidden]) means this block simply does not match a closed panel, so
     `display:none` is the only display declaration left standing. */
  .nav.is-open .brands-menu__panel:not([hidden]) {
    position: static;
    transform: none;
    display: grid;
    min-inline-size: 0;
    padding: var(--s-2) var(--s-3);
    background: rgba(10, 22, 14, 0.32);
    border: 0;
    border-block-end: 1px solid rgba(var(--rgb-accent), 0.12);
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    white-space: normal;
  }
  [dir="ltr"] .nav.is-open .brands-menu__panel:not([hidden]) { transform: none; }
  .search-toggle { inline-size: 32px; block-size: 32px; }
  .search-toggle svg { inline-size: 18px; block-size: 18px; }

  /* Below desktop the related strip becomes a one-row carousel instead of
     wrapping onto a second row. Cards are 62% wide so the next one always
     peeks in, which is what tells the reader it scrolls. It bleeds to the
     screen edges: the negative margin only cancels the container's own
     padding, so this never widens the page. */
  /* Doubled class on purpose: the narrower breakpoint later in this file
     also matches .scents, and at equal specificity it would win and put the
     first card in a 1fr column while the rest went to auto columns. */
  .scents.scents--compact {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 62%;
    gap: var(--s-3);
    max-inline-size: none;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline-start: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-block-end: var(--s-2);
    scrollbar-width: none;
  }
  .scents--compact::-webkit-scrollbar { display: none; }
  .scents--compact .scent { scroll-snap-align: start; }
  .scents--compact .arch { aspect-ratio: 3 / 3.1; }
}

@media (max-width: 560px) {
  :root { --s-7: 4rem; --s-8: 5rem; }

  /* Two up, not one. Stacked single cards made the home page 7,962px on a
     390 screen, about twenty screenfuls, and four of the six scents sat below
     any reasonable scroll. The grid stays at two columns from the wider
     breakpoint; here the gap tightens and the hook drops to meta size so the
     narrower column still holds a readable line. */
  /* Scoped away from the compact strip on purpose. This breakpoint sits after
     the 900 one, so an unscoped .scent rule wins over .scents--compact at
     equal specificity and would resize the product page's carousel too. */
  .scents:not(.scents--compact) { gap: var(--s-3); }
  .scents:not(.scents--compact) .arch {
    aspect-ratio: 3 / 3.6;
    margin-block-end: var(--s-3);
  }
  .scents:not(.scents--compact) .scent__hook {
    font-size: var(--t-meta);
    line-height: 1.6;
  }
  .scents:not(.scents--compact) .scent__name { font-size: 0.95rem; }
  .scents:not(.scents--compact) .scent__price { font-size: 1.05rem; }
  /* Pins the carousel to what it already renders here, which the old
     single-column rule was setting from outside. */
  .scents--compact .arch { aspect-ratio: 3 / 3.4; }
  /* Two link columns at phone width: three lists in 390px minus the gutters
     leaves about 110px each, and "בשמים לאישה" does not fit on one line in it. */
  .site-footer__grid { grid-template-columns: 1fr 1fr; gap: var(--s-3); }
  .site-footer__service { flex-direction: column; align-items: stretch; text-align: center; }
  .site-footer__service .btn { justify-content: center; }
  .site-footer__logo img { block-size: 34px; }
  .site-footer__tag { font-size: var(--t-meta); }
  .site-footer__social { flex-wrap: wrap; gap: var(--s-2); margin-block-start: var(--s-2); }
  .site-footer__social svg { inline-size: 17px; block-size: 17px; }
  .site-footer__base { justify-content: flex-start; }
  .pyramid__row { grid-template-columns: 76px 3rem 1fr; gap: var(--s-2); }
  .pyramid__notes { font-size: var(--t-meta); }
  .trust { grid-template-columns: 1fr; }
  .trust__item + .trust__item {
    border-inline-start: 0;
    border-block-start: 1px solid var(--c-hairline);
  }
  .product__buy .btn { flex: 1 1 100%; }

  /* An 18px button is too small an anchor at this width. The bubble is 288px
     wide and hung off the page edge, which made the whole document scroll
     sideways by 58px at 390 even with every bubble shut, because a
     visibility:hidden box still counts toward scrollable overflow. Anchoring
     to the row instead and letting the bubble span it keeps it inside the
     viewport in both reading directions, and an open bubble is no longer cut
     off on a phone. */
  .metric__head, .pyramid__row { position: relative; }
  .metric__head .info, .pyramid__row .info { position: static; }
  .metric__head .info__bubble, .pyramid__row .info__bubble {
    inset-inline: 0;
    inline-size: auto;
  }
}
