/* ============================================================================
   THE PUBLIC FRONT PAGE'S ASSEMBLING PLATE.
   index.html only. Nothing in the signed-in application loads this file.

   WHERE IT COMES FROM. This is the homepage animation as it stood at merge
   2968c1b, brought back deliberately after two later rewrites replaced it with
   a flat 2D version. The owner chose that version; the job here was to
   replicate it, not to reinterpret it. What makes it the one worth keeping:

     REAL PERSPECTIVE.   A 1100px perspective container with the plate laid
                         back at rotateX(52deg). The flat version had no
                         perspective at all, so "landing" was a slide.
     FOOD ON THE Z AXIS. Each dish starts near the viewer (translateZ 200-300px,
                         scale 1.35, 3px of blur) and recedes onto the plate.
                         That is what reads as depth; the discs themselves stay
                         UPRIGHT, because they are photographs and a photograph
                         rotated 52 degrees is a smear.
     THE PLATE SETTLES.  It arrives at rotateX(64deg) scale(0.9) and opens to
                         rotateX(52deg) scale(1). The plate is part of the
                         assembly rather than a fixed platform the food lands on.
     A CLOSING HALO.     A ring of --grad-intelligence that stays invisible for
                         the first 62% and then closes around the finished
                         plate. The product's "this meal is complete" note.
     ONE COMPOSITION.    Pointer depth moves stage, plate, halo and food
                         together as a single object, capped at 10px/8px.

   WHAT WAS ADAPTED, AND WHY IT HAD TO BE. 2968c1b's hero was a 210vh sticky
   scene with the copy centred OVER the plate. That layout was removed for
   measured reasons that still hold: the headline clipped under the sticky
   header, and both CTAs sat below the fold at 1440x900. What replaced it was
   two columns, copy left and plate right, and that read wrong in its own way -
   the plate hung at the top of its column with a third of the fold empty
   underneath it. The hero is now ONE CENTRED COLUMN with the plate below the
   copy: the original's stacking without the original's sticky runway. Three
   consequences, each one a scaling of the original rather than a change of
   language:

     1. THE CLOCK. The original staggered six dishes across cover 4%-30% to
        34%-60% of a 210vh runway, with the plate and halo over 0%-62%. Those
        proportions are preserved exactly and multiplied by 26vh/60% = 0.4333,
        so the same sequence, the same overlap and the same order land inside
        the fold. 26vh is not a taste: .hero-plate drifts upward on its own
        scroll animation and is half off the top by 45vh, and an earlier
        tuning that ran to 62vh landed the last dish on a plate nobody could
        still see. scripts/hero-assembly.test.mjs measures it.
     2. THE FLIGHT DISTANCES. The original expressed them in pixels (-230px to
        +220px) against a 62vmin stage inside a full-width sticky wrapper that
        clipped them. Here the stage is a 520px column, so the offsets are
        expressed in FOOD WIDTHS instead: every direction and every relative
        magnitude is the original's, scaled to 0.6 so the travel happens
        inside the frame. Pixels here gave the document a sideways scroll at
        360px twice before; a multiple of the dish's own size cannot.
     3. THE CLIP. The original clipped at .hero-sticky, which spanned the
        viewport. .hero-stage inherits that job. It is the structural
        guarantee that no part of this animation can reach the document edge,
        and it is why the caption lives OUTSIDE it, on the figure.

   The palette, the copy, the navigation and the CTA system are the current
   product's and are untouched by this file.
   ========================================================================= */

/* ---- THE FIGURE ----------------------------------------------------------
   The sizing box and the pointer-depth carrier. It is deliberately NOT a
   .layer-mid: system.css parallaxes that class with its own transform at
   0,2,0 specificity, which would beat the stage's translate and give the
   composition two competing depth systems. This one is the original's. */
.hero-plate {
  position: relative;
  aspect-ratio: 1;
  width: min(100%, 560px);
  justify-self: center;
  /* pointer depth, capped at 10px and 8px exactly as the original. The
     properties are written by the 14-line pointer block in index.html and are
     never set on a touch device or under reduced motion, so the fallback of
     0 is the normal state and this rule then costs nothing. */
  transform: translate3d(calc(var(--mx, 0) * 10px), calc(var(--my, 0) * 8px), 0);
  transition: transform var(--motion-slow) var(--ease-out);
  /* the hand-off. The hero does not stop dead at the fold: the plate keeps
     drifting up and shrinking as the first scene leaves. `translate` and
     `scale` are separate properties from `transform`, so this composes with
     the pointer depth above instead of overwriting it. */
  animation: plateFloat 0s linear both;
}
@keyframes plateFloat {
  from {
    translate: 0 0;
    scale: 1;
  }
  to {
    translate: 0 -8%;
    scale: 0.86;
  }
}
@supports (animation-timeline: view()) {
  .hero-plate {
    animation: plateFloat auto linear both;
    animation-timeline: view();
    animation-range: exit -10% exit 90%;
  }
}

/* ---- THE STAGE -----------------------------------------------------------
   Perspective, and the clip. perspective-origin sits above centre because the
   viewer is looking DOWN at a table, and 42% is the original's number. */
.hero-stage {
  position: absolute;
  inset: 0;
  perspective: 1100px;
  perspective-origin: 50% 42%;
  /* NOTHING MAY EVER REACH THE DOCUMENT EDGE. A dish begins outside the frame
     and flies in, which is precisely the shape of bug that has given this page
     a sideways scrollbar twice. `clip` rather than `hidden` because this is a
     structural bound and not a scroll container: `hidden` on a perspective
     parent also makes the box programmatically scrollable, and a keyboard user
     can then land in a region with nothing focusable in it. */
  overflow: clip;
  /* AND THE CLIP MUST NOT BE VISIBLE. This is the one place where the original
     could not simply be copied. Its stage was 62vmin centred in a full-width
     sticky scene with ~360px of clearance on each side, so a dish flying in
     from 220px out was never anywhere near the clip. Here the stage is a 560px
     figure with only its own padding as clearance, and a
     dish carrying scale(1.35) at translateZ(220px) - which perspective
     magnifies by a further 1100/(1100-220) = 1.25 - starts well outside it.
     Screenshot at 1440x900, 10% scroll: two discs sliced by a hard vertical
     line down the right of the frame.
     A feathered mask turns that edge into a fade, so a dish materialises out
     of the light as it approaches instead of being cut by an invisible wall.
     The stops are chosen off the plate's own geometry: it is inset 8%
     horizontally and its rotated ellipse spans about 26%-70% vertically, so
     the falloff is over before it reaches anything solid. */
  -webkit-mask-image:
    linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 6%, #000 94%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 6%, #000 94%, transparent 100%);
  mask-composite: intersect;
}

/* ---- THE PLATE -----------------------------------------------------------
   rotateX was 64deg in the first cut of the original, which squashed a 368px
   plate to a 136px sliver that read as a shadow rather than an object. 52deg
   keeps the sense of a plate laid on a table and gives the ellipse back half
   its height. The rim, the two rings and the contact shadow are what stop it
   disappearing into a near-white page. */
.hero-plate .plate {
  position: absolute;
  inset: 12% 8% 16%;
  border-radius: 50%;
  transform: rotateX(52deg);
  transform-style: preserve-3d;
  background: radial-gradient(
    66% 66% at 40% 30%,
    var(--surface) 0%,
    var(--background-secondary) 52%,
    var(--blue-400) 88%,
    var(--blue-500) 100%
  );
  box-shadow:
    0 0 0 1px var(--border-strong),
    0 2px 0 var(--blue-300),
    0 40px 70px -24px rgba(var(--ink-rgb), 0.46),
    0 10px 22px -8px rgba(var(--ink-rgb), 0.2),
    inset 0 -18px 40px rgba(var(--ink-rgb), 0.09),
    inset 0 8px 26px rgba(var(--white-rgb), 0.7);
  animation-name: plateSettle;
  animation-duration: 1ms;
  animation-fill-mode: both;
  animation-timing-function: linear;
}
/* the two rings that make it read as porcelain rather than a grey circle */
.hero-plate .plate::before,
.hero-plate .plate::after {
  content: '';
  position: absolute;
  border-radius: 50%;
}
.hero-plate .plate::before {
  inset: 6%;
  border: 1px solid var(--border-strong);
  box-shadow: inset 0 2px 5px rgba(var(--ink-rgb), 0.07);
}
.hero-plate .plate::after {
  inset: 24%;
  border: 1px solid var(--border);
}
@keyframes plateSettle {
  from {
    transform: rotateX(64deg) scale(0.9);
    opacity: 0.65;
  }
  to {
    transform: rotateX(52deg) scale(1);
    opacity: 1;
  }
}

/* ---- THE HALO ------------------------------------------------------------
   The ring of light that closes around the plate as the last dish lands. It
   is the first appearance of the intelligence gradient on the page, and it is
   held at zero for the first 62% of its own range so that it reads as a
   RESPONSE to the finished meal rather than as decoration that was always
   there. Border-box background plus an excluded padding-box mask is how a
   gradient becomes a 1.5px ring. */
.hero-plate .plate-halo {
  position: absolute;
  inset: 8% 4% 12%;
  border-radius: 50%;
  border: 1.5px solid transparent;
  background: var(--accent-cyan) border-box;
  -webkit-mask:
    linear-gradient(#000 0 0) padding-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  transform: rotateX(52deg);
  animation-name: haloIn;
  animation-duration: 1ms;
  animation-fill-mode: both;
  animation-timing-function: linear;
  pointer-events: none;
}
@keyframes haloIn {
  0%,
  62% {
    opacity: 0;
    transform: rotateX(52deg) scale(0.82);
  }
  100% {
    opacity: 0.85;
    transform: rotateX(52deg) scale(1);
  }
}

/* ---- THE FOOD ------------------------------------------------------------
   Six discs, upright, each carrying its own flight path in custom properties
   so that one keyframe animates all six along six different lines.

   --x/--y   where it lands, as a percentage of the stage
   --s       its diameter, same units
   --fx/--fy where it starts, in MULTIPLES OF ITS OWN WIDTH (see the header:
             the original's pixels do not survive a 350px phone stage)
   --fz      how far in front of the plate it starts, in px, because depth is
             measured against a fixed 1100px perspective and scaling it would
             change how deep the scene reads rather than how big it is */
.hero-plate .food {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--s, 21%);
  aspect-ratio: 1;
  margin: calc(var(--s, 21%) / -2) 0 0 calc(var(--s, 21%) / -2);
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  box-shadow:
    0 12px 26px -6px rgba(var(--ink-rgb), 0.34),
    inset 0 2px 6px rgba(var(--white-rgb), 0.42),
    inset 0 -8px 18px rgba(var(--shade-rgb), 0.14);
  animation-name: assemble;
  animation-duration: 1ms;
  animation-fill-mode: both;
  animation-timing-function: linear;
}
@keyframes assemble {
  from {
    opacity: 0;
    transform: translate3d(
        calc(var(--fx, 0) * 100%),
        calc(var(--fy, -0.4) * 100%),
        var(--fz, 220px)
      )
      scale(1.35);
    filter: blur(3px);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
    filter: blur(0);
  }
}

/* THE ARRANGEMENT, exactly the original's: six positions on a ring at 24%,
   50% and 76% across and 25.5%, 34.8%, 53.2% and 62.5% down. The flight
   vectors keep the original's directions and relative lengths; only the unit
   changed. Landing order runs clockwise from the top. */
.hero-plate .f1 {
  --x: 50%;
  --y: 25.5%;
  --fx: -0.92;
  --fy: -0.62;
  --fz: 260px;
  background-image: url('img/curry.jpg');
}
.hero-plate .f2 {
  --x: 76%;
  --y: 34.8%;
  --fx: 0.87;
  --fy: -0.72;
  --fz: 300px;
  background-image: url('img/biryani.jpg');
}
.hero-plate .f3 {
  --x: 76%;
  --y: 53.2%;
  --s: 18%;
  --fx: 1.32;
  --fy: -0.24;
  --fz: 220px;
  background-image: url('img/dosa.jpg');
}
.hero-plate .f4 {
  --x: 50%;
  --y: 62.5%;
  --s: 19%;
  --fx: 0.79;
  --fy: 0.85;
  --fz: 280px;
  background-image: url('img/south_breakfast.jpg');
}
.hero-plate .f5 {
  --x: 24%;
  --y: 53.2%;
  --s: 19%;
  --fx: -0.85;
  --fy: 0.91;
  --fz: 240px;
  background-image: url('img/balanced_plate.jpg');
}
.hero-plate .f6 {
  --x: 24%;
  --y: 34.8%;
  --s: 18%;
  --fx: -1.38;
  --fy: 0.12;
  --fz: 200px;
  background-image: url('img/ingredients_flatlay.jpg');
}

/* ---- THE CLOCK -----------------------------------------------------------
   A scroll-driven animation has no delay: the timeline IS the clock, so the
   stagger is expressed as a slice of the range rather than as animation-delay.
   The slices OVERLAP, which is why the plate is never momentarily still.

   Original range        x 0.567         here (of the plate's own entry)
   ---------------------------------------------------------------------
   plate, halo   0-62%                   42%   - 56%   of cover
   f1            4-30%                   46%   - 49.5%
   f2           10-36%                   47.4% - 50.9%
   f3           16-42%                   48.8% - 52.3%
   f4           22-48%                   50.2% - 53.7%
   f5           28-54%                   51.6% - 55.1%
   f6           34-60%                   53%   - 56.5%

   WHY COVER AND NOT ENTRY. Entry is exhausted the moment the subject is fully
   on screen, and stacked under the copy the plate is fully on screen at rest on
   anything as tall as a tablet: measured, 100% of entry spent before a pixel of
   scroll, six dishes already down. Cover keeps running from the first pixel in
   to the last pixel out, so there is somewhere left to put the sequence.

   WHY IT SITS AROUND THE MIDDLE OF COVER. Two bounds. Below: the passage
   already spent at rest, which is 43% on a 768x1024 tablet and 28% on a
   1440x900 laptop, so nothing may start before 43%. Above: the plate has to
   finish clear of the 77px header, which on a 1280x800 laptop is cover 58%.
   46%-56.5% is what fits between them, on every shape at once.

   WHY SCROLL AND NOT A TIMER, which the original settled and is worth keeping
   written down: a timer plays whether anyone is looking or not, at a speed
   nobody chose, once. Scroll progress means the person composes the plate at
   their own pace, and it runs backwards when they scroll back up. */
/* THE CLOCK IS THE PLATE'S OWN PASSAGE THROUGH THE VIEWPORT, everywhere.
   It used to be scroll(root block) over 0vh-20.7vh, measured from the top of
   the DOCUMENT, with a named view timeline kept in reserve for landscape
   phones. Two columns made that nearly true and stacking made it false: the
   plate now starts below the copy, so on a laptop the first dish landed while
   the plate was still under the fold and by the time it came up the meal was
   already made. "It only fills once I scroll past it" is the report, and the
   document-scroll clock is the whole reason.

   Tying it to the subject fixes it at every size at once, which is why the
   two-clock arrangement is gone rather than re-tuned: one shape cannot be
   right for a 900px laptop and a 390px landscape phone if it is measured from
   somewhere neither of them puts the plate. */
@supports (animation-timeline: view()) {
  .hero-plate .plate,
  .hero-plate .plate-halo,
  .hero-plate .food {
    animation-timeline: --heroplate;
    animation-duration: auto;
  }
  /* The same proportions the document clock carried, remapped onto the plate's
     entry. It starts at 12% and not at 0%: entry 0% is the instant the plate's
     first pixel crosses the fold, so an opening dish there lands on a sliver.
     It ends at 92% and not 100% so the last dish settles while the plate is
     properly on screen rather than in the frame where it stops entering. */
  /* THE FORMATION IS 30% SLOWER, AND SCROLLING IS NOT. This is a scroll-driven
     animation, so the only honest way to slow it is to spread the same
     keyframes over more of the passage: every offset below is its old distance
     from the 42% anchor multiplied by 1.3. Nothing else moved - same start,
     same six positions, same flight vectors, same clockwise order, same ending
     state. First dish begins at 47.2 instead of 46 and the last settles at
     60.85 instead of 56.5, so the stretch a reader actually watches goes from
     10.5% of the cover range to 13.65%: 1.30x.
     Touching animation-duration would have done nothing (it is `auto` under a
     view timeline), and touching the scroll container would have slowed the
     PAGE, which is the one thing this must not do. */
  .hero-plate .plate,
  .hero-plate .plate-halo {
    animation-range: cover 42% cover 60.2%;
  }
  .hero-plate .f1 {
    animation-range: cover 47.2% cover 51.75%;
  }
  .hero-plate .f2 {
    animation-range: cover 49.02% cover 53.57%;
  }
  .hero-plate .f3 {
    animation-range: cover 50.84% cover 55.39%;
  }
  .hero-plate .f4 {
    animation-range: cover 52.66% cover 57.21%;
  }
  .hero-plate .f5 {
    animation-range: cover 54.48% cover 59.03%;
  }
  .hero-plate .f6 {
    animation-range: cover 56.3% cover 60.85%;
  }
}

/* A SHORT VIEWPORT NEEDS ITS OWN CLOCK, and this is the one case where it
   genuinely does. The window above is bounded below by how much of the plate is
   already past at rest (a 768x1024 tablet has spent 40% of the passage before
   anyone touches anything) and above by the plate having to finish clear of the
   header. On a 844x390 landscape phone those two bounds cross: the plate cannot
   sit between a 71px header and a 390px fold at all at the wide sizing, so the
   figure is smaller there and the sequence runs over its ENTRY, which on that
   shape is barely begun at rest. */
@media (max-height: 560px) {
  @supports (animation-timeline: view()) {
    .hero-plate .plate,
    .hero-plate .plate-halo {
      animation-range: entry 30% entry 100%;
    }
    .hero-plate .f1 {
      animation-range: entry 34.5% entry 64%;
    }
    .hero-plate .f2 {
      animation-range: entry 41.3% entry 70.6%;
    }
    .hero-plate .f3 {
      animation-range: entry 48.1% entry 77.4%;
    }
    .hero-plate .f4 {
      animation-range: entry 54.8% entry 84.2%;
    }
    .hero-plate .f5 {
      animation-range: entry 61.6% entry 91%;
    }
    .hero-plate .f6 {
      animation-range: entry 68.4% entry 97.7%;
    }
  }
}

/* The named timeline the block above rides. Declared on the FIGURE so all six
   dishes share ONE subject: a bare view() resolves against each animated
   element's own box, and six dishes at six different heights would each get
   their own clock, putting the stagger in the layout instead of in the ranges.
   One subject, one clock, six slices. */
.hero-plate {
  view-timeline-name: --heroplate;
  view-timeline-axis: block;
}

/* ---- THE CAPTION ---------------------------------------------------------
   One honest line, so the picture states the promise rather than decorating
   it. It sits on the FIGURE and not in the stage, because the stage clips and
   this has to be readable.

   CENTRED WITH `translate`, NOT `transform`, and that is not a style choice.
   This element is .layer-front, and system.css parallaxes the scene's layers
   with `transform` - including a reduced-motion rule that resets them with
   `[data-scene] .layer-front { transform: none !important }`. That selector is
   0,2,0 and beats `.hero-note` at 0,1,0 even with !important on both, so the
   centring transform was thrown away and the caption sat 118px right of the
   plate for anyone who asked for less motion. Measured: note centre 1191
   against stage centre 1073 at 1440px. `translate` is a separate property that
   composes with `transform` instead of competing with it. */
.hero-note {
  position: absolute;
  /* the plate's box runs 12%-84% down, and rotateX(52deg) squashes it about
     its own centre to roughly 26%-70%, so 76% is just clear of the front rim
     rather than the 98% that a box-relative `bottom: -2%` would give. */
  top: 76%;
  left: 50%;
  translate: -50% 0;
  white-space: nowrap;
  font-size: var(--text-caption);
  font-weight: 650;
  color: var(--foreground-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 7px 14px;
  box-shadow: var(--shadow-sm);
  animation: noteRise 700ms var(--ease-out) 1500ms both;
}
@keyframes noteRise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 900px) {
  .hero-plate {
    /* bigger than the 380px it was given when it shared a two-column fold with
       the copy: a taller figure is also LESS ENTERED at rest, which is what
       leaves the clock below somewhere to run on a tablet */
    width: min(92%, 480px);
  }
}
/* ...except where there is no height to spend. A 480px figure needs 417px of
   room below a 71px header and a landscape phone has 390px of viewport, so the
   plate would sit half under the navigation however the clock is tuned. */
@media (max-height: 560px) {
  .hero-plate {
    width: min(60vh, 380px);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* KILL THE TIMELINE, not the duration. Once an animation has a scroll
     timeline its declared duration is ignored, so system.css's global 0.001ms
     clamp does nothing here: measured with this rule removed, every dish sat
     permanently mid-flight and the plate never assembled. Setting the timeline
     to none hands them all back to the 1ms fallback, which fills to the END
     state - an assembled plate, immediately, which is what "reduce" means.
     It has to be the timeline and not `transform: none`, because the plate's
     rest state IS a transform (rotateX(52deg)) and the halo's is opacity. */
  .hero-plate .plate,
  .hero-plate .plate-halo,
  .hero-plate .food,
  .hero-plate {
    animation-timeline: none !important;
  }
  .hero-plate {
    animation: none !important;
    transform: none !important;
    translate: none !important;
    scale: none !important;
  }
  /* the caption is centred by `translate`, which nothing here touches, so it
     only needs its entrance stopped */
  .hero-note {
    animation: none !important;
    opacity: 1 !important;
  }
}
