/* ==========================================================================
   NETTORRA — showcase demos
   The two cream client-style pages, sharing the persistent Nettorra
   header/footer. Every class here is prefixed (.sc- shared, .reno- .prod- per
   page) so nothing collides with home.css.

   Each page sets its own palette on <body class="page-…">; the palette vars
   are consumed by the shared .sc-* components below.

   The luxury demo used to live here too. It was rebuilt on a near-black sheet
   of its own, css/lux.css, and every .lux- rule has been removed rather than
   left to rot — see the note on palettes below for why that mattered.
   ========================================================================== */

/* --- 1. Palettes ------------------------------------------------------- */

/* .page-lux is deliberately absent from this file. The luxury demo was rebuilt
   on its own near-black sheet (css/lux.css) and no longer loads this one — but
   the router adds stylesheets and never removes them, so a visitor going from
   this page to that one arrives with showcase.css still attached. While the
   cream palette lived here, `.page-lux #page` out-specified anything lux.css
   could say about #page and repainted the whole demo cream-on-dark. Deleting
   the rules is the fix; leaving them and fighting back with !important would
   only move the problem. */

.page-reno,
.page-prod {
  --sc-y: clamp(5rem, 11vw, 10rem);
  --sc-radius: 3px;
}

/* Renovation — warmer, copper */
.page-reno {
  --sc-bg: #F5F0E8;
  --sc-bg-2: #E7DCCC;
  --sc-ink: #201A14;
  --sc-ink-soft: #64574A;
  --sc-line: rgba(32, 26, 20, .15);
  --sc-accent: #B06A38;
  --sc-dark: #191310;
  --sc-dark-soft: #C9B7A3;
}

/* Product — the palette is the juice. Beetroot, chlorophyll and blood orange
   at full saturation: not a chosen scheme so much as an inventory of what goes
   in the press, because that is what raw produce looks like when nothing has
   been heated.

   The four field colours belong to the presses themselves and are set on the
   panels, so the whole window changes to the next one as the reader moves down
   the page. What is left here is the dark those fields are cut out of, and the
   cream — which is the paper of the label on every bottle, and so the one
   colour on this page that is allowed to carry text.

   That dark is blackcurrant, not black. A neutral near-black reads as an
   absence of colour, which is the opposite of what this page claims, and it
   dulls every field laid over it. Saturated is the move rather than lighter:
   all the produce on this page is photographed on black and screened onto the
   background, so raising the background's brightness would wash the fruit out
   to white — but raising its saturation only tints the light coming through
   it, which is what a bottle does. */
.page-prod {
  --sc-bg: #1A0721;
  --sc-bg-2: #2A0C2E;
  --sc-ink: #FFF6E8;
  --sc-ink-soft: #D3A9C4;
  --sc-line: rgba(255, 246, 232, .2);
  --sc-accent: #A7E240;
  --sc-accent-2: #FF2D7E;
  --sc-accent-3: #FF8A1F;
  --sc-dark: #14041A;
  --sc-dark-soft: #C79FBC;
}

.page-reno,
.page-prod { background: var(--sc-bg); }

/* Scope the palette to the swapped view so the navy header and footer keep
   their own styling. */
.page-reno #page,
.page-prod #page {
  color: var(--sc-ink);
  background: var(--sc-bg);
}

/* --- 2. Shared layout + type ------------------------------------------- */

.sc-sec { position: relative; padding-block: var(--sc-y); }
.sc-sec--tight { padding-block: clamp(3.5rem, 7vw, 6rem); }
.sc-sec--dark { background: var(--sc-dark); color: #F3EDE3; }
.sc-sec--alt { background: var(--sc-bg-2); }

.sc-shell { width: min(100% - (var(--gutter) * 2), 1180px); margin-inline: auto; }
.sc-shell--narrow { width: min(100% - (var(--gutter) * 2), 780px); }

.sc-eyebrow {
  font-family: var(--font-display);
  font-size: .7rem;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--sc-accent);
}
.sc-sec--dark .sc-eyebrow { color: var(--sc-dark-soft); }

.sc-serif {
  font-family: var(--font-serif);
  font-weight: 300;
  line-height: 1.02;
  letter-spacing: -.012em;
  text-wrap: balance;
}
.sc-d1 { font-size: clamp(2.9rem, 8.4vw, 6.6rem); }
.sc-d2 { font-size: clamp(2.1rem, 5.2vw, 4rem); }
.sc-d3 { font-size: clamp(1.55rem, 3vw, 2.35rem); }

.sc-lede {
  font-size: clamp(1.02rem, 1.5vw, 1.2rem);
  line-height: 1.78;
  color: var(--sc-ink-soft);
}
.sc-sec--dark .sc-lede { color: var(--sc-dark-soft); }

.sc-body { color: var(--sc-ink-soft); line-height: 1.85; }
.sc-body p + p { margin-top: 1.15rem; }

.sc-rule { height: 1px; border: 0; margin: 0; background: var(--sc-line); }

.sc-caption {
  font-family: var(--font-display);
  font-size: .74rem;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sc-ink-soft);
}

/* --- 3. Buttons -------------------------------------------------------- */

.sc-btn {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  padding: 1.05rem 2.1rem;
  border-radius: 100px;
  border: 1px solid transparent;
  font-family: var(--font-display);
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: transform .35s var(--ease-out), background-color .3s, color .3s, border-color .3s, box-shadow .35s;
}
.sc-btn:hover { transform: translateY(-2px); }
.sc-btn svg { width: 15px; height: 15px; transition: transform .35s var(--ease-out); }
.sc-btn:hover svg { transform: translateX(4px); }

.sc-btn--solid { background: var(--sc-ink); color: var(--sc-bg); }
.sc-btn--solid:hover { background: var(--sc-accent); color: #fff; }

.sc-btn--line { border-color: rgba(255, 255, 255, .45); color: #fff; }
.sc-btn--line:hover { background: rgba(255, 255, 255, .12); border-color: #fff; }

.sc-btn--light { background: #fff; color: #17120C; }
.sc-btn--light:hover { background: var(--sc-accent); color: #fff; }

.sc-btn--glow {
  /* Green into orange, skipping beet: dark type clears AA across both of these
     (13.7:1 and 7.0:1) where beet only reached 4.4:1. */
  background: linear-gradient(100deg, var(--sc-accent), var(--sc-accent-3));
  /* Dark label, not white — the gradient starts on chlorophyll green, which is
     far too light to carry white type at any point along the sweep. */
  color: var(--sc-dark);
  box-shadow: 0 20px 55px -20px rgba(159, 232, 112, .55);
}
.sc-btn--glow:hover { box-shadow: 0 26px 65px -18px rgba(159, 232, 112, .75); }

/* --- 4. Placeholder variants -------------------------------------------
   .ph lives in base.css and is navy-tinted. These retint it for the
   showcase palettes without touching the base component.
   ---------------------------------------------------------------------- */

.ph--sand {
  background:
    radial-gradient(120% 130% at 20% 14%, rgba(226, 200, 158, .95) 0%, transparent 58%),
    radial-gradient(110% 120% at 84% 26%, rgba(180, 154, 121, .8) 0%, transparent 60%),
    radial-gradient(140% 130% at 52% 104%, rgba(112, 94, 73, .6) 0%, transparent 64%),
    linear-gradient(160deg, #E6D9C3, #BCA98C);
}
.ph--stone {
  background:
    radial-gradient(120% 130% at 24% 16%, rgba(206, 194, 176, .9) 0%, transparent 58%),
    radial-gradient(110% 120% at 82% 82%, rgba(126, 114, 100, .75) 0%, transparent 62%),
    linear-gradient(160deg, #CFC5B4, #8B8172);
}
.ph--dusk {
  background:
    radial-gradient(85% 62% at 50% 104%, rgba(233, 168, 92, .72) 0%, transparent 62%),
    radial-gradient(120% 90% at 12% 4%, rgba(150, 133, 112, .58) 0%, transparent 64%),
    linear-gradient(170deg, #524534 0%, #33291E 48%, #171208 100%);
}
.ph--raw {
  background:
    radial-gradient(120% 120% at 26% 14%, rgba(176, 172, 164, .7) 0%, transparent 58%),
    radial-gradient(100% 110% at 80% 84%, rgba(92, 89, 84, .65) 0%, transparent 60%),
    linear-gradient(165deg, #96928B, #4E4B46);
}
.ph--copper {
  background:
    radial-gradient(110% 120% at 22% 18%, rgba(233, 176, 122, .9) 0%, transparent 58%),
    radial-gradient(110% 120% at 82% 78%, rgba(150, 90, 48, .78) 0%, transparent 62%),
    linear-gradient(160deg, #D9B48C, #7C4E2C);
}
.ph--bold {
  background:
    radial-gradient(90% 90% at 24% 18%, rgba(159, 232, 112, .85) 0%, transparent 58%),
    radial-gradient(95% 95% at 82% 32%, rgba(225, 29, 107, .82) 0%, transparent 60%),
    radial-gradient(120% 110% at 55% 106%, rgba(255, 106, 26, .55) 0%, transparent 62%),
    linear-gradient(160deg, #16330E, #060D05);
}
.ph--void {
  background:
    radial-gradient(72% 72% at 50% 46%, rgba(159, 232, 112, .42) 0%, transparent 62%),
    radial-gradient(60% 60% at 76% 88%, rgba(255, 106, 26, .26) 0%, transparent 60%),
    linear-gradient(160deg, #10220B, #050A04);
}
.ph--tint {
  background:
    radial-gradient(92% 92% at 28% 20%, var(--c1, rgba(159, 232, 112, .9)) 0%, transparent 60%),
    radial-gradient(110% 110% at 78% 86%, var(--c2, rgba(225, 29, 107, .85)) 0%, transparent 62%),
    linear-gradient(160deg, #10220B, #050A04);
}

/* Light placeholders need dark chrome on the label + texture. */
.ph--sand .ph__tag,
.ph--stone .ph__tag,
.ph--raw .ph__tag,
.ph--copper .ph__tag {
  color: rgba(26, 20, 13, .74);
  background: rgba(255, 252, 246, .62);
  border-color: rgba(26, 20, 13, .14);
}
.ph--sand::before,
.ph--stone::before,
.ph--raw::before,
.ph--copper::before {
  background-image:
    linear-gradient(rgba(26, 20, 13, .055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(26, 20, 13, .055) 1px, transparent 1px);
}
.ph--sand::after,
.ph--stone::after,
.ph--copper::after {
  background: conic-gradient(from 210deg at 50% 50%, transparent 0deg, rgba(255, 255, 255, .3) 60deg, transparent 130deg);
}

.ph--hero { border-radius: 0; }

/* Slot label for full-bleed scenes. It is a sibling of the layer, not a child,
   because the engine scales the layer and would carry the label off-screen. */
.sc-slot {
  position: absolute;
  z-index: 4;
  top: clamp(5.4rem, 12vh, 7.5rem);
  right: var(--gutter);
  margin: 0;
  max-width: min(60vw, 30rem);
}

/* Being a sibling, the label misses the .ph:has(> img) rule in base.css, so
   each full-bleed slot retires its own label once the art lands. */
.sc-cine__layer:has(.ph > img) ~ .sc-slot,
.reno-swap__layer--before:has(.ph > img) ~ .reno-swap__slot--before,
.reno-swap__layer--frame:has(.ph > img) ~ .reno-swap__slot--frame,
.reno-swap__layer--after:has(.ph > img) ~ .reno-swap__slot--after { display: none; }

/* --- 5. Parallax frames ------------------------------------------------- */

.sc-frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--sc-radius);
  background: var(--sc-bg-2);
}
.sc-frame > .ph {
  position: absolute;
  inset: -10%;
  border-radius: 0;
}
/* The 10% overscan gives the parallax room to travel, but it also pushes the
   label outside the frame's clip — pull it back into the visible window. */
.sc-frame > .ph > .ph__tag {
  position: absolute;
  left: 11%;
  bottom: 12%;
  margin: 0;
}
.sc-frame--21x9 { aspect-ratio: 21 / 9; }
.sc-frame--16x9 { aspect-ratio: 16 / 9; }
.sc-frame--3x2  { aspect-ratio: 3 / 2; }
.sc-frame--4x3  { aspect-ratio: 4 / 3; }
.sc-frame--1x1  { aspect-ratio: 1 / 1; }
.sc-frame--4x5  { aspect-ratio: 4 / 5; }
.sc-frame--3x4  { aspect-ratio: 3 / 4; }

.sc-bleed { width: 100%; }

/* --- 6. Cinematic hero shell (shared by the two editorial demos) -------- */

.sc-cine { position: relative; }
.sc-cine__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  background: #08060A;
}
.sc-cine__layer {
  position: absolute;
  inset: -8%;
  will-change: transform;
}
.sc-cine__layer .ph { position: absolute; inset: 0; border-radius: 0; }

/* Top scrim keeps the white header legible, bottom scrim seats the copy. */
.sc-scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(5, 6, 10, .74) 0%, rgba(5, 6, 10, .18) 20%, rgba(5, 6, 10, 0) 34%),
    linear-gradient(0deg, rgba(5, 6, 10, .86) 0%, rgba(5, 6, 10, .1) 46%, rgba(5, 6, 10, 0) 66%),
    radial-gradient(130% 92% at 50% 46%, transparent 38%, rgba(5, 6, 10, .58) 100%);
}

.sc-cine__body {
  position: relative;
  z-index: 3;
  height: 100%;
  display: grid;
  align-content: end;
  color: #fff;
  padding-bottom: clamp(3.5rem, 9vh, 6.5rem);
}

.sc-cue {
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  font-family: var(--font-display);
  font-size: .68rem;
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .7);
}
.sc-cue i {
  width: 1px;
  height: 34px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .85), transparent);
  transform-origin: top;
  animation: sc-cue 2.6s var(--ease-io) infinite;
}
@keyframes sc-cue {
  0%, 100% { transform: scaleY(.25); opacity: .3; }
  45%      { transform: scaleY(1);   opacity: 1; }
}

/* --- 7. Nettorra sign-off band ------------------------------------------ */

/* The one band on these pages that is Nettorra talking rather than the client.
   It is already Nettorra ink, which is why the circuit brain goes here and
   nowhere else on a showcase page: cream-and-brass or bottle-green is the
   client's world, and navy circuitry dropped into it would read as a mistake.
   Right-pinned and bleeding off the edge, with a wash carrying it back to flat
   navy under the type — the same arrangement as the tree on the homepage. */
.sc-outro {
  position: relative;
  overflow: hidden;
  /* The artwork blends in `screen`, and a blend is only as predictable as the
     backdrop under it. Isolating the band pins that backdrop to this section's
     own navy, so the plate cannot start blending with whatever a section above
     it grows later. */
  isolation: isolate;
  background: var(--navy-950);
  color: var(--text);
  border-top: 1px solid rgba(255, 255, 255, .08);
  /* Deeper than the band needs for its two lines of copy, so the artwork has
     somewhere to sit. At the old height the brain was taller than the section
     and got topped and tailed by the overflow clip. */
  padding-block: clamp(4.25rem, 8.5vw, 7.25rem);
}
.sc-outro__art {
  position: absolute;
  z-index: 0;
  top: 50%;
  right: -3%;
  width: min(44%, 28rem);
  /* Height is capped against the band rather than left to the image, because the
     band's own height comes from copy that reflows: at a couple of viewport
     widths it lands within a few pixels of the artwork and one word of new copy
     would crop it. The ratio holds the shape, `contain` shrinks the artwork to
     fit rather than cutting it, and the right anchor keeps it pinned to the
     bleed as it shrinks. */
  aspect-ratio: 1500 / 1050;
  max-height: 88%;
  transform: translateY(-50%);
  opacity: .72;
  pointer-events: none;
  /* The plate is a lit object photographed on black, which means it already
     carries a cut-out — just stored as luminance rather than as alpha, because
     that is the only channel a JPEG has. `screen` reads it that way: black adds
     nothing to the navy underneath and the glow adds all of itself, so the
     frame's edges dissolve into the band instead of sitting on it as a
     rectangle. No cut-out to cut, and no PNG to pay for one. */
  mix-blend-mode: screen;
  /* Fades in from the left so the heading and paragraph keep flat navy behind
     them. The buttons do sit over it, which is the point — they are solid
     enough to hold, and it is the only part of the band where the artwork
     actually reads as being behind something. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 30%);
  mask-image: linear-gradient(90deg, transparent, #000 30%);
}
.sc-outro__art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center right;
}

.sc-outro__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 2.5rem;
}
.sc-outro__label {
  font-family: var(--font-display);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--cyan);
  margin-bottom: .6rem;
}
.sc-outro h2 {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.4vw, 1.85rem);
  font-weight: 600;
}
.sc-outro p {
  margin-top: .55rem;
  color: var(--text-dim);
  font-size: .95rem;
  max-width: 36rem;
}
.sc-outro__actions { display: flex; flex-wrap: wrap; gap: .8rem; }


/* ==========================================================================
   PAGE 3 — RENOVATION
   ========================================================================== */

.reno-hero {
  height: 200vh;
  /* This hero stays full-bleed — nothing contracts. Pinned at 0 so the shared
     .reel__bar, which fades itself out on --open, keeps its full strength. */
  --open: 0;
  --reel-accent: #D9B48C;
}

/* The film carries the flight; the still underneath is only the landing frame
   and the fallback. It sits above the image and only appears once a real frame
   has been decoded, so there is never a black hole where the hero should be. */
/* contain, not cover, for the same reason the shared .reel__film is: this stage
   is 1.6:1 and the flight is 1.78:1, so `cover` was taking an eighth off the
   width — including the lit window on the right that the whole camera move is
   built around. The ground fills the two thin bands, in the page's own dark
   rather than black, so the frame belongs to the palette. */
.reno-hero__film {
  position: absolute;
  z-index: 1;
  inset: 0;
  width: 100%;
  height: 100%;
  /* contain unconditionally — see the note on the shared .reel__film in
     base.css. This carried the same `cover` default and the same 7/5 switch,
     and so it lost the same 29% of the frame on an ordinary tall desktop window
     and 74% of it on a phone. The ground fills the bands in the page's own dark
     rather than black, so the frame belongs to the palette. */
  object-fit: contain;
  background: var(--sc-dark, #191310);
  opacity: 0;
  transition: opacity .6s var(--ease-out);
}
.reno-hero__film.is-ready { opacity: 1; }

/* Same reasoning as the shared .reel__film in base.css: on a window taller than
   it is wide the contained film is a thin band, and a thin band centred in a
   full screen is a void with a picture in it. Pulled up under the header it is
   the top of a layout instead. */
@media (max-aspect-ratio: 1/1) {
  .reno-hero__film { object-position: 50% clamp(4.5rem, 9svh, 7rem); }
}

/* Pinned flat, and the last of the push gone with it.

   The layer is `inset: -8%` and used to carry a scale from 1.06 down to 1 —
   oversize so a photograph could drift without ever showing an edge. That is
   correct for a photograph and wrong for what this slot now holds, which is the
   film's own opening frame. It has one job: be indistinguishable from the film
   until the film arrives. A still that is 16% too big and creeping cannot be.

   It is the same argument the film already makes for itself a few lines up —
   the camera move is in the footage, and a second one in CSS is what makes a
   shot swim. Applying it to the film but not to the picture it hands off from
   was the gap. */
.reno-hero .sc-cine__layer {
  inset: 0;
  transform: none;
}

/* Same geometry as .reno-hero__film, deliberately duplicated rather than
   shared: the two selectors have nothing else in common, and a still that
   quietly stops matching the film is the whole bug. Any change here is a change
   there. */
.reno-hero .ph--hero > img {
  object-fit: contain;
  background: var(--sc-dark, #191310);
}
@media (max-aspect-ratio: 1/1) {
  .reno-hero .ph--hero > img { object-position: 50% clamp(4.5rem, 9svh, 7rem); }
}
/* This hero used to run backwards. The copy was at full strength over the first
   frame and cleared away over the dwell, so the reader met the headline before
   the film and watched the words leave as the kitchen arrived. The other two
   showcases do the opposite, and the opposite is right: the flight is the
   argument, and copy that lands with it reads as the film delivering the page.

   data-film-end is .85 on this hero, so the last 15% of the travel is the
   dwell. --open-rate is the reciprocal of what is left after --open-at, kept as
   a multiply rather than a divide-by-custom-property — the one calc() form
   browsers have historically disagreed about. */
.reno-hero {
  --open-at: .85;
  --open-rate: 6.667;
  --open: clamp(0, calc((var(--p, 0) - var(--open-at)) * var(--open-rate)), 1);
}

.reno-hero .sc-cine__body {
  transform: translate3d(0, calc(var(--p) * -4vh), 0);
}

/* Everything except the name. */
.reno-hero__open {
  opacity: var(--open);
  transform: translateY(calc((1 - var(--open)) * 26px));
}

/* The one line that stays. It has to hold on its own now, because the wash
   behind it arrives with the headline rather than sitting over the whole
   flight — which is the point: the film runs clean. A shadow on eight words
   costs nothing and keeps the small amber caps legible over the brightest
   frame in the cut, the daylit strip-out at the top. */
/* The one line that has to hold against the whole film, and the film is now
   the bright end of the page: a daylit strip-out with sun on bare concrete
   directly behind this text. The copy bed underneath only fades in with the
   landing, so through the flight this halo is all there is. Three stops — a
   hard edge, a close glow, a wide one — rather than a plate behind the words,
   which would read as a label stuck on the picture. */
.reno-hero__brand {
  position: relative;
  z-index: 1;
  text-shadow:
    0 1px 2px  rgba(5, 6, 10, .95),
    0 2px 10px rgba(5, 6, 10, .88),
    0 0 28px   rgba(5, 6, 10, .72);
}

/* Same job and same manners as the cue on the property hero, in this page's
   own colour: centred on the bottom edge, rules either side, gone by the time
   the camera has travelled a fifth of the way. */
.reno-hero__cue {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: clamp(1.4rem, 4vh, 2.4rem);
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .7rem;
  font-family: var(--chrome-mono, var(--font-display));
  font-size: .62rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: rgba(240, 228, 214, .74);
  text-shadow: 0 1px 12px rgba(5, 6, 10, .9);
  opacity: clamp(0, calc(1 - var(--p, 0) * 5), 1);
  pointer-events: none;
}
.reno-hero__cue::before,
.reno-hero__cue::after {
  content: "";
  width: clamp(1.6rem, 5vw, 3.4rem);
  height: 1px;
  background: currentColor;
  opacity: .5;
}

/* The film runs far brighter than the dusk still it replaced — a daylit
   strip-out at the top of the flight, a lit window through the middle — and the
   copy is set hard left across a 21:9 frame. One wash from the left edge anchors
   the column and holds the small amber eyebrow above 4.5:1 even on the frame's
   worst highlight, without flattening the whole picture the way a stronger
   full-frame scrim would. Locked to the stage, not the parallax layer, so it
   stays put while the shot moves underneath it.

   It rides the same ramp as the copy but does not start at zero, and the reason
   is the sentence above. Gated entirely on --open it was absent for the whole
   flight — which is precisely the stretch where the eyebrow is the only thing
   on the page, over the brightest frame in the film. The contrast case the wash
   was written for was the one case it was not covering. So: about a third of it
   stands throughout, enough to seat the eyebrow and too little to read as a
   scrim, and the rest arrives with the headline. */
.reno-hero .sc-cine__stage::before {
  content: "";
  position: absolute;
  z-index: 2;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(96deg,
    rgba(5, 6, 10, .46) 0%,
    rgba(5, 6, 10, .40) 30%,
    rgba(5, 6, 10, .16) 58%,
    transparent 78%);
  opacity: calc(.36 + .64 * var(--open));
}
.reno-hero h1 { margin: 1.4rem 0 1.25rem; color: #fff; max-width: 17ch; }
.reno-hero__sub { max-width: 46ch; color: rgba(255, 255, 255, .8); line-height: 1.7; }
.reno-hero__foot {
  margin-top: clamp(2rem, 5vh, 3rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem 2rem;
}

/* --- The three-stage build sequence — the centrepiece of this page ------ */

.reno-swap { height: 440vh; background: #14100D; }
.reno-swap__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  background: #14100D;
}
.reno-swap__layer {
  position: absolute;
  inset: 0;
  will-change: transform, opacity;
}
.reno-swap__layer .ph { position: absolute; inset: 0; border-radius: 0; }

/* Three visits stacked in time order, finished at the bottom. Each one above
   dissolves away in turn, so the house appears to rebuild in place. The scales
   step down so the drone reads as easing closer across the whole sequence. */
.reno-swap__layer--before {
  z-index: 3;
  opacity: clamp(0, calc((.42 - var(--p)) / .16), 1);
  transform: scale(calc(1.10 - var(--p) * .06));
}
.reno-swap__layer--frame {
  z-index: 2;
  opacity: clamp(0, calc((.80 - var(--p)) / .16), 1);
  transform: scale(calc(1.13 - var(--p) * .09));
}
.reno-swap__layer--after {
  z-index: 1;
  transform: scale(calc(1.16 - var(--p) * .14));
}

.reno-swap__veil {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    /* A soft scrim under the copy itself. The vignette on the last line
       darkens the edges and deliberately leaves the middle clear — which was
       right when these slots were flat placeholder gradients, and wrong now
       they are photographs. The brightest thing in the Week 26 frame is the
       glazed rear wall lit from inside, and it lands dead centre, exactly
       where the headline does. Wide and very soft, so it reads as light
       falling away rather than as a panel sitting behind the type. */
    radial-gradient(68% 46% at 50% 45%, rgba(8, 6, 5, .58) 0%, rgba(8, 6, 5, .30) 55%, rgba(8, 6, 5, 0) 100%),
    linear-gradient(180deg, rgba(8, 6, 5, .72) 0%, rgba(8, 6, 5, 0) 30%),
    linear-gradient(0deg, rgba(8, 6, 5, .78) 0%, rgba(8, 6, 5, 0) 52%),
    radial-gradient(120% 90% at 50% 50%, rgba(8, 6, 5, .1) 30%, rgba(8, 6, 5, .62) 100%);
}

.reno-swap__body {
  position: relative;
  z-index: 4;
  height: 100%;
  display: grid;
  place-content: center;
  text-align: center;
  color: #fff;
  padding-inline: var(--gutter);
}
.reno-swap__copy { grid-area: 1 / 1; }
.reno-swap__copy h2 { margin: 1.2rem auto 0; max-width: 17ch; }
.reno-swap__copy p {
  margin: 1.1rem auto 0;
  max-width: 44ch;
  color: rgba(255, 255, 255, .78);
}
/* Each caption holds while its own layer is the one on show, then clears before
   the next dissolve starts. The middle one has to come and go, so it takes the
   lower of a rising and a falling ramp. */
.reno-swap__copy--before,
.reno-swap__slot--before { opacity: clamp(0, calc((.30 - var(--p)) / .10), 1); }
.reno-swap__copy--frame,
.reno-swap__slot--frame {
  opacity: clamp(0, min(calc((var(--p) - .44) / .08), calc((.70 - var(--p)) / .08)), 1);
}
.reno-swap__copy--after,
.reno-swap__slot--after  { opacity: clamp(0, calc((var(--p) - .84) / .08), 1); }

.reno-swap__pill {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem 1.1rem;
  border-radius: 100px;
  border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(10, 8, 6, .4);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font-family: var(--font-display);
  font-size: .66rem;
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
}
.reno-swap__pill i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.reno-swap__copy--frame .reno-swap__pill { border-color: rgba(198, 194, 186, .55); color: #DCD8D0; }
.reno-swap__copy--after .reno-swap__pill { border-color: rgba(210, 150, 96, .7); color: #F0C79A; }

.reno-swap__rail {
  position: absolute;
  z-index: 5;
  left: 50%;
  bottom: clamp(1.75rem, 5vh, 3rem);
  transform: translateX(-50%);
  width: min(420px, 62vw);
  height: 2px;
  background: rgba(255, 255, 255, .22);
  overflow: hidden;
  border-radius: 2px;
}
.reno-swap__rail i {
  display: block;
  height: 100%;
  width: calc(var(--p) * 100%);
  background: #fff;
}
.reno-swap__hint {
  position: absolute;
  z-index: 5;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(clamp(1.75rem, 5vh, 3rem) + 1.2rem);
  font-family: var(--font-display);
  font-size: .62rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
  white-space: nowrap;
  opacity: clamp(0, calc(1.1 - var(--p) * 2.2), 1);
}

/* Featured projects */
.reno-projects__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 3rem;
  margin-bottom: clamp(2.25rem, 4.5vw, 3.5rem);
}
.reno-projects__head p { max-width: 42ch; }
.reno-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.25rem, 2.6vw, 2.25rem);
}
.reno-card { display: block; }
.reno-card .sc-frame { transition: box-shadow .5s var(--ease-out); }
.reno-card .ph { transition: transform .7s var(--ease-out); }
.reno-card:hover .ph { transform: scale(1.06); }
.reno-card:hover .sc-frame { box-shadow: 0 30px 60px -30px rgba(32, 26, 20, .6); }
.reno-card__meta {
  margin-top: 1.15rem;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.reno-card h3 { font-family: var(--font-serif); font-weight: 400; font-size: 1.4rem; }
.reno-card__loc {
  font-family: var(--font-display);
  font-size: .64rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sc-accent);
  white-space: nowrap;
}
.reno-card__desc { margin-top: .5rem; color: var(--sc-ink-soft); font-size: .95rem; }

/* Founder story */
.reno-story { background: var(--sc-bg-2); }
.reno-story__grid {
  display: grid;
  grid-template-columns: .85fr 1.15fr;
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: center;
}
.reno-story h2 { margin: 1.4rem 0 1.5rem; }
.reno-story__sig {
  margin-top: 2.25rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--sc-line);
}
.reno-story__sig strong {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.6rem;
  font-weight: 400;
}
.reno-story__sig span {
  font-family: var(--font-display);
  font-size: .66rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--sc-ink-soft);
}

/* Stats bar */
.reno-stats { background: var(--sc-dark); color: #F1E8DC; }
.reno-stats__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.reno-stats__item { text-align: center; }
.reno-stats__n {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(2.6rem, 5.5vw, 4.2rem);
  line-height: 1;
  background: linear-gradient(120deg, #F3DEC4, #B06A38);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.reno-stats__l {
  margin-top: .85rem;
  font-family: var(--font-display);
  font-size: .66rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: rgba(241, 232, 220, .62);
}

/* Process */
.reno-steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
.reno-step { position: relative; padding-top: 2.25rem; }
.reno-step::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: var(--sc-line);
}
.reno-step::after {
  content: "";
  position: absolute;
  top: -3px; left: 0;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--sc-accent);
}
.reno-step__n {
  font-family: var(--font-display);
  font-size: .66rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--sc-accent);
}
.reno-step h3 {
  margin: .75rem 0 .7rem;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 1.45rem;
}
.reno-step p { color: var(--sc-ink-soft); font-size: .95rem; line-height: 1.75; }

/* Testimonial */
.reno-quote { background: var(--sc-bg-2); text-align: center; }
.reno-quote blockquote {
  margin: 1.75rem auto 0;
  max-width: 21ch;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.9rem, 4.4vw, 3.2rem);
  line-height: 1.14;
  letter-spacing: -.012em;
}
.reno-quote figcaption {
  margin-top: 2rem;
  font-family: var(--font-display);
  font-size: .68rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--sc-ink-soft);
}
.reno-quote figcaption b { color: var(--sc-ink); font-weight: 600; }
.reno-quote__stars { display: inline-flex; gap: .3rem; color: var(--sc-accent); }
.reno-quote__stars svg { width: 15px; height: 15px; }

/* Closing CTA */
.reno-cta { position: relative; overflow: hidden; background: var(--sc-dark); color: #fff; }
.reno-cta__bg { position: absolute; inset: -12%; z-index: 0; }
.reno-cta__bg .ph { position: absolute; inset: 0; border-radius: 0; opacity: .55; }
.reno-cta__veil {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(18, 13, 10, .84), rgba(18, 13, 10, .94));
}
.reno-cta__inner { position: relative; z-index: 2; text-align: center; }
.reno-cta h2 { margin: 1.5rem auto 1.3rem; max-width: 16ch; }
.reno-cta p { margin-inline: auto; max-width: 48ch; color: rgba(255, 255, 255, .74); }
.reno-cta__actions {
  margin-top: clamp(2rem, 4vw, 2.75rem);
  display: flex;
  flex-wrap: wrap;
  gap: .9rem;
  justify-content: center;
}


/* ==========================================================================
   PAGE 4 — PULP, COLD-PRESSED

   The page is the juice. Every band is one flat, saturated field at full
   strength — the colour the produce actually goes in as — and the whole
   viewport changes to the next one as you move down the presses. Nothing
   else on the page is allowed to be that loud, so the colour carries the
   argument on its own and the type stays quiet on top of it.

   Two devices, used twice each, and that is the whole system:
     · a field-filling word set behind the product, in the field's own
       darker shade, overprinted so it reads as ink on the photograph
     · a counter, because four presses in a row is a sequence and the
       reader is entitled to know where in it they are
   ========================================================================== */

.page-prod {
  /* Archivo carries a width axis as well as a weight one, which is what the
     field-filling words need: at 900/125 the letters are heavy and wide
     enough to read as a block of colour rather than as a line of text.
     Caveat appears once, on the pressed-on date, because a weekly press
     really does hand-mark its labels. */
  --prod-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --prod-hand: "Caveat", "Bradley Hand", cursive;
}


/* --- 1. Hero — the film ------------------------------------------------- */

/* Long enough that the film has room to run under the reader's thumb without
   the push feeling hurried, short enough that nobody is trapped in it. */
.prod-film {
  height: 300vh;
  /* Matches data-film-end on the video. The film runs for the first 82% of the
     travel and the copy arrives over the last 18%, by which point the bottle is
     standing full. Rate is the reciprocal of what is left, written as a
     multiply — see the note on .reel in base.css. */
  --open-at: .82;
  --open-rate: 5.556;
  --open: clamp(0, calc((var(--p, 0) - var(--open-at)) * var(--open-rate)), 1);
}

.prod-film__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: grid;
  /* Two in-flow children, not three: the media, grade and ghost are all out of
     flow. A third track meant the foot landed in the 1fr and inherited every
     spare pixel — invisible on a wide screen, but on a phone the ticker wraps
     and its two lines were pushed to opposite ends of a 400px box. */
  grid-template-rows: auto minmax(0, 1fr);
  padding: clamp(5.5rem, 13vh, 9rem) var(--gutter) clamp(1.6rem, 4.5vh, 3rem);
  /* The film's own darkest tone. Anything the film does not cover — a very
     tall window, the moment before the first frame decodes — matches it. */
  background: #3D0A22;
}

.prod-film__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Portrait and near-square windows. Full-bleed here is not an option: a 2.33:1
   film filling a 390x844 phone keeps a fifth of the frame width and blows it up
   five times. This cut is already shot close — it is a slow crawl across the
   produce — so a phone-shaped hole through the middle of it is a crop of a
   close-up, which is where the complaint that you cannot see it came from.

   So the film gets a band instead of the whole window, and the band is the
   film's own shape. That is the whole trick: match the box to the plate and
   there is no crop to argue about, because `cover` and `contain` land on the
   same rectangle. 390 wide gives a 167px strip, which sounds thin and is not —
   cinemascope on a phone is a strip, and a whole strip beats three fifths of a
   tall one. The head sits above it and the ticker below, so nothing is fighting
   the picture.

   The ratio is the film's, 3840x1648, not a rounded 21/9. Round it and the band
   misses the plate by a pixel or two and you get a hairline of ground along one
   edge — which is exactly the seam this is meant to avoid.

   Top and bottom edges stay feathered, because a hard cut across a photograph
   reads as a broken layout while a soft one reads as depth. */
@media (max-aspect-ratio: 3 / 2) {
  .prod-film__media {
    inset: auto 0 auto 0;
    top: 50%;
    height: auto;
    aspect-ratio: 3840 / 1648;
    transform: translateY(-50%);
    -webkit-mask-image: linear-gradient(to bottom,
      transparent 0, #000 9%, #000 91%, transparent 100%);
            mask-image: linear-gradient(to bottom,
      transparent 0, #000 9%, #000 91%, transparent 100%);
  }

  /* The band leaves real estate above and below it, and flat plum across that
     much of a phone screen is the dullest thing on the page. Two washes in the
     press colours — beet up under the head, orange down by the ticker — give it
     somewhere to fall off to. */
  .prod-film__stage {
    background:
      radial-gradient(80% 42% at 18% 8%,  rgba(142, 17, 65, .55) 0%, rgba(142, 17, 65, 0) 100%),
      radial-gradient(70% 34% at 88% 96%, rgba(232, 114, 28, .3) 0%, rgba(232, 114, 28, 0) 100%),
      #3D0A22;
  }
}

/* The film is 2.33:1 — cinemascope, wider than any screen it will ever be shown
   on. Filling a 100svh stage with it under `cover` throws away most of the
   frame: on a 1440x900 window that is 1425x900 of stage against a 2097x900
   scaled film, so only 68% of the picture survives before anything else
   happens. Adding a scale on top of that took it to 59%, and what was left was
   one apple. The film was never swapped; it was cropped until it could not be
   recognised.

   So on any window wide enough for it to make sense, the whole frame is shown
   and the stage keeps the two bands above and below. That is not a compromise
   — it is how the footage was shot, and the bands are useful: the eyebrow and
   headline sit in the top one and the ticker in the bottom, which is the first
   time on this page the type has not been fighting the picture behind it.

   The push stays, because the slow creep is what ties the frame to the thumb,
   but it now starts at exactly 1. At rest the reader sees the entire film; by
   the end of the section the push has taken 2% off each side. */
/* One rule, both shapes: show the whole frame and do not move it.

   There used to be a second rule under here that gave landscape windows their
   own treatment, because portrait was cropping and landscape was not. Now that
   the portrait band carries the film's own ratio there is nothing left to
   branch on — a 2.33:1 picture in a 2.33:1 band and a 2.33:1 picture
   letterboxed into a 100svh stage are both the entire picture.

   The push that used to live here — a 6% creep over the section — went with it.
   It is a small number, but it is only small in the abstract: it took the two
   ends of a cinemascope frame off at exactly the point in the scroll where the
   press is at its most violent, and it did it while the film was already
   moving. Two camera moves in opposite directions is not depth, it is swim. The
   film has its own move; CSS should get out of its way and let it play. */
.prod-film__media > img,
.prod-film__media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 50%;
  transform: none;
}

/* The film's first frame is the poster, so there is nothing to cross-fade
   between — this only hides the gap while the file decodes. */
.prod-film__vid {
  opacity: 0;
  transition: opacity .45s linear;
}
.prod-film__vid.is-ready { opacity: 1; }

/* An oval of shade under the head, a bed running out from it, and two short
   washes holding the top and bottom edges down.

   The oval is here because of where the film is bright rather than how bright
   it is. Averaged over the frame this footage is dark — the last two seconds
   are nearly black — so the instinct is to take the grade off. That is
   backwards. The bottle is lit, it stands about a third of the way across, and
   it is near-white on almost every frame: 250,253,249 at the four-second mark,
   sampled at 33% width, which is exactly where the last letters of "heated"
   land. A wash that falls off evenly across the left third spends its density
   on the dark part of the frame and has nothing left by the time it reaches
   that edge, so the pink word measured 2.4:1 and the body line 3.6:1.

   Pooling the density over the head block instead fixes it without dimming the
   picture: "heated" reads 4.7:1 and the body line 6.1:1, and the right-hand
   half of the frame — the bottle, the whole point of the shot — is left
   brighter than it was before. The ink is untouched throughout. The brand pink
   is the brand pink.

   Every stop is measured out from the gutter in rem rather than set as a
   percentage, because the thing being covered is the copy column and that is
   34rem wide at every screen size. Sized in percentages the bed tracked the
   window instead: correct at 1440, where the column takes 37% of the width,
   and useless at 1024, where it takes 48% and the body line fell off the end
   of the wash at 2.7:1. */
/* Split in two, because the two halves are now needed at different times.

   The edge washes stay on throughout: the top one keeps the fixed header
   legible and the bottom one carries the cue and the ticker, and both of those
   are on screen from the first frame.

   The copy bed does not. It exists for a headline that no longer arrives until
   the film has finished, and leaving it on through the flight would put a
   permanent shadow across the left third of a shot that is meant to run clean.
   It comes in with the words it is there for. */
.prod-film__grade {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(28, 6, 36, .6) 0%, rgba(28, 6, 36, .1) 26%, rgba(28, 6, 36, 0) 42%),
    linear-gradient(to top, rgba(28, 6, 36, .8) 0%, rgba(28, 6, 36, .2) 24%, rgba(28, 6, 36, 0) 50%);
}

.prod-film__grade::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: var(--open);
  background:
    radial-gradient(50rem 62% at calc(var(--gutter) - 2rem) 26%,
      rgba(28, 6, 36, .86) 0%, rgba(28, 6, 36, .56) 46%, rgba(28, 6, 36, 0) 100%),
    linear-gradient(to right,
      rgba(28, 6, 36, .92) calc(var(--gutter) - 2.5rem),
      rgba(28, 6, 36, .82) calc(var(--gutter) + 24rem),
      rgba(28, 6, 36, .4)  calc(var(--gutter) + 36rem),
      rgba(28, 6, 36, 0)   calc(var(--gutter) + 48rem));
}

/* Below this the copy stops being a column. At 1023 it runs to 48% of the
   screen and at 768 to 63%, so a bed shaped sideways has to cover almost the
   whole frame to reach the ends of the lines — measured, it puts 80% of the
   picture under shade and there is no film left to watch.

   Turned upright instead: down from the top far enough to clear the head, up
   from the bottom far enough to clear the cue and the ticker, and open across
   the middle, where the bottle is. Same coverage, a third less of the frame
   touched. Worst frame of the whole scrub reads 6.5:1 on the body line and
   4.4:1 on "heated" at the widest this rule applies, and it holds down to a
   phone.

   These are rem too, and for a second reason: viewport height varies far more
   than width. As percentages of the frame, the bottom wash sat right on top of
   the cue on a 1024-tall screen and left it at 3.5:1. Measured up from the
   bottom edge in rem it lands in the same place on every screen. */
@media (max-width: 1023px) {
  /* Upright rather than sideways, so the same split applies along the other
     axis: the bottom wash is chrome and stays, the top one is the copy bed and
     arrives with the copy. It reaches 34rem down here, which is most of a phone
     screen — exactly the reason not to leave it on over the film. */
  .prod-film__grade {
    background:
      linear-gradient(to top,
        rgba(28, 6, 36, .9)  0,
        rgba(28, 6, 36, .78) 7.5rem,
        rgba(28, 6, 36, .24) 11rem,
        rgba(28, 6, 36, 0)   15rem);
  }
  .prod-film__grade::before {
    background:
      linear-gradient(to bottom,
        rgba(28, 6, 36, .86) 0,
        rgba(28, 6, 36, .8)  22rem,
        rgba(28, 6, 36, .44) 28rem,
        rgba(28, 6, 36, 0)   34rem);
  }
}

/* Hung off the bottom edge and cropped by it. A word this size cannot sit
   politely inside the frame — letting the frame cut it is what makes it read
   as scale rather than as a large heading. */
.prod-film__ghost {
  position: absolute;
  z-index: 1;
  left: 50%;
  bottom: -.14em;
  transform: translateX(-50%);
  margin: 0;
  font-family: var(--prod-display);
  font-weight: 900;
  font-stretch: 125%;
  font-size: clamp(6rem, 27vw, 25rem);
  line-height: .74;
  letter-spacing: -.05em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #FBF3E4;
  opacity: .13;
  pointer-events: none;
  user-select: none;
}

.prod-film__head {
  position: relative;
  z-index: 3;
  max-width: 34rem;
}

/* The only writing over the film. Everything else in the head waits. */
.prod-film__brand {
  font-family: var(--chrome-mono);
  font-size: .68rem;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: #FBF3E4;
  opacity: .72;
  margin: 0 0 1.1rem;
  /* Holds on its own over the brightest frame of the press — a white spray at
     about two seconds — without a panel behind it. */
  text-shadow: 0 1px 12px rgba(61, 10, 34, .9);
}

.prod-film__open {
  opacity: var(--open);
  transform: translateY(calc((1 - var(--open)) * 22px));
}

.prod-film h1 {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: .98;
  letter-spacing: -.035em;
  color: #FBF3E4;
  margin: 0;
  text-wrap: balance;
}

.prod-film h1 em {
  font-style: normal;
  /* The one word the whole page is about, so it is the one word that gets a
     different colour. Struck through with a rule the width of the word,
     because "not heated" is a thing that was left out.

     Scorch amber rather than the brand pink. The word names the one thing this
     juice never had, so it should be the colour of heat — and the rule then
     reads as that heat being taken back out, which is the whole claim. It also
     settles the weakest measurement in the hero: pink came in at 4.7:1 against
     the brightest frame of the film, and this reads about 6.4:1 on the same
     frame with nothing else touched. */
  position: relative;
  color: #FFA53D;
}
.prod-film h1 em::after {
  content: "";
  position: absolute;
  left: -.02em;
  right: -.02em;
  top: .54em;
  height: .07em;
  background: currentColor;
  /* Driven by --open, not --p. On --p the rule was already four-fifths drawn by
     the time the headline faded in, so the reader met a word that was mostly
     struck out and never saw it happen. On --open it draws itself across the
     word over exactly the span the headline is arriving: the heat comes out of
     it as it lands. */
  transform: scaleX(var(--open, 0));
  transform-origin: left;
  transition: transform .1s linear;
}

.prod-film__sub {
  margin: 1.5rem 0 0;
  max-width: 30rem;
  color: rgba(251, 243, 228, .74);
  line-height: 1.75;
  font-size: clamp(.92rem, 1.2vw, 1rem);
}

.prod-film__foot {
  position: relative;
  z-index: 3;
  align-self: end;
  display: grid;
  gap: 1rem;
}

.prod-film__ticker {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: .7rem 2.2rem;
  font-family: var(--chrome-mono);
  font-size: .66rem;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: rgba(251, 243, 228, .8);
}
.prod-film__ticker span { display: inline-flex; align-items: center; gap: .6rem; }
.prod-film__ticker i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #FF6A9B;
}

/* Gone by the time the reader has taken the advice. */
.prod-film__cue {
  color: rgba(251, 243, 228, .8);
  opacity: clamp(0, calc(1.1 - var(--p, 0) * 4), 1);
}


/* --- 1b. Colour beds ----------------------------------------------------- */

/* The film ends in the dark and the page gets lighter from there: beetroot at
   the method, all four presses at once as you reach them, chlorophyll through
   the exploded view, and full citrus at the door. Scrolling PULP is meant to be
   a sunrise through a bottle — which is also what the copy claims about the
   juice, that colour is the first thing heat takes.

   Per-section beds rather than one fixed layer behind the whole page: the four
   presses stick, and a wrapper around them would either move where they stop or
   stop them sticking. Each bed reads its own section's --p, so the colour
   arrives as the section does.

   Every bed is opaque. The produce is photographed on black and blended with
   screen, so the black falls away and only the fruit is added to what it lies
   on — but screen against nothing leaves the black behind, so there has to be
   something underneath. That blend is also why these are JPEGs: it keeps the
   wet spray and flung droplets, where a cut-out mask would square them off. */

.prod-bed {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--bed-base, var(--sc-bg));
  pointer-events: none;
}

/* --wash-min keeps a floor under every bed. Fading each one from nothing left a
   hard line wherever a lit section met the top of the next one, and the page is
   supposed to be getting lighter, not flickering. Each bed also opens with a
   band of the colour above it, so the sections hand over rather than butt. */
.prod-bed__wash {
  position: absolute;
  inset: -1px;
  background: var(--wash);
  opacity: clamp(var(--wash-min, 0), calc(var(--p, 0) * 2.1 - .18), 1);
}

/* The blend sits on the wrapper, not the picture. The scroll engine writes
   transform straight onto whatever carries data-px, so the wrapper is spoken
   for — and a transform on the picture would make it its own blending group,
   which is where the black comes back. */
.prod-fruit {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, 50%);
  width: var(--w, 18rem);
  mix-blend-mode: screen;
  opacity: var(--o, .85);
}
.prod-fruit img {
  display: block;
  width: 100%;
  height: auto;
  transform: rotate(var(--r, 0deg));
}


/* --- 2. The press ------------------------------------------------------- */

.prod-press {
  overflow: hidden;
  --wash-min: .32;
  /* Warmer ink from here down: the muted mauve was chosen against near-black
     and goes grey the moment there is colour behind it. */
  --sc-ink-soft: #E4D2C6;
  --wash:
    radial-gradient(105% 76% at 16% 104%, rgba(228, 20, 116, .82), rgba(228, 20, 116, 0) 70%),
    radial-gradient(74% 58% at 88% 96%, rgba(255, 122, 20, .38), rgba(255, 122, 20, 0) 68%),
    radial-gradient(88% 66% at 94% -6%, rgba(242, 30, 132, .34), rgba(242, 30, 132, 0) 70%);
}
.prod-press > .sc-shell { position: relative; z-index: 1; }

/* --- The press ----------------------------------------------------------

   A photograph used to sit here: two kilos of produce hanging over one empty
   bottle. It was a good picture and it was the hero film's picture — same
   subject, same register, directly below it — so the page spent its opening
   twice and never showed the thing the section is actually about.

   The machine is what is missing, and the machine's whole character is that it
   is slow. Thirty tonnes arriving over four minutes is not a photograph; it is
   a duration, and a scroll is a duration the reader controls. So the reader
   works the press: the cake compresses, the bottle fills, and three gauges
   read out the cost.

   Two gauges climb. The third does not, and that is the section's argument in
   one object — every other juice on the shelf gets to 72°C and this one never
   leaves 4°C. It is the only element on the panel with no colour in it, because
   the stillness is the point and colour would make it look like it was trying.

   Nothing here counts or rounds. The figures are printed and the bars fill, so
   there is no arithmetic to fail silently into a stuck zero. */

.press {
  /* The stroke, remapped off the scene's own progress rather than used raw.
     data-scene="enter" reaches 1 the moment the module's top edge touches the
     top of the window — which for a rig sitting low on a tall page means the
     entire travel is spent while it is still down near the fold. Held to the
     middle of the pass instead: the press starts as the rig arrives and
     finishes while it is high and whole on screen. */
  --s: clamp(0, calc((var(--p, 0) - .28) * 1.85), 1);
  --juice: #A3123F;
  /* Just enough headroom for the platen to have somewhere to have come from.
     The first build gave the frame thirteen rems and the cake five of them, so
     once the cake was pressed the module was two thirds empty air with a
     hairline down each side — a goalpost, not a press. Height alone was not the
     fix, though: see --rig-w. A press is measured by its proportion, not its
     size. */
  --rig-h: clamp(8.5rem, 15vw, 10.5rem);
  /* The one measurement that decides whether this reads as a machine. Left to
     fill its column the bed came out four hundred and eighty pixels wide and a
     hundred tall, and a 4:1 rectangle is a shelf however carefully it is
     shaded. Capped and centred it is roughly as wide as it is deep, which is
     the proportion a bench press actually has. */
  --rig-w: clamp(18rem, 26vw, 23rem);
  --steel: linear-gradient(180deg, rgba(251, 243, 228, .52), rgba(251, 243, 228, .16));

  position: relative;
  display: grid;
  /* Machine on the left, its readings on the right. A press bed is a wide flat
     thing and the gauges are a stack, so side by side is the shape they
     actually make — and it keeps the bed to a plausible footprint. Run full
     width the cake ended up nine hundred pixels across and seventeen tall,
     which is not a press, it is a rule. */
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
  gap: clamp(1.6rem, 4vw, 3.2rem);
  align-items: center;
  padding: clamp(1.5rem, 3.4vw, 2.5rem);
  border: 1px solid var(--sc-line);
  border-radius: var(--sc-radius);
  background: rgba(24, 4, 16, .38);
}

.press__rig {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(1.2rem, 3vw, 2.4rem);
  align-items: end;
  /* width, not max-width. An auto inline margin turns a grid item into a
     shrink-to-fit box, so max-width plus auto margins collapsed the whole rig
     to the width of its own contents — a 126px tower. An explicit width keeps
     the cap and justify-self does the centring. */
  width: min(100%, var(--rig-w));
  justify-self: center;
}

.press__side { display: grid; gap: .8rem; justify-items: start; }
.press__side--out { justify-items: center; align-content: end; }

/* The frame: two uprights at the outside edges, capped by the crosshead and
   footed by the bed. The first build put the rods at 22% and 78%, which is
   where a table has legs, not where a press has uprights — it read as a bench
   with a bar on it. At the edges, with the platen sliding between them, the
   same four rules read as a machine. */
.press__col {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: var(--rig-h);
}
.press__col::before,
.press__col::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 9px;
  background: linear-gradient(90deg,
    rgba(251, 243, 228, .06), rgba(251, 243, 228, .32), rgba(251, 243, 228, .06));
}
.press__col::before { left: 0; }
.press__col::after  { right: 0; }

.press__beam,
.press__deck { position: relative; z-index: 1; }

/* Inset to clear the uprights, because that is the gap they slide in. */
.press__platen,
.press__cake { position: relative; z-index: 1; margin-inline: 13px; }

.press__beam {
  height: clamp(.55rem, 1.2vw, .75rem);
  border-radius: 2px 2px 0 0;
  background: var(--steel);
}

/* The piston. One, central, between the crosshead and the platen — and the
   only thing in the drawing that changes length, so it is what the eye reads
   as the stroke. */
.press__ram { position: relative; flex: 1 1 auto; min-height: 1rem; }
.press__ram::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  transform: translateX(-50%);
  width: 16px;
  border-radius: 0 0 2px 2px;
  background: linear-gradient(90deg,
    rgba(251, 243, 228, .08), rgba(251, 243, 228, .48), rgba(251, 243, 228, .08));
}

/* The dark hairline under the platen is doing real work. Pressed flat, the
   platen, the cake and the deck are three bars inside about thirty pixels and
   they merged into one — the shadow is what tells you the top bar is resting on
   something rather than being part of it. */
.press__platen {
  height: clamp(.55rem, 1.2vw, .75rem);
  border-radius: 2px;
  background: var(--steel);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .65), 0 8px 20px rgba(0, 0, 0, .5);
}

/* Six slabs of whole produce that lose five sixths of their height and all of
   their difference. The colours are the cut-outs the rest of the page uses —
   beet, kale, apple, carrot, cucumber, ginger — and each one is overprinted
   with beetroot juice on the same ramp, so the pile does not merely shrink, it
   becomes one thing. That is "two kilos in, one bottle out" drawn rather than
   asserted. */
.press__cake {
  /* Loaded to the brim. The cake takes about three quarters of the space under
     the crosshead at rest, which leaves the ram a short stub — the look of a
     press that has just been filled and not yet started. It also means the void
     the platen leaves behind at the end of the stroke is the ram's, not the
     frame's. */
  --cake-tall: clamp(4.2rem, 8.6vw, 5.5rem);
  --cake-thin: clamp(.7rem, 1.5vw, .95rem);
  display: flex;
  overflow: hidden;
  height: calc(var(--cake-tall) - (var(--cake-tall) - var(--cake-thin)) * var(--s));
}
.press__cake i {
  position: relative;
  flex: 1 1 0;
  background: var(--c);
}
/* Held off until the cake is nearly flat, then run in hard.

   On a straight ramp the six colours spent the entire stroke half-covered in
   crimson, and a green slab under half a crimson is brown — the whole middle of
   the press was a muddy smear. Delayed and steepened, the produce keeps its own
   colours through the crushing, browns briefly, and turns over to juice in one
   beat at the end. The brown is worth keeping: milled fruit really does go the
   colour of pomace on its way to being juice, so the one stage that looked like
   a mistake is the one stage that is literally accurate. */
.press__cake i::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--juice);
  opacity: clamp(0, calc((var(--s) - .55) * 2.7), 1);
}

.press__deck {
  position: relative;
  height: clamp(.4rem, .9vw, .5rem);
  border-radius: 0 0 2px 2px;
  background: var(--steel);
}
/* Juice standing on the deck. Two pixels, and it is the difference between a
   stack of bars getting shorter and something being squeezed out of it. */
.press__deck::before {
  content: "";
  position: absolute;
  left: -2px;
  right: -2px;
  bottom: 100%;
  height: 2px;
  background: var(--juice);
  opacity: var(--s);
}

/* Shouldered, so it is a bottle and not a battery — the first pass was a plain
   rounded rectangle with a stub on top and that is exactly what it looked
   like. The taper is a wide radius on the top corners only. */
.press__bottle {
  position: relative;
  width: clamp(2.7rem, 5.4vw, 3.4rem);
  /* Neck included, the bottle stands as tall as the press frame, so the two
     halves of "two kilos in, one bottle out" are drawn to one scale. */
  height: calc(var(--rig-h) - .85rem);
  border: 1.5px solid rgba(251, 243, 228, .4);
  border-radius: 1.25rem 1.25rem .4rem .4rem;
}
/* The neck. Drawn outside the body, so the body must not clip — which is why
   the fill carries its own radius rather than relying on overflow. */
.press__bottle::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translateX(-50%);
  width: .95rem;
  height: .85rem;
  border: 1.5px solid rgba(251, 243, 228, .4);
  border-bottom: 0;
  border-radius: .18rem .18rem 0 0;
}
.press__bottle i {
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 2px;
  /* Stops just under the shoulder. A juice filled to the very brim reads as a
     rendering error rather than as a full bottle. */
  height: calc(var(--s) * (100% - 1.5rem));
  border-radius: .2rem .2rem .3rem .3rem;
  background: linear-gradient(180deg, #C4184C, var(--juice));
}

.press__tag {
  font-family: var(--chrome-mono);
  font-size: .6rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(251, 243, 228, .62);
}

/* Three readings of one stroke, so they stack and are ruled apart rather than
   boxed. The rules are the only structure here that is not part of the drawing
   of the machine, and there are exactly two of them. */
.press__panel {
  margin: 0;
  display: grid;
  gap: clamp(1rem, 2.4vw, 1.5rem);
}
.press__gauge + .press__gauge {
  border-top: 1px solid var(--sc-line);
  padding-top: clamp(1rem, 2.4vw, 1.5rem);
}

.press__gauge dt {
  font-family: var(--chrome-mono);
  font-size: .6rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: rgba(251, 243, 228, .58);
}
/* Figure and bar on one line, caption under both: the figure says what the
   stroke is worth and the bar says how much of it has happened, so they belong
   to each other rather than sitting in a column. */
.press__gauge dd {
  margin: .55rem 0 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 1rem;
}

.press__fig {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 108%;
  font-size: clamp(1.4rem, 2.8vw, 1.85rem);
  letter-spacing: -.02em;
  line-height: 1;
  color: #FBF3E4;
  /* All three figures set to the same box so the bars start on one axis. */
  min-width: 3.6em;
}

.press__track {
  position: relative;
  height: 4px;
  border-radius: 2px;
  background: rgba(251, 243, 228, .16);
}
.press__fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: calc(var(--s) * 100%);
  border-radius: 2px;
  background: linear-gradient(90deg, #FF7A14, #E41474);
}

/* The one that holds. The scale runs to 72°C — the temperature at which juice
   is pasteurised — and 4 is 5.5% of the way along it, so the mark is a stub at
   the origin that stays a stub while the two beside it run the full width. No
   colour in it: the two that climb are the brand's pink and orange, and this
   one is the paper the page is printed on. */
.press__gauge--hold .press__fill {
  width: 5.5%;
  background: #FBF3E4;
}
.press__gauge--hold .press__track::after {
  content: "";
  position: absolute;
  right: 0;
  top: -5px;
  bottom: -5px;
  width: 1.5px;
  background: rgba(251, 243, 228, .45);
}
/* Printed at the far end of the scale, where the mark never gets to. */
.press__limit {
  position: absolute;
  right: 0;
  top: calc(100% + .5rem);
  font-family: var(--chrome-mono);
  font-size: .56rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(251, 243, 228, .5);
}

.press__foot {
  grid-column: 1 / -1;
  margin-top: .7rem;
  font-size: .82rem;
  line-height: 1.6;
  color: var(--sc-ink-soft);
}
/* The limit label hangs under this gauge's track, so its caption starts lower
   than the other two or it would sit on top of the words. */
.press__gauge--hold .press__foot { margin-top: 1.7rem; }

.prod-press__grid {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  display: grid;
  grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.prod-press h2 {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 108%;
  font-size: clamp(1.9rem, 4vw, 3.2rem);
  line-height: 1.02;
  letter-spacing: -.03em;
  margin: .8rem 0 0;
}

.prod-press__body { color: var(--sc-ink-soft); line-height: 1.85; }
.prod-press__body p + p { margin-top: 1.15rem; }

.prod-press__tags {
  margin-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  gap: .55rem;
}

.prod-tag {
  font-family: var(--chrome-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: .5rem .85rem;
  border: 1px solid var(--sc-line);
  border-radius: 999px;
  color: var(--sc-ink-soft);
}


/* --- 3. The four presses ------------------------------------------------ */

/* Four columns of light, left to right in the order the presses arrive:
   beet, green, orange, and the charcoal one as smoke. */
.prod-worlds {
  overflow: hidden;
  --wash-min: .5;
  --sc-ink-soft: #EBDACB;
  --wash:
    linear-gradient(to bottom, rgba(228, 20, 116, .58), rgba(228, 20, 116, 0) 32%),
    radial-gradient(52% 108% at 11% 130%, rgba(220, 20, 112, .90), rgba(220, 20, 112, 0) 100%),
    radial-gradient(52% 108% at 37% 130%, rgba(126, 206, 30, .82), rgba(126, 206, 30, 0) 100%),
    radial-gradient(52% 108% at 63% 130%, rgba(252, 116, 14, .82), rgba(252, 116, 14, 0) 100%),
    radial-gradient(52% 108% at 89% 130%, rgba(186, 172, 146, .60), rgba(186, 172, 146, 0) 100%);
}
.prod-worlds > .sc-shell { position: relative; z-index: 1; }

.prod-worlds__intro { text-align: center; max-width: 44rem; margin-inline: auto; }
.prod-worlds__intro h2 {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 108%;
  font-size: clamp(1.9rem, 4vw, 3.2rem);
  line-height: 1.04;
  letter-spacing: -.03em;
  margin: .8rem 0 1rem;
}

/* Four panels, each one viewport tall, each stuck to the top of the screen
   for as long as the stack has scroll left. Because they are in source order
   the next one paints over the one before it, so each press slides up and
   covers the last — which is what makes the whole window change colour rather
   than the colour appearing inside a box on a page.

   No overflow clipping anywhere up this chain, or the sticking stops. */
.prod-worlds__stack { position: relative; }

.prod-world {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  padding: clamp(5.5rem, 13vh, 9rem) var(--gutter) clamp(1.6rem, 4.5vh, 3rem);
  background: var(--field);
  color: var(--on);
  /* The header is fixed, so the arrows below must stop short of it. */
  scroll-margin-top: 0;
}

.prod-world__art { position: absolute; inset: 0; z-index: 0; }

/* Each panel carries data-scene="enter", so --p runs 0 to 1 across exactly the
   stretch where the panel climbs up over the one before it, then holds. The
   picture arrives a little over-size and off its mark and eases back to true on
   that run, so the drink lands in the frame rather than simply appearing.

   Direction and depth are set per press on the article, because the four shots
   are composed differently — beet stands low, green is a tall bottle mid-frame,
   orange is a wide splash, charcoal sits close and dark — and one canned move
   across all four would flatten that.

   One rule holds them together: an offset only ever has half the scale gain
   to move into, so --from-x / --from-y must stay under --from-z / 2 or the
   picture pulls off its own frame and shows bare panel behind it. At 4% into
   a .09 scale that left 2px of cover on a phone. They are all at 3% into .10
   or better now, which is a fifth of the frame in hand.

   The transform is on the picture and not on .prod-world__art, so the pressed
   name laid over it stays anchored while the picture settles behind it. */
.prod-world__art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Eased out rather than linear: at scrub speed a constant-rate settle reads
     as a slide, and the last part of the move is what sells it as weight
     coming to rest. */
  --settle: clamp(0, var(--p, 1), 1);
  --rest: calc(1 - (1 - var(--settle)) * (1 - var(--settle)));
  transform:
    translate3d(
      calc((1 - var(--rest)) * var(--from-x, 0%)),
      calc((1 - var(--rest)) * var(--from-y, 0%)),
      0)
    scale(calc(1 + (1 - var(--rest)) * var(--from-z, .1)));
  will-change: transform;
}

/* Comes up with the picture, a beat behind it, so the words land after the
   drink does rather than riding in alongside. */
.prod-world__copy {
  --settle: clamp(0, var(--p, 1), 1);
  opacity: clamp(0, var(--settle) * 2.4 - .9, 1);
  transform: translate3d(0, calc((1 - var(--settle)) * 1.4rem), 0);
}

/* Overprinted rather than layered behind, because the photograph is a single
   flat field with the bottle standing in it — there is no behind to get to.
   multiply against the field's own darker shade means the word only ever
   darkens what is already there, so it reads as ink laid onto the picture
   instead of a panel floating over it. */
.prod-world__ghost {
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-54%);
  margin: 0;
  text-align: center;
  font-family: var(--prod-display);
  font-weight: 900;
  font-stretch: 125%;
  font-size: clamp(4rem, 20vw, 17rem);
  line-height: .76;
  letter-spacing: -.05em;
  text-transform: uppercase;
  color: var(--deep);
  mix-blend-mode: multiply;
  opacity: .9;
  pointer-events: none;
  user-select: none;
}

/* Charcoal is already as dark as ink gets, so darkening it further leaves
   nothing to read. That word lightens instead — same device, run backwards,
   which is also the only honest way to set the name of a black drink. */
.prod-world--dark .prod-world__ghost { mix-blend-mode: screen; }

/* Lifts the copy off the field without putting a box around it. */
.prod-world__veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to top, var(--veil) 0%, transparent 46%);
}

.prod-world__copy {
  position: relative;
  z-index: 3;
  align-self: end;
  max-width: 26rem;
}

.prod-world__no {
  font-family: var(--chrome-mono);
  font-size: .66rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  opacity: .75;
  margin: 0 0 .7rem;
}

.prod-world__name {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 108%;
  font-size: clamp(1.7rem, 3.6vw, 2.9rem);
  line-height: 1;
  letter-spacing: -.03em;
  margin: 0;
}

.prod-world__note {
  margin: .9rem 0 0;
  font-size: .93rem;
  line-height: 1.7;
  opacity: .82;
  max-width: 24rem;
}

.prod-world__date {
  margin: 1.1rem 0 0;
  font-family: var(--chrome-mono);
  font-size: .6rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  opacity: .68;
  display: flex;
  align-items: baseline;
  gap: .6rem;
}
.prod-world__date span {
  font-family: var(--prod-hand);
  font-size: 1.5rem;
  letter-spacing: .01em;
  text-transform: none;
  opacity: 1;
}

/* The number lives inside the panel it counts, so it cannot fall out of step
   with what is on screen — there is nothing to keep in sync. Same reason the
   arrows are ordinary links: the browser already knows how to go to a panel. */
.prod-world__nav {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1.4rem;
  font-family: var(--chrome-mono);
}

.prod-world__count {
  margin: 0;
  font-size: .72rem;
  letter-spacing: .2em;
  display: flex;
  align-items: baseline;
  gap: .45rem;
}
.prod-world__count b { font-weight: 400; font-size: 1.5rem; letter-spacing: .02em; }
.prod-world__count i { font-style: normal; opacity: .45; }
.prod-world__count span { opacity: .6; }

.prod-world__arws { display: flex; gap: .5rem; }

.prod-world__arw {
  width: 2.6rem;
  height: 2.6rem;
  display: grid;
  place-items: center;
  border: 1px solid currentColor;
  border-radius: 50%;
  color: inherit;
  opacity: .55;
  transition: opacity .25s var(--ease-out), background-color .25s var(--ease-out);
}
.prod-world__arw svg { width: 15px; height: 15px; }
.prod-world__arw:hover,
.prod-world__arw:focus-visible { opacity: 1; background: rgba(255, 255, 255, .12); }

/* Reference bar down the left edge, doing a job: four marks, one per press,
   the current one filled. */
.prod-world__rail {
  position: absolute;
  z-index: 3;
  left: calc(var(--gutter) / 2);
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}
.prod-world__rail li {
  width: 2px;
  height: 26px;
  background: currentColor;
  opacity: .22;
}
.prod-world__rail li.is-on { opacity: .95; height: 40px; }


/* --- 4. The fall -------------------------------------------------------- */

.prod-ex {
  height: 300vh;
  background: var(--sc-bg-2);
  --bed-base: var(--sc-bg-2);
  --wash-min: .3;
  --wash:
    radial-gradient(96% 78% at 50% 114%, rgba(126, 214, 34, .92), rgba(126, 214, 34, 0) 72%),
    radial-gradient(66% 50% at 50% -12%, rgba(167, 226, 64, .36), rgba(167, 226, 64, 0) 74%);
}

.prod-ex__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  padding: clamp(4rem, 10vh, 6rem) var(--gutter) clamp(1.5rem, 4vh, 2.6rem);
}

/* The written half of the section, standing clear of the fall at the foot.
   Two columns on the wide end — what goes in on the left, what does not on the
   right — with the measure ruled underneath them both.

   This used to be a centred title card above the fall, which put it exactly
   where a 33rem apple passes. No amount of fading fixed that; moving it did. */
.prod-ex__foot {
  position: relative;
  z-index: 5;
  display: grid;
  isolation: isolate;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  column-gap: clamp(1.5rem, 5vw, 4rem);
  row-gap: clamp(1rem, 2.6vh, 1.6rem);
}

/* The pour ends up flooding the bottom of the frame with green light, and cream
   over that measures 1.8:1 — the headline, the rail label and the weight all
   disappear at the exact moment the section resolves. Rather than inverting the
   type mid-scroll, which dips through a bad contrast crossing on the way, the
   foot gets its own ground: a soft plum base that keeps every measurement it
   already passed, with the green still glowing around and above it. */
.prod-ex__foot::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: -50vw;
  right: -50vw;
  top: -1.6rem;
  bottom: calc(clamp(1.5rem, 4vh, 2.6rem) * -1 - 2px);
  background: linear-gradient(to top,
    rgba(20, 4, 26, .82),
    rgba(20, 4, 26, .74) 46%,
    rgba(20, 4, 26, 0));
  pointer-events: none;
}

/* Set in the utility face rather than the display one so it reads as spec
   alongside the rail below it, not as a second headline arguing with the
   first. Arrives once the last piece is down. */
.prod-ex__close {
  --in: clamp(0, (var(--p, 0) - .74) * 8, 1);
  margin: 0;
  text-align: right;
  font-family: var(--chrome-mono);
  font-size: .68rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sc-accent);
  opacity: var(--in);
  transform: translate3d(0, calc((1 - var(--in)) * .7rem), 0);
}
.prod-ex__head h2 {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 108%;
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  line-height: 1.04;
  letter-spacing: -.03em;
  margin: .8rem 0 0;
}

/* The rail at the foot fills left to right; this is the same event at the size
   of the frame. It is held back until the last ingredient is on its way down,
   so it never washes out the produce falling through it, and it is a bloom
   rather than a flood: the strength sits in the middle of the frame — the part
   that empties out once the run is over — and falls away before it reaches the
   type at the foot, which has to stay cream on plum to stay readable. */
.prod-fall__pour {
  --rise: clamp(0, (var(--p, 0) - .62) * 3.2, 1);
  position: absolute;
  inset: 0;
  /* Anchored below the frame and reaching a little past halfway, so the top of
     the section keeps its plum and the green reads as light coming up off the
     juice rather than a fill colour. */
  background: radial-gradient(110% 72% at 50% 116%,
    rgba(163, 232, 62, .6),
    rgba(126, 214, 34, .34) 44%,
    rgba(126, 214, 34, 0) 78%);
  transform: translate3d(0, calc((1 - var(--rise)) * 26%), 0);
  opacity: var(--rise);
}

/* Deliberately no z-index. The cuts inside blend with what is behind them, and
   a z-index here would make this list its own stacking context — the pieces
   would then blend against nothing and keep the black they were shot on. With
   auto it stays in the stage's context, where the bed is painted just below it
   in tree order, which is exactly the backdrop they need. The head above sits
   at 5 and the measure at 4, so the stack still holds. */
.prod-fall {
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each ingredient owns a slice of the section's scroll: .16 apart and .48
   long, so there are two in the air for most of it. The whole run is pulled .22
   earlier than its index, which means two pieces are already falling the moment
   the section pins — without that head start --p: 0 is an empty field, which is
   the exact thing this section was rebuilt to stop being.

   The run finishes around .74 rather than at the very end, which leaves the
   last quarter to the rail sitting full at 2.1 kg and the closing line. */
.prod-fall__item {
  --t: clamp(0, (var(--p, 0) + .22 - var(--i) * .16) / .48, 1);
  /* Fruit does not fall at a constant speed. A mild ease-in is enough to sell
     the weight — squaring it outright leaves the piece hanging at the top of
     the frame for half its life, which at scrub speed reads as stuck. */
  --fall: calc(var(--t) * (.72 + var(--t) * .28));

  position: absolute;
  top: 0;
  /* --lane and --w arrive as inline styles, which outrank anything a media
     query can say. So the breakpoints set --lane-mq / --w-mq instead and this
     reads those first, falling back to the per-item values. Overriding --lane
     directly looked right in the stylesheet and did nothing in the browser. */
  left: var(--lane-mq, var(--lane, 50%));
  width: var(--w-mq, var(--w, 18rem));
  /* Centred on its lane with a margin rather than a translate: a transform on
     this element would make it a stacking context, and the cut inside would
     then blend against nothing and keep its black. */
  margin-left: calc(var(--w-mq, var(--w, 18rem)) / -2);
  /* How far a piece travels, and how fast it lets go. Tightened on small
     screens so a piece and its caption are gone before they reach the type at
     the foot, which sits much higher up the frame there. */
  --drop: 118svh;
  --lift: 44svh;
  --exit: 3.2;
  --exit-tag: 4;
}

/* Translate only. The blend already costs a compositing pass, and anything
   that re-rasterises on the way past shows up as a dropped frame. */
.prod-fall__cut,
.prod-fall__tag {
  transform: translate3d(0, calc(var(--fall) * var(--drop) - var(--lift)), 0);
}

.prod-fall__cut {
  display: block;
  mix-blend-mode: screen;
  /* In fast, out slower and earlier, so the piece dissolves into the foot of
     the frame rather than sliding under the rail. */
  opacity: clamp(0, min(var(--t) * 9, (1 - var(--t)) * var(--exit)), 1);
  will-change: transform;
}
.prod-fall__cut img {
  display: block;
  width: 100%;
  height: auto;
  transform:
    rotate(calc(var(--tilt, 0deg) + var(--t) * var(--tumble, 0deg)))
    scale(calc(.86 + var(--t) * .22));
  /* Three of these plates are cut fruit shot under a hard key. The kale plate
     came back cold: sampled off the leaf itself it sits at 194deg, a blue-cyan,
     and screened onto a blackcurrant field it reads as the one teal thing on a
     page about green. Pulled to 104deg, which is a leaf rather than the page's
     lime accent. Only that plate is touched. */
  filter: hue-rotate(var(--hue, 0deg)) saturate(var(--sat, 1)) brightness(var(--bri, 1));
  /* These are studio plates: the produce is lit in the middle and the corners
     carry the fall-off from the key light. Everywhere else on the page they
     run at half opacity and that haze never shows, but at full strength screen
     turns it into a hard bright wedge across the frame. Faded out past the
     action instead, which is well clear of the flung water. */
  -webkit-mask-image: radial-gradient(closest-side at 50% 50%, #000 72%, transparent 100%);
  mask-image: radial-gradient(closest-side at 50% 50%, #000 72%, transparent 100%);
}

/* 86%, not 100%: the plates are square but the produce sits in the middle of
   them, and hung off the true edge the callout floats in what looks like empty
   space with a very long rule reaching back to it. */
.prod-fall__tag {
  position: absolute;
  top: 44%;
  left: 86%;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: .55rem;
  white-space: nowrap;
  font-family: var(--chrome-mono);
  font-size: .64rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--sc-ink);
  /* Held under full strength so the callout stays subordinate to the piece it
     names, and cut short of the picture's own fade so the words are gone
     before the produce is.

     It also arrives well after the piece does. Brought up with the picture, a
     callout can land beside a piece that is still up in the headline zone, and
     two lines of type end up sharing the same inch of screen. By the time this
     reaches full strength its piece is clear of the head. */
  opacity: clamp(0, min(var(--t) * 4 - .5, (1 - var(--t)) * var(--exit-tag)), .92);
}
/* A rule back to the piece. Without it the callout reads as text that happens
   to be near some fruit rather than as a label on it. */
.prod-fall__tag::before {
  content: "";
  align-self: center;
  width: clamp(16px, 3vw, 34px);
  height: 1px;
  background: currentColor;
  opacity: .45;
}
.prod-fall__n { color: var(--sc-accent); font-weight: 600; }
.prod-fall__g { color: var(--sc-ink); opacity: .6; }

/* Mirrored by moving the rule to the end rather than reversing the row —
   row-reverse flips the words too, and the callout came out as
   "430 g  Kale & spinach  02". */
.prod-fall__item:nth-child(even) .prod-fall__tag {
  left: auto;
  right: 86%;
}
.prod-fall__item:nth-child(even) .prod-fall__tag::before { order: 1; }

/* The measure. Fills as the ingredients land, and the three ticks sit at each
   one's running share of the total — 700g, then 1,130, then 1,910 of 2,100 —
   so the gaps between them are the proportions of the recipe rather than
   decoration. The number at the end is the one the spec strip below quotes. */
.prod-fall__measure {
  position: relative;
  z-index: 4;
  margin: 0;
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(.9rem, 2.4vw, 2rem);
  font-family: var(--chrome-mono);
  font-size: .62rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--sc-ink);
}
/* Held at .78 rather than lower: at the end of the section the pour puts a
   green field behind this line, and cream below about .75 stops clearing 4.5:1
   against it. Still plainly subordinate to the weight at the other end. */
.prod-fall__measure-l { opacity: .78; }
.prod-fall__measure-r { font-weight: 600; }

.prod-fall__rail {
  position: relative;
  height: 1px;
  background: var(--sc-line);
}
.prod-fall__fill {
  position: absolute;
  inset: 0;
  background: var(--sc-accent);
  transform: scaleX(var(--p, 0));
  transform-origin: left;
}
.prod-fall__tick {
  position: absolute;
  left: var(--at);
  top: -4px;
  width: 1px;
  height: 9px;
  background: currentColor;
  opacity: .4;
}


/* --- 5. Spec strip ------------------------------------------------------ */

/* Warm from here on. The muted mauve that carries the labels everywhere else
   goes grey against orange, so this section and the closing one switch to a
   warm cream — and the rules over the numbers come up to match. */
.prod-spec {
  overflow: hidden;
  --sc-ink-soft: #EFDDC5;
  --sc-line: rgba(251, 243, 228, .34);
  --wash-min: .42;
  --wash:
    linear-gradient(to bottom, rgba(126, 206, 30, .72), rgba(126, 206, 30, 0) 50%),
    radial-gradient(125% 105% at 50% 126%, rgba(250, 112, 14, .80), rgba(250, 112, 14, 0) 70%),
    radial-gradient(74% 74% at 8% -22%, rgba(250, 172, 34, .34), rgba(250, 172, 34, 0) 72%);
}
.prod-spec > .sc-shell { position: relative; z-index: 1; }

.prod-spec__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2.5rem;
}
.prod-spec__item { border-top: 1px solid var(--sc-line); padding-top: 1.35rem; }
.prod-spec__n {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(1.9rem, 3.6vw, 3rem);
  line-height: 1;
  letter-spacing: -.03em;
  margin: 0;
}
.prod-spec__l {
  margin: .6rem 0 0;
  font-family: var(--chrome-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sc-ink-soft);
}


/* --- 6. Closing ---------------------------------------------------------- */

.prod-cta {
  position: relative;
  overflow: hidden;
  text-align: center;
  --sc-ink-soft: #F6E7CE;
  --wash-min: .36;
  /* The last band is the page landing back on Nettorra's own dark, so the
     brightest thing on the site does not end in a straight line. It is mixed to
     .sc-outro's navy, not this page's near-black — landing on the wrong dark
     still leaves a seam, just a subtler one. */
  --wash:
    linear-gradient(to top, rgb(5, 12, 30), rgba(5, 12, 30, 0) 12%),
    linear-gradient(to bottom, rgba(222, 104, 20, .40), rgba(222, 104, 20, 0) 30%),
    /* Both ellipses are deliberately tall rather than tightly peaked. The fine
       print lands at 74% of this section on desktop and 84% on a phone — where
       the buttons stack — so a narrow core would light one and strand the other
       on the mid-tone shoulder, which is the one place its ink cannot read. */
    radial-gradient(70% 58% at 50% 80%, rgba(252, 214, 96, .86), rgba(252, 214, 96, 0) 64%),
    radial-gradient(120% 95% at 50% 88%, rgba(232, 136, 22, .95), rgba(232, 136, 22, 0) 68%),
    radial-gradient(90% 70% at 10% -10%, rgba(206, 26, 100, .34), rgba(206, 26, 100, 0) 68%);
}
.prod-cta__inner { position: relative; z-index: 1; }
.prod-cta h2 {
  font-family: var(--prod-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(2.1rem, 5vw, 3.8rem);
  line-height: 1.02;
  letter-spacing: -.035em;
  margin: .8rem 0 1.1rem;
}
.prod-cta p { margin-inline: auto; max-width: 46ch; color: var(--sc-ink-soft); }
.prod-cta__actions {
  margin-top: 2.4rem;
  display: flex;
  flex-wrap: wrap;
  gap: .9rem;
  justify-content: center;
}
/* Typed down to p.<class> on purpose: .prod-cta p above is one specificity point
   heavier than a bare class, so the colour below never landed without it. */
.prod-cta p.prod-cta__fine {
  margin-top: 2rem;
  font-family: var(--chrome-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  /* This line sits over the peak of the citrus, so cream stops being readable
     exactly where the page is doing its best work. The ink crosses to a burnt
     near-black on the same --p that raises the light — stamped into the glow
     like the print on a label, rather than washed out by it. The crossover is
     steep on purpose: a half-and-half ink on a half-lit bed is the one state
     that reads worse than either end. */
  color: color-mix(
    in oklab,
    #20090A calc(clamp(0, calc(var(--p, 0) * 3.2 - .5), 1) * 100%),
    var(--sc-ink-soft)
  );
  opacity: .92;
}



/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1000px) {
  .reno-story__grid,
  .prod-press__grid { grid-template-columns: 1fr; }

  /* With the method in one column there is no empty half for the beetroot to
     fill — the copy runs the full width and the produce lands on it. Both
     pieces drop to the bottom corners instead. */
  .prod-press .prod-fruit { top: 87%; opacity: calc(var(--o) * .75); }

  /* Machine over readings rather than beside them. Side by side needs about
     twenty-six rems each and there is not that much left once the gutters are
     paid for — the bottle was the first thing to be squeezed out of shape. */
  .press { grid-template-columns: 1fr; gap: 1.8rem; }

  .reno-grid { grid-template-columns: repeat(2, 1fr); }
  .reno-steps { grid-template-columns: repeat(2, 1fr); row-gap: 2.5rem; }

  /* Side-mounted callouts need room outside the lane, so pull the lanes in and
     the tags closer. */
  .prod-fall__item { --w-mq: clamp(10rem, 30vw, 20rem); }
  .prod-fall__item:nth-child(odd) { --lane-mq: 34%; }
  .prod-fall__item:nth-child(even) { --lane-mq: 66%; }
  .prod-fall__tag { left: 90%; font-size: .58rem; letter-spacing: .15em; }
  .prod-fall__item:nth-child(even) .prod-fall__tag { right: 90%; }
}

@media (max-width: 720px) {
  .reno-hero { height: 165vh; }

  /* The landing is driven straight off scroll position, which is fine while
     there is room for it. Here there is not. --open-at .85 and --open-rate
     6.667 give the reveal the last fifteen percent of the scene's travel, and
     the line above cuts that travel from 100vh to 65vh: measured on a 390x844
     phone it comes to 78 pixels of scroll, against about 135 on a desktop. One
     thumb-flick crosses 78 pixels in two or three frames, so the copy arrives
     in two or three steps — it pops rather than fades, which is what reads as
     the glitch after the film lands.

     A transition fixes the symptom without touching the choreography. The
     values still track scroll exactly; the paint just eases between whatever
     samples the scroll gives it, so a coarse momentum step becomes a fade. The
     alternative — widening the window by moving --open-at earlier — would start
     the copy at p .70 while the film is still scrubbing to its end at .85, and
     the whole point of this hero is that the copy arrives as the film lands.

     .sc-cine__body is deliberately not in this list. Its transform is the
     continuous parallax that runs for the entire flight, not a one-shot
     reveal, and easing it would make the film's copy lag the film itself. */
  .reno-hero__open,
  .reno-hero .sc-cine__stage::before {
    transition: opacity .4s var(--ease-out, ease), transform .4s var(--ease-out, ease);
  }

  /* The copy runs full width on a phone and drops onto the produce, which is
     placed for a two-column page. Push the band lower so it still rises from
     the bottom edge and the lines stay clear of it. */
  .prod-worlds .prod-fruit {
    top: calc(var(--y) + 14%);
    width: calc(var(--w) * .8);
    opacity: calc(var(--o) * .82);
  }

  /* Same problem at the foot of the page, where the apple ends up under the
     delivery line. A small drop clears it — any more and the extra foot below
     pushes the apple into the landing band, where it disappears. */
  .prod-cta .prod-fruit {
    top: calc(var(--y) + 2%);
    width: calc(var(--w) * .8);
    opacity: calc(var(--o) * .78);
  }

  /* The buttons stack here, which pushes the delivery line from 74% of the
     section down to about 90% — inside the band that lands the page back on
     Nettorra's navy. It was being painted over rather than sitting on the light.
     The extra foot gives it back the same clearance it has on a desktop. */
  .prod-cta { padding-bottom: calc(var(--sc-y) + 4.5rem); }

  /* The copy fills the width down here, so a wash coming in from the left leaves
     the end of the eyebrow sitting out on bare picture. Turn it upright: it now
     rises from the bottom, covering the whole block instead of one column. */
  .reno-hero .sc-cine__stage::before {
    background: linear-gradient(0deg,
      rgba(5, 6, 10, .58) 0%,
      rgba(5, 6, 10, .52) 42%,
      rgba(5, 6, 10, .28) 70%,
      transparent 92%);
  }

  .reno-swap { height: 330vh; }

  /* The three aerials are 2560x1429 — one wide drone frame of a wide house.
     The stage is 100svh, so on a phone it is roughly 390 x 750, and `cover`
     settles that shape difference by scaling the picture until it fills:
     1343px wide, of which the screen shows 390. A quarter of the frame, and
     the quarter with the least in it, because a house shot from the air is a
     horizontal subject and a vertical crop keeps mostly roof. The sequence
     only works if you can see it is the same house on all three visits, and
     you cannot see that through a slot. On a laptop the stage is about 16:9
     already, which is why this reads correctly there and only there.

     So on a phone the layers stop filling the stage and become a band the
     shape of the photograph, sitting above the copy. Same move, and for the
     same reason, as the press shots further down this block: fit it whole and
     let the field colour carry the rest. The stage is #14100D above and below
     the band, so there is no edge where the picture stops. */
  .reno-swap__layer .ph {
    inset: 9svh 0 auto;
    height: auto;
    aspect-ratio: 2560 / 1429;
  }

  /* The push-in stays, because the drone easing closer is the motion that
     carries the section, but it is shallower here: 10% off a full-bleed frame
     is a drift, while 10% off a 390px band takes a visible bite out of both
     ends of the house — which is the crop this block exists to undo. */
  .reno-swap__layer--before { transform: scale(calc(1.05 - var(--p) * .03)); }
  .reno-swap__layer--frame  { transform: scale(calc(1.06 - var(--p) * .04)); }
  .reno-swap__layer--after  { transform: scale(calc(1.07 - var(--p) * .06)); }

  /* The vignette was drawn for a full-bleed photograph with the headline lying
     on top of it. The headline now sits on the field below the band, so the
     only job left is keeping the rail and the hint legible along the bottom
     edge; the rest of it was just darkening the picture. */
  .reno-swap__veil {
    background: linear-gradient(0deg, rgba(8, 6, 5, .58) 0%, rgba(8, 6, 5, 0) 24%);
  }

  /* Grid centring puts the copy in the middle of the stage, which is now the
     gap immediately under the band. Half the padding moves the centre down, so
     the caption clears the picture and reads as belonging to it. */
  .reno-swap__body { padding-top: 24svh; }

  .prod-film { height: 230vh; }
  .prod-ex { height: 230vh; }

  .sc-slot { font-size: .58rem; max-width: 56vw; }

  .reno-grid { grid-template-columns: 1fr; }
  .reno-stats__grid { grid-template-columns: repeat(2, 1fr); row-gap: 2.5rem; }
  .prod-spec__grid { grid-template-columns: repeat(2, 1fr); row-gap: 2rem; }

  /* Each press is shot wide, bottle centred, produce thrown out to both
     edges. Cropped to a phone, cover keeps the bottle and throws away
     everything flying around it — which is the half that makes the picture.
     Fitted whole into the top of the panel instead, with the field colour
     carrying the rest of the screen; the photograph and the background are
     the same colour, so there is no visible edge where one stops. */
  .prod-world { grid-template-rows: minmax(0, 1fr) auto; }
  .prod-world__art { bottom: auto; height: 58%; }
  .prod-world__art img { object-fit: contain; object-position: 50% 62%; }
  /* 26vw sizes off a four-letter word; the five-letter ones ran off the right
     edge and read as a layout fault rather than as scale. Sized so the longest
     name just fits the screen — still enormous, still the loudest thing in the
     panel, but a whole word. */
  .prod-world__ghost { top: 30%; font-size: clamp(3rem, 21vw, 9rem); }
  .prod-world__copy { max-width: none; }
  .prod-world__note { max-width: none; font-size: .88rem; }
  .prod-world__rail { display: none; }
  .prod-world__veil { background: linear-gradient(to top, var(--veil) 0%, var(--veil) 30%, transparent 66%); }
  .prod-world__nav { justify-content: space-between; }

  /* No room beside a lane on a phone, so the callout goes under the piece and
     rides down with it. Nothing lies to the side, so both lanes come back to
     the middle and the pieces fall in a single column. */
  .prod-fall__item,
  .prod-fall__item:nth-child(odd),
  .prod-fall__item:nth-child(even) {
    --lane-mq: 50%;
    --w-mq: min(74vw, 18rem);
    --drop: 104svh;
    --lift: 38svh;
    --exit: 4.6;
    --exit-tag: 6;
  }

  /* One column on a phone, and the closing line keeps its own row so the block
     does not jump when it arrives. */
  .prod-ex__foot { grid-template-columns: minmax(0, 1fr); }
  .prod-ex__close { text-align: left; font-size: .6rem; letter-spacing: .16em; }
  .prod-ex__head h2 { font-size: clamp(1.15rem, 5.4vw, 1.6rem); margin-top: .45rem; }
  .prod-fall__tag,
  .prod-fall__item:nth-child(even) .prod-fall__tag {
    top: calc(100% + .5rem);
    left: 50%;
    right: auto;
    width: max-content;
    justify-content: center;
    flex-direction: row;
    font-size: .58rem;
  }
  /* The tag no longer sits beside the picture, so it has to travel with it —
     same fall, shifted back onto its own centre line. Nothing to point at from
     underneath, so the rule goes. */
  .prod-fall__tag { transform: translate3d(-50%, calc(var(--fall) * var(--drop) - var(--lift)), 0); }
  .prod-fall__tag::before { display: none; }

  .prod-fall__measure { font-size: .56rem; letter-spacing: .16em; gap: .8rem; }
  .sc-btn { padding: .95rem 1.6rem; font-size: .76rem; }

  /* The band's copy stacks to full width on a phone, so there is no longer a
     clear right-hand side for the mark to sit in and no left-hand side for the
     wash to protect — at desktop settings it lands as a small blob in the
     middle of the paragraph. Widened into a backdrop behind the whole stack and
     dropped well back instead, which keeps the treatment reading the same. */
  .sc-outro__art {
    right: -16%;
    width: min(96%, 24rem);
    /* Lower than the desktop setting because the plate is now behind the copy
       rather than beside it — the mask that used to keep the left-hand side
       clear is gone at this width, so the whole paragraph sits over the glow
       and the glow has to give way to it. */
    opacity: .34;
    -webkit-mask-image: none;
    mask-image: none;
  }
}

@media (max-width: 480px) {
  .prod-film__ticker { gap: .6rem 1.5rem; font-size: .62rem; }

  /* On a phone the ticker wraps to two lines and lands straight on top of the
     cropped wordmark, so both stop reading. The giant word is the flavour
     panels' device anyway — it loses nothing by staying out of the hero here. */
  .prod-film__ghost { display: none; }
  .prod-world__date span { font-size: 1.3rem; }
}


/* ==========================================================================
   REDUCED MOTION
   The scroll engine parks every scene at --p: 1, which would leave the copy
   that fades out on scroll invisible. Pin everything to a readable state and
   unpin the sticky sections so the pages become ordinary documents.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .reno-hero,
  .reno-swap,
  .prod-film,
  .prod-ex { height: auto; }

  .sc-cine__stage,
  .reno-swap__stage,
  .prod-film__stage { position: relative; height: 100svh; }

  /* The four presses work by sticking, so one slides up over the last.
     Unstuck, they are four full-height panels in a row — the same sequence
     in the same order, with nothing moving over anything. */
  .prod-world { position: relative; }
  .prod-world__art img,
  .prod-world__copy { transform: none; opacity: 1; }

  .prod-ex__stage { position: relative; height: auto; overflow: visible; }
  .reno-hero .sc-cine__layer,
  .reno-swap__layer,
  .prod-film__media > img,
  .prod-film__media > video { transform: none; }

  .reno-hero .sc-cine__body,
  .reno-hero__open,
  .prod-film__head,
  .prod-film__open,
  .prod-film__ticker,
  .reno-swap__copy--after,
  .reno-swap__slot--after { opacity: 1; transform: none; }
  /* Nothing is holding the copy back here, so the beds behind it have to be
     present or the headlines sit on a photograph unaided. */
  .reno-hero .sc-cine__stage::before,
  .prod-film__grade::before { opacity: 1; }
  /* --open parks with --p at 1 in this mode, but the transition would still run
     the rule across the word on load. It is already struck. */
  .prod-film h1 em::after { transition: none; }

  /* The scene is static here, so a scroll-progress readout would lie. The build
     sequence collapses to its last stage — the earlier visits are carried by the
     project cards further down the page. */
  .reno-swap__hint,
  .reno-swap__layer--before,
  .reno-swap__copy--before,
  .reno-swap__slot--before,
  .reno-swap__layer--frame,
  .reno-swap__copy--frame,
  .reno-swap__slot--frame,
  .reno-hero__film,
  .reno-hero__cue,
  .prod-film__cue,
  .sc-cue i { display: none; }

  /* The engine parks --p at 1, which is the far end of every fall — all four
     pieces would be off the bottom of the frame and faded out. Laid out as
     what they actually are instead: the four ingredients, side by side, each
     one named and weighed. */
  .prod-ex__close { opacity: 1; transform: none; }
  /* --p parks at 1 here, so the pour is already at full strength over a section
     that is now several screens tall rather than one. Held back to a tint. */
  .prod-fall__pour { opacity: .5; transform: none; }
  .prod-fall {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: clamp(1rem, 3vw, 2.5rem);
    margin-top: 2.5rem;
  }
  .prod-fall__item {
    position: relative;
    top: auto;
    left: auto;
    width: auto;
    margin-left: 0;
  }
  .prod-fall__cut { opacity: 1; transform: none; }
  .prod-fall__cut img { transform: none; }
  .prod-fall__tag,
  .prod-fall__item:nth-child(even) .prod-fall__tag {
    position: relative;
    top: auto;
    left: auto;
    right: auto;
    opacity: .92;
    transform: none;
    margin-top: .9rem;
    justify-content: center;
    flex-direction: row;
    white-space: normal;
  }
  .prod-fall__tag::before { display: none; }
  .prod-fall__measure { margin-top: 2rem; }
  .prod-fall__fill { transform: none; }
}

@media (prefers-reduced-motion: reduce) and (max-width: 720px) {
  .prod-fall { grid-template-columns: repeat(2, 1fr); }
}
