/* ═══ Nkwata · Camera movements ═════════════════════════════════════════
   One scroll gesture, three shots, and it runs backwards.

   What the visitor sees, going down:

     1  A picture sits inside its frame, with the caption, the price and
        the quarter around it. You can see where it is.
     2  The camera pushes in. The frame, the caption and the background
        drain away until the picture is the only thing left and it has
        grown past the edges of the screen. This is the moment the room
        is the whole world.
     3  The camera pulls back out — and what was one picture turns out
        to be one of three. Two more slide in beside it. The row settles.

   Going up, the same three shots play in reverse, exactly. That is not
   extra work: a scroll-driven animation is a position on a timeline, not
   an event that fired. Scrolling back up is simply scrubbing backwards.
   Anything built on "when this becomes visible, play" cannot do that.

   Built on view-timeline so the browser runs it off the main thread.
   Only transform and opacity move, so it stays smooth on the cheap
   Android phones most of our visitors are holding. Firefox has no
   scroll-driven animations yet, so 13-camera.js drives the same numbers
   there through a single custom property. Everything below reads that
   property in the fallback branch, so there is one set of numbers, not
   two implementations that can drift apart.

   Nothing here is load-bearing. Strip every rule in this file and the
   page still reads, still scrolls, still works.
   ═══════════════════════════════════════════════════════════════════ */

/* Progress through the shot, 0 to 1. Registered so it is a real number
   the fallback can calculate with rather than a string. */
@property --cam-p {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

/* ── The track ──────────────────────────────────────────────────────
   The section is tall. The stage inside it is pinned. The height is
   how long the shot lasts: 300vh is roughly three screens of scrolling
   for the whole move, which lands close to the pace of the page it was
   taken from. */
.cam {
  position: relative;
  height: 300vh;
  view-timeline: --cam block;
  overflow: clip;
}
.cam--short { height: 230vh; }
.cam--long  { height: 380vh; }

.cam-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  place-items: center;
  overflow: clip;
  background: var(--walnut);
  color: var(--on-dark);
}

/* The room behind the subject: texture, gradient, nothing to read. */
.cam-bg {
  position: absolute;
  inset: -8%;
  background:
    radial-gradient(120% 90% at 50% 15%, rgba(176,141,87,.22), transparent 62%),
    linear-gradient(180deg, var(--walnut-2), var(--espresso));
  will-change: transform, opacity;
}

/* The row the three panels live in. One column at the start because
   there is only one panel to see; three by the end. */
.cam-row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  place-items: center;
  width: min(1240px, 94vw);
}

.cam-panel {
  position: relative;
  grid-area: 1 / 1;
  width: min(760px, 86vw);
  aspect-ratio: 16 / 10;
  border-radius: var(--r-l);
  overflow: clip;
  box-shadow: var(--sh-3);
  background: var(--walnut-3);
  will-change: transform, opacity;
  transform-origin: 50% 50%;
}
.cam-panel img,
.cam-panel video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* The two that are not there yet. */
.cam-panel--left,
.cam-panel--right { z-index: 1; }
.cam-panel--hero  { z-index: 2; }

/* Everything around the subject — the frame, the caption, the price.
   It is present at the start, gone at the height of the push-in, and
   back by the time the row settles. */
.cam-around {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* The site header is fixed and sits over the stage, so the eyebrow and
     the title start below it rather than underneath it. */
  padding: clamp(84px, 11vh, 108px) clamp(18px, 4vw, 48px) clamp(18px, 4vw, 48px);
  pointer-events: none;
  z-index: 3;
  will-change: opacity, transform;
}
.cam-around > * { pointer-events: auto; }

.cam-eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--brass-2);
}
.cam-title {
  font-family: var(--display);
  font-size: clamp(26px, 4.6vw, 54px);
  line-height: 1.02;
  max-width: 16ch;
  color: var(--on-dark);
}
.cam-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  align-items: baseline;
  justify-content: space-between;
  color: var(--on-dark-2);
  font-size: 14px;
}
.cam-foot b { color: var(--on-dark); font-weight: 700; }

/* The haze at the top of the push-in. An overlay rather than a blur
   filter on the picture, because a filter re-rasterises the image on
   every frame and a gradient does not. */
.cam-haze {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 55% at 50% 50%, transparent 25%, rgba(25,19,16,.55) 78%),
    linear-gradient(180deg, rgba(25,19,16,.35), transparent 35%, rgba(25,19,16,.45));
  will-change: opacity;
}

/* A thin rule that draws itself as the row settles — the shot has a
   full stop. */
.cam-rule {
  position: absolute;
  left: 50%; bottom: clamp(14px, 3vh, 34px);
  width: min(560px, 70vw);
  height: 1px;
  transform: translateX(-50%) scaleX(0);
  transform-origin: 50% 50%;
  background: linear-gradient(90deg, transparent, var(--brass-soft), transparent);
  z-index: 3;
}

/* ══════════════════════════════════════════════════════════════════
   BRANCH ONE — browsers with scroll-driven animations.
   Chrome, Edge and Safari 26 run every keyframe below on the
   compositor. Nothing in JavaScript touches these.
   ══════════════════════════════════════════════════════════════════ */
@supports ((animation-timeline: view()) and (animation-range: entry)) {

  /* The subject. Starts held back inside its frame, is pushed past the
     edges of the screen, then pulled back to take its place in the row.
     The three stops are the three shots. */
  /* The subject never leaves the middle of the frame. It is the camera
     that moves, not the room — which is the whole difference between
     this and a carousel. */
  @keyframes cam-hero {
    0%   { transform: scale(.60); }
    40%  { transform: scale(1.34); }
    72%  { transform: scale(.70); }
    100% { transform: scale(.52); }
  }

  /* The two that were always there and could not be seen. They do not
     fade in from nowhere: they slide in from beyond the frame, which is
     what makes it read as the camera pulling back rather than as two
     pictures appearing. */
  @keyframes cam-left {
    0%, 52% { transform: scale(.70) translateX(-10%); opacity: 0; }
    72%     { transform: scale(.62) translateX(-70%); opacity: .5; }
    100%    { transform: scale(.52) translateX(-104%); opacity: 1; }
  }
  @keyframes cam-right {
    0%, 52% { transform: scale(.70) translateX(10%); opacity: 0; }
    72%     { transform: scale(.62) translateX(70%); opacity: .5; }
    100%    { transform: scale(.52) translateX(104%); opacity: 1; }
  }

  @keyframes cam-around {
    0%   { opacity: 1; transform: translateY(0) scale(1); }
    26%  { opacity: 0; transform: translateY(-14px) scale(.98); }
    74%  { opacity: 0; transform: translateY(10px) scale(.98); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }

  @keyframes cam-bg {
    0%   { opacity: 1;  transform: scale(1); }
    42%  { opacity: .25; transform: scale(1.16); }
    100% { opacity: 1;  transform: scale(1); }
  }

  @keyframes cam-haze {
    0%, 14% { opacity: 0; }
    44%     { opacity: 1; }
    66%     { opacity: 0; }
    100%    { opacity: 0; }
  }

  @keyframes cam-rule {
    0%, 78% { transform: translateX(-50%) scaleX(0); }
    100%    { transform: translateX(-50%) scaleX(1); }
  }

  /* `contain` is the stretch of the timeline while the tall section
     completely covers the viewport — which is exactly the stretch while
     the stage is pinned. Using `cover` instead would start the move
     while the section was still sliding up, and the first shot would be
     half over before the stage held still. */
  .cam-panel--hero,
  .cam-panel--left,
  .cam-panel--right,
  .cam-around,
  .cam-bg,
  .cam-haze,
  .cam-rule {
    animation-timeline: --cam;
    animation-range: contain 0% contain 100%;
    animation-timing-function: linear;
    animation-fill-mode: both;
  }

  .cam-panel--hero  { animation-name: cam-hero; }
  .cam-panel--left  { animation-name: cam-left; }
  .cam-panel--right { animation-name: cam-right; }
  .cam-around       { animation-name: cam-around; }
  .cam-bg           { animation-name: cam-bg; }
  .cam-haze         { animation-name: cam-haze; }
  .cam-rule         { animation-name: cam-rule; }

  /* A section with only one picture still gets the push-in and the
     pull-back; it just has nothing to reveal. */
  .cam--solo .cam-panel--hero { animation-name: cam-hero-solo; }
  @keyframes cam-hero-solo {
    0%   { transform: scale(.60); }
    42%  { transform: scale(1.32); }
    100% { transform: scale(.88); }
  }
}

/* ══════════════════════════════════════════════════════════════════
   BRANCH TWO — Firefox, and anything older.
   13-camera.js writes one number, --cam-p, onto the section as it
   scrolls. Every transform below is that number in an expression, so
   the numbers are the same numbers and the two branches cannot drift.
   If the script never runs, --cam-p stays at 0 and the first shot is
   simply what the visitor sees. Nothing breaks, nothing is hidden.
   ══════════════════════════════════════════════════════════════════ */
@supports not ((animation-timeline: view()) and (animation-range: entry)) {

  .cam-panel--hero {
    /* .60 → 1.34 over the first 40%, then back down to .52 */
    transform: scale(calc(
      0.60
      + 0.74 * min(var(--cam-p) / 0.40, 1)
      - 0.82 * clamp(0, (var(--cam-p) - 0.40) / 0.60, 1)
    ));
  }
  .cam--solo .cam-panel--hero {
    transform: scale(calc(
      0.60 + 0.72 * min(var(--cam-p) / 0.42, 1)
      - 0.44 * clamp(0, (var(--cam-p) - 0.42) / 0.58, 1)));
  }

  .cam-panel--left {
    opacity: clamp(0, (var(--cam-p) - 0.52) / 0.26, 1);
    transform:
      scale(calc(0.70 - 0.18 * clamp(0, (var(--cam-p) - 0.52) / 0.48, 1)))
      translateX(calc(-10% - 94% * clamp(0, (var(--cam-p) - 0.52) / 0.48, 1)));
  }
  .cam-panel--right {
    opacity: clamp(0, (var(--cam-p) - 0.52) / 0.26, 1);
    transform:
      scale(calc(0.70 - 0.18 * clamp(0, (var(--cam-p) - 0.52) / 0.48, 1)))
      translateX(calc(10% + 94% * clamp(0, (var(--cam-p) - 0.52) / 0.48, 1)));
  }

  .cam-around {
    opacity: calc(
      1 - clamp(0, var(--cam-p) / 0.26, 1)
        + clamp(0, (var(--cam-p) - 0.74) / 0.26, 1));
  }
  .cam-bg {
    opacity: calc(1 - 0.75 * (1 - abs(calc(var(--cam-p) - 0.42)) / 0.58));
  }
  .cam-haze {
    opacity: calc(
      clamp(0, (var(--cam-p) - 0.14) / 0.30, 1)
      - clamp(0, (var(--cam-p) - 0.44) / 0.22, 1));
  }
  .cam-rule {
    transform: translateX(-50%)
      scaleX(clamp(0, (var(--cam-p) - 0.78) / 0.22, 1));
  }
}

/* ══════════════════════════════════════════════════════════════════
   Reduced motion, small screens, and the printed page.

   A visitor who has asked their phone for less movement gets the last
   shot and nothing else: three pictures, side by side, still. They are
   not shown a worse page — they are shown the end of the shot, which is
   the part that carries the information.
   ══════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .cam { height: auto; }
  .cam-stage { position: static; height: auto; padding: 48px 0; }
  .cam-panel,
  .cam-around,
  .cam-bg,
  .cam-haze,
  .cam-rule {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .cam-haze { display: none; }
  .cam-row {
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
  }
  .cam-panel { grid-area: auto; width: 100%; }
  .cam-around { position: static; padding: 24px 0 0; }
}

/* Below 640px there is no room for three panels side by side, so the
   shot keeps the push-in and the pull-back and drops the reveal. */
@media (max-width: 640px) {
  .cam { height: 220vh; }
  .cam-panel--left,
  .cam-panel--right { display: none; }
  .cam-panel { width: 92vw; }
}

@media print {
  .cam { height: auto; }
  .cam-stage { position: static; height: auto; }
  .cam-panel { animation: none; transform: none; }
  .cam-panel--left, .cam-panel--right { display: none; }
}
