/* ═══════════════════════════════════════════════════════════════════════
   RIBBON — the live glass ribbon over every child-page hero.
   Host contract (see assets/js/ribbon.js):

     <section class="section page-hero">            (or <header class="case-hero">)
       <div class="ribbon-hero" data-ribbon aria-hidden="true"></div>
       <div class="wrap"> …copy… </div>

   The host is a full-bleed absolutely-positioned layer OVER the copy
   (z-index 2; .page-hero > .wrap and .case-hero > .wrap sit at 1), with
   pointer-events off so the copy stays selectable underneath. It reaches up
   under the fixed header (the hero's top padding is empty space, the ribbon
   may use it) and is masked to nothing at its bottom edge so the canvas
   never ends in a hard line above the next section.

   The canvas clears transparent and composites normally: it is nothing
   wherever the glass is not, and where the glass is it is opaque and shows
   the hero copy REFRACTED — ribbon.js rasters the .wrap into a texture that
   only the transmission pass sees. (A transparent clear used to fade the
   glass with the alpha of the empty frame behind it; the opaque copy plane
   behind the slab is what fixes that.) The light theme has no dark room for
   the glass to sit in, so the host is hidden there and the
   IntersectionObserver in ribbon.js parks the render loop.
   ═══════════════════════════════════════════════════════════════════════ */
.ribbon-hero{
  position:absolute;left:0;right:0;
  top:calc(-1 * var(--header-h,72px));bottom:0;
  z-index:0   /* 7 Sep: behind the copy again (was 2, above it, for the refraction) */;pointer-events:none;overflow:hidden;
  opacity:0;transition:opacity 1.4s var(--ease,ease);
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 16%,#000 80%,transparent 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 16%,#000 80%,transparent 100%);
}
.ribbon-hero.is-live{opacity:1}
.ribbon-hero canvas{display:block;width:100%;height:100%}

/* The hero has to be the containing block, and tall enough that the ribbon
   has somewhere to sit below the copy (the densest part of the ribbon is
   framed into the lower half by the camera). .section is already relative;
   .case-hero is not. */
.page-hero:has(> .ribbon-hero){position:relative;min-height:calc(var(--vh,1vh) * 64)}
.case-hero:has(> .ribbon-hero){position:relative;min-height:calc(var(--vh,1vh) * 62)}
.case-hero > .wrap{position:relative;z-index:1}

body.phase-light .ribbon-hero{display:none}
html.rzv-no-motion .ribbon-hero{transition:none}

@media(max-width:760px){
  .page-hero:has(> .ribbon-hero){min-height:calc(var(--vh,1vh) * 50)}
  .case-hero:has(> .ribbon-hero){min-height:calc(var(--vh,1vh) * 54)}
}

/* 8 Sep 2026, Haseeb (services hub): "have the 3D ribbon overlap with the marquee",
   then "The ribbon overlaps but isn't on top of marquee refracting etc."
   The host spills past the hero's bottom edge into #chips; the bottom mask still
   fades it out, just lower. z-index 1 lifts it over #chips' rows (z auto, in the
   same .page-main stacking context) — .page-hero > .wrap is also 1 and later in
   the DOM, so the hero copy still paints over the glass exactly as before. The
   chips stay clickable (pointer-events:none above); ribbon.js rasters the moving
   rows into the transmission pass so the glass refracts them ("The chips band").
   Hub only (the class is set in services/index.html). */
.ribbon-hero--spill{bottom:calc(var(--vh,1vh) * -34);z-index:1}
@media(max-width:760px){ .ribbon-hero--spill{bottom:calc(var(--vh,1vh) * -22)} }

/* 10 Sep: the ribbon was briefly raised above .page-hero > .wrap so the slab
   could cover the copy. It occluded the words instead of revealing them — the
   glass reads as opaque because the copy raster fills its frame with the page
   ground, so the slab's alpha follows that fill (see rasterCopy in ribbon.js and
   the "see-through" control in /lab/ribbon/). Back behind the copy until that is
   solved; raising it again is this one rule:
   .page-hero > .ribbon-hero{z-index:2} */
