/* =====================================================================
   Core / reel.css — the Projects reel
   ---------------------------------------------------------------------
   Loaded by projects.html ONLY, after core/site.css and core/resn.css.

   The page is a stage rather than a document, and it is the only thing
   on the route: one project at a time, held in the middle of the
   viewport, with the names of the projects on either side bleeding in
   from the edges. Moving sideways moves the names; the picture only
   exists while you are standing still. Past the last project comes the
   first one again — the reel is a ring and has no end to reach.

   One slide per project, each placed by core/reel.js at its own shortest
   signed distance from the middle. Inside a slide, ONE element carries
   the turn (.reel-turn) with the plate and the name both inside it, so a
   name leans with its picture instead of sitting flat over a card that
   is leaning away.

   WHAT TAKES THE POINTER. The middle slide's plate does, because there
   it means "open this". Every other slide's NAME does, because there it
   means "come here". Nothing else on the stage is clickable, which is
   why the two never have to be told apart.

   TYPE. The same faces as the Studio page, which is the other half of
   this site's work: Outfit extralight in capitals for the project names
   (--font-title / --wt-title, at the tracking .studio-title uses), and
   Outfit semibold, small and widely tracked, for the line under them.

   Everything with a number in it is written onto the reel as a custom
   property by CONFIG in core/reel.js — edit the values there, not here.
   The rules below only consume:

     --reel-card-w / --reel-card-h   the size of the plate
     --reel-pitch                    distance between two projects
     --reel-scale                    live scale of the plate
     --reel-scale-ms                 how long the plate takes to get there
     --reel-in-ms / --reel-out-ms    picture fade in / out

   The per-slide offset and opacity are written straight onto the
   elements by the same file, once a frame.

   The one block of numbers that is NOT reel.js's is the cover treatment
   at the foot — see THE PLATE IS A SHEET OF GLASS. Those are the
   --cover-* tokens in core/site.css, the same ones the Studio rack's
   panels are drawn with, and /covers is where a visitor sets them.
===================================================================== */

/* ---------- the page becomes a stage ----------------------------------
   `reel-stage` is on <body> in the markup rather than added by script:
   the reel is the whole route, so the page must never be scrollable,
   not even for the moment before the module runs. project.html carries
   the same .resn language and is an ordinary scrolling document, which
   is why this is a class and not a rule on the page type. */
body.reel-stage{overflow:hidden}
body.reel-stage .page.page-projects{
  max-width:none;
  width:100%;
  margin:0;
  padding:0;
  height:100vh;
  height:100svh;
  overflow:hidden;
}

/* ---------- the reel -------------------------------------------------- */
.reel{
  position:absolute;inset:0;
  /* clip, not hidden: `hidden` leaves this a scroll container, and the
     browser will scroll it sideways to bring a tabbed-to project into
     view — which shunts the whole reel out of alignment. `clip` has no
     scroll port to move. The reel decides where it is. */
  overflow:hidden;
  overflow:clip;
  touch-action:pan-y;
  -webkit-user-select:none;user-select:none;
}

/* preserve-3d so the slides' own perspective() transforms compose into
   one space rather than each being flattened into its own plane. The
   perspective itself is written per slide by core/reel.js, NOT here: a
   `perspective` on this element would make it a stacking context, and
   the marks at the foot of the stage are its siblings. */
.reel-track{position:absolute;inset:0;transform-style:preserve-3d}

/* One project. Centred by left:50% and half its own width back, so the
   only thing left for core/reel.js to write is the offset — which is
   what lets a slide be moved from one end of the ring to the other
   without any of the centring having to be recomputed. */
.reel-slide{
  position:absolute;
  left:50%;top:50%;
  width:var(--reel-card-w);height:var(--reel-card-h);
  margin-left:calc(var(--reel-card-w) / -2);
  transform:translate3d(0,-50%,0);
  will-change:transform;
  pointer-events:none;
}
/* where the ring closes: a slide on its way round is not paintable */
.reel-slide.is-gone{visibility:hidden}
.reel-slide.is-current{z-index:2}

/* What actually turns (core/tilt.js). Nothing in this file may set
   `transform` on it. */
.reel-turn{
  position:absolute;inset:0;
  transform-style:preserve-3d;
}

/* ---------- the plate ------------------------------------------------- */
.reel-plate{
  position:absolute;inset:0;
  display:block;
  overflow:hidden;
  background:var(--r-bg-2);
  opacity:0;
  transform:scale(var(--reel-scale,1));
  transition:opacity var(--reel-out-ms,150ms) linear,
             transform var(--reel-scale-ms,780ms) var(--r-ease);
  will-change:transform;
  /* THE PLATE CONTAINS ITS OWN LIGHT — see THE PLATE IS A SHEET OF GLASS
     at the foot of this file. Without it the gloss's `screen` blend
     reaches past the picture, and what is behind this page is a live
     shader. */
  isolation:isolate;
}
/* only the middle one is openable */
.reel-slide.is-current .reel-plate{pointer-events:auto}

/* The three cycled gradients that stand in for a cover are at the foot
   of this file with the rest of the glass — they carry --cover-ground,
   so they belong with it. */

/* on = the reel is standing still, so the picture is up */
.reel-plate.is-on{
  opacity:1;
  transition:opacity var(--reel-in-ms,620ms) var(--r-ease),
             transform var(--reel-scale-ms,780ms) var(--r-ease);
}

.reel-plate img,.reel-plate video{
  position:absolute;inset:0;
  width:100%;height:100%;
  opacity:0;
  transition:opacity var(--r-slow) ease-out;
}

/* ---------- the name --------------------------------------------------
   A button, because on every slide but the middle one it is what walks
   the reel to that project. The padding is there to clear a 44px touch
   target without changing where the name sits: it grows symmetrically
   around a box that is centred by its own transform. */
.reel-name{
  position:absolute;z-index:2;
  left:50%;top:50%;
  transform:translate(-50%,-50%);
  width:max-content;
  margin:0;padding:12px 18px;
  border:0;background:none;
  text-align:center;
  color:var(--r-white);
  cursor:pointer;
  pointer-events:auto;
  transition:color var(--r-mid) var(--r-ease);
}
/* the middle name steps aside so the click reaches the plate under it */
.reel-slide.is-current .reel-name{pointer-events:none;cursor:default}
/* ...and no slide takes the pointer at all while the stage is being
   dragged: by then the gesture is about the reel, not about a card */
.reel.is-dragging .reel-name{pointer-events:none}
.reel-name:focus-visible{outline:1px solid var(--r-accent);outline-offset:6px}

.reel-word{
  display:block;
  font-family:var(--font-title);
  font-size:var(--reel-title,32px);
  font-weight:var(--wt-title);
  line-height:1.04;
  /* capitals need the tracking opened — the same setting .studio-title
     uses, so the two pages' headings are one voice */
  letter-spacing:.055em;
  text-transform:uppercase;
  white-space:nowrap;
}
/* What the piece is, in the studio's own words. It belongs to the
   project you are LOOKING at, so it is held back until the pointer is on
   the plate in the middle. Aiming at a neighbour's name is a different
   question — "which one is that?" — and the answer to it is the name
   lighting up, nothing more. */
.reel-cap{
  position:absolute;left:50%;top:calc(100% - 4px);
  transform:translate(-50%,4px);
  display:block;white-space:nowrap;
  font-family:var(--font-title);
  font-size:11px;font-weight:600;
  letter-spacing:.13em;text-transform:uppercase;
  color:var(--r-text);
  opacity:0;
  transition:opacity var(--r-mid) var(--r-ease),transform var(--r-mid) var(--r-ease);
}
.reel.is-hovered .reel-slide.is-current .reel-cap{opacity:1;transform:translate(-50%,0)}
/* There is no hover on a phone to ask the question with, so the answer
   is given to whichever project has ARRIVED: the caption comes up on the
   middle card once the reel has parked, and goes again the moment it
   moves. Being in the middle is the gesture. */
@media (hover:none){
  .reel:not(.is-moving) .reel-slide.is-current .reel-cap{opacity:1;transform:translate(-50%,0)}
}

/* ---------- the two marks ---------------------------------------------
   The A and the V of the VAL logo as CSS masks, laid on their sides: the
   A is a bare apex, so a quarter turn anticlockwise points it left, and
   the V, drawn pointing down, points right. Same marks the Studio rack
   pages with, turned through 90 degrees because this reel moves
   sideways. They replace a drawn arrow — the site has its own.

   Small, low, and with nothing between them. They sit at the foot of the
   stage as a pair of marks rather than as a device: the reel is what the
   page is about, and two small glyphs are as much instruction as it
   needs. The 44px boxes are the touch targets, invisible around marks a
   third of that — the same trick the corner chrome uses in
   core/site.css, so the glyphs stay small without the targets going
   under a fingertip. */
.reel-nav{
  position:absolute;z-index:3;
  left:50%;bottom:clamp(14px,3.4vh,40px);
  transform:translateX(-50%);
  display:flex;align-items:center;gap:18px;
}
.reel-step{
  width:44px;height:44px;             /* the touch target, not the mark */
  display:grid;place-items:center;
  margin:0;padding:0;border:0;background:none;
  color:var(--r-gray-4);
  cursor:pointer;
  pointer-events:auto;
  transition:color var(--r-mid) var(--r-ease);
}
.reel-step:hover,.reel-step:focus-visible{color:var(--r-white)}
.reel-step:focus-visible{outline:1px solid var(--r-accent);outline-offset:2px}
.reel-mark{
  display:block;width:12px;height:13px;background:currentColor;
  transform:rotate(-90deg);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
.reel-mark.a{-webkit-mask-image:url(/assets/val-a.svg);mask-image:url(/assets/val-a.svg)}
.reel-mark.v{-webkit-mask-image:url(/assets/val-v.svg);mask-image:url(/assets/val-v.svg)}

/* ---------- states ---------------------------------------------------- */
.reel-status{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  margin:0;max-width:34ch;text-align:center;
  font-size:var(--r-fs-lede);font-weight:300;color:var(--r-gray);
}

.reel-vh{
  position:absolute;width:1px;height:1px;
  margin:-1px;padding:0;border:0;
  clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}

/* ---------- narrow ---------------------------------------------------- */
@media (max-width:900px){
  .reel-nav{bottom:clamp(10px,2.4vh,24px)}
}

/* =====================================================================
   THE PLATE IS A SHEET OF GLASS
   ---------------------------------------------------------------------
   The same treatment the Studio rack gives a cover, given to the reel's
   plate — because they are the same thing seen on two pages, and a cover
   that is dark glass in the rack and a flat printed card here would read
   as two different sites.

   NOT ONE NUMBER IS STATED HERE. Every value is a --cover-* custom
   property declared once in core/site.css's token block, which
   core/covers.js then writes the visitor's own over on :root — so
   /covers tunes this page as well as the rack, from the same sliders,
   and the two cannot drift apart.

   Four layers, back to front:

     the backing   the plate's own dark gradient, at --cover-ground.
                   Below 1 the glass sheet running behind the page comes
                   through it, which is what makes a cover read as a
                   window onto the same material the rest of the site is
                   made of rather than as a card lying on top of it.
     the picture   set back a little — --cover-opacity, with a touch of
                   saturation taken out, the way glass takes colour out.
     the veil      a wash of the page's ground over the picture, carrying
                   with it the pool of shade the reel has always had
                   behind its names. Half of this studio's covers are
                   dense TouchDesigner screenshots and a name has to
                   survive them, so the two are one layer: the wash is
                   flat, the pool is the gradient over it.
     the gloss     one highlight, which TRAVELS. --tilt-x and --tilt-y are
                   written by core/tilt.js off the same spring that turns
                   the plate (GLINT, switched on in core/reel.js's
                   CONFIG.TILT), so the reflection moves with the surface
                   rather than after it.

   IT IS A BACKGROUND THAT MOVES, NOT A BOX THAT MOVES. The gloss is
   pinned at inset:0 and never given a size or a transform of its own;
   what travels is `background-position` over a gradient drawn wider than
   its box. A transformed, oversized child with a blend mode inside a
   preserve-3d subtree is exactly the case where the parent's overflow
   clip stops being applied — the highlight walks off the edge of the
   picture and, because its transform is a main-thread write while the
   plate's rotation is composited, arrives a frame or two late. The long
   version of this is in core/site.css under THE GLOSS.
===================================================================== */

/* The backing. Three gradients, cycled, so a project still waiting on
   its cover reads as a deliberate dark plate instead of a hole — and at
   --cover-ground below 1 the shader behind the page comes through them.
   The same three the rack uses, with the same alphas. */
.reel-slide:nth-child(3n+1) .reel-plate{
  background:linear-gradient(160deg,
    rgba(18,21,26,var(--cover-ground)) 0%,
    rgba(5,7,10,var(--cover-ground)) 72%);
}
.reel-slide:nth-child(3n+2) .reel-plate{
  background:radial-gradient(120% 130% at 18% 12%,
    rgba(23,27,33,var(--cover-ground)) 0%,
    rgba(5,7,10,var(--cover-ground)) 68%);
}
.reel-slide:nth-child(3n+3) .reel-plate{
  background:linear-gradient(200deg,
    rgba(15,18,22,var(--cover-ground)) 0%,
    rgba(6,8,11,var(--cover-ground)) 55%,
    rgba(18,22,27,var(--cover-ground)) 100%);
}

/* The picture, set back under the glass. */
.reel-plate img,.reel-plate video{
  object-fit:var(--cover-fit);
  filter:saturate(var(--cover-sat)) blur(var(--cover-blur));
}
.reel-plate img.is-loaded,.reel-plate video.is-loaded{opacity:var(--cover-opacity)}

/* The veil, and the pool of shade behind the name inside it. Both inert
   to the pointer: the plate is one link and neither may take a press off
   it. */
.reel-plate::before,
.reel-plate::after{
  content:"";position:absolute;inset:0;
  pointer-events:none;
}
.reel-plate::before{
  z-index:3;
  background-color:rgba(5,6,7,var(--cover-veil));
  background-image:radial-gradient(64% 62% at 50% 50%,
    rgba(0,0,0,.68) 0%,rgba(0,0,0,.36) 46%,rgba(0,0,0,0) 100%);
}

/* The gloss. */
.reel-plate::after{
  z-index:4;
  mix-blend-mode:screen;
  opacity:calc(1 + var(--tilt-lift, 0) * .5);
  transition:opacity var(--r-mid) var(--r-ease);
  background-repeat:no-repeat;
  background-size:100% 100%, 260% 170%;
  background-position:
    0 0,
    calc(50% - var(--tilt-x, 0) * var(--cover-glide))
    calc(50% + var(--tilt-y, 0) * var(--cover-glide) * .4);
  background-image:
    /* the lit top edge, which is what gives the plate a thickness */
    linear-gradient(to bottom,
      rgba(255,243,230,var(--cover-rim)) 0,
      rgba(255,243,230,0) 1px),
    /* the highlight itself */
    linear-gradient(var(--cover-angle),
      rgba(255,255,255,0)                              calc(50% - var(--cover-spread)),
      rgba(255,243,230,var(--cover-gloss))             calc(50% - var(--cover-spread) * .28),
      rgba(216,224,231,calc(var(--cover-gloss) * .45)) calc(50% + var(--cover-spread) * .12),
      rgba(255,255,255,0)                              calc(50% + var(--cover-spread)));
}

/* A phone has no hover, so a plate at rest never catches the light at
   all — and the reel's cover would read as flat until something was
   pressed. The lit edge stays; the highlight is parked just off centre
   so every project is caught at the same angle. */
@media (hover: none){
  .reel-plate::after{
    background-position:0 0, calc(50% + var(--cover-glide) * .55) 50%;
  }
}

/* ---------- reduced motion --------------------------------------------
   The reel still works — it just cuts between projects instead of
   gliding, and the picture never leaves. */
@media (prefers-reduced-motion:reduce){
  .reel-plate,.reel-cap{transition-duration:.01ms!important}
  .reel-slide.is-current .reel-plate{opacity:1}
}

/* ---------- held back — `incoming: true` in content/projects.json -------
   A project that exists and has no page yet. It keeps its place in the
   reel, its cover and its order; what it loses is its own address —
   the plate carries no href at all while this is set (core/reel.js).

   It has to read as DELIBERATE rather than as a cover that failed to
   load, and there are only two moves in it: the picture sits further
   back under the veil, and one word says why. Nothing dims the name —
   the reader still has to be able to read what is coming. */
.reel-slide[data-incoming] .reel-plate img.is-loaded,
.reel-slide[data-incoming] .reel-plate video.is-loaded{
  opacity:calc(var(--cover-opacity) * .5);
  filter:saturate(calc(var(--cover-sat) * .55)) blur(var(--cover-blur));
}

.reel-badge{
  position:absolute;z-index:5;
  left:var(--r-fs-body);top:var(--r-fs-body);
  padding:.5em .85em .45em;
  font-size:var(--r-fs-label);
  letter-spacing:var(--r-track-label);
  text-transform:uppercase;
  color:var(--r-accent);
  border:1px solid currentColor;
  /* the plate is one link; the badge may not take a press off it */
  pointer-events:none;
}

@media (max-width:760px){
  .reel-badge{left:12px;top:12px;padding:.45em .7em .4em}
}
