/* ============================================================
   BAC · preload.css — the preload (single definition)
   Rules: docs/motion-language.md + design-rules § 9.
   Reference: References/preload example/ (35-frame capture).
   Cut and measured in site/_preload-lab.html.

   THE ARGUMENT. The page arrives under a sheet of paper. The
   count is the only thing on it, and it builds itself — each
   square rolling out of the one before it — and then the sheet
   swings off its pivot and the page is under it, already whole.
   Nothing is "revealed" by the preload: the page was there.
   That is the difference between this and a curtain.

   STATIC-FIRST, structurally: the overlay is INJECTED by
   preload.js and every rule here is scoped under .pl-on. With
   JS off, under prefers-reduced-motion, or on any visit after
   the first in a session, there is no overlay in the document
   at all and the page renders immediately.

   ⚠ IT DOES NOT ANIMATE THE PAGE. The headline reveal, the
   card lift, the drawn rules — all of that is the page's own
   load-in and it stays the page's. This file paints one panel
   and takes it away. preload.js fires `bac:preload-done` so the
   page can start its own motion after the panel has gone rather
   than behind it.

   ⚠ A preload is the ONE motion on the site that spends the
   reader's time rather than decorating time they were already
   spending. Budgeted at 3s + a 1450ms wipe, once per session,
   and skippable from the first frame — see preload.js.
   ============================================================ */

/* ⚠ NO :root HERE. Every value below lives in tokens/tokens.css
   beside --ld-k, and is read with a FALLBACK so this sheet still
   works on a page that does not link the spine — index-v4 is
   exactly that page. A module that declares its own :root gives
   a token two homes, which is the check.py C1 failure and is how
   loadin.css was bitten before it. */


/* the page underneath is held still only while the panel is up */
/* ⚠ THE SCROLLBAR IS NEVER REMOVED, AND THAT IS THE WHOLE RULE.
   Two goes at this, and the first was only half right.

   The lock was `html.pl-on{overflow:hidden}`, which deletes the
   scrollbar for as long as the panel is up. Measured on index-v6:
   clientWidth 1425 normally, 1440 while locked — so when the lock
   lifted every centred element slid 7.5px left, exactly as the hero
   was arriving.

   scrollbar-gutter:stable fixed the SHIFT and left a second, subtler
   fault behind: the gutter renders as an empty track while locked
   and a real scrollbar with a thumb once released, so the furniture
   still visibly changed at the handoff — "a weird looking scrollbar
   to the default".

   So the scrollbar is simply always there. overflow-y:scroll on the
   root forces it whether the page scrolls or not, and the lock no
   longer touches the root's overflow at all. Nothing appears,
   disappears or changes width across the whole sequence.

   ⚠ AND THE PAGE IS STILL HELD. The panel is position:fixed and
   covers the viewport, so anything scrolling underneath it is
   invisible — and the module resolves on the first wheel, touch,
   key or click regardless, so a reader who scrolls has not escaped
   the lock, they have ended it, which is the behaviour doctrine
   asks for anyway. */
html{ overflow-y:scroll; }

/* ⚠ AND LENIS HAS TO BE OVERRIDDEN, OR THE RULE ABOVE LOSES.
   Read from the source (darkroomengineering/lenis, packages/core):
   lenis.stop() sets isStopped, which puts `lenis-stopped` on the
   root, and lenis.css carries

       .lenis:not(.lenis-autoToggle).lenis-stopped{ overflow:clip }

   That selector is (0,3,0) against `html` at (0,0,1), so Lenis wins
   and the scrollbar disappears anyway — which is why the change
   still showed after the overflow was taken off the preload's own
   lock. motion/smooth.js calls lenis.stop() for the preload, so
   this fires on every load, not in some edge case.

   Matching Lenis's specificity and restoring the axis is the whole
   fix. `overflow-x` is left clipped so nothing can scroll sideways;
   only the vertical scrollbar is kept, and kept ALWAYS.

   ⚠ THE PAGE IS STILL HELD. Lenis is stopped, so its smooth scroll
   does nothing; the panel is fixed and covers the viewport; and the
   module resolves on the first wheel, touch, key or click. A reader
   who drags the scrollbar has ended the preload, not escaped it.

   ⚠ NOT autoToggle. Lenis's autoToggle option writes overflow as an
   INLINE style and transitions it with allow-discrete — inline
   beats any stylesheet, so adopting it would put this back exactly
   where it started. The class path is the one that can be
   overridden, so the class path is the one to use. */
/* ⚠ THE :not() COUNTS, AND THAT IS WHY THE FIRST ATTEMPT LOST.
   Lenis's rule is `.lenis:not(.lenis-autoToggle).lenis-stopped` —
   three classes, (0,3,0), because :not() contributes its argument's
   specificity. Written as `html.lenis.lenis-stopped` this was
   (0,2,1): two classes and an element, which loses to three classes
   however it reads. Measured: overflow-y still computed `clip` and
   the headline still moved 542 → 548.

   Mirroring the :not() makes it (0,3,1) and it wins on the class
   count with the element to spare. */
html.lenis.lenis-stopped:not(.lenis-autoToggle){
  overflow-x:clip; overflow-y:scroll;
}
html.pl-on body{ overflow:hidden; }

/* ── THE PANEL ─────────────────────────────────────────────
   A square whose BOTTOM-LEFT CORNER is the pivot, reaching up
   and to the right far enough to cover everything.
   transform-origin sits on that corner, so one rotation swings
   the whole thing and its left edge is the wipe. Nothing
   translates — the edge's angle at any instant IS the swing,
   which is why a slide cannot stand in for it. */
.pl{
  position:fixed; z-index:9999;
  left:var(--pl-px,-6vw); top:calc(var(--pl-py,106vh) - var(--pl-side,260vmax));
  width:var(--pl-side,260vmax); height:var(--pl-side,260vmax);
  /* ⚠ --pl-bg / --pl-fg, DEFAULTED TO THE PALETTE, NEVER DECLARED.
     The panel painted `var(--paper)` directly, so the only way to
     change its ground was to redefine --paper — which would repaint
     the whole page to move one overlay. These two give the panel its
     own address; absent, they resolve to exactly what was there
     before, so nothing changes for any page that does not set them. */
  background:var(--pl-bg,var(--paper,#F9F8F5));
  color:var(--pl-fg,var(--ink,#042839));
  overflow:hidden;                    /* ⚠ load-bearing — see below */
  transform-origin:0 100%;            /* the pivot */
  transform:rotate(0deg);
  will-change:transform;
}
html.pl-out .pl{
  transition:transform calc(var(--pl-wipe,1450ms) * var(--pl-k,1)) var(--ease-draw,cubic-bezier(.25,.1,0,1));
  transform:rotate(var(--pl-swing,96deg));
}

/* ⚠ THE TYPE MUST NOT RIDE THE SWING. A child of a rotating box
   both TURNS and ORBITS the pivot; counter-rotating about the
   child's own centre kills the turn and leaves the orbit, so the
   count sails off around the corner. This layer cancels both by
   counter-rotating about THE SAME PIVOT — an exact inverse — so
   everything inside sits still in viewport terms while the
   colour turns underneath it. The panel's overflow:hidden then
   does the rest: the count is clipped away by the panel's own
   edge, so the wipe takes it with it instead of the count being
   animated out separately. One move, not two. */
.pl-in{
  position:absolute; inset:0;
  transform-origin:0 100%;
  transform:rotate(0deg);
}
html.pl-out .pl-in{
  transition:transform calc(var(--pl-wipe,1450ms) * var(--pl-k,1)) var(--ease-draw,cubic-bezier(.25,.1,0,1));
  transform:rotate(calc(var(--pl-swing,96deg) * -1));
}

/* the lockup, placed at the VIEWPORT centre expressed in the
   panel's coordinates — the panel's top-left is (--pl-px,
   --pl-py - --pl-side), so this is that offset subtracted out */
.pl-cluster{
  position:absolute;
  left:calc(50vw - var(--pl-px,-6vw));
  top:calc(50vh - var(--pl-py,106vh) + var(--pl-side,260vmax));
  transform:translate(-50%,-50%);
  text-align:center;
  font-family:var(--sans,"Helvetica Neue",Arial,sans-serif);
}

/* ── THE COUNT ─────────────────────────────────────────────
   EACH SQUARE ROLLS OUT OF THE ONE BEFORE IT. Not a fade, not
   a slide — a tumble. The square is drawn already turned a
   quarter anticlockwise about its own BOTTOM-LEFT corner, and a
   square turned -90° about that corner sits exactly one slot to
   the left: on top of its predecessor. Turning it back to 0
   rolls it over that corner and into its own slot. One
   property, one pivot, no keyframes — and the geometry is exact
   rather than eyeballed, which is the reason to hinge on the
   corner instead of animating x.

   The gap is the one imprecision: the pivot is this square's
   corner, not the shared edge, so it starts overlapping its
   predecessor by --pl-gap rather than sitting flush on it. At
   3px on a 15px square that reads as spawning out of it.

   Opacity SNAPS rather than transitions — a square that fades
   while it rolls reads as an effect on a square, where this has
   to read as the square arriving. */
/* ⚠ CLIPPED ON X ONLY. Every square starts rotated -90° about its
   own bottom-left corner, which parks it one slot LEFT of its own —
   on top of a predecessor for squares two onward, and outside the
   row for the first, so the first rolls in from off-stage. The row
   hides that.

   The TOP MUST STAY OPEN. A square rotating about a corner sweeps
   its far corner above the row — the diagonal is ~1.41× the side,
   so mid-roll it stands ~0.41 of its height proud. Clipping both
   axes sliced that arc off flat and the roll read as a square
   sliding sideways under a lid. overflow-x:clip WITH
   overflow-y:visible is legal precisely because it is `clip`; with
   `hidden` the other axis would be forced to `auto` and the row
   would become a scroll container. */
.pl-count{ display:flex; gap:var(--pl-gap,3px); justify-content:center;
  margin-bottom:10px; overflow-x:clip; overflow-y:visible; }
.pl-count i{
  display:block; width:var(--pl-sq,15px); height:var(--pl-sq,15px);
  background:currentColor;
  opacity:0; transform:rotate(-90deg);
  transform-origin:0 100%;
  transition:transform calc(var(--pl-roll,500ms) * var(--pl-k,1)) var(--ease-enter,cubic-bezier(.22,1,.36,1)),
             opacity 0s;
}
.pl-count i.on{ opacity:1; transform:rotate(0deg); }
/* ── THEY LEAVE THE WAY THEY ARRIVED ────────────────────────
   ⚠ LONGER AND STAGGERED, 15 Aug — operator: "feels too abrupt".
   Two things were making it abrupt and only one of them was the
   duration. At 240ms the fade was quick, but the bigger problem
   was that all three went AT ONCE: a row that filled one square at
   a time and then vanished as a block reads as the count being
   switched off rather than finishing.

   So the fade is 560ms, and each square leaves --pl-fade-step
   after the one before it — in ARRIVAL order, left to right, so
   the row empties the same direction it filled. The motion carries
   straight on rather than reversing, which is what stops it
   reading as an undo.

   The wipe waits for all of it: preload.js adds the stagger to the
   fade when it schedules the panel, so the sheet never starts
   moving under a square that is still on screen. */
.pl-count.spent i{ opacity:0;
  transition:opacity calc(var(--pl-fade,560ms) * var(--pl-k,1)) var(--ease-enter,cubic-bezier(.22,1,.36,1));
  transition-delay:calc(var(--pl-fade-step,70ms) * var(--pl-i,0) * var(--pl-k,1)); }

/* ⚠ THE FIRST SQUARE'S DROP IS RETIRED, 15 Aug — operator's call.
   It used to fall in flat while the rest rolled, on the reasoning
   that the first has nothing to roll out of. True, but it cost the
   sequence its through-line: one gesture to begin and another to
   continue reads as two ideas and the row never finds a rhythm.
   All four roll now, and the first rolls in from outside the clip —
   it does have something to roll out of, it is just off-stage. */


.pl-cap{
  margin:0; font-size:13px; line-height:1; letter-spacing:.01em;
}

/* ⚠ THERE IS NO VISIBLE SKIP CONTROL — operator's call, 14 Aug.
   The preload is STILL escapable: preload.js resolves on the
   first click, key, wheel or touch, so a reader who reaches for
   the page gets it. What is gone is the affordance that told
   them so. Doctrine (docs/motion-language.md) asks for the way
   out to be visible before they want it, and this is a knowing
   departure from that, not an oversight. */

/* ── REDUCED MOTION · the module refuses to run, and these are
      the belt-and-braces rules in case it is ever forced ───── */
@media (prefers-reduced-motion:reduce){
  .pl{ display:none; }
  .pl-count i{ transform:none; transition:none; }
}

@media print{ .pl{ display:none; } }
