/* The rift backdrop. Pairs with rift.js and templates/partials/rift.html.

   The field is only ever seen through the letterforms — every other region of
   every page paints opaque black over it — so it is sized to the word, not to
   the viewport.

   That is not only tidiness. A filtered element is composited at its CSS box
   times the device scale, which has nothing to do with the canvas's own
   resolution: on a 6K display a full-viewport field is a 6016x3280 filtered
   layer, about 19.7 megapixels, to show a band 484px tall. WebKit gives up on
   a layer that size and draws nothing at all rather than drawing it
   unfiltered, which is how the wordmark ended up blank on exactly the display
   it was meant to look best on. Bound to the aperture it is 6016x968, a third
   of the area, and it stops growing with the height of the page. */

.aperture-stage {
    position: relative;
    /* The breathing scales the field past this box; clip it here so it cannot
       widen the page, and so the composited layer stays the size of the word. */
    overflow: hidden;
}

/* Wider than the window it is seen through, so the drift below has somewhere
   to travel: the hue sweep slides along the word instead of sitting fixed under
   it. 114% is deliberately modest — the layer's size is what blanked the
   masthead on large displays, and this is the one dimension worth spending on. */
/* The pointer layer. Separate from the container so the parallax transform and
   the drift animation do not contend for the same property. */
.rift-parallax {
    position: absolute;
    inset: 0;
    will-change: transform;
}

#canvas-container {
    position: absolute;
    /* Vertical headroom for the parallax: without it, moving the field up or
       down would drag black in at the edge of the band. Horizontal headroom is
       already there — the container is 114% wide for the drift. */
    top: -30px;
    bottom: -30px;
    left: 0;
    width: 114%;
    z-index: 0;
    will-change: transform;
}

#canvas-container canvas {
    width: 100%;
    height: 100%;
    display: block;
    transform-origin: center;
}

/* Above the field, so the plate's cut is what reveals it. */
.aperture-link {
    position: relative;
    z-index: 1;
}

/* The breathing waits for the drawing to finish, so the field is a finished
   picture before anything moves it. */
/* The drawn field is the shader's input, not the picture. It stays in the DOM
   because the swatches crop it — they want the artwork, not the warp — but it
   is never shown. If WebGL2 is missing the class comes off and this canvas is
   what you see: the field, undistorted, which is a worse picture than the warp
   and a much better one than nothing. */
#canvas-container canvas.rift-source {
    display: none;
}

#canvas-container canvas.rift-view {
    width: 100%;
    height: 100%;
    display: block;
}

/* The breathing scales the *container*, not the filtered canvas.

   Scaling the filtered element grows its filter region with it, and WebKit
   stops compositing a filtered layer once that region gets large enough: on a
   6K display the field drew, showed for a few seconds, and the wordmark went
   blank as breathe approached 1.06. It never appeared at 1440x900, because the
   region never got near the ceiling there.
   
   Transforming the parent instead means the filter is computed once in the
   canvas's own box and the finished result is scaled, so the region is a
   constant and the animation costs nothing extra. */
#canvas-container.is-drawn {
    animation: breathe 53s ease-in-out infinite alternate;
}

/* 53s, not 22. Every ambient animation on the site was scaled by the same
   2.4 — the breathing, the soundwave drift, the frequency phase, the
   portrait drift and the shader's own wave — so their rhythms keep the
   relation they were tuned in and none of them starts beating against
   another differently. The brief was eerie space, and at 22s the word
   was moving rather than drifting. */

/* Breathing is transform-only, deliberately.

   These keyframes used to carry `filter: url(#gravitational-distortion)
   blur(0px)` to `blur(1.2px)`. Animating a filter chain that contains a url()
   reference makes the engine re-run the whole SVG filter graph every frame,
   over the full-viewport canvas. Chromium keeps that on the GPU and pays
   nothing; Safari re-rasters on the CPU and the page went from a 15ms mean
   frame to 69ms, with a 213ms 95th percentile — quarter-second stalls while
   scrolling. The static filter on the rule above still applies throughout; only
   the blur ramp is gone, and a 1.2px blur drifting in over 14 seconds under a
   turbulence displacement was not visible work. Transform alone composites. */
/* One transform carries both the breathing and the drift, because an element
   only has one. translateX is a percentage of the container's own width, and
   the container is 114% of the stage, so -12.28% walks the far edge exactly to
   the window — the whole extra width gets used and no further. */
@keyframes breathe {
    0% {
        transform: translateX(0) scale(1) rotate(-0.4deg);
    }
    100% {
        transform: translateX(-12.28%) scale(1.06) rotate(0.8deg);
    }
}

/* rift.js already skips the wave loop here; this stops the CSS half too, so the
   fractal is drawn and then completely still. */
@media (prefers-reduced-motion: reduce) {
    #canvas-container.is-drawn {
        animation: none;
    }
}
