/* ==========================================================================
   ribbon-3d.css
   Styles for the WebGL satin awareness ribbon (assets/js/ribbon-3d.js).

   These rules only style what is INSIDE the container. The page's own CSS is
   responsible for positioning/sizing the container (.hero__ribbon), e.g.
   position:absolute; inset:0 0 0 45%;

   Markup:
     <div class="hero__ribbon" data-ribbon3d aria-hidden="true">
       <img class="ribbon3d__fallback" src="..." alt="" width="1200" height="1200">
       <canvas class="ribbon3d__canvas"></canvas>
     </div>

   How the two layers work together
   - The <img> is a pre-rendered still of the same ribbon. It is what people
     see with no JS, no WebGL, or before the first WebGL frame is ready.
   - Once the canvas has drawn its first frame the script adds `.is-live` to
     the container and the still fades out, revealing the live canvas.
   ========================================================================== */

/* Stack the two layers in one grid cell. (Using a grid instead of
   position:absolute means we never fight the page's own `position` rule.) */
[data-ribbon3d] {
  display: grid;
  overflow: hidden;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

.ribbon3d__fallback,
.ribbon3d__canvas {
  grid-area: 1 / 1;
  display: block;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  pointer-events: none;
  /* Never let either layer pick up a background: the ribbon must sit
     straight on the page with no visible rectangle. */
  background: transparent;
}

/* The still is a square render with the ribbon filling 90% of its height,
   centred. It is placed with the same --rb-x / --rb-y / --rb-h custom
   properties the live canvas uses, so both land in the same spot. */
.ribbon3d__fallback {
  position: absolute;
  left: calc(var(--rb-x, 0.5) * 100%);
  top: calc(var(--rb-y, 0.5) * 100%);
  width: auto;
  height: calc(var(--rb-h, 0.86) / 0.9 * 100%);
  aspect-ratio: 1;
  max-width: none;
  transform: translate(-50%, -50%);
  opacity: 1;
  transition: opacity 0.3s ease;
}

/* Live WebGL frame is up: fade the still away. */
[data-ribbon3d].is-live .ribbon3d__fallback {
  opacity: 0;
}

/* Printing: canvases often print blank, so bring the still back. */
@media print {
  .ribbon3d__canvas {
    display: none;
  }
  [data-ribbon3d].is-live .ribbon3d__fallback {
    opacity: 1;
  }
}

/* Inner pages: the same ribbon, pulled in behind each page header and shaped
   around that page's header image or card (designs in ribbon-3d.js).
   --rb-w is the ribbon's width in CSS px. The decorative circles step aside
   once the ribbon is drawing (they remain the no-WebGL fallback). */
.page-hero__ribbon { position: absolute; inset: 0; z-index: -1; pointer-events: none; --rb-w: 48; }
.page-hero::before, .page-hero::after { transition: opacity 0.4s ease; }
.page-hero:has(.page-hero__ribbon.is-live)::before,
.page-hero:has(.page-hero__ribbon.is-live)::after { opacity: 0; }
@media (max-width: 699px) { .page-hero__ribbon { --rb-w: 30; } }

/* The stretch of ribbon that passes in front of a photo (see syncFront). */
.ribbon3d__front { position: absolute; pointer-events: none; }
.ribbon3d__front--shadow { z-index: 2; overflow: hidden; }
.ribbon3d__front--shadow canvas { position: absolute; display: block; }
.ribbon3d__front--cloth { z-index: 3; display: block; }
@media print { .ribbon3d__front { display: none !important; } }
