/* ==========================================================================
   NAV CUBE STRIP
   A square tube running the width of the top bar, cut into equal cubes, each
   rolling forward over its top edge a beat after the one to its left - so a
   wave travels left to right across the menu, continuously.

   ── WHY THIS IS CHEAP ──────────────────────────────────────────────────────
   Every frame of this animation is a `transform` on a composited layer. No
   JavaScript runs per frame, nothing repaints, nothing reflows. The compositor
   thread rotates quads the GPU already holds; the main thread stays free for
   the page. The only JS involved runs once on load and again (debounced) on
   resize, purely to count how many cubes fit.

   The rules that keep it that way, and which must not be relaxed:
     1. ONLY `transform` is animated. Animating box-shadow, filter, background
        or width would force paint every frame and is what turns effects like
        this into a fan-spinner.
     2. The lighting is a STATIC overlay the cubes rotate underneath, not a
        per-face animation. Real shading, zero per-frame cost.
     3. Cube count is capped and falls with viewport width. Each cube is four
        composited faces, so the count is the layer budget.
     4. `contain` walls the whole strip off from the rest of the page's layout
        and paint.

   ── THE OPT-OUT ────────────────────────────────────────────────────────────
   NOT prefers-reduced-motion. This site deliberately overrides that query, so
   keying off it would make this the one component that freezes on a machine
   where the rest of the site keeps moving. The sanctioned control is the
   accessibility widget's Pause Motion toggle, which puts `acc-pause-motion` on
   <html> - same opt-out every other animation on both sites uses.
   ========================================================================== */

.navcubes {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;

    /* Between .nav::before (the scan-line texture, z-index 0) and .nav-inner
       (the menu itself, z-index 2). The strip is scenery; the menu sits in
       front of it and is never competed with. */
    z-index: 1;

    /* The depth of the scene. Lower numbers exaggerate the perspective; this is
       deliberately shallow-ish so faces turn visibly without the row fanning
       out into a fish-eye at the edges. */
    perspective: 900px;
    perspective-origin: 50% 42%;

    /* Walls off layout, paint and size from the rest of the page. Without this
       a rotating cube near the bar's edge can invalidate paint outside it. */
    contain: layout paint size style;

    /* Edge colours, in one place. --c-brand is the logo green (#1f6f3c);
       --nc-edge is deliberately below it, --nc-edge-lit above it. */
    --nc-edge:     rgba(20, 82, 44, .92);
    --nc-edge-lit: rgba(45, 186, 99, .22);
}

/* The tube. One flex row, vertically centred in the bar. */
.navcubes__strip {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: var(--nc-cube, 58px);
    margin-top: calc(var(--nc-cube, 58px) / -2);
    display: flex;
    transform-style: preserve-3d;
}

/* ── One cube ──────────────────────────────────────────────────────────────
   --i is the cube's index, written into the markup once. --nc-n is how many
   cubes are actually on screen, set by the sizing script. */
.navcube {
    position: relative;
    flex: 0 0 var(--nc-cube, 58px);
    width: var(--nc-cube, 58px);
    height: var(--nc-cube, 58px);
    transform-style: preserve-3d;

    animation: ncRoll var(--nc-dur, 5.6s) cubic-bezier(.62, 0, .3, 1) infinite;

    /* THE WAVE, and the seamless loop, in one line.

       Phase for cube i is (dur - i * dur/n), so each cube runs a step behind
       the one to its left and the wave travels left to right. Every delay is
       negative, so the row is already mid-cycle on the first painted frame -
       there is no start-up sweep and no state to reset.

       Because the step is exactly dur/n, the phase spread from the first cube
       to the last is precisely ONE full cycle: cube n would land back on cube
       0's phase. The wave leaving the right edge and the wave entering from the
       left are the same wave, continuous across the seam. Nothing jumps,
       nothing cuts, and there is no pause at the end because there is no end -
       the strip is a loop that happens to be drawn as a line. */
    animation-delay: calc(
        (var(--i) * var(--nc-dur, 5.6s) / var(--nc-n, 20)) - var(--nc-dur, 5.6s)
    );
}

/* A full turn. 0deg and -360deg are the same transform, so the cycle closes on
   itself no matter how the four faces are shaded - the seamlessness is
   structural, not something tuned by eye.

   Negative rotateX takes the TOP edge toward the viewer (CSS +Y points down, so
   a positive angle would roll it away). It holds flat for the first 58% of the
   cycle, which is the gap between waves; the roll itself gets the remaining
   42% - about 2.3s at the default duration. Fast enough to read as motion,
   slow enough to look machined rather than flicked. */
@keyframes ncRoll {
    0%, 58%  { transform: rotateX(0deg); }
    100%     { transform: rotateX(-360deg); }
}

/* ── Faces ─────────────────────────────────────────────────────────────────
   Four of them - front, top, back, bottom - built on the X axis, which is the
   only axis this rolls on. Left and right faces are omitted on purpose: the
   cubes touch end to end, so those two are never visible and would be pure
   layer cost.

   backface-visibility:hidden means the GPU skips a face the moment it turns
   away, so a rolling cube costs two drawn faces, not four. */
.navcube__f {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;

    /* THE EDGE. This is what makes the cubes legible against a dark bar - a
       shape with no drawn edge just reads as a smudge of colour at this size.

       The colour is the logo green taken down: --c-brand is #1f6f3c, and this
       sits below it at #14522c so it defines geometry without lighting up as a
       colour in its own right. A darker line than the face it borders is what
       an edge actually looks like; a brighter one reads as neon piping.

       Three inset rings, painted once and never animated:
         1px green      - the edge itself
         1px black      - seats the green so it does not glow
         1px top green  - the lit top arris, brighter because it faces the light
       All inset, so the box never grows and neighbouring cubes stay flush. */
    box-shadow:
        inset 0 0 0 1px var(--nc-edge, rgba(20, 82, 44, .92)),
        inset 0 0 0 2px rgba(0, 0, 0, .38),
        inset 0 1px 0 1px var(--nc-edge-lit, rgba(45, 186, 99, .22));
}

/* Every face carries the SAME base treatment. The impression of a lit object
   comes from .navcubes__light below - a fixed light the cubes turn underneath -
   rather than from painting one face brighter than another. That is both how
   real lighting behaves and the reason the loop cannot betray itself: no face
   is identifiable, so no face reveals where the cycle restarted. */
.navcube__f--front,
.navcube__f--back,
.navcube__f--top,
.navcube__f--bottom {
    background:
        linear-gradient(168deg,
            rgba(176, 48, 58, .95) 0%,
            rgba(118, 29, 40, .96) 38%,
            rgba(56, 17, 25, .97) 78%,
            rgba(28, 13, 18, .98) 100%),
        linear-gradient(90deg,
            rgba(0, 0, 0, .24) 0%,
            rgba(0, 0, 0, 0) 20%,
            rgba(0, 0, 0, 0) 80%,
            rgba(0, 0, 0, .24) 100%);
}

/* Depth is half the cube, so the box is genuinely cubic - a square tube cut
   into squares, not a flat card spinning. */
.navcube__f--front  { transform: translateZ(calc(var(--nc-cube, 58px) / 2)); }
.navcube__f--back   { transform: rotateX(180deg) translateZ(calc(var(--nc-cube, 58px) / 2)); }
.navcube__f--top    { transform: rotateX(90deg)  translateZ(calc(var(--nc-cube, 58px) / 2)); }
.navcube__f--bottom { transform: rotateX(-90deg) translateZ(calc(var(--nc-cube, 58px) / 2)); }

/* ── The light ─────────────────────────────────────────────────────────────
   One static element covering the strip. Faces rotating up into the top of the
   bar pass under the highlight and brighten; faces rolling down pass under the
   shadow and sink. This is what sells the third dimension, and it costs one
   composited quad that never changes.

   `screen` lifts without washing out; the burgundy underneath keeps its
   saturation instead of going pink. */
.navcubes__light {
    position: absolute;
    inset: 0;
    pointer-events: none;
    mix-blend-mode: screen;
    opacity: .70;
    background:
        /* specular band, just above centre - the "top edge catching light" */
        linear-gradient(180deg,
            rgba(255, 226, 176, .00) 0%,
            rgba(255, 226, 176, .22) 26%,
            rgba(201, 162, 74, .14) 40%,
            rgba(0, 0, 0, .00) 56%,
            rgba(0, 0, 0, .00) 100%),
        /* a slow falloff to the ends of the tube, so the row reads as long */
        radial-gradient(120% 180% at 50% 38%,
            rgba(255, 255, 255, .10) 0%,
            rgba(255, 255, 255, .00) 62%);
}

/* ── Legibility scrim ──────────────────────────────────────────────────────
   The menu has to stay easy to read, which is the whole job here - the cubes
   are scenery. This sits over the strip and under .nav-inner, darkest through
   the vertical band the links actually occupy. Tune the alphas here rather
   than dimming the cubes: contrast behind the text is what matters, and this
   preserves the effect at the top and bottom edges where no text sits. */
.navcubes__scrim {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        linear-gradient(180deg,
            rgba(6, 10, 20, .30) 0%,
            rgba(6, 10, 20, .62) 34%,
            rgba(6, 10, 20, .66) 66%,
            rgba(6, 10, 20, .34) 100%);
}

/* The strip as a whole sits back in the mix. Raising this is the single knob
   for "more visible"; the scrim above is the knob for "still readable". */
.navcubes__strip { opacity: .92; }

/* ── Responsive ────────────────────────────────────────────────────────────
   Cube size is set by the sizing script so the tube always divides the bar
   exactly. These are the fallbacks for the pre-script frame and for the
   no-JavaScript case, plus the cap on how many cubes ever get rendered.

   Cube edge is kept near 0.6 of the bar height on purpose: a cube mid-roll
   sweeps a silhouette up to 1.41x its edge, and this keeps that inside the bar
   so the tube is never seen to clip against the top and bottom. */
@media (max-width: 1199px) { .navcubes { --nc-n: 16; } }
@media (max-width: 991px)  { .navcubes { --nc-n: 13; } }
@media (max-width: 767px)  { .navcubes { --nc-n: 10; --nc-dur: 5.0s; } }
@media (max-width: 479px)  { .navcubes { --nc-n: 8;  --nc-dur: 4.6s; } }

/* Phones get a shorter tube AND a lighter one. Fewer cubes is fewer composited
   layers, which is the number that matters on a low-end handset. */
@media (max-width: 767px) {
    .navcubes__strip { opacity: .80; }
    .navcubes { perspective: 620px; }
}

/* ── Motion opt-out ────────────────────────────────────────────────────────
   Paused, not hidden. The tube stays exactly where it is, lit and dimensional,
   simply not turning - a deliberate still frame rather than a blank bar. */
html.acc-pause-motion .navcube {
    animation: none !important;
    transform: rotateX(0deg) !important;
}

/* Belt and braces: if the browser cannot do 3D transforms at all, the faces
   would stack flat on top of each other. Hide the strip rather than show a
   smear. */
@supports not (transform-style: preserve-3d) {
    .navcubes__strip { display: none; }
}
