/* ==========================================================================
   about.css — the About page.

   An inverted page: black ground, light type, and a bottom bar that swaps to
   #C8C8C8 with #393939 links, the reverse of the other two pages. Everything
   is scoped under body[data-page="about"] so the shared chrome in ui.css keeps
   its own colours everywhere else.
   ========================================================================== */

body[data-page="about"],
body[data-page="mask"]{
  --about-ground: #000000;
  --about-ink:    #C8C8C8;
  --about-dim:    rgba(200,200,200,.5);
  background: var(--about-ground);
  color: var(--about-ink);
}

/* ---------------- stage ----------------------------------------------------
   Owns the wheel gesture (js/about-text.js drives a virtual scroll, because
   body is overflow:hidden site-wide) so it must not pass touch scrolling
   through to the page underneath. */
.about{
  position: relative;
  width: 100%;
  height: 100svh;
  overflow: hidden;
  background: var(--about-ground);
  touch-action: none;
}

/* ---------------- the figure -----------------------------------------------
   Sits behind the type. The canvas is transparent, so the black comes from
   .about, which is what lets the saturation layer above reach the ground as
   well as the model. */
.about__figure{
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: var(--fig-w, 420px);
  height: 100%;
  z-index: 1;
  pointer-events: none;
}
/* Two canvases can live here at once: the original WebGL one and, once
   js/about-xray.js has three.js up and has drawn a frame, its own. They are
   stacked rather than swapped so the hand-over needs no reflow, and so the
   first can keep rendering its silhouette buffer underneath. */
.about__figure canvas{
  position: absolute;
  inset: 0;
  display: block;
  width: 100%; height: 100%;
}

/* THE EFFECT.

   In Figma this is a red fill over the model set to Saturation. Reproduced
   literally with mix-blend-mode, but it only works because of something that
   is easy to remove by accident: the model is PURE GREY, and a saturation
   blend over pure grey does nothing at all — grey carries no hue for the blend
   to saturate. The shader in js/about-figure.js therefore dithers every pixel
   with a little random colour (CONFIG.grain), and this layer amplifies that
   dither to full saturation. That is where the speckled colour comes from.

   Turn the shader's grain to 0 and this layer stops doing anything. They are
   a pair — but they are NOT the same control, which cost a round to work out:

     shader grain  -> DENSITY. A saturation blend takes the saturation of the
                      source and applies it whole, so it does not care how big
                      the backdrop's hue difference is, only that there is one.
                      Dropping grain from 0.09 to 0.022 barely moved the
                      measured chroma (164 -> 128) — it only thinned out how
                      many pixels differ at all after 8-bit quantisation.
     --fx-opacity  -> INTENSITY. This is the real dial. Below 1 the result is a
                      mix of the plain grey and the fully saturated version,
                      which is what produces a pastel rather than confetti.

   Both were then swept together against the reference, because the tint in
   the shader and this layer COMPOUND: the blend amplifies the tint's hue just
   as it amplifies the noise, so a green tint that measures +6 on its own came
   out at +29 through the blend. 0.14 with tint #FCFFF8 lands at a green lift
   of about +12 and a chroma spread of about 28, against the reference's +10.7
   and 30.2. */
.about__fx{
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: var(--fx-color, #e8271c);
  mix-blend-mode: var(--fx-blend, saturation);
  opacity: var(--fx-opacity, 1);
}

/* ---------------- the text -------------------------------------------------
   One column of full-width rows. Each row is a three-track grid — left gutter,
   a channel the width of the figure, right gutter — and the line sits in the
   left or right track depending on data-side. That is what makes the copy read
   around the figure instead of over it, while keeping one linear reading order
   in the markup. */
.about__stage{
  position: absolute;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  cursor: default;
}
.about__column{
  position: absolute;
  left: 0; right: 0;
  /* The copy begins near the TOP, as the reference does. At 42svh the upper
     third of the page was empty black and the story looked like it was
     missing rather than waiting below. */
  top: var(--start-offset, 6svh);
  display: flex;
  flex-direction: column;
  /* js/about-text.js overwrites this on mount. It is derived there, not set
     here, because the number that matters is the gap you SEE down one side —
     and the copy alternates sides, so that is roughly twice this. */
  gap: var(--line-gap, 4.5px);
  will-change: transform;
}

/* Full-width rows. The copy is pushed off one edge by a padding that
   js/about-text.js recomputes from the figure's live silhouette, so the block
   parts around the BODY rather than around a fixed column — and re-parts as it
   turns. The three-track grid this replaced could only ever clear a rectangle. */
.about__line{
  display: flex;
  align-items: baseline;
  /* every one of these is a dial on the About panel */
  font-size: var(--text-size, 10px);
  line-height: var(--text-leading, .5);
  letter-spacing: var(--text-track, -0.4px);
  text-transform: uppercase;
  color: var(--text-color, #FFFFFF);
  opacity: var(--text-opacity, 1);
}
.about__line > *{ margin: 0; white-space: nowrap; }
.about__line[data-side="l"]{ justify-content: flex-end; }
.about__line[data-side="r"]{ justify-content: flex-start; }
.about__line[data-side="c"]{ justify-content: center; }

/* One colour for the whole story. The --head and --quiet classes are kept as
   hooks, but they no longer set a colour of their own: a dimmed line read as a
   rendering fault rather than as emphasis, and mixed brightness down a column
   is the first thing the eye picks up. Change --text-color on the panel to
   move all of it at once. */
.about__line--head > *,
.about__line--quiet > *{ color: inherit; }

/* ---------------- corner label ---------------------------------------------- */
.about__tag{
  position: absolute;
  left: 200px; top: 11px;
  z-index: 4;
  font-size: var(--ui-10);
  line-height: .5;
  letter-spacing: -0.4px;
  text-transform: uppercase;
  color: var(--about-dim);
}

/* ---------------- the bottom bar is NOT inverted here ------------------------
   It used to be: #C8C8C8 ground with #393939 links, the reverse of the other
   pages, on the reasoning that a dark page wants a light bar. That made the
   bar a different object on the black pages from the one on the light pages,
   and the bar is the one element that is on every page and every section — it
   is the site's constant, so it cannot change identity halfway round.

   #393939 EVERYWHERE, which is ui.css's own --ui-bar and what the expanding
   footer in css/footer.css already paints. Deleting the override IS the fix;
   re-stating the default here would be a second copy of one colour, which is
   how the two drifted apart in the first place. */

/* The grain is tuned for a near-white page; against this black ground
   `overlay` does nothing, for the same reason the loader needed its own
   settings. Same texture, same animation, `screen` so it can lift. */
body[data-page="about"] .grain,
body[data-page="mask"] .grain{
  --grain-blend: lighten;
  --grain-opacity: .06;
  --grain-brightness: 100%;
  --grain-contrast: 50%;
  --grain-size: 1em;
}

@media (prefers-reduced-motion: reduce){
  .about__column{ transition: none; }
}


/* ---------------- block 02's figure box --------------------------------------
   .about__figure is --fig-w wide because the copy runs down both sides of it
   and that box IS the channel the text parts around. Block 02 has no copy, so
   the same box would only amputate the head the moment it is scaled up — and
   that page exists to scale it up. Full width there.

   SCOPED TO .mask-body, and it has to be. Written as
   `body[data-page="mask"] .about__figure` it caught BOTH figures, so block 01
   — which is the About block, channel and all — had its 420px column widened
   to the whole viewport. Nothing errored: the copy simply re-flowed around a
   figure that was now four times wider and rendered at a different aspect, and
   the block that was supposed to be untouched was the one thing on the page
   that had changed. A selector that names the page instead of the element is
   how a "page-scoped" rule reaches something it was never meant to. */
body[data-page="mask"] .mask-body{
  left: 0;
  width: 100%;
  transform: translateY(-50%);
}
/* THE RED EFFECT, AND WHICH BLOCK IT REACHES.
   .about__fx is a red fill on `saturation`, and it only ever made sense over a
   PURE GREY model: grey has no saturation of its own, so the blend gives it
   one and the shader's colour noise decides how much. It was lifted above the
   dissolve canvas so block 02 would wear the page's look too.

   Then block 02's head got its own photograph, and a saturation blend over a
   real photograph does not tint it — it drives every pixel to full chroma, and
   a face comes out burnt orange and black. So the default is back under the
   sheet: block 01 keeps the treatment, block 02 shows its own colour.

   The class puts it back over everything, for a block 02 with the map off. */
body[data-page="mask"] .about__fx{ z-index: 2; }
body[data-page="mask"] .about.is-fx-over .about__fx{ z-index: 6; }
/* The grab cursor belongs to block 02, so it only appears once block 02 has
   the screen — js/mask.js adds the class at the same threshold that hands the
   pointer over. Over block 01 the cursor is the About page's. */
body[data-page="mask"] .about.is-grabbable{ cursor: grab; }
body[data-page="mask"] .about.is-dragging{ cursor: grabbing; }


/* The white block, as a canvas over the figure (js/mask-scroll.js). Above the
   model and the effect layer so it genuinely covers them, below the bar and
   the film grain so the page's chrome survives the transition. */
.mask-sheet{
  position: absolute;
  inset: 0;
  z-index: 5;
  display: block;
  width: 100%; height: 100%;
  pointer-events: none;
  visibility: hidden;
}


/* The mask page's second-block figure. Hidden from the compositor because it
   is only ever a texture source for js/mask-scroll.js — `visibility` rather
   than `display`, so it keeps its size and its backing store. */
body[data-page="mask"] .mask-body{ visibility: hidden; }

/* ---------------- the ASCII ground on the mask page -------------------------
   js/work-cursor.js's canvas, mounted inside .about here rather than .stage.

   UNDER THE 3D, which is the whole point of putting it here: z-index 0 puts it
   beneath the figure, the red effect at 2, the white sheet at 5 and the bar —
   so the characters are the ground block 02's head stands on, not a veil over
   it. The work page runs the same canvas at z-index 1 for the same reason,
   above its gradient and below everything the page draws.

   It has to be INSIDE .about and not on the body: a blend composites against
   its own stacking context's backdrop, and .about is what paints the white. */
body[data-page="mask"] .work-blobs{
  /* VISIBLE, and read twice — which is the only way it reaches both blocks.

     The two blocks are not the same kind of thing. Block 01 is a DOM stack on
     black, so the canvas simply sits in it at z-index 0: above .about's own
     background and below the figure, the red effect at 2 and the sheet at 5.
     Block 02 is one canvas that js/mask-scroll.js paints, so a DOM layer is
     behind an opaque sheet there and can never show — it reaches that block by
     being SAMPLED as a texture into the sheet's own ground instead.

     Both at once, and they do not double up: while block 02 is showing, the
     sheet covers this canvas completely, so what you see is the composited
     copy. It was `visibility: hidden` when only the second block wanted it. */
  z-index: 0;
}

/* THE COPY'S ENTRANCE, held until it is on screen. js/about-text.js adds this
   at mount and removes it on the first scroll. Same mechanism the loader uses
   (css/loader.css) and for the same reason: an entrance that plays where nobody
   is looking has not happened. */
#aboutColumn.is-held .anim-line{ animation-play-state: paused; }
