/* ==========================================================================
   04 — What we stand on (the aperture)

   The set-piece. Three pillars, three stages, one circle: the period that ends
   the active sentence grows until it is larger than the viewport. Everything
   INSIDE the rim is the resolved page — the silk field, the glyph, full --bone
   type. Everything OUTSIDE it is the screened page — the 4px grain of --ink and
   the same copy at --bone-25. Crossing the rim is the whole argument stated as
   a boundary you can see.

   Structurally that means the section renders its copy TWICE, in two layers
   that must lay out identically:

     .pillars__layer--base   always visible, --bone-25, no glyph
     .pillars__layer--in     masked to the inside of the aperture by .aperture-in
                             (base.css), full --bone, carries the glyph

   So every rule that affects layout is written once, on .pillars__layer /
   .pillar / .pillar__title / .pillar__text, and only COLOUR differs between the
   two. Both layers are position:absolute inset:0 of the sticky, which is exactly
   the viewport box while pinned — that is what makes .aperture-in's mask, whose
   --ax/--ay are viewport pixels, land on the right spot. Do not give either
   layer padding, a transform, or an inset other than 0: any of the three moves
   the mask's coordinate origin off the viewport and the rim drifts.

   JS (js/ui/pillars.js) writes exactly three numbers here: --p on the fill,
   --g per glyph, and — through js/ui/screen.js, never directly — --ax/--ay/--ar.
   ========================================================================== */

.pillars {
  position: relative;

  --glyph-w: clamp(300px, 38vw, 560px);
  --rail: clamp(26px, 3.4vw, 58px);   /* the lane the progress track sits in */
}

/* The pinned frame. js/core/stage.js owns .pillars' height (3 stages × 0.9vh
   plus one viewport); this is the one viewport that stays on screen. */
.pillars__sticky {
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: 0 var(--gutter);
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Instruments — outside both layers, so they are never masked and never dimmed.
   The meta and the counter share .section__meta's size and the same top offset,
   so they read as one line across the top: "03 — what we stand on … 01 / 03".
   -------------------------------------------------------------------------- */
.pillars__counter {
  position: absolute;
  top: clamp(90px, 12vh, 140px);
  right: var(--gutter);
  font-family: var(--font-mono);
  font-weight: 300;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-align: right;
  color: var(--bone-70);           /* 8.27:1. See the note on .section__meta in base.css. */
}

.pillars__track {
  position: absolute;
  left: var(--gutter);
  top: 50%;
  display: block;
  width: 1px;
  height: clamp(120px, 22vh, 220px);
  transform: translateY(-50%);
  background: var(--bone-12);
}
.pillars__fill {
  position: absolute;
  inset: 0;
  background: var(--bone-45);
  transform: scaleY(var(--p, 0));
  transform-origin: top center;
}

/* --------------------------------------------------------------------------
   The two layers. Identical geometry, different colour. See the header note.
   -------------------------------------------------------------------------- */
.pillars__layer {
  position: absolute;
  inset: 0;
}
/* THE ALWAYS-VISIBLE COPY, so it has to be readable on its own — the full-contrast twin below is
   aria-hidden and masked to the inside of the aperture, which means for most of the section this
   layer IS the section. At --bone-25 it measured 1.99:1 on a 21.6px sentence needing 4.5:1.
   --bone-55 is 5.41:1 and still less than a third of the --bone the aperture resolves to, so
   crossing the rim still reads as the page resolving. */
.pillars__layer--base { color: var(--bone-55); }
/* The inner copy is aria-hidden and duplicated — it must never take a pointer or
   a selection, or the reader selects the sentence twice. */
.pillars__layer--in {
  color: var(--bone);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* All three pillars occupy the same box; js/ui/pillars.js reveals one per stage
   with core/stagger.js. The gutter is applied HERE (not on the layer) so the
   layer's own border box stays the full viewport rect for the aperture mask. */
.pillar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--gutter) + var(--rail));
  right: var(--gutter);
  display: grid;
  align-content: center;
  justify-items: start;
}

.pillar__title {
  font-weight: 300;
  font-size: clamp(2.6rem, 6.4vw, 6.6rem);
  line-height: 1;
  letter-spacing: -0.035em;
  margin-bottom: clamp(16px, 2.2vw, 28px);
}
.pillar__text {
  max-width: 30ch;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.5;
}

/* core/stagger.js makes every character an inline-block, which hands the line
   breaker a break opportunity between any two of them — words would split
   mid-word in the wrapping sentence. js/ui/pillars.js re-parents each run of
   non-space characters into one of these; the space characters stay outside and
   remain the only break opportunities. */
.pillar__word { white-space: nowrap; }
/* display: contents so the aria-hidden shell js/ui/pillars.js wraps the split run in has no box
   of its own — .pillar__word stays a direct layout child of .pillar__title / .pillar__text. */
.pillar__split { display: contents; }

/* --------------------------------------------------------------------------
   Glyphs — a RIGHT-HAND INSTRUMENT, in the same register as the counter and the
   track: outside both copy layers, so the aperture never masks them and the copy
   never has to share its box with a drawing.

   x is the gutter, not the period. .pillars__glyphs is inset:0 of the sticky (the
   full viewport rect), so right: var(--gutter) lands the glyph's box edge on the
   right layout guide — measured 1375 against a guide at 1375.5 at 1440px. The ink
   stops 27-68px short of it, so it never touches the 1px chrome segment sliding
   down that guide, and the box clears the copy's right edge by 342-399px.

   y IS still the period: js/ui/pillars.js writes --dot-y. That keeps Precision's
   horizontal hairline collinear with the sentence's last baseline and maximises
   the glyph's intersection with the rim while the rim is still crossing it — a
   chord is widest on the centre line.

   Leaving the masked layer is what makes a right-hand column work at all. v2's
   layout failed only because the glyph was masked: the aperture reaches
   0.42*hypot = 713px by the end of stage 0, and a glyph on the right gutter has
   its centre 745.6px from the period, so pillar 01's spiral drew where nothing
   could see it — measured as ZERO changed pixels for the first 70% of its own
   stage. Unmasked it reads through the .screen grain at 5.4:1 from the first
   frame and at 10.8:1 once the rim has passed.

   No z-index: the container follows both layers in the DOM, so it paints last.
   The earlier z-index:1 existed only because the glyph sat on top of the copy;
   with 342-399px of clearance there is nothing left to sit on top of.
   -------------------------------------------------------------------------- */
.pillars__glyphs {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.glyph {
  position: absolute;
  right: var(--gutter);
  top: var(--dot-y, 50%);
  width: var(--glyph-w);
  aspect-ratio: 1;
  height: auto;
  transform: translateY(-50%);
  opacity: var(--g, 0);
  overflow: visible;
  will-change: opacity;
}

.g-ring { stroke: var(--bone); stroke-width: 0.9; fill: none; opacity: 0.28; vector-effect: non-scaling-stroke; }
.g-line { stroke: var(--bone); stroke-width: 0.9; stroke-linecap: round; opacity: 0.55; vector-effect: non-scaling-stroke; }
.g-trail { stroke: var(--bone); stroke-width: 1; fill: none; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.g-base { stroke: var(--bone); stroke-width: 0.8; opacity: 0.18; vector-effect: non-scaling-stroke; }
.g-core { fill: var(--accent); filter: drop-shadow(0 0 6px rgba(61, 100, 245, 0.9)); }
.g-core-ring { stroke: var(--bone); stroke-width: 0.9; fill: none; vector-effect: non-scaling-stroke; }
.g-curve { stroke-width: 1.5; fill: none; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.g-curve-glow { stroke: var(--accent); stroke-width: 8; fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* --------------------------------------------------------------------------
   Narrow — the glyph moves above the copy, the track turns horizontal.
   (js/ui/pillars.js separately shrinks the aperture target and lifts its centre
   below 768px, so the rim stays in frame on a tall portrait viewport.)
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  /* No right-hand lane exists here. At 390px the pillar sentence measures 350px — 89.7% of the
     viewport — and --glyph-w's floor is 240px, so ANY right placement overlaps it by at least
     240px (measured 51-74% of the sentence's own box, worse than the centred placement it would
     replace), and the glyph's centre could only move 33px anyway. The two separate VERTICALLY
     instead: glyph in the upper third, copy pinned to the floor. --dot-y is still written by
     pillars.js and is simply unused here. */
  .glyph {
    right: auto;
    left: 50%;
    top: 38%;
    transform: translate(-50%, -50%);
  }
  .pillars {
    --glyph-w: clamp(240px, 66vw, 420px);
    --rail: 0px;
  }
  .pillar {
    left: var(--gutter);
    align-content: end;                        /* pins the copy to the floor */
    padding-bottom: clamp(110px, 20vh, 200px);  /* clears the glyph above it */
  }
  .pillars__track {
    top: auto;
    bottom: clamp(44px, 8vh, 84px);
    width: clamp(80px, 30vw, 160px);
    height: 1px;
    transform: none;
  }
  .pillars__fill {
    transform: scaleX(var(--p, 0));
    transform-origin: left center;
  }
}

@media (max-width: 560px) {
  .pillar__text { max-width: none; }
}

/* --------------------------------------------------------------------------
   Reduced motion — base.css already removes .aperture-in entirely, so the base
   layer has to carry the section on its own: full contrast, normal flow, all
   three pillars stacked and readable. js/ui/pillars.js builds no stage in this
   mode, so nothing sets a height on .pillars and the section is exactly as tall
   as its own content.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .pillars__sticky {
    position: relative;
    top: auto;
    min-height: 0;
    display: block;
    overflow: visible;
    padding: clamp(80px, 12vh, 140px) var(--gutter);
  }

  .pillars .section__meta--abs {
    position: static;
    margin-bottom: clamp(36px, 6vh, 72px);
  }
  /* Both are instruments for a scrub that no longer runs: the counter would read
     "01 / 03" beside all three pillars, and the track would never fill. */
  /* Nothing builds these here — ui/pillars.js returns before buildGlyphs() — and the layout they
     anchored to is static now. Until the glyphs left .aperture-in this was collateral of the
     display:none on that layer; it has to be explicit. */
  .pillars__counter,
  .pillars__track,
  .pillars__glyphs { display: none; }

  .pillars__layer { position: static; }
  .pillars__layer--base { color: var(--bone); }

  .pillar {
    position: static;
    display: block;
    padding: 0;
    margin-bottom: clamp(44px, 7vh, 88px);
  }
  .pillar:last-child { margin-bottom: 0; }
  .pillar__text { color: var(--bone-70); }
}
