/* ============================================================================
   Concept A: "Follow di Smoke"

   Only the film is styled here. Everything below the film lives in
   shared/afterfilm.css, and every colour, size, and easing comes from
   shared/tokens.css. Nothing in this file hard-codes a colour or a word.

   Layer order inside the stage. tokens.css fixes --z-film 1, --z-beats 5,
   --z-embers 6, --z-grain 7. The in-between numbers below sit above the
   picture and under the words, so text is never dimmed by its own weather:

     1  the five scenes
     2  the firelight flicker
     3  the smoke blobs
     4  the vignette, then the fade to char, then the text scrim
     5  the hero and the beats
     6  the embers canvas
     7  the film grain
   ========================================================================== */

/* ---- The driver -----------------------------------------------------------
   A tall empty element. Its height is the length of the film in scroll.
   Six screens on desktop, five on phones, where scrolling costs more effort. */
.film {
  position: relative;
  height: 600vh;
}

/* ---- The stage ------------------------------------------------------------
   Sticky, never pinned, so ScrollTrigger has no pin spacer to get wrong.
   svh keeps it exactly one screen tall on phones where the browser chrome
   slides away. */
.film__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: var(--char);
  /* The stage is the containing block for everything absolute inside it. */
  isolation: isolate;
}

/* ---- The five shots -------------------------------------------------------
   Each scene fills the stage. The subject sits in the middle third of every
   image, so a 360px phone crop still lands on the food. */
.scene {
  position: absolute;
  inset: 0;
  z-index: var(--z-film);
  opacity: 0;
  will-change: transform, opacity;
}

/* a1 is the first frame, so it is the only one visible before a scroll. */
.scene[data-scene="a1-di-ends"] { opacity: 1; }

.scene img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
}

/* Only the two scenes the film blurs or brightens carry a filter, because a
   filter forces its own compositing layer and five of them would cost more
   than it is worth. a.js animates the two numbers, never the filter string. */
.scene[data-scene="a1-di-ends"],
.scene[data-scene="a3-di-fyah"] {
  --blur: 0;
  --bright: 1;
  filter: blur(calc(var(--blur) * 1px)) brightness(var(--bright));
}

/* ---- Firelight (positions 46 to 62 only) ---------------------------------
   A low, warm bloom that wobbles on its own 1.6s sine, so the fire is alive
   even when the visitor has stopped scrolling. a.js pauses it outside the
   range and never starts it under reduced motion. */
.film__flicker {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    60% 55% at 50% 88%,
    color-mix(in srgb, var(--bonnet) 25%, transparent) 0%,
    transparent 70%
  );
}

/* ---- Smoke ----------------------------------------------------------------
   Three big soft blobs. They roll up through the frame at the two cuts and
   hide the swap underneath, which is what makes two stills read as one move. */
.film__smoke {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.smoke__blob {
  position: absolute;
  width: 90vmax;
  height: 90vmax;
  margin-left: -45vmax;
  margin-top: -45vmax;
  opacity: 0;
  border-radius: 50%;
  filter: blur(60px);
  will-change: transform, opacity;
  /* rgba(201,184,163,0.55) in the brief is Festival Cream muted at 55 percent. */
  background: radial-gradient(
    circle at 50% 50%,
    color-mix(in srgb, var(--cream-muted) 55%, transparent) 0%,
    transparent 68%
  );
}

/* Spread the three so they never sit on top of each other. This is done with
   left and top, not a transform, because the timeline owns every blob's
   transform and would wipe an offset written here. */
.smoke__blob:nth-child(1) { left: 28%; top: 58%; }
.smoke__blob:nth-child(2) { left: 66%; top: 50%; }
.smoke__blob:nth-child(3) { left: 44%; top: 44%; }

/* ---- Vignette, fade, scrim ------------------------------------------------ */
.film__vignette {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(
    ellipse 75% 70% at 50% 50%,
    transparent 35%,
    color-mix(in srgb, var(--char) 55%, transparent) 100%
  );
}

.film__fade {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background: var(--char);
}

/* The gradient itself comes from the shared .scrim-b utility. This rule only
   places it. It is always on, because the design system requires text over
   film images to sit on a scrim at every moment, not just at the beats. */
.film__scrim {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 62%;
  z-index: 4;
  pointer-events: none;
}

/* ---- Embers and grain ----------------------------------------------------- */
.film__embers {
  position: absolute;
  inset: 0;
  z-index: var(--z-embers);
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.film__grain {
  position: absolute;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.06;
  /* The shared core shipped grain.png (confirmed by the Director's link check
     on 2026-09-20), so the grain.svg fallback layer was removed: it was a
     guaranteed 404 on every page load. */
  background-image: url("../../assets/grain.png");
  background-repeat: repeat;
  background-size: 256px 256px;
}

/* ---- The hero -------------------------------------------------------------
   Chapter one. Left aligned off the gutter, as every headline on this site is.
   The whole block drifts up, blurs, and leaves between positions 2 and 12. */
.film__hero {
  position: absolute;
  inset: 0;
  z-index: var(--z-beats);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--s-7) var(--gutter) var(--s-6);
  pointer-events: none;
  --blur: 0;
  filter: blur(calc(var(--blur) * 1px));
  will-change: transform, opacity, filter;
}

.hero__copy { max-width: 100%; }

.hero__kicker {
  color: var(--gold);
  margin: 0 0 var(--s-2);
}

/* The wordmark. Words stay whole when the line wraps on a phone, and each word
   clips its own characters so they can rise out of nothing. The padding and
   the matching negative margin give the clip box room for the letter shapes
   without moving the type off its line. */
.hero__mark {
  margin: 0;
  color: var(--cream);
  overflow: visible;
}

.mega__w {
  display: inline-block;
  overflow: hidden;
  padding: 0.14em 0.03em;
  margin: -0.14em -0.03em;
}

.mega__c {
  display: inline-block;
  white-space: pre;
  will-change: transform;
}

.hero__place {
  color: var(--cream-muted);
  margin: var(--s-2) 0 0;
}

/* The scroll cue, bottom centre, clear of the thermometer on the right edge. */
.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: var(--s-3);
  transform: translateX(-50%);
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
  color: var(--cream-muted);
}

.hero__line {
  display: block;
  width: 1px;
  height: var(--s-4);
  background: linear-gradient(to bottom, var(--gold), transparent);
  transform-origin: 50% 0;
  animation: heroPulse 2.4s var(--ease-smoke) infinite;
}

@keyframes heroPulse {
  0%   { transform: scaleY(0.2); opacity: 0.2; }
  45%  { transform: scaleY(1);   opacity: 1; }
  100% { transform: scaleY(0.2); opacity: 0.2; }
}

/* ---- The beats ------------------------------------------------------------
   The words over the film. All four sit in the same place, bottom left off the
   gutter, and only one is ever up at a time. a.js drives opacity and the small
   rise against the scroll from the film's own progress. */
.beat {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(var(--gutter) + var(--s-2));
  z-index: var(--z-beats);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

.beat__head {
  margin: 0;
  max-width: 18ch;
  color: var(--cream);
}

.beat__sub {
  margin: var(--s-2) 0 0;
  max-width: 34ch;
  color: var(--cream-muted);
}

/* Beat three is only a holder. Its three words carry their own envelopes, so
   the block itself must not be faded out from under them. */
.beat--3 { opacity: 1; }

/* Beat three: three words, stacked, each one burning in on its own. --burn is
   0 when the word is not there and 1 when it is sharp, and it drives the blur
   and the glow at once, exactly the burn-in described in the design system. */
.beat__words {
  margin: 0;
  max-width: 18ch;
  color: var(--cream);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.beat__word {
  --burn: 0;
  display: block;
  opacity: 0;
  filter: blur(calc((1 - var(--burn)) * 12px));
  text-shadow: 0 0 calc((1 - var(--burn)) * 0.7em)
    color-mix(in srgb, var(--gold) 55%, transparent);
  will-change: transform, opacity, filter;
}

/* ---- Phones ---------------------------------------------------------------
   Five screens of scroll instead of six, and room on the right so no line of
   text can ever run under the pan thermometer. */
@media (max-width: 699px) {
  .film { height: 500vh; }

  .beat { padding-right: 3.5rem; }

  .film__hero { padding-top: var(--s-6); }

  .smoke__blob { filter: blur(40px); }

  /* A phone at three times pixel density pays about ten megabytes for every
     permanent compositing layer. Five full-screen shots plus three big blurred
     blobs is more than a cheap handset should hold open all the time, so the
     hints come off here and the browser makes a layer only while something is
     actually moving. */
  .scene,
  .smoke__blob,
  .beat,
  .beat__word,
  .film__hero,
  .mega__c {
    will-change: auto;
  }
}

/* ---- Reduced motion -------------------------------------------------------
   No film. a.js adds .is-static and builds no timeline at all, and the page
   becomes a plain vertical stack: one screen per shot with its words over it.
   Nothing is hidden from the visitor, it simply does not move. */
#film.is-static {
  height: auto;
}

#film.is-static .film__stage {
  position: static;
  height: auto;
  overflow: visible;
  display: grid;
  grid-template-columns: 100%;
}

#film.is-static .scene {
  position: relative;
  inset: auto;
  grid-column: 1;
  height: 100svh;
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* One row per shot, and every block of words shares the row of the shot it
   belongs to, so the text always sits on its own picture. */
#film.is-static .scene[data-scene="a1-di-ends"]  { grid-row: 1; }
#film.is-static .scene[data-scene="a2-di-pan"]   { grid-row: 2; }
#film.is-static .scene[data-scene="a3-di-fyah"]  { grid-row: 3; }
#film.is-static .scene[data-scene="a4-di-spice"] { grid-row: 4; }
#film.is-static .scene[data-scene="p1-di-plate"] { grid-row: 5; }

#film.is-static .film__hero {
  position: relative;
  inset: auto;
  grid-column: 1;
  grid-row: 1;
  min-height: 100svh;
  filter: none;
  opacity: 1;
  transform: none;
  will-change: auto;
}

#film.is-static .beat {
  position: relative;
  left: auto;
  right: auto;
  bottom: auto;
  grid-column: 1;
  align-self: end;
  width: 100%;
  opacity: 1;
  transform: none;
  will-change: auto;
  /* Each block carries its own scrim now that the single shared one is gone. */
  padding: var(--s-7) var(--gutter) var(--s-5);
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--char) 88%, transparent) 0%,
    transparent 100%
  );
}

#film.is-static .beat--1 { grid-row: 2; }
#film.is-static .beat--2 { grid-row: 3; }
#film.is-static .beat--3 { grid-row: 4; }
#film.is-static .beat--4 { grid-row: 5; }

#film.is-static .beat__word {
  --burn: 1;
  opacity: 1;
  filter: none;
  text-shadow: none;
}

/* The weather all goes: no smoke, no embers, no flicker, no fade to black. */
#film.is-static .film__smoke,
#film.is-static .film__flicker,
#film.is-static .film__embers,
#film.is-static .film__fade,
#film.is-static .film__vignette,
#film.is-static .film__grain,
#film.is-static .film__scrim {
  display: none;
}

#film.is-static .hero__line {
  animation: none;
  opacity: 0.6;
}

/* Belt and braces: if a visitor turns reduced motion on after the page loaded,
   the pulse stops even though .is-static was never added. */
@media (prefers-reduced-motion: reduce) {
  .hero__line { animation: none; }
}
