/* ============================================================================
   LOOM · intro act styling (prototype)

   Two jobs, and nothing else:
     1. a sticky stage the engine can pin, with the canvas filling it
     2. the hero's dissolve UP THROUGH the mesh, driven by the one number
        intro.js publishes on the act as --loom-rise

   Everything else on this page comes from the build's own sheets: tokens.css,
   tokens-ink.css, ground-ink.css, scrollcraft.css, fonts.css, and the hero
   rules copied out of index.html's <style>. No colour is invented here.
   ========================================================================== */

/* -------------------------------------------------------------- the stage --
   The stage is the hero's OWN .sheet__fold, carrying [data-sc-stage] so the
   engine adopts it. index.html already makes a solo sheet's fold
   `position:sticky; height:100svh`, which is exactly what a pin needs, and the
   engine warns on console if anything overrides a stage's `position`. So this
   file adds the ground and the canvas and never touches `position` on it. */
#a1 { position: relative; }

/* ground-ink.css gives `.skip` position:relative (to lift it over the
   tapestry), which wins over index.html's own `.skip { position: absolute }`
   and so leaves the skip link IN FLOW: a 48px band above the first act, as
   measured on the real page too. Harmless under a hero; fatal under an intro,
   because the first 48px of scroll would be dead and a visible link would sit
   above the scan line. Restated here at a specificity ground-ink cannot match,
   scoped to this prototype. The real fix belongs in ground-ink.css and is
   flagged in NOTES.md rather than made, since that file is another agent's. */
body > a.skip:not(:focus-visible) { position: absolute; }

#a1 .loom__stage {
  display: grid;
  place-items: center;
  /* THE INK GROUND, STATED LITERALLY, not as var(--sc-canvas).
     The engine drifts --sc-canvas on <html> from each act's data-sc-drift,
     and index.html's acts all declare the CREAM value (#EFE9DD) left over
     from the bone build. ground-ink.css absorbs that by pinning
     `body { background: #141210 }` and making every sheet transparent, so on
     the real page the drift lands on nothing. A stage that reads --sc-canvas
     instead goes bone the moment the drift starts — measured here at p=0.25,
     a full white flash mid-weave. So the stage pins the ink the same way
     ground-ink.css pins the body. */
  background: #141210;
}
#a1 canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
}

/* The one line of type in the intro, under the scan line: the page saying
   what it is while the cloth is still being made. It fades as the weave
   takes over, so the reader is never asked to read two things at once. */
#a1 .loom__cap {
  /* ABSOLUTE, not a grid child. The stage is index.html's own .sheet__fold,
     which is `display:grid; align-content:center` — so as a flow child the
     caption was centred WITH the hero and landed at the top of the frame.
     Taking it out of flow pins it to the bottom of the stage where it belongs
     and leaves the hero alone in the grid. */
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(2.4rem, 9vh, 6rem);
  z-index: 2;
  font-family: var(--sc-font-display);
  font-size: clamp(0.56rem, 0.95vw, 0.7rem);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--sc-ink-soft);
  text-align: center;
  opacity: calc(1 - var(--loom-weave, 0) * 1.25);
}
#a1 .loom__cap b { color: var(--sc-accent); font-weight: inherit; }

/* ------------------------------------------------- the hero's dissolve UP --
   THE IDEA: the hero does not fade in on top of the cloth. It comes UP THROUGH
   it — light finding the gaps between the wires and widening them until the
   whole plate is through.

   HOW: a repeating-linear-gradient mask whose stripes run across the hero at
   the same pitch as the woven wefts. At rise = 0 the mask is a set of narrow
   slits (almost nothing gets through). As rise climbs the slits widen until
   the mask is solid and the hero is simply the hero. A mask built this way is
   one compositor-side layer, not a per-frame canvas readback, which is why it
   holds 60fps while the canvas is also drawing.

   WHY NOT the brief's "CSS mask-image from a canvas of mesh density": a CSS
   mask cannot point at a live <canvas> element at all — mask-image takes a
   url(), an image or a gradient, so a canvas-driven mask means a toDataURL (or
   an OffscreenCanvas -> blob) round trip EVERY FRAME, which is a readback off
   the GPU on each one. This gradient does the same picture with no readback.
   NOT TRIED and left for the real build: a WebGL mask written straight into
   tapestry.js's own scene, which would make the gaps the actual mesh rather
   than stripes that match its pitch. See NOTES.md.

   The mask is applied to a WRAPPER around .hero, never to .hero itself, so
   that if this act is ever restored to data-sc-act="flow" the engine's own
   data-sc-reveal transform on .hero and this transform are on separate
   elements and cannot fight.

   The hero lives INSIDE the act intro.js writes --loom-rise on, so it simply
   inherits it. The `, 1` fallback is the point: if intro.js never runs (JS
   off, a 404, a thrown error) the hero sits at its finished state, fully
   readable, rather than hidden behind a mask waiting for a number that will
   never arrive. */
#a1 .loom__veil {
  --rise: var(--loom-rise, 1);
  /* Slit pitch tracks the mesh's NEAR-row spacing. 1.1svh, not 2.4: at 2.4
     the hero read as venetian blinds — big black bars with the name behind
     them — rather than as light coming through cloth (measured on the p=0.78
     proof). Halving it puts the pitch at the spacing of the nearest wefts,
     which is the thing the eye is meant to match it to. */
  --slit: 1.1svh;
  /* 0 -> a 4% duty cycle (slits), 1 -> 100% (solid) */
  --duty: calc(4% + 96% * var(--rise));
  -webkit-mask-image: repeating-linear-gradient(
    to bottom,
    #000 0, #000 var(--duty), transparent var(--duty), transparent 100%);
  mask-image: repeating-linear-gradient(
    to bottom,
    #000 0, #000 var(--duty), transparent var(--duty), transparent 100%);
  -webkit-mask-size: 100% var(--slit);
  mask-size: 100% var(--slit);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  /* it rises: a short lift, and the de-blur that reads as coming into focus
     out of the fabric rather than sliding up from below */
  transform: translate3d(0, calc((1 - var(--rise)) * 3.2svh), 0);
  filter: blur(calc((1 - var(--rise)) * 5px));
  /* opacity starts at 0, not 0.1: at 0.1 the slitted ghost of the hero was
     legible as banding on the very first frame (measured), which gave the
     ending away before the weave had thrown a single row. The intro has to
     open on the scan line ALONE. */
  opacity: var(--rise);
  will-change: transform, filter, opacity;
}

/* -------------------------------------------------------- the filler act --
   Something below the hero so the scroll has somewhere to go and the intro's
   resolution is not also the end of the document. Deliberately plain: this
   prototype is about the intro, and a second designed act here would make the
   proof shots argue about two things. */
#a99 .loom__filler {
  display: grid;
  gap: var(--sc-5);
  max-width: 52ch;
  margin-inline: auto;
  text-align: center;
}
#a99 .loom__filler p { color: var(--sc-ink-soft); margin: 0; }
#a99 .loom__filler .poster__no { color: var(--sc-accent); }

/* ------------------------------------------------------- reduced motion ----
   Final hero state, grid static, no weaving. intro.js draws its one still
   frame at p = 1 and starts no loop; here the hero is simply unmasked. */
@media (prefers-reduced-motion: reduce) {
  #a1 .loom__veil {
    -webkit-mask-image: none; mask-image: none;
    transform: none; filter: none; opacity: 1; will-change: auto;
  }
  /* The caption is an instruction to wait for a weave that is not going to
     happen, and it landed on top of the hero's credit line. Gone. */
  #a1 .loom__cap { display: none; }
}
