/* ==========================================================================
   Hemporium storefront
   Tokens and rules follow DESIGN-PACKAGE.md (pre-rebrand; colour and type
   sections are superseded by _review/velloro-rebrand-brief.md). Do not
   introduce raw hex in components; add a token instead.
   ========================================================================== */

:root {
  /* surfaces */
  --ink:        #07090A;
  --ink-rgb:    7 9 10;     /* channels of --ink, so the scrim can carry alpha */
  --surface:    #0E1113;
  --surface-rgb: 14 17 19;   /* same colour, as channels, so it can carry alpha */
  --surface-2:  #151A1D;
  --line:       #1F2529;

  /* text */
  --bone:       #E8EDE9;
  --bone-rgb:   232 237 233;  /* channels of --bone, for alpha */
  --muted:      #8A9490;

  /* brand: gold-on-black, replacing the old acid-green identity.
     Sampled from the original mark / wordmark art; the palette outlived the name.
     --gold is safe on large text, hairlines and icons against --ink, but NOT
     as small body copy or as text-on-gold - see --gold's use on .btn--primary
     below, which pairs it with --ink text rather than setting gold-on-gold. */
  --gold:       #D4AF37;
  --gold-rgb:   212 175 55;  /* channels of --gold, for alpha via color-mix/rgb() */
  --gold-soft:  #E8C86A;     /* hovers, highlights */
  --gold-deep:  #9C7B1E;     /* pressed states, hairline rules */
  /* The third strength the brief asks for: gold at the intensity of a thing
     you notice without looking at. Paths, particles, the far edge of a glow.
     Kept as a token rather than written as a one-off alpha each time, so the
     atmosphere stays one colour instead of drifting per component. */
  --gold-atmo:  rgb(var(--gold-rgb) / .14);
  --warn:       #FF4D4D;

  /* The chromatic switch drives these two: JS rewrites --accent per flavour,
     which themes buttons, the cart, rings and rules together.

     The default is Natural's green rather than gold because Natural is the
     flavour the page loads selected - the site should already be wearing the
     colour of the product it is showing. It matters for the age gate in
     particular: that renders before boot() runs, so it can only ever use this
     default, and left on gold the gate was the one screen that disagreed with
     everything after it.

     Gold has not gone anywhere - it is the brand's own colour and still
     carries the wordmark, the monogram and the hairline rules through --gold.
     --accent is the product's colour, which is a different job.

     Keep this hex in sync with the 'natural' entry of $flavourColours in
     index.php, and with whichever flavour the DB returns first - that is the
     one the switcher selects on load, so this default only looks right while
     that flavour is Natural. */
  --accent:     #7FC24A;      /* Natural - the default selected flavour */
  --accent-ink: var(--ink);

  /* type */
  --display: 'Cormorant Garamond', Cambria, Georgia, serif;
  --body:    'Onest', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono:    'JetBrains Mono', ui-monospace, 'Cascadia Mono', monospace;

  /* spacing scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px;

  /* The hero and the formula band overlap by this much, so their fades cross
     rather than both reaching zero on the same line. Two sections share it -
     .fband pulls itself up by it, .hero__copy pads by it plus a step so the
     trust list is never what the band lands on - which is why it is a token
     and not a number written twice. */
  /* Floor raised from 16 to 22. The badges made the copy column taller, which
     made the hero taller, which made the photograph's 3% bottom fade longer in
     pixels - and the overlap has to beat that fade, not the viewport. At 1024
     the two crossed at 0.49, just under the point where the seam starts to dip
     to black. */
  --seam: clamp(22px, 1.8vw, 36px);
  --hdr-h: 66px;                     /* the header bar, which the shop front's hero lies under */
  --s10: 128px;   /* the gap between one part of the story and the next */

  --radius:    14px;
  --radius-sm: 9px;
  /* Product widgets only. The design calls for 22-28px on those, and raising
     --radius itself would round every hairline box on the page with them. */
  --radius-lg: 24px;

  /* 1360, up from 1220. The old width left the page a narrow column of
     content marooned in black, which is the specific thing the brief objects
     to. The two product bands run edge to edge and ignore this; everything
     else - formula, documents, FAQ, footer - widens with it. */
  --maxw:      1360px;

  /* One blur for the translucent surfaces, so they are the same material. */
  --glass-blur: 14px;

  --ease: cubic-bezier(.22,.61,.36,1);
  --dur:  420ms;
}

/* --------------------------------------------------------------- reset -- */
*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute only sets display:none at user-agent weight, so any
   component rule with its own display beats it and the element stays on
   screen with no content in it. That is what left an empty green pill in the
   cart when a line had no discount. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  /* The grey-blue rectangle a phone paints over whatever you just tapped.
     It is drawn by the browser on top of the element, ignores border-radius on
     some builds, and lingers long enough after a tap to read as "this control
     is stuck selected" - which is exactly what it looked like on the add and
     quantity buttons. Every control here already answers a press on its own. */
  -webkit-tap-highlight-color: transparent;
}

/* A tap gives a button :focus but not :focus-visible, and the UA's own focus
   ring then sits on the control until something else takes focus. Keyboard
   focus is untouched - :focus-visible above still draws the accent ring. */
:focus:not(:focus-visible) { outline: none; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font: 400 16px/1.55 var(--body);
  /* clip, not hidden - and the difference is the sticky header.

     `overflow-x: hidden` forces the used value of overflow-y to `auto`, which
     turns <body> into a scroll container. `position: sticky` then resolves
     against THAT box instead of the viewport - but the page actually scrolls
     on the document element, so the header has nothing to stick to and rides
     up out of view. Chrome papers over it; iOS Safari does not, which is why
     the navbar stayed at the top of the page on a phone and nowhere else.

     `clip` prevents the same horizontal overflow without establishing a
     scroll container, so sticky keeps resolving against the viewport. The
     `hidden` above it is the fallback for anything too old to know `clip`,
     which is the behaviour those browsers already had. */
  overflow-x: hidden;
  overflow-x: clip;
  /* ambient wash, recoloured by the flavour switch */
  background-image:
    radial-gradient(1100px 620px at 50% -8%,
      color-mix(in srgb, var(--accent) 13%, transparent), transparent 68%);
  transition: background-image var(--dur) var(--ease);
}

img { max-width: 100%; display: block; height: auto; }

/* ------------------------------------------------- shop copy is locked -- */
/* No text selection and no dragging pictures off the page.

   Scoped off .adm rather than applied to :root, because admin.css is layered
   on this file and the panel is a working tool - order numbers, addresses
   and phone numbers get copied out of it all day. .setup is install.php,
   which prints a filename you are meant to copy and then delete.

   Form fields are handed selection straight back. user-select: none
   inherits into inputs in Blink, and while it still lets you TYPE, it stops
   you selecting what you typed - so a customer could not double-click a
   mistyped word in the checkout to fix it. Locking the shop's copy is the
   ask; breaking its own forms is not.

   -webkit-user-drag is Blink and WebKit only. Gecko ignores it, so the
   dragstart guard in anim.js covers Firefox - this is the half that works
   with JS off. */
body:not(.adm):not(.setup) {
  -webkit-user-select: none;
  user-select: none;
}
body:not(.adm):not(.setup) :is(input, textarea, select, [contenteditable]) {
  -webkit-user-select: text;
  user-select: text;
}
body:not(.adm):not(.setup) img {
  -webkit-user-drag: none;
  user-drag: none;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* letter-spacing was -.015em (tight) for Unbounded, a geometric sans that
   wants crowding. Cormorant Garamond is a serif built to breathe - the
   wordmark it's named after uses generous tracking - so headings now open up
   instead of tightening. */
h1, h2, h3 { font-family: var(--display); font-weight: 700; line-height: 1.1; margin: 0; letter-spacing: .006em; }
p { margin: 0; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* Focus is never removed. Only restyled. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--s5); }

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--s4);
}

.lede { color: var(--muted); font-size: 18px; max-width: 62ch; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Section rhythm. Uses a single class so nothing cancels out later. */
.band { padding-block: var(--s9); }
.band + .band { padding-top: 0; }

/* The run below the oil.

   The design wants documents, questions and footer to read as one dark field
   with gold on it, rather than as three bands stacked. Two things do that: a
   deeper ground than the page's own, and a gradient rather than an edge where
   it begins - so the oil section does not end, it darkens into what follows. */
/* The closing field: documents and questions on one ground, with the gold
   artwork over the top of it and the page darkening into it from the oil. */
.closing {
  position: relative;
  isolation: isolate;
  /* The page's own ground. This was #070D16 - a navy invented to sit under
     the artwork - and against the neutral near-black the flavour and oil
     sections use it read as a different section of a different website. The
     artwork is navy; the page is not; the page wins and the artwork is
     blended into it below. */
  background: var(--ink);
}

/* The artwork, full width at its own aspect, hard against the top of the
   field so its gold arc IS the join with the oil section - there is no black
   band between them to be a gap, because the arc occupies that space. */
.closing__glow {
  position: absolute;
  top: 0; left: 0;
  z-index: -2;
  width: 100%;
  height: auto;
  max-width: none;
  pointer-events: none;
  /* Faint, and blended at every edge.

     At .85 the picture brought its own navy with it and the join with the
     oil section above was a colour change. At .38, masked in from all four
     sides, what survives is the gold - a glow at the edges of the field
     rather than a panel laid on it. */
  opacity: .38;
  -webkit-mask-image:
    linear-gradient(to bottom, transparent, #000 18%, #000 55%, transparent 90%),
    linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image:
    linear-gradient(to bottom, transparent, #000 18%, #000 55%, transparent 90%),
    linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* The scrim, and it is the whole reason this section is legible.

   The gold in this picture is not only at the edges - an arc runs across the
   top of it and swirls drift through the middle. Text was being laid over
   that: a gold heading on a gold swirl, which is not low contrast, it is no
   contrast, and the claims underneath were competing with the sparkle behind
   them.

   So the field's own colour is washed back over the middle, heaviest where
   the words are and clearing towards the left and right edges, which is
   exactly where the design keeps its gold. The picture is not dimmed
   uniformly - that would have dulled the edges too - it is dimmed WHERE THE
   READING HAPPENS. */
.closing::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(100% 70% at 50% 44%,
      rgb(var(--ink-rgb) / .96) 0%,
      rgb(var(--ink-rgb) / .82) 42%,
      rgb(var(--ink-rgb) / .35) 68%,
      transparent 86%);
}

/* The oil section gives up its bottom padding entirely: the arc is what
   follows the product now, not a strip of ink. */
.band:has(+ .closing) { padding-bottom: 0; }

/* And the heading clears the arc rather than sitting in it. Scaled with the
   viewport because the artwork is sized by width, so the arc's height moves
   with it - a fixed value clears it at 1440 and lands in it at 1920. */
.band--trust { padding-block: clamp(86px, 7.4vw, 140px) var(--s6); }
/* On a desktop the formula band above it is gone (formula.php), and with
   it the arc this padding cleared: the hero's foot is what stands above. */
@media (min-width: 941px) { .band--trust { padding-top: clamp(56px, 5vw, 88px); } }
.band--faq   { padding-block: 0 var(--s7); }

/* ---------------------------------------------------------- age gate -- */
/* ---------------------------------------------------------- age gate -- */
/* The first thing every visitor sees, so it carries the identity properly:
   the real monogram, the serif lockup the header uses, and two answers that
   are visibly the same component at different weights. */
/* Already answered, so it never renders at all.
   The class is put on <html> by a one-line inline script in the head, which
   runs while the document is still parsing - long before this element exists,
   and therefore before the browser has anything to paint. site.js still sets
   the hidden attribute when it runs, and that is the belt to this braces:
   with JavaScript off, or if the inline script throws, the gate shows and the
   visitor is asked their age, which is the safe direction to fail in. */
html.age-ok .agegate { display: none !important; }

.agegate {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: var(--s6);
  /* A pool of brand light behind the mark rather than flat black - on a plain
     ink field the monogram had nothing to sit in and the panel read as an
     error page. */
  background:
    radial-gradient(820px 560px at 50% 30%,
      rgb(var(--gold-rgb) / .16), rgb(var(--gold-rgb) / .04) 46%,
      rgb(var(--gold-rgb) / 0) 72%),
    var(--ink);
  /* Nothing here is meant to be taken away and pasted somewhere: it is a
     doorway, not a document. Dragging a selection across the question also
     used to leave a blue highlight sitting over the panel, which is the one
     piece of unstyled browser chrome the whole screen had. */
  user-select: none;
  -webkit-user-select: none;
}
.agegate[hidden] { display: none; }

.agegate__box {
  max-width: 520px; text-align: center;
  display: flex; flex-direction: column; align-items: center;
}

/* Was the brand name as 13px letter-spaced text. This is the actual mark, at
   a size that can carry the page. The mark is a letterform on a transparent
   ground now, so nothing hides an edge: no radius, no blend. */
.agegate__mark {
  width: clamp(88px, 14vw, 120px); height: auto;
  display: block;
  margin-bottom: var(--s5);
  /* The mark is decoration - it is aria-hidden and has an empty alt - so it
     has nothing to offer a pointer. Refusing events is what actually stops
     the drag; -webkit-user-drag alone leaves it grabbable in Firefox, and the
     draggable="false" attribute in the markup covers that side. */
  -webkit-user-drag: none;
  pointer-events: none;
}

.agegate__word {
  font-family: var(--display);
  font-size: clamp(24px, 3.4vw, 32px); font-weight: 600; line-height: 1;
  letter-spacing: .30em; padding-left: .30em;   /* trailing space padded back */
  text-transform: uppercase; color: var(--gold);
  margin-bottom: var(--s3);
}
.agegate__eyebrow {
  font: 500 11px/1 var(--mono);
  letter-spacing: .22em; text-transform: uppercase;
  color: rgb(var(--bone-rgb) / .45);
  margin-bottom: var(--s7);
}

/* A hairline between the identity and the question, so the panel reads as two
   parts rather than one long stack. */
.agegate__eyebrow::after {
  content: ''; display: block;
  width: 46px; height: 1px; margin: var(--s6) auto 0;
  background: rgb(var(--gold-rgb) / .45);
}

/* The question is set in the body sans, not the display serif.
   Cormorant is a light, high-contrast serif - lovely for the wordmark, but its
   Cyrillic thins out badly at large sizes and this is a question a visitor has
   to read and answer, not admire. Serif for the brand, sans for anything
   functional, which is also how the rest of the site is set. */
.agegate__q {
  font-family: var(--body);
  font-size: clamp(26px, 4vw, 38px);
  font-weight: 600; line-height: 1.22; letter-spacing: -.015em;
  /* s7, not s4. A lede used to sit between the question and the buttons and
     carried this gap; it said the same thing as .agegate__legal underneath
     them, so it went, and the space it was holding open has to come from
     somewhere or the answers crowd the question. */
  margin-bottom: var(--s7);
}
.agegate__q em { font-style: normal; color: var(--accent); }

.agegate__row {
  display: flex; gap: var(--s3); justify-content: center; flex-wrap: wrap;
  width: 100%;
}
/* Both answers are the same component - same height, same shape - so the
   choice reads as a choice. Only the weight differs. */
.agegate__act {
  flex: 1 1 auto; min-width: 168px; max-width: 300px;
}
/* No transform rules here on purpose. These carry .btn--apple, whose press is
   a spring in site.js - a CSS transform transition on the same element fights
   it. The earlier lift-on-hover was also the wrong idea: Apple's buttons do
   not jump toward the cursor, they light up where it is and compress when
   pressed. */

/* "Не" is a real button, not a link in disguise, but stays clearly secondary. */
#ag-no { color: rgb(var(--bone-rgb) / .62); }
#ag-no:hover { color: var(--bone); border-color: rgb(var(--bone-rgb) / .38); }

.agegate__legal {
  margin-top: var(--s7); font-size: 12.5px; line-height: 1.6;
  color: rgb(var(--bone-rgb) / .38); max-width: 40ch;
}

@media (max-width: 520px) {
  /* Stacked and full width - two half-width buttons on a phone are a
     mis-tap waiting to happen on a question that gates the whole site. */
  .agegate__row { flex-direction: column; }
  .agegate__act { max-width: none; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .agegate__act, .agegate__act:hover, .agegate__act:active {
    transition: none; transform: none;
  }
}

/* ------------------------------------------------------------ header -- */
.hdr {
  position: sticky; top: 0; z-index: 90;
  background: color-mix(in srgb, var(--ink) 86%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  transition: background .35s var(--ease), border-color .35s var(--ease);
}
/* On the shop front's first screen the bar lies over the hero picture with
   no ground of its own; site.js takes is-clear off once the page has
   scrolled and the blurred ground comes back. */
.hdr.is-clear { background: transparent; border-bottom-color: transparent; backdrop-filter: none; }
.hdr__in { display: flex; align-items: center; gap: var(--s5); height: var(--hdr-h); }
/* On a desktop the bar spans the window, with the hero's own side margin
   (the rail's, the foot lines'): the brand stands at the left where the
   rail does, the basket at the right - and the wordmark stays clear of the
   hero's ring at every width. In the 1360px column it drifted towards the
   middle on a wide screen and the ring came up behind its last letters. */
@media (min-width: 941px) {
  .hdr__in { max-width: none; padding-inline: clamp(24px, 3.4vw, 60px); }
}
/* over the picture, the wordmark carries its own shadow */
.hdr.is-clear .hdr__word { text-shadow: 0 1px 14px rgb(0 0 0 / .7); }
/* the actions - account and basket - as one group on the right */
.hdr__acts { display: flex; align-items: center; gap: var(--s3); margin-left: auto; }
.hdr__nav ~ .hdr__acts { margin-left: 0; }
.hdr__brand {
  text-decoration: none;
  /* 44px minimum touch target - the wordmark image is shorter than that, so
     the link box stays tall and the image just sits centred inside it. */
  display: inline-flex; align-items: center; min-height: 44px;
}
.hdr__brand { gap: var(--s3); }

/* The mark is the gold H holding a leaf and a drop, on a transparent
   ground - a letterform, not a disc, so no radius: a circle would clip its
   serifs. 42px beside 22px of tracked capitals: the H is the identity and
   should stand a little taller than the name, as it does in the logo. The
   header's own height does not change: .hdr__in is 66px and the brand link
   keeps its 44px minimum. */
.hdr__mark { width: 42px; height: 42px; display: block; flex: none; }

/* Live text, matching the logo's own treatment: the display serif, uppercase,
   and tracked wide. The trailing letter-space is padded back on the left so the
   word still looks optically centred against the mark instead of sitting a
   third of a letter to the left. */
.hdr__word {
  font-family: var(--display);
  font-size: 22px; font-weight: 600; line-height: 1;
  letter-spacing: .30em; padding-left: .30em;
  text-transform: uppercase;
  color: var(--gold);
  white-space: nowrap;
}
@media (max-width: 560px) {
  /* The nav collapses before this does; shrink the word rather than wrap it. */
  .hdr__word { font-size: 19px; letter-spacing: .20em; padding-left: .20em; }
  /* A shade smaller than the desktop mark, because the phone's bar is 66px
     too but has two round controls on the other end of it. */
  .hdr__mark { width: 36px; height: 36px; }
}
/* The nav in the middle of the bar, the design's way: auto margins on both
   sides, so it centres in what the brand and the actions leave. */
.hdr__nav { display: flex; gap: clamp(var(--s5), 2.6vw, 44px); margin-inline: auto; }
.hdr__nav a { color: rgb(var(--bone-rgb) / .78); text-decoration: none; font-size: 15px; transition: color .18s; }
.hdr__nav a:hover { color: #fff; }

/* The header runs out of room long before the phone breakpoint arrives. At
   768 it wants 839px for 758 of space: brand 193, nav 355, account 93, cart
   126, plus 72 of gaps. It has overflowed the document sideways there for a
   while - measured twice against earlier commits and confirmed pre-existing
   both times - and raising --maxw to 1360 widened the header along with
   everything else, so the band where it fails got wider too.

   Tightened rather than hidden. Dropping the nav would cost every tablet
   visitor their in-page navigation to save an overflow that closing the gaps
   and the account label already fixes: -36 from the gaps, -24 from the nav's
   own spacing, -40 from the label, against 81 needed. */
@media (max-width: 1000px) {
  .hdr__in  { gap: var(--s3); }
  .hdr__nav { gap: var(--s4); }
  .hdr__nav a { font-size: 14px; }
  .acctbtn__t { display: none; }
}

.cartbtn {
  position: relative;   /* the landing ring is ::after */
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding: 0 var(--s3) 0 var(--s4);
  background: rgb(var(--bone-rgb) / .06); color: var(--bone);
  /* a pill, as the design draws it, on a light glass ground */
  border: 1px solid rgb(var(--bone-rgb) / .14); border-radius: 999px;
  cursor: pointer;
  /* This is a <button> on the shop front and an <a> everywhere else - see
     cart_button() in config.php - and an anchor brings the browser's underline
     with it. The rule never needed saying while the control existed only as a
     button. */
  text-decoration: none;
  transition: border-color .18s, background .18s, box-shadow .32s var(--ease);
}
.cartbtn:hover { border-color: var(--accent); }

/* The bag, and the tick that replaces it for a moment.

   Both live in one 18px slot with the tick stacked on top of the bag, so
   swapping them moves nothing around it - the button keeps its width and the
   count keeps its place. The bag is the affordance: it says what the control
   opens without reading the word, which is what lets a phone drop the word.
   The tick is the acknowledgement, and it is the piece the old effect was
   missing - a ring and a bump say something happened, a tick says WHAT. */
.cartbtn__i {
  position: relative;
  width: 18px; height: 18px; flex: none;
  display: grid; place-items: center;
  color: var(--muted);
  transition: color .18s;
}
.cartbtn:hover .cartbtn__i { color: var(--bone); }
.cartbtn__i svg {
  grid-area: 1 / 1;
  display: block;
  transition: opacity .2s var(--ease), transform .28s var(--ease);
}
.cartbtn__tick { opacity: 0; transform: scale(.5); color: var(--accent); }

.cartbtn.is-added .cartbtn__i { color: var(--accent); }
.cartbtn.is-added .cartbtn__bag  { opacity: 0; transform: scale(.5); }
.cartbtn.is-added .cartbtn__tick { opacity: 1; transform: scale(1); }

.cartbtn__t { white-space: nowrap; }

/* The ghost that flies into the cart.

   Only its position and size are set in JS, because those are measured off the
   plate at click time. Everything that makes it look like a lifted piece of
   the picture rather than a floating thumbnail belongs here.

   The shadow is what sells it: at rest the ghost is pixel-identical to the
   plate underneath, so without something to separate them the flight looks
   like the picture smearing. A drop shadow lifts it off the page on the first
   frame. */
.fly {
  object-fit: cover;
  border-radius: var(--radius-sm);
  z-index: 200;
  pointer-events: none;
  will-change: transform, opacity;
  box-shadow: 0 22px 60px -14px rgb(0 0 0 / .78),
              0 0 0 1px rgb(var(--bone-rgb) / .06);
}

/* What the cart button does when something lands in it.

   Adding no longer opens the drawer, so this and the flying ghost are the
   whole of the acknowledgement - the button has to answer the click itself.

   One beat, not a loop, for the same reason #cart-go's ring is: a control that
   pulses forever stops meaning anything. The ring fades out on .cartbtn's own
   transition rather than being animated, so the class can be dropped at any
   point without a jump. */
.cartbtn.is-added {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
.cartbtn.is-added .cartbtn__n { animation: cartbeat .58s var(--ease); }

/* The landing ring: one ring that leaves the button outward and fades.

   The static ring above says "this button changed"; this says "something
   arrived HERE", which is the half the flight needs answered - the ghost has
   just vanished into this spot and nothing acknowledged the impact.

   It expands rather than contracts. A contracting ring reads as a target
   being acquired; an expanding one reads as the impact spreading, which is
   the event that actually happened. */
.cartbtn::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.cartbtn.is-added::after { animation: cartland .62s var(--ease) forwards; }

@keyframes cartbeat {
  0%   { transform: scale(1); }
  36%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

@keyframes cartland {
  0%   { opacity: .85; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.38); }
}

@supports not (color: color-mix(in srgb, red, blue)) {
  .cartbtn.is-added { box-shadow: 0 0 0 3px var(--gold-atmo); }
}
.cartbtn__n {
  font-family: var(--mono); font-size: 12px; min-width: 21px; height: 21px;
  display: grid; place-items: center; border-radius: 999px;
  /* gold, as the design's pill has it; the empty state stays grey */
  background: var(--gold); color: var(--ink); font-weight: 600;
}
.cartbtn__n[data-empty="1"] { background: var(--line); color: var(--muted); }

/* The four badges: born in the old hero, kept for the checkout's done screen
   (.done .hbadges), which draws the same row under its seal.

   A ring, an icon and two lines of caps. The ring is drawn here rather than
   borrowed from .flav__medallion, which is 62px and fixed and carries a tinted
   fill: this one has to shrink with the column and be quiet enough that four
   of them in a row do not outweigh the buttons above them.

   The labels break where the markup breaks them. Four short two-line labels in
   a row want the same number of lines each, and left to wrap they do not - at
   some widths one of them takes three and the row goes ragged. */
.hbadges {
  list-style: none; padding: 0;
  margin: clamp(var(--s6), 2.6vw, var(--s8)) 0 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(var(--s3), 1.4vw, var(--s5));
  max-width: 34rem;
}
.hbadges li {
  display: flex; flex-direction: column; align-items: center;
  gap: .85em; text-align: center;
}
.hbadges__ring {
  flex: none;
  width: clamp(36px, 3.1vw, 50px);
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid; place-items: center;
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--gold) 46%, transparent);
}
.hbadges__ring svg { width: 46%; height: auto; display: block; }
.hbadges__label {
  font-family: var(--mono);
  font-size: clamp(8px, .62vw, 11px);
  letter-spacing: .12em;
  line-height: 1.5;
  text-transform: uppercase;
  color: rgb(var(--bone-rgb) / .68);
}

@supports not (color: color-mix(in srgb, red, blue)) {
  .hbadges__ring { border-color: var(--gold-deep); }
}


/* ------------------------------------------------------------ button -- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 48px; padding: 0 var(--s6);
  border-radius: var(--radius-sm); border: 1px solid transparent;
  font-weight: 500; cursor: pointer; text-decoration: none;
  transition: transform .16s var(--ease), background .2s, border-color .2s, color .2s;
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.btn--primary:hover { filter: brightness(1.1); }
.btn--ghost { background: transparent; color: var(--bone); border-color: var(--line); }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn--wide { width: 100%; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ------------------------------------------------------------ formula -- */
.formula__list {
  list-style: none; margin: var(--s7) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  border-top: 1px solid var(--line);
}
.formula__list li {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) 0; border-bottom: 1px solid var(--line);
  color: var(--bone); font-size: 15px;
}
/* One flat gold dot, not one colour per item - "used in rare doses", same
   restraint as the rest of this identity. Not the chip dot's replacement in
   disguise: this one never varies, so it never had the problem the chip dot
   had. */
.formula__list li::before {
  content: ''; width: 5px; height: 5px; border-radius: 999px;
  background: var(--gold); flex: none;
}
.formula__quotes { display: grid; gap: var(--s5); margin-top: var(--s7); max-width: 70ch; }
.formula__quotes blockquote { margin: 0; font-style: italic; }

/* ==========================================================================
   THE HERO (v6)

   One screen: the ring over the mountains as the ground, the name in the
   middle, and the three products on rock podiums in front of it, each the
   way into its own section below. No copy column, no buttons, no badges -
   what was said here is said in the sections the podiums lead to.

   The header lies over the picture. The hero is pulled up under the sticky
   bar by the bar's own height and pads itself by the same, so the bar's
   ground can be transparent on this screen (site.js clears it back to the
   blurred one once the page has scrolled) while everything in the hero
   still starts below the bar.
   ========================================================================== */
.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: var(--ink);
  /* a full screen, and never so short that the podiums and the title fight */
  min-height: max(640px, 100svh);
  margin-top: calc(-1 * var(--hdr-h));
  display: grid;
}
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* the ring is a little left of centre in the plate; centred cover keeps
     it behind the title at every ratio, and the water at the foot */
  object-position: 50% 40%;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 93%, rgb(0 0 0 / 0) 100%);
  mask-image: linear-gradient(to bottom, #000 93%, rgb(0 0 0 / 0) 100%);
}
/* the water darkens towards the foot so the labels read on it */
.hero::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 34%; z-index: 0; pointer-events: none;
  background: linear-gradient(to bottom, transparent, rgb(var(--ink-rgb) / .55));
}

.hero__in {
  position: relative; z-index: 1;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  justify-items: center;
  /* the foot lines live in the bottom padding, so the labels never meet them */
  padding: calc(var(--hdr-h) + clamp(40px, 7vh, 90px)) clamp(24px, 4vw, 72px) clamp(88px, 12vh, 124px);
  min-height: inherit;
}

/* ---- the title ---- */
.hero__title { text-align: center; }
.hero__title h1 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(38px, 4.6vw, 76px); font-weight: 500; line-height: 1;
  letter-spacing: .12em; text-transform: uppercase;
  color: #fff;
  text-shadow: 0 2px 30px rgb(0 0 0 / .55);
}
.hero__sub {
  margin: .9em 0 0;
  font-family: var(--mono); font-size: clamp(11px, .85vw, 15px);
  letter-spacing: .42em; text-transform: uppercase;
  color: rgb(var(--bone-rgb) / .62);
}

/* ---- the podiums ---- */
.podiums {
  list-style: none; margin: 0; padding: 0;
  align-self: end;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(12px, 2.4vw, 40px);
  width: min(100%, 1360px);
}
.podiums li { min-width: 0; display: flex; justify-content: center; }
.podium {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  width: 100%; max-width: 460px;
  text-decoration: none; color: inherit;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}
/* The picture and, under it, its reflection: the same image flipped,
   faded and squeezed, so the podium stands on the water like the ones the
   plate has painted at the edges. */
.podium__fig {
  position: relative; width: 100%;
  transition: transform .5s cubic-bezier(.22, 1, .36, 1), filter .5s var(--ease);
  filter: drop-shadow(0 30px 40px rgb(0 0 0 / .55));
}
.podium__fig img { display: block; width: 100%; height: auto; }
.podium__fig::after {
  content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 42%;
  background: var(--img) center top / 100% auto no-repeat;
  transform: scaleY(-1) translateY(-2px);
  transform-origin: top;
  opacity: .28;
  -webkit-mask-image: linear-gradient(to top, transparent, #000 90%);
  mask-image: linear-gradient(to top, transparent, #000 90%);
  filter: blur(1.5px);
  pointer-events: none;
}
.podium:hover .podium__fig, .podium:focus-visible .podium__fig {
  transform: translateY(-10px);
  filter: drop-shadow(0 40px 50px rgb(0 0 0 / .6)) drop-shadow(0 0 40px rgb(var(--gold-rgb) / .22));
}
.podium:active .podium__fig { transform: translateY(-4px) scale(.99); }
.podium--soon:hover .podium__fig, .podium--soon:focus-visible .podium__fig { transform: translateY(-4px); }

/* the label: a ring with the arrow, the name in the serif, the line under
   it in the mono - left-aligned as a pair, the way the design sets them */
.podium__label {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 16px;
  margin-top: clamp(22px, 3.4vh, 44px);
}
.podium__arrow {
  flex: none; width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid rgb(var(--gold-rgb) / .7);
  color: var(--gold);
  background: rgb(var(--ink-rgb) / .35);
  transition: background .3s var(--ease), color .3s var(--ease), transform .3s var(--ease), box-shadow .3s var(--ease);
}
.podium__arrow svg { transition: transform .3s var(--ease); }
.podium:hover .podium__arrow, .podium:focus-visible .podium__arrow {
  background: var(--gold); color: var(--ink);
  box-shadow: 0 0 0 4px rgb(var(--gold-rgb) / .16), 0 8px 24px rgb(0 0 0 / .4);
}
.podium:hover .podium__arrow svg, .podium:focus-visible .podium__arrow svg { transform: translateX(3px); }
.podium--soon:hover .podium__arrow, .podium--soon:focus-visible .podium__arrow { background: rgb(var(--gold-rgb) / .12); color: var(--gold); box-shadow: none; }
.podium--soon:hover .podium__arrow svg { transform: none; }
.podium__t { display: grid; gap: 5px; text-align: left; }
.podium__t b {
  font-family: var(--display); font-size: clamp(18px, 1.35vw, 22px); font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase; line-height: 1;
  color: #fff;
}
.podium__t small {
  font-family: var(--mono); font-size: clamp(9.5px, .68vw, 11.5px);
  letter-spacing: .2em; text-transform: uppercase;
  color: rgb(var(--gold-rgb) / .8);
}
/* the one that is coming: quieter, and a hint where a link would go */
.podium--soon { cursor: default; }
.podium--soon .podium__t b { color: rgb(var(--bone-rgb) / .82); }
.podium__hint {
  position: absolute; left: 50%; bottom: -30px;
  transform: translate(-50%, 6px);
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid rgb(var(--gold-rgb) / .4);
  background: rgb(var(--ink-rgb) / .8);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--gold); white-space: nowrap;
  opacity: 0; transition: opacity .3s var(--ease), transform .3s var(--ease);
  pointer-events: none;
}
.podium--soon:hover .podium__hint, .podium--soon:focus-visible .podium__hint { opacity: 1; transform: translate(-50%, 0); }
.podium:focus-visible .podium__arrow { outline: 2px solid var(--gold); outline-offset: 3px; }

/* ---- the rail, the foot lines ---- */
.hero__rail {
  position: absolute; left: clamp(24px, 3.4vw, 60px); top: 34%;
  margin: 0;
  display: grid; gap: 18px; justify-items: start;
}
.hero__rail-w {
  font-family: var(--mono); font-size: clamp(10px, .72vw, 12.5px);
  letter-spacing: .34em; text-transform: uppercase; line-height: 1.9;
  color: var(--gold);
}
.hero__rail-line { display: block; width: 1px; height: 44px; background: rgb(var(--gold-rgb) / .5); }
.hero__rail-t { font-size: clamp(13px, .95vw, 16px); line-height: 1.45; color: rgb(var(--bone-rgb) / .82); }

.hero__foot {
  position: absolute; bottom: clamp(20px, 3vh, 34px);
  margin: 0;
  display: flex; align-items: center; gap: 14px;
  font-family: var(--mono); font-size: clamp(9.5px, .68vw, 11.5px);
  letter-spacing: .3em; text-transform: uppercase; line-height: 1.7;
  color: rgb(var(--bone-rgb) / .62);
  pointer-events: none;
}
.hero__foot--l { left: clamp(24px, 3.4vw, 60px); }
.hero__foot--r { right: clamp(24px, 3.4vw, 60px); text-align: right; }
.hero__foot-rule { display: block; width: 56px; height: 1px; background: rgb(var(--gold-rgb) / .55); }

@media (max-width: 1180px) {
  /* the rail and the pager would crowd the outer podiums */
  .hero__rail { display: none; }
  .hero__foot--r { display: none; }
}
@media (max-width: 1024px) {
  .hero__title h1 { letter-spacing: .08em; }
  .podium__label { gap: 12px; }
  .podium__arrow { width: 40px; height: 40px; }
}
@media (max-height: 760px) and (min-width: 941px) {
  /* a short window: the podiums take less of the height so the title keeps
     its air */
  .podium { max-width: 360px; }
  .podium__label { margin-top: 18px; }
}
@media (prefers-reduced-motion: reduce) {
  .podium__fig, .podium__arrow, .podium__arrow svg, .podium__hint { transition: none; }
  .podium:hover .podium__fig, .podium:focus-visible .podium__fig { transform: none; }
}

@media (max-width: 899px) {
  /* Under 940px the phone's own hero (app.css / app.js) stands in for this
     one; these serve only a browser without the script: the podiums stack. */
  .hero { margin-top: 0; }
  .hero__in { padding-top: clamp(28px, 5vh, 64px); }
  .podiums { grid-template-columns: minmax(0, 1fr); }
  .hero__rail, .hero__foot { display: none; }
}

/* ==========================================================================
   THE FORMULA BAND

   The whole plate, edge to edge, with the live interface laid over the printed
   one - the same construction as the cartridge and oil bands. What goes live
   and what stays printed follows the rule those two settled:

     an opaque live surface can cover its printed counterpart;
     bare live text cannot cover bare printed text.

   The nodes are the first case and are covered. The heading is the second, and
   was cropped off the plate for a while because of it - until the ground
   behind it was actually measured and turned out to be flat: p50 luma 4.2, p90
   12.8, black with a scatter of particles. An opaque patch of the plate's own
   black vanishes into that. So the heading is covered too, and the plate keeps
   the 84px of particle field the crop had been throwing away.

   That field is the point. The design has no black between its sections - one
   continuous field runs out of the hero, through this band and down into the
   cartridge section - and the cropped band, with the page's black above and
   below it, read as a strip floating in a void.

   Every figure is measured off the 941x223 plate:

     oval centres  fit x = 12.03% + 13.06%*i to within 0.15% across all seven
     oval ring     y 89..183, so centre 60.99% and height 42.15%
     widest point  85px of 941, so width 9.03%
     printed text  eyebrow x 440..491 y 43..49; heading x 338..614 y 61..83
                   (the white ink at x 460..499 below y 83 is the drop's
                   specular highlight, not the heading)
     plate ground  rgb(2 4 4), flat - the hero's was vignetted, which is the
                   whole reason the same veil failed there
   ========================================================================== */

/* No padding at the top at all, and a little at the bottom. .band's own 96px
   would put back most of the black this section exists to remove, and the
   band's feathered edges are the separation - it does not need a gap as well.

   The bottom keeps some, because what follows is the lede: running body copy
   straight into the artwork would be worse than a little air. */
/* .band.band--formula, not .band--formula: a later media query sets
   .band { padding-block: var(--s8) } and at equal specificity the later rule
   wins, which put 64px back under this section on narrow screens. Two classes
   settle it by weight rather than by position. */
.band.band--formula { padding-block: 0; }

/* Outside .wrap, so it is full bleed without negative margins. */
.fband {
  position: relative;
  width: 100%;
  /* Pulled up into the hero's last pixels so the two fades cross rather than
     both bottoming out on the same line. The band is later in the document so
     it paints over, and its top is transparent, so what shows through there
     is the hero. Removed on the stacked branch, where the plate is hidden and
     there is nothing to dissolve into. */
  margin-top: calc(-1 * var(--seam));
  /* The plate's own ratio. Everything below is a percentage of this box, so if
     it stops matching the plate the overlays stop landing on the artwork. */
  aspect-ratio: 941 / 223;
  overflow: hidden;
  isolation: isolate;
  /* cqw, not vw: the overlay has to track the PLATE, and the plate is the
     width of this box. Sized in vw the type drifts off the artwork as soon as
     the box stops being the full viewport. */
  container-type: inline-size;

  /* The plate's own green, sampled twice and the same both times: the printed
     eyebrow cores at rgb(181 209 23) and the printed node icons at
     rgb(190 222 20). Everything live inside this band that wants to be green
     uses it.

     Deliberately NOT --accent. --accent is the selected flavour's colour and
     re-themes on every flavour change; the artwork underneath cannot follow
     it, so on any flavour but Natural the live green and the printed green
     disagreed inside the same picture. */
  --plate-lime: #B6D41E;
}
.fband__plate {
  position: absolute; inset: 0; z-index: 1;
  display: block;
  width: 100%; height: 100%;
  /* fill, not cover: the box already carries the plate's ratio, so there is
     nothing to crop and cover could shave an edge at some widths. */
  object-fit: fill;
  /* Feathered top and bottom, because the plate's ground and the page's are
     not the same black - rgb(2 4 4) against --ink's rgb(7 9 10). Five levels
     is nothing to name and plenty to see: butted together they drew a hard
     rule across the full width at each edge of the band.

     Only top and bottom. The left and right edges are the plate's own - the
     artwork was composed to run to them - and the band is full bleed, so there
     is no page beside it to ramp into. */
  -webkit-mask-image: linear-gradient(to bottom,
    rgb(0 0 0 / 0) 0%, #000 3.5%, #000 94%, rgb(0 0 0 / 0) 100%);
  mask-image: linear-gradient(to bottom,
    rgb(0 0 0 / 0) 0%, #000 3.5%, #000 94%, rgb(0 0 0 / 0) 100%);
}

/* The veil over the printed eyebrow and heading.

   It is the FULLY OPAQUE part of this that has to clear the printed ink, not
   the box - which is the mistake the first version made. Feathered 16%/84% of
   a box running 15.5..39%, it was only solid to 35.2%, and the heading's
   descenders reach 37.2%: the tails came through under the live heading as a
   row of faint grey marks.

   Measured, as percentages of the plate:

     printed eyebrow   x 46.8..52.2   y 19.3..22.0
     printed heading   x 35.9..65.2   y 27.4..37.2   (excluding the drop's
                       specular highlight at x 51%, which reads as white ink
                       but belongs to the artwork)
     drop's gold tip   starts y 37.2 - the same row the descenders end on

   So the box is 27..73% by 12..40%, and the feather is asymmetric: 16% at the
   top, where there is nothing but field to lose, and 8% at the bottom, where
   every percent of fade eats into the drop. Solid from 16.5% to 37.8%, which
   clears the eyebrow by 2.8 and the descenders by 0.6, and the fade below that
   only dims the first six rows of the drop's tip - three pixels wide on the
   plate.

   It is a piece of the plate, not a colour. Two earlier versions were flat
   fills and both were visible for the same reason in two different ways:
   rgb(2 4 4) - the plate's black sampled between the particles - read as a
   dark box, because the field here is median luma 4 but MEAN luma 7.5 and the
   eye averages the sparkles in rather than seeing past them. Matched to the
   mean at rgb(7 8 6) it stopped being dark and started being smooth, which
   against a field of particles is just as visible: you could see where the
   sparkles stopped.

   So the veil carries the strip of field directly above the printed text -
   same columns, so the particle scale and the filament character match - and
   the fill behind it stays as the fallback if the image never arrives. See
   tools/pack-v3-assets.py for how the patch is cut and why it is mirrored.

   background-size is 100% 100% and not cover: the patch is cut to 433x62,
   which IS this box at plate scale, so it maps 1:1 and the particles land at
   the size they are everywhere else in the band. cover would rescale them.

   Feathered on all four edges. Not for the black, which matches to under a
   luma level, but for the particles: a hard edge cuts sparkles in half, and
   half a sparkle is more visible than a whole one. */
.fband__veil {
  position: absolute; z-index: 2;
  left: 27%; top: 12%;
  width: 46%; height: 28%;
  /* The image itself is set inline in the markup; this is the ground it sits
     on and the fallback if it never arrives. */
  background-color: rgb(7 8 6);
  background-position: center;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;
  -webkit-mask-image:
    linear-gradient(to right, rgb(0 0 0 / 0) 0%, #000 14%, #000 86%, rgb(0 0 0 / 0) 100%),
    linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 16%, #000 92%, rgb(0 0 0 / 0) 100%);
  mask-image:
    linear-gradient(to right, rgb(0 0 0 / 0) 0%, #000 14%, #000 86%, rgb(0 0 0 / 0) 100%),
    linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 16%, #000 92%, rgb(0 0 0 / 0) 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Flowed, not pinned line by line. The hero had to pin every baseline because
   its live copy was tracing printed copy it could not fully hide; here the
   veil has already erased what is underneath, so the block only has to sit in
   the right place and can be sized for reading. */
.fband__head {
  position: absolute; z-index: 3;
  left: 0; right: 0; top: 14.8%;
  text-align: center;
  pointer-events: none;
}
/* The floor is 8px rather than 9. It is not about legibility at 8px - it is
   that the eyebrow is the only thing here sized in px, so the moment its clamp
   bottoms out it stops shrinking with the band and starts pushing the headline
   down into the ovals. At 1001px, the narrowest width that still shows the
   plate, a 9px floor put the headline's descenders on the discs. */
.fband__head .eyebrow {
  margin: 0 0 .5em;
  /* Sized off the printed eyebrow, not off the page's 12px default: that
     line is 62px wide on a 941px plate - 6.6% of the band - and the page
     default rendered it at 4.6%, a caption where the artwork has a label. */
  font-size: clamp(9px, 1.05cqw, 20px);
  letter-spacing: .30em;
  color: var(--plate-lime);
}
/* Regular, not the 700 every other h2 on the page inherits.

   This is the one heading that has to belong to a photograph rather than to
   the page, and the plate sets it light: measured across the printed line, the
   vertical strokes are 2px against a 16px cap, so stem/cap is 0.125 - regular
   for a high-contrast serif, where bold would be 0.20 and up. At 700 the same
   words in the same face came out blocky beside the artwork they sit in, which
   is what made it read as pasted on rather than part of the picture.

   Dropping the weight also drops the line width, so the size goes up to keep
   the line at the plate's own 34.2% of the band. */
.fband__head h2 {
  margin: 0;
  font-weight: 400;
  font-size: clamp(20px, 3.0cqw, 57px);
  /* 1.02, not the 1.1 this face is comfortable at. It is a single line at
     every width, so the leading is doing nothing except pushing the baseline
     down - and down is where the node ovals are. Cormorant's descenders reach
     further than the printed face's do at the same line width, so the
     clearance had to come from somewhere. */
  line-height: 1.02;
  color: var(--bone);
}

.fnodes {
  position: absolute; inset: 0; z-index: 3;
  list-style: none; margin: 0; padding: 0;
}

/* --cx is set per item in the markup, from the measured centre. */
.fnode {
  position: absolute;
  left: var(--cx);
  top: 60.99%;
  width: 10%;      /* printed 9.03% */
  height: 44.84%;  /* printed 42.15% */
  transform: translate(-50%, -50%);
  display: flex;
}

.fnode__disc {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: .5em;
  width: 100%; height: 100%;
  padding: .5em .55em;
  border-radius: 50%;
  text-align: center;
  /* Lime, not gold: gold is the ring around these, not the mark inside. See
     --plate-lime on .fband for where the colour comes from and why it is not
     --accent. */
  color: var(--plate-lime);
  /* The plate's own black, sampled, not the page's --ink. */
  background: rgb(2 4 4);
  border: 1px solid color-mix(in srgb, var(--gold) 46%, transparent);
  transition: transform .3s var(--ease), border-color .3s var(--ease),
              box-shadow .3s var(--ease), color .3s var(--ease);
}
.fnode__ico { display: block; }
/* The printed icons run about a third of the oval's width. Slightly under that
   here, because the live labels are a shade larger than the plate's and the
   two share the same 94px of oval. */
.fnode__ico svg { width: clamp(18px, 2.9cqw, 44px); height: auto; display: block; }

.fnode__label {
  font-size: clamp(10px, .82cqw, 13px);
  line-height: 1.3;
  text-wrap: balance;
  color: rgb(var(--bone-rgb) / .74);
  transition: color .3s var(--ease);
}

/* Grows rather than lifts. The old hover raised the disc 4px, which is fine
   over the page's own ground and wrong over a plate: 4px up uncovers 4px of
   the printed oval along the bottom edge and the ring doubles. Scaling can
   only ever cover more, and the disc is already 1% wider and 2.7% taller than
   what it hides, so there is room for it to. */
.fnode:hover .fnode__disc {
  transform: scale(1.055);
  color: #D2ED4A;
  border-color: color-mix(in srgb, var(--gold) 82%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 26%, transparent),
              0 14px 34px -16px color-mix(in srgb, var(--gold) 62%, transparent);
}
.fnode:hover .fnode__label { color: var(--bone); }

@supports not (color: color-mix(in srgb, red, blue)) {
  .fnode__disc { border-color: var(--gold-deep); }
}

/* The band the plaque sits in.

   Between the formula band and the cartridge section there was nothing but the
   page's own black either side of the plaque, which left it standing in a void
   between two full-bleed bands. This runs the supplied background for that
   stretch edge to edge and puts the plaque on it.

   Feathered top and bottom rather than butted. The plate's ground is a shade
   off --ink and both neighbours are themselves feathered, so a hard edge here
   would be the one seam on the page that announces itself. */
.plaque-band {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Overlapped at BOTH ends so its artwork crosses into its neighbours' rather
     than either of them reaching black.

     The top is the usual arrangement: pulled up by --seam so this band's fade
     and the formula band's cross.

     The bottom needed the opposite trick. The cartridge plate has a hard top
     edge - it is inside the protected artboard and nothing there fades - so
     there is nothing to dissolve INTO. Instead this band is lifted above it and
     hangs over its top, and its own bottom fade does the dissolve: plaque
     artwork thinning out over cartridge artwork. z-index 1 is what makes that
     work, because .flav-art is positioned at z-index auto, so without it the
     later element in the document wins and the plate covers this band's fade
     instead of receiving it.

     The overlap is sized to swallow the fade whole - 34-48px against a fade of
     26-31px - so the dissolve happens entirely over the plate and never over
     the page. */
  --plaque-seam: clamp(34px, 2.6vw, 48px);
  z-index: 1;
  margin-top: calc(-1 * var(--seam));
  margin-bottom: calc(-1 * var(--plaque-seam));
  /* Was 5vw, which put 96px above and below a 344px plaque and made the band
     half again taller than the thing it exists to hold. */
  padding-block: clamp(var(--s5), 2.4vw, var(--s7));
}
.plaque-band__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%; height: 100%;
  /* cover, because the band's height is set by the plaque inside it and has no
     relationship to the plate's 2.112 ratio. Centred: the composition puts its
     molecules and roundels at both ends, so cropping from one side loses a
     motif the other side keeps. */
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  /* Short fades, 4% and 5%. At 14% the band arrived and left over 60px at each
     end, which with the section's padding gone is most of the air it has.

     The top one is the shorter of the two because it has to cross the formula
     band's fade rather than reach black: at 6% the two were both partial for
     long enough to leave 15% of the page showing through at the worst row of
     the overlap. At 4% the band is opaque before the formula band has thinned
     much, and the worst row is 93% covered.

     The bottom one is longer, 7%, because it is not crossing anything - it is
     dissolving this band's artwork over the cartridge plate's, which is opaque
     underneath the whole way. Longer is simply softer there. */
  -webkit-mask-image: linear-gradient(to bottom,
    rgb(0 0 0 / 0) 0%, #000 4%, #000 93%, rgb(0 0 0 / 0) 100%);
  mask-image: linear-gradient(to bottom,
    rgb(0 0 0 / 0) 0%, #000 4%, #000 93%, rgb(0 0 0 / 0) 100%);
}
.plaque-band > .wrap { position: relative; z-index: 1; }

/* THE PLAQUE, below the formula band.

   The supplied design for this paragraph: a double gold hairline frame, a leaf
   crest breaking the top edge, a small-caps gold heading, an ornamental rule
   and the copy. It replaces a plain centred lede.

   The frame is two borders rather than one, because that is what the reference
   draws and a single line at this weight looks like a table cell. The outer
   carries the corner radius and the inner sits 9px inside it at a tighter one,
   so the two do not run parallel at the corners.

   The crest and the seal are opaque in the section's own ground colour. That
   is what breaks the frame line behind them: the border runs the full way
   round underneath and they paint over it. The alternative is drawing the top
   edge as two segments with a gap, which would have to be re-measured every
   time the crest changes size. */
.plaque {
  position: relative;
  max-width: 62rem;
  margin: 0 auto;
  padding: clamp(var(--s7), 4.2vw, var(--s9)) clamp(var(--s5), 4vw, var(--s8))
           clamp(var(--s6), 3.4vw, var(--s8));
  text-align: center;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 10px;
  /* Near-opaque now that there is artwork behind it. At .5 the band's waves
     read straight through the paragraph; the reference shows the plaque's
     interior as a flat dark field with the background stopping at its frame. */
  background: linear-gradient(to bottom,
              rgb(var(--surface-rgb) / .94), rgb(var(--ink-rgb) / .94));
}
.plaque::before {
  content: '';
  position: absolute;
  inset: 9px;
  border: 1px solid color-mix(in srgb, var(--gold) 26%, transparent);
  border-radius: 5px;
  pointer-events: none;
}

/* Centred on the top edge, half above and half below it. */
.plaque__crest {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -50%);
  width: clamp(46px, 5.6vw, 58px);
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid; place-items: center;
  color: var(--gold);
  border: 1px solid color-mix(in srgb, var(--gold) 62%, transparent);
  /* Opaque, and the page's own ground - this is the mask that breaks the
     frame line, so it cannot carry any alpha. */
  background: var(--ink);
}
.plaque__crest svg { width: 46%; height: auto; display: block; }

.plaque__title {
  margin: 0;
  font-family: var(--display);
  /* Small caps, which is what the reference sets: a full-height V and small
     capitals after it. Not uppercase - uppercase loses that. */
  font-variant-caps: small-caps;
  font-weight: 400;
  /* The vw term tops out at 1000px of viewport, which is about where the
     panel reaches its own 62rem cap. Sized in vw alone the type kept growing
     after the panel had stopped, so the two came apart on wide screens. */
  font-size: clamp(20px, 3.2vw, 32px);
  line-height: 1.25;
  letter-spacing: .045em;
  color: var(--gold);
  text-wrap: balance;
}

/* The ornamental rule: two hairlines fading outward from a centre diamond. */
.plaque__rule {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(8px, 1vw, 16px);
  margin: clamp(var(--s4), 1.6vw, var(--s5)) 0 clamp(var(--s5), 2vw, var(--s6));
}
.plaque__rule::before,
.plaque__rule::after {
  content: '';
  height: 1px;
  width: clamp(48px, 9vw, 140px);
  background: linear-gradient(to right,
              rgb(var(--gold-rgb) / 0), rgb(var(--gold-rgb) / .55));
}
.plaque__rule::after { transform: scaleX(-1); }
.plaque__rule i {
  width: 7px; height: 7px;
  transform: rotate(45deg);
  border: 1px solid color-mix(in srgb, var(--gold) 70%, transparent);
}

.plaque__body {
  margin: 0 auto;
  max-width: 58ch;
  font-size: clamp(15px, 1.9vw, 18px);
  line-height: 1.75;
  color: rgb(var(--bone-rgb) / .84);
  text-wrap: pretty;
}

/* Centred on the bottom edge, the same trick as the crest. */
.plaque__seal {
  position: absolute;
  bottom: 0; left: 50%;
  transform: translate(-50%, 50%);
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--gold) 62%, transparent);
  background: var(--ink);
  display: grid; place-items: center;
}
.plaque__seal::after {
  content: '';
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--gold);
}

@supports not (color: color-mix(in srgb, red, blue)) {
  .plaque { border-color: var(--gold-deep); }
  .plaque::before,
  .plaque__crest,
  .plaque__seal { border-color: var(--gold-deep); }
  .plaque__rule i { border-color: var(--gold-deep); }
}

@media (max-width: 700px) {
  /* The band stops hanging over the section above it.

     margin-top: -var(--seam) exists so this band's artwork crosses INTO the
     formula plate's, and the two fades meet instead of either reaching black.
     There is no formula plate on a phone - the band is a grid of tiles - so
     the overlap had nothing to dissolve into and simply covered them: the
     last row of tiles was cut off 22px from the bottom. .fband carries
     isolation: isolate, so the tiles' own z-index 3 is sealed inside it and
     cannot outrank this band's z-index 1 at the page level.

     The bottom overlap stays. The cartridge plate below is still artwork, and
     that is still the seam it was built to close. */
  .plaque-band { margin-top: 0; }

  /* And it stops painting ON the cartridge.

     The bottom overlap is right - the band should hang over the plate so its
     artwork thins out over the plate's rather than over black. z-index: 1 is
     what made that work on the desktop, where the plate has a hard top edge
     and nothing to dissolve with.

     The plate fades in on its own here, so the layering wants to be the other
     way round. With the z-index dropped, .flav-art - later in the document -
     paints above the band, and the plate's own 6% ramp reveals the band's
     gold through it: a real cross-dissolve rather than one panel over
     another. It also gets the cartridge back. The band was covering the top
     34px of the window and the mouthpiece starts at 31px, so the tip of the
     product was underneath it. */
  .plaque-band { z-index: auto; }

  /* The plaque itself, rebuilt for a narrow screen.

     It was a 458px-wide ceremonial frame scaled down: 48px of padding, a
     crest, an ornamental rule, five lines of centred body copy and a seal on
     the bottom edge. Centred copy is the part that hurts - five ragged lines
     with both margins moving give the eye nothing to return to - and the
     ornament was costing more height than the paragraph it framed.

     The frame, the crest and the rule stay, because they are what makes this
     a plaque rather than a paragraph in a box. The body goes left-aligned and
     the seal goes: it sat below the copy repeating what the crest already
     said, and it was the last 16px of a box that was too tall. */
  .plaque {
    padding: var(--s6) var(--s5) var(--s5);
    border-radius: 10px;
  }
  .plaque::before { display: none; }
  .plaque__seal { display: none; }

  .plaque__crest { width: 42px; }

  .plaque__title {
    font-size: clamp(18px, 5.2vw, 24px);
    letter-spacing: .03em;
  }
  .plaque__rule { margin: var(--s3) 0 var(--s4); }
  .plaque__rule::before,
  .plaque__rule::after { width: clamp(30px, 12vw, 60px); }

  .plaque__body {
    text-align: left;
    font-size: 15px;
    line-height: 1.66;
    max-width: none;
    color: rgb(var(--bone-rgb) / .8);
  }
}


@media (max-width: 1000px) {
  /* The plate is 4.2:1. Below about a thousand pixels its band is under 240px
     tall, which puts the ovals near 100px and their labels into single figures
     - the artwork survives scaling and the type does not.

     So the plate goes and the section becomes an ordinary column. It was a
     single file of seven full-width pills, one under the other, each 150px
     tall to hold an icon and two words: 1372px of mostly black for six short
     labels, and nothing to read on the way down.

     A phone gets a grid instead. Two columns of compact tiles, three rows,
     about 300px for the same six facts - they can be taken in at a glance
     rather than scrolled past. The vertical rule goes with the single file it
     was drawn for. */
  .band--formula { padding-block: var(--s6); }
  .fband {
    aspect-ratio: auto;
    overflow: visible;
    container-type: normal;
  }
  .fband__plate, .fband__veil { display: none; }

  .fband__head {
    position: static;
    margin: 0 auto var(--s5);
    max-width: 62ch;
    padding-inline: var(--s5);
  }
  .fband__head h2 { font-size: clamp(26px, 5.4vw, 44px); }

  .fnodes {
    position: relative; inset: auto;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s3);
    padding-inline: var(--s5);
    justify-items: stretch;
  }
  /* Drawn to stand in for the printed filaments down a single file. There is
     no single file any more. */
  .fnodes::before { display: none; }

  .fnode {
    position: relative;
    left: auto; top: auto;
    width: auto; max-width: none; height: auto;
    aspect-ratio: auto;
    transform: none;
  }
  /* A tile, not an oval. The oval was the plate's shape, and the plate is not
     here - kept round, each one has to be wide enough for its longest label
     and tall enough for the circle, which is where the 150px came from. */
  .fnode__disc {
    justify-content: flex-start;
    gap: var(--s2);
    min-height: 96px;
    padding: var(--s4) var(--s3);
    border-radius: var(--radius-sm);
    background: rgb(var(--surface-rgb) / .55);
    border-color: color-mix(in srgb, var(--gold) 28%, transparent);
  }
  .fnode__label { font-size: 12.5px; line-height: 1.35; }
  .fnode__ico svg { width: 21px; }
  .fnode:hover .fnode__disc { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .fnode__disc, .fnode__label { transition: none; }
  .fnode:hover .fnode__disc { transform: none; }
}

/* -------------------------------------------- the signature: flavours -- */
/* Headline left, lede and medallion right, split by a hairline. Grid rather
   than flex + space-between: the headline must take the room it needs and the
   aside must take what is left, which is what minmax(0,1fr) auto says. With
   space-between the two blocks drifted apart as the headline got shorter. */
.flav__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--s5) var(--s7);
  margin-bottom: var(--s7);
}
/* 46px, down from 52. The head is two columns now, so the headline no longer
   has the band to itself, and at 52 "Светът около него се променя." took a
   third line - which pushed the aside's baseline out of line with it. */
.flav__head h2 { font-size: clamp(30px, 4.2vw, 46px); }

.flav__head-aside {
  display: flex; align-items: center; gap: var(--s6);
  padding-left: var(--s6);
  border-left: 1px solid var(--line);
}
/* Capped. The <br> in the markup sets where the lede WANTS to break, but a
   grid `auto` track sizes to max-content, so the aside claimed the width of
   its longest line - 434px - and left the headline too little to hold two
   lines. The cap decides the column width; the <br> still decides the break. */
.flav__lede { margin: 0; max-width: 34ch; color: var(--muted); font-size: 15px; line-height: 1.65; }

/* A ring, not a filled disc: the identity's marks are drawn in hairline gold
   on black, and a filled badge here would outweigh the wordmark in the header
   directly above it. */
.flav__medallion {
  flex: none;
  width: 62px; height: 62px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--gold);
  border: 1px solid color-mix(in srgb, var(--gold) 52%, transparent);
  background: radial-gradient(circle at 50% 40%,
              color-mix(in srgb, var(--gold) 10%, transparent), transparent 70%);
}
.flav__medallion svg { width: 27px; height: 27px; display: block; }

@supports not (color: color-mix(in srgb, red, blue)) {
  .flav__medallion { border-color: var(--gold-deep); background: none; }
}

/* Under ~900px the aside sits below the headline, and a left border on a
   full-width block would read as a stray vertical line rather than a split. */
@media (max-width: 900px) {
  .flav__head { grid-template-columns: 1fr; }
  .flav__head-aside { padding-left: 0; border-left: 0; }
}

/* ==========================================================================
   Flavour stage: spotlight border
   The panel's own background carries a tight, bright gradient at the pointer,
   and ::before lays an opaque face over the whole padding box. Only the 1px
   border ring is left uncovered, so the light reads as a bead travelling
   around the edge. The content never sits over the bright part, which is why
   this can run across the whole widget without touching the contrast of the
   name, the price or the button.
   ========================================================================== */
.flav__stage {
  /* Image column is fixed to the photography's real width rather than a
     fraction, so the picture is never upscaled past its native size and the
     copy takes whatever is left. Previously both columns were fractions and
     the tall portrait shot stretched the row to its own height, leaving the
     short text column with several hundred pixels of nothing under it -
     which is what made this card look big and empty at the same time. */
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  grid-template-areas:
    "media panel"
    "picker picker";
  /* No gap and no padding: the photograph runs to the card's own edges and
     the copy column carries its own padding instead. Inset inside a padded
     card, the picture floated in the middle of the panel with a band of empty
     surface above and below it - which is what made this card read as big and
     empty at once. Flush, the image sets the card's height and there is no
     leftover space to look empty. `stretch` is what lets it do that; with
     `start` the image kept its intrinsic height and the gap came back. */
  gap: 0;
  align-items: stretch;
  /* Both product cards share this, so the flavour card and the oil card read
     as one component used twice rather than two different ones. */
  min-height: 560px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  position: relative; overflow: hidden;
  --mx: 50%;
  --my: -40%;                 /* parked off the top edge until first hover */
  /* Just the surface now. The rim moved to ::after, which can light every
     edge rather than only the one the pointer happens to be near. */
  background: var(--surface);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

/* The opaque face. inset:0 on an absolutely positioned child resolves against
   the padding box, so it stops exactly at the inside of the border and leaves
   that one pixel showing the gradient behind it. */
.flav__stage::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  /* Opaque again.
     This was briefly translucent with a backdrop blur, so the scroll-driven
     background film could read through the card. There is no background film
     any more - the hero owns it - so the translucency bought nothing and cost
     two things: a backdrop-filter on a full-width element, and a bug. Being
     absolutely positioned with z-index 0, it paints above every non-positioned
     in-flow child, which is why the flavour switcher underneath the photo was
     showing through a 55% sheet and looked like empty space while the
     positioned image and copy looked fine. */
  background: var(--surface);
  border-radius: calc(var(--radius) - 1px);
}

/* Belt and braces for the same class of bug: anything that sits in normal flow
   inside the stage is explicitly lifted above the ::before ground. */
.flav__stage > * { position: relative; z-index: 1; }

/* The lit border.

   This used to work by turning the real border transparent and letting the
   stage's own background gradient show through the 1px gap. That only ever lit
   the edge nearest the cursor: the gradient is a circle centred on the pointer,
   and on a 1172x560 card the far edge is over a thousand pixels away, well
   outside any sane radius - so three sides of the card stayed dark.

   Now the ring is its own layer: a background painted across the whole border
   box and masked down to just the border, so every edge is lit. It carries two
   things - a flat base tint so all four sides always show something, and a hot
   spot that follows the pointer. Near the cursor is brightest, the rest still
   glows. */
.flav__stage::after {
  content: ''; position: absolute; inset: 0; z-index: 3;
  pointer-events: none; border-radius: inherit;
  border: 1px solid transparent;
  background:
    radial-gradient(620px circle at var(--mx) var(--my),
      var(--glow, var(--accent)) 0%,
      color-mix(in srgb, var(--glow, var(--accent)) 60%, transparent) 30%,
      transparent 74%),
    color-mix(in srgb, var(--glow, var(--accent)) 34%, transparent);
  background-origin: border-box;
  background-clip: border-box;
  /* Show the border area only: paint the whole box, then punch out the padding
     box. Without this the gradient would flood the card. */
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: 0;
  transition: opacity .25s var(--ease);
}
.flav__stage.is-lit::after { opacity: 1; }
/* The real border stays put underneath; the ring above simply covers it. */

/* A wide, very faint wash over the face. Measured against the body text so it
   stays well clear of the readable minimum: it is atmosphere, not lighting. */
.flav__glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(760px circle at var(--mx) var(--my),
    color-mix(in srgb, var(--glow, var(--accent)) 16%, transparent) 0%,
    transparent 62%);
  opacity: 0;
  transition: opacity .3s var(--ease);
}
.flav__stage.is-lit .flav__glow { opacity: 1; }

.flav__viz { position: relative; z-index: 2; aspect-ratio: 16 / 9; border-radius: var(--radius-sm); overflow: hidden; background: var(--ink); }

/* The flavour stage's own image is now a tall portrait shot (~341x645,
   flavor-*-full.webp), not the 16:9 landscape aspect-ratio above - that base
   value still stands because the oil section reuses it unmodified below,
   3:2 photography and all. #stage scopes the portrait numbers to the one
   instance that has them, the same way #stage-oil already scopes --accent
   for the oil block: an id beats the shared class without touching it. */
/* ==========================================================================
   THE FLAVOUR ARTBOARD

   The reference plates are section mockups with the interface printed into the
   photograph - eyebrow, headline, badge, selector, product widget, all flat
   pixels. None of it can be cropped away: the printed selector runs directly
   beneath the mango, and in the mango plate the mango is the hero fruit, some
   ten pixels below the selector's edge.

   So the plate ships whole and the printed interface is COVERED by the real
   one. That only works if the overlays stay locked to the artwork at every
   width, which is what this coordinate system is for:

     - the artboard holds the plate's own aspect ratio, so its box is always
       geometrically similar to the image;
     - the plate fills it exactly, so image pixels and box percentages stay in
       a fixed relationship;
     - every overlay is positioned in percentages of the artboard.

   Scale the artboard and the overlays scale with the printed artwork instead
   of sliding across it.

   The --ui-* values below were measured against the 2048x749 plate by drawing
   candidate boxes onto it and checking them, not by estimating from the
   design. One set serves all five: the plates are compositionally identical,
   and pack-v3-assets.sh normalises them to a single canvas to keep them that
   way. Re-measure only if a genuine per-plate misalignment is ever found.
   ========================================================================== */
.flav-art {
  position: relative;
  width: 100%;
  /* Edge to edge. The artboard is a direct child of .band, which carries no
     inline padding of its own, so full bleed needs no 100vw and brings no
     scrollbar rounding error with it. No black margin left or right any more:
     the composition runs the width of the screen and the widget rides with
     it, instead of being pinned inside a 1220px column. */
  /* Every overlay inside is sized in cqw - percentages of THIS box - so the
     live type tracks the printed type it covers instead of tracking the
     viewport. Sized against the viewport it drifted badly: at 1920 the live
     headline came out 44px against the plate's 38, wrapped onto a third line
     the printed one does not have, and exposed the printed text underneath.
     The artboard is the coordinate system for type as much as for position. */
  container-type: inline-size;
  /* The registered plate's ratio. Everything here depends on it matching the
     asset exactly - if it drifts, the plate letterboxes inside the box and
     every overlay lands somewhere the artwork is not.

     2030x757 rather than the sources' ~2074x758: pack-v3-assets.py now slides
     each plate so the cartridge lands in the same place on all five, and the
     canvas is narrowed to give them room to slide. */
  aspect-ratio: 2030 / 757;
  border-radius: var(--radius);
  /* The clip moved DOWN, onto the two layers that actually need it.

     It was here, and it was cutting the widget off. The card is absolutely
     positioned inside this box and sized by its own content; once it carries
     an accessory row it is taller than the room between its top offset and
     the artboard's bottom edge, and `overflow: hidden` simply removed the
     difference - the battery row was rendered and then clipped away, which is
     what "hidden by the oil section" was.

     Only the plate and the veil ever needed clipping, and both are inset:0
     children that can round their own corners. Nothing else in here draws
     outside the box except the widget, which now may. */
  /* Kept, and restored deliberately. Letting the artboard show its overflow
     at every width was a mistake: the widget was ALREADY taller than its
     share of the plate on narrow laptops, before any accessory row existed -
     the clip was simply hiding it. Unclipping it did not create room, it made
     a pre-existing overflow visible, and it landed on the formula section
     above. So the clip stands everywhere except the one width band that has
     been measured to have room. See the min-width block further down. */
  overflow: hidden;
  isolation: isolate;
  z-index: 1;

  /* The UNION of where each printed element lands across all five registered
     plates, printed by pack-v3-assets.py and copied here - not one plate's
     measurement. Registration slides the printed furniture by up to 30px, so
     an overlay sized to a single plate would uncover the other four.

     A pill wider than it strictly needs to be is invisible. Re-run the build
     after any change to the plates and it reprints these.

     These moved twice. First when natural and blueberries were regenerated:
     their cartridges used to sit at 50.32% and 50.24% against the other three
     at 51.5-51.8%, so registration had to slide the printed furniture by up
     to 30px and every overlay was padded to swallow it. The new plates land
     at 51.2% and 51.6% and the slide is down to 5.8px.

     Then again when the build started MEASURING the widget instead of
     deriving it from a hand measurement. That hand figure was 8px short on
     the right edge, which had gone unnoticed while the overlays were padded
     for a 30px slide - the padding was covering the error. Tighten the
     padding and a strip of the printed card's gold border appeared down the
     side of the live one. The border is the brightest column in its
     neighbourhood, so it can be found the same way the cartridge is. */
  --ui-sel-x: 1.81%;
  --ui-sel-y: 70.86%;
  --ui-sel-w: 40.68%;
  --ui-sel-h: 14.16%;

  /* Left edge pulled 0.08% wider than measured and the width 0.24% wider, so
     the live card clears the printed border rather than landing exactly on
     it. Sub-pixel rounding at odd widths is the whole reason. */
  --ui-wid-x: 70.90%;
  --ui-wid-y: 19.66%;
  --ui-wid-w: 25.55%;
  --ui-wid-h: 71.25%;

  --ui-copy-x: 2.11%;
  --ui-copy-y: 16.71%;
  --ui-copy-w: 27.71%;
}
/* TALLER BELOW 1500. The widget is sized by its type, and its type has
   floors (below); as the plate narrows the card stops shrinking with it and
   outgrows its 71% of the height, which used to push its top out of the
   artboard into the formula band. So the box gets a taller ratio in two
   steps and the plate is cover-cropped to it: the v5 plates are clean and
   carry black margins at both ends, so what the crop takes is empty. Every
   overlay is in percentages of the box and cover keeps the vertical
   proportions, so the selector stays under the fruit and the widget's floor
   stays where it was - there is simply more box above it. */
@media (max-width: 1500px) { .flav-art { aspect-ratio: 2030 / 880; } }
@media (max-width: 1280px) and (min-width: 1025px) {
  .flav-art { aspect-ratio: 2030 / 990; }
  /* five names in the selector's 40% of a 1100px plate: the fruit icons go
     and the ends close up, so the names stop being cut to "Натура…" */
  .flav-art .chip__ico { display: none; }
  .flav-art .chip { padding-inline: clamp(8px, .9cqw, 14px); font-size: 11.5px; }
  .flav-art { --ui-sel-w: 45%; }
  /* the card's button: the label alone, its arrow would run into it */
  .flav__stage--card .btn--apple .btn__arrow { display: none; }
}

/* z1 - the photography. */
/* Carries the clip the artboard used to. inherit, so the plate keeps the same
   rounded corners it had when the parent was doing this. */
.flav-art__scene {
  position: absolute; inset: 0; z-index: 1;
  overflow: hidden; border-radius: inherit;
}
.flav-art__scene img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* cover, centred. The box carries the plate's own ratio above 1500 and
     nothing is cropped; below it the box is taller (see .flav-art) and cover
     takes the surplus off the plate's black ends, equally, which moves the
     cartridge by a fifth of a percent - nothing the overlays notice. */
  object-fit: cover;
  object-position: center;
  opacity: 0;
  /* Longer than --dur, and with a breath of scale under it.

     The plates are registered on the cartridge now, so the product no longer
     slides - but the FRUIT is genuinely arranged differently in each, and has
     to be: a different one is the hero every time. That change should read as
     a dissolve rather than a cut, and a 300ms swap does not give the eye time
     to let go of what it was tracking. 520ms with a 1.5% settle does. */
  transform: scale(1.015);
  transition: opacity .52s var(--ease), transform .52s var(--ease);
}
/* The same crossfade initFlavours has always driven, on the same class it has
   always toggled. Nothing about the switch changed - only what it switches. */
.flav-art__scene img.is-on { opacity: 1; transform: scale(1); }

/* z2 - the veil over the printed copy.

   An ellipse, not a box. The printed type has to disappear, but the gold
   filaments through that corner should survive, and the mango starts about 2%
   of the canvas to the right of where the text ends - a hard edge in that gap
   reads as damage, which is the mistake the earlier baked-in wash made. This
   is opaque where the type is and gone before it reaches fruit. */
.flav-art__veil {
  position: absolute; z-index: 2; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  /* Narrow in x, tall in y, and that shape is the whole point.

     The printed copy ends at x 30.3% of the plate and the mango begins at
     32.5% - two and a half percent apart. A veil generous enough to be safe
     horizontally is a veil that dims the hero fruit, which is precisely the
     failure the earlier baked-in wash had. So this one is held to rx 22%,
     opaque out to 80% of it (x 32.6%) and gone by 100%, while ry 62% lets it
     cover the full height of the copy without widening.

     It does not have to be perfect: the live copy is pinned on top of the
     printed copy, so this is only covering the gaps between lines. */
  /* Two shapes, because how far right the veil may reach depends on how far
     down it is.

     Beside the eyebrow and the headline - y 18..40% - the plate is dark
     background, so the veil can run out to 35% and cover the printed type
     completely. Beside the lede and the badge it cannot: the mango starts at
     x 32.5%, y 52.7%, and reaching for the printed text there is what dimmed
     the hero fruit in every earlier attempt. So the lower shape is held to
     19%, and the live lede - pinned on top of the printed one at the same
     size - covers what the veil deliberately does not. */
  /* Plateaus at FULL opacity, not .985.

     A light grey on near-black survives one and a half percent of
     transparency far better than intuition suggests - the printed headline
     was still legible behind the live one at magnification. It costs nothing
     to close: what the plateau covers is the plate's own near-black ground. */
  background:
    radial-gradient(ellipse 26% 26% at 15% 30%,
      rgb(var(--ink-rgb)) 0%,
      rgb(var(--ink-rgb)) 78%,
      rgb(var(--ink-rgb) / .3) 94%,
      rgb(var(--ink-rgb) / 0) 100%),
    radial-gradient(ellipse 19% 34% at 13% 56%,
      rgb(var(--ink-rgb)) 0%,
      rgb(var(--ink-rgb)) 78%,
      rgb(var(--ink-rgb) / .3) 94%,
      rgb(var(--ink-rgb) / 0) 100%);
}
/* The upgrade path. Clean plates arrive, this one line switches the veil off,
   and no selector, widget, layout or flavour code is touched. */
.flav-art.has-clean-plate .flav-art__veil { display: none; }

/* z3 - live copy, over the printed copy it replaces. */
/* Each line is pinned to the plate rather than flowed.

   Flowed, the block only had to be a few pixels out for the printed type to
   appear from behind the live type - and it was, because margins in ems do not
   reproduce a fixed 749px layout. Pinned, live text lands ON printed text, and
   that overlap is what actually hides it. The veil only has to deal with the
   gaps between lines, which is a job it can do without reaching the fruit.

   Percentages are the printed baselines: eyebrow y135, headline y190, lede
   y328, badge y460, all of 749. */
.flav-art__copy {
  position: absolute; z-index: 3;
  left: var(--ui-copy-x);
  top: 0;
  width: var(--ui-copy-w);
  height: 100%;
}
.flav-art__copy > * { position: absolute; margin: 0; left: 0; }
.flav-art__copy .eyebrow {
  top: 16.71%;
  font-size: clamp(11px, .83cqw, 17px);
}
.flav-art__copy h2 {
  top: 25.40%;
  /* 2.85, not the 2.25 the printed line HEIGHT implied. Matching the height
     left the live line 24% narrower than the printed one, so the tail of the
     printed headline came out from behind it - the plate's serif is tracked
     wider than ours at the same size. This was set by measuring the rendered
     width against the printed width, not by trusting the cap height. */
  font-size: clamp(24px, 2.85cqw, 58px);
  line-height: 1.16;
  /* The <br> decides the breaks. Without this the line can wrap on its own at
     some widths, which puts the second line somewhere the printed one is not
     and re-opens the gap this is all meant to close. */
  width: max-content;
  max-width: none;
}
.flav-art__lede {
  top: 43.84%;
  color: rgb(var(--bone-rgb) / .72);
  font-size: clamp(13.5px, 1.07cqw, 22px);
  line-height: 1.55;
}
.flav-art__copy .tag--check { top: 61.49%; }
/* The oil artboard's badge is the last row of its list (index.php), so it
   follows the list's last line instead of being pinned under where that line
   was at one width. No bullet, no indent, a little air above it. */
.oil-art__list .oil-art__tag { padding-left: 0; margin-top: .35em; }
.oil-art__list .oil-art__tag::before, .oil-art__list .oil-art__tag::after { display: none; }   /* the ring and the tick */
.oil-art__list .oil-art__tag .tag--check { position: static; }

/* The badge. A claim, so it is set as a quiet outline rather than a filled
   chip - it belongs to the tertiary tier, well under the product name. */
.tag--check {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .5em 1em;
  border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
  border-radius: 999px;
  color: var(--accent);
  font: 500 clamp(11px, .83cqw, 17px)/1 var(--body);
  letter-spacing: .04em;
  white-space: nowrap;
}
.tag--check svg { width: 1em; height: 1em; flex: none; }

@supports not (color: color-mix(in srgb, red, blue)) {
  .tag--check { border-color: var(--gold-deep); }
}

/* ---------------------------------------------------------- the widget --
   z5. It carries .flav__stage, so the rim bead, the ambient wash and the lit
   shadow all attach to it with no JavaScript change whatsoever - the selector
   $$('.flav__stage') already finds it. This modifier only undoes the parts of
   .flav__stage that described a two-column card. */
.flav__stage--card {
  border-radius: var(--radius-lg);
  position: absolute; z-index: 5;
  left: var(--ui-wid-x);
  top: var(--ui-wid-y);
  width: var(--ui-wid-w);
  /* Min, not fixed. It has to cover the printed widget completely, but its own
     content decides the rest - a hard height would either clip the button or
     leave a gap under it as the type rescales. */
  min-height: var(--ui-wid-h);
  display: block;
  /* Near-opaque, not glass. Real translucency here would show the printed
     widget straight through the live one, which is the whole problem being
     solved. It still reads as glass: the border, the inner highlight and the
     depth shadow do that work without exposing a single pixel underneath. */
  background: linear-gradient(145deg,
              rgb(var(--surface-rgb) / .985), rgb(var(--ink-rgb) / .985));
  box-shadow: 0 24px 60px -28px rgb(0 0 0 / .8),
              inset 0 1px 0 rgb(255 255 255 / .045);
}
/* Anchored by its BOTTOM, and that is what lets it hold an accessory row.

   Registered from the top it grew downward, and there is only about 19px of
   slack between the printed widget and the foot of the plate - so the battery
   row pushed the card off the bottom edge onto the oil plate below. That could
   not be fixed by giving the band room: #vkusove and #maslo carry zero padding
   between them ON PURPOSE, so the two plates butt together and their feathered
   edges dissolve into one seam.

   Pinned by the bottom instead, extra height goes UP - into the top of the
   plate, which was sampled and is empty: average brightness 9.7 of 255 across
   that band, 16 of 741 pixels above 60. The card is near-opaque, so covering
   more of an empty dark area is invisible, and it still covers the printed
   widget completely, because its floor sits on the printed widget's floor and
   min-height keeps it at least as tall.

   Scoped to the flavour artboard on purpose: .oil-art__widget shares
   .flav__stage--card and sets its own top, and giving that one a bottom too
   would pin both edges and stretch it. */
/* Every width where the card is an overlay - which is 1025 up.

   THE BUTTON WAS BEING CUT OFF. Registered from its top and clipped to the
   plate, the card is taller than its share of the plate at these widths, and
   what fell past the edge was the bottom of it: at 1250 the add-to-cart
   button was 39px of a 38px button outside the clip, i.e. gone entirely.
   Nobody on a 1024-1280 laptop could add anything to the basket. That is what
   was reported as the press and hover animation no longer working - there was
   no button under the pointer to animate.

   Anchoring by the floor and letting it show puts the growth at the top
   instead, where it costs about 29px of empty plate rather than the whole
   control. The accessory row still moves to the cart below 1280, which is
   what keeps that 29px from becoming 80. */
@media (min-width: 1025px) {
  .flav-art { overflow: visible; }
  .flav-art > .flav__stage--card {
    top: auto;
    bottom: calc(100% - var(--ui-wid-y) - var(--ui-wid-h));
  }
}

.flav__stage--card > .flav__panel {
  padding: clamp(10px, 1.5cqw, 31px);
  border-left: 0;
}
.flav__stage--card .flav__name { font-size: clamp(20px, 1.66cqw, 34px); margin-bottom: .3em; }
.flav__stage--card .flav__desc {
  font-size: clamp(13px, .88cqw, 18px);
  max-width: none; margin-bottom: 1em;
  /* TWO LINES, ALWAYS - and this is the line that stops the widget changing
     size as you switch flavour.

     The tasting notes are not the same length: natural, mango and cherry run
     to two lines, berries and grape to one. With no floor here the card was
     19px shorter on those two - and because the card is anchored by its FLOOR
     and grows upward, that 19px came off the top, so the whole widget jumped
     every time you moved between them.

     The shop's base .flav__desc has carried min-height: 3.2em for exactly
     this reason since long before the rating row existed; this rule used to
     override it to 0. 3.2em is two lines at this line-height, so the reserve
     tracks the type rather than being a pixel guess. */
  min-height: 3.2em;
}
.flav__stage--card .flav__rule { margin-bottom: .8em; }
.flav__stage--card .eyebrow { font-size: clamp(10.5px, .68cqw, 14px); }
.flav__stage--card .spec { margin: 0 0 1em; padding-bottom: .9em; gap: .5em 0; }
.flav__stage--card .spec__item { padding: 0 clamp(5px, .8cqw, 16px); gap: 4px; }
.flav__stage--card .spec dt { font-size: clamp(9.5px, .58cqw, 12px); letter-spacing: .08em; }
.flav__stage--card .spec dd { font-size: clamp(13px, .88cqw, 18px); }
.flav__stage--card .spec__ico svg { width: clamp(12px, .88cqw, 18px); height: auto; }
.flav__stage--card .flav__buy { gap: clamp(8px, 1.1cqw, 22px); }
.flav__stage--card .flav__price-eur { font-size: clamp(21px, 1.56cqw, 32px); }
.flav__stage--card .flav__price-alt { font-size: clamp(12px, .83cqw, 17px); }
.flav__stage--card .btn--apple {
  min-height: clamp(44px, 3.1cqw, 58px);
  font-size: clamp(13.5px, .88cqw, 18px);
  /* narrower ends than the base button: at 1100 the card is 280px wide and
     "Добави в количката" broke onto two lines behind 40px of padding a side */
  padding-inline: clamp(12px, 1.2cqw, 28px);
}
.flav__stage--card .btn--apple .btn__label { white-space: nowrap; }

.flav__panel { grid-area: panel; }

/* The copy column carries the padding the card gave up, and a hairline marks
   where the photograph ends - without it the dark edge of the image and the
   card surface merge into one another and the split reads as accidental. */
.flav__panel {
  padding: var(--s7);
  display: flex; flex-direction: column;
  border-left: 1px solid var(--line);
}

/* Square off the inner edges. The card clips its own corners with
   overflow:hidden, so the art needs no radius of its own - and with one it
   showed a sliver of surface in each outer corner. */
.flav__stage .flav__viz {
  border-radius: 0;
  aspect-ratio: auto;
  height: 100%;
}

/* ------------------------------------------------- the panel's offer -- */
/* Top right of the copy column, which is where the eye lands after the name
   and before the price - close enough to the price to be read as part of it,
   far enough not to crowd it.

   Absolute, so it costs the column no height and the spec list below is
   exactly where it was. The panel is already position: relative.

   Not a control. It reports; the button underneath it is what acts. So no
   pointer cursor, no press state, and nothing about it invites a click that
   would do something the shopper did not ask for. */
.promo {
  /* Static by default. The corner is granted by .is-promo-wide, which
     fitPromo() sets only when the corner measures out - so the fallback is
     the layout that always fits rather than the one that sometimes does. */
  position: static;
  align-self: stretch;
  width: 100%;
  margin-bottom: var(--s4);
  z-index: 3;
  display: inline-flex; align-items: center; gap: var(--s3);
  padding: 7px 14px 7px 7px;
  border-radius: 999px;
  /* Transparent, not absent: the border still occupies its pixel, so the
     badge does not resize when the widget is hovered and it appears. */
  border: 1px solid transparent;
  background: color-mix(in srgb, var(--accent) 9%, var(--surface-2));
  color: var(--bone);
  text-align: left;
  transition: border-color .28s var(--ease), background .28s var(--ease);
}
/* No max-width here. It belongs to the corner, where the badge must not span
   the panel it is floating over - carried into the banner default it capped
   the badge at 229px inside a 277px column, so the offer stopped short of
   where the name, the price and the button all end and the top of the card
   read as narrower than the rest of it. As a banner it takes the row. */

/* The corner. Granted only when fitPromo() has measured room for it, so the
   badge never lands on the flavour name whatever the name or the figure. The
   three lines stack again here - there is no right-hand space to move the
   condition into at 170px wide. */
.flav__panel.is-promo-wide .promo {
  position: absolute;
  top: var(--s5); right: var(--s5);
  width: auto;
  max-width: calc(100% - var(--s7));
  margin-bottom: 0;
  border-color: transparent;
}
.flav__panel.is-promo-wide .promo__txt {
  grid-template-columns: auto;
  grid-template-areas: "kicker" "amount" "action";
  align-items: start;
  column-gap: 0;
}
.flav__panel.is-promo-wide .promo__action { justify-self: start; text-align: left; }

/* The heading keeps out of the badge's way by exactly the width the badge
   turned out to be, which fitPromo() measures and writes here. Only the two
   lines that sit on the badge's rows give way; the description, the specs and
   the price below it keep the full column. 0px when there is no offer. */
.flav__panel .eyebrow,
.flav__panel .flav__name { padding-right: var(--promo-w, 0px); }

/* The rim belongs to the widget, not to the badge. At rest the offer is a
   quiet tint in the corner; bring the pointer anywhere into the card and it
   draws its edge, which is the card telling you the corner is worth reading.
   Away from the card it stops competing with the product. */
.flav__stage:hover .promo {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, var(--surface-2));
}

/* ------------------------------------------------------ the ring and glyph */
.promo__mark {
  position: relative; flex: none;
  display: grid; place-items: center;
  width: 44px; height: 44px;
}
.promo__ring { position: absolute; inset: 0; width: 44px; height: 44px; }
.promo__track { stroke: color-mix(in srgb, var(--bone) 12%, transparent); }
.promo__fill {
  stroke: var(--accent);
  /* The sweep. The offset is set from script and this is what carries it
     round, so adding one to the basket rolls the arc forward instead of
     snapping it. */
  transition: stroke-dashoffset .72s var(--ease), stroke .28s var(--ease);
}
.promo__ico {
  position: relative;
  display: grid; place-items: center;
  width: 19px; height: 19px;
  color: var(--accent);
  transition: color .28s var(--ease), transform .28s var(--ease);
}
.promo__ico svg { width: 100%; height: 100%; display: block; }

/* The three lines, sized the way an advert is: a small word to say what this
   is, the money at a size you cannot miss, and the condition under it. The
   saving used to be the smallest thing here, in grey, under a line counting
   boxes - which is the opposite of the order anyone reads an offer in. */
/* Across the row rather than down it.

   As a banner the badge is as wide as the panel, and three lines in one
   narrow column on the left left the condition sitting on a row of its own
   with the whole right half empty beside it. It belongs to the amount -
   "14,98 €" and "с 3 броя" are one sentence - so it sits on the amount's line
   and takes the far edge.

   The condition spans both rows and centres against them, which is where the
   ring on the left already sits - so the badge reads as one row of three
   things on a common centre line.

   It was baseline-aligned to the amount, on the theory that they are one
   sentence. Sharing a baseline with 20px digits when you are 12.5px does not
   read as centred, though: it drops the small text onto the big text's feet,
   which is why it looked like it was sitting at the bottom of the badge. */
.promo__txt {
  display: grid; gap: 1px; min-width: 0; line-height: 1.08;
  flex: 1 1 auto;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "kicker action"
    "amount action";
  align-items: baseline;
  column-gap: var(--s4);
}
.promo__kicker { grid-area: kicker; }
.promo__amount { grid-area: amount; }
.promo__action {
  grid-area: action;
  justify-self: end;
  align-self: center;
  text-align: right;
}

/* THE OFFER'S NAME, AND WHY IT WAS HARD TO READ.
   This is where a rule's own name lands - "Седмична промоция" and the like -
   and it was set at 9.5px, 700 weight, .16em of tracking, uppercased, in
   --accent. Every one of those five decisions costs legibility, and they were
   being paid all at once on the longest string in the panel.
   Uppercase Cyrillic is the part that does not survive it: Bulgarian has no
   ascenders or descenders in capitals, so СЕДМИЧНА ПРОМОЦИЯ is a row of
   same-height rectangles with no word shape left to recognise. Sentence case
   at 11.5px keeps the label quiet without asking anyone to decode it.
   The colour stays --accent but no longer at full strength on the flavours
   whose accent is a mid-tone: on a dark panel a saturated green at 9.5px is
   under 4.5:1, and this is the line naming the discount. */
.promo__kicker {
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .02em; text-transform: none;
  color: color-mix(in srgb, var(--accent) 62%, var(--bone));
  white-space: nowrap;
}
.promo__amount {
  font-family: var(--mono);
  font-size: clamp(20px, 1.5vw, 25px);
  font-weight: 700;
  color: var(--bone);
  letter-spacing: -.01em;
  white-space: nowrap;
}
/* The line telling you what it costs to earn the offer. At .72 alpha on
   --surface-2 this sat at about 5.3:1 - legible in a screenshot, tiring in a
   dim room, which is where a phone gets read. .85 is the same grey, further
   from the background. */
.promo__action {
  font-size: 12.5px;
  color: rgb(var(--bone-rgb) / .85);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ----------------------------------------------------------------- won -- */
/* The award state. The ring has closed, so the arc itself is the frame and
   the glyph inside it changes from "one more" to a cup. */
.promo.is-won { background: color-mix(in srgb, var(--accent) 17%, var(--surface-2)); }
.flav__stage:hover .promo.is-won { border-color: var(--accent); }
.promo.is-won .promo__kicker { color: var(--gold); }
.promo.is-won .promo__amount { color: var(--gold); }
.promo.is-won .promo__action { color: rgb(var(--bone-rgb) / .82); }
.promo.is-won .promo__ico { color: var(--gold); transform: scale(1.06); }
.promo.is-won .promo__fill { stroke: var(--gold); }
.promo.is-won .promo__track { stroke: color-mix(in srgb, var(--gold) 18%, transparent); }

/* The moment it is won, and only that moment: the badge takes a breath, the
   cup lands with a half turn, and a ring of light leaves the mark. Once. A
   celebration that repeats on every repaint stops being one. */
.promo.is-just-won { animation: promo-pop .62s var(--ease) both; }
.promo.is-just-won .promo__ico { animation: promo-trophy .62s var(--ease) both; }
.promo.is-just-won .promo__mark::after {
  content: '';
  position: absolute; inset: -4px;
  border-radius: 999px;
  border: 2px solid var(--gold);
  animation: promo-halo .9s var(--ease) both;
  pointer-events: none;
}

/* One pass of light when the offer changes. An advert has to reclaim the eye
   after the page has been sitting still; a loop would only be noise, so this
   runs once per change and the class is taken off again. Behind the text,
   never over it.

   The clip is on .is-fresh, not on .promo. Permanent overflow: hidden would
   also cut the win halo, which expands past the pill on purpose - and the
   two classes are mutually exclusive, so the sweep gets its clip and the
   celebration keeps its room. */
.promo.is-fresh { overflow: hidden; }
.promo.is-fresh::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 38%,
              color-mix(in srgb, var(--bone) 13%, transparent) 50%, transparent 62%);
  animation: promo-sweep .85s var(--ease) both;
}

@keyframes promo-sweep {
  0%   { transform: translateX(-115%); }
  100% { transform: translateX(115%); }
}

@keyframes promo-pop {
  0%   { transform: scale(1); }
  38%  { transform: scale(1.07); }
  100% { transform: scale(1); }
}
@keyframes promo-trophy {
  0%   { transform: scale(.3) rotate(-24deg); opacity: 0; }
  55%  { transform: scale(1.22) rotate(6deg); opacity: 1; }
  100% { transform: scale(1.06) rotate(0); opacity: 1; }
}
@keyframes promo-halo {
  0%   { transform: scale(.7); opacity: .85; }
  100% { transform: scale(1.9); opacity: 0; }
}

/* Narrower than this, the badge stops floating over the copy and becomes a
   full row above it.

   1279 rather than the artboard's own 1024. Measured: at 1280 the corner
   leaves 18px between the badge and the longest flavour name, and at 1050 it
   leaves MINUS 19 - the name runs under the badge. Reserving the width as
   padding does not save it either, because "Натурален" is one unbreakable
   word: told to fit in less, it overflows rather than wraps. Below the width
   where the corner genuinely has room, the badge takes a row of its own.

   No pointer here either, as a rule, so the rim is shown outright - a border
   that only ever appears on hover is a border a touch screen never sees. */
/* As a banner it owns a row, so the rim is shown outright - and on a touch
   screen a border that only ever appears on hover is one nobody ever sees. */
.promo { border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.promo.is-won { border-color: color-mix(in srgb, var(--gold) 45%, transparent); }

/* ---------------------------------------------------------- accessory ----
   The 510 battery, under the buy button inside the cartridge card.

   THE PICTURE IS A CUT-OUT, not a tile. It is a transparent WebP trimmed to
   the object, so it sits on the card's own gradient with nothing behind it -
   no black square that only matches one background, and no seam where that
   square meets the panel. It is also the reason the row can be short: the
   battery is tall and thin, so it STANDS in the row and leans out of the top
   of it on a negative margin, the way a product is shown breaking out of its
   frame rather than being boxed inside one. The row pays for the part below
   the line only.

   Everything else is deliberately quiet - a hairline above, a plus rather
   than a cart glyph, no accent fill on the row itself - because this is an
   addition to the decision above it, not a rival to it.

   No offer and no struck-through price, by construction: an accessory is
   excluded from the discount engine, so there is never anything to show. */
.addon {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid color-mix(in srgb, var(--bone) 10%, transparent);
}
.addon__pic {
  width: auto;
  /* Sized to the row and kept INSIDE it.

     It leaned upward on a negative margin at first, to buy back the height a
     tall thin object costs. That was wrong about what is above it: the space
     over the divider is the add-to-cart button, not empty card, so the
     battery stood across it. The card is anchored by its bottom now and has
     room to spare, so the row simply takes the height it needs. */
  height: clamp(40px, 3.9cqw, 62px);
  object-fit: contain;
  /* The plate it came from had a soft studio shadow, which was cut away with
     the background. This puts a much subtler one back, so it stands on the
     card instead of floating over it. */
  filter: drop-shadow(0 6px 10px rgb(0 0 0 / .55));
}
.addon__txt { display: grid; gap: 1px; min-width: 0; padding-bottom: 3px; }
.addon__name {
  font-size: clamp(11px, .92cqw, 15px); font-weight: 500; color: var(--bone);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.addon__price {
  font-family: var(--mono); font-size: clamp(10px, .82cqw, 13px);
  color: rgb(var(--bone-rgb) / .6);
  /* A price is one token. Its column is a 1fr that is allowed to shrink below
     content, so without this "24,99 €" broke onto a second line inside a 37px
     box on the narrowest phones. */
  white-space: nowrap;
}
.addon__btn {
  display: inline-flex; align-items: center; gap: 6px;
  flex: none;
  min-height: clamp(30px, 2.5cqw, 40px);
  padding: 0 clamp(10px, .95cqw, 16px);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--bone);
  font: inherit; font-size: clamp(10px, .82cqw, 14px); font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.addon__btn:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent);
}
.addon__btn svg { flex: none; width: 13px; height: 13px; color: var(--accent); }
.addon__btn[disabled] {
  cursor: default; border-color: var(--line); background: none; color: var(--muted);
}
.addon__btn[disabled] svg { display: none; }
/* the switch and its word, as one cell at the row's end (cart-drawer.js
   keeps the switch true while the battery is in the basket) */
.addon__sw { display: inline-flex; align-items: center; gap: 10px; flex: none; }
/* the battery is never bought alone: while the basket holds no cartridge
   and no oil, the row is put away wherever it is drawn */
.addon.is-off { display: none; }

/* WHICH OF THE TWO HOMES IS SHOWN.

   The row exists twice in the markup - once under the flavour card, once in
   the cart drawer - and exactly one is ever visible.

   The card is an overlay registered onto a printed plate, so its room is a
   fixed share of the plate's height and the plate scales with the viewport.
   Below about 1280 there is not enough of it: the card is anchored by its
   floor, so the extra height goes upward and lands on the formula section
   above. Below 1025 the question disappears - the card stops being an overlay
   and becomes an ordinary block that can be any height it likes.

   So the cart copy appears in that band and nowhere else. It is a height
   problem, and CSS cannot measure height, so the widths were measured
   instead: at 1280 the card clears its plate, at 1030 it is 80px over. */
.cart__addon { display: none; }

@media (min-width: 1025px) and (max-width: 1279px) {
  .flav-art .addon { display: none; }
  .cart__addon {
    display: grid;
    margin-top: 0;
    margin-bottom: var(--s4);
    padding-top: 0;
    border-top: 0;
    padding-bottom: var(--s4);
    border-bottom: 1px solid color-mix(in srgb, var(--bone) 10%, transparent);
  }
  /* The drawer is a fixed 380-odd px wide whatever the viewport, so this copy
     is sized in px rather than in the artboard's container units - there is
     no container query in here to track. */
  .cart__addon .addon__pic { height: 46px; }
  .cart__addon .addon__name { font-size: 14px; }
  .cart__addon .addon__price { font-size: 12.5px; }
  .cart__addon .addon__btn { min-height: 36px; font-size: 12.5px; padding: 0 12px; }
}

/* One row on a phone: picture, price, button.

   The NAME is dropped here rather than wrapped or truncated, because the
   button already says what the thing is - "Добави батерия" - and repeating it
   two centimetres to the left is the least useful thing competing for a
   277px row. What cannot be dropped is the price, so that is what stays
   beside the picture.

   Everything shrinks a little to earn the single line: the thumbnail from 64
   to 44, and the button to its own text plus a thumb's worth of padding. */
@media (max-width: 940px) {
  .addon {
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s3);
    row-gap: 0;
  }
  .addon__pic { height: 44px; }
  .addon__name { display: none; }
  .addon__txt { padding-bottom: 0; }
  .addon__price { font-size: 13px; color: rgb(var(--bone-rgb) / .75); }
  .addon__btn {
    grid-column: auto;
    min-height: 38px;
    padding: 0 12px;
    font-size: 12.5px;
    gap: 5px;
  }
  .addon__btn svg { width: 12px; height: 12px; }
}

/* The narrowest phones. The same row, with the last of the slack taken out of
   the padding rather than out of the line. */
@media (max-width: 350px) {
  /* 222px of row to hold a thumbnail, a price that will not wrap and a button
     that has to keep the word батерия in it - the label is the only thing
     naming the product now. The slack comes out of the picture and the
     padding, in that order. */
  .addon { gap: 7px; }
  .addon__pic { height: 32px; }
  .addon__btn { padding: 0 8px; font-size: 11.5px; gap: 4px; }
  .addon__btn svg { width: 11px; height: 11px; }
  .addon__price { font-size: 11.5px; }
}

@media (prefers-reduced-motion: reduce) {
  .addon__btn { transition: none; }
}

/* ------------------------------------------------------ banked offers ----
   An offer that has been won while a bigger one is still ahead. It leaves the
   ring - which can only count towards one thing - and settles here as a small
   ticked pill.

   Ticked and dimmer than the widget above on purpose. This is a receipt, not
   an advert: it says the saving is already yours and needs no further action,
   which is exactly what stops the shopper reading it as a second offer
   competing with the one still being counted towards. */
.promo-won {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: var(--s4);
}
.promo-won[hidden] { display: none; }

.promo-won__b {
  /* Takes the row, because the widget above it does. Shrink-wrapped to its
     own text it read as a stray tag rather than as the same object one step
     down: a banked offer is the same kind of thing as the one still being
     counted towards, and the eye reads that from the shared edge more than
     from the shared shape. The corner layout overrides this, where the badge
     is not full width either. */
  flex: 1 1 100%;
  display: flex; align-items: center; gap: 6px;
  padding: 6px 14px 6px 8px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--gold) 26%, transparent);
  background: color-mix(in srgb, var(--gold) 8%, var(--surface-2));
  font-size: 12px; line-height: 1.3;
  color: rgb(var(--bone-rgb) / .78);
}
.promo-won__b svg { width: 12px; height: 12px; flex: none; color: var(--gold); }
.promo-won__b b { font-weight: 500; color: var(--bone); min-width: 0; }
/* Held to the right edge, which is where the widget above keeps the figure
   the shopper is looking for. */
.promo-won__b span {
  margin-left: auto; padding-left: var(--s3);
  color: var(--gold); font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* In the corner layout the widget is lifted out of the flow, so the bubbles
   have to be lifted with it or they would sit at the top of the column with
   nothing above them. --promo-h is the badge's measured height, written by
   fitPromo() in the same pass that decides the corner is possible at all. */
.flav__panel.is-promo-wide .promo-won {
  position: absolute;
  top: calc(var(--s5) + var(--promo-h, 0px) + 8px);
  right: var(--s5);
  z-index: 3;
  margin-bottom: 0;
  justify-content: flex-end;
  max-width: calc(100% - var(--s7));
}
/* In the corner the badge is only as wide as its own text, so a full-width
   bubble under it would be wider than the thing it belongs to. */
.flav__panel.is-promo-wide .promo-won__b { flex: 0 1 auto; }
.flav__panel.is-promo-wide .promo-won__b span { margin-left: 0; }

/* On the narrowest phones the badge runs out of row.

   The banner lays the three lines as two columns - the name and the figure on
   the left, the condition held to the right edge - and at 320px the figure
   and the condition meet in the middle: "10,00 €" printed straight through
   "още 2 броя". Both tracks floor at their own min-content, so the grid
   overflows its box rather than shrinking, and the overflow is what collides.

   Below 360 it stacks instead, which is exactly what the corner layout
   already does when it has no right-hand space to put the condition in.
   Measured, not guessed: the overlap is 7px at 320 and there is 13px of
   clearance by 340, so the switch sits above the failure and below the
   narrowest width that was ever fine. */
@media (max-width: 360px) {
  .promo__txt {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "kicker" "amount" "action";
  }
  .promo__action { justify-self: start; text-align: left; align-self: baseline; }
}

/* The states are carried by colour and by the glyph; the movement is the
   part that can go. */
@media (prefers-reduced-motion: reduce) {
  .promo, .promo__fill, .promo__ico { transition: none; }
  .promo.is-just-won,
  .promo.is-just-won .promo__ico,
  .promo.is-just-won .promo__mark::after,
  .promo.is-fresh::after { animation: none; }
  .promo.is-just-won .promo__mark::after,
  .promo.is-fresh::after { display: none; }
}

/* The seal is a mark on the product, so it sits on the photograph rather than
   in the sales copy. z-index 3 clears the stills (which are absolutely
   positioned inside .flav__viz) without entering the stage's own rim layers. */
.flav__seal {
  position: absolute; z-index: 3;
  top: var(--s5); left: var(--s5);
  width: 104px; height: 104px;
  color: var(--gold);
  pointer-events: none;
}
.flav__seal svg { width: 100%; height: 100%; display: block; }
.flav__seal-t {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .2em; fill: currentColor;
}
.flav__seal-t--mid { font-size: 9px; letter-spacing: .14em; }

@media (max-width: 900px) {
  /* Stacked, the panel sits under the art, so its edge is horizontal. */
  .flav__panel { border-left: 0; border-top: 1px solid var(--line); }
  .flav__seal { width: 84px; height: 84px; }
}



/* The clip sits above the stills and is revealed only once it is actually
   playing, so a slow network shows the photo rather than a black rectangle.
   It was rendered from that same photo, so the cross-fade has nothing to
   give away. */
.flav__stage.is-playing 
/* The bead on the rim is the effect; this only lifts the panel off the page
   so it reads as the thing being pointed at. Shadow and border only, so it
   cannot touch the contrast of anything inside. */
.flav__stage.is-lit {
  box-shadow: 0 20px 60px -28px color-mix(in srgb, var(--accent) 30%, transparent);
}
.flav__viz { transition: box-shadow var(--dur) var(--ease); }
.flav__stage.is-lit .flav__viz {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--glow, var(--accent)) 26%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .flav__glow { display: none; }
  }
.flav__viz img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.flav__viz img.is-on { opacity: 1; }

.flav__panel { position: relative; z-index: 2; }
.flav__name { font-family: var(--display); font-size: clamp(30px, 4.4vw, 46px); font-weight: 700; margin-bottom: var(--s4); }

/* A short gold rule under the name, fading out rather than stopping dead, so
   it reads as a flourish belonging to the serif above it and not as the top
   border of whatever comes next. */
.flav__rule {
  display: block; width: 54px; height: 1px;
  margin-bottom: var(--s5);
  background: linear-gradient(90deg, var(--gold), transparent);
}

/* Held to a comfortable measure instead of the full column: the copy is two
   short lines and, run to the panel's whole width, it stretched into one long
   line that fought the headline for the eye. min-height reserves the second
   line so the price below does not jump when a one-line flavour is picked. */
.flav__desc { color: var(--muted); max-width: 36ch; min-height: 3.2em; margin-bottom: var(--s6); }

/* The spec strip: icon and label on top, value beneath, hairlines between.
   `margin-top: auto` pushes it and everything after it to the bottom of the
   panel, so the name sits at the top and the buy block at the foot however
   tall the photograph makes the card - which is what keeps the two product
   cards aligned with each other. */
.spec {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: var(--s4) 0;
  margin: auto 0 var(--s6);
  padding-bottom: var(--s6);
  border-bottom: 1px solid var(--line);
}
.spec__item {
  display: grid; gap: 7px;
  padding: 0 var(--s5);
  border-right: 1px solid var(--line);
}
.spec__item:first-child { padding-left: 0; }
.spec__item:last-child  { padding-right: 0; border-right: 0; }
.spec dt {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; color: var(--muted);
}
.spec dd { margin: 0; color: var(--bone); font-size: 17px; font-weight: 500; }
/* Outside a product card the strip is just a list of figures: nothing follows
   it that the rule would separate, and there is no flex column for the auto
   margin to push against. */
.spec--plain { margin: var(--s6) 0 0; padding-bottom: 0; border-bottom: 0; }

.spec__ico { display: inline-flex; color: var(--gold); }
.spec__ico svg { width: 17px; height: 17px; display: block; }

/* Price above, action below and full width - not side by side. The button is
   the one thing the block is asking for, and a button sized to its own label
   sitting beside the price read as one of two equal items. */
.flav__buy { display: grid; gap: var(--s5); }

.flav__price { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s3); font-family: var(--mono); }
.flav__price-eur { font-size: 30px; font-weight: 500; letter-spacing: -.015em; }
/* The lev figure is required alongside the euro one, not competing with it. */
.flav__price-alt { font-size: 16px; color: var(--muted); }
.flav__price[data-unset="1"] { color: var(--muted); font-size: 17px; }

/* The cart mark leads the label; the arrow is pinned to the far edge so the
   button reads as "do this -> and go there" across its whole width rather
   than as a huddle of three things in the middle. Above the sheen and the
   specular, same as .btn__label and .btn__arrow. */
.btn__cart { position: relative; z-index: 1; flex: none; opacity: .92; }
/* Scoped to .flav__buy on purpose, NOT to .btn--wide. The cart drawer's
   checkout link (#cart-go) is also .btn--apple.btn--wide and also carries a
   .btn__arrow, so pinning the arrow by modifier would have silently moved it
   there too - in a component nobody asked to change. .btn--apple is already
   position:relative and .btn is already justify-content:center, so the anchor
   and the centring need no rule of their own. */
.flav__buy .btn__arrow { position: absolute; right: var(--s6); }

@media (max-width: 520px) {
  /* Three specs will not sit on one line here; let them wrap and drop the
     dividers, which would otherwise hang at the end of a wrapped row. */
  .spec__item { border-right: 0; padding-left: 0; }
  .spec { gap: var(--s4) var(--s6); }
}

/* --------------------------------------------------------- flavour picker
   Used to be a row of pills, each carrying a 10px dot in the flavour's own
   accent hex (--chip, set inline per product). That was the last place a
   raw per-product colour still reached the page, and it is exactly what the
   rebrand brief calls out: five different hues cycling through the switcher
   reads as noisy against a gold-on-black identity built on restraint. Icons
   replace the dots; colour no longer varies by flavour at all, only by
   state (muted / hover / selected), same as everything else in this
   identity. See --accent in :root for why the flavours no longer carry
   their own hue further up the page either. */
/* The flavour switcher.
   Under the card, the way the archived site had it, at full section width.
   Inside the card's text column it only had ~465px, which forced the buttons
   small enough to be awkward to hit and wrapped them onto a second row - and
   that row is also what kept this card from matching the oil card's height. */
/* ------------------------------------------------- the flavour selector --
   z4, sitting exactly over the printed selector it replaces.

   The buttons inside are the SAME buttons as before - same classes, same data
   attributes, same roving tabindex, same listeners in initFlavours. Only the
   container moved and the paint changed. The flavour system is still the only
   thing that decides which flavour is selected; this is what that decision
   looks like.

   Two jobs at once: it is the real control, and it is what hides the printed
   one. That is why it can be an opaque surface without looking like a patch -
   a cover that IS the interface reads as deliberate. */
.picker {
  position: absolute; z-index: 4;
  left: var(--ui-sel-x);
  top: var(--ui-sel-y);
  width: var(--ui-sel-w);
  height: var(--ui-sel-h);

  display: flex; align-items: center;
  gap: clamp(2px, .35cqw, 8px);
  padding: 0 clamp(4px, .5cqw, 11px);
  border-radius: 999px;

  /* Near-opaque on purpose. Glassmorphism here would show the printed
     selector's own icons and labels straight through the live one - the
     precise failure this whole approach exists to avoid. The gradient, the
     hairline and the inner highlight give it the READ of glass; the alpha
     never gets high enough to leak the pixels underneath. */
  background: linear-gradient(180deg,
              rgb(var(--surface-rgb) / .985), rgb(var(--ink-rgb) / .985));
  border: 1px solid rgb(var(--bone-rgb) / .10);
  box-shadow: 0 12px 40px rgb(0 0 0 / .42),
              inset 0 1px 0 rgb(255 255 255 / .04);
}

.chip {
  /* Equal columns, so all five controls are one size and one shape - the row
     has to read as a single instrument, and content-sized controls gave every
     one a different hover target. Equal columns were tried first and
     truncated "Боровинка"; that was the bar being 796px inside a 1220px page,
     not a fault of the approach. Edge to edge there is room for the longest
     label at full size. */
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  gap: clamp(2px, .4vw, 8px);
  height: 78%;
  padding: 0 clamp(2px, .35vw, 10px);
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: rgb(var(--bone-rgb) / .88);
  font: 500 clamp(12px, .93cqw, 19px)/1 var(--body);
  cursor: pointer;
  /* Visible, not disabled-looking: every flavour has to stay legible as a
     choice, so this dials saturation back rather than draining it. */
  opacity: .68;
  filter: saturate(.65);
  transition: transform .26s cubic-bezier(.34, 1.4, .64, 1),
              opacity .2s var(--ease), filter .2s var(--ease),
              background-color .2s var(--ease), border-color .2s var(--ease),
              color .2s var(--ease), box-shadow .2s var(--ease);
}
.chip__ico { display: inline-flex; color: var(--flav, var(--gold)); flex: none; }
.chip__ico svg { width: clamp(14px, 1.15cqw, 24px); height: auto; display: block; }
.chip__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.chip:hover {
  opacity: 1;
  filter: saturate(1);
  transform: translateY(-2px);
  color: var(--bone);
  border-color: color-mix(in srgb, var(--flav, var(--gold)) 45%, transparent);
  background: color-mix(in srgb, var(--flav, var(--gold)) 10%, transparent);
}

/* The press travels the opposite way and gets there fast - a press that moves
   less than the hover already did reads as the click not registering. */
.chip:active {
  transform: translateY(0) scale(.965);
  transition-duration: .08s;
}

/* Coloured light caught inside black glass, rather than a solid coloured pill:
   the selector sits fourth in the hierarchy, under the cartridge, the fruit
   and the widget, and a saturated block here would outrank all three. */
.chip[aria-selected="true"] {
  opacity: 1;
  filter: saturate(1.15);
  color: var(--bone);
  border-color: color-mix(in srgb, var(--flav, var(--gold)) 75%, transparent);
  background: color-mix(in srgb, var(--flav, var(--gold)) 11%, rgb(var(--ink-rgb) / .98));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--flav, var(--gold)) 35%, transparent),
              0 0 24px color-mix(in srgb, var(--flav, var(--gold)) 22%, transparent);
}

@supports not (color: color-mix(in srgb, red, blue)) {
  .chip:hover,
  .chip[aria-selected="true"] { border-color: var(--flav, var(--gold)); }
}

/* Roving tabindex means only one chip is tabbable, so the focused one has to
   be unmistakable or keyboard users lose their place. The hover treatment
   cannot carry this on its own - it is not available to a keyboard. */
.chip:focus-visible {
  outline: 2px solid var(--flav, var(--gold-soft));
  outline-offset: 3px;
}

/* ==========================================================================
   The artboard on narrow screens

   The overlay geometry is proportional, so it survives being scaled down - but
   only as geometry. At 375px the artboard is 137px tall, which would put the
   selector at 19px and the widget's type below legibility. Proportion holds;
   usability does not.

   So below 1024px the overlays stop being overlays and stack underneath in
   normal flow, and the artboard becomes a WINDOW onto the plate rather than
   the whole of it. No second image file is involved: the crop is done by the
   viewport, so the same asset serves every breakpoint.

   The window is x 30..70%, y 0..70% of the plate, which is chosen to exclude
   every piece of printed interface at once:

     printed selector  y 70.8..84.9%  - below the window's bottom edge
     printed widget    x 71..95.2%    - right of the window's right edge
     printed copy      x 4..30%       - left of the window's left edge

   Nothing needs covering here, so the veil switches off too.

   object-fit cannot do this: it crops one axis, and this crops both. Hence the
   inner box sized in percentages of the outer one, which keeps the plate's own
   aspect ratio intact while showing a rectangle of it.

   The window is x 31.3..70.67%, y 0..70.5%, every bound set by a printed
   element rather than chosen for looks. The numbers moved with registration,
   because the printed furniture moved with it:

     left  31.30%  the printed copy now ends at 30.74%
     right 70.67%  the printed widget now starts at 70.90%
     below 70.5%   the printed selector now starts at 70.86%

     scene width 254%    = 100/39.37, so 39.37% of the plate spans the window
     left -79.5%         = -(31.3/39.37), so the window opens at x 31.3%
     outer ratio 1.4975  = (0.3937*2030)/(0.705*757)
   ========================================================================== */
@media (max-width: 1024px) {
  /* The artboard stops being a fixed-ratio box here and becomes an ordinary
     column: window, then copy, then picker, then widget.

     It used to keep aspect-ratio: 1.4975 - the window's ratio - while its
     children were put back into normal flow below the window. .flav-art
     clips, so at 375px the box was 250px tall and everything after the
     window fell outside it: the picker sat 237px down and showed 13px of
     itself, and the product widget, add button and all, was entirely past
     the bottom edge of a box nothing could see out of. On a phone the
     section had no way to choose a flavour and no way to buy one.

     The crop is unchanged, only moved one level down. The scene is now the
     window - in flow, so it reserves its own height - and the oversized
     plate is positioned inside it. The percentages resolve the same, because
     the scene is still exactly as wide as the artboard. */
  .flav-art {
    aspect-ratio: auto;
    max-width: none;
    overflow: visible;
  }
  /* The window frames the whole cartridge AND every fruit.

     Two earlier attempts bracket this one. x635..1435 y0..534 sliced the
     cartridge's threaded base off at y632. x880..1401 y0..680 kept the base
     but started right of the mango, which lives at plate x600..870 - so the
     one plate whose hero fruit is the mango showed no mango at all, and the
     flavour that was selected was the flavour you could not see.

     x640..1401, y0..680 holds all of it: mango at the left edge, blueberries,
     the cartridge at 49% across, cherries and grapes to the right, and the
     base well clear of the bottom. It is also a shorter window - 1.119 rather
     than 0.766 - so the section stopped being a tower.

     What that costs is the plate's own PRINTED selector, which runs x60..855
     y549..640 and now reaches into the bottom-left corner. ::after covers it.
     See the veil below. */
  .flav-art__scene {
    position: relative;
    inset: auto;
    width: 100%;
    aspect-ratio: 761 / 680;
    overflow: hidden;
    border-radius: inherit;
    /* Same fade-in as the oil plate below, but shorter: the mouthpiece is
       already at 8.2% of this window. */
    -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 6%);
    mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 6%);
  }

  /* The cover over the plate's printed selector.

     Same rule the rest of this build runs on: an opaque live surface covers
     its printed counterpart. Here there is no live surface to put over it -
     the picker is in flow below the window - so the printed one is erased.

     THREE VERSIONS FAILED BEFORE THIS ONE, and all three failed the same way:
     they covered a REGION instead of covering the MARK.

     A blurred box (40% x 26%, filter: blur(16px)). CSS blur(r) is a Gaussian
     with standard deviation r, so a step edge does not reach full opacity
     until about 2.3 sigma inside it - at 375px that is ~75 plate px per side.
     To get an opaque core over ink 800px wide the box had to be drawn far
     past it, and it was: down from y503 with the blur reaching y471, straight
     across the mango, whose lower edge is at y538..548. That is the black
     rectangle. And because the core was smaller than the box, the pill's own
     edges were only SHADED - every plate lights a different chip, so what
     showed through changed on each pick. That is the menu flashing.

     A gradient-feathered box, then a masked ellipse. Softer each time, and
     each still a dark shape laid over photography, which is what "heavily
     edited" means. There is no size of blur that makes retouching not
     retouching.

     So this one is not a region. It is the PILL, and only the pill.

     The printed selector is a rounded rect at plate x..855, y549..659 - the
     footprint measured two ways that agree: a brightness step down each edge,
     and the region where all five plates still match one another (the printed
     furniture is common to them; the fruit is not). This element is that
     footprint plus about 4px, with the same fully-rounded right cap, so it
     replaces the mark with its own silhouette rather than throwing a shadow
     across the corner. Nothing outside the pill is touched: the mango keeps
     its lower edge, the blueberries keep theirs, and the gold arcs below
     y663 - which every earlier version painted over - are back.

     What is behind the pill is not lost, because the pill was already opaque:
     the plate has no artwork under it to reveal.

     The fill is measured, not chosen. Sampling the ground either side at the
     pill's own height and discarding the top 45% of luminance (the gold arcs,
     which no flat fill can imitate) gives rgb(36..47, 14..24, 1..3) at the
     left and rgb(27..35, 13..19, 1..3) at the right, consistent across all
     five plates - so the gradient runs between their means. The earlier
     versions faded to rgb(8 8 8), a near-black against ground that is warm
     brown, and that is why they read as a hole punched in the corner. */
  .flav-art__scene::after {
    content: '';
    position: absolute;
    /* Plate x560..866, y546..663, as percentages of the 761x680 window. Runs
       past the window's left edge on purpose - overflow clips it there, so
       the left end needs no cap and cannot lighten the corner. */
    left: -10.51%;
    width: 40.21%;
    top: 80.29%;
    height: 17.21%;
    /* The pill's own shape. A crisp radius rather than a blur: the mark it
       replaces had a crisp edge too, and a soft one over a sharp photograph
       is exactly the smudge this is here to stop being. */
    border-radius: 0 999px 999px 0;
    background: linear-gradient(to right, rgb(41 19 2), rgb(31 15 2));
    pointer-events: none;
  }

  .flav-art__scene img {
    inset: auto;
    left: -84.099%;
    top: 0;
    width: 266.754%;
    /* The plate is deliberately wider than its window. Without this the
       global img { max-width: 100% } clamps it straight back to the window's
       width and the crop silently becomes the whole plate, 140px tall. */
    max-width: none;
    height: auto;
    aspect-ratio: 2030 / 757;
  }

  /* Room between the price and the button.

     .flav__stage--card tightens .flav__buy to clamp(8px, 1.1cqw, 22px), and on
     a phone that container query resolves to its 8px floor. That floor was set
     when the price block ended in a FIGURE. It ends in a sentence now - the
     rule's name and what it saves - and a caption sitting 10px above a 50px
     green pill reads as though it belongs to the button rather than to the
     price above it. Only the narrow end needs lifting; the clamp already gives
     a wide panel up to 22px. */
  .flav__stage--card .flav__buy,
  .oil-art .flav__buy { gap: var(--s4); }

  /* Stacked below an edge-to-edge artboard, the overlays need the page's own
     gutter - the band has none now that the artboard carries no container. */
  /* Scoped to the artboard's own children. Unscoped, .flav__stage--card also
     matched the oil widget, which lives in a grid that already has padding -
     so it came out inset from the copy beside it. */
  .flav-art__copy,
  .flav-art > .picker,
  .flav-art > .flav__stage--card { margin-inline: var(--s5); }
  /* The printed copy is outside the window now, so there is nothing to hide
     and a veil would only darken fruit. */
  .flav-art__veil { display: none; }

  /* Out of the overlay coordinate system and into ordinary flow. */
  /* Back into flow - including the copy's children, which are pinned to plate
     coordinates on desktop and would otherwise stay absolutely positioned
     against an artboard they are no longer sitting on. */
  .flav-art__copy,
  .flav-art__copy > *,
  .picker,
  .flav__stage--card {
    position: static;
    width: auto;
    left: auto; top: auto;
  }

  /* relative, not static, for the three that carry a z-index.
     
     static discards z-index, and .flav-art__scene is absolute at z-index 1 -
     so on this branch the plate painted over the picker and the copy. The
     chips were not clickable at all: elementFromPoint on every one of the five
     returned the plate image. relative keeps them in flow exactly as static
     did and lets .picker's z-index 4 and .flav-art__copy's z-index 3 apply
     again, which is what they were declared for. */
  .flav-art__copy,
  .picker,
  .flav__stage--card { position: relative; }
  .flav-art__copy { height: auto; }
  .flav-art__copy .eyebrow { margin-bottom: .6em; }
  .flav-art__copy h2 { margin: 0 0 .5em; width: auto; }
  .flav-art__lede { margin: 0 0 1.1em; }

  .flav-art__copy {
    margin-top: var(--s6);
    max-width: 46ch;
  }
  .flav-art__copy h2 { font-size: clamp(26px, 5.4vw, 40px); }
  .flav-art__lede { font-size: 15px; }
  .tag--check { font-size: 12px; }

  .picker {
    margin-top: var(--s5);
    height: auto;
    padding: var(--s2);
    /* One row that scrolls, rather than five labels squeezed past legibility.
       Snap so a swipe lands on a whole control. */
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .picker::-webkit-scrollbar { display: none; }

  /* "There are more flavours - swipe."

     Five chips need about 650px and the row has 340. It scrolled, but the
     only thing saying so was a chip clipped by the edge, which reads as a
     layout mistake rather than an invitation. Nothing else on the page
     scrolls sideways, so nobody thinks to try.

     A sticky flex item does the whole job without touching the markup or the
     selector: it is the last child of the scroller, so right: 0 with
     position: sticky parks it against the right edge while the chips run
     under it, and a negative margin equal to its width means it costs no
     layout space and cannot push a chip out of reach. Gradient underneath so
     the row visibly continues, chevron on top pointing the way.

     It fades out when there is nothing left to scroll to - is-end comes from
     the scroll listener in site.js - so it never points at a wall. */
  .picker { scroll-padding-inline-end: var(--s6); }
  .picker::after {
    content: '';
    flex: none;
    position: sticky;
    right: calc(-1 * var(--s2));      /* cancel .picker's own padding */
    align-self: stretch;
    width: 62px;
    margin-left: -62px;
    pointer-events: none;
    border-radius: inherit;
    background:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23D4AF37' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 18l6-6-6-6'/%3E%3C/svg%3E") center right 6px / 20px 20px no-repeat,
      linear-gradient(to right, rgb(var(--ink-rgb) / 0), rgb(var(--ink-rgb) / .82) 46%, rgb(var(--ink-rgb) / .94));
    transition: opacity .22s var(--ease);
  }
  .picker.is-end::after { opacity: 0; }
  .chip {
    flex: none;
    scroll-snap-align: start;
    /* Comfortably past the 44px touch minimum. */
    min-height: 46px;
    height: auto;
    padding: 0 var(--s4);
    font-size: 15px;
    gap: var(--s3);
  }
  .chip__ico svg { width: 22px; }

  .flav__stage--card {
    margin-top: var(--s5);
    min-height: 0;
  }
  .flav__stage--card > .flav__panel { padding: var(--s5); }
  .flav__stage--card .flav__name { font-size: clamp(24px, 5vw, 34px); }
  .flav__stage--card .flav__desc { font-size: 15px; }
  .flav__stage--card .eyebrow { font-size: 11px; }
  .flav__stage--card .spec dt { font-size: 10.5px; }
  .flav__stage--card .spec dd { font-size: 17px; }
  .flav__stage--card .spec__ico svg { width: 17px; }
  .flav__stage--card .flav__price-eur { font-size: 30px; }
  .flav__stage--card .flav__price-alt { font-size: 16px; }
  .flav__stage--card .btn--apple { min-height: 56px; font-size: 16px; }
}


@media (prefers-reduced-motion: reduce) {
  .chip, .chip:hover, .chip:active { transition: none; transform: none; }
  /* The border and ring still change, so the button is still answering - it
     just does not bounce. */
  .cartbtn.is-added .cartbtn__n { animation: none; }
  .cartbtn.is-added::after { animation: none; }
}

/* color-mix is widely supported but not universal; without it the tint and
   hover border would silently do nothing, so fall back to a plain border. */
@supports not (background: color-mix(in srgb, red 10%, blue)) {
  .chip:hover,
  .chip[aria-selected="true"] { border-color: var(--flav, var(--gold)); }
}

/* ------------------------------------------------------------- hero -- */
/* The product photograph is the background. See the note on the section in
   index.php for why layering a product shot over the film did not work. */
/* --------------------------------------------------------------- oil -- */
/* The oil sits on the same stage as the flavours but has no picker under it
   and only one image, so it gets an even split. The ratio is 3:2 because that
   is what the artwork is: cropping it loses the list down the left edge and
   the pipette on the right. The clip is 3:2 as well, so hovering swaps two
   frames that are cropped identically and nothing shifts. */
/* One photo, no switcher: it is always the visible one. Not relying on the
   is-on class here means the flavour picker cannot reach in and hide it. */

/* -------------------------------------------------------- compliance -- */
/* ==========================================================================
   DOCUMENTS AND TRUST, and the run of page below the oil

   Built to a supplied design. The three sections that close the page -
   evidence, questions, footer - now read as one dark field with gold on it
   rather than three separately framed bands, which is what the mockup is
   doing and why the seams between them are gradients rather than edges.
   ========================================================================== */

/* The centred eyebrow with a hairline running out of both sides of it. The
   plain .eyebrow is left alone: it is left-aligned above every other section
   and this is only for the two that close the page. */
.eyebrow--rule {
  text-align: center;
  /* Bone, not gold. This heading sits at the top of the field where the
     artwork's arc is brightest, and gold letters on a gold swirl were
     invisible - the scrim behind fixes the ground, but a heading this small
     at .28em tracking needs the contrast of the page's own text colour to
     hold together. The gold stays on the rule beneath it and on the claim
     titles, which sit lower and on clean ground. */
  color: var(--bone);
  font-size: 12.5px;
  letter-spacing: .28em;
  margin: 0 0 var(--s6);
  padding-bottom: var(--s4);
  position: relative;
  /* Lifts the letters off whatever survives the scrim behind them. */
  text-shadow: 0 2px 14px rgb(var(--ink-rgb) / .9);
}
/* A hairline beneath the words, fading out at both ends. The first attempt
   put a line either SIDE of the title, which is a different device and not
   the one the design uses - it made the heading look like a form legend. */
.eyebrow--rule::after {
  content: '';
  position: absolute;
  left: 50%; bottom: 0;
  transform: translateX(-50%);
  width: min(560px, 70%);
  height: 1px;
  background: linear-gradient(to right,
    transparent,
    color-mix(in srgb, var(--gold) 45%, transparent) 25%,
    color-mix(in srgb, var(--gold) 45%, transparent) 75%,
    transparent);
}

/* ---------------------------------------------------- the trust strip -- */
/* A row, not a grid of cards. The evidence is one statement made in three
   parts, and three bordered boxes said it as three separate things. The
   dividers are hairlines between the columns, so the row reads across. */
/* One document and a form, side by side.

   The row used to be three claims and a way in. With two of the papers gone
   the strip had a column of content and three of air, so the space goes to
   the thing a visitor with a question actually needs. The document keeps the
   narrower side: it is evidence, and the form is the invitation. */
.trust {
  display: grid;
  /* Equal halves. The ratio used to be .82 : 1, from when this row held three
     document cards against the form and the cards were the smaller thing
     three times over. It holds ONE card now, so the band is simply two boxes
     side by side - and two boxes of visibly different width, one of them a
     third the height of the other, read as a layout that ran out of content
     rather than one that was composed. Equal columns make them a pair.

     Below 900 this collapses to a single column, so the split only ever
     applies on a desktop. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s7);
  /* Stretch, not start. The two boxes are a pair and now read as one: equal
     width and equal height, rather than a short card floating beside a tall
     form. Stacked below 900 each item is alone in its row, so this has no
     effect there. */
  align-items: stretch;
  margin-top: var(--s6);
}
.trust__i {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  /* Centred in the box it now shares a height with. The content was pinned to
     the top so the two headings would line up, which was the right call while
     the card held one line and the rest was emptiness - all of it pooling at
     the bottom. With something to say it reads better balanced, and the space
     above and below becomes margin rather than a gap where content ran out. */
  align-content: center;
  gap: var(--s4);
  padding: var(--s5);
  border: 1px solid color-mix(in srgb, var(--gold) 16%, transparent);
  border-radius: 14px;
  transition: border-color .3s var(--ease);
}
.trust__i:hover { border-color: color-mix(in srgb, var(--gold) 34%, transparent); }

/* The line that holds the space while the document does not exist. Quiet -
   it is a statement of position, not a selling point, and it sits under a
   status that already said the paper is not ready. */
.trust__while {
  margin: var(--s3) 0 0;
  max-width: 46ch;
  font-size: 13.5px; line-height: 1.6;
  color: rgb(var(--bone-rgb) / .55);
}
/* Points at the form beside it, which is the thing to do about a document
   that is not there yet. */
.trust__ask {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: var(--s4);
  font-size: 13.5px; font-weight: 500;
  color: var(--gold); text-decoration: none;
}
.trust__ask svg { transition: transform .22s var(--ease); }
.trust__ask:hover svg { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .trust__ask svg { transition: none; }
  .trust__ask:hover svg { transform: none; }
}

/* ------------------------------------------------------- the mark and copy */
.trust__ico {
  display: grid; place-items: center; flex: none;
  width: 46px; height: 46px;
  border: 1px solid color-mix(in srgb, var(--gold) 38%, transparent);
  border-radius: 10px;
  color: var(--gold);
  transition: color .3s var(--ease), border-color .3s var(--ease),
              box-shadow .3s var(--ease);
}
.trust__ico svg { width: 24px; height: 24px; display: block; }

.trust__txt h3 {
  position: relative; display: inline-block;
  font-family: var(--body); font-size: 16px; font-weight: 500;
  color: var(--gold); margin-bottom: 6px;
  transition: color .3s var(--ease);
}
.trust__txt p { font-size: 14px; line-height: 1.5; color: var(--muted); }
.trust__go { display: inline-block; margin-top: var(--s2); font-size: 13px; }

/* A document that does not exist yet says so, rather than being hidden. The
   column keeps its place and the row keeps its shape; when the constant in
   config.php is filled it becomes a link on its own. */
.trust__i.is-pending .trust__ico {
  border-style: dashed;
  color: rgb(var(--bone-rgb) / .38);
}
.trust__pending {
  display: inline-block; margin-top: var(--s2);
  font-family: var(--mono); font-size: 11.5px;
  color: rgb(var(--bone-rgb) / .42);
}

/* ------------------------------------------------------------ the response */
/* One idea, not three.

   The first attempt tinted the column, lifted the mark AND shifted the
   heading white all at once, which on a row of three read as the layout
   twitching rather than as an answer. This is a gold rule drawing itself
   under the title while the mark warms - the same gesture the footer links
   use, so the page has one way of saying "this is live".

   These rules sit AFTER .is-pending deliberately. An earlier version had
   them before it, so the pending state's dimmed icon colour won the cascade
   and hovering a claim did nothing to its mark. */
.trust__txt h3::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .34s var(--ease);
}
.trust__i:hover .trust__txt h3::after,
.trust__i:focus-within .trust__txt h3::after { transform: scaleX(1); }

.trust__i:hover .trust__ico,
.trust__i:focus-within .trust__ico {
  border-color: color-mix(in srgb, var(--gold) 62%, transparent);
  box-shadow: 0 0 22px -8px color-mix(in srgb, var(--gold) 75%, transparent);
}
/* Including the pending ones: the mark warms even when the paper behind it
   is not ready, because the row still has to answer the pointer. */
.trust__i:hover .trust__ico,
.trust__i.is-pending:hover .trust__ico { color: var(--gold); }

/* A claim that HAS its document is the way to it, and says so. */
.trust__i:not(.is-pending) { cursor: pointer; }


/* The lift and the glow are decoration; the colour changes carry it. */
@media (prefers-reduced-motion: reduce) {
  .trust__ico, .trust__txt h3::after { transition: none; }
  /* The rule still appears; it just does not draw itself. */
  .trust__i:hover .trust__txt h3::after { transform: scaleX(1); }
}


/* ----------------------------------------------------- the question form -- */
/* Built from the site's own .field, so the input shapes, the focus rings, the
   error colouring and the desktop border contrast are the ones the checkout
   already uses rather than a second set invented for one form. */
.ask {
  display: grid;
  gap: var(--s4);
  padding: var(--s6);
  border: 1px solid color-mix(in srgb, var(--gold) 16%, transparent);
  border-radius: 14px;
  background: rgb(var(--surface-rgb) / .5);
}
.ask__h {
  font-family: var(--display); font-size: clamp(20px, 2vw, 26px);
  font-weight: 600; color: var(--bone); margin: 0;
}
.ask__lede { margin: -6px 0 var(--s2); font-size: 14px; color: var(--muted); }

/* Two on a line where there is room, one where there is not. Named columns
   rather than auto-fit: name and email are a pair and should break together. */
.ask__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
@media (max-width: 560px) { .ask__row { grid-template-columns: 1fr; } }

.ask textarea { min-height: 96px; resize: vertical; }

/* The honeypot. Not display:none - some bots skip what is not rendered -
   but out of the flow, out of the tab order and out of sight. */
.ask__trap {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

.ask__foot {
  display: flex; align-items: center; gap: var(--s4);
  flex-wrap: wrap; margin-top: var(--s2);
}
.ask__foot .btn { flex: 0 0 auto; }
.ask__note { margin: 0; font-size: 12.5px; line-height: 1.5; color: rgb(var(--bone-rgb) / .5); }
.ask__note a { color: var(--gold); }

/* What happened, once something has. role="status" announces it; the colour
   is the second channel, never the only one. */
.ask__said {
  margin: 0; padding: var(--s3) var(--s4);
  border-radius: 10px; font-size: 14px;
}
.ask__said.is-ok {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
}
.ask__said.is-bad {
  color: #FF6B6B;
  background: color-mix(in srgb, #FF4D4D 10%, transparent);
  border: 1px solid color-mix(in srgb, #FF4D4D 34%, transparent);
}

@media (max-width: 900px) {
  .ask { padding: var(--s5); }
}

.trust__note {
  max-width: 86ch; margin: var(--s6) auto 0;
  text-align: center;
  font-size: 13.5px; line-height: 1.6;
  color: rgb(var(--bone-rgb) / .5);
}

/* Stacked, the dividers become rules ABOVE each item rather than beside it -
   a vertical hairline between things that are no longer side by side is just
   a line in the wrong place. */
@media (max-width: 900px) {
  .trust { grid-template-columns: 1fr; gap: var(--s5); }
  .trust__end {
    justify-items: start; text-align: left;
    grid-auto-flow: column; justify-content: start; align-items: center;
    gap: var(--s4);
    padding: var(--s5) 0 0;
    border-left: 0;
    border-top: 1px solid color-mix(in srgb, var(--gold) 16%, transparent);
  }
  .trust__note { text-align: left; margin-top: var(--s6); }
}


.notice {
  border-left: 2px solid var(--accent);
  padding: var(--s2) 0 var(--s2) var(--s5);
  color: var(--muted); font-size: 15px; max-width: 74ch;
}

/* ----------------------------------------------------------- delivery -- */
.steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--s5); margin-top: var(--s6); }
.step { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: var(--s5); }
.step h3 { font-family: var(--body); font-size: 17px; font-weight: 600; margin-bottom: var(--s2); }
.step p { color: var(--muted); font-size: 15px; }
.step__k { font-family: var(--mono); font-size: 12px; color: var(--accent); display: block; margin-bottom: var(--s3); }

/* ---------------------------------------------------------------- faq -- */
/* Wide rows, and calm after the product bands. Each row is its own surface
   rather than a line in a list: at full width a bare hairline between two
   questions leaves the open one with no edges, and the answer floats.

   The <details>/<summary> behaviour is the browser's and is untouched - this
   is paint only. */
/* Centred and wider than it was, on the same dark field as the strip above:
   the design puts the questions in the middle of the page rather than in a
   left-aligned column under a heading. */
.faq { max-width: 1040px; margin: var(--s5) auto 0; display: grid; gap: var(--s2); }
/* Opaque enough to read against, on a field that still has light in it. */
.faq details {
  background: rgb(var(--surface-rgb) / .55);
  border: 1px solid color-mix(in srgb, var(--gold) 15%, transparent);
  border-radius: 12px;
  padding-inline: var(--s6);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.faq details:hover { border-color: color-mix(in srgb, var(--gold) 34%, transparent); }
.faq details[open] {
  border-color: color-mix(in srgb, var(--gold) 40%, transparent);
  background: rgb(var(--surface-rgb) / .9);
}
.faq summary {
  cursor: pointer; padding: var(--s4) 0; font-weight: 500;
  list-style: none; display: flex; justify-content: space-between; gap: var(--s4);
  min-height: 52px; align-items: center;
}
.faq summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; border-radius: 4px; }
.faq summary::after { transition: transform .22s var(--ease); }
.faq details[open] summary::after { transform: rotate(180deg); }

@supports not (color: color-mix(in srgb, red, blue)) {
  .faq details:hover, .faq details[open] { border-color: var(--gold-deep); }
  .closing { background: var(--ink); }
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; color: var(--gold); font-family: var(--mono); font-size: 20px; flex: none; }
.faq details[open] summary::after { content: '−'; }
.faq details p { padding-bottom: var(--s4); color: var(--muted); max-width: 70ch; }
.faq details p:last-child { padding-bottom: var(--s5); }
/* The bulleted answer. Gold marks rather than the browser's discs, so a list
   inside an answer belongs to this page rather than to the user agent. */
.faq__list {
  list-style: none;
  padding: 0 0 var(--s5);
  margin: 0;
  display: grid; gap: 6px;
  color: var(--muted); max-width: 70ch;
}
.faq__list li { position: relative; padding-left: var(--s4); }
.faq__list li::before {
  content: '';
  position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px;
  border-radius: 999px;
  background: var(--gold);
}

/* --------------------------------------------------------- cart panel -- */
.cart {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(430px, 100%);
  z-index: 120; background: var(--surface); border-left: 1px solid var(--line);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform .34s var(--ease), width .34s var(--ease);
}
/* Widens only when the discounts column has something in it. */
.cart.is-wide { width: min(1040px, 100%); }
.cart.is-open { transform: none; }
.cart__scrim {
  position: fixed; inset: 0; z-index: 110; background: rgba(0,0,0,.62);
  opacity: 0; pointer-events: none; transition: opacity .34s var(--ease);
}
.cart__scrim.is-open { opacity: 1; pointer-events: auto; }
.cart__top { display: flex; align-items: center; justify-content: space-between; padding: var(--s5); border-bottom: 1px solid var(--line); }
.cart__top h2 { font-size: 19px; }
.cart__x { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 26px; line-height: 1; width: 44px; height: 44px; }
.cart__x:hover { color: var(--bone); }
/* The two columns. One track until the discounts column has something to
   show, so an ordinary basket is the same narrow drawer as before. */
.cart__split { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr; overscroll-behavior: contain; }
.cart__split.is-split { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); }

.cart__main { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
/* overscroll-behavior: contain, and it is not cosmetic.

   Without it, dragging past the end of this list CHAINS the scroll to the
   page behind the drawer: the page moves under the overlay, iOS adds its
   rubber-band on top, and the whole drawer appears to shift while your finger
   is still on a product row. Closing the drawer then leaves you somewhere
   else on the page entirely. `contain` stops the chain at this box - the list
   still scrolls, it just no longer hands the gesture on. The office picker
   has had this since it was built; the cart never did. */
.cart__body {
  flex: 1; overflow-y: auto; padding: var(--s5);
  display: grid; gap: var(--s3); align-content: start;
  overscroll-behavior: contain;
}
.cart__empty { color: var(--muted); text-align: center; padding-block: var(--s8); }
/* the hold: a battery alone in the basket - the reason, in the warm
   register, above the totals, with the way to a product */
.cart__hold {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 10px; padding: 12px 14px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--gold) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  color: var(--bone);
}
.cart__hold[hidden] { display: none; }
.cart__hold svg { flex: none; margin-top: 1px; color: var(--gold); }
.cart__hold p { margin: 0; font-size: 13.5px; line-height: 1.5; }
.cart__hold a { color: var(--gold-soft); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; white-space: nowrap; }
.cart__hold a:hover { color: var(--gold); }
/* ==========================================================================
   The cart's scroll edge

   The panel already told the visitor where the list STARTS - .cart__top
   carries a bottom hairline - but said nothing about where it ENDS. The foot
   sat on the same surface with padding-top:0 and no edge of its own, so a
   full basket ran straight into the totals and the last product was sliced
   through the middle. A figure cut in half does not read as "there is more
   below"; it reads as a broken layout, and the eye has nowhere to stop.

   Three things fix it, and they are the same three every scrolling panel with
   a fixed action bar uses:

     boundary   a hairline, so the foot is a plane and not more list. It
                mirrors .cart__top and costs nothing.
     elevation  an upward shadow, so the plane is above the content rather
                than beside it.
     dissolve   a short fade over the last of the list, so items go out of
                view by dimming instead of being guillotined.

   The last two appear ONLY while there is still list hidden behind the foot
   (see .has-more, set from cartEdge() in site.js). A permanent shadow under
   a two-item basket is a lie about depth, and a fade over content that has
   already ended just dims the final row for no reason.
   ========================================================================== */
.cart__foot {
  position: relative;
  /* Above .cart__body, so the fade covers the list rather than the list
     painting over it. Positioned and later in the DOM would be enough on its
     own; the index is here so it survives a reorder. */
  z-index: 1;
  /* padding-top was 0. The totals need to clear the hairline, or the border
     reads as an underline on the last product instead of the top of a panel. */
  padding: var(--s5);
  border-top: 1px solid var(--line);
  background: var(--surface);
  transition: box-shadow .3s var(--ease), border-top-color .3s var(--ease);
}

/* The dissolve. Sits ABOVE the foot (bottom:100%), over the tail of the list.
   It fades to a fully transparent SURFACE rather than to `transparent`: the
   CSS-wide keyword is transparent BLACK, and interpolating a dark panel
   towards it drags the midpoint darker than either end - a dirty smear across
   the gradient instead of a clean fade. */
.cart__foot::before {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 100%;
  height: 44px;
  background: linear-gradient(to top,
              var(--surface), rgb(var(--surface-rgb) / 0));
  opacity: 0;
  transition: opacity .3s var(--ease);
  pointer-events: none;
}

.cart__main.has-more .cart__foot::before { opacity: 1; }
.cart__main.has-more .cart__foot {
  border-top-color: rgb(var(--bone-rgb) / .14);
  /* Tight and mostly negative-spread: a soft halo climbing the panel would
     read as glow. This reads as one surface lying over another. */
  box-shadow: 0 -14px 26px -18px rgb(0 0 0 / .85);
}


.cart__side {
  min-width: 0; overflow-y: auto;
  padding: var(--s5); border-left: 1px solid var(--line);
  display: grid; gap: var(--s3); align-content: start;
  /* Same reasoning as .cart__body. This panel only scrolls in its rare tall
     case, but when it does the gesture must stop here too. */
  overscroll-behavior: contain;
}


/* ==========================================================================
   THE OIL BAND

   Same construction as the flavour artboard and the same way round: copy left,
   product centre, widget right. oil_section.png is a mockup with its interface
   printed in, exactly like the flavour plates, so the live interface covers
   the printed one here too.

   Simpler in two ways, though. There is one plate rather than five, so nothing
   has to be registered and nothing crossfades. And the printed copy has only a
   cannabis leaf to its right rather than a hero mango, and the leaf starts at
   27% while the copy ends at 25.7% - so the veil has room to be a plain
   generous ellipse instead of the two carefully-shaped ones next door.

   Coordinates measured off the 1774x887 plate by overlaying boxes on it. Worth
   noting the two plates share a grid: the printed widget sits at 70.57% here
   against 70.90% on the flavour plates, and its top at 19.73% against 19.66%.
   ========================================================================== */
.oil-art {
  position: relative;
  width: 100%;
  aspect-ratio: 1774 / 887;
  overflow: hidden;
  isolation: isolate;
  container-type: inline-size;

  /* printed widget: x 1252..1722, y 175..695 of 1774x887 */
  --ui-wid-x: 70.57%;
  --ui-wid-y: 19.73%;
  --ui-wid-w: 26.49%;
  --ui-wid-h: 58.62%;

  /* printed copy: x 66..586, y 112..640. The right edge is set by the HEADING,
     which runs to 586 - not by the checklist, which stops at 456. Measuring
     the block off the list was the first mistake here and left the tail of
     "баланс" showing past the live heading. */
  --ui-copy-x: 3.72%;
  --ui-copy-w: 29.31%;
}

.oil-art__scene { position: absolute; inset: 0; z-index: 1; }
.oil-art__scene img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* fill, not cover: the box already carries the plate's ratio, so there is
     nothing to crop, and cover could shave an edge at some widths - which is
     the drift the coordinate system exists to prevent. */
  object-fit: fill;
}

/* One ellipse is enough here. Nothing precious sits beside the printed copy -
   the leaf begins at 27% and the copy ends at 25.7% - so this can be opaque
   across the whole block and still fade out before it touches anything. */

/* Pinned to the plate's own baselines, same reasoning as the flavour band:
   live text landing on printed text is what hides it, and a flowed block only
   has to be a few pixels out for the printed type to reappear beside it.
   eyebrow y112, heading y165, list y245 - all of 887. */
.oil-art__copy {
  position: absolute; z-index: 3;
  left: var(--ui-copy-x);
  top: 0;
  width: var(--ui-copy-w);
  height: 100%;
}
.oil-art__copy > * { position: absolute; margin: 0; left: 0; }
/* Everything in this column moved down 12% from the printed baselines, which
   puts the panel's top at 21.13%.

   This has been three numbers, and each one was right for the card it was
   measured against. Flush with the widget at 19.73% sat low while the card was
   tall enough to reach the artboard's bottom edge; 16.5% was right for that
   card and left this one - a third shorter - riding high with a quarter of the
   artboard empty underneath it.

   So the target stopped being either top edge and became the CENTRES. At 1440
   the widget runs 19.73% to 78.3% and centres at 49.03%; this card is 58.9% of
   the height, so a top at 20.13% centres it at 49.6%. Two blocks of different
   heights read as level when their middles agree, not their tops.

   The panel sits 9.56cqw, or 19.12% of the height, above the list, so a list
   at 35.62% puts its top at 16.5%.

   The old numbers were the printed baselines - eyebrow 12.63%, heading 18.60%,
   list 27.62% - traced off the mockup so the live copy would land on the
   printed copy and hide it. The plate is composited now and carries no printed
   copy, so there is nothing left to trace and the column is free to sit where
   the composition wants it. */
.oil-art__copy .eyebrow { top: 24.63%; font-size: clamp(11px, .78cqw, 18px); }
.oil-art__copy h2 {
  top: 30.60%;
  /* 3.21cqw, set from the printed line's measured WIDTH rather than its cap
     height. Sizing by height gave 2.82cqw, which rendered 25.78% of the
     artboard against the printed 29.31% - close enough to look right and wide
     enough of the mark to leave the tail of "баланс" showing beside it. The
     plate's serif tracks wider than ours at the same nominal size, the same
     way it does on the flavour plates. */
  font-size: clamp(24px, 2.42cqw, 58px);
  line-height: 1.14;
  width: max-content;
  max-width: none;
}
.oil-art__list {
  top: 39.62%;
  /* 75% of the copy block, not all of it.

     The block's width is set by the HEADING, which runs to 33% of the plate.
     The printed LIST stops at 25.7%. Left at full width the live lines ran to
     31%, wrapped at different words than the printed ones underneath - so the
     printed wrap points showed in the gaps - and spilled over the cannabis
     leaf that begins at 27%. 21.98/29.31 is the printed list's own measure. */
  width: 75%;
  list-style: none; padding: 0;
  /* .85em, not .62. The nine rendered lines have to span the same 44.53% of
     the plate that the printed nine do, and at .62 they spanned 40.72% - so
     the printed seventh line sat below the live one in plain view. Measured
     the printed seventh line sat below the live one in plain view.

     This chased an exact trace of the printed list for several passes - .62,
     .85, .94, 1em - and landing it did not stop the printed wrap points from
     showing in the gaps, because the live type does not break where the plate
     does. The veil above covers the block outright instead, which is what
     makes that chase unnecessary; this spacing is now set for reading. */
  display: grid; gap: .8em;
}

/* The panel the copy sits on, matching the hero's.

   Drawn from the LIST rather than as its own positioned box, which looks
   perverse until the other way is tried. A fixed percentage box cannot hug
   this text: the list is width-constrained, so it wraps to more lines as the
   column narrows - nine rendered lines at 1440, fewer at 1920 - and its bottom
   travels several percent of the artboard between them. Pinned to a fixed
   percentage the card looked right at one width and carried 102px of empty
   tail at the other. Hung off the list it holds 34-45px at both.

   The offsets are in cqw because the artboard's ratio is exactly 2, so a
   percentage of its width is half a percentage of its height and one unit says
   both. Reading out from the list's own box:

     left    list x 3.72%, panel 1.92%              ->  -1.80cqw
     right   heading ends 25.84%, panel 27.64%      ->  -1.94cqw
     top     list top, panel 18.49% of height above ->  -9.25cqw
     bottom  3.6% of the height below the last line ->  -1.80cqw

   The right offset is set by the HEADING, not the list. The list's longest
   line stops at 22.78% and the heading runs to 25.84%, so measuring the card
   off the list would cut the heading's tail off.

   This hug was briefly wrong and is right again. It went in over the old
   plate, which carried printed copy down its left third and therefore an
   opaque veil to hide it - and the veil ran lower than the hugged panel did at
   wide viewports, leaving 88px of flat ink below the card as a black block.
   The ink had always been there; the card is what turned it from ground into a
   box. Then clean assets arrived, the veil went, and hugging became correct
   again - with the artwork showing through the glass instead of flat ink. */
.oil-art__list::before {
  content: '';
  position: absolute;
  z-index: -1;
  left: -1.80cqw;
  right: -1.94cqw;
  top: -9.25cqw;
  bottom: -1.80cqw;
  border-radius: clamp(18px, 1.6vw, 28px);
  background: rgb(var(--ink-rgb) / .55);
  border: 1px solid rgb(var(--bone-rgb) / .07);
  -webkit-backdrop-filter: blur(14px) saturate(.9);
  backdrop-filter: blur(14px) saturate(.9);
  pointer-events: none;
}
.oil-art__list li {
  position: relative;
  padding-left: 1.75em;
  color: rgb(var(--bone-rgb) / .82);
  /* 13px floor, not 9. The artboard branch runs down to about 1025px, and
     at 1.00cqw alone that put the list at 11.1px - small enough that the card
     stopped being copy and started being texture. The floor costs
     proportionality below roughly 1300px, where the list grows a little
     relative to the plate; the panel hangs off the list's own box, so it grows
     with it rather than cutting it off. */
  font-size: clamp(13px, 1.00cqw, 24px);
  line-height: 1.45;
}
/* The ring and the tick are two elements. Drawing both with gradients on one
   element gave an X: two diagonals of equal weight meeting in the middle read
   as a cross, and a tick is two strokes of different length meeting at an
   angle - which a rotated corner border gives for free. */
.oil-art__list li::before {
  content: '';
  position: absolute; left: 0; top: .12em;
  width: 1.15em; height: 1.15em;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--accent) 48%, transparent);
}
.oil-art__list li::after {
  content: '';
  position: absolute;
  left: .42em; top: .38em;
  width: .26em; height: .5em;
  border: solid var(--accent);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(42deg);
}
@supports not (color: color-mix(in srgb, red, blue)) {
  .oil-art__list li::before { border-color: var(--gold-deep); }
}

.oil-art__widget {
  position: absolute; z-index: 5;
  left: var(--ui-wid-x);
  top: var(--ui-wid-y);
  width: var(--ui-wid-w);
  min-height: var(--ui-wid-h);
}

@media (max-width: 1024px) {
  /* A window onto the plate, and the overlays stack below it - the same answer
     as the flavour band, for the same reason: proportion survives scaling but
     usability does not, and at 375px the widget's type would be illegible.

     Window is x 27..70.5%, y 0..78%: right of the printed copy (ends 25.7%),
     left of the printed widget (starts 70.57%). The oil plate has no printed
     selector, so unlike the flavour band there is no bottom bound to respect
     and the window can run deeper.

       scene width 230%   = 100/43.5
       left -62%          = -(27/43.5)
       outer ratio 1.115  = (0.435*1774)/(0.78*887)
  */
  /* Same correction as the flavour band above, and for the same reason: the
     artboard held the window's ratio while its children flowed below it, so
     the clip cut the copy short and took the whole widget - the oil's own
     add button included - off the page. The window moves onto the scene,
     which reserves its height in flow. */
  .oil-art {
    aspect-ratio: auto;
    display: block;
    overflow: visible;
    /* Climbs into the gap above it. The band before this one ends with its
       own padding, and stacked there is nothing in that padding but black -
       on the desktop the two plates overlap and there is no gap to fill. Not
       the full 32px: a few pixels are left so the plate's box never reaches
       the buy card and starts painting over its rounded corner. */
    margin-top: -24px;
  }
  .oil-art__scene {
    position: relative;
    inset: auto;
    width: 100%;
    aspect-ratio: 1.115;
    overflow: hidden;
    border-radius: inherit;
    /* Emerges from the page instead of starting on a line.

       Stacked, each plate is a rectangle of artwork butted against flat black
       - the cartridge section ends on a card, then a gap, then the oil plate
       begins at full strength on its top edge, and the join reads as a black
       band with a hard edge under it. The desktop closes the same seam by
       overlapping its plates; there is no overlap here, so the plate fades
       in.

       6%, not the 10% this started at. The fade is itself dark, so a long one
       adds to the very gap it is meant to disguise: 44px of ramp on top of
       32px of band padding was 76px of near-black between the buy card and
       the bottle. 6% is 26px and the bottle does not begin until 16.6%. */
    -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 6%);
    mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, #000 6%);
  }
  .oil-art__scene img {
    inset: auto;
    left: -62%;
    top: 0;
    width: 230%;
    /* The plate is deliberately wider than its window. Without this the
       global img { max-width: 100% } clamps it straight back to the window's
       width and the crop silently becomes the whole plate, 140px tall. */
    max-width: none;
    height: auto;
    aspect-ratio: 1774 / 887;
  }
  /* Both go. Stacked, the copy is in normal flow on the page's own ground -
     there is no plate behind it to be lifted off, and no printed copy to
     hide. */
  .oil-art__list::before { display: none; }

  .oil-art__copy,
  .oil-art__copy > *,
  .oil-art__widget {
    position: static;
    width: auto;
    left: auto; top: auto;
  }
  /* relative, not static, for the two that something is positioned against.

     .flav__glow is absolute with inset 0 and belongs to the widget card.
     Static gave it no positioned ancestor to attach to, so it fell through to
     .oil-art and stretched over the whole band - 1192px of radial gradient
     lying across the copy at z-index 1, with the copy's own z-index 3
     discarded by the same `static`. It is transparent at rest, which is the
     only reason this was not obvious.

     Same fix, same reason, as the flavour artboard above. */
  .oil-art__copy,
  .oil-art__widget { position: relative; }
  .oil-art__copy { height: auto; margin: var(--s6) var(--s5) 0; }
  .oil-art__copy .eyebrow { margin-bottom: .6em; font-size: 12px; }
  .oil-art__copy h2 { font-size: clamp(26px, 5.4vw, 40px); width: auto; margin-bottom: .8em; }
  .oil-art__list { gap: .7em; }
  .oil-art__list li { font-size: 15px; }
  /* Air under the claims before the badge.

     On desktop every child of .oil-art__copy is absolutely positioned, so the
     column carries `margin: 0` and the badge is placed by a percentage. In
     this branch they all drop back into normal flow and that zero margin came
     with them - leaving the badge sitting flush against "Лабораторно тестван
     продукт", 0px between them, so the claim and the seal read as one run-on
     line. The flavour section's identical badge gets 17px from the lede above
     it; this matches that rather than inventing a second spacing. */
  .oil-art__copy .oil-art__tag { margin-top: var(--s3); }
  .oil-art__widget { margin: var(--s5) var(--s5) 0; min-height: 0; }
}

/* ==========================================================================
   WHERE THE TWO BANDS MEET

   Both plates end on a straight horizontal line against the page's black, and
   two straight lines with a 96px gutter between them is exactly the "here is
   another unrelated section" the brief asks the page not to do. The cartridge
   should become the oil, not be followed by it.

   Three things, and none of them touches the overlays:

     the gutter goes      the bands butt against each other
     they overlap         the oil rides up over the tail of the flavour plate
     the edges dissolve   each plate is feathered top and bottom

   The mask is on the SCENE, not on the artboard. Masking the artboard would
   take the live copy, the selector and the widget down with it, and they have
   to stay crisp - it is the photography that should fade, not the interface
   laid over it.

   4% and 5%, not more: the cartridge's mouthpiece reaches 5.3% down the
   flavour plate and the oil bottle's cap 6.8% down its own, so a deeper
   feather would start dissolving the products themselves. The plates are
   near-black at those edges anyway, so a shallow feather is all it takes.

   Desktop only. Below 1024 the artboards are windows with the copy stacked
   underneath them, so there is no plate-to-plate seam to hide, and the mask
   would land outside the visible window in any case.
   ========================================================================== */
@media (min-width: 1025px) {
  #vkusove { padding-bottom: 0; }
  #maslo   { padding-top: 0; }

  /* Percentage margins resolve against WIDTH, which is what is wanted here:
     the overlap should scale with the composition, not with the viewport's
     height. -3% is about 58px at 1920. */
  .oil-art { margin-top: -3%; }

  .flav-art__scene {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 4%, #000 96%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 4%, #000 96%, transparent 100%);
  }
  .oil-art__scene {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
  }

  /* The veils are positioned against the artboard, so they would otherwise
     run past the feathered edge of the plate they are covering and read as
     two dark thumbprints on the page's background. */
  .flav-art__veil {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
  }
}

/* ------------------------------------------------------- line item -- */
/* Each product is its own panel, built from the same surface, hairline and
   radius as the flavour stage so the cart reads as part of the same shop. */
.li {
  display: grid; grid-template-columns: 88px minmax(0, 1fr) auto;
  gap: var(--s5); align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s5);
  transition: border-color var(--dur) var(--ease);
}
.li:hover { border-color: color-mix(in srgb, var(--accent) 26%, var(--line)); }
.li.is-off { border-color: color-mix(in srgb, var(--accent) 22%, var(--line)); }
.li__img { width: 88px; height: 88px; object-fit: cover; border-radius: var(--radius-sm); }
.li__n { font-weight: 500; font-size: 17px; line-height: 1.3; }
.li__p { font-family: var(--mono); font-size: 13px; color: var(--muted); margin-top: 5px; }



.li__save {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 8px; padding: 5px 12px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent); font-size: 13px; white-space: nowrap;
}

/* ------------------------------------------------------ gift line -- */
/* The unit a rule gave away.

   Deliberately NOT another .li. A full product row says "a second thing you
   bought", which is the one thing this is not, and at 120px tall it competed
   with the line that earned it. This is a strip: a third of the height, one
   line of text, and a dashed edge - the convention for something granted
   rather than picked - so the eye reads the pair as one purchase with a
   bonus attached, and reads the bonus as an offer rather than a charge.

   It is pulled up into the list's own gap so it hangs off the row above
   instead of standing beside it, and it keeps that row's full width: an
   indent made it read as a footnote to the purchase, when it is part of it. */
.gift {
  display: grid;
  grid-template-columns: 34px auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  margin-top: calc(var(--s3) * -1 + 3px);
  padding: 7px 12px 7px 9px;
  border: 1px dashed color-mix(in srgb, var(--accent) 42%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
}
.gift__img {
  width: 34px; height: 34px; object-fit: cover;
  border-radius: 6px; opacity: .85;
}
/* The one loud thing on the strip: small, but the only uppercase in the
   cart, which is what makes it read as a label and not as a product. */
.gift__tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.gift__tag svg { flex: none; }
.gift__mid {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
  font-size: 13px; color: var(--muted);
}
.gift__n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Never allowed to shrink or wrap: on a rule giving two away, the count is
   the difference between one free cartridge and two. */
.gift__q { flex: none; font-family: var(--mono); }
.gift__q b { font-weight: 600; color: var(--bone); }
.gift__t {
  font-family: var(--mono); font-size: 14px; font-weight: 600;
  color: var(--accent); white-space: nowrap;
}

/* Narrow drawer: the product name is the first thing worth dropping. The tag
   and the price are the whole message - what it is, and that it costs
   nothing - and both survive. */
@media (max-width: 420px) {
  .gift { padding-right: 9px; }
  /* The flavour goes, the count stays. The row above already names the
     product; nothing else on the strip says how many are free. */
  .gift__n { display: none; }
}

/* ---------------------------------------------------- totals card -- */
.sumcard {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s4);
}
.tot { display: flex; justify-content: space-between; gap: var(--s4); margin-bottom: var(--s2); font-size: 15px; }
.tot span:last-child { font-family: var(--mono); }
.tot--sub { color: var(--muted); }
.tot--big {
  font-size: 19px; font-weight: 600;
  margin-top: var(--s4); margin-bottom: 0;
  padding-top: var(--s4); border-top: 1px solid var(--line);
  align-items: baseline;
}
.tot--muted { color: var(--muted); font-size: 14px; }
.tot__alt { font: inherit; }        /* the lev beside the euro, as one string - the phone restyles it */

/* ------------------------------------------------------------- forms -- */
.form { display: grid; gap: var(--s5); max-width: 760px; margin-top: var(--s6); }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }

/* The label is written AFTER the input now, because the floating-label rule
   on the checkout needs the input as its previous sibling - CSS can look
   forwards from an element but never back. A flex column with the label
   ordered first restores the reading order everywhere the label is still a
   heading above its box; on the checkout's first step the label is taken out
   of flow entirely, so the order there is moot. */
/* relative so the city's suggestion list has something to hang from at every
   width - the phone branch already made it relative for the floating label. */
.field { position: relative; display: flex; flex-direction: column; }
.field label { order: -1; }

.field label { display: block; font-size: 14px; margin-bottom: var(--s2); font-weight: 500; }

/* The data note. Styled here rather than only in the phone branch, because
   the copy is worth reading on any screen and an unstyled paragraph with a
   loose SVG in it is not a design decision. */
.fieldnote {
  display: flex; align-items: center; gap: var(--s3);
  margin: var(--s2) 0 0;
  font-size: 13px; line-height: 1.45; color: var(--muted);
}
.fieldnote__i { display: grid; place-items: center; flex: none; color: var(--accent); }
/* The link was its own grid item, which put it on a line of its own. It is
   inside the sentence's span now, so it wraps with the words - and a shade
   smaller, because it is an aside to a line that is already an aside. */
.fieldnote__t { min-width: 0; }
.fieldnote a { color: var(--accent); font-size: .92em; white-space: nowrap; }
.field .hint { display: block; font-size: 13px; color: var(--muted); font-weight: 400; margin-top: 2px; }
.field input, .field select, .field textarea {
  width: 100%; min-height: 48px; padding: 12px var(--s4);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--bone);
  transition: border-color .18s;
}
.field textarea { min-height: 96px; resize: vertical; }

/* What the browser does to a field it has filled for you.

   Chrome paints its own near-white background and near-black text over
   whatever the page asked for, so a remembered name or email arrived as a
   white slab in a dark form. The background cannot be set directly - it is a
   UA style with its own priority - but an inset shadow big enough to cover
   the box is drawn over it, and -webkit-text-fill-color outranks color for
   the text. The long transition is the usual guard against Chrome repainting
   the yellow a moment after load. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-text-fill-color: var(--bone);
  -webkit-box-shadow: 0 0 0 1000px var(--surface-2) inset;
  box-shadow: 0 0 0 1000px var(--surface-2) inset;
  caret-color: var(--accent);
  transition: background-color 9999s ease-in-out 0s;
}

/* A placeholder is a hint, not a value. Without this it inherits the
   browser's own grey, which reads as filled-in text on a dark field. */
.field input::placeholder, .field textarea::placeholder { color: var(--muted); opacity: 1; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--muted); }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); outline: none; }
.field select:disabled { opacity: .5; cursor: not-allowed; }

/* Short office lists do not get a search box. .pick__list tops out at 300px
   against rows of about 44, so up to seven offices are on screen at once and
   there is nothing to search FOR - the field would only be one more thing
   between the click and the answer. checkout.js sets .is-short.

   This lived in the phone branch, which is why a village with three Econt
   offices still put a search field over them on a desktop. */
.pick.is-short .pick__search { display: none; }

/* The resting field, on a wide screen.

   --line is #1F2529 against a #151A1D fill: about one step of lightness
   between them, which at 1px is not a border anyone sees. What was left was
   the four rounded corners, where the arc crossed the darker page behind and
   caught just enough contrast to register - so the field read as an unlidded
   box with marks at its edges rather than as a control with an outline.

   The phone branch never had the problem: there the field carries a floating
   label and a focus ring, and the shape is legible without leaning on the
   rim. This is the desktop's own answer, and it stops at the resting state -
   hover and focus already had enough contrast to say what they were. */
@media (min-width: 941px) {
  .field input,
  .field select,
  .field textarea {
    border-color: color-mix(in srgb, var(--muted) 30%, var(--line));
  }
}

/* Errors sit next to the field they describe, never only at the top. */
/* .pick__btn too. The office's <select> is display:none once the picker has
   replaced it, so a red border on the select was a red border on nothing -
   the field showed its message with the control beside it looking untouched. */
.field.has-error input,
.field.has-error select,
.field.has-error textarea,
.field.has-error .pick__btn { border-color: var(--warn); }
.err { display: none; color: var(--warn); font-size: 13px; margin-top: var(--s2); }
.field.has-error .err { display: block; }

.seg { display: flex; gap: var(--s3); flex-wrap: wrap; }
.seg button {
  flex: 1 1 190px; min-height: 52px; padding: var(--s3) var(--s4);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--muted); cursor: pointer;
  text-align: left; transition: border-color .2s, color .2s;
  display: flex; align-items: center; gap: var(--s3);
}
/* An inline <svg> with a viewBox and no width or height is 300x150 by the
   spec's own default, and a flex item that size dragged the two delivery
   buttons to about a fifth of the screen each. The phone branch happened to
   set a size and so never showed it; nothing else did. Sized here, once, for
   every width. */
.seg button svg { width: 24px; height: 24px; flex: none; color: var(--muted); }
.seg button[aria-pressed="true"] svg { color: var(--accent); }
.seg button b { color: var(--bone); font-weight: 600; font-size: 15px; }
.seg button small { font-family: var(--mono); font-size: 12px; }
.seg button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }

.formnote { font-size: 13px; color: var(--muted); }

/* .done used to be declared here - a centred block with an .is-on switch,
   left over from a confirmation screen that no page has rendered for some
   time. The class name is now the checkout's order-confirmed screen and the
   rules live with the rest of that page, at ORDER CONFIRMED near the end of
   this file. Removed rather than left: a `display: none` on .done would have
   hidden the new screen outright. */

/* ------------------------------------------------------------ footer -- */
/* Built to the supplied design: the gold artwork behind, the mark and the
   promise on the left, three columns of links, and the legal line under a
   rule. It sits on the same #0A1220 field the two sections above it use, so
   the bottom of the page is one surface and the footer has no top edge of its
   own to announce it. */
.ftr {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink);
  border-top: 1px solid color-mix(in srgb, var(--gold) 12%, transparent);
  padding-block: var(--s7) var(--s5);
  margin-top: 0;
  color: var(--muted);
  font-size: 14px;
}

.ftr__in { position: relative; }

.ftr__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: var(--s6) var(--s6);
  align-items: start;
}

/* ------------------------------------------------------------- brand -- */
.ftr__brand { max-width: 42ch; }
.ftr__mark {
  display: flex; align-items: center; gap: var(--s3);
  margin: 0 0 var(--s4);
}
/* The mark, on its transparent ground: nothing to blend away any more. */
.ftr__mark img { width: 56px; height: 56px; }
.ftr__word {
  font-family: var(--display); font-size: 30px; font-weight: 600;
  letter-spacing: .06em; color: var(--bone);
}
.ftr__claim { color: rgb(var(--bone-rgb) / .78); line-height: 1.6; margin: 0 0 var(--s5); max-width: 34ch; }
/* The legal block is required, not read. Quiet enough to stay out of the way,
   bright enough to pass a contrast check at 13px. */
.ftr__legal-co { font-size: 12.5px; line-height: 1.75; color: rgb(var(--bone-rgb) / .5); margin: 0; }

/* The company block, as a list of marked facts rather than a paragraph. */
.ftr__co {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 7px;
  font-size: 12.5px; line-height: 1.45;
  color: rgb(var(--bone-rgb) / .5);
}
.ftr__co li { display: flex; align-items: flex-start; gap: 9px; }

/* One size and one weight for every footer mark, so a column of them reads as
   a set. Gold at half strength: present enough to guide the eye down the
   list, quiet enough that the words stay the brightest thing on this ground -
   which is the whole premise of the footer having no artwork. */
.ftr__ico {
  flex: none;
  display: inline-grid; place-items: center;
  width: 15px; height: 15px;
  color: color-mix(in srgb, var(--gold) 62%, transparent);
}
.ftr__ico svg { width: 15px; height: 15px; }
/* Nudged down by a hair: these sit beside text with a taller line box, and
   optically centred beats geometrically centred next to a cap height. */
.ftr__co .ftr__ico { margin-top: 2px; }
.ftr__col a:hover .ftr__ico { color: var(--gold); }

/* ----------------------------------------------------------- columns -- */
/* The footer carries no artwork on purpose - one flat ground, so the only
   things with any brightness on it are the words. That is what lets the
   links be read as links and the hierarchy be read at a glance. */
.ftr__col h3 {
  font-family: var(--body); font-size: 11.5px; font-weight: 600;
  color: var(--gold); text-transform: uppercase; letter-spacing: .16em;
  margin-bottom: var(--s3);
}
.ftr__col ul { list-style: none; padding: 0; margin: 0; display: grid; }

/* The link, and the rule under it.

   The underline is a pseudo-element scaled from the left rather than
   text-decoration, so it can grow on hover instead of appearing all at once,
   and so it sits clear of the descenders. Resting colour is .78 rather than
   .7: on this ground .7 was legible but tiring to read down a column. */
.ftr__col a {
  position: relative;
  color: rgb(var(--bone-rgb) / .78);
  text-decoration: none;
  display: inline-flex; align-items: center; gap: 9px;
  min-height: 38px;
  transition: color .2s var(--ease);
}
/* Starts after the mark. The rule is an underline for the label; running it
   beneath the icon as well made it read as a box around the pair. */
.ftr__col a::after {
  content: '';
  position: absolute; left: 24px; right: 0; bottom: 9px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .26s var(--ease);
}
.ftr__col a:hover,
.ftr__col a:focus-visible { color: var(--gold); }
.ftr__col a:hover::after,
.ftr__col a:focus-visible::after { transform: scaleX(1); }
.ftr__col a:focus-visible {
  outline: none;
  /* Drawn on the ground rather than round the text, so a 38px touch target
     does not put a box the height of the gap round one word. */
  box-shadow: 0 0 0 2px var(--gold);
  border-radius: 4px;
}

/* ------------------------------------------------------------- legal -- */
.ftr__legal {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid color-mix(in srgb, var(--gold) 14%, transparent);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s4); flex-wrap: wrap;
  font-size: 13px; color: rgb(var(--bone-rgb) / .45);
}
.ftr__age {
  border: 1px solid color-mix(in srgb, var(--gold) 30%, transparent);
  border-radius: 999px; padding: 4px 12px;
  font-family: var(--mono); font-size: 12px; color: var(--gold);
}

@media (max-width: 900px) {
  .ftr__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s6) var(--s5); }
  .ftr__brand { grid-column: 1 / -1; max-width: none; }
}
@media (max-width: 520px) {
  /* Two columns, still.

     One column put seven links, a phone number and an address in a single
     835px ribbon that took four thumb-flicks to pass. The groups are two and
     three items each and their longest word is "Поверителност" - about 110px
     at this size - so a 160px column holds them with room to spare. The brand
     block keeps the full width above them, because the claim and the company
     details are sentences, not a list.

     What this buys is roughly 140px, and a footer that reads as a footer
     rather than as more page. */
  .ftr__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s5) var(--s4);
  }
  .ftr__brand { grid-column: 1 / -1; max-width: none; }
  .ftr__legal { justify-content: flex-start; }
}

/* ------------------------------------------------------------ reveal -- */
/* Content is visible by default and only hidden once the reveal system has
   actually taken charge of it.
   It used to be hidden unconditionally and made visible by JavaScript, which
   meant any error anywhere earlier in the boot sequence left every section
   below the hero at opacity 0: the whole page below the fold went black. A
   scroll animation is an enhancement, and an enhancement must never be the
   only thing standing between the reader and the content. */
.rise { opacity: 1; transform: none; }
html.reveal-on .rise {
  opacity: 0; transform: translateY(12px);
  /* .36s, down from .6s, and 12px down from 18px. Together with the observer
     now firing before the section reaches the viewport (see anim.js), a
     section is finished appearing at about the moment it arrives instead of
     spending half a second resolving while you read it. The reveal is still
     there; it has stopped being something you wait for. */
  transition: opacity .36s var(--ease), transform .36s var(--ease);
  /* Tells the compositor which two properties are about to move, so the fade
     is promoted to its own layer rather than repainting the section. Dropped
     the instant the class lands, so nothing holds a layer it is not using. */
  will-change: opacity, transform;
}
html.reveal-on .rise.is-in { will-change: auto; }
html.reveal-on .rise.is-in { opacity: 1; transform: none; }

/* -------------------------------------------------------- responsive -- */
@media (max-width: 940px) {
  /* Stacked: photograph on top, copy beneath. Still no padding on the card
     itself - the art stays edge to edge the way it is on the wide layout, and
     the panel keeps carrying its own. */
  .flav__stage { grid-template-columns: 1fr; padding: 0; }
  /* #stage's own grid-template-columns (an id rule, for specificity over the
     shared .flav__stage class - see where it's set) would otherwise outrank
     the single-column rule above at every width, portrait stage included. */
  #stage { grid-template-columns: 1fr; }

  /* The height has to come back from the ratio here.

     Side by side, the picture is as tall as the row, and the row is as tall
     as the copy - so height:100% is all it needs. Stacked, the picture IS a
     row, and there is nothing above it to inherit a height from: with the
     ratio removed for the wide layout it resolved to zero and the photograph
     vanished from the card entirely on every phone. Both selectors are ids or
     have to beat one, so the ratio is restated rather than inherited. */
  .flav__stage .flav__viz,
  #stage .flav__viz,
  .flav__media, .flav__media > .flav__viz { height: auto; }

  /* min-height was there to hold two short columns level with each other.
     Stacked there is nothing to be level with, and 560px of floor under a
     card that is already twice that tall only adds dead space. */
  .flav__stage { min-height: 0; }

  .oil { grid-template-columns: 1fr; }
  /* .ftr__grid used to be forced to a single column here. It was the LAST of
     three rules touching this grid - after the 2-column rule at 900 and the
     1-column rule at 520 - so it won at every width below 940 and quietly
     cancelled both. The footer stacked into one tall ribbon on tablets as
     well as phones, which is neither breakpoint's intention. The two rules
     that do have intentions are left to express them. */
}

@media (max-width: 620px) {
  /* 48px of padding inside a 343px-wide card leaves the copy about 240px to
     live in. */
  .flav__panel { padding: var(--s5); }
}
/* The basket row, narrow.

   Three columns - 88px of picture, the detail, then price and stepper - left
   the middle column about 50px wide on a 375px screen. .li__save is a
   nowrap pill reading "Спестяваш 4,02 €", so it did not wrap, it simply ran
   on under the stepper and the two drew over each other.

   Two columns instead, with price and stepper dropped onto a row of their own
   underneath. The detail column gets the width it needs, the stepper keeps
   its 44px targets, and nothing overlaps. */
@media (max-width: 620px) {
  /* Tighter, so two rows and the start of a third fit above the totals.

     At 24px of padding and a 24px gap a row came to 193px, and the list only
     ever had room for one of them before the order button started at the
     bottom of the drawer. Nothing here is removed - the row keeps its picture,
     its name, its struck price, its saving and a 40px stepper - it is only
     the air between them that gives, and 12px is still air. */
  .li {
    grid-template-columns: 58px minmax(0, 1fr);
    gap: 10px;
    padding: 10px;
  }
  .li__n { font-size: 16px; }
  .li .li__save { margin-top: 5px; padding: 3px 9px; font-size: 11px; }

  /* The list's own frame, and the totals block under it. Same reasoning: the
     drawer is 812px tall and every millimetre spent on padding is a
     millimetre the basket cannot show. */
  /* Compact throughout, because vertical space in a drawer is the only
     currency it has. These no longer exist to hit a height target - the
     column scrolls now - but a phone still shows more of the basket per
     screenful when the padding is honest about the width it is on.

     .cart__foot .cart__cont, not .cart__cont: the base rule for the link sits
     further down the file and was quietly winning on document order - the
     margin here said 8 and the browser reported 12. The same two-class trap
     as .cart__side .dsc and .li .li__right. */
  .cart__body { padding: 12px; }
  .cart__foot { padding: 10px 12px 12px; }
  .cart__foot .sumcard { padding: 9px 12px; margin-bottom: 6px; }
  .cart__foot .cart__cont { margin-top: 6px; padding: 7px; }
  .li__img { width: 58px; height: 58px; }
  /* .li .li__right, not .li__right: the base rules for the right column and
     the price live further down this file, so at equal specificity they win
     on document order and the column kept its stacked grid. Two classes
     settle it by weight - the same trap .band.band--formula documents. */
  .li .li__right {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s4);
  }
  .li .li__t { text-align: left; }
  .li .li__save { white-space: normal; }
  /* 40px squares, with the TAP TARGET taken to 48 by padding rather than by
     size. 46 was tried and it was too much: the pill stood 58px tall beside a
     72px product picture and a 15px price, so the row read as a stepper with
     a product attached to it rather than the other way round.
     Padding is what separates the two things that were being conflated. The
     drawn button is 40px, which sits correctly against the picture and the
     type; the box that answers a thumb is 40 + 4 top and bottom = 48, past
     what both Apple and Google ask for. box-sizing keeps the drawn size
     honest while the padding grows the hit area outward.
     The buttons sit at opposite ends of the pill with the number between
     them, so the two enlarged boxes come nowhere near touching. */
  .li .stepper__b {
    width: 40px; height: 40px;
    /* 4px on every side, so the box that answers a thumb is 48x48 while the
       drawn button stays 40x40. The two buttons grow inward by 4px each and
       the number slot between them is 36 wide, so 28px of it is still nobody's
       target - they come nowhere near overlapping. */
    padding: 4px;
    box-sizing: content-box;
  }
  .li .stepper__b svg { width: 16px; height: 16px; }
  .li .stepper__v { min-width: 36px; height: 24px; font-size: 15px; }
  /* The pill gives up its own padding, because the buttons now carry it.
     Left on, the container simply grew by the same amount the buttons did and
     the whole control stayed as large as it was - which was the complaint.
     The pill is 132x50 now against 144x58 before, standing beside a 58px
     product picture instead of towering over it. */
  .li .stepper { padding: 0; }

  /* The discounts panel, compacted until it does not need to scroll.

     A panel with its own scrollbar inside a drawer that already scrolls is
     two scrollbars in 375px, and it read as a bug rather than as a feature.
     The way out is not a bigger cap - that only takes the room back off the
     basket - it is to stop the panel spending so much of its height on air.

     Where the 279px went: 96 of it was padding, twice over, once on the
     column and once on the card inside it. Each rule then sat in its own
     bordered box with 12/16 padding and a 34px icon, and the rule's name
     wrapped to two lines at 14px. Nothing there is wrong on a desktop
     column; all of it is generous on a phone.

     Trimmed rather than removed: every rule keeps its icon, its full name -
     still wrapping, never truncated, because the name is the whole point -
     and its amount. Two earned discounts now come to about 100px of rows
     instead of 138, and the panel as a whole fits inside its cap without a
     scrollbar.

     Every selector below is scoped to .cart__side, and that is not decoration.
     The base .dsc rules live about 1800 lines further down this file, so a
     bare `.dsc { padding }` here loses to them on document order at equal
     specificity - which is exactly what happened on the first attempt: the
     column's own padding changed and nothing inside it did. Two classes
     settle it by weight. Same trap .li .li__right documents above. */
  /* 10 and 8, not 12 and 12. The worst case - an earned section and an
     unlocking one on screen together - came to 250 against a 236 box, and
     these three values are the 16px that closes it. Below this the cards stop
     reading as cards. */
  /* The drawer's own header, which was 93px: a 44px control and 48px of
     padding around a single word. On a 393px phone that is an eighth of the
     drawer spent on a title nobody needs to read twice. 12px of padding and a
     17px word bring it to about 60 while the close control keeps its full
     44px touch target - the padding shrinks, the target does not. */
  .cart__top { padding: 8px 12px; }
  .cart__top h2 { font-size: 17px; }

  .cart__side { padding: 10px; gap: 8px; }
  .cart__side .dsc { padding: 8px; }
  .cart__side .dsc__h { margin-bottom: var(--s2); }
  .cart__side .dsc__h h3 { font-size: 14px; }
  .cart__side .dsc__row { padding: 8px 10px; gap: var(--s2); }
  .cart__side .dsc__row + .dsc__row { margin-top: 6px; }
  .cart__side .dsc__ico { width: 28px; height: 28px; border-radius: 8px; }
  .cart__side .dsc__name { font-size: 13px; line-height: 1.35; }
  .cart__side .dsc__amt { font-size: 13px; }
  /* The "unlock more" tier carries a progress ring, so it gets the same
     treatment or it would tower over the earned rows beside it. */
  .cart__side .dsc__goal { padding: 10px; gap: 10px; }
  .cart__side .dsc__goal + .dsc__goal { margin-top: 6px; }
  .cart__side .dsc__ring { width: 40px; height: 40px; }
  /* The hint under the rule's name - "Още 2 броя от който и да е вкус" - wraps
     to two lines and was the tallest single thing in the panel. It is a hint,
     not a heading; 11px carries it. */
  .cart__side .dsc__gap { font-size: 11px; line-height: 1.35; }
  .cart__side .dsc__goaltxt { gap: 2px; }
  .cart__side .dsc__bar { margin-top: 3px; }
  .cart__side .dsc__worth { font-size: 12px; padding: 4px 8px; }

  /* The section labels, shrunk rather than dropped.

     Both sections can be on screen at once - one earned, one still to unlock -
     and at 16px with 16px beneath, the two headings alone were 52px of a panel
     that has about 209 to spend. They still say which half is yours; they just
     say it in the size a label should be. The gold-versus-progress colouring
     does most of that work anyway. */
  .cart__side .dsc__h { margin-bottom: 4px; }
  .cart__side .dsc__h h3 {
    font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--muted); font-weight: 600;
  }
  .cart__side .dsc--won .dsc__h h3 { color: var(--gold); }
  .cart__side .dsc__crown { width: 12px; height: 12px; }
}

@media (max-width: 620px) {
  /* 64px. With .band + .band { padding-top: 0 } that is the whole distance
     between one section and the next, and on a phone every one of those gaps
     is 64px of flat black between two pieces of artwork - the seams the
     desktop closes with overlapping plates. Halved. */
  .band { padding-block: var(--s6); }
  .wrap { padding-inline: var(--s4); }
  .hdr__nav { display: none; }
  .row2 { grid-template-columns: 1fr; }
  /* 76vh was here, and being the later rule it was quietly beating the
     100svh set for the full-bleed hero above. Same value, so the two agree
     now instead of one silently winning. */
  /* Both calls to action inside the first screen. At 375x812 the pair ended
     at 827 and 757 - the secondary one was cut by the fold, which on a hero
     that now promises to BE the screen reads as an accident. 24px comes off
     the gap and the panel's top padding, which is enough and is not enough to
     notice. */
  .hero { min-height: 100svh; }
  /* The artwork starts where the header ends. .hero__in carries 32px of top
     padding so the composition clears the bar on a desktop, where the hero is
     a two-column row and the picture is beside the copy rather than under the
     header. Stacked, that padding is a strip of page between the two, and on
     a hero that runs edge to edge everywhere else it reads as the one seam
     nobody closed. */
  .hero__in { gap: var(--s4); padding-top: 0; }
}

/* ---------------------------------------------------- reduced motion -- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  .rise { opacity: 1; transform: none; }
}

/* ==========================================================================
   Scroll-scrub hero
   The section is one screen, not a scroll track: the video inside it plays
   once, on its own, and the page below scrolls normally.

   This used to be a scroll-scrubbed film - first as the hero alone, later
   (briefly) doubling as the fixed backdrop for the whole page too. Both are
   gone. It read as two rules fighting over one film, and the owner asked
   for the archived, plain-playback hero back. Do not "helpfully"
   reintroduce scroll-driven seeking here - see initScrub in site.js for the
   reasoning behind the revert.
   ========================================================================== */



.scrub__poster,
















@media (max-width: 899px) {
  /* The take still plays, but there is no room to run three captions past
     it on a phone, so the opening one stays put for the whole clip. */





}
@media (prefers-reduced-motion: reduce) {


  /* The video is paused in JS as well; this keeps the still on top of it. */




}

/* ==========================================================================
   Swap cell.
   Office and address share ONE grid cell, so switching delivery method
   swaps them in place instead of pushing the layout around.
   ========================================================================== */
.swap { display: grid; }
.swap > [data-swap] { grid-area: 1 / 1; }
/* The inactive field keeps its box so the cell height never changes.
   inert already blocks focus and pointer events. */
.swap > [data-swap][inert] { visibility: hidden; }

/* ==========================================================================
   Cart drawer additions
   ========================================================================== */
.li__mid { min-width: 0; }
.li__right { display: grid; justify-items: end; gap: var(--s3); }
/* The cell always holds the same three spans. Whether a discount applies is
   carried by a class, not by swapping the markup, so a quantity change cannot
   change the cell's height or recolour the figure. */
.li__t { font-family: var(--mono); font-size: 14px; white-space: nowrap; }

/* Three different jobs, three different weights. The payable figure is the
   one you read, so it stays the brightest and largest whether or not a
   discount applied; the saving is the accent colour, because that is the part
   that is worth noticing. Colouring both of them alike made it impossible to
   tell at a glance which number was which. */
.li__t .li__now { font-size: 20px; font-weight: 600; color: var(--bone); }
.li__t.has-off .li__now { color: var(--bone); }
/* The price a line was, directly above the price it is.

   It used to sit in the middle column under "2 x 24,90 €" while the right
   column showed the net figure with a bare "−24,90 €" beneath it. Two things
   went wrong with that on a buy-two-get-one line, where the saving happens to
   equal the unit price: the net read as the price of ONE item rather than the
   charge for two, because the 49,80 it came down from was in a different
   column - and the minus under it read as a further deduction from a figure
   that had already had it taken off. 24,90 then −24,90 looks like nothing to
   pay.

   Before and after belong together. The struck total sits on top of the net
   in the same column, which is how a price reduction is written everywhere
   else, and the saving keeps its one home in the Спестяваш badge instead of
   being printed twice. */
.li__t .li__was {
  display: block;
  font-size: 13px;
  margin: 0 0 2px;
  opacity: .75;
}

/* While the server is re-pricing, the figures that depend on it are dimmed
   rather than blanked. Opacity only: nothing here may move the row. */
.li__t.is-stale .li__now,
.li__t.is-stale .li__was { opacity: .45; }
.li__t .li__now,
.li__t .li__was { transition: opacity .16s var(--ease); }

/* ==========================================================================
   Quantity stepper
   The digit track is a FIXED width. That is the whole trick: the number can
   change from 1 to 10 and nothing in the row, or in the cart, moves a pixel.
   ========================================================================== */
.stepper {
  display: inline-flex; align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px;
  user-select: none;
}
/* 36px, up from 30. These are the controls a shopper pokes at repeatedly -
   the only ones in the drawer they use more than once - and they were the
   smallest thing in it. The phone rule below takes them further again. */
.stepper__b {
  width: 36px; height: 36px; flex: none;
  display: grid; place-items: center;
  background: none; border: 0; border-radius: 999px;
  color: var(--muted); cursor: pointer;
  transition: background .16s, color .16s;
}
.stepper__b:hover { background: color-mix(in srgb, var(--bone) 10%, transparent); color: var(--bone); }
.stepper__b:active { background: color-mix(in srgb, var(--bone) 16%, transparent); }
.stepper__b.is-bin:hover { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.stepper__b svg { pointer-events: none; width: 15px; height: 15px; }

/* fixed box, clipped, so the outgoing and incoming digits share one slot */
.stepper__v {
  position: relative; display: block;
  width: 34px; height: 24px; flex: none;
  overflow: hidden;
  font-family: var(--mono); font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.stepper__v b {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-weight: 500; color: var(--bone);
  will-change: transform, opacity;
}

.cart__free { font-size: 13px; color: var(--accent); margin-bottom: var(--s3); min-height: 1.2em; }
.cart__cont {
  width: 100%; margin-top: var(--s3); padding: var(--s3);
  background: none; border: 0; color: var(--muted);
  font-size: 14px; cursor: pointer; min-height: 44px;
}
.cart__cont:hover { color: var(--bone); }
.btn--disabled { opacity: .4; pointer-events: none; }

/* ==========================================================================
   Checkout page
   ========================================================================== */
.co-page { background-image: none; }

/* The shop front's capsule and oil bands. On a desktop the products have
   pages of their own now (kapsuli.php, product.php?p=oil) and the hero's
   podiums lead there; the bands stay in the markup because the phone's
   app (app.js) is built from them, and only the phone shows them. */
@media (min-width: 941px) { .is-phone-only { display: none !important; } }

/* The formula page (formula.php): the band stands on its own under the
   header, so it no longer pulls itself up into a hero's fade, and it keeps
   a little air above and below. */
.formula-page .fband { margin-top: 0; }
.formula .band.band--formula { padding-top: clamp(20px, 2.4vw, 36px); }
.formula-page .plaque-band { padding-bottom: clamp(48px, 6vw, 96px); }
/* .co-back is gone. It was the grey text link in the top right of the
   checkout, the policy, the login and the account page - four pages that each
   drew it slightly differently - and it has been replaced everywhere by
   .backbtn, top left, drawn once by back_link() in config.php. */

.co-wrap { padding-block: var(--s7) var(--s9); }
.co { display: grid; grid-template-columns: 1fr 370px; gap: var(--s7); align-items: start; }
.co-h1 { font-size: clamp(30px, 4.6vw, 46px); margin-bottom: var(--s5); }

/* step rail */
/* The plumbing every branch needs: no list markers, no indent, and the
   circle's shared shape. The look that sits on top of it follows. */
.rail { list-style: none; padding: 0; margin: 0 0 var(--s7); }
.rail__i { display: flex; color: var(--muted); }
.rail__i span {
  flex: none; border-radius: 999px;
  display: grid; place-items: center;
  font-family: var(--mono);
  border: 1px solid var(--line); background: var(--surface-2);
}
.rail__i.is-done { cursor: pointer; }
.rail__i.is-done:hover { color: var(--bone); }

/* One design, every width. This was built for the phone branch and lived
   inside it, so the desktop checkout kept an older flat row: numbers and
   words side by side, no connector, and no way to see which steps were
   behind you. There was never a reason for the wide screen to have the
   lesser one. */
/* ------------------------------------------------------------ the rail */
/* Circle over label, joined by a line, rather than a row of number-and-word
   pairs. Three equal columns so the circles land on thirds however long the
   Bulgarian words turn out, and the line is drawn by each step towards the
   one before it - which means the run that lights up is always the run
   BEHIND the current step, with no rule needed for the last item. */
.rail {
  display: grid;
  --rail-dot: 42px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin-bottom: var(--s7);
  flex-wrap: nowrap;
}
.rail__i {
  position: relative;
  flex-direction: column;
  align-items: center;
  gap: var(--s3);
  padding: 0;
  font-size: 13px;
  text-align: center;
}
.rail__i + .rail__i::before {
  content: '';
  position: absolute;
  /* The circles' own centre line, derived rather than typed. --rail-dot is
     the circle's diameter; everything that has to meet its middle reads it
     from there, so nothing drifts when the desktop branch grows it. */
  top: calc(var(--rail-dot) / 2);
  right: 50%; left: -50%;
  height: 1px;
  background: var(--line);
}
.rail__i.is-on + .rail__i::before,
.rail__i.is-done + .rail__i::before {
  background: var(--accent);
  box-shadow: 0 0 10px -1px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* The tick rides the line, not the circle.

   Inside the circle it replaced the step's number, which is the one thing
   in there worth keeping - the rail is how you count where you are. On the
   leg behind you it says the same thing about the part of the journey it
   actually describes, and the numbers all stay.

   Centred on the join: the connector runs from -50% to 50% of this item, so
   its midpoint is this item's own left edge. The disc is opaque, so the
   line stops either side of it rather than running underneath. */
/* is-done only, not is-on. The leg is coloured as soon as it leads out of
   the step you are on, because that is progress reaching you - but a tick
   is a claim that the step behind it is finished, and the one you are
   standing on is not. */
.rail__i.is-done + .rail__i::after {
  content: '';
  position: absolute;
  top: calc(var(--rail-dot) / 2); left: 0;
  transform: translate(-50%, -50%);
  width: calc(var(--rail-dot) * .405); height: calc(var(--rail-dot) * .405);
  border-radius: 999px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2307090A' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center / 10px 10px no-repeat, var(--accent);
}

.rail__i span {
  /* Above the connector, and opaque. The line is drawn by the step's own
     ::before, which without this paints over its circle - and a transparent
     circle would let it through the middle anyway. The page's own ink, so
     the line appears to stop at each rim. */
  position: relative;
  z-index: 1;
  width: var(--rail-dot); height: var(--rail-dot);
  font-size: calc(var(--rail-dot) * .38);
  background: var(--ink);
  color: var(--muted);
  transition: color .2s var(--ease), border-color .2s var(--ease),
              box-shadow .2s var(--ease);
}

/* The current step is a ring with light behind it, not a filled disc. A
   filled circle is what the finished ones get on other sites; here nothing
   is finished yet, and the glow is the thing that says "you are here". */
.rail__i.is-on { color: var(--accent); }
.rail__i.is-on span {
  color: var(--bone);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 12%, transparent),
              0 0 20px -2px color-mix(in srgb, var(--accent) 65%, transparent);
}
/* A finished step keeps its number and takes the accent for its rim, so
   the row still reads 1 2 3 and the tick on the leg says which are behind
   you. */
.rail__i.is-done span { border-color: var(--accent); color: var(--accent); }
.rail__i.is-done { color: rgb(var(--bone-rgb) / .70); }

/* ------------------------------------------------ the rail, wide screens --
   Three equal thirds of a wide column put the circles a long way apart with
   the connector doing all the work, so the rail is capped and left-aligned
   under the heading. Everything else here is what a pointer earns that a
   finger does not: bigger discs, a hover the mouse can find, a focus ring for
   the keyboard, and states that move rather than swap.

   The circle grows with the viewport between the cap and the phone's size,
   which is why --rail-dot is a clamp rather than a number. */
@media (min-width: 941px) {
  .rail {
    --rail-dot: clamp(46px, 3.6vw, 58px);
    max-width: 600px;
    margin-bottom: var(--s7);
  }
  .rail__i { gap: var(--s4); font-size: 14px; }

  /* A disc with depth rather than a flat ring: a top-lit gradient, a hairline
     rim and a shadow under it, so the three read as objects on the page and
     the one you are on can lift off it. */
  .rail__i span {
    background:
      radial-gradient(120% 120% at 50% 0%,
        color-mix(in srgb, var(--bone) 7%, var(--surface-2)) 0%,
        var(--ink) 72%);
    box-shadow: 0 2px 10px -4px rgb(0 0 0 / .8);
    font-weight: 600;
    transition: color .22s var(--ease), border-color .22s var(--ease),
                box-shadow .22s var(--ease), transform .22s var(--ease),
                background .22s var(--ease);
  }

  /* Thicker legs on a wide rail: at 1px across 200px of gap the line was a
     scratch. Still one pixel of colour on the phone. */
  .rail__i + .rail__i::before { height: 2px; border-radius: 2px; }

  /* ---------------------------------------------------------- the states */
  /* Where you are: lifted, ringed and lit. The lift is what separates it
     from the two flat discs either side without changing any colour. */
  .rail__i.is-on span {
    transform: translateY(-2px);
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 12%, transparent),
                0 0 26px -3px color-mix(in srgb, var(--accent) 70%, transparent),
                0 8px 18px -8px rgb(0 0 0 / .9);
  }
  /* One slow breath, not a spinner. It says "this one is live" and stops
     mattering the moment you read it, which is why it is this quiet. */
  .rail__i.is-on span::after {
    content: '';
    position: absolute;
    inset: -5px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
    animation: rail-breathe 2.8s var(--ease) infinite;
    pointer-events: none;
  }

  /* Behind you, and clickable - the rail is how you get back to a step you
     have already filled in, and on a pointer that has to look like an offer.
     The whole item is the target, so the label lights with the disc. */
  .rail__i.is-done:hover span,
  .rail__i.is-done:focus-visible span {
    transform: translateY(-2px);
    border-color: var(--accent);
    color: var(--bone);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent),
                0 8px 18px -8px rgb(0 0 0 / .9);
  }
  .rail__i.is-done:active span { transform: translateY(0); }

  /* Keyboard gets the same answer as the mouse, on the item rather than in
     the browser's own outline, which a 58px disc would swallow. */
  .rail__i:focus-visible { outline: none; }
  .rail__i:focus-visible span {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--ink), 0 0 0 5px var(--accent);
  }

  /* Ahead of you: dimmed, and plainly not a button. */
  .rail__i:not(.is-on):not(.is-done) { color: rgb(var(--bone-rgb) / .38); }
  .rail__i:not(.is-on):not(.is-done) span { color: rgb(var(--bone-rgb) / .38); }

  .rail__i.is-done { color: rgb(var(--bone-rgb) / .74); }
  .rail__i.is-done:hover { color: var(--bone); }

  /* The label moves with its disc so the pair stays one object. */
  .rail__i { transition: color .22s var(--ease); }
}

@keyframes rail-breathe {
  0%, 100% { opacity: .55; transform: scale(1); }
  50%      { opacity: 0;   transform: scale(1.18); }
}

/* The lift and the breath are decoration. The colour states carry the whole
   message without them. */
@media (prefers-reduced-motion: reduce) {
  .rail__i span,
  .rail__i.is-on span,
  .rail__i.is-done:hover span { transition: none; transform: none; }
  .rail__i.is-on span::after { animation: none; }
}

.step-p { display: none; }
.step-p.is-on { display: grid; gap: var(--s5); }
.step-h { font-size: 20px; font-family: var(--body); font-weight: 600; margin-bottom: var(--s2); }
/* The step's two actions, weighted.

   Продължи is the one thing the step is for. On a wide screen it stands at
   the right, where a forward action is looked for; on a phone it is the
   full width of the card and the first thing under the last field, in the
   thumb's reach, and Enter on the keyboard presses it. Назад is a way
   out, not a choice to weigh against it: a quiet text button to the left,
   or beneath, never a second pill of the same size - two equal pills are
   a coin toss, and the wrong one gets pressed.

   The markup has the primary first, so Tab and a screen reader reach it
   first too; row-reverse puts it at the right on a desktop. */
.step-nav {
  display: flex; flex-direction: row-reverse; justify-content: space-between; align-items: center;
  gap: var(--s3); margin-top: var(--s2);
}
.step-nav .btn--primary { flex: 0 1 auto; min-width: 240px; }
/* .step-nav .btn: outranks .btn--apple.btn--ghost, which sets the same things */
.step-nav .btn.step-nav__back {
  flex: 0 0 auto;
  min-height: 48px;
  padding-inline: var(--s4);
  border-color: transparent;
  color: rgb(var(--bone-rgb) / .72);
  font-weight: 500;
}
.step-nav .btn.step-nav__back:hover { color: var(--bone); border-color: rgb(var(--bone-rgb) / .20); }
.step-nav__back .btn__chev { margin-left: -4px; opacity: .8; transition: transform .22s var(--ease); }
.step-nav__back:hover .btn__chev { transform: translateX(-2px); }
@media (max-width: 640px) {
  .step-nav { flex-direction: column; align-items: stretch; gap: var(--s2); }
  .step-nav .btn--primary { min-width: 0; width: 100%; }
  .step-nav .btn.step-nav__back { align-self: center; min-height: 44px; padding-inline: var(--s5); }
}

/* review */
.review { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s5); }
/* The review is a list of what you typed, one line each, with a mark instead
   of a word for what kind of thing it is.

   It was a two-column dl with a 168px column of labels - Име, Телефон, Имейл
   - which is nine words to read before reaching any of the answers, and each
   one says what its answer obviously is. The mark carries the kind; the eye
   goes to the value.

   A list, not a definition list, because there is no term left to define:
   the label lives in an .sr-only span inside each value, which is what keeps
   the rows meaningful to a screen reader that cannot see the drawing. */
.rv { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; font-size: 15px; }
.rv__r {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) 0;
}
.rv__r + .rv__r { border-top: 1px solid rgb(var(--bone-rgb) / .06); }
.rv__i { width: 20px; height: 20px; justify-self: center; color: var(--muted); }
.rv__v { min-width: 0; color: var(--bone); line-height: 1.4; overflow-wrap: anywhere; }
.co-edit {
  display: inline-block; margin-top: var(--s5); padding: 0;
  background: none; border: 0; color: var(--accent);
  font-size: 14px; cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px; min-height: 44px;
}

/* summary card */
.co-side { position: sticky; top: 86px; }
.co-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s5); }
.co-card h2 { font-size: 13px; font-family: var(--body); font-weight: 600; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); margin-bottom: var(--s4); }
.co-li { display: grid; grid-template-columns: 46px 1fr auto; gap: var(--s3); align-items: center; padding-block: var(--s3); border-bottom: 1px solid var(--line); }
.co-li:last-of-type { border-bottom: 0; }
.co-li img { width: 46px; height: 46px; object-fit: cover; border-radius: 7px; }
.co-li__n { font-size: 14px; font-weight: 500; }
.co-li__q { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.co-li__t { font-family: var(--mono); font-size: 13px; white-space: nowrap; }
/* The strip again in the checkout summary, where the panel is narrower and
   the rows are already small: the same dashed offer, one size down. */
.gift--co {
  grid-template-columns: 30px auto minmax(0, 1fr) auto;
  gap: var(--s2);
  margin: 2px 0 var(--s3);
  padding: 6px 10px 6px 8px;
}
.gift--co .gift__img { width: 30px; height: 30px; }
.gift--co .gift__tag { font-size: 9px; }
.gift--co .gift__mid { font-size: 12px; }
.gift--co .gift__t { font-size: 13px; }
/* The row above it owns the divider; the strip belongs to that row, so a
   second line between them would split one purchase in two. */
.co-li:has(+ .gift--co) { border-bottom: 0; }

.co-free { font-size: 13px; color: var(--gold); margin-top: var(--s4); }

.co-empty { text-align: center; padding-block: var(--s9); }
.co-empty h1 { font-size: clamp(28px, 5vw, 42px); }

@media (max-width: 940px) {
  .co { grid-template-columns: 1fr; }
  /* Details first, then the basket.

     Stacked, the summary card was pulled above the form with order: -1 - the
     desktop reading, where it sits in a column beside the form and is the
     first thing on the right. One under the other that is a different page:
     you arrive from the cart, having just looked at exactly these products,
     and are shown them again before being asked for anything. The form is
     what the page is for, and it now opens with it.

     The summary keeps everything it had and follows immediately after, so the
     products, the totals and the link back to change them are one scroll
     down rather than a screen in the way. Nothing is lost from the review
     step either: that lists the customer's own details, never the basket. */
  .co-side { position: static; order: 0; }
  .rv { grid-template-columns: 1fr; gap: var(--s1); }
  .rv dd { margin-bottom: var(--s3); }
}

/* ==========================================================================
   CHECKOUT, STEP ONE, ON A PHONE

   Built to a supplied mockup. Everything here is inside the stacked branch;
   the desktop checkout keeps the rail, the note and the stacked labels it
   has always had.
   ========================================================================== */
@media (max-width: 940px) {

  /* ------------------------------------------------------- the auth card */
  /* A narrow column wants the words tighter to the icon and a smaller leaf;
     everything else the card already is. */
  .authnudge { gap: var(--s4); font-size: 14px; }
  .authnudge__t { max-width: 30ch; }

  /* ------------------------------------------------------- the field card */
  /* The fields, the note and the two buttons are one panel: they are a
     single act. Both form steps get it - .step-p--form is set on the two
     that collect something, and not on the review, which is a list. */
  .step-p--form.is-on {
    gap: var(--s4);
    padding: var(--s5);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
  }
  /* Stacked, side by side leaves the phone and the email about 150px each. */
  .step-p--form .row2 { grid-template-columns: 1fr; gap: var(--s4); }

  /* ---------------------------------------------------- floating labels */
  /* The label starts where the placeholder would be and rises onto the top
     border when the field has something in it or is being typed in. Two
     states, one transition, and the label never leaves the control - which is
     the whole point of the pattern: at no moment is there a filled box whose
     purpose you have to remember.

     :placeholder-shown is doing the "is it empty" test, which is why every
     input in this step carries a placeholder even when it shows nothing. */
  .step-p--form .field { position: relative; }

  /* > input, not input. These are the step's own fields - 64px tall with the
     top padding a floating label needs. The office picker's search box is
     also an <input> inside a .field, several levels down, and it was picking
     all of this up: a 64px search row with its text pushed to the bottom by
     24px of padding that exists for a label it does not have. The child
     combinator says "the field's own control" and leaves the popup alone. */
  .step-p--form .field > input {
    height: 64px;
    padding: var(--s5) var(--s5) 0;
    border-radius: 14px;
    background: var(--surface-2);
    font-size: 16px;                  /* 16 or iOS zooms the page on focus */
  }
  /* Every placeholder here is a single space - present so :placeholder-shown
     has something to test, and showing nothing. Kept transparent as well, so
     no user-agent default can put grey text under a floating label. */
  .step-p--form .field > input::placeholder { color: transparent; }

  .step-p--form .field label {
    position: absolute;
    left: var(--s5);
    /* Centred on the INPUT, and in pixels rather than per cent.

       The label is positioned against .field, and .field is taller than its
       input whenever there is an error under it - 92px against 64. At top:
       50% that moved the anchor from 32 to 46, so pressing Продължи on an
       incomplete form dropped every label 14px down its own box while the
       message appeared. The control had not changed; only the box around it
       had.

       32px is half the input's height, which is fixed on this branch, so the
       label is centred on the control it belongs to and stays there whatever
       the field does underneath.

       Centred on the empty control, not on the line the value will occupy:
       a filled field makes no promise that the label and the value share a
       line - when it lifts, the value arrives beneath it, and the two are
       never in the same place at the same moment. */
    top: 32px;
    transform: translateY(-50%);
    transform-origin: left center;
    margin: 0;
    padding: 0;
    font-size: 16px;
    /* The box is the type, nothing more. Inherited, the line-height was
       24.8px, so even scaled the label's box stayed 21.7px tall and its
       bottom edge came down to 36.2 - past the top of the typed text at 34.1.
       They were not close, they overlapped. */
    line-height: 1;
    font-weight: 400;
    color: var(--muted);
    pointer-events: none;             /* the click belongs to the input */
    transition: transform .18s var(--ease), color .18s var(--ease);
  }
  /* Into the field's own top padding, not onto its border.

     It used to land on the border and carry a chip of --surface to punch a
     hole in it. Two things were wrong with that: the chip is the card's
     colour and the border it sits on divides the card from the input, which
     is --surface-2, so half the chip never matched whatever was behind it -
     and .8 put the word at 12.8px, small enough to read as a caption rather
     than as the field's name.

     The input is 64px tall with 24px of top padding precisely so a label can
     live up there. Sitting fully inside it, over one colour, there is nothing
     to hide and no chip to mismatch; .875 keeps it at 14px, which is the size
     the rest of the form's labels use. */
  /* Up to a centre of 14, and down to 12.8px. That is a 3.2px difference
     against the 16px being typed - enough to read as a caption rather than as
     a second value - and it leaves 13.7px of clear air between the label's
     underside and the top of the text. -18 rather than -30 because it starts
     from the field's middle now rather than from the value's line. */
  .step-p--form .field > input:focus + label,
  .step-p--form .field > input:not(:placeholder-shown) + label {
    transform: translateY(-50%) translateY(-18px) scale(.8);
  }
  .step-p--form .field > input:focus + label { color: var(--accent); }

  /* Focus should feel like the field woke up: the rim takes the accent, a
     soft ring separates it from the card, the fill warms a shade and the
     caret is the accent rather than the browser's white bar. */
  .step-p--form .field > input {
    caret-color: var(--accent);
    transition: border-color .18s var(--ease), box-shadow .18s var(--ease),
                background .18s var(--ease);
  }
  /* The focus ring carries the fill with it.

     box-shadow is one property: setting a ring here replaced the 1000px inset
     that hides Chrome's autofill background, so a remembered value looked
     right until you touched it and then went white-on-white. Both shadows are
     declared together, and the inset is the tinted colour this state uses -
     so a field that was never autofilled looks exactly as it did. */
  .step-p--form .field > input:focus {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent),
                0 0 0 1000px color-mix(in srgb, var(--accent) 6%, var(--surface-2)) inset;
    outline: none;
  }
  /* -webkit-text-fill-color is what colours autofilled text, and it does not
     change on focus - restated so the two states cannot drift. */
  .step-p--form .field > input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--bone);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent),
                0 0 0 1000px color-mix(in srgb, var(--accent) 6%, var(--surface-2)) inset;
  }

  /* The search inside the office popup: a row, not a field.

     It sits above the list it filters, so it should read as part of the
     popup's frame rather than as another thing to fill in - no fill of its
     own, no radius, and the icon and the caret on one line. */
  .step-p--form .pick__search { padding: var(--s2) var(--s4); gap: var(--s3); }
  .step-p--form .pick__q {
    height: 44px;
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    font-size: 16px;          /* 16 or iOS zooms the page on focus */
    caret-color: var(--accent);
  }
  .step-p--form .pick__q:focus { box-shadow: none; background: none; }

  /* -------------------------------------------------- step two's own bits */
  /* The controls that are not text fields, brought to the same shape so the
     column reads as one set rather than as a form with two halves. */
  .step-p--form .seg button,
  .step-p--form .pick__btn,
  .step-p--form .coupon__in {
    min-height: 56px;
    border-radius: 14px;
    background: var(--surface-2);
  }
  /* Side by side, an icon over each word. Two choices are a pair; stacked
     they read as a list you work down. flex-basis 0 so they share the row
     evenly whatever the words are. */
  .step-p--form .seg { gap: var(--s3); flex-wrap: nowrap; }
  .step-p--form .seg button {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--s2);
    padding: var(--s4) var(--s2);
    text-align: center;
  }
  .step-p--form .seg button svg { width: 24px; height: 24px; flex: none; color: var(--muted); }
  .step-p--form .seg button b { font-size: 14px; }
  .step-p--form .seg button[aria-pressed="true"] svg { color: var(--accent); }
  .step-p--form .seg button[aria-pressed="true"] {
    background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
  }

  /* A label over a group of buttons, or over a row with its own button, is a
     caption and stays one - there is nothing for it to float out of. It is
     sized and coloured like a floated label so the two kinds sit together. */
  .step-p--form .field--group { position: static; }
  .step-p--form .field--group > label {
    position: static;
    transform: none;
    margin-bottom: var(--s3);
    font-size: 12.8px;
    color: var(--muted);
  }

  /* The office picker is a listbox, so :placeholder-shown can say nothing
     about it - but it always shows either its own placeholder or a chosen
     office, so its label is always in the floated position. The button gets
     the same top padding the inputs have, to leave room for it. */
  .step-p--form .field--pick { position: relative; }
  .step-p--form .field--pick > label {
    transform: translateY(-50%) translateY(-18px) scale(.8);
  }
  .step-p--form .pick__btn { padding: 26px var(--s5) 8px; }
  .step-p--form .pick.is-open .pick__btn,
  .step-p--form .pick__btn:focus-visible {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
  }

  /* The comment box is the one control taller than a line. Its label floats
     to the same place; the text starts below it. */
  .step-p--form .field > textarea {
    min-height: 84px;
    padding: 30px var(--s5) var(--s3);
    border-radius: 14px;
    background: var(--surface-2);
    font-size: 16px;
    caret-color: var(--accent);
    transition: border-color .18s var(--ease), box-shadow .18s var(--ease),
                background .18s var(--ease);
  }
  .step-p--form .field > textarea::placeholder { color: transparent; }
  .step-p--form .field > textarea:focus {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent),
                0 0 0 1000px color-mix(in srgb, var(--accent) 6%, var(--surface-2)) inset;
    outline: none;
  }
  /* A textarea's label cannot centre on a box whose height is the point, so
     it centres on the FIRST LINE of it - 30px of padding plus half a 16px
     line - and lifts from there to the same 12px the inputs use. */
  .step-p--form .field > textarea + label { top: 38px; }
  .step-p--form .field > textarea:focus + label,
  .step-p--form .field > textarea:not(:placeholder-shown) + label {
    transform: translateY(-50%) translateY(-26px) scale(.8);
  }

  /* The mask over the comment box's top padding.

     A textarea scrolls its text THROUGH its own padding - the browser paints
     the overflow up there - and the floating label lives in that padding. A
     long comment printed the two on top of each other and neither could be
     read.

     checkout.js grows the box so an ordinary comment never scrolls at all.
     This covers the one past that cap: a strip in the field's own colour
     across the full width of the band, so the text disappears under it
     cleanly instead of half-showing beside the label. Backing only the
     label's own glyphs was not enough - the rest of the scrolled line still
     came out either side of it.

     Inset by a pixel and rounded to match, so it lands inside the border
     rather than over it, and it covers the top of the scroll gutter too. */
  .step-p--form .field--note { position: relative; }
  .step-p--form .field--note::before {
    content: '';
    position: absolute;
    top: 1px; left: 1px; right: 1px;
    height: 29px;
    border-radius: 13px 13px 0 0;
    background: var(--surface-2);
    pointer-events: none;
    z-index: 1;
  }
  /* The fill warms on focus, so the mask warms with it or it shows up as a
     lighter bar sitting on a tinted field.

     Keyed on the textarea's own :focus, not on :focus-within of the field.
     The two are not the same test - :focus-within can hold while :focus does
     not - and any state where they disagree is one where the mask and the
     field it is imitating are different colours. Same condition, same
     answer, always. */
  .step-p--form .field--note:has(> textarea:focus)::before {
    background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
  }
  /* Over the mask, not under it. */
  .step-p--form .field--note > label { z-index: 2; }

  /* The discount code: one row, and the field names itself.

     It had a caption above it, a "По желание." beside that, and a placeholder
     inside asking the same question a third way - three pieces of furniture
     around one small input. The label floats like every other field's and
     Приложи sits at the end of the row at the same height. */
  .step-p--form .coupon__row { gap: var(--s3); align-items: stretch; }
  .step-p--form .coupon__wrap { position: relative; flex: 1 1 auto; min-width: 0; display: block; }
  .step-p--form .coupon__in {
    width: 100%;
    height: 64px;
    padding: var(--s5) var(--s5) 0;
    font-size: 16px;
    caret-color: var(--accent);
  }
  .step-p--form .coupon__in::placeholder { color: transparent; }
  .step-p--form .coupon__in:focus,
  .step-p--form .coupon__in:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--bone);
    box-shadow: 0 0 0 1000px var(--surface-2) inset;
  }
  .step-p--form .coupon__l {
    position: absolute;
    left: var(--s5);
    top: 32px;
    transform: translateY(-50%);
    transform-origin: left center;
    margin: 0;
    font-size: 16px;
    line-height: 1;
    font-weight: 400;
    color: var(--muted);
    pointer-events: none;
    transition: transform .18s var(--ease), color .18s var(--ease);
  }
  .step-p--form .coupon__in:focus + .coupon__l,
  .step-p--form .coupon__in:not(:placeholder-shown) + .coupon__l {
    transform: translateY(-50%) translateY(-18px) scale(.8);
  }
  .step-p--form .coupon__in:focus + .coupon__l { color: var(--accent); }
  .step-p--form .coupon__go {
    flex: none;
    min-height: 64px;
    padding-inline: var(--s5);
    border-radius: 14px;
  }

  /* Step two's inputs carry real placeholders - the city's loading message,
     the address's format - which are useful where the label is a heading
     above the box and in the way where it is inside it. Hidden here only. */
  .step-p--form .field > input::placeholder { color: transparent; }

  /* ------------------------------------------------------- the data note */
  .fieldnote { gap: var(--s3); font-size: 13px; }
  .fieldnote__i {
    width: 34px; height: 34px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  }

  .step-p--form .step-nav { margin-top: var(--s2); }
}

@media (max-width: 940px) and (prefers-reduced-motion: reduce) {
  .step-p--form .field label { transition: none; }
}

/* ==========================================================================
   Cookie banner
   Refusing is the same one click as accepting, and both buttons sit together.
   ========================================================================== */
.cc {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 180;
  padding: var(--s4);
  transform: translateY(105%);
  transition: transform .34s var(--ease);
}
.cc.is-in { transform: none; }
.cc[hidden] { display: none; }
.cc__box {
  max-width: 900px; margin-inline: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--s5);
  box-shadow: 0 18px 60px rgba(0,0,0,.55);
}
.cc__h { font-size: 18px; margin-bottom: var(--s3); }
.cc__t { color: var(--muted); font-size: 15px; max-width: 76ch; }

.cc__more { margin-top: var(--s4); }
.cc__more summary {
  cursor: pointer; color: var(--accent); font-size: 14px;
  min-height: 44px; display: flex; align-items: center;
}
.cc__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--s4); margin-block: var(--s3) var(--s4);
}
.cc__grid h3 { font-family: var(--body); font-size: 14px; font-weight: 600; margin-bottom: var(--s2); }
.cc__grid p  { font-size: 13px; color: var(--muted); }
.cc__note { font-size: 13px; color: var(--muted); }

.cc__row { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; margin-top: var(--s5); }
.cc__row .btn { flex: 0 1 auto; }
.cc__link { margin-left: auto; font-size: 14px; color: var(--muted); min-height: 44px; display: inline-flex; align-items: center; }
.cc__link:hover { color: var(--accent); }

@media (max-width: 600px) {
  .cc__row .btn { flex: 1 1 100%; }
  .cc__link { margin-left: 0; }
}

/* ==========================================================================
   Legal / document pages
   ========================================================================== */
.doc-page { padding-block: var(--s7) var(--s9); max-width: 860px; }
.doc-page h1 { font-size: clamp(30px, 5vw, 48px); margin-bottom: var(--s3); }
.doc-page h2 {
  font-size: 21px; margin-top: var(--s8); margin-bottom: var(--s4);
  padding-top: var(--s5); border-top: 1px solid var(--line);
}
.doc-page p { margin-bottom: var(--s4); color: var(--muted); max-width: 74ch; }
.doc-page p strong { color: var(--bone); font-weight: 500; }
.doc-page a { color: var(--accent); }

.plist { color: var(--muted); padding-left: var(--s5); margin-bottom: var(--s4); max-width: 74ch; }
.plist li { margin-bottom: var(--s2); }
.plist strong { color: var(--bone); font-weight: 500; }

.ptbl { width: 100%; border-collapse: collapse; margin-bottom: var(--s5); font-size: 14px; }
.ptbl th, .ptbl td { padding: var(--s3) var(--s4); text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
.ptbl th { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.ptbl td { color: var(--muted); }
.ptbl td:first-child { color: var(--bone); }
@media (max-width: 760px) {
  .ptbl, .ptbl thead, .ptbl tbody, .ptbl tr, .ptbl th, .ptbl td { display: block; }
  .ptbl thead { display: none; }
  .ptbl tr { border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: var(--s4); padding: var(--s3); }
  .ptbl td { border: 0; padding: var(--s2); }
}

/* Lenis: it manages scrolling, so the browser must not also smooth it. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

/* The flying product ghost when something is added to the cart. */
.fly { will-change: transform, opacity; }

/* A cheaper shadow where the paint budget is smallest.

   The desktop ghost carries a 60px blur. That is painted into its layer when
   the layer is created and again at the single radius change - twice, not
   forty times, now that the radius is no longer tweened - but a 60px blur on
   a ~90px square is still one of the most expensive things this page paints,
   and on a phone it lands in the same frame as the drawer opening. A 24px
   blur reads the same at this size and costs a fraction. */
@media (max-width: 1024px) {
  .fly {
    box-shadow: 0 10px 24px -8px rgb(0 0 0 / .72),
                0 0 0 1px rgb(var(--bone-rgb) / .06);
  }
}

/* ==========================================================================
   Apple-style pill button
   Fully rounded, solid fill, no border. The radius is deliberately far larger
   than the height so it stays a true capsule at any size.

   Three layers stack inside it:
     ::before  a specular highlight that follows the cursor
     ::after   a sheen that sweeps across on hover
     .btn__*   the label and arrow, above both
   ========================================================================== */
.btn--apple {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border-radius: 980px;
  border: 0;
  min-height: 56px;
  padding-inline: var(--s7);
  gap: var(--s3);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.01em;
  transition: filter .22s var(--ease), opacity .22s, transform .12s var(--ease);
}

/* The press, in CSS as well as in the spring.

   initAppleButtons() gives these a Motion spring on pointerdown, and it lives
   in site.js - which the checkout does not load. Every button on the order
   form was therefore inert to the touch: no light, no give, nothing.

   This does not fight the spring. Motion animates transform as an inline
   style, and an inline style outranks a stylesheet rule, so wherever the JS
   runs it still owns the press; where it does not, this is the press. */
.btn--apple:active { transform: scale(.972); }
.btn--apple.btn--primary:active { filter: brightness(.95); }

@media (prefers-reduced-motion: reduce) {
  .btn--apple:active { transform: none; }
  /* The sweep too. It was never covered here - the block only ever silenced
     the press - and now that a click replays it on every press rather than
     once on arrival, a visitor who asked for less motion would have got
     considerably more of it. */
  .btn--apple:hover::after,
  .btn--apple.is-swept::after { animation: none; }
}

/* the light under the cursor */
.btn--apple::before {
  content: '';
  /* z-index -1, not 0. Both of these are positioned, and a positioned element
     at z-index 0 paints ABOVE non-positioned inline content - so the 42% white
     specular was washing over the label itself and the dark text on a filled
     button became unreadable the moment you hovered it. The button sets
     `isolation: isolate`, so a negative index here stays inside the button and
     lands where it belongs: above the fill, beneath the text. */
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(150px circle at var(--mx, 50%) var(--my, 50%),
              rgba(255,255,255,.42), transparent 62%);
  opacity: 0;
  transition: opacity .28s var(--ease);
  pointer-events: none;
}
.btn--apple:hover::before { opacity: 1; }

/* The same component, secondary weight. Apple's own hierarchy is one filled
   button and one plain one of identical size and shape - not a button beside
   something that merely looks clickable - so this keeps the pill, the press
   and the pointer-tracked highlight, and only drops the fill. */
.btn--apple.btn--ghost {
  background: transparent;
  border: 1px solid rgb(var(--bone-rgb) / .20);
  color: var(--bone);
}
.btn--apple.btn--ghost:hover { border-color: rgb(var(--bone-rgb) / .42); }
/* White specular reads as a smear on a dark unfilled surface; far softer here. */
.btn--apple.btn--ghost::before {
  background: radial-gradient(150px circle at var(--mx, 50%) var(--my, 50%),
              rgb(var(--bone-rgb) / .12), transparent 62%);
}
/* The sweep belongs to the filled, primary action only. */
.btn--apple.btn--ghost::after { display: none; }

/* Keyboard users get the same affordance as the pointer. */
.btn--apple:focus-visible { outline: 2px solid var(--gold-soft); outline-offset: 3px; }

/* Elevation, in the accent's own colour. A filled button on a near-black page
   has nothing to cast against, so without this it reads as a flat sticker
   rather than a raised control. It lifts on hover and drops back on press,
   which is the same physical story the scale is telling. */
.btn--apple.btn--primary {
  box-shadow: 0 6px 22px -12px color-mix(in srgb, var(--accent) 90%, transparent);
}
.btn--apple.btn--primary:hover {
  box-shadow: 0 12px 34px -12px color-mix(in srgb, var(--accent) 100%, transparent);
}
.btn--apple.btn--primary:active {
  box-shadow: 0 3px 12px -10px color-mix(in srgb, var(--accent) 90%, transparent);
}

/* The larger scale, for the two surfaces that are a moment rather than a
   toolbar: the age gate and the hero. Same component, same effects - only the
   size steps up, so the site still reads as one system. */
.btn--apple.btn--lg {
  min-height: 64px;
  padding-inline: var(--s8);
  font-size: 17px;
}
@media (max-width: 520px) {
  .btn--apple.btn--lg { min-height: 58px; padding-inline: var(--s6); font-size: 16px; }
}

/* the sweep */
.btn--apple::after {
  content: '';
  /* Same reason as ::before - the sweep is 50% white and was crossing over
     the label rather than under it. */
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(105deg, transparent 38%,
              rgba(255,255,255,.5) 50%, transparent 62%);
  transform: translateX(-130%);
  pointer-events: none;
}
.btn--apple:hover::after { animation: sheen .85s var(--ease); }
/* The same sweep, replayed on demand.

   :hover fires it once, when the pointer ARRIVES - so on a desktop the second
   click of a run got nothing, and on a phone, where there is no hover at all,
   the button never swept once. A class the script can take off and put back
   is the only way to restart a CSS animation, and this is that class. */
.btn--apple.is-swept::after { animation: sheen .85s var(--ease); }
@keyframes sheen { to { transform: translateX(130%); } }

.btn__label, .btn__arrow { position: relative; z-index: 1; }
.btn__arrow {
  transform: translateX(-3px);
  opacity: .75;
  transition: transform .28s var(--ease), opacity .28s var(--ease);
}
.btn--apple:hover .btn__arrow { transform: translateX(2px); opacity: 1; }

.btn--apple:hover { filter: brightness(1.06); }
.btn--apple:focus-visible { outline: 2px solid var(--bone); outline-offset: 3px; }
.btn--apple.btn--disabled { opacity: .35; }

/* The attention beat: a brightness flare with a barely-there swell. Used
   when something enters the cart, and again on hover.
 *
 * `spin` is repeated in every rule that declares this shorthand, and it must
 * stay FIRST in the list. Animations are matched by position, so keeping it
 * at index 0 lets the rotation carry on from where it was. Drop it, or move
 * it, and the conic gradient snaps back to 0deg the moment you hover.
 */
.glowring.is-pulsing { animation: spin 4.4s linear infinite, ringbeat .8s var(--ease); }

.glowring:hover,
.glowring:focus-within { animation: spin 4.4s linear infinite, ringbeat .8s var(--ease); }

/* transform and filter only: neither affects layout, so nothing shifts */
@keyframes ringbeat {
  0%   { filter: brightness(2.4) saturate(1.4); transform: scale(1); }
  40%  { filter: brightness(1.6) saturate(1.2); transform: scale(1.014); }
  100% { filter: brightness(1)   saturate(1);   transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .btn--apple, .btn__arrow { transition: none; }
  .btn--apple::before, .btn--apple::after { display: none; }
  .btn--apple:hover .btn__arrow { transform: translateX(-3px); }
  .glowring.is-pulsing { animation: none; }
}

/* ==========================================================================
   Rotating gradient border

   A custom property has to be REGISTERED as an <angle> before a browser will
   interpolate it. Left unregistered, --spin is just a string and the conic
   gradient snaps from 0 to 360 instead of sweeping. That registration is the
   whole trick.
   ========================================================================== */
@property --spin {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: true;          /* the blurred bloom reads the same angle */
}

.glowring {
  --ring: 2px;             /* how much gradient shows around the button */
  position: relative;
  border-radius: 980px;
  padding: var(--ring);
  isolation: isolate;

  /* At rest: one bright pass and one accent pass with a long transparent
     tail, so a highlight travels round the edge rather than lighting all of
     it. This is the quiet state and follows the flavour colour. */
  --grad-rest: conic-gradient(
    from var(--spin),
    transparent 0deg,
    color-mix(in srgb, var(--accent) 70%, transparent) 42deg,
    #ffffff 74deg,
    color-mix(in srgb, var(--accent) 85%, transparent) 104deg,
    transparent 150deg,
    transparent 264deg,
    color-mix(in srgb, var(--accent) 45%, transparent) 316deg,
    transparent 360deg
  );

  /* On hover only: a purple-led spectrum. Saturated the whole way round
     with no transparent tail, so the entire edge lights up rather than one
     travelling highlight. First and last stop match, or the seam shows on
     every rotation. */
  --grad-hover: conic-gradient(
    from var(--spin),
    #A855F7   0deg,     /* purple        */
    #D946EF  45deg,     /* fuchsia       */
    #FF4D8D  90deg,     /* pink          */
    #FFFFFF 130deg,     /* highlight     */
    #C084FC 170deg,     /* light purple  */
    #7B3FE4 215deg,     /* deep violet   */
    #6366F1 260deg,     /* indigo        */
    #B47CFF 305deg,
    #A855F7 360deg      /* back to purple, seamless */
  );

  background: var(--grad-rest);
  animation: spin 4.4s linear infinite;
}

/* the bloom: the same gradient, blurred, sitting behind the ring */
.glowring::before {
  content: '';
  position: absolute;
  inset: calc(var(--ring) * -1.5);
  border-radius: inherit;
  background: var(--grad-rest);
  filter: blur(11px);
  opacity: .5;
  z-index: -1;
  transition: opacity .38s var(--ease);
  pointer-events: none;
  will-change: opacity;
  transform: translateZ(0);
}

/* The hover ring sits on its own layer and crossfades in, because a
   background-image cannot be transitioned between two gradients. */
.glowring::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--grad-hover);
  opacity: 0;
  transition: opacity .42s var(--ease);   /* slower out */
  pointer-events: none;
  z-index: 0;
  will-change: opacity;
  transform: translateZ(0);
}
.glowring:hover::after,
.glowring:focus-within::after { transition-duration: .28s; }   /* quicker in */
.glowring:hover::after,
.glowring:focus-within::after { opacity: 1; }

/* the bloom follows, blurred enough that the swap itself is invisible */
/* The bloom only brightens. Swapping its gradient or its blur mid-transition
   repaints a large blurred layer, which is what made this stutter. */
.glowring:hover::before,
.glowring:focus-within::before { opacity: .82; }

/* Padding must NOT change on hover. It resizes the element, which shifts the
   button underneath it, and that shift is the jump. */

@keyframes spin { to { --spin: 360deg; } }

/* The button sits on top and stays fully opaque, so only the padding ring
   of the gradient is ever visible. */
.glowring > .btn--apple {
  width: 100%;
  position: relative;
  z-index: 1;
}

/* Nothing to advertise when the cart is empty. */
.glowring:has(.btn--disabled),
.glowring:has(.btn[disabled]) { animation-play-state: paused; opacity: .3; }
.glowring:has(.btn--disabled)::before,
.glowring:has(.btn[disabled])::before { opacity: 0; }

/* Inline variant: sits in a row next to the price rather than filling a
   panel, so it must not stretch. */
.glowring--inline { display: inline-block; width: auto; }
.glowring--inline > .btn--apple { width: auto; }

@media (prefers-reduced-motion: reduce) {
  .glowring,
  .glowring:hover,
  .glowring:focus-within,
  .glowring.is-pulsing { animation: none; }
  .glowring::before { filter: blur(8px); opacity: .4; }
}

/* ==========================================================================
   Discounted prices
   The old figure is struck through and dimmed, the payable figure sits next
   to it in the accent, and the saving is named underneath. Three signals for
   the same fact, because a strikethrough alone is easy to miss.
   ========================================================================== */
.li__was, .co-li__t .li__was {
  display: inline-block;
  text-decoration-line: line-through;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--warn) 75%, transparent);
  text-decoration-thickness: 1.5px;
  margin-right: var(--s2);
}
.li__now {
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--accent);
}
.li__off {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--accent);
  margin-top: 2px;
  white-space: nowrap;
}
.co-li__t .li__now { font-size: 13px; }

/* the discount row in both summaries */
.tot--off { color: var(--accent); }
.tot--off span:last-child { font-family: var(--mono); }
.tot--sub { color: var(--muted); font-size: 14px; }

.cart__ship, .co-note {
  font-size: 12px;
  color: var(--muted);
  margin-top: var(--s3);
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .li__was { transition: none; }
}

/* the cart note is gone; keep the class out of the way if it ever returns */
.cart__ship { display: none; }

/* ==========================================================================
   Newcomer offer banner
   Only rendered when the visitor is at least 85% likely to be new, so it is
   never shown to someone who cannot actually claim it.
   ========================================================================== */
.newbanner {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s5); padding: var(--s3) var(--s5);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--muted); font-size: 14px;
}
.newbanner__tag {
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  padding: 4px 11px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
}

/* the flavour panel price can now hold three parts */
.flav__price .li__was { font-size: 15px; }
.flav__price .li__now { font-size: 26px; font-weight: 500; }
/* .flav__price is a baseline flex row now, so the saving - which is a
   sentence, not a figure - takes a line of its own underneath the two prices
   rather than trailing off the end of them.

   One line PER RULE, because more than one can be running at a time and a
   shopper is owed the name of each. nowrap is lifted here for the same
   reason: the rule's name is typed by the shop owner and can be any length,
   and a name that runs past the panel is worse than one that wraps. */
.flav__price .li__off {
  font-size: 12px;
  flex-basis: 100%;
  white-space: normal;
  line-height: 1.45;
}
/* The name is the label and the figure is the payload, so the name carries
   the weight and the figure stays in the accent the price is already in. */
.flav__price .li__off b {
  font-weight: 600;
  color: color-mix(in srgb, var(--bone) 82%, var(--accent));
}
.flav__price .li__off b::after { content: ' ·'; opacity: .5; }

@media (max-width: 560px) {
  .newbanner { border-radius: var(--radius); }
}

/* ==========================================================================
   Accounts
   Optional throughout. Nothing here ever blocks a guest from ordering.
   ========================================================================== */
.acctbtn {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding: 0 var(--s4);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); text-decoration: none; font-size: 15px;
  transition: color .18s, border-color .18s;
}
.acctbtn:hover { color: var(--bone); border-color: var(--muted); }
/* the design's account control: the glyph alone, in a round, unbordered
   target beside the basket */
.acctbtn--icon { width: 44px; padding: 0; justify-content: center; border-color: transparent; border-radius: 50%; color: rgb(var(--bone-rgb) / .8); }
.acctbtn--icon:hover { color: #fff; background: rgb(var(--bone-rgb) / .06); border-color: transparent; }
.acctbtn__i {
  width: 24px; height: 24px; flex: none; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  font-size: 12px; font-weight: 700;
}
/* The header controls, on a phone.

   They were sitting immediately after the wordmark with 148px of empty header
   to their right, because what pushes them over on a desktop is
   .hdr__nav { margin-left: auto } and the nav is display: none here - the
   auto margin goes with it. The margin moves onto the pair.

   The pair is the point. Two circles of the same size, 8px apart, hard against
   the right edge: near things read as one thing, distant things as two, so
   grouping them tightly and pushing them away from the wordmark says "these
   belong together and neither of them is the logo" without a rule or a
   divider. Everything else follows the same discipline - one shape, one size,
   one icon weight, no labels, no colour except where colour carries meaning.

   The surface is the material treatment: a translucent fill over a blurred
   backdrop with a hairline lighter than the fill. It borrows the ground it
   sits on rather than declaring a colour of its own, which is what keeps a
   control legible over both the dark header and the artwork scrolling beneath
   it. Contrast is carried by the icon, not by the container.

   The count is the one saturated thing in the header, and it is the one thing
   that changes without being touched. */
@media (max-width: 620px) {
  .acctbtn__t, .cartbtn__t { display: none; }

  /* Right edge, as a pair. */
  .acctbtn { margin-left: auto; }
  .hdr__in { gap: var(--s2); }

  /* 44, not 42, and a 20px icon inside it.

     19 in 42 leaves (42-19)/2 = 11.5px on each side, so both icons were laid
     out on a half-pixel and their 1.8px strokes straddled the device grid -
     which is what the softness was. Even icon in an even box puts every edge
     on a whole pixel at any density.

     No backdrop-filter here either. The header already blurs what is behind
     it, and a second one nested inside promotes each button to its own
     backdrop root: the button and everything in it - icon, count, digits -
     gets rasterised into that layer, and on a phone GPU that layer is not
     always allocated at device resolution. The translucent fill over the
     header's own blur reads the same and stays vector-sharp. */
  .acctbtn,
  .cartbtn {
    width: 44px; min-width: 44px; height: 44px; min-height: 44px;
    padding: 0;
    justify-content: center;
    border-radius: 999px;
    background: rgb(var(--bone-rgb) / .07);
    border: 1px solid rgb(var(--bone-rgb) / .10);
    color: rgb(var(--bone-rgb) / .88);
    /* Springless and short. A press should feel answered, not animated. */
    transition: transform .16s var(--ease), background .16s var(--ease),
                border-color .16s var(--ease);
  }
  /* The press: the control gives, and gives back. No colour change, because
     the colour means something here and a press does not. */
  .acctbtn:active,
  .cartbtn:active {
    transform: scale(.93);
    background: rgb(var(--bone-rgb) / .14);
  }
  .acctbtn:hover { border-color: rgb(var(--bone-rgb) / .18); color: rgb(var(--bone-rgb) / .88); }
  .cartbtn:hover { border-color: rgb(var(--bone-rgb) / .18); }

  /* One icon weight across both, sized to the same optical square. */
  .acctbtn svg { width: 20px; height: 20px; stroke-width: 1.8; }
  .cartbtn__i { width: 20px; height: 20px; color: inherit; }
  .cartbtn__i svg { width: 20px; height: 20px; }
  .cartbtn:hover .cartbtn__i { color: inherit; }

  /* Sat on the corner rather than in the row, ringed in the header's own
     colour so it stays a separate object where it laps the button's edge.
     An empty cart shows nothing: a nought is a thing to read, and there is
     nothing to say. */
  /* The count.

     It was a 17px box carrying 10.5px type on a 16.275px line - an odd box,
     a fractional font size and a fractional line height, every one of which
     lands a glyph between device pixels. 18px, 11px and a line-height of 1
     are all whole numbers, so the disc and the digit inside it are drawn on
     the grid.

     And it hangs off the corner of the button's BOX, which on a round button
     is a corner the button does not have - so it floated diagonally away from
     the edge with a gap under it. One pixel out instead of two puts its
     centre 18.4px from the circle's centre against a 22px radius, which sits
     it on the rim, overlapping, the way a badge on a round control should. */
  .cartbtn__n {
    position: absolute;
    top: -1px; right: -1px;
    min-width: 18px; height: 18px;
    /* 2px, not 4. A single digit is 10.61px of mono at this size, so 4px a
       side pushed the box to 18.61 and put its left edge on a fraction - the
       one number in the header, drawn off the grid. At 2px the digit plus its
       padding is under the 18px floor, so min-width decides and the disc is
       exactly 18 square. Two digits still get a pill rather than a squash. */
    padding: 0 2px;
    font-size: 11px;
    line-height: 1;
    border: 2px solid var(--ink);
  }
  .cartbtn__n[data-empty="1"] { display: none; }
}

@media (max-width: 620px) and (prefers-reduced-motion: reduce) {
  .acctbtn:active, .cartbtn:active { transform: none; }
}

.authwrap { display: grid; place-items: center; min-height: 76vh; padding-block: var(--s7); }
.authbox {
  width: 100%; max-width: 480px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--s6);
}
.authbox h1 { font-size: 26px; margin-bottom: var(--s2); }
.authlede { color: var(--muted); font-size: 14px; }

.authtabs { display: flex; gap: var(--s2); margin-bottom: var(--s6); }
.authtabs a {
  flex: 1; text-align: center; padding: 11px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); color: var(--muted);
  text-decoration: none; font-size: 15px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color .16s, border-color .16s, background .16s;
}
.authtabs a:hover { color: var(--bone); }
.authtabs a.on { background: var(--surface-2); color: var(--bone); border-color: var(--muted); }

.autherr {
  border-left: 2px solid var(--warn); padding: var(--s3) var(--s4);
  color: var(--warn); font-size: 14px; margin-bottom: var(--s5);
}
.flashline {
  border-left: 2px solid var(--gold); padding: var(--s3) var(--s4);
  color: var(--gold); font-size: 14px; margin-bottom: var(--s5);
}
/* The sign-in card.

   This was a dashed strip everywhere and a designed card only on a phone,
   which left the desktop with the card's markup and none of its rules: the
   leaf fell back to its own 440px and filled the block. The card is the
   design now, at every width, and the phone branch only adjusts what a narrow
   column needs. */
.authnudge {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s5);
  overflow: hidden;
  padding: var(--s5);
  margin-bottom: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--bone);
  font-size: 15px;
}
.authnudge__i {
  display: grid; place-items: center;
  width: 62px; height: 62px; flex: none;
  border-radius: 999px;
  color: var(--accent);
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.authnudge__t { margin: 0; line-height: 1.5; max-width: 46ch; }
.authnudge__t a { color: var(--accent); }
/* Bleeds off the right edge behind the text, clipped by the card. Sized as a
   share of the card so it stays the same proportion of a 343px column and an
   894px one, rather than a stamp on the wide layout. The hue-rotate turns the
   supplied gold line art into the accent - one number to change if it should
   go back to gold. */
.authnudge__leaf {
  position: absolute;
  right: -14px; top: 50%;
  width: clamp(150px, 24%, 264px);
  height: auto;
  aspect-ratio: 1;
  transform: translateY(-50%);
  object-fit: contain;
  pointer-events: none;
  opacity: .85;
  filter: hue-rotate(32deg) saturate(1.15);
  -webkit-mask-image: linear-gradient(to right, rgb(0 0 0 / 0), #000 38%);
  mask-image: linear-gradient(to right, rgb(0 0 0 / 0), #000 38%);
}

/* order history */
.orderlist { display: grid; gap: var(--s4); }
.ordercard {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--s5);
  transition: border-color .18s;
}
.ordercard:hover { border-color: var(--muted); }
.ordercard__top { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); margin-bottom: var(--s3); }
.ordercard__no { font-size: 14px; color: var(--accent); }
.ordercard__items { font-size: 14px; color: var(--muted); line-height: 1.5; }
.ordercard__foot {
  display: flex; justify-content: space-between; gap: var(--s4);
  margin-top: var(--s4); padding-top: var(--s3);
  border-top: 1px solid var(--line); font-size: 13px;
}
.ordercard__foot span:last-child { color: var(--bone); font-size: 15px; }
.ordercard__disc { font-size: 12px; color: var(--gold); margin-top: var(--s2); }

.pinlike {
  width: 100%; min-height: 46px; padding: 11px var(--s4);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--bone);
}
.pinlike:focus { border-color: var(--accent); outline: none; }
.pinlike:disabled { opacity: .55; }

/* ==========================================================================
   Coupon box
   Step 2 of checkout. The order summary sits alongside it, so the effect of
   applying a code is visible without moving.
   ========================================================================== */
/* Lives at step 2 of checkout, so it is laid out as one more field in that
   form rather than as a panel of its own. */
.coupon__l { display: block; margin-bottom: var(--s2); }
/* .coupon__wrap is a <span>. Blockified as a flex item it stacked its two
   children in source order - input, then label - which put "Код за отстъпка"
   UNDERNEATH the box it names. order: -1 is the same trick .field label uses
   to sit above its own control. The phone branch never showed it because
   there the label is positioned and floats inside the field.
   align-items keeps Приложи on the input's line now that the label is
   above it and the two columns are different heights. */
.coupon__wrap { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.coupon__l { order: -1; }
.coupon__row { display: flex; gap: var(--s2); align-items: flex-end; }

.coupon__in {
  flex: 1 1 auto; min-width: 0; min-height: 46px;
  padding: 0 var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 999px;
  color: var(--bone); font-family: var(--mono); font-size: 14px;
  letter-spacing: .06em; text-transform: uppercase;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.coupon__in::placeholder {
  letter-spacing: 0; text-transform: none;
  font-family: var(--body); color: var(--muted);
}
.coupon__in:focus { outline: none; border-color: var(--accent); }

.coupon__go {
  flex: none; min-height: 46px; padding: 0 var(--s5);
  background: none; color: var(--bone);
  border: 1px solid var(--line); border-radius: 999px;
  font-family: var(--body); font-size: 14px; font-weight: 500; cursor: pointer;
  transition: border-color .18s var(--ease), color .18s var(--ease);
}
.coupon__go:hover { border-color: var(--accent); color: var(--accent); }
.coupon.is-busy .coupon__go { opacity: .5; pointer-events: none; }

/* The border carries the verdict, so the result is visible without reading.
   is-ok's fallback used to be #7FFF2A - the acid-green hex, hand-copied
   rather than referencing --acid, which is exactly why the sitewide token
   removal missed it: nothing here said "acid". A coupon that verified would
   have kept flashing the old brand colour forever. Falls back to --gold now,
   for the same reason the fallback existed in the first place: --ok is not
   actually defined anywhere, so this always was the true colour, never a
   fallback in practice. */
.coupon.is-ok   .coupon__in { border-color: var(--ok, var(--gold)); }
.coupon.is-bad  .coupon__in { border-color: #FF4D4D; }
.coupon.is-warn .coupon__in { border-color: #FFA51F; }

.coupon__msg { margin-top: var(--s2); font-size: 13px; color: var(--muted); }
.coupon.is-ok   .coupon__msg { color: var(--ok, var(--gold)); }
.coupon.is-bad  .coupon__msg { color: #FF4D4D; }
.coupon.is-warn .coupon__msg { color: #FFA51F; }

/* ==========================================================================
   Office picker
   Replaces the native select for Econt offices. Sofia alone has 133 of them,
   so the list is searchable and each row carries its address: the office name
   on its own ("София Аксаков") is not enough to pick by.
   ========================================================================== */
select.is-replaced { display: none; }

/* The city suggestions.

   Same furniture as the office picker's popup, because they are the same kind
   of thing - a list of places you choose one of - and the step should not
   offer two different-looking answers to that.

   is-active is the row the arrow keys are on. It is a background change, not
   a border: a border on one row shifts nothing but reads as a box drawn round
   it, and what is wanted is the row itself lit up. */
.sugg {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px); left: 0; right: 0;
  max-height: 264px;
  overflow-y: auto;
  margin: 0; padding: var(--s2);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 22px 48px -18px rgb(0 0 0 / .7);
  -webkit-overflow-scrolling: touch;
}
.sugg__i {
  display: grid; gap: 1px;
  padding: 10px var(--s3);
  border-radius: 9px;
  cursor: pointer;
}
.sugg__n { font-size: 15px; color: var(--bone); }
.sugg__m { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.sugg__i:hover { background: rgb(var(--bone-rgb) / .06); }
.sugg__i.is-active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
}
.sugg__i.is-active .sugg__n { color: var(--accent); }

@supports not (color: color-mix(in srgb, red, blue)) {
  .sugg__i.is-active { background: rgb(var(--bone-rgb) / .12); }
}

.pick { position: relative; }

.pick__btn {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%; min-height: 48px; padding: 10px var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--bone); text-align: left; cursor: pointer;
  font-family: inherit; font-size: inherit;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.pick__btn:hover:not(:disabled) { border-color: var(--muted); }
.pick__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pick__btn:disabled { cursor: not-allowed; opacity: .55; }
.pick.is-open .pick__btn { border-color: var(--accent); }

.pick__val { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; }
.pick__ph { color: var(--muted); }

/* Name and address are two lines in the button as well as in the list, so
   choosing does not change the height of the field. */
.pick__n {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 15px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pick__a {
  font-size: 12px; color: var(--muted); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pick__tag {
  flex: none; padding: 2px 7px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-family: var(--mono); font-size: 10px; letter-spacing: .04em;
  text-transform: uppercase;
}

.pick__chev { flex: none; color: var(--muted); transition: transform .22s var(--ease); }
.pick.is-open .pick__chev { transform: rotate(180deg); color: var(--accent); }

/* ------------------------------------------------------------- popup -- */
.pick__pop {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 6px);
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: var(--radius-sm);
  box-shadow: 0 20px 50px rgba(0,0,0,.55);
  overflow: hidden;
  animation: pickIn .16s var(--ease);
}
@keyframes pickIn { from { opacity: 0; transform: translateY(-6px); } }

.pick__search {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}
.pick__q {
  flex: 1 1 auto; min-width: 0; min-height: 28px;
  background: none; border: 0; color: var(--bone);
  font-family: inherit; font-size: 14px;
}
.pick__q:focus { outline: none; }
.pick__q::placeholder { color: var(--muted); opacity: 1; }

.pick__list {
  max-height: 300px; overflow-y: auto;
  margin: 0; padding: var(--s2); list-style: none;
  overscroll-behavior: contain;
}
.pick__list li {
  display: grid; gap: 2px;
  padding: 9px var(--s3);
  border-radius: 8px; cursor: pointer;
}
/* One highlight, driven by class rather than :hover, so the keyboard and the
   mouse cannot disagree about which row is current. */
.pick__list li.is-active { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.pick__list li.is-chosen .pick__n { color: var(--accent); }
.pick__list li.is-chosen .pick__n::after { content: '✓'; margin-left: auto; }

.pick__none { padding: var(--s5) var(--s4); text-align: center; color: var(--muted); font-size: 14px; }

@media (prefers-reduced-motion: reduce) {
  .pick__pop { animation: none; }
  .pick__chev { transition: none; }
}

/* ==========================================================================
   Cart discounts column
   Everything here is read from the same quote that produced the total, so it
   can never advertise a discount the checkout would not honour.
   ========================================================================== */
.dsc {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s5);
}
.dsc__h { margin-bottom: var(--s4); }
.dsc__h h3 { font-family: var(--body); font-size: 16px; font-weight: 600; letter-spacing: 0; }

/* an applied rule */
.dsc__row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s3); align-items: center;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.dsc__row + .dsc__row { margin-top: var(--s2); }
.dsc__ico {
  display: grid; place-items: center; width: 34px; height: 34px; border-radius: 9px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent);
}
.dsc__name { font-size: 14px; overflow: hidden; text-overflow: ellipsis; }
.dsc__amt { font-family: var(--mono); font-size: 14px; color: var(--accent); white-space: nowrap; }

/* ------------------------------------------------------ a next tier -- */
.dsc__goal {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s4); align-items: center;
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.dsc__goal + .dsc__goal { margin-top: var(--s3); }
.dsc__goaltxt { display: grid; gap: 3px; min-width: 0; }
.dsc__gap { font-size: 12px; color: var(--muted); }

.dsc__ring { position: relative; display: grid; place-items: center; width: 52px; height: 52px; }
/* Fills its box, whatever size the box is.

   The SVG carries width="52" height="52" as ATTRIBUTES. On a replaced element
   those act as a used width and beat `inset: 0`, so when the mobile panel
   compacted .dsc__ring to 40px the artwork kept drawing at 52 - overflowing
   12px to the right and bottom, which put the circle's centre 6px off from
   the percentage sitting in the middle of it. The viewBox is 0 0 52 52, so
   the shape scales cleanly to any box; it only had to be told to. On desktop
   the box is 52 and this resolves to the same 52 it always was. */
.ring { position: absolute; inset: 0; width: 100%; height: 100%; }
.ring__bg { stroke: var(--line); }
.ring__n { font-family: var(--mono); font-size: 12px; font-weight: 500; }

/* Distance to the tier is carried by colour as well as by the number, so the
   nearest one is obvious at a glance. */
.dsc__goal--near .ring__fg, .dsc__goal--near .dsc__bar i { stroke: var(--accent); background: var(--accent); }
.dsc__goal--near .ring__n, .dsc__goal--near .dsc__worth { color: var(--accent); }
.dsc__goal--mid  .ring__fg, .dsc__goal--mid  .dsc__bar i { stroke: #FFA51F; background: #FFA51F; }
.dsc__goal--mid  .ring__n, .dsc__goal--mid  .dsc__worth { color: #FFA51F; }
.dsc__goal--far  .ring__fg, .dsc__goal--far  .dsc__bar i { stroke: #4DA3FF; background: #4DA3FF; }
.dsc__goal--far  .ring__n, .dsc__goal--far  .dsc__worth { color: #4DA3FF; }

.dsc__bar { display: block; height: 4px; margin-top: 4px; border-radius: 999px; background: var(--line); overflow: hidden; }
.dsc__bar i { display: block; height: 100%; border-radius: 999px; transition: width .4s var(--ease); }

/* The product a goal row is asking for, shown in the 52px marker slot.

   .dsc__ring is already a 52px grid with everything centred, and .ring is
   absolutely positioned inside it, so the picture drops into the middle of the
   arc without any further layout - the same slot the percentage used to sit
   in on rows that now show a photograph instead.

   Round, because the arc around it is, and because these plates are square
   crops of the flavour artwork - the same image_card the basket and the
   flavour picker use, so it is a picture the shopper has already seen. */
.dsc__pic {
  position: relative;
  width: 36px; height: 36px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--ink);
}

/* The mark for a goal that names no single product. One plate cannot stand
   for "any flavour", so the picture is dropped and this takes its place: the
   same 36px circle, dashed to read as a slot waiting to be filled rather
   than as a thing you already have. The column of goals keeps one rhythm
   whether or not there is a photo. */
.dsc__addring {
  position: relative;
  display: grid; place-items: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent);
}

/* "Add one of these": a dashed ring stands in for the arc there is no progress
   to draw, and a plus badge says what to do about it. */
.dsc__goal--product .dsc__pic {
  box-shadow: 0 0 0 1px rgb(var(--ink-rgb) / .9),
              0 0 0 2px color-mix(in srgb, #4DA3FF 42%, transparent);
}
.dsc__goal--product .dsc__ring::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1px dashed color-mix(in srgb, #4DA3FF 45%, transparent);
}
.dsc__plus {
  position: absolute;
  right: 1px; bottom: 1px;
  display: grid; place-items: center;
  /* 18, not 17. An odd-sized well centres an even-sized glyph on a half
     pixel, which is what blurred the cross inside it - see goalMark() for the
     same arithmetic on the larger one. */
  width: 18px; height: 18px;
  border-radius: 50%;
  color: var(--ink);
  background: #4DA3FF;
  border: 2px solid var(--surface);
}
.dsc__goal--product .dsc__worth { color: #4DA3FF; }

@supports not (color: color-mix(in srgb, red, blue)) {
  .dsc__goal--product .dsc__ring::before { border-color: var(--line); }
  .dsc__goal--product .dsc__pic { box-shadow: none; }
}
.dsc__worth {
  font-family: var(--mono); font-size: 13px; white-space: nowrap;
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid currentColor;
  background: color-mix(in srgb, currentColor 12%, transparent);
}
/* Below the split width the second column stacks under the basket.

   THE ARCHITECTURE CHANGED HERE, so the history is worth keeping.

   Three shapes were tried. A stacked two-row grid let main overflow its row
   and paint over the discounts. A single scrolling block fixed that but put
   the order button below the whole basket. Then a fixed-height column: main
   exactly one screenful, the list scrolling inside it, the totals and button
   pinned - which held the button on screen at the cost of squeezing the
   discounts panel into a scrollbar of its own.

   That last cost is the one that mattered. A panel with its own scrollbar,
   inside a drawer, on a 393px phone is two scrollbars in a hand's width, and
   it hid the very thing it exists to advertise. Worse, the whole arrangement
   only worked at a height it had been tuned against: the sums are 719 of
   drawer, and an iPhone leaves nearer 700 once Safari has taken its share, at
   which point the panel is over its cap and scrolling again.

   So the priority was made explicit instead of arithmetic:

     1. the discounts panel is never scrollable. It shows what it has.
     2. the products are never hidden behind it.
     3. the order button is reachable - by scrolling, if it has to be.

   The drawer scrolls as one column again, and this time the button does not
   get lost, because main GROWS to fill any spare height. Short basket: main
   stretches and the foot rests at the bottom of the drawer exactly as though
   it were pinned. Long basket, or a shop running four promotions, or a short
   screen: the column simply runs past the bottom and you scroll to it. No
   caps, no floors, no height that has to be guessed in advance - which is
   what made every earlier version fragile at a size it had not been measured
   at. */
@media (max-width: 900px) {
  .cart.is-wide { width: min(560px, 100%); }
  /* On top now, so the rule that separates it from the basket belongs under
     it rather than over it. */
  .cart__side { border-left: 0; border-bottom: 1px solid var(--line); }

  /* One scroll for the whole drawer. Flex rather than block so that `order`
     can lift the discounts above the basket without moving them in the
     markup, where they belong after it for a screen reader and for the
     desktop two-column split. */
  .cart__split.is-split {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Never scrolls, never shrinks, no cap. Whatever the shop is running, the
     panel shows all of it - that is the whole point of the change. */
  .cart__split.is-split .cart__side {
    order: -1;
    flex: 0 0 auto;
    max-height: none;
    overflow: visible;
  }

  /* Grows into spare height, never shrinks. This is what keeps the foot at
     the bottom of the drawer when the basket is short, so nothing looks like
     it is floating in the middle, while still letting the column run past the
     bottom when it is long. */
  .cart__split.is-split .cart__main {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
    height: auto;
    min-height: 0;
  }

  /* No inner scroll. The list is as tall as its contents and the split
     carries the scrolling for everything. The "more below" cue measures this
     element's own overflow, so it switches itself off here rather than
     needing to be told. */
  .cart__split.is-split .cart__body {
    flex: 1 0 auto;
    min-height: 0;
    overflow: visible;
  }
  .cart__split.is-split .cart__foot { flex: none; }

  /* "There is more basket below this."

     The fade over the tail of the list is the cue, and on its own it is only
     legible when a row happens to be cut by it. Whether one is is pure
     chance: the list takes the height left over after the totals, so on one
     phone the last visible row is sliced in half and on the next it ends
     flush with the edge and the fade has nothing to fade. A cue that works on
     some screens is not a cue.

     So it says so instead. The same element carries a chevron in a small
     round well sitting on the boundary, which does not care how the rows
     happen to land. It rides has-more, which is measured from the list's own
     scroll position - so it is there whenever there is something below, gone
     the moment you reach the end, and back if you scroll up again.

     Taller too: 44px was sized against a fade alone. */
  .cart__foot::before {
    height: 56px;
    background:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23E8EDE9' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center bottom 10px / 16px 16px no-repeat,
      linear-gradient(to top, var(--surface) 30%, rgb(var(--surface-rgb) / 0));
  }
}

.scrolls::-webkit-scrollbar,
.cart__body::-webkit-scrollbar,
.cart__side::-webkit-scrollbar,
.cart__split::-webkit-scrollbar,
.pick__list::-webkit-scrollbar { width: 10px; height: 10px; }

.scrolls::-webkit-scrollbar-track,
.cart__body::-webkit-scrollbar-track,
.cart__side::-webkit-scrollbar-track,
.cart__split::-webkit-scrollbar-track,
.pick__list::-webkit-scrollbar-track { background: transparent; }

/* The transparent border plus background-clip is what insets the thumb, so it
   reads as a floating pill rather than a bar wedged against the edge. */
.scrolls::-webkit-scrollbar-thumb,
.cart__body::-webkit-scrollbar-thumb,
.cart__side::-webkit-scrollbar-thumb,
.cart__split::-webkit-scrollbar-thumb,
.pick__list::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 2px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
  transition: background-color .18s var(--ease);
}

.scrolls:hover::-webkit-scrollbar-thumb,
.cart__body:hover::-webkit-scrollbar-thumb,
.cart__side:hover::-webkit-scrollbar-thumb,
.cart__split:hover::-webkit-scrollbar-thumb,
.pick__list:hover::-webkit-scrollbar-thumb { background: var(--muted); background-clip: content-box; }

.scrolls::-webkit-scrollbar-thumb:hover,
.cart__body::-webkit-scrollbar-thumb:hover,
.cart__side::-webkit-scrollbar-thumb:hover,
.cart__split::-webkit-scrollbar-thumb:hover,
.pick__list::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--accent) 70%, var(--muted));
  background-clip: content-box;
}

.scrolls::-webkit-scrollbar-thumb:active,
.cart__body::-webkit-scrollbar-thumb:active,
.cart__side::-webkit-scrollbar-thumb:active,
.cart__split::-webkit-scrollbar-thumb:active,
.pick__list::-webkit-scrollbar-thumb:active { background: var(--accent); background-clip: content-box; }

/* The stepper arrows at each end are the ugliest part of the default and are
   no use on a trackpad or a wheel. */
.scrolls::-webkit-scrollbar-button,
.cart__body::-webkit-scrollbar-button,
.cart__side::-webkit-scrollbar-button,
.cart__split::-webkit-scrollbar-button,
.pick__list::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

.scrolls::-webkit-scrollbar-corner,
.cart__body::-webkit-scrollbar-corner,
.cart__side::-webkit-scrollbar-corner,
.cart__split::-webkit-scrollbar-corner,
.pick__list::-webkit-scrollbar-corner { background: transparent; }

/* The page itself, so the window edge matches the panels. */
html { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
html::-webkit-scrollbar { width: 12px; }
html::-webkit-scrollbar-track { background: var(--ink); }
html::-webkit-scrollbar-thumb {
  background: var(--line); border: 2px solid transparent;
  background-clip: content-box; border-radius: 999px;
}
html::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }
html::-webkit-scrollbar-button { display: none; }

/* ==========================================================================
   Earned discounts: the gold treatment
   Money already won gets its own metal. The tiers underneath keep the
   progress palette, so the two sections read as "yours" and "not yet" before
   a single word is parsed.
   The technique is the one the checkout button uses: a gradient on the panel
   itself with an opaque face laid over the inside, leaving the gradient
   showing only as the border. Nothing bright ever sits behind text.
   ========================================================================== */
:root {
  --gold-1: #FFE9AE;   /* highlight */
  --gold-2: #F0C463;   /* body */
  --gold-3: #B8862F;   /* shadow */
}

.dsc--won {
  position: relative;
  border-color: transparent;
  background:
    linear-gradient(135deg,
      var(--gold-3) 0%,
      var(--gold-2) 26%,
      var(--gold-1) 44%,
      var(--gold-2) 62%,
      var(--gold-3) 100%);
}
/* The opaque face. inset:0 resolves against the padding box, so exactly the
   1px border ring is left showing the metal behind it. */
.dsc--won::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  border-radius: calc(var(--radius) - 1px);
  background:
    radial-gradient(120% 80% at 50% -20%,
      color-mix(in srgb, var(--gold-2) 13%, transparent), transparent 70%),
    var(--surface-2);
}
.dsc--won > * { position: relative; z-index: 1; }

/* The heading is the only place the gradient is used as ink, and it is large
   and bold enough to carry it. */
.dsc--won .dsc__h h3 {
  display: flex; align-items: center; gap: 7px;
  background: linear-gradient(100deg, var(--gold-1), var(--gold-2) 55%, var(--gold-1));
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.dsc__crown { color: var(--gold-2); flex: none; }

.dsc--won .dsc__row {
  background: color-mix(in srgb, var(--gold-3) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--gold-2) 26%, transparent);
}
.dsc--won .dsc__ico {
  color: var(--gold-2);
  background: color-mix(in srgb, var(--gold-2) 13%, transparent);
  border-color: color-mix(in srgb, var(--gold-2) 34%, transparent);
}
/* Plain solid gold, not a gradient: at 14px monospace a gradient fill turns
   the digits to mush and the amount is the one thing that must stay exact. */
.dsc--won .dsc__amt { color: var(--gold-1); font-weight: 500; }

/* --------------------------------------------------------- the sheen -- */
/* One sweep, the moment a discount is first won. It runs once and then the
   panel is still, because a reward that keeps re-announcing itself stops
   reading as a reward. */
.dsc--won.is-won::after {
  content: ''; position: absolute; inset: 0; z-index: 2;
  border-radius: var(--radius); pointer-events: none;
  background: linear-gradient(100deg,
    transparent 35%,
    color-mix(in srgb, var(--gold-1) 26%, transparent) 50%,
    transparent 65%);
  background-size: 280% 100%;
  animation: sheen 1.15s var(--ease) 1;
}
@keyframes sheen {
  from { background-position: 140% 0; opacity: 1; }
  to   { background-position: -40% 0; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .dsc--won.is-won::after { animation: none; content: none; }
}

/* ==========================================================================
   ORDER CONFIRMED

   Built to a supplied mockup, and built as its own screen rather than as the
   last panel of the checkout. Everything above it is a form; this is what is
   left when the form is finished, and it is allowed to look like nothing else
   on the site.

   The shape changes with the viewport rather than only the type sizes. On a
   phone it is the whole screen, edge to edge, because that is the composition
   the mockup is - a full-bleed field with a column of things down the middle.
   On a wide screen that field is 2:3 portrait art, and object-fit cover across
   a 16:9 window throws away both the leaves at the top and the gold waves at
   the bottom, which is most of what the picture is. So from 760px up the same
   screen becomes a card: the artwork keeps its proportions inside it, the card
   is centred in the viewport, and the page's own ink is the surround. One set
   of rules, two honest shapes.

   The site header is hidden throughout. The mark and the wordmark are inside
   the composition, and .hdr carries the same wordmark 60px above them.
   ========================================================================== */

/* The reveal is a class on <body>, set by checkout.js at the same moment it
   unhides the section - so if that JS never runs, neither does any of this and
   the page is simply the checkout it already was. */
body.is-done .hdr { display: none; }
body.is-done .co-wrap {
  max-width: none;
  padding: 0;
  /* Centred in the window rather than sitting under a header that is no longer
     there. On a phone the section is a screen tall anyway and this does
     nothing; on a wide one it is what places the card. */
  min-height: 100svh;
  display: grid;
  align-items: center;
}

.done {
  position: relative;
  /* Traps the backdrop's z-index -1 inside the section, the same way
     .btn--apple does for its specular. Without it the picture would paint
     behind the page rather than behind this screen's own content. */
  isolation: isolate;
  overflow: hidden;
  display: grid;
  place-items: center;
  /* width, not just max-width. .co-wrap is a grid and this is its only item,
     so it would normally stretch to the column - but the wide branch gives it
     margin-inline: auto to centre the card, and an auto margin turns stretch
     off and sizes the box to its own content instead. That made a 660px card
     555px wide, which is the width of the widest thing inside it. */
  width: 100%;
  min-height: 100svh;
  padding: clamp(var(--s7), 9vw, var(--s9)) var(--s5);
  background: var(--ink);
  text-align: center;
}
/* display: grid above outranks the UA sheet's display: none for [hidden], so
   the section has to be told again. This is the whole reason the screen would
   otherwise be visible before an order was placed. */
.done[hidden] { display: none; }

.done__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  /* max-width: none because the global img rule clamps every picture to its
     container, which on a backdrop sized in percentages does nothing useful
     and on an oversized one crops it to the wrong box. */
  max-width: none;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
}

.done__in {
  display: grid;
  justify-items: center;
  width: 100%;
  max-width: 460px;
}

/* ----------------------------------------------------------- the brand -- */
.done__brand { display: grid; justify-items: center; gap: var(--s4); margin: 0; }
.done__mark { width: clamp(88px, 24vw, 124px); height: auto; display: block; }
.done__word {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(27px, 7vw, 40px);
  line-height: 1;
  /* The tracking is what makes a wordmark out of a word. text-indent puts back
     the leading half of it, because letter-spacing only ever adds a gap AFTER
     each glyph and the last one leaves the line optically off-centre. */
  letter-spacing: .17em;
  text-indent: .17em;
  /* Metal rather than flat gold, out of the two golds the palette already
     holds. color first, so a browser without background-clip: text - or one
     that fails the @supports - keeps a legible wordmark instead of a
     transparent one. */
  color: var(--gold);
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .done__word {
    background: linear-gradient(160deg, var(--gold-deep) 4%, var(--gold) 30%,
                color-mix(in srgb, var(--gold) 42%, #FFF) 52%, var(--gold) 74%,
                var(--gold-deep) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* ------------------------------------------------------------ the seal -- */
.done__seal {
  position: relative;
  width: clamp(106px, 29vw, 136px);
  aspect-ratio: 1;
  margin-block: clamp(var(--s6), 8vw, var(--s8));
  display: grid;
  place-items: center;
}

.done__ring {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  border-radius: 999px;
  display: grid;
  place-items: center;
  border: 2px solid var(--gold);
  color: var(--gold);
  background: radial-gradient(circle at 50% 42%,
              rgb(var(--ink-rgb) / .55), rgb(var(--ink-rgb) / .88) 78%);
  box-shadow:
    0 0 38px -8px color-mix(in srgb, var(--gold) 62%, transparent),
    inset 0 0 26px -10px color-mix(in srgb, var(--gold) 70%, transparent);
}
.done__ring svg { width: 46%; height: auto; display: block; }

/* The trail of light that arrives with the seal.

   A conic gradient masked down to a ring, turned once. It is not a spinner:
   a spinner says "still working", and this screen exists to say the opposite,
   so the sweep runs a single revolution and leaves. */
.done__sweep {
  grid-area: 1 / 1;
  width: 176%;
  height: 176%;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  background: conic-gradient(from 0deg,
              transparent 0deg,
              transparent 186deg,
              color-mix(in srgb, var(--gold) 30%, transparent) 268deg,
              var(--gold) 336deg,
              color-mix(in srgb, var(--gold) 40%, #FFF) 354deg,
              transparent 360deg);
  -webkit-mask: radial-gradient(closest-side, transparent 63%, #000 67%,
                #000 73%, transparent 77%);
  mask: radial-gradient(closest-side, transparent 63%, #000 67%,
        #000 73%, transparent 77%);
}

/* The tick is drawn, not switched on. Dash length 24 covers the path's ~22.6
   units with a little to spare; the resting offset is 0, so if the animation
   never runs - no .is-done, reduced motion - the mark is simply there. */
.done__tick { stroke-dasharray: 24; stroke-dashoffset: 0; }

/* --------------------------------------------------------- the heading -- */
.done__h {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(31px, 8.4vw, 48px);
  line-height: 1.06;
  letter-spacing: .004em;
  margin: 0;
  color: var(--bone);
  text-wrap: balance;
}
/* em for the emphasis, upright for the face. See the note in checkout.php:
   the shop has no italic cut of Cormorant and a synthesised slant on a serif
   this high-contrast reads as a fault. */
.done__h em { font-style: normal; color: var(--accent); }

/* ---------------------------------------------------------- the divider -- */
.done__rule {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: min(100%, 300px);
  margin: clamp(var(--s5), 4.5vw, var(--s6)) 0 0;
  color: var(--gold);
}
.done__rule::before,
.done__rule::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(to right, transparent,
              color-mix(in srgb, var(--gold) 62%, transparent));
}
.done__rule::after {
  background: linear-gradient(to left, transparent,
              color-mix(in srgb, var(--gold) 62%, transparent));
}
.done__rule svg { width: 13px; height: 13px; flex: none; display: block; }

/* ------------------------------------------------------------- the lede -- */
.done__lede {
  margin: clamp(var(--s5), 4.5vw, var(--s6)) 0 0;
  font-size: clamp(15px, 4.2vw, 17px);
  line-height: 1.62;
  color: rgb(var(--bone-rgb) / .78);
  max-width: 32ch;
}
/* The hard break is an improvement only while both halves still fit on one
   line. Under 360px the first one does not, and a forced break there makes
   three ragged lines out of two even ones. */
@media (max-width: 359px) {
  .done__lede br { display: none; }
}

/* -------------------------------------------------- the order number -- */
.done__no {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: min(100%, 366px);
  min-height: 68px;
  margin: clamp(var(--s6), 6vw, var(--s7)) 0 0;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--gold) 34%, transparent);
  border-radius: 16px;
  background: rgb(var(--ink-rgb) / .52);
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  transition: border-color .22s var(--ease), background .22s var(--ease),
              transform .12s var(--ease);
}
.done__no:hover {
  border-color: color-mix(in srgb, var(--gold) 62%, transparent);
  background: rgb(var(--ink-rgb) / .68);
}
.done__no:active { transform: scale(.985); }
.done__no-i {
  flex: none;
  align-self: stretch;
  width: 66px;
  display: grid;
  place-items: center;
  color: var(--gold);
  border-right: 1px solid color-mix(in srgb, var(--gold) 26%, transparent);
}
.done__no-i svg { width: 24px; height: 24px; display: block; }
.done__no-v {
  flex: 1 1 auto;
  min-width: 0;
  /* Handed back out of the storefront-wide selection lock. That lock is about
     the shop's copy; an order number is the customer's own receipt, and on a
     desktop dragging across it is still the reflex even with a copy button
     sitting under the cursor. It is also what the button's fallback selects
     when the clipboard is not available. */
  -webkit-user-select: text;
  user-select: text;
  padding-inline: var(--s4);
  font-family: var(--mono);
  font-size: clamp(17px, 5vw, 22px);
  letter-spacing: .07em;
  text-indent: .07em;
  color: var(--accent);
}
/* A tag above the pill rather than a state on it: covering the number to say
   it has been copied hides the thing you just asked for. */
.done__no-said {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translate(-50%, 5px);
  padding: 5px var(--s3);
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
  background: rgb(var(--ink-rgb) / .94);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.done__no.is-said .done__no-said { opacity: 1; transform: translate(-50%, 0); }

/* ------------------------------------------------------- the way back -- */
/* Two classes, so this beats .btn--apple.btn--ghost's own border on
   specificity rather than on where it happens to sit in the file. */
.btn--apple.done__go {
  width: min(100%, 366px);
  min-height: 62px;
  margin-top: clamp(var(--s5), 4.5vw, var(--s6));
  gap: var(--s5);
  border-radius: 999px;
  border-color: color-mix(in srgb, var(--gold) 40%, transparent);
  color: var(--bone);
  box-shadow: 0 0 30px -12px color-mix(in srgb, var(--gold) 80%, transparent);
}
.btn--apple.done__go:hover {
  border-color: color-mix(in srgb, var(--gold) 76%, transparent);
  box-shadow: 0 0 34px -8px color-mix(in srgb, var(--gold) 72%, transparent);
}
.done__go svg { width: 20px; height: 20px; flex: none; color: var(--gold); }

/* ---------------------------------------------------------- the badges -- */
/* .done .done__badges, not .done__badges: the hero's own rules put these in
   two columns under 900px and cap them at 22rem, and one class would lose to
   that inside its media query. */
.done .done__badges {
  margin-top: clamp(var(--s7), 8vw, var(--s8));
  width: 100%;
  max-width: 100%;
}
/* A flat 10px, because the hero's size is not transferable here.

   .hbadges__label is clamp(8px, .62vw, 11px) - sized against the VIEWPORT,
   which is right where the row it was written for spans most of one. Inside a
   620px card the viewport says nothing about the space a label has: at a
   1280px window .62vw resolves to 7.9px, so the clamp floors it at 8 and four
   captions in a 429px column came out unreadably small. The column here is a
   known width, so the size can be one too. */
.done .hbadges__label { font-size: 10px; }

/* --------------------------------------------------------- the arrival -- */
/* One rise, staggered down the column, so the screen assembles rather than
   appearing all at once. fill-mode backwards, not both: the resting state of
   every one of these is already the animation's last frame, and `both` would
   leave the transform asserted afterwards - which is enough to defeat the
   press on the number pill, since an animated value outranks a declared one. */
@keyframes done-rise {
  from { opacity: 0; transform: translateY(15px); }
  to   { opacity: 1; transform: none; }
}
body.is-done .done__in > * {
  animation: done-rise .62s var(--ease) backwards;
  animation-delay: var(--d, 0s);
}
body.is-done .done__brand { --d: .04s; }
body.is-done .done__seal  { --d: .12s; }
body.is-done .done__h     { --d: .30s; }
body.is-done .done__rule  { --d: .38s; }
body.is-done .done__lede  { --d: .44s; }
body.is-done .done__no    { --d: .52s; }
body.is-done .done__go    { --d: .60s; }
body.is-done .done__badges{ --d: .68s; }

@keyframes done-sweep {
  0%   { opacity: 0;  transform: rotate(-130deg); }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0;  transform: rotate(320deg); }
}
body.is-done .done__sweep { animation: done-sweep 1.55s var(--ease) .22s backwards; }

@keyframes done-tick {
  from { stroke-dashoffset: 24; }
  to   { stroke-dashoffset: 0; }
}
body.is-done .done__tick { animation: done-tick .52s var(--ease) .52s backwards; }

@media (prefers-reduced-motion: reduce) {
  /* Everything above is arrival, not information. The screen says the same
     thing standing still. */
  body.is-done .done__in > *,
  body.is-done .done__sweep,
  body.is-done .done__tick { animation: none; }
  .done__sweep { display: none; }
}

/* ---------------------------------------------------- and on a short one -- */
/* The spacing above is sized for a handset of the last few years, where the
   column runs a little past the fold and the badges are the only thing you
   scroll to reach. On a 640 or 667pt screen that scroll starts one element
   sooner and takes Обратно към магазина with it - and that is the single
   control on this screen, so it has to be in reach without one.

   Height as well as width in the query: a 375pt phone held in landscape is
   short but this branch is not what it wants, and a tall one at the same width
   has no problem to solve. The air comes out of the picture-sized things, in
   proportion; the type is untouched. */
@media (max-width: 759px) and (max-height: 700px) {
  .done { padding-block: var(--s6); }
  .done__brand { gap: var(--s3); }
  .done__mark { width: clamp(72px, 20vw, 96px); }
  .done__seal {
    width: clamp(92px, 26vw, 110px);
    margin-block: var(--s5);
  }
  .done__no { margin-top: var(--s5); }
  .done .done__badges { margin-top: var(--s6); }
}

/* ------------------------------------------------- and on a wide screen -- */
@media (min-width: 760px) {
  /* Full screen, not a card.

     This used to be a 660px card centred in the window with the page's ink
     as the surround. The reasoning was the artwork: it is 2:3 portrait, and
     covering a 16:9 window crops the leaves off the top and the gold waves
     off the bottom, which is most of the picture. The card kept all of it.

     What it also did was leave two wide bands of flat black either side of a
     narrow panel, on the one screen that is meant to feel like an arrival.
     The picture is worth less than that. So the field goes edge to edge and
     the crop is paid for: a gradient in the page's own ink washes in from
     both sides, which hides where the artwork runs out and puts the fall-off
     under the column rather than at a visible seam.

     The content keeps the card's measure - 660 less its padding - because
     that width is what makes the four badge captions fit, and a line of text
     580px long is a line of text however wide the screen behind it is. */
  .done {
    max-width: none;
    width: 100%;
    min-height: 100svh;
    margin-inline: 0;
    /* Tighter than the card was. Full-bleed removed the card's own frame but
       not the reason the wide branch was cramped: a laptop is 720-900px tall
       and this column is most of it, so anything spent on outer padding is
       taken off the end of the screen - which is where the way onward is. */
    padding: var(--s6) var(--s5);
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  /* Sized to the old card's inner measure, so nothing inside had to change. */
  .done__in { max-width: 564px; }
  body.is-done .co-wrap { padding: 0; }

  /* The edges the artwork no longer reaches. Drawn over the picture and under
     everything else, so the composition fades into the page instead of
     stopping at a hard border. */
  /* Three explicit layers, because two of them used to share z-index: -1 and
     relied on paint order to sort themselves out; numbered now: photograph,
     wash, content. (The mark once needed a blend here to lose its opaque
     black plate over the artwork; it has a transparent ground now.) */
  .done__bg { z-index: -2; }
  .done::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: linear-gradient(to right,
      var(--ink) 0%,
      color-mix(in srgb, var(--ink) 55%, transparent) 22%,
      transparent 42%,
      transparent 58%,
      color-mix(in srgb, var(--ink) 55%, transparent) 78%,
      var(--ink) 100%);
  }

  /* Tighter than the phone, on purpose, and this is the one place the two
     shapes want different rhythm rather than the same rhythm scaled.

     A phone scrolls this screen by nature - there is one column and it is
     taller than any handset. A laptop does not: at 1280x720, the airy spacing
     put Обратно към магазина at y935, a clear 200px below the fold, so the
     screen's only way onward was somewhere you had to go looking for it. The
     air comes out of the gaps that were carrying the least - the space around
     the seal and above the badges - rather than off the type. */
  .done__seal { margin-block: var(--s6); }
  .done .done__badges { margin-top: var(--s7); }
  .done__no { margin-top: var(--s6); }
}

@supports not (color: color-mix(in srgb, red, blue)) {
  .done__ring { box-shadow: none; }
  .done__no,
  .btn--apple.done__go { border-color: var(--gold-deep); }
  .done__no-i { border-right-color: var(--gold-deep); }
  .done__rule::before,
  .done__rule::after { background: var(--gold-deep); }
}

/* ==========================================================================
   RATINGS, THE PRODUCT PAGE AND REVIEWS
   ==========================================================================

   Added with the "Научи повече" feature. Three things live here: the star row
   that appears in both product widgets on the shop front, the product page
   itself, and the review section on it.

   The register is the one the rest of the site already uses - hairline
   borders on --line, --surface-2 panels, gold for anything earned and
   --accent for anything the product owns - so the new pages read as the same
   shop rather than as a bolted-on review plugin.
   ========================================================================== */

/* --------------------------------------------------------------- stars -- */
/* Two identical rows of five, one hollow and one gold, with the gold row
   clipped to a percentage. That is what draws 4.3 as four stars and a third
   rather than rounding it to four or five - and rounding is not a small lie
   when three reviews separate 4.4 from 4.6.

   Sized in em off the element's own font-size, so one modifier class changes
   the whole row and nothing has to be measured twice. */
.stars {
  position: relative;
  display: inline-block;
  line-height: 0;
  white-space: nowrap;
  font-size: 15px;          /* the em box every star is measured against */
  flex: none;
}
.stars__off,
.stars__on { display: block; }
.stars__on {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--fill, 0%);
  overflow: hidden;
}
.stars svg {
  width: 1em; height: 1em;
  display: inline-block; vertical-align: top;
  margin-right: .1em;
}
.stars svg:last-child { margin-right: 0; }
/* Hollow, not grey-gold. An unfilled star has to read as "nothing here",
   which a dimmed version of the filled colour never quite does. */
.stars__off svg { fill: rgb(var(--bone-rgb) / .15); }
.stars__on  svg { fill: var(--gold); }

.stars--sm { font-size: 13px; }
.stars--md { font-size: 17px; }
.stars--lg { font-size: 23px; }

/* ------------------------------------------- the row inside the widget -- */
/* Under the flavour's name and its gold rule, above the tasting note - which
   is where a shopper's eye is already travelling, and where every shop that
   sells on reputation puts it.

   It is one line high on purpose. The widget is an overlay pinned to a
   printed artboard: on the flavour plate it is anchored by its floor and
   grows UP into the empty top band, and on the oil plate it grows DOWN into
   21% of spare plate. One row fits in both. A second button would not. */
.flav__meta {
  display: flex; align-items: center;
  gap: var(--s3);
  margin-bottom: .9em;
}
/* THE RATING, AS ONE STATEMENT.
   stars · 4,3 · 3 отзива
   The middle dots are what turn three loose fragments into a single line to
   be read left to right. Spacing alone had to do that job before, and spacing
   cannot: at this size the gaps between the stars and the gap after them are
   nearly the same, so the row read as a pile of small things rather than as a
   sentence. The dot is the convention every shop and every app store uses for
   precisely this.
   Drawn with ::before on each element AFTER the first, so the separator count
   is always right by construction - no dot can be left stranded at the end,
   and none has to be added or removed when a piece is hidden. */
.flav__meta-r {
  display: inline-flex; align-items: center; gap: .34em;
  color: var(--muted);
  font-size: 12.5px;
  min-width: 0;
}
.flav__meta-r > * + *::before,
.pd__rate > * + *::before {
  content: '·';
  /* A middle dot is a small glyph in a small type size, and at .3 alpha it was
     effectively invisible - which left the row looking exactly as unseparated
     as it did before. Set larger than its surrounding text and at more than
     half strength so it actually divides the line.
     line-height: 0 with vertical-align: middle is what stops the enlarged
     glyph adding leading and pushing the row taller. */
  font-size: 1.5em;
  line-height: 0;
  vertical-align: middle;
  margin-right: .34em;
  color: rgb(var(--bone-rgb) / .6);
  font-weight: 400;
}
/* The score is the fact; the count is the evidence behind it. They should not
   be the same weight or the same colour. */
.flav__meta-r b {
  color: var(--bone);
  font-family: var(--mono);
  font-weight: 600;
  letter-spacing: -.01em;
}
/* > span:not(.stars), NOT `span`.
   The stars are built from three nested spans - the row, the hollow layer and
   the gold layer - so a bare `span` here reached inside them. The gold layer
   is clipped to a percentage with overflow: hidden, and handing THAT
   text-overflow: ellipsis made the browser draw "..." at the clip edge:
   the three dots that appeared inside the fourth star on a 4,0 rating.
   Only the count needs to be truncated, and it is the one direct child that
   is not the stars. */
.flav__meta-r > span:not(.stars) {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A hidden <b> still counts as a sibling, so with no reviews the row resolves
   to "stars · Няма отзиви" on its own - no branch needed in the markup. */
.flav__meta-r b[hidden] { display: none; }

/* A CUSTOMER'S LINE, UNDER THE SCORE.
   The stars say how much; this says what, in the words of one of the people
   behind the number - a phrase from one published review, drawn at random
   on each visit, signed with the name the review carries. Set in the serif
   the reviews' own titles wear, italic, so it reads as a voice and not as
   copy the shop wrote about itself. One line: it is a glimpse, and the
   reviews themselves are one press away. */
.flav__quote {
  display: flex; align-items: baseline; gap: .4em;
  margin: -.55em 0 .9em;
  font-family: var(--display);
  font-style: italic;
  font-size: 15.5px;
  line-height: 1.3;
  color: rgb(var(--bone-rgb) / .8);
  min-width: 0;
}
/* the words give way, the name never does */
.flav__quote q { quotes: '„' '“'; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.flav__quote i {
  flex: none;
  font-family: var(--body);
  font-style: normal;
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: .01em;
}
.flav__quote[hidden] { display: none; }
.flav__stage--card .flav__quote { font-size: clamp(14px, .95cqw, 16px); margin: -.4em 0 .8em; }
.flav__stage--card .flav__quote i { font-size: clamp(11px, .7cqw, 12px); }
.pd__quote { margin: .35em 0 1em; }
.pd__quote q { white-space: normal; }

/* "Научи повече", under the buy button.

   Not a second pill. The primary control on this card is a large filled
   button, and an outlined pill directly beneath it reads as two buttons
   stacked and asks the eye to choose between them - on a card about 300px
   wide that is a lot of furniture. A centred text link with an arrow is
   unmistakably a control, costs a third of the height, and leaves the buy
   button as the only loud thing on the card, which is correct.

   Full width so the whole row is the target rather than the words alone -
   that matters on a phone, where this sits under a thumb. */
.morelink {
  display: inline-flex; align-items: center; justify-content: center; gap: .4em;
  width: 100%;
  min-height: 34px;
  padding: .3em .6em;
  border-radius: 999px;
  color: var(--muted);
  font-size: 13px; font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: color .22s var(--ease), background .22s var(--ease);
}
.morelink svg {
  width: 1.05em; height: 1.05em; flex: none;
  transition: transform .22s var(--ease);
}
.morelink:hover { color: var(--bone); background: rgb(var(--bone-rgb) / .05); }
.morelink:hover svg { transform: translateX(3px); color: var(--accent); }
.morelink:active { transform: scale(.985); }
.morelink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Inside the artboard widgets everything is measured in container query
   units, because the card is a percentage of a plate and scales with it.
   These follow the same ladder the name, the specs and the button use. */
.flav__stage--card .flav__meta { margin-bottom: .85em; }
.flav__stage--card .flav__meta-r { font-size: clamp(11px, .72cqw, 15px); gap: .4em; }
.flav__stage--card .stars { font-size: clamp(12px, .86cqw, 18px); }
.flav__stage--card .morelink {
  font-size: clamp(11px, .7cqw, 14px);
  min-height: clamp(28px, 2.1cqw, 40px);
}

/* Below 1024 both plates become a window with the interface stacked beneath,
   at real pixel sizes rather than plate percentages. */
@media (max-width: 1024px) {
  .flav__stage--card .flav__meta-r { font-size: 12.5px; }
  .flav__stage--card .stars { font-size: 14px; }
  .flav__stage--card .morelink { font-size: 12.5px; min-height: 38px; }
}

/* The row is one short line again now that the link has moved out of it -
   stars, a score and a word - so there is nothing left to wrap. */

/* ========================================================== product page ==
   product.php. Two halves: what the product is, and what buyers said.
   ========================================================================= */

.pd { padding-bottom: var(--s9); }

/* ==========================================================================
   The product switcher (partials/pswitch.php): Капсули | Масла - centred
   under the header on the product page, under the hero on the capsules
   page, which scrolls to it on arrival from the other one. The phone app's
   segmented control at desktop size: a glass trough, a lit gold pill that
   glides under the product on show, the two cut-outs on the tabs; a light
   that follows the pointer on a tab, the picture lifting under it, a spring
   on the press. Desktop only - on a phone the app's own tabs do this, and
   the product pages there stay as they are.
   ========================================================================== */
.pswitch-wrap {
  display: flex; justify-content: center;
  padding: 14px 24px 0;
}
/* under a hero, a little more air above */
.hero + .pswitch-wrap { padding-top: clamp(22px, 2.6vw, 36px); }
.pswitch {
  --accent: var(--gold);
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: 1fr 1fr;
  width: min(100%, 460px);
  padding: 6px; border-radius: 999px;
  /* the other page's link points at this id: the page arrives scrolled so
     this stands 14px under the header, where the one just pressed was */
  scroll-margin-top: calc(var(--hdr-h) + 14px);
  background: linear-gradient(180deg, rgb(var(--surface-rgb) / .94), rgb(var(--ink-rgb) / .97));
  border: 1px solid rgb(var(--bone-rgb) / .12);
  box-shadow: 0 18px 48px rgb(0 0 0 / .55), inset 0 1px 0 rgb(255 255 255 / .06);
  view-transition-name: pswitch;
}
.pswitch__glide {
  position: absolute; top: 6px; bottom: 6px; left: 6px; width: calc(50% - 6px);
  border-radius: 999px; z-index: 0; overflow: hidden; will-change: transform;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 84%, #fff), color-mix(in srgb, var(--accent) 88%, #000));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent),
              0 10px 30px color-mix(in srgb, var(--accent) 38%, transparent),
              inset 0 1px 0 rgb(255 255 255 / .35);
  transition: box-shadow .35s var(--ease);
  /* named, so that between the two product pages it is one element that
     travels from the tab it was under to the tab it is under - the glide
     is the browser's, across the navigation */
  view-transition-name: pswitch-glide;
}
/* brighter while it travels */
.pswitch.is-gliding .pswitch__glide {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 70%, transparent),
              0 14px 40px color-mix(in srgb, var(--accent) 60%, transparent),
              inset 0 1px 0 rgb(255 255 255 / .5);
}
/* the sheen that crosses the pill when it lands */
.pswitch__glide i {
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 38%, rgb(255 255 255 / .55) 50%, transparent 62%);
  transform: translateX(-130%);
}
.pswitch.is-landed .pswitch__glide i { animation: pswitch-sheen .8s var(--ease); }
@keyframes pswitch-sheen { to { transform: translateX(130%); } }
/* before the script measures, the pill sits under the current tab by CSS */
.pswitch[data-on="oil"]:not(.is-placed) .pswitch__glide { transform: translateX(100%); }

.pswitch__tab {
  position: relative; z-index: 1; isolation: isolate; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 56px; padding: 0 18px; border-radius: 999px;
  color: rgb(var(--bone-rgb) / .74); text-decoration: none;
  font: 600 16px/1 var(--body); letter-spacing: -.01em;
  -webkit-tap-highlight-color: transparent;
  transition: color .16s var(--ease);
}
/* the light under the pointer, as on the site's buttons */
.pswitch__tab::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; z-index: -1; pointer-events: none;
  background: radial-gradient(130px circle at var(--mx, 50%) var(--my, 50%), rgb(255 255 255 / .13), transparent 62%);
  opacity: 0; transition: opacity .25s var(--ease);
}
.pswitch__tab:hover::before { opacity: 1; }
.pswitch__tab[aria-current="page"]::before { background: radial-gradient(130px circle at var(--mx, 50%) var(--my, 50%), rgb(255 255 255 / .3), transparent 62%); }
.pswitch__tab:hover { color: #fff; }
.pswitch__tab[aria-current="page"], .pswitch__tab.is-going { color: var(--ink); }
.pswitch__tab:focus-visible { outline: 2px solid var(--gold-soft); outline-offset: 3px; }
.pswitch__tab span { transition: transform .35s cubic-bezier(.22, 1, .36, 1); }
.pswitch__tab:hover span { transform: translateX(2px); }
.pswitch__img {
  height: 34px; width: auto; display: block; flex: none;
  transition: transform .32s cubic-bezier(.22, 1, .36, 1), filter .2s var(--ease);
}
.pswitch__tab:hover .pswitch__img { transform: translateY(-3px) rotate(-5deg) scale(1.08); }
.pswitch__tab.is-going .pswitch__img { transform: translateY(-2px) scale(1.1); }
.pswitch__tab[aria-current="page"] .pswitch__img { filter: drop-shadow(0 1px 0 rgb(255 255 255 / .35)); }
@media (max-width: 940px) { .pswitch-wrap { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .pswitch__tab, .pswitch__tab span, .pswitch__img, .pswitch__glide, .pswitch__tab::before { transition: none; }
  .pswitch__tab:hover .pswitch__img, .pswitch__tab:hover span { transform: none; }
  .pswitch.is-landed .pswitch__glide i { animation: none; }
}
/* The two product pages cross-fade into each other on the way between
   them (and any other page into any other), with the switcher held in
   place by its name above - in the browsers that know cross-document view
   transitions; elsewhere the pages simply follow each other. */
@media (min-width: 941px) and (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .18s; }
::view-transition-old(pswitch), ::view-transition-new(pswitch) { animation-duration: .14s; }
/* the pill's journey: the group's position tween is the glide, on the
   site's spring; the two pictures of it are the same, so no cross-fade */
/* .3s, and the page's own glide started at the press - whatever is left
   of the journey when the old page's last frame is taken finishes here */
::view-transition-group(pswitch-glide) { animation-duration: .3s; animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
::view-transition-old(pswitch-glide) { animation: none; opacity: 0; }
::view-transition-new(pswitch-glide) { animation: none; opacity: 1; }

/* The plate's floor. Without it the card is only as tall as its copy, the art
   column collapses to that, and a 3:2 photograph gets cropped to a letterbox
   strip. 560 is what .flav__stage uses, so the two cards stand the same
   height as each other. */
.pdplate { min-height: clamp(420px, 42vw, 560px); }

/* The cart pill in the header. Not a drawer - there is no drawer on this page
   - just a count and a way to the checkout, hidden until it has something to
   report. */
/* THE WAY BACK.
   First in the bar, ahead of the wordmark. That is where a back control
   belongs - it is where the browser's own is, where both phone platforms put
   theirs, and where a breadcrumb starts. It used to sit top right, which is
   the corner every shop reserves for the basket.
   A bordered pill, not grey text: it is the only way off this page apart from
   the browser chrome, so it should not be the quietest thing in the header.
   Gold on hover rather than the product's accent - this control belongs to
   the site, not to the flavour being looked at, and on the cherry page an
   accent-red back button reads as a warning. */
.backbtn {
  display: inline-flex; align-items: center; gap: 8px;
  flex: none;
  min-height: 38px;
  padding: 0 15px 0 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgb(var(--bone-rgb) / .03);
  color: var(--bone);
  font-size: 14px; font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color .22s var(--ease), background .22s var(--ease),
              color .22s var(--ease), transform .16s var(--ease);
}
.backbtn svg {
  width: 16px; height: 16px; flex: none;
  transition: transform .22s var(--ease);
}
.backbtn:hover {
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
  color: var(--gold-soft);
}
/* The arrow leads the way out. */
.backbtn:hover svg { transform: translateX(-3px); }
.backbtn:active { transform: scale(.97); }
.backbtn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.backbtn__short { display: none; }

/* The basket keeps the right-hand corner it has everywhere else. On the shop
   front .hdr__nav's own auto margin pushes the whole right-hand group over;
   these pages have no nav, so the control carries it itself. */
.co-page .cartbtn { margin-left: auto; }

@media (max-width: 700px) {
  /* The bar now carries three things. The label sheds its destination and
     keeps its verb; the arrow in front of it says the rest. */
  .backbtn { padding: 0 13px 0 10px; font-size: 13.5px; }
  .backbtn__long { display: none; }
  .backbtn__short { display: inline; }
}
@media (max-width: 420px) {
  /* Tightest case: the wordmark, the arrow and the basket. The label goes and
     the pill becomes a round target, which is the shape a bare arrow wants. */
  .backbtn { padding: 0; width: 38px; justify-content: center; }
  .backbtn__short { display: none; }
}
/* .pd-cart is gone. It was a second, differently drawn basket control for
   the product pages - a trolley and a bare number where the shop front has
   a bag, a word and a count. Both are cart_button() now. */

/* ------------------------------------------------------- the plate ----- */
/* One card, the photograph flush to its left edge, the copy in a panel beside
   it behind a hairline. This is .flav__stage's arrangement, and it is the same
   arrangement for the same reason: a picture inset inside a padded card floats
   with dead surface above and below it, and the card ends up looking big and
   empty at once. Flush, the picture sets the height and there is nothing left
   over to look empty.
   The first version of this page was a bare two-column grid with the photo
   letterboxed inside a square box under a drop-shadow. It had no frame, no
   edge and none of the site's language, which is what made it read as
   unstyled next to every other page. */
.pdplate {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
  gap: 0;
  align-items: stretch;
  margin-top: clamp(20px, 3vw, 44px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface);
  /* The lift the shop front's cards have. Without it the card sits flat on a
     black page and the border is the only thing saying it is a card at all. */
  box-shadow: 0 28px 70px rgb(0 0 0 / .45);
}

/* The product's own colour, bled in from the photograph's edge so the picture
   and the panel belong to one another instead of being two rectangles that
   happen to touch. --accent is set per product on <body>, so grape glows
   purple and the oil glows gold with no second rule. */
.pdplate__glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(680px circle at 42% 42%,
    color-mix(in srgb, var(--accent) 15%, transparent) 0%, transparent 66%);
}

.pdplate__art { position: relative; z-index: 2; min-width: 0; background: var(--ink); }
.pdplate__art img {
  display: block;
  width: 100%; height: 100%;
  /* cover, with the card's own min-height below deciding how much is taken
     off the sides. The photography is 3:2 with the cartridge lying across the
     middle, so a centre crop keeps the product and trims background. */
  object-fit: cover;
  object-position: center;
}
/* The photograph's inside edge, softened into the panel so the seam is not a
   hard vertical line down the middle of the card. */
.pdplate__art::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 72%, rgb(var(--surface-rgb) / .55) 100%);
  pointer-events: none;
}

/* THE RIGHT SIDE. A strip - name and rating on one line, price and button
   on the next - and the four cards under it in two columns. It is a column
   of its own inside the card, behind the hairline, padded like the panel it
   replaces; the strip is kept short so the cards, which are what the side
   is for, start within the first screen. */
.pdplate__side {
  position: relative; z-index: 2;
  min-width: 0;
  display: flex; flex-direction: column; gap: clamp(18px, 2vw, 26px);
  padding: clamp(20px, 2.6vw, 36px);
  border-left: 1px solid var(--line);
}
.pd__strip { display: flex; flex-direction: column; }
.pd__strip .eyebrow { margin-bottom: var(--s2); }

/* The name and its score on one line, the score sitting on the name's
   baseline. Below 1180px the line is too short for both and the score drops
   under the name, still left-aligned, still before the rule. */
.pd__title {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4) var(--s5);
  flex-wrap: wrap;
}
/* The name is the shop front's own .flav__name, only larger - this is an h1
   on a page about one product, not a label inside a switcher. Smaller than
   it was, because it now shares its line and its column. */
.pd__name { font-size: clamp(30px, 2.9vw, 42px); margin-bottom: 0; line-height: 1.05; }
.pdplate__side .flav__rule { margin: var(--s3) 0 var(--s4); }

/* The rating, a link to the reviews behind it. */
.pd__rate {
  display: inline-flex; align-items: center; gap: var(--s3);
  padding: 4px 0;
  color: var(--muted);
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
  transition: color .22s var(--ease);
}
.pd__rate b {
  color: var(--bone);
  font-family: var(--mono); font-size: 16px; font-weight: 600;
}
/* Same trap as .flav__meta-r above: a bare `span` reached into the stars and
   underlined all three layers on hover. */
.pd__rate > span:not(.stars) { border-bottom: 1px solid transparent; transition: border-color .22s var(--ease); }
.pd__rate:hover { color: var(--bone); }
.pd__rate:hover > span:not(.stars) { border-bottom-color: rgb(var(--bone-rgb) / .3); }
.pd__rate-none { color: var(--muted); }

/* The price and its tag on one line, the button full width under them -
   the shop front's own arrangement (.flav__buy), and the one that holds at
   every width: a button that shared the price's line wrapped under it
   alone at the far end as soon as the column narrowed. */
.pd__buy { display: grid; gap: var(--s4); }
.pd__price-row { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; }
.pd__buy .flav__price { align-items: baseline; }
.pd__buy .flav__price-eur { font-size: clamp(24px, 2.1vw, 30px); }
.pd__buy .flav__price-alt { font-size: 14px; }
.pd__buy .tag--check { position: static; font-size: 10.5px; padding: .45em .9em; }
.pd__buy .btn--apple { width: 100%; min-height: 52px; }

/* The button's own confirmation. Gold rather than accent-coloured, because
   "done" is the brand's colour everywhere else on this site - and a green tick
   on the green Natural page would say nothing at all. */
#pd-add.is-added { background: var(--gold); color: var(--ink); }

@media (max-width: 940px) {
  /* Picture on top, everything else beneath. The photograph is what
     identifies the page, and reading a name before seeing the thing it
     names is the wrong way round. A fixed aspect here because the side is
     no longer beside it to lend the column a height. */
  .pdplate { grid-template-columns: minmax(0, 1fr); }
  .pdplate__art { aspect-ratio: 3 / 2; }
  .pdplate__side { border-left: 0; border-top: 1px solid var(--line); }
}
@media (max-width: 560px) {
  .pdplate__art { aspect-ratio: 4 / 3; }
}

/* ---------------------------------------------------------- info cards -- */
.pd__h2 {
  font-family: var(--display);
  font-size: clamp(26px, 3vw, 40px);
  font-weight: 700;
  margin-bottom: var(--s6);
}

.pd__cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s3);
}
@media (max-width: 560px) { .pd__cards { grid-template-columns: minmax(0, 1fr); } }
.pdc {
  position: relative;
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  overflow: hidden;
  transition: border-color .28s var(--ease), transform .28s var(--ease),
              box-shadow .28s var(--ease);
}
/* A gold hairline along the top edge, fading out - the same flourish the
   flavour name carries under it on the shop front, so these cards are
   recognisably from the same set rather than four grey boxes. */
.pdc::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, var(--gold), transparent 70%);
  opacity: .5;
  transition: opacity .28s var(--ease);
}
.pdc:hover {
  border-color: color-mix(in srgb, var(--accent) 38%, var(--line));
  transform: translateY(-3px);
  box-shadow: 0 16px 34px rgb(0 0 0 / .35);
}
.pdc:hover::before { opacity: 1; }
/* The icon in a tinted disc rather than loose on the surface: at 22px a
   hairline glyph floating in a corner reads as a stray mark. */
.pdc__ico {
  display: grid; place-items: center;
  width: 38px; height: 38px;
  margin-bottom: var(--s3);
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--gold) 26%, transparent);
  background: color-mix(in srgb, var(--gold) 8%, transparent);
  color: var(--gold);
}
.pdc__ico svg { width: 18px; height: 18px; display: block; }
.pdc h3 {
  font-size: 15px; font-weight: 600;
  margin-bottom: var(--s2);
  color: var(--bone);
}
.pdc p { color: var(--muted); font-size: 13.5px; line-height: 1.62; }
/* The one card that now has evidence links to it. Underlined, because in a
   paragraph of grey the link is the only thing that must look like one. */
.pdc__go { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgb(var(--gold-rgb) / .5); }
.pdc__go:hover { color: var(--gold-soft); text-decoration-color: currentColor; }

@media (max-width: 880px) {
  .pd__hero { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  /* Picture first on a phone: it is what identifies the page, and reading a
     name before seeing the thing it names is the wrong way round. */
  .pd__art { max-width: 380px; margin-inline: auto; }
}

/* --------------------------------------------------------- description -- */
/* THE SPREAD. One card in the plate's language - the surface, the hairline
   border, the large radius, the lift, the product's colour bled in from a
   corner - in two columns. The rail on the left carries what a reader wants
   at a glance and stays put while the text scrolls past it; the text on the
   right is set to be read: a measure of about 64 characters, the body face
   at reading size, a generous leading, the chapters numbered in gold. */
.pd__desc { padding-top: clamp(28px, 4.4vw, 64px); }
.desc {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(260px, 34%) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 80px);
  padding: clamp(24px, 3.6vw, 56px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: 0 28px 70px rgb(0 0 0 / .45);
  /* clip, not hidden: hidden would make the card a scroll container and the
     rail's sticky would stick to a box that never scrolls */
  overflow: clip;
}
.desc__glow {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(560px circle at 0% 0%, color-mix(in srgb, var(--accent) 12%, transparent) 0%, transparent 62%),
    radial-gradient(720px circle at 100% 100%, color-mix(in srgb, var(--gold) 7%, transparent) 0%, transparent 60%);
}
/* the gold hairline along the top edge the cards wear, here on the spread */
.desc::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 1px; z-index: 1;
  background: linear-gradient(90deg, var(--gold), transparent 60%);
  opacity: .55;
}

/* ---- the rail ---- */
/* Sticky under the header, so the headline and the formula keep the reader
   company down the whole text. */
.desc__rail { min-width: 0; align-self: start; position: sticky; top: calc(66px + 28px); }
.desc__rail-in { display: flex; flex-direction: column; }
.desc__rail .eyebrow { margin-bottom: var(--s3); }
.desc__lead {
  font-family: var(--display);
  font-size: clamp(24px, 2.05vw, 32px); font-weight: 600; line-height: 1.22;
  letter-spacing: -.005em;
  color: var(--bone);
  margin: 0 0 var(--s4);
  text-wrap: balance;
}
.desc__rail .flav__rule { margin-bottom: var(--s5); }
.desc__label {
  margin: 0 0 var(--s3);
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--gold-rgb) / .85);
}
/* The formula as tiles: the component's name in the mono, its part in a
   line of grey under it. Glass on the surface, a hairline each, the same
   corner as the info cards; two to a row. */
.desc__tiles {
  list-style: none; margin: 0 0 var(--s6); padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3);
}
.desc__tile {
  position: relative;
  display: flex; flex-direction: column; gap: 5px;
  padding: 13px 14px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: rgb(var(--bone-rgb) / .025);
  transition: border-color .28s var(--ease), background .28s var(--ease), transform .28s var(--ease);
}
.desc__tile::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 1px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--gold) 70%, transparent), transparent 80%);
  opacity: .5; transition: opacity .28s var(--ease);
}
/* an odd fifth tile takes the whole last row rather than half of it */
.desc__tile:last-child:nth-child(odd) { grid-column: 1 / -1; }
.desc__tile:hover { border-color: color-mix(in srgb, var(--accent) 34%, var(--line)); background: rgb(var(--bone-rgb) / .045); transform: translateY(-2px); }
.desc__tile:hover::before { opacity: 1; }
.desc__tile b {
  font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: .06em;
  color: var(--bone);
}
.desc__tile span { font-size: 12.5px; line-height: 1.5; color: var(--muted); }
/* The pack: a small boxed block on the card's second surface. */
.desc__pack {
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.desc__pack .desc__label { margin-bottom: var(--s2); }
.desc__pack p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--bone); }
.desc__pack p + p { margin-top: 2px; color: var(--muted); }

/* ---- the text ---- */
.desc__body { min-width: 0; }
.prose {
  position: relative;
  max-width: 64ch;
  color: rgb(var(--bone-rgb) / .8);
  font-size: clamp(15.5px, 1.05vw, 17px);
  line-height: 1.75;
  counter-reset: chapter;
}
.prose > * + * { margin-top: 1.05em; }
/* the opening paragraph, a shade larger and in bone: the text's own lead-in
   now that the headline stands in the rail */
.prose > p:first-child {
  font-size: 1.12em; line-height: 1.6;
  color: rgb(var(--bone-rgb) / .92);
}
/* Chapters: a gold number in the mono, a hairline running from it, the
   title in the serif under. The number is the counter's, so the copy can
   gain or lose a chapter without anybody renumbering. */
.prose h3 {
  position: relative;
  margin-top: 2.4em;
  padding-top: 1.35em;
  font-family: var(--display);
  font-size: clamp(22px, 1.75vw, 27px); font-weight: 600; line-height: 1.22;
  color: var(--bone);
  counter-increment: chapter;
}
.prose h3::before {
  content: counter(chapter, decimal-leading-zero);
  position: absolute; left: 0; top: 0;
  font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: .18em;
  line-height: 1;
  color: var(--gold);
}
.prose h3::after {
  content: ''; position: absolute; left: 34px; right: 0; top: 5px; height: 1px;
  background: linear-gradient(90deg, rgb(var(--gold-rgb) / .45), transparent 70%);
}
.prose h3 + * { margin-top: .75em; }
.prose ul { list-style: none; padding: 0; margin-top: .9em; }
.prose li { position: relative; padding-left: 1.5em; }
.prose li + li { margin-top: .5em; }
/* the gold mark before each item, the site's bullet everywhere else */
.prose li::before {
  content: ''; position: absolute; left: .15em; top: .78em;
  width: .42em; height: .42em; border-radius: 50%;
  background: var(--gold); opacity: .8;
}
.prose li b { color: var(--bone); font-weight: 600; }

/* ---- the fold ---- */
/* product.js measures where the last block kept ends and holds the text at
   that height; the last lines dissolve into the card's surface, and under
   them the "Покажи още" pill sits on a hairline that runs the width of the
   text, three gold dots above it. Growing is a height animation in the
   script; the gradient fades with it. is-clamped is only ever added there:
   without the script there is no fold and the whole text stands. */
.pd__prose.is-clamped { overflow: hidden; }
/* The dissolve is a mask, not a painted gradient: the card's ground is not
   one colour (the glow tints it) and a strip of paint over it showed as a
   box. The mask is 170px taller than the text and slides: shifted up, its
   transparent end lies over the text's last lines; at rest it lies past
   the bottom and everything shows. mask-position animates, so the dissolve
   clears as the text grows. */
.pd__prose {
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 170px), transparent 100%);
          mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 170px), transparent 100%);
  -webkit-mask-size: 100% calc(100% + 170px); mask-size: 100% calc(100% + 170px);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: 0 0; mask-position: 0 0;
  transition: -webkit-mask-position .6s var(--ease), mask-position .6s var(--ease);
}
.pd__prose.is-clamped { -webkit-mask-position: 0 -170px; mask-position: 0 -170px; }
.pd__more {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: var(--s3);
  max-width: 64ch; margin-top: var(--s2); padding-top: var(--s2);
}
.pd__more[hidden] { display: none; }
.pd__more-dots {
  font-family: var(--mono); font-size: 16px; letter-spacing: .32em; line-height: 1;
  color: rgb(var(--gold-rgb) / .7);
  padding-left: .32em;
  transition: opacity .3s var(--ease), transform .3s var(--ease), height .3s var(--ease);
}
.pd__more.is-open .pd__more-dots { opacity: 0; transform: translateY(-6px); height: 0; overflow: hidden; }
/* the hairline the button sits on */
.pd__more::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 22px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 18%, var(--line) 82%, transparent);
}
.pd__more-btn {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding: 0 var(--s5) 0 var(--s6);
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
  border-radius: 999px;
  background: var(--surface);
  color: var(--bone);
  font: 500 14.5px/1 var(--body); letter-spacing: .01em;
  cursor: pointer;
  box-shadow: 0 8px 24px rgb(0 0 0 / .35), inset 0 1px 0 rgb(255 255 255 / .05);
  transition: border-color .22s var(--ease), color .22s var(--ease), background .22s var(--ease), transform .12s var(--ease), box-shadow .22s var(--ease);
}
.pd__more-btn:hover {
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  background: color-mix(in srgb, var(--gold) 9%, var(--surface));
  color: var(--gold-soft);
  box-shadow: 0 12px 30px rgb(0 0 0 / .4), 0 0 0 4px color-mix(in srgb, var(--gold) 8%, transparent);
}
.pd__more-btn:active { transform: scale(.97); }
.pd__more-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.pd__more-btn svg { color: var(--gold); transition: transform .35s var(--ease); }
.pd__more-btn[aria-expanded="true"] svg { transform: rotate(180deg); }

@media (max-width: 940px) {
  /* one column: the rail first, no longer sticky, then the text */
  .desc { grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 5vw, 40px); padding: clamp(20px, 5vw, 32px); }
  .desc__rail { position: static; }
  .desc__lead { font-size: clamp(22px, 5.6vw, 28px); }
  .prose, .pd__more { max-width: none; }
}
@media (max-width: 420px) {
  .desc__tiles { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .pd__prose, .pd__more-dots, .pd__more-btn svg, .desc__tile { transition: none; }
}

/* ================================================================ switch ==
   The battery's control, wherever its row is drawn - the phone's shop, the
   capsules page, the cart: a switch, not a button. A track with a knob
   that slides across on a spring; off, the track is a quiet glass; on, it
   fills with the accent (--sw-on: the page's green, the capsules page's
   gold) and a tick comes up in the knob. Pressed, the knob stretches the
   way a phone's own does, so the thumb feels it give before it moves.
   role="switch" + aria-checked; the script only flips the attribute.
   ========================================================================= */
.sw {
  --sw-on: var(--accent);
  --sw-w: 52px; --sw-h: 31px; --sw-k: 25px;
  position: relative; flex: none;
  width: var(--sw-w); height: var(--sw-h); padding: 0; margin: 0;
  border: 0; border-radius: 999px;
  background: rgb(var(--bone-rgb) / .13);
  box-shadow: inset 0 0 0 1px rgb(var(--bone-rgb) / .14), inset 0 1px 2px rgb(0 0 0 / .35);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background .28s var(--ease), box-shadow .28s var(--ease);
}
.sw__k {
  position: absolute; top: 3px; left: 3px;
  width: var(--sw-k); height: var(--sw-k); border-radius: 999px;
  display: grid; place-items: center;
  background: linear-gradient(180deg, #fff, #E9ECEA);
  box-shadow: 0 2px 6px rgb(0 0 0 / .45), 0 0 0 .5px rgb(0 0 0 / .1);
  transition: transform .34s cubic-bezier(.34, 1.45, .64, 1), width .18s var(--ease), background .28s var(--ease);
}
.sw__k svg {
  width: 13px; height: 13px; color: var(--sw-on);
  opacity: 0; transform: scale(.4) rotate(-30deg);
  transition: opacity .18s var(--ease), transform .34s cubic-bezier(.34, 1.45, .64, 1);
}
.sw[aria-checked="true"] {
  background: var(--sw-on);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sw-on) 70%, #fff 0%), inset 0 1px 0 rgb(255 255 255 / .25), 0 0 20px color-mix(in srgb, var(--sw-on) 38%, transparent);
}
.sw[aria-checked="true"] .sw__k { transform: translateX(calc(var(--sw-w) - var(--sw-k) - 6px)); }
.sw[aria-checked="true"] .sw__k svg { opacity: 1; transform: none; transition-delay: .06s; }
/* the press: the knob stretches toward where it is going */
.sw:active .sw__k { width: calc(var(--sw-k) + 6px); }
.sw[aria-checked="true"]:active .sw__k { transform: translateX(calc(var(--sw-w) - var(--sw-k) - 12px)); }
.sw:focus-visible { outline: 2px solid var(--sw-on); outline-offset: 3px; }
.sw[disabled] { cursor: default; opacity: .4; }
/* the word beside it: the switch's own label, small, tracked; it takes the
   accent once the switch is on */
.sw-lbl { font: 600 12.5px/1 var(--body); letter-spacing: .02em; color: rgb(var(--bone-rgb) / .72); white-space: nowrap; transition: color .25s var(--ease); }
.sw-lbl.is-on { color: var(--sw-on); }
@media (prefers-reduced-motion: reduce) {
  .sw, .sw__k, .sw__k svg, .sw-lbl { transition: none; }
}

/* ============================================================ pack cards ==
   The packs of capsules to choose from: 1, 3, 5, 10. The capsules page
   draws them beside the flavour chips (kapsuli.php), the phone's shop
   draws the same cards under the flavours (app.js) - one definition, so a
   pack looks the same wherever it is chosen.
   ========================================================================= */
/* Each card is a column: a label band across the top (gold on the popular
   one, quiet on the rest - every card gets one so the four line up), a row
   for the tick and the discount pill, the cut-outs over the pool of light,
   the count, the offer's line, the price with what it was, the saving. The
   band and the pill have rows of their own, so nothing lies over anything. */
.pcards {
  --spring: cubic-bezier(.22, 1, .36, 1);
  display: grid; gap: 12px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.pcard {
  --fc: var(--gold);
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  width: 100%; min-width: 0;
  margin: 0; padding: 0 10px 16px;
  text-align: center; font: inherit; color: var(--bone);
  background: linear-gradient(to bottom, rgb(var(--surface-rgb) / .92), rgb(var(--ink-rgb) / .92));
  border: 1px solid rgb(var(--bone-rgb) / .1);
  border-radius: 20px;
  overflow: hidden;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: transform .48s var(--spring), border-color .3s var(--ease), box-shadow .48s var(--spring);
}
.pcard::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  border: 1.5px solid var(--gold);
  opacity: 0; transform: scale(.985);
  transition: opacity .3s var(--ease), transform .48s var(--spring);
}
.pcard:hover { transform: translateY(-4px); border-color: rgb(var(--bone-rgb) / .22); }
.pcard[aria-pressed="true"] {
  transform: translateY(-5px);
  border-color: transparent;
  box-shadow: 0 26px 50px -28px rgb(var(--gold-rgb) / .55), 0 0 0 1px rgb(var(--gold-rgb) / .3);
}
.pcard[aria-pressed="true"]::before { opacity: 1; transform: none; }
.pcard:active { transform: translateY(-2px) scale(.99); transition-duration: .12s; }
.pcard:focus-visible { outline: 2px solid var(--gold-soft); outline-offset: 4px; }
.pcard--pop { border-color: rgb(var(--gold-rgb) / .4); }

/* the band: only the popular pack wears one, and it is written large */
.pcard__band {
  display: block; width: calc(100% + 20px); margin: 0 -10px 10px;
  padding: 10px 8px 9px;
  font-size: 13px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; line-height: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--ink); background: var(--gold);
}
/* the others leave the band's height empty at the top, so the cut-outs,
   the counts and the prices of all four stand on the same lines */
.pcard__row { margin-top: 42px; }
.pcard__band + .pcard__row { margin-top: 0; }

/* the tick and the pill share a row */
.pcard__row { display: flex; justify-content: space-between; align-items: center; width: 100%; height: 26px; }
.pcard__tick {
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--gold); color: var(--ink);
  opacity: 0; transform: scale(.6);
  transition: opacity .25s var(--ease), transform .45s var(--spring);
}
.pcard[aria-pressed="true"] .pcard__tick { opacity: 1; transform: none; }
.pcard__pct {
  margin-left: auto;
  padding: 6px 10px; border-radius: 999px;
  background: var(--gold); color: var(--ink);
  font-size: 13.5px; font-weight: 700; line-height: 1; letter-spacing: -.01em;
  box-shadow: 0 6px 16px -8px rgb(var(--gold-rgb) / .9);
}

/* the cut-outs over the pool of light; ten as two rows of five, the back
   row half a capsule across so each one stands in a gap of the front row */
.pcard__art {
  position: relative;
  display: flex; justify-content: center; align-items: flex-end; gap: 0 3px;
  --h: 84px; --w: calc(var(--h) * .279);
  /* one box for every pack, whatever its own height, so the counts and the
     prices under them stand on one line across the four */
  height: 112px; width: 100%;
  margin-top: 2px;
  background: radial-gradient(56% 60% at 50% 100%, rgb(var(--gold-rgb) / .2), transparent 72%);
}
.pcard__art img {
  display: block; width: auto; height: var(--h);
  filter: drop-shadow(0 8px 10px rgb(0 0 0 / .55));
  transition: transform .6s var(--spring);
}
.pcard__art--1 { --h: 96px; }
.pcard__art--3 { --h: 86px; }
.pcard__art--5 { --h: 78px; gap: 0 2px; }
.pcard__art--10 {
  --h: 64px;
  display: grid; grid-template-columns: repeat(5, var(--w)); column-gap: 3px;
  justify-content: center; align-items: end;
  padding-right: calc(var(--w) * .5);
}
.pcard__art--10 img { width: var(--w); }
.pcard__art--10 img:nth-child(-n + 5) {
  margin-bottom: calc(-.5 * var(--h));
  transform: translateX(calc(var(--w) * .5 + 2px)) scale(.88);
  transform-origin: 50% 100%;
  filter: drop-shadow(0 6px 8px rgb(0 0 0 / .5)) brightness(.8);
}
.pcard:hover .pcard__art img, .pcard[aria-pressed="true"] .pcard__art img { transform: translateY(-3px); }
.pcard:hover .pcard__art--10 img:nth-child(-n + 5), .pcard[aria-pressed="true"] .pcard__art--10 img:nth-child(-n + 5) { transform: translate(calc(var(--w) * .5 + 2px), -3px) scale(.88); }

.pcard__qty {
  display: block; margin-top: 12px;
  font-family: var(--display); font-weight: 500; font-variant-numeric: lining-nums;
  font-size: 22px; line-height: 1.1; color: #fff;
}
.pcard__price { display: block; margin-top: 10px; line-height: 1; }
.pcard__price b {
  display: block;
  font-family: var(--display); font-weight: 600; font-variant-numeric: lining-nums;
  font-size: clamp(24px, 1.9vw, 29px); letter-spacing: .01em; color: #fff;
}
.pcard__price s { display: block; margin-top: 5px; font-size: 13.5px; color: var(--muted); text-decoration-color: rgb(var(--bone-rgb) / .5); }
.pcard__save { display: block; margin-top: 8px; font-size: 14px; font-weight: 600; color: var(--gold-soft); }
.pcard__save--alt { font-weight: 400; color: var(--muted); }


@media (max-width: 1280px) {
  .pcard__art { --h: 76px; }
  .pcard__art--1 { --h: 86px; } .pcard__art--3 { --h: 78px; } .pcard__art--5 { --h: 70px; } .pcard__art--10 { --h: 58px; }
  .pcard__band { font-size: 12px; letter-spacing: .06em; }
}
@media (max-width: 1024px) {
  .pcards { gap: 8px; }
  .pcard { padding: 0 6px 14px; }
  .pcard__band { width: calc(100% + 12px); margin-inline: -6px; }
  .pcard__qty { font-size: 19px; }
  .pcard__price b { font-size: 23px; }
  .pcard__save { font-size: 13px; }
  .pcard__price b { font-size: 19px; }
}
@media (max-width: 940px) {
  .pcards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .pcard__art { --h: 84px; }
  .pcard__art--1 { --h: 96px; } .pcard__art--3 { --h: 86px; } .pcard__art--5 { --h: 78px; } .pcard__art--10 { --h: 64px; }
}
@media (prefers-reduced-motion: reduce) {
  .pcard, .pcard::before, .pcard__art img, .pcard__tick { transition: none; }
  .pcard:hover, .pcard[aria-pressed="true"] { transform: none; }
}

/* ============================================================== reviews ==
   The arrangement is the one Amazon, Google and the App Store all arrived at
   independently: a big average on the left, the 5-to-1 distribution on the
   right, the write box, then the list. They arrived at it because an average
   alone hides whether "4 stars" means everyone was content or half were
   delighted and half were not, and the bars answer that in one glance.
   ========================================================================= */

/* .revs, not .rv. `.rv` was already taken - it is the checkout's
   "review your order" list - and its `padding: 0` shorthand was winning
   over .wrap's padding-inline, so this whole section ran edge to edge with
   the sort row clipped off the left of the screen. */
.revs { padding-top: clamp(48px, 7vw, 104px); scroll-margin-top: 90px; }

.revs__flash {
  padding: var(--s4) var(--s5);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  font-size: 14.5px;
  margin-bottom: var(--s5);
}
.revs__flash.is-ok {
  border-color: color-mix(in srgb, var(--gold) 45%, transparent);
  background: color-mix(in srgb, var(--gold) 10%, var(--surface-2));
  color: var(--bone);
}
.revs__flash.is-bad {
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 8%, var(--surface-2));
  color: var(--bone);
}

/* ------------------------------------------------------------ summary -- */
.revs__sum {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  align-items: center;
  padding: var(--s6) clamp(20px, 3vw, 40px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  margin-bottom: var(--s6);
}
.revs__score { display: grid; justify-items: center; gap: var(--s3); }
.revs__avg {
  font-family: var(--display);
  font-size: clamp(46px, 6vw, 74px);
  font-weight: 700;
  line-height: 1;
  color: var(--bone);
  /* Tabular figures: 4,1 and 4,8 must occupy the same width, or the number
     shuffles sideways every time a review lands. */
  font-variant-numeric: tabular-nums;
}
.revs__n { color: var(--muted); font-size: 13px; }

.revs__bars { display: grid; gap: 7px; min-width: 0; }
.revs__bar {
  display: grid;
  grid-template-columns: 3.4em minmax(0, 1fr) 2.4em;
  align-items: center;
  gap: var(--s3);
  font-size: 12.5px;
  color: var(--muted);
}
.revs__bar-k { font-family: var(--mono); white-space: nowrap; }
.revs__bar-n { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; }
.revs__bar-t {
  height: 7px;
  border-radius: 999px;
  background: rgb(var(--bone-rgb) / .07);
  overflow: hidden;
}
.revs__bar-t i {
  display: block; height: 100%;
  border-radius: 999px;
  background: var(--gold);
}

@media (max-width: 620px) {
  .revs__sum { grid-template-columns: minmax(0, 1fr); gap: var(--s5); text-align: center; }
  .revs__bar { font-size: 12px; }
}

/* --------------------------------------------------------- write box --- */
.rvform,
.rvgate {
  padding: clamp(20px, 3vw, 34px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: var(--s6);
}
.rvform__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.rvform__head h3 { font-size: 18px; font-weight: 600; }

/* The verified badge. It is the whole reason this section is worth reading,
   so it is gold and it is stated plainly rather than being a small grey
   footnote after the name. */
.rvbadge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px;
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 9%, transparent);
  color: var(--gold-soft);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}
.rvbadge svg { width: 11px; height: 11px; flex: none; }
.rvbadge--sm { padding: 3px 9px; font-size: 10.5px; }

/* ---- the star picker ----
   Real radio inputs, in natural 1-to-5 order, styled through their labels.
   No JavaScript: the rating survives a submit with scripting off, keyboard
   arrows walk the group the way they do in every other radio group, and the
   browser's own "required" message points at the right control.

   The fill is done with :has() rather than the old reversed-DOM sibling
   trick. That trick needs the stars written 5,4,3,2,1 and flipped visually,
   which puts the tab order and the arrow keys backwards - a cost paid purely
   to work around a selector that now exists. */
.rvstars { border: 0; padding: 0; margin: 0 0 var(--s5); }
.rvstars legend {
  padding: 0;
  color: var(--muted); font-size: 12.5px;
  margin-bottom: var(--s3);
}
.rvstars input { position: absolute; opacity: 0; width: 0; height: 0; }
.rvstars label {
  display: inline-block;
  cursor: pointer;
  padding: 2px;
  line-height: 0;
}
.rvstars label svg {
  width: 34px; height: 34px;
  fill: rgb(var(--bone-rgb) / .15);
  transition: fill .16s var(--ease), transform .16s var(--ease);
}

/* Chosen: this star and every star before it. */
.rvstars:has(#rv1:checked) label[for="rv1"] svg,
.rvstars:has(#rv2:checked) :is(label[for="rv1"], label[for="rv2"]) svg,
.rvstars:has(#rv3:checked) :is(label[for="rv1"], label[for="rv2"], label[for="rv3"]) svg,
.rvstars:has(#rv4:checked) :is(label[for="rv1"], label[for="rv2"], label[for="rv3"], label[for="rv4"]) svg,
.rvstars:has(#rv5:checked) label svg { fill: var(--gold); }

/* Pointing at a star previews it, and while previewing, the chosen state
   steps back - otherwise hovering 2 when 5 is chosen shows five gold stars
   and the preview means nothing. Written after the rules above so it wins on
   order rather than on a specificity trick. */
.rvstars:hover label svg { fill: rgb(var(--bone-rgb) / .15); }
.rvstars label:hover svg,
.rvstars label:has(~ label:hover) svg { fill: var(--gold-soft); }
.rvstars label:hover svg { transform: scale(1.12); }
.rvstars input:focus-visible + label svg {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.rvform__lab {
  display: block;
  color: var(--muted); font-size: 12.5px;
  margin-bottom: 7px;
}
.rvform__lab i { font-style: normal; opacity: .65; }
.rvform__in,
.rvform__ta {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--bone);
  font-family: var(--body); font-size: 15px;
  line-height: 1.6;
  transition: border-color .22s var(--ease), background .22s var(--ease);
}
.rvform__in { margin-bottom: var(--s5); }
.rvform__ta { resize: vertical; min-height: 130px; }
.rvform__in:focus,
.rvform__ta:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
}
.rvform__in::placeholder,
.rvform__ta::placeholder { color: rgb(var(--bone-rgb) / .28); }

.rvform__count {
  min-height: 1.3em;
  margin-top: 6px;
  font-family: var(--mono); font-size: 11.5px;
  color: var(--muted);
}
.rvform__count.is-short { color: var(--gold-soft); }

.rvform__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
  margin-top: var(--s5);
}
.rvform__note { color: var(--muted); font-size: 12.5px; max-width: 44ch; }
.rvform__foot .btn { flex: none; }

/* The two "you cannot write here" states. They say why, and they offer the
   step that would change it. A missing box with no explanation is the usual
   way this goes wrong. */
.rvgate { text-align: center; }
.rvgate p { color: var(--muted); font-size: 14.5px; margin-bottom: var(--s3); }
.rvgate p b { color: var(--bone); font-weight: 600; }
.rvgate .btn { margin-top: var(--s4); }

/* ------------------------------------------------------------- list ---- */
.revs__tools {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.revs__tools-k {
  color: var(--muted);
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .14em; text-transform: uppercase;
}
.revs__sort {
  padding: 6px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted); font-size: 12.5px;
  text-decoration: none;
  transition: color .2s var(--ease), border-color .2s var(--ease), background .2s var(--ease);
}
.revs__sort:hover { color: var(--bone); border-color: rgb(var(--bone-rgb) / .28); }
.revs__sort.on {
  color: var(--ink);
  background: var(--bone);
  border-color: var(--bone);
  font-weight: 600;
}

.revs__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }

.rvcard {
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
/* The reader's own review, still waiting. Dashed rather than solid, and gold
   rather than red: it is not an error, it is a thing in progress, and the
   border should say so before the sentence inside it does. */
.rvcard.is-pending {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--gold) 40%, var(--line));
  background: color-mix(in srgb, var(--gold) 5%, var(--surface-2));
}

.rvcard__top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s3);
}
.rvcard__who { display: inline-flex; align-items: center; gap: var(--s3); min-width: 0; }
.rvcard__av {
  display: grid; place-items: center;
  width: 32px; height: 32px; flex: none;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 20%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  color: var(--bone);
  font-size: 13px; font-weight: 600;
  text-transform: uppercase;
}
.rvcard__name {
  color: var(--bone); font-size: 14.5px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rvcard__name i { font-style: normal; color: var(--muted); font-weight: 400; }
.rvcard__date { color: var(--muted); font-family: var(--mono); font-size: 11.5px; flex: none; }

.rvcard__rate {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s4);
}

.rvcard__pending {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--gold-soft); font-size: 12.5px;
  margin-bottom: var(--s3);
}
.rvcard__pending svg { width: 14px; height: 14px; flex: none; }

.rvcard__title { font-size: 15.5px; font-weight: 600; margin-bottom: 7px; color: var(--bone); }
/* 70ch, not the card's full width. Long lines are the single most common
   reason a review section is skimmed rather than read. */
.rvcard__body { color: rgb(var(--bone-rgb) / .8); font-size: 14.5px; line-height: 1.7; max-width: 70ch; }

.revs__empty {
  padding: var(--s7) var(--s5);
  text-align: center;
  color: var(--muted); font-size: 14.5px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

@media (max-width: 560px) {
  .rvcard { padding: var(--s4); }
  .rvform__foot { flex-direction: column; align-items: stretch; }
  .rvform__foot .btn { width: 100%; }
  .rvstars label svg { width: 30px; height: 30px; }
}

/* ------------------------- paying for the rating row out of the card ---- */
/* THE PROBLEM THIS SOLVES.
   Above 1024 both product widgets are overlays pinned to a printed artboard.
   The flavour card is anchored by its FLOOR, so anything added to it grows
   upward - and its panel has no `auto` margin at these widths, so every pixel
   of new content is a pixel the card climbs. The rating row measured 37px,
   which took the card from sitting just over the top of the photograph to
   floating 85px clear of it, out of the band entirely.
   So the row is paid for rather than added. The margins below are the ones
   that were carrying the most air, tightened by about what the row costs; the
   net change to the card's height is a few pixels rather than forty. Every
   value is in em, so they keep scaling with the plate like everything else
   in this card does.
   Only above 1024. Below that the plates become windows with the interface
   stacked underneath at real pixel sizes, where there is room and this
   tightening would just look cramped. */
@media (min-width: 1025px) {
  .flav__stage--card .eyebrow    { margin-bottom: .7em; }
  .flav__stage--card .flav__rule { margin-bottom: .35em; }
  .flav__stage--card .flav__meta { margin-bottom: .5em; }
  .flav__stage--card .flav__desc { margin-bottom: .75em; }
  .flav__stage--card .spec       { margin: 0 0 .75em; padding-bottom: .7em; }
}

/* --------------------------- the shortest plate needs the tightest card -- */
/* Between 1025 and 1200 the artboard is at its shortest while the card still
   carries everything it carries at 1920. That is the one band where the card
   climbs meaningfully past the top of the photograph, and moving "Научи
   повече" under the buy button cost it another 26px there.
   These are the same margins the wider rule already trims, taken one step
   further and only here. Nothing about the arrangement changes - only the air
   between the parts, which is what there is least of at this width. */
@media (min-width: 1025px) and (max-width: 1200px) {
  .flav__stage--card .eyebrow    { margin-bottom: .5em; }
  .flav__stage--card .flav__name { margin-bottom: .15em; }
  .flav__stage--card .flav__rule { margin-bottom: .25em; }
  .flav__stage--card .flav__meta { margin-bottom: .35em; }
  .flav__stage--card .flav__desc { margin-bottom: .5em; }
  .flav__stage--card .spec       { margin: 0 0 .55em; padding-bottom: .55em; }
  .flav__stage--card .flav__buy  { gap: 6px; }
}
