/* ══════════════════════════════════════════════════════════════════════════
   Rutriven homepage — F7 · first viewport
   A close translation of the supplied bakery landing page: warm cream canvas,
   cocoa ink, a large soft white panel, one oversized friendly display line with
   rotated stickers biting into it, a colour block whose product breaks out over
   its top edge, and drawn marks integrated into the composition.
   Bakery subject matter is replaced by Rutriven's real catalogue; no bakery
   logo, wording, artwork or food motif is reproduced.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --canvas: #F4EBDB;      /* warm cream page */
  --panel: #FFFCF6;       /* soft white hero panel */
  --cocoa: #2E1C15;       /* warm dark ink + framing */
  --cocoa-soft: rgba(46, 28, 21, 0.62);

  --amber: #FFD23F;       /* the warm block + primary pill */

  /* Coral is split by job, the same way green already is (contract §4). The
     bright coral is a GRAPHIC colour — dots, rules, seals, the footer's
     wordmark period — where nothing has to be read out of it. `--coral-ink` is
     the one allowed to carry words on cream: #F2622E measures 2.71:1 against
     the page, which is below the floor for any size, so every coral eyebrow,
     emphasis and accent word on this page was failing. #B83A11 is 4.86:1 on
     cream and 5.62:1 on the panel, and reads as the same colour at a glance. */
  --coral: #F2622E;
  --coral-ink: #B83A11;

  --green: #1CB375;
  --green-deep: #16764F;   /* white-on-green floor: 5.62:1 */
  --sky: #3C9BE0;

  --display: 'Titan One', 'Gabarito', system-ui, sans-serif;
  --body: 'Rubik', system-ui, -apple-system, sans-serif;

  /* The leading floor for any DISPLAY heading that can wrap to a second line.
     This is a property of the language, not a taste setting. Measured on the
     shipped face: Titan One's caps stop at 0.72em above the baseline, but its
     Hungarian diacritics — Á É Í Ó Ö Ő Ú Ü Ű — reach 0.92em, a fifth of the em
     higher, and its glyphs drop 0.01em below. Two lines therefore collide
     outright below 0.93em, and merely TOUCH at 0.94–0.95: the page shipped
     `Kezdd egy / döntéssel` with the Ö's dots fused into the line above, and
     `Segítünk / választani` and `Gyakori / kérdések—` the same way.
     0.98 buys a 0.05em optical gap, the smallest value that still reads as two
     separate lines. Anything tighter is only safe for a heading with no
     accented capital on its second line, which no Hungarian copy can promise. */
  --display-lh: 0.98;

  --wave: #F8F1E4;        /* the hero's cream band, and the page's quiet field */
  /* The shelf's ground, and deliberately NOT --wave. The shelf block says it
     gives the cards "a FIELD: one contained ground it sits inside... the
     difference between a shelf and a scatter" — but it was painting that
     field in --wave, which is four, six and nine points of RGB away from the
     page's own --canvas. At that distance the field was invisible: the cards
     still read as floating on the page, only now with two rules drawn around
     them, and the section sat in an unbroken run of cream with the lead
     above it. A warm sand is far enough from --canvas to read as a separate
     surface and far enough from the cards' near-white --panel to let them sit
     ON it as objects, which is the whole claim the block makes. Its own token
     because --wave also paints the hero band, the search overlay and the
     stage toggle, none of which are asking for this. */
  --shelf-field: #EADEC5;

  --shell: 1440px;
  --inset: 34px;          /* hero panel inset from the viewport */
  /* Where the shell's TEXT starts, measured from the viewport edge. Full-bleed
     sections need this to line their content up with the contained ones —
     without it a bleeding section either starts at the screen edge or gets a
     hand-tuned padding that drifts out of alignment at the next breakpoint. */
  --gutter: max(var(--inset), calc((100vw - var(--shell)) / 2 + var(--inset) * 2));
  --radius-panel: 46px;
  --radius-block: 34px;

  /* Motion tokens. One curve, three durations — the second reference builds its
     whole transition on a single strong ease-in-out, and reusing it is what
     makes the stage change read as one movement instead of several. */
  --ease-stage: cubic-bezier(0.78, 0, 0.27, 1);
  --t-travel: 1250ms;     /* a pack moving between positions */
  --t-fade: 620ms;        /* entering and leaving opacity */

  /* The colour field trails the product — it does not lag behind it. Measured
     on the real transition, the old settings put the new pack's NAME and PRICE
     on screen at 323ms while the block did not finish reaching the new hue
     until 987ms, so for well over half a second the page showed one product
     priced on another product's colour. That is the exact mismatch the whole
     four-colour system exists to prevent.
     The curve matters as much as the duration: the previous ease was flat for
     its first third, so the field appeared not to move at all until the change
     was already over. This one commits early and eases out. */
  --t-stage: 640ms;
  --ease-field: cubic-bezier(0.32, 0.72, 0.24, 1);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--cocoa);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.5;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

/* One display recipe: heavy, friendly, tight, caps. */
 .hero-h1, .hero-sub, .btn-pill, .stick, .inline-pill{
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.005em;
  font-synthesis: none;
}

:where(a, button, summary, input):focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 3px;
  border-radius: 8px;
}

/* ══ the fascia ═══════════════════════════════════════════════════════════
   Announcement rail + header as one cocoa object. See index.html for why the
   shell moved off the page background: on `--canvas` the header had no ground
   and no silhouette, so it read as four small controls scattered above a large
   authored storefront. Everything below inherits that decision — a light
   object on the fascia is loud, a cream-outlined one is quiet, and the
   hierarchy no longer depends on hairline weights nobody can see.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --fascia: #2E1C15;        /* the shell's field — the page's cocoa ink */
  --fascia-deep: #23130E;   /* the rail, one step down, so the two read as layers */
  --fascia-ink: #150B07;    /* borders and hard offsets ON the fascia */
  --cream: #F6EEE2;         /* type on the fascia — 13.9:1 */
  --cream-2: #CDBFAE;       /* the rail's secondary phrase — 7.4:1 on --fascia-deep */
  --cream-line: rgba(246, 238, 226, 0.30);
  --bar-h: 56px;          /* 44px cell + 8px padding + 4px border — keep in step with .mbar */
}

/* The reference's buttons are outlined pills with a hard offset shadow — the
   printed/packaging character the whole page is built on. */
.btn-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 24px;
  border-radius: 999px;
  background: var(--amber);
  color: var(--cocoa);
  border: 2.5px solid var(--cocoa);
  box-shadow: 2px 3px 0 var(--cocoa);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-decoration: none;
  transition: transform 0.13s ease, box-shadow 0.13s ease;
}
.btn-pill:hover { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--cocoa); }
.btn-pill-lg { min-height: 60px; padding: 0 38px; font-size: 16px; box-shadow: 4px 5px 0 var(--cocoa); }

@media (max-width: 900px) {

  

  /* The bar floats over the document, so the document has to end above it.
     Without this the last rows of the footer sit under the bar — a regression
     this repo has already recorded once on the product archive.

     `--bar-h` is now what the bar actually measures (44 + 8 padding + 4
     border = 56), and the comment says how it is derived so the next person to
     change a padding knows to change this too. It used to be a hand-declared
     76px against a real 69px: decoupled in the safe direction by luck, but
     decoupled. The extra 14px below it is deliberate breathing room, not
     slack — it keeps the last footer row clear of the bar's drop shadow. */
  body { padding-bottom: calc(var(--bar-h) + 14px + env(safe-area-inset-bottom, 0px)); }
}

.rail-toggle { position: relative; }

@media (max-width: 640px) {
  :root { --inset: 16px; --radius-panel: 28px; --radius-block: 22px; --stage-h: 246px; }

  .btn-pill { min-height: 44px; }        /* touch minimum */
  .btn-pill-lg { min-height: 56px; width: 100%; }

}

/* ══ reduced motion ══════════════════════════════════════════════════════
   The contract is: nothing moves on its own, and nothing is hidden. The stage
   still changes state — it is the product picker, not decoration — but it does
   so instantly, only when the visitor asks, and the dots become the control
   rather than a progress indicator. */
@media (prefers-reduced-motion: reduce) {
  .btn-pill { transition: none; }

  .rail-track { animation: none !important; }
}

/* ── the scriptless note ───────────────────────────────────────────────────
   Rendered only when scripting is off, so it is never in a screenshot and
   never in a parity measurement. It still gets the shop's type and colour,
   because a shopper who sees it is a shopper, not an error case. */
.noscript-note {
  margin: 0 auto;
  max-width: 720px;
  padding: 48px 24px 40px;
  text-align: center;
}
.noscript-note h2 {
  margin: 0 0 10px;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(24px, 4vw, 34px);
  line-height: 1.05;
}
.noscript-note p { margin: 0 0 18px; }
.noscript-note a { text-decoration: underline; text-underline-offset: 3px; }

/* ══════════════════════════════════════════════════════════════════════════
   Rutriven homepage hero — ELSŐ SOR (front row)

   A product-led first screen: one pack standing forward at object scale on a
   lit shelf, two more behind it in depth, the shop's own commerce facts set
   beside it, and the rest of the shelf running off the edge of the frame as
   real controls.

   WHAT IS BORROWED from the reference is composition grammar, not artwork: a
   single sharp focal object oversized against a flat coloured field the frame
   runs out of room for; an asymmetric frame with a large share left empty;
   hierarchy from weight and colour inside one type size rather than a ladder of
   sizes; small cards overlapping the object on a nearer plane; and the purchase
   action beside the object rather than under a paragraph.

   WHAT IS NOT BORROWED: the reference sells clean two-colour can mockups. This
   shop sells supplier retail packaging — loud print, five colourways, black and
   red tubs whose own typography competes with the page the moment a pack is
   enlarged to billboard size. So the pack is held at OBJECT scale with air
   around it, three silhouettes are grouped so the eye reads an arrangement
   instead of a label, and everything behind the lead loses contrast and focus.

   EVERY SURFACE HERE IS THE PAGE'S OWN. The back panel is the category door
   from four hundred pixels further down, at four hundred pixels wide; the rail
   is the same door at a hundred; the watermark inside it is `.band-mark`'s rule
   with a word in it; the objects on the near plane wear the 2.5px cocoa outline
   and flat offset shadow the product cards wear; and --cocoa, --amber, --canvas,
   --panel, --display, --green-deep and the motion curves come from home.css.

   THE TWO AUTHORED WIDTHS ARE 390 AND 1440, and 390 was authored first. The
   phone is not this composition stacked: it is the same six elements — scene,
   flag, facts, action, maker, shelf — re-arranged so the whole of the useful
   hero lands inside one screen instead of running down past three.
   ══════════════════════════════════════════════════════════════════════════ */

/* `--fr-lead-h` is read back by the script and is a `clamp()` at most widths.
   An unregistered custom property hands `getComputedStyle` the clamp back
   verbatim, so `parseFloat` returns NaN and the slot height silently becomes
   zero everywhere except the one width it was authored at. Registering it makes
   the browser resolve it first. `hero.js` keeps a measured fallback. */
@property --fr-lead-h {
  syntax: '<length>';
  inherits: true;
  initial-value: 442px;
}

.fr {
  /* Set by hero.js from the LEAD product's family. Declared here so the resting
     frame is correct before a line of script runs. */
  --fr-accent: #F0996B;
  --fr-ink: #8A3A12;

  /* The slot's height, before the pack's own proportion is applied. */
  --fr-lead-h: 442px;
  /* The lead's RENDERED box, written by hero.js after its proportion is
     applied. Everything else on the stage is a function of these two: the back
     panel's width, height and offset, the two neighbours' positions, the flag,
     the seal and the maker card. A pack format nobody has photographed yet
     composes its own frame.

     THE DEFAULTS TRACK `--fr-lead-h` RATHER THAN BEING TWO CONSTANTS, and that
     is a layout-stability fix, not tidying. The script cannot write them until
     it has resolved the cast and measured the pack, which on a phone on a slow
     connection is after first paint — so whatever is declared here IS the frame
     a shopper sees first. Two fixed pixel values were right at exactly one
     width: at 390 the back panel was painted 377px tall and then re-laid at
     256, which moved the whole document 266px and was most of a 1.1 CLS on the
     measured mobile profile. Derived from the same clamp the slot uses, the
     pre-script frame is within a few pixels of the final one at every width.

     `.90` is the proportion factor's own middle: the factor is bounded to
     0.80–1.00 and this catalogue's packs sit between 0.87 and 1.00. `.85` is
     the median wide-to-tall ratio of the authored cuts. Neither is a
     measurement of a particular product, which is the point — no pack is
     pinned and the frame is honest before any of them arrives. */
  --fr-lh: calc(var(--fr-lead-h) * .90);
  --fr-lw: calc(var(--fr-lh) * .85);

  --fr-ground: 40px;        /* the floor, measured up from the stage's bottom */
  --fr-bleed: -256px;       /* how far the back panel runs past the stage */

  /* The back panel's geometry, declared once because two elements need it —
     the panel itself and the flag clipped to its top edge. Percentages resolve
     against the same containing block for both. */
  --fr-back-l: calc(50% - var(--fr-lw) * .82);
  --fr-back-b: calc(var(--fr-ground) - 26px);
  --fr-back-h: calc(var(--fr-lh) * .96);

  /* WHERE THE LIGHT IS. One key, high and to the left, and every shadow in the
     frame is cast from it: floor shadows fall down and to the RIGHT, and the
     nearer an object is the longer its throw. Before this each pack sat on a
     symmetrical puddle, which is the lighting of a catalogue cut-out and the
     reason three good photographs still read as three cut-outs. */
  --fr-cast: 1;

  /* The page's own flat offset shadow — the one the product cells, the facet
     pills and the framed packshots all wear. */
  --fr-sh-hard: 4px 5px 0 rgba(46, 28, 21, .18);

  /* THE PANEL IS LIGHTER THAN THE PAGE, so the page's own soft inks do not
     survive on it. `--cocoa-soft` is 62% cocoa tuned against the cream canvas;
     composited on this near-white studio field it measures 4.36:1, under the
     4.5 floor. Two local inks, measured, both still clearly secondary. */
  --fr-soft: rgba(46, 28, 21, .72);
  --fr-label: rgba(46, 28, 21, .8);

  --fr-sh-sm:
    0 2px 3px -1px rgba(46, 28, 21, .10),
    0 1px 0 0 rgba(46, 28, 21, .02),
    0 0 0 1px rgba(46, 28, 21, .07);
  --fr-sh-panel:
    0 2.8px 2.2px rgba(46, 28, 21, .018),
    0 6.7px 5.3px rgba(46, 28, 21, .026),
    0 12.5px 10px rgba(46, 28, 21, .032),
    0 22.3px 17.9px rgba(46, 28, 21, .038),
    0 41.8px 33.4px rgba(46, 28, 21, .046),
    0 100px 80px rgba(46, 28, 21, .06);

  display: block;
  padding: var(--inset) var(--inset) 0;
}

/* ── the frame ─────────────────────────────────────────────────────────────
   A studio rather than a poster: warm white at the top where the light is,
   settling into the page's own cream at the floor. The bottom stop is
   `--canvas` — the exact colour of the field the panel sits on — so the studio
   does not end at an edge, it runs out of light and becomes the page, with the
   cocoa maker band below reading as the next object standing on the same
   surface rather than the start of a different design. */
.fr-panel {
  position: relative;
  isolation: isolate;
  /* `clip`, NOT `hidden`. Both crop the shelf where it runs off the right edge,
     but `hidden` makes the panel a scroll container, and the browser will SCROLL
     it the moment a keyboard reaches one of the deliberately cut tiles — the
     whole composition slides sideways. `clip` crops without creating anything
     scrollable, so the frame cannot be moved. */
  overflow: hidden;
  overflow: clip;
  border-radius: var(--radius-panel);
  background:
    linear-gradient(174deg,
      #FFFEFB 0%, #FEFAF3 42%, #F9F1E4 78%, var(--canvas, #F4EBDB) 100%);
  box-shadow: var(--fr-sh-panel);
  min-height: 700px;
}
/* The panel's bottom corners are the only place the page sees cream on cream,
   and a 46px radius there draws two crescents that read as a card's corners. A
   short wash of the page colour across the last band dissolves them, so the eye
   leaves the hero at a floor line and not at an edge. */
.fr-panel::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 128px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(244, 235, 219, 0) 0%, rgba(244, 235, 219, .55) 62%, var(--canvas, #F4EBDB) 100%);
}

/* The only out-of-focus thing in the frame. It enters from the top-left corner
   — where the eye enters — mostly outside the frame, so what lands on the
   headline is warmth and not a wash. */
.fr-bloom { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.fr-bloom::before,
.fr-bloom::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  transition: background var(--t-stage) var(--ease-field),
              transform 900ms cubic-bezier(.22, .8, .24, 1);
}
.fr-bloom::before {
  transform: translate(calc(var(--mx, 0px) * -1.4), calc(var(--my, 0px) * -.9));
  width: 760px; height: 600px; left: -434px; top: -430px;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--fr-accent) 78%, transparent), transparent 74%);
  opacity: .4;
  filter: blur(26px);
}
.fr-bloom::after {
  width: 720px; height: 520px; right: -230px; bottom: -250px;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--fr-accent) 52%, transparent), transparent 72%);
  opacity: .26;
  filter: blur(34px);
}

/* ── the zones ─────────────────────────────────────────────────────────────
   Two grid areas and one object positioned against the frame. The left column
   is a fixed measure so the headline's line breaks are a decision rather than a
   consequence of the viewport; the stage takes the rest; and the family shelf
   is NOT a third column, because a grid track is a promise that the thing in it
   is a separate region, which is exactly how it read when it had one. */
.fr-cols {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(350px, 452px) minmax(0, 1fr);
  grid-template-areas: 'say stage';
  gap: 0 30px;
  align-items: stretch;
  padding: 72px 196px 54px 74px;
  min-height: 700px;
}

/* ── what the shop says, then what the catalogue says ───────────────────── */
.fr-say { grid-area: say; display: flex; flex-direction: column; align-self: center; }

.fr-h1 {
  margin: 0 0 30px;
  padding-bottom: 26px;
  border-bottom: 1px solid rgba(46, 28, 21, .15);
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(40px, 3.9vw, 58px);
  line-height: var(--display-lh);
  letter-spacing: .004em;
  text-transform: uppercase;
  color: var(--cocoa);
  font-synthesis: none;
}
.fr-h1 span { display: block; }

/* The identity line. Stacked at 1440, where the column is 452px wide and a 34px
   price wants its own baseline; set as one row at 390, where a separate price
   line is a whole row of a screen that does not have one to spare. */
.fr-id { display: grid; gap: 4px; }
.fr-name {
  display: inline-block;
  justify-self: start;
  font-size: 24px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -.012em;
  color: var(--cocoa);
  text-decoration: none;
  text-underline-offset: 4px;
}
.fr-name:hover { text-decoration: underline; }
/* Padding out, margin back in: the link occupies 44px for a thumb and the same
   22px optically, so nothing in the column moves.

   `position`/`z-index` for the same reason as `.ck-name`: the padded area is
   invisible, so a later sibling in normal flow paints straight over it and
   takes it back. At 1440 the price below was eating 16px of this link's own
   target — hit-testing found 35px under a 51px box — while at 390 the column
   stacks differently and it measured fine. The padding is only a target if
   nothing is painted on top of it. */
.fr-name { position: relative; z-index: 1; padding-block: 11px; margin-block: -11px; }
.fr-price { margin: 0; }
.fr-amt {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--cocoa);
}

/* What the price is FOR, and only what the record actually holds. */
.fr-meta { margin: 4px 0 0; font-size: 13.5px; color: var(--fr-soft); }
.fr-meta:empty { display: none; }

/* One accepted sentence, two lines at most, and it is the first thing the phone
   drops: the sentence is Rutriven's voice and it is on this product's own card
   sixty per cent of a screen further down, while the facts above it are the
   reason anyone would buy from a first screen. */
.fr-why {
  margin: 12px 0 0;
  max-width: 34ch;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fr-soft);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
.fr-why:empty { display: none; }

/* ── the action ────────────────────────────────────────────────────────────
   A light, small, detailed object beside a single heavy one — the reference's
   arrangement, with its quantity stepper replaced by a fact this shop can stand
   behind. A stepper here would be a lie: the cart the shell owns adds one, and
   a control whose number the cart ignores is worse than no control. */
/* WRAPS, and only where it has to. At 320 the stock pill and the control are
   35px wider than the screen, and neither can shrink — the pill's contents are
   nowrap and the control's label is display type — so the row was clipping the
   page's primary purchase action to read "KOSÁR". Wrapping costs nothing at
   every width where both fit on one line, and at 320 it puts the button under
   the pill instead of half of it off the edge. */
.fr-buy {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 12px; margin-top: 22px;
}

.fr-stock {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 8px 15px 8px 12px;
  border-radius: 999px;
  background: #fff;
  box-shadow: var(--fr-sh-sm);
  font-size: 12.5px;
  color: var(--cocoa);
  white-space: nowrap;
}
.fr-stock b { font-weight: 600; }
.fr-stock i { font-style: normal; color: var(--fr-soft); }
.fr-stock i::before { content: '·'; margin: 0 7px; opacity: .5; }
.fr-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--green-deep, #16764F);
  box-shadow: 0 0 0 3px rgba(22, 118, 79, .14);
}

/* The page's dark anchor. The shelf's own Kosárba buttons are amber on cream,
   which is right for eighteen equal cards and wrong for the one action the
   first screen is built around: on this field amber is bright but not heavy,
   and a composition with no dark mass in its lower left has no weight to
   balance the object. Cocoa carrying an amber badge is the header's cart
   control at a larger size, so the hero's primary is the shop's own furniture
   rather than a new button. */
.fr-cta {
  display: inline-flex;
  align-items: center;
  gap: 13px;
  padding: 7px 28px 7px 7px;
  border-radius: 999px;
  background: var(--cocoa);
  color: var(--cream);
  text-decoration: none;
  box-shadow: 0 10px 22px -10px rgba(46, 28, 21, .55), 0 2px 5px rgba(46, 28, 21, .14);
  transition: transform 180ms var(--ease-field), box-shadow 180ms var(--ease-field);
}
.fr-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 28px -12px rgba(46, 28, 21, .6), 0 3px 7px rgba(46, 28, 21, .16); }
.fr-cta-b {
  display: grid; place-items: center; flex: none;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--amber); color: var(--cocoa);
}
.fr-cta-b .ic { width: 21px; height: 21px; }
.fr-cta-t {
  font-family: var(--display); font-weight: 400;
  font-size: 16px; letter-spacing: .02em; text-transform: uppercase;
  white-space: nowrap;
}
/* A product WooCommerce says has options, or one not buyable from here, gets a
   route to the page where that choice exists — never a button that
   half-completes. Quieter by construction: it is not the same act. */
.fr-cta-ghost {
  background: transparent;
  color: var(--cocoa);
  box-shadow: inset 0 0 0 1.5px rgba(46, 28, 21, .24);
  padding-left: 24px;
}
.fr-cta-ghost:hover { box-shadow: inset 0 0 0 1.5px rgba(46, 28, 21, .5); }
.fr-cta-ghost .fr-cta-b { display: none; }

/* NOTHING TO STAGE — a shop with nothing purchasable at all. See the guard in
   hero.js. The frame reserves its height for layout stability, so without this
   the first screen would be 650px of empty panel. The proposition and the two
   ways out are still true and still render; everything that needed a product
   does not. */
.fr[data-fr-empty] .fr-panel,
.fr[data-fr-empty] .fr-cols { min-height: 0; }
.fr[data-fr-empty] .fr-cols { padding-block: 44px; }
.fr[data-fr-empty] .fr-stage,
.fr[data-fr-empty] .fr-id,
.fr[data-fr-empty] .fr-meta,
.fr[data-fr-empty] .fr-why,
.fr[data-fr-empty] .fr-buy { display: none; }
.fr[data-fr-empty] .fr-rail { position: static; width: auto; height: auto; transform: none; margin-top: 26px; }

/* ── the stage ─────────────────────────────────────────────────────────── */
.fr-stage { grid-area: stage; position: relative; align-self: stretch; min-height: 560px; }

/* THE BACK PANEL — a category door at hero scale.

   A lit wall rather than a flat swatch: brighter at the top where the key is,
   the accent itself through the middle, and a little cocoa mixed in at the
   floor. That vertical grade is most of the depth in this frame; the rest is
   the pack overlapping the panel's top edge, which is a cue the circle it
   replaces could not offer at all. */
.fr-back {
  position: absolute;
  left: var(--fr-back-l);
  right: var(--fr-bleed);
  bottom: var(--fr-back-b);
  height: var(--fr-back-h);
  border-radius: 34px 34px 0 0;
  overflow: hidden;
  background:
    /* THE SPOTLIGHT. A lit wall has a hot spot where the key falls on it, and
       putting that hot spot directly behind the lead pack does two jobs at
       once: it separates a dark pack from the colour it is standing against,
       and it is the only element in the frame that points at the product
       without drawing anything.

       The rings are the page's own motif, at a period wide enough to be the
       light's falloff rather than a target. That distinction is the whole
       reason the disc this replaces failed: at 26px on a 620px circle the
       shop's card texture turned into a bullseye, which was loud, which was
       read as the composition's strongest statement, and it was saying
       nothing. Ninety-two pixels on an 850px wall shows the same four or five
       rings a 190px product card shows — the same texture, at the same density
       on the eye. */
    repeating-radial-gradient(circle at 42% 66%,
      rgba(255, 255, 255, .05) 0 2px, rgba(255, 255, 255, 0) 2px 92px),
    radial-gradient(58% 72% at 42% 62%,
      rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, .1) 46%, rgba(255, 255, 255, 0) 78%),
    linear-gradient(177deg,
      color-mix(in srgb, #FFFFFF 18%, var(--fr-accent)) 0%,
      var(--fr-accent) 46%,
      color-mix(in srgb, var(--cocoa) 11%, var(--fr-accent)) 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .38),
    0 24px 40px -28px rgba(46, 28, 21, .5);
  transition: background var(--t-stage) var(--ease-field);
}
/* The shelf's front edge. Fifteen pixels of a darker mix at the base is the
   difference between a coloured rectangle and a surface something is standing
   on — and it is the line the packs' contact shadows land against. */
.fr-back::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 15px;
  background: color-mix(in srgb, var(--cocoa) 20%, var(--fr-accent));
  box-shadow: inset 0 1px 0 rgba(46, 28, 21, .12);
}

/* The shelf label, clipped to the panel's top edge, and a real route into the
   family standing on it. */
.fr-flag {
  position: absolute;
  z-index: 4;
  left: calc(var(--fr-back-l) + 26px);
  bottom: calc(var(--fr-back-b) + var(--fr-back-h) - 17px);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 15px 9px 16px;
  border-radius: 999px;
  background: var(--cocoa);
  color: var(--canvas);
  text-decoration: none;
  font-family: var(--display);
  font-weight: 400;
  font-size: 14px;
  letter-spacing: .03em;
  text-transform: uppercase;
  box-shadow: 0 10px 20px -12px rgba(46, 28, 21, .6);
  transition: transform 200ms var(--ease-field);
}
.fr-flag:hover { transform: translateY(-2px); }
/* THE TARGET IS BIGGER THAN THE PILL. The flag is 30px of ink and it is the
   hero's route into the family standing on the stage — one of the four things
   a thumb is meant to hit on the first screen. Growing the pill to 44 would
   make the loudest object on the scene a label; growing the TARGET costs no
   pixels and is what the 44px rule is about. Same move on the product name and
   the maker card below. */
.fr-flag::before { content: ''; position: absolute; inset: -7px -6px; }
.fr-x { display: inline-block; transition: transform 200ms var(--ease-field); }
.fr-flag:hover .fr-x, .fr-e:hover .fr-x { transform: translateX(3px); }

/* The room's own shadow under the whole group, off-centre to the right because
   the key light is over the headline's shoulder. */
.fr-stage::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(var(--fr-ground) - 30px);
  width: 76%;
  height: 70px;
  transform: translateX(-46%);
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(70, 41, 24, .14), rgba(70, 41, 24, .05) 56%, transparent 74%);
  filter: blur(16px);
  pointer-events: none;
}

.fr-pool {
  position: absolute;
  left: 50%;
  bottom: calc(var(--fr-ground) - 34px);
  width: 56%;
  height: 148px;
  transform: translateX(-53%);
  background: radial-gradient(52% 50% at 46% 56%,
    color-mix(in srgb, var(--fr-accent) 14%, #FFFFFF) 0%,
    color-mix(in srgb, var(--fr-accent) 10%, rgba(255, 255, 255, .5)) 38%,
    rgba(255, 252, 246, 0) 74%);
  filter: blur(14px);
  pointer-events: none;
}

.fr-group { position: absolute; inset: 0; }

/* Three positions, declared once. A slot owns WHERE and HOW FAR AWAY; the
   product owns nothing but its own proportion (--fr-f, written per pack by
   hero.js from the measured packshot). That separation is what lets a 795x914
   pouch and a 470x384 tub stand in the same slot on different days without
   either being recomposed by hand. */
.fr-pk {
  position: absolute;
  left: 50%;
  bottom: calc(var(--fr-ground) + var(--dy));  /* +dy = further back = higher */
  height: calc(var(--fr-lead-h) * var(--k) * var(--fr-f, 1));
  aspect-ratio: var(--fr-ar, .78);
  transform: translate(
    calc(-50% + var(--dx) + var(--mx, 0px) * var(--par, 1)),
    calc(var(--my, 0px) * var(--par, 1)));
  transition: transform 520ms cubic-bezier(.22, .8, .24, 1);
  --fr-f: 1;
}
.fr-pk img {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: contain;
  object-position: bottom center;
  /* THE LAST FIVE PER CENT DISSOLVES INTO THE FLOOR, and this is the asset
     problem solved at source rather than covered over. Several of these
     packshots were cut out of supplier studio photographs that included a
     glossy white reflection under the product, and the cut kept it: invisible
     on the white it was shot against, a pair of hard white wings either side of
     the base on a cream panel, worse on the family's accent. A short fade at the
     bottom turns the borrowed reflection into the same soft contact every object
     in this room has — and it is also just true, because the base of a product
     standing on a lit floor is the part the floor light eats. */
  -webkit-mask-image: linear-gradient(to bottom, #000 93%, rgba(0, 0, 0, .35) 98%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 93%, rgba(0, 0, 0, .35) 98%, transparent 100%);
}

/* Tighter than a row and closer in size than a ladder, so the packs OCCLUDE
   each other — the depth cue the eye trusts most, and the one this composition
   was not using while the silhouettes merely touched. */
.fr-pk-lead { --dx: -14px; --dy: 0px;   --k: 1;    --par: 1;    z-index: 3; }
.fr-pk-mid  { --dx: clamp(124px, calc(var(--fr-lw) * .44), 186px);
              --dy: 32px;  --k: .74;  --par: .56;  z-index: 2; }
.fr-pk-back { --dx: clamp(-206px, calc(var(--fr-lw) * -.47), -140px);
              --dy: 52px;  --k: .57;  --par: .3;   z-index: 1; }

/* THE SHADOWS ARE CAST, NOT PLACED. The box is 160% of the pack and shifted so
   its 31.25% mark sits on the pack's centre line; the contact is drawn there and
   the throw one seventh of the box further right, scaled by how near the object
   is. So the depth is stated twice, once by size and once by light. */
.fr-pk::before {
  content: '';
  position: absolute;
  left: 50%; bottom: -6px;
  width: 160%; height: 50px;
  transform: translateX(-31.25%);
  background:
    radial-gradient(26% 44% at 31.25% 46%,
      rgba(52, 28, 14, .5), rgba(52, 28, 14, .15) 58%, transparent 76%),
    radial-gradient(34% 30% at calc(31.25% + 14% * var(--cast, 1)) 58%,
      rgba(62, 36, 18, .2), rgba(62, 36, 18, .06) 54%, transparent 74%);
  filter: blur(9px);
  pointer-events: none;
}
.fr-pk-mid::before  { opacity: .7;  bottom: -5px; height: 42px; --cast: .7; }
.fr-pk-back::before { opacity: .48; bottom: -4px; height: 34px; --cast: .45; }

/* The key is up and to the left, so the drop shadows offset down and RIGHT
   rather than falling straight down. */
.fr-pk-lead img {
  filter:
    drop-shadow(10px 18px 15px rgba(58, 32, 18, .20))
    drop-shadow(26px 46px 40px rgba(58, 32, 18, .13));
}
/* Recession by FOCUS AND CONTRAST, not by fading: on the family's own accent a
   pack at 72% opacity takes the colour of the panel behind it and becomes a
   stain of it. The neighbours stay nearly opaque and lose their edge instead. */
.fr-pk-mid img {
  opacity: .96;
  filter:
    saturate(.9) contrast(.97) brightness(1.03) blur(.6px)
    drop-shadow(8px 13px 13px rgba(58, 32, 18, .16))
    drop-shadow(0 0 10px rgba(255, 252, 246, .5));
}
/* THE HAZE IS A LIGHT, NOT A FADE, and on this backdrop that distinction is
   load-bearing. Against near-white cream, desaturating a pack was enough to push
   it back. Against the family's own accent it is not: the scarlet kreatin tub
   desaturated on a coral wall is the same value and nearly the same hue as the
   wall, and it disappeared into it completely. So the neighbours keep their
   contrast and are separated from the wall by a soft light rim instead — which
   is what standing further from a lit backdrop actually looks like. */
.fr-pk-back img {
  opacity: .92;
  filter:
    saturate(.74) contrast(.9) brightness(1.07) blur(1.7px)
    drop-shadow(6px 10px 12px rgba(58, 32, 18, .15))
    drop-shadow(0 0 14px rgba(255, 252, 246, .72));
}

/* The lead answers a pointer: the thing being sold lifts slightly toward you
   and its contact tightens underneath, which is what picking an object up looks
   like. Scoped to the layer that is ON so a swap under the pointer still
   arrives from below instead of snapping to the lifted position. */
@media (hover: hover) and (pointer: fine) {
  .fr-stage:hover .fr-pk-lead img[data-state="on"] {
    transform: translateY(-9px) scale(1.012);
    filter:
      drop-shadow(8px 14px 11px rgba(58, 32, 18, .17))
      drop-shadow(22px 54px 46px rgba(58, 32, 18, .16));
  }
  .fr-stage:hover .fr-pk-lead::before { transform: translateX(-31.25%) scale(.94); opacity: .88; }
}
.fr-pk-lead::before { transition: transform 420ms var(--ease-field), opacity 420ms var(--ease-field); }
.fr-pk-lead img {
  transition: opacity var(--t-fade) var(--ease-field),
              transform 420ms cubic-bezier(.22, .8, .24, 1),
              filter 420ms var(--ease-field);
}

/* The swap. Two layers per slot so a pack arrives while the last one is still
   leaving, staggered front to back: a group that moves in one block reads as a
   slide, a group that moves in sequence reads as depth. */
.fr-pk img {
  transition:
    opacity var(--t-fade) var(--ease-field),
    transform var(--t-fade) var(--ease-stage);
}
.fr-pk img[data-state="on"]  { opacity: 1; transform: none; }
.fr-pk img[data-state="out"] { opacity: 0; transform: translateY(-14px) scale(1.03); }
.fr-pk img[data-state="in"]  { opacity: 0; transform: translateY(18px) scale(.96); }
/* The lead's FIRST arrival only. See `paintSlot` in hero.js: an element at zero
   opacity cannot be counted as the largest contentful paint, so the opening
   pack rises into place at full opacity and everything else still cross-fades. */
.fr-pk img[data-first][data-state="in"] { opacity: 1; }
.fr-pk-mid  img { transition-delay: 60ms; }
.fr-pk-back img { transition-delay: 110ms; }

/* ── the nearer plane ──────────────────────────────────────────────────────
   Two small objects in FRONT of the product, both carrying facts the shop
   already holds. A frame with only one depth between backdrop and subject is a
   picture; a frame with something in front of the subject is a space. Neither
   is decorative and neither renders without its fact.

   Both are built from the page's furniture — panel white, a 2.5px cocoa outline
   and the flat offset shadow every product cell wears — rather than from
   frosted glass, which was the clearest single tell that the first screen had
   been designed somewhere else. */
.fr-chip {
  position: absolute;
  left: 50%;
  transform: translateX(calc(-50% - clamp(196px, var(--fr-lw) * .66, 250px))) rotate(-1.5deg);
  bottom: calc(var(--fr-ground) + 22px);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 15px 9px 11px;
  border-radius: 999px;
  background: var(--panel, #FFFCF6);
  border: 2.5px solid var(--cocoa);
  box-shadow: var(--fr-sh-hard);
  transition: transform 260ms var(--ease-field), box-shadow 260ms var(--ease-field);
}
.fr-chip:hover {
  transform: translateX(calc(-50% - clamp(196px, var(--fr-lw) * .66, 250px))) rotate(-1.5deg) translateY(-3px);
  box-shadow: 6px 8px 0 rgba(46, 28, 21, .2);
}
/* The padding belongs to the ANCHOR, not to the card around it: identical to
   look at, and the difference between a 22px target and a 45px one. */
.fr-chip { padding: 0; }
.fr-chip-l {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 15px 9px 11px;
  color: var(--cocoa); text-decoration: none;
}
.fr-chip-c { color: rgba(46, 28, 21, .45); transition: transform 200ms var(--ease-field); flex: none; }
.fr-chip:hover .fr-chip-c { transform: translateX(3px); color: var(--cocoa); }
.fr-chip-m {
  display: grid; place-items: center; flex: none;
  width: 52px; height: clamp(22px, calc(21px * var(--mk, 1)), 44px);
}
.fr-chip-m img { max-height: 100%; max-width: 100%; width: auto; object-fit: contain; }
/* A maker with no approved artwork is set in the page's own display face at the
   marks' optical weight — never boxed, never given someone else's mark. Same
   rule the maker band downstairs runs on. */
.fr-chip-w {
  font-family: var(--display); font-weight: 400;
  font-size: 11px; line-height: 1.05; letter-spacing: .01em; text-transform: uppercase;
  color: var(--cocoa); text-align: center;
}
.fr-chip-t { display: grid; gap: 1px; min-width: 0; }
.fr-chip-a { font-size: 13px; font-weight: 600; color: var(--cocoa); letter-spacing: -.005em; white-space: nowrap; }
.fr-chip-b { font-size: 11.5px; color: var(--fr-soft); white-space: nowrap; }

/* The comparable price, as an object rather than a line of small print. An
   authoritative price divided by an accepted pack quantity is the one number on
   this shelf that is genuinely Rutriven's and genuinely useful — the
   reference's rotated seal with a real fact inside it instead of a promotional
   one. It does not render for a product with no accepted quantity. */
.fr-seal {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: calc(var(--fr-ground) + var(--fr-lead-h) * .54);
  width: 98px; height: 98px;
  display: grid; place-content: center; text-align: center;
  border-radius: 50%;
  background: var(--cocoa);
  color: var(--cream);
  transform: translateX(calc(-50% + clamp(142px, var(--fr-lw) * .48, 196px))) rotate(-8deg);
  box-shadow:
    0 14px 26px -10px rgba(46, 28, 21, .45),
    0 3px 8px rgba(46, 28, 21, .16),
    0 0 0 5px color-mix(in srgb, var(--fr-accent) 55%, transparent);
}
.fr-seal::before {
  content: '';
  position: absolute; inset: 7px;
  border: 1px solid rgba(246, 238, 226, .28);
  border-radius: 50%;
}
.fr-seal-k { display: block; font-size: 8.5px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; opacity: .6; }
.fr-seal-v { display: block; margin-top: 4px; font-family: var(--display); font-weight: 400; font-size: 17px; letter-spacing: .01em; }
.fr-seal-u { display: block; margin-top: 1px; font-size: 9.5px; opacity: .72; }

/* ── the rest of the shelf ─────────────────────────────────────────────────
   Every family that can currently be staged, as the category door this page
   already owns, positioned against the frame rather than given a column and cut
   by the right edge so the shelf reads as carrying on past what fits. The
   family on stage slides further in, so the rail is also where the frame
   reports what it is showing.

   IT ENDS IN THE TWO WAYS OUT. "Összes termék" and "Célpakk" used to be a bare
   link in the panel's bottom corner and a sentence ending in a word — two type
   sizes, two weights, two places, neither of them part of anything. They are
   the last two cells of the same object now, in the page's own door-with-a-note
   shape: cocoa for the catalogue, because cocoa is this shop's furniture, and
   leaf for Célpakk, because leaf is the colour Célpakk already owns everywhere
   else on this page. Two fewer floating links and no new buttons.

   Still buttons, still no timer: nothing here moves until a shopper presses it.
   ───────────────────────────────────────────────────────────────────────── */
.fr-rail {
  position: absolute;
  z-index: 4;
  /* HANGS OFF THE PANEL, NOT OFF THE SCREEN. The shelf running past the frame
     is the composition and the back wall still does it. The rail is different
     because the rail is CONTROLS: at -74 it ended about forty pixels outside
     the viewport at every width from 1240 up, which took the rounded corner off
     every door and, worse, cut the two cells that are the page's secondary
     routes — "Összes termék" and "Célpakk" — through their own arrows. A door
     cropped by the frame reads as a shelf carrying on; a button cropped by the
     frame reads as a bug.

     The panel sits about 34px inside the content edge at every width this rule
     covers, so -24 keeps the overhang and keeps the whole control on screen. */
  right: -24px;
  top: 50%;
  width: 224px;
  height: min(600px, calc(100% - 118px));
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.fr-doors {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-auto-rows: 1fr;
  gap: 12px;
}
.fr-r {
  position: relative;
  display: block;
  width: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 20px;
  overflow: hidden;
  font: inherit;
  color: var(--cocoa);
  cursor: pointer;
  background-color: var(--a, var(--fr-accent));
  background-image:
    repeating-radial-gradient(circle at 50% 60%,
      rgba(46, 28, 21, .075) 0 2px, rgba(46, 28, 21, 0) 2px 26px);
  /* Cast to the LEFT, into the frame: these objects are lit by the same key as
     everything else and they are nearer the viewer than the panel is. */
  box-shadow: -13px 6px 26px -14px rgba(46, 28, 21, .4);
  transform: translateX(var(--tx, 0px));
  transition: transform 300ms var(--ease-field), box-shadow 300ms var(--ease-field);
}
.fr-r:hover { --tx: -16px; box-shadow: -18px 8px 30px -14px rgba(46, 28, 21, .46); }
/* Distinct from the pressed state, which is also a cocoa ring: an amber band
   inside a cocoa one. The page's focus ring is amber, and amber alone
   disappears on the butter tile — the pair is what makes it legible on all six
   accents. */
.fr-r:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: -8px;
  box-shadow: inset 0 0 0 5px var(--amber), -18px 8px 30px -14px rgba(46, 28, 21, .46);
}
.fr-r[aria-pressed="true"] {
  --tx: -30px;
  box-shadow: -20px 8px 30px -14px rgba(46, 28, 21, .46),
              inset 0 0 0 2.5px var(--cocoa);
}
.fr-r-p {
  position: absolute;
  left: 0; right: 74px; top: 6%;
  height: 66%;
  display: grid; place-items: center;
  /* Explicit 1fr tracks, not `auto`: with auto tracks the row takes the
     packshot's intrinsic height, `max-height: 100%` resolves against that row
     rather than against the tile, and a 900px pouch renders at 900px inside a
     100px door — which is exactly what it did. */
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
}
.fr-r-p img {
  max-height: 100%; max-width: calc(100% - 20px); width: auto;
  object-fit: contain;
  filter: drop-shadow(4px 7px 7px rgba(60, 36, 6, .26));
  transition: transform 300ms var(--ease-field);
}
.fr-r:hover .fr-r-p img { transform: translateY(-4px) scale(1.04); }
/* A supplier photograph that was never cut out arrives on a white rectangle,
   and on a saturated plate a white rectangle has to become an object. Same rule
   and same frame the product cards use. */
.fr-r-p[data-shot="photo"] img {
  border: 2px solid var(--cocoa);
  border-radius: 8px;
  background: var(--panel, #FFFCF6);
  filter: none;
  box-shadow: 3px 4px 0 rgba(46, 28, 21, .22);
}
.fr-r-l {
  position: absolute;
  left: 11px; bottom: 8px;
  max-width: 128px;
  padding: 4px 10px 5px;
  border-radius: 999px;
  background: var(--cocoa);
  color: var(--canvas);
  font-size: 10px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.fr-exits { flex: 0 0 auto; display: grid; gap: 8px; }
.fr-e {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 10px;
  padding: 10px 74px 11px 16px;
  border-radius: 18px;
  text-decoration: none;
  box-shadow: -13px 6px 26px -14px rgba(46, 28, 21, .4);
  transform: translateX(var(--tx, 0px));
  transition: transform 300ms var(--ease-field), box-shadow 300ms var(--ease-field);
}
.fr-e:hover { --tx: -16px; box-shadow: -18px 8px 30px -14px rgba(46, 28, 21, .46); }
.fr-e-all { background: var(--cocoa); color: var(--canvas); }
.fr-e-cp  { background: var(--green-deep, #16764F); color: #F3FBF6; }
.fr-e-t {
  grid-column: 1 / -1;
  font-family: var(--display); font-weight: 400; font-size: 13px;
  letter-spacing: .01em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fr-e .fr-x { grid-column: 2; grid-row: 2; font-size: 15px; opacity: .8; }
.fr-e-n {
  grid-column: 1;
  margin-top: 1px;
  font-size: 11.5px;
  opacity: .8;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── the frame arriving ────────────────────────────────────────────────────
   The one piece of motion nobody asked for, and the argument for it is that a
   product scene is the one composition where arrival is not decoration: objects
   that settle onto a shelf were PUT there, objects that are simply present were
   pasted there. It happens once, it is over in under a second, and it ends on
   exactly the resting frame.

   Authored as `backwards`-filled animations rather than a class the script
   toggles: nothing is hidden waiting for JavaScript, so the failure mode of the
   script not running is the resting frame, not an empty panel. */
@keyframes fr-rise { from { opacity: 0; transform: translateY(16px); } }
@keyframes fr-pack { from { opacity: 0; transform: translate(calc(-50% + var(--dx)), 52px); } }
@keyframes fr-wall { from { opacity: 0; transform: translateY(34px); } }
@keyframes fr-pop  { from { opacity: 0; transform: var(--fr-pop-from) scale(.7); } }
@keyframes fr-slide { from { opacity: 0; transform: translateX(46px); } }

.fr-h1   { animation: fr-rise 640ms 40ms  var(--ease-stage) backwards; }
.fr-id   { animation: fr-rise 640ms 150ms var(--ease-stage) backwards; }
.fr-meta { animation: fr-rise 640ms 200ms var(--ease-stage) backwards; }
.fr-why  { animation: fr-rise 640ms 250ms var(--ease-stage) backwards; }
.fr-buy  { animation: fr-rise 640ms 300ms var(--ease-stage) backwards; }

.fr-back { animation: fr-wall 760ms 20ms var(--ease-stage) backwards; }
.fr-flag { animation: fr-rise 620ms 380ms var(--ease-stage) backwards; }
.fr-pk-lead { animation: fr-pack 720ms 120ms var(--ease-stage) backwards; }
.fr-pk-mid  { animation: fr-pack 720ms 190ms var(--ease-stage) backwards; }
.fr-pk-back { animation: fr-pack 720ms 250ms var(--ease-stage) backwards; }

.fr-chip {
  --fr-pop-from: translateX(calc(-50% - clamp(196px, var(--fr-lw) * .66, 250px)));
  animation: fr-pop 560ms 420ms var(--ease-stage) backwards;
}
.fr-seal {
  --fr-pop-from: translateX(calc(-50% + clamp(142px, var(--fr-lw) * .48, 196px))) rotate(-14deg);
  animation: fr-pop 560ms 500ms var(--ease-stage) backwards;
}
.fr-r { animation: fr-slide 620ms calc(340ms + var(--i, 0) * 60ms) var(--ease-stage) backwards; }
.fr-e { animation: fr-slide 620ms 660ms var(--ease-stage) backwards; }
.fr-e + .fr-e { animation-delay: 720ms; }

/* ── between the two authored widths ───────────────────────────────────────
   Not a third composition and not a set of hand-tuned numbers per laptop: the
   stage's only fixed quantity is the lead's slot height, and every other object
   on it already measures itself off the lead's rendered box. So the band
   between the two authored frames needs the lead to scale with the viewport and
   nothing else to be told about it.
   ───────────────────────────────────────────────────────────────────────── */
@media (max-width: 1239px) {
  .fr { --fr-lead-h: clamp(300px, 26vw, 372px); --fr-bleed: -220px; }
  .fr-cols {
    grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
    gap: 0 24px;
    padding: 56px 168px 48px 52px;
  }
  .fr-panel, .fr-cols { min-height: 626px; }
  .fr-stage { min-height: 470px; }
  .fr-h1 { font-size: clamp(34px, 3.7vw, 46px); margin-bottom: 24px; padding-bottom: 22px; }
  .fr-name { font-size: 21px; }
  .fr-amt { font-size: 30px; }
  .fr-seal { width: 92px; height: 92px; bottom: calc(var(--fr-ground) + var(--fr-lead-h) * .62); }
  .fr-seal-v { font-size: 16px; }

  .fr-rail { right: -20px; width: 198px; height: min(520px, calc(100% - 96px)); }
  .fr-r-p { right: 62px; }
  .fr-r-l { max-width: 112px; }
  .fr-e { padding-right: 62px; }

  /* The maker card sits to the LEFT of the lead, and below about 1240 that is
     where the purchase column is. It moves to the right of the group instead of
     being dropped: a composition should lose an offset before it loses an
     element, and this element is one of the best things in the frame. */
  .fr-chip {
    bottom: calc(var(--fr-ground) - 4px);
    transform: translateX(calc(-50% + clamp(120px, var(--fr-lw) * .42, 170px))) rotate(1.5deg);
    --fr-pop-from: translateX(calc(-50% + clamp(120px, var(--fr-lw) * .42, 170px)));
  }
  .fr-chip:hover {
    transform: translateX(calc(-50% + clamp(120px, var(--fr-lw) * .42, 170px))) rotate(1.5deg) translateY(-3px);
  }
}

/* Below this the zones stop fitting side by side at any type size still worth
   reading, so the shelf lies down under the composition and keeps the same idea
   turned ninety degrees: the same doors, the same two exits, and the row still
   cut by the right edge of the frame. */
@media (max-width: 1079px) {
  .fr { --fr-lead-h: clamp(268px, 29vw, 330px); --fr-ground: 46px; --fr-bleed: -80px; }
  /* Same reservation as the phone band, for the same reason: between 761 and
     1079 the shelf moves under the stage and the script's arrival was moving
     the document about 130px. The settled frame runs 600 to 640 across the
     band, and this is the floor of that rather than the ceiling. */
  .fr-cols { min-height: 600px; }
  .fr-cols {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: 'say stage' 'rail rail';
    gap: 24px 26px;
    padding: 50px 38px 40px;
    min-height: 0;
  }
  .fr-panel { min-height: 0; }
  .fr-say { align-self: center; }
  .fr-stage { min-height: clamp(360px, 40vw, 440px); }

  .fr-rail {
    grid-area: rail;
    position: static;
    width: auto; height: auto;
    transform: none;
    flex-direction: row;
    align-items: stretch;
    gap: 10px;
    margin-right: -58px;
  }
  .fr-doors {
    flex: 1 1 auto;
    grid-auto-rows: auto;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 10px;
  }
  .fr-r { height: 116px; }
  .fr-r:hover, .fr-r[aria-pressed="true"] { --tx: 0px; }
  .fr-r[aria-pressed="true"] { box-shadow: inset 0 0 0 2.5px var(--cocoa); }
  .fr-r-p { right: 0; top: 8%; height: 56%; }
  .fr-r-l { max-width: calc(100% - 24px); }
  .fr-exits { flex: 0 0 214px; align-content: stretch; }
  /* The row is cut by the frame like everything else in it, so the arrow has to
     sit inside what survives the crop rather than at the box's own right edge. */
  .fr-e { padding: 8px 74px 9px 16px; align-content: center; }
  .fr-e:hover { --tx: 0px; }

  /* ── touch targets ──────────────────────────────────────────────────────
     Every control here is assumed to be reached by a thumb, and the shop's
     floor for that is 44px. Three of them are visually smaller than that and
     must stay visually smaller — a shelf label, a maker pill and a product name
     are all sized by the composition, and padding them to 44 would change the
     design to satisfy a number.

     So each grows an invisible overlay instead. The hit area reaches the floor,
     the painted object does not move a pixel, and nothing else on the screen is
     pushed by it. */
  /* NOT `.fr-flag` — it is already absolutely positioned against the wall, and
     re-declaring `position: relative` here dropped it out of the scene and into
     the top of the document. Only the two that are statically positioned need
     a containing block. */
  .fr-chip-l, .fr-name { position: relative; }
  .fr-flag::after, .fr-chip-l::after, .fr-name::after {
    content: '';
    position: absolute;
    left: -6px; right: -6px;
    top: 50%;
    height: 46px;
    transform: translateY(-50%);
  }
  .fr-name::after { left: 0; right: 0; }
}

/* ══ 390 ═══════════════════════════════════════════════════════════════════
   AUTHORED, NOT COLLAPSED — and this pass is the one that made that true.

   What was here before was the desktop idea narrowed: a headline, then a family
   line, then a 230px scene, then a hairline, then a maker line, a name, a
   sentence, a price, a size line, a stock pill, a full-width button, and only
   then the family shelf. Eleven stacked rows. The useful part of the hero ended
   below the fold, the shelf ended under the thumb bar, and the first screen had
   become a very long product card standing between the visitor and the shop.

   What it is now is the same six elements — SCENE, FLAG, FACTS, ACTION, MAKER,
   SHELF — re-composed so that all six land inside one screen:

     the SCENE runs edge to edge of the panel and gets taller, not shorter,
       because it is now the only large thing and can afford to be the biggest

     the FLAG moves onto the scene, so naming the family costs no row at all

     the MAKER stays a card ON the scene rather than becoming a line of type
       above the product: it is one of the strongest things in the frame and the
       phone is where an overlapping plane is worth the most

     the NAME and the PRICE share one row, the size, comparable price and stock
       share the next, and the accepted sentence is dropped — the phone's job on
       a first screen is the shop's facts, and the sentence is on this same
       product's card further down the page

     the ACTION is one button with the stock fact beside it, not under it

     the SHELF is a thumb-pushable row of the same doors ending in the same two
       exits, cut by the right edge, which is both the affordance and the
       statement that there is more

   Eleven rows become six, the hero ends around 720px on an 844px screen, and
   the cocoa maker band below is visible at the fold instead of pushed off it.
   ───────────────────────────────────────────────────────────────────────── */
@media (max-width: 760px) {
  .fr {
    --fr-lead-h: clamp(212px, 54vw, 268px);
    --fr-ground: 22px;
    --fr-bleed: -26px;
    /* Pulled further left and made taller relative to the pack, so at this
       width the wall is cut by BOTH side edges of the frame and reads as
       something the phone is standing too close to. */
    --fr-back-l: calc(50% - var(--fr-lw) * 1.24);
    --fr-back-b: calc(var(--fr-ground) - 18px);
    --fr-back-h: calc(var(--fr-lh) * .98);
    padding: 9px 9px 0;
  }
  .fr-panel { min-height: 0; border-radius: 28px; }
  .fr-panel::after { height: 78px; }
  .fr-bloom::before { width: 520px; height: 440px; left: -232px; top: -246px; opacity: .46; }
  .fr-bloom::after { width: 400px; height: 320px; right: -186px; bottom: -150px; opacity: .3; }

  .fr-cols {
    display: flex;
    flex-direction: column;
    grid-template-areas: none;
    padding: 18px 18px 16px;
    /* THE FRAME IS RESERVED BEFORE IT IS FILLED, which the desktop grid has
       always done (`min-height: 700px` on the base rule, 626 in the 1080 band)
       and the phone had explicitly switched off with `min-height: 0`.

       The hero's name, price, size, comparable price, purchase control and
       shelf are all written by the script, because which product is standing
       there is a join between an approved list and today's stock and no host
       can put it in the markup. So on a phone on a slow connection the panel
       was painted 380px tall and re-laid at 647 about two seconds later, and
       the whole document below it moved 267px — a 1.0 CLS on the measured
       mobile profile, against a 0.1 budget, and a real one: it lands while a
       thumb is already reaching for the first screen.

       The reservation is the stage's own clamp plus the block beneath it,
       which is a fixed set of rows — one name line, one facts line, one
       control and the shelf. Tied to the same clamp, so it tracks the width
       rather than being right at 390 and wrong at 700. It is set a little
       under the settled height rather than over: under-reserving leaves a few
       pixels of movement, over-reserving leaves a permanent gap at the foot of
       every phone hero, and the gap is the worse of the two. */
    min-height: calc(clamp(228px, 61vw, 282px) + 400px);
    gap: 0;
  }
  /* Source order is stage, facts, shelf. On the phone the headline comes first,
     so it — and only it — is lifted out of the facts block. */
  .fr-say { display: contents; }
  .fr-h1 {
    order: 1;
    margin: 0;
    padding: 0;
    border: 0;
    font-size: clamp(27px, 7.6vw, 32px);
  }
  .fr-stage { order: 2; }
  .fr-id    { order: 3; }
  .fr-meta  { order: 4; }
  .fr-buy   { order: 5; }
  .fr-rail  { order: 6; }

  .fr-stage {
    min-height: 0;
    height: clamp(228px, 61vw, 282px);
    margin: 10px -18px 0;
  }
  .fr-back { border-radius: 26px 26px 0 0; }
  .fr-back::after { height: 12px; }
  .fr-flag {
    left: 18px;
    bottom: calc(var(--fr-back-b) + var(--fr-back-h) - 15px);
    padding: 6px 12px 7px 13px;
    font-size: 11.5px;
    gap: 6px;
  }
  .fr-pool { width: 58%; height: 92px; bottom: calc(var(--fr-ground) - 26px); transform: translateX(-38%); }
  .fr-stage::after { width: 62%; height: 34px; bottom: calc(var(--fr-ground) - 14px); transform: translateX(-38%); }

  /* Two packs, not three: three silhouettes inside 350px is a smear, not depth.
     The pair sits RIGHT of centre, which is what makes the phone composition
     work — it leaves the wall's left third for the two objects on the nearer
     plane, so the flag, the pack and the maker card occupy three bands of the
     scene instead of arguing over the middle of it. The spread is still
     measured off the lead. */
  .fr-pk-lead { --dx: 14px; }
  .fr-pk-mid  { --dx: clamp(68px, calc(var(--fr-lw) * .47), 106px); --dy: 24px; --k: .7; }
  .fr-pk-back { display: none; }

  /* The maker card STAYS, at a size a thumb can hit, overlapping the lead's
     lower left where there is nothing but shelf behind it. */
  .fr-chip {
    left: 0;
    bottom: calc(var(--fr-ground) + 2px);
    transform: rotate(-1.5deg);
    margin-left: 14px;
    padding: 7px 13px 7px 9px;
    gap: 9px;
    border-width: 2px;
    --fr-pop-from: none;
  }
  .fr-chip:hover { transform: rotate(-1.5deg); }
  .fr-chip-m { width: 40px; height: clamp(18px, calc(17px * var(--mk, 1)), 34px); }
  .fr-chip-a { font-size: 12px; }
  .fr-chip-b { display: none; }
  .fr-chip-c { width: 13px; height: 13px; }

  .fr-seal {
    width: 72px; height: 72px;
    bottom: calc(var(--fr-ground) + var(--fr-lead-h) * .66);
    transform: translateX(calc(-50% + clamp(104px, var(--fr-lw) * .72, 138px))) rotate(-8deg);
    box-shadow:
      0 10px 20px -10px rgba(46, 28, 21, .45),
      0 0 0 4px color-mix(in srgb, var(--fr-accent) 55%, transparent);
    --fr-pop-from: translateX(calc(-50% + clamp(104px, var(--fr-lw) * .72, 138px))) rotate(-14deg);
  }
  .fr-seal::before { inset: 5px; }
  .fr-seal-k { font-size: 7px; letter-spacing: .1em; }
  .fr-seal-v { font-size: 13.5px; margin-top: 2px; }
  .fr-seal-u { font-size: 8px; }

  /* One row for identity and price, one for everything the price is for. */
  .fr-id {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-top: 12px;
  }
  .fr-name {
    font-size: 17.5px;
    line-height: 1.24;
    min-width: 0;
    /* PADDING, NOT THE OVERLAY. The 1079 band gives this link a 46px invisible
       `::after` so a 22px name is still a 44px target — and the two-line clamp
       here needs `overflow: hidden`, which CROPS that overlay. The hit area was
       silently back to 22px on the one width where a thumb is the only way to
       press it. Padding is part of the element's own box, so it survives the
       clip and is hit-testable; the negative margin puts the row back where it
       was, and the clamp still counts lines rather than pixels. */
    padding-block: 11px; margin-block: -11px;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  }
  .fr-name::after { content: none; }
  .fr-price { flex: none; }
  .fr-amt { font-size: 25px; }
  .fr-meta { margin-top: 4px; font-size: 12.5px; }
  .fr-why { display: none; }

  /* The stock fact and the action share a line. Stacking them cost a row, and
     at this width the pill is 140px of a 336px line that the button does not
     need. The button still reaches 52px, above the 44px floor. */
  .fr-buy { margin-top: 11px; gap: 9px; }
  .fr-stock { padding: 7px 12px 7px 10px; font-size: 11.5px; }
  .fr-cta {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 52px;
    padding: 5px 18px 5px 5px;
  }
  .fr-cta-b { width: 42px; height: 42px; }
  .fr-cta-t { font-size: 15px; }

  /* The shelf, same doors, same two exits, turned into a row a thumb can push.
     Five families and two exits will not divide a 336px frame into seven
     legible objects, so rather than shrink them to unreadable chips the row
     keeps them at a real size and runs off the right edge — which is both the
     desktop frame's own idea and the clearest statement that there is more of
     it. The cut cell is the affordance; no arrows, no dots, no timer. */
  /* THE DOORS SCROLL; THE TWO EXITS DO NOT. Seven cells will not fit a 336px
     row, and the first version put all seven in one scroller — which hid
     "Összes termék" and "Célpakk" behind a gesture nobody advertised, on the
     width where those two routes matter most. So the rail becomes two rows: the
     families scroll sideways and are cut by the frame, which is a legible
     affordance because a cut door obviously continues; and the two ways out sit
     underneath as a fixed pair, always visible, never scrolled past. */
  .fr-rail {
    position: static;
    width: auto; height: auto;
    transform: none;
    flex-direction: column;
    margin: 13px 0 0;
    gap: 8px;
  }
  .fr-doors {
    flex: 0 0 auto;
    display: grid;
    grid-auto-flow: column;
    grid-auto-rows: auto;
    grid-auto-columns: 98px;
    gap: 9px;
    margin-right: -18px;
    padding-right: 18px;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  .fr-doors::-webkit-scrollbar { display: none; }
  .fr-r {
    height: 94px;
    border-radius: 16px;
    scroll-snap-align: start;
  }
  .fr-r:hover, .fr-r[aria-pressed="true"] { --tx: 0px; }
  .fr-r[aria-pressed="true"] { box-shadow: inset 0 0 0 2.5px var(--cocoa); }
  .fr-r-p { right: 0; top: 7%; height: 56%; }
  .fr-r-p img { max-width: calc(100% - 16px); }
  .fr-r-l { left: 7px; bottom: 7px; max-width: 84px; font-size: 8.5px; padding: 3px 7px 4px; letter-spacing: .04em; }

  .fr-exits {
    /* The 1079 band gives this a 214px flex basis for the row layout it has
       there. In a COLUMN flex container that basis is a height, which is how two
       46px pills became a 214px block. */
    flex: 0 0 auto;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 8px;
  }
  .fr-e {
    min-height: 46px;
    align-content: center;
    border-radius: 14px;
    padding: 7px 12px 8px 13px;
  }
  .fr-e:hover { --tx: 0px; }
  .fr-e-t { font-size: 12.5px; }
  .fr-e-n { font-size: 10.5px; }
}

/* Two exits side by side need about 150px each to set "Összes termék" and
   "Segítünk választani" without ellipsing, and a 320 screen has 137. Half a
   point off each line is the whole fix; stacking them would cost a row on the
   one screen size that has none to spare. */
@media (max-width: 349px) {
  .fr-e { padding-inline: 10px 9px; }
  .fr-e-t { font-size: 11.5px; letter-spacing: .01em; }
  /* "Segítünk választani" needs 86px and a 320 screen leaves 78, so the note
     WRAPS here instead of ellipsing. A cell that grows by a line is a cell; a
     word cut in half by a fade reads as a fault. Half a point off the type was
     not enough on its own — this is the fix, the size is the polish. */
  .fr-e-n { font-size: 9.5px; white-space: normal; line-height: 1.25; }
}

/* ── motion off ────────────────────────────────────────────────────────────
   The resting frame is the composition, so turning motion off removes the
   transitions and the arrival and nothing else. Nothing here is load-bearing:
   the depth is built from scale, contrast, occlusion and shadow, all static. */
@media (prefers-reduced-motion: reduce) {
  .fr *, .fr *::before, .fr *::after {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    animation: none !important;
  }
  .fr-stage:hover .fr-pk-lead img[data-state="on"] { transform: none; }
  /* The swap arrives instantly rather than travelling, and the layer that is ON
     stays on. An earlier cut of this rule flattened every [data-state] to
     transparent, which emptied the stage for the one visitor it exists to look
     after. */
  .fr-pk img[data-state="in"], .fr-pk img[data-state="out"] { transform: none; }
}
