/* fun mode V3, "alive": the garden layer.
   Loads after fun-v2/screens.css; everything is scoped to html.fun-mode.fun-v3
   so nothing here can touch the live V2 look. Nodes carrying data-v3 are
   injected by fun-v3/alive.js and removed again when the mode goes off.

   What this layer adds on top of the V2 scrapbook: real cut-out flowers and
   fruit poking out from behind everything, torn coloured paper behind the
   sheets, a rose for a cursor, and five real scanned bees that fly around
   it. The owner's removal pass took out every purely vector thing that
   used to be here — the drawn fruit stickers, the drawn butterflies, the
   dot grids, the stripe blocks, the gradient bar, the ellipsis, the chip,
   the ring, the doodle arrows and the ladybird. Cut paper is the only
   non-photographic thing left, because a colour field is paper, not an
   icon. Decoration only: no rule here touches copy, order or a content
   box's size, and nothing sits over a link or a button. */

html.fun-mode.fun-v3 {
  --v3-coral: #ff5c4d;
  --v3-orange: #ff7a3d;
  --v3-lime: #9fe870;
  --v3-ink: #16150f;
}

/* every host becomes its own stacking context so a negative z-index lands
   under its own content and above the page */
html.fun-mode.fun-v3 .v3-host { position: relative; isolation: isolate; }

/* every injected node */
html.fun-mode.fun-v3 [data-v3] {
  position: absolute;
  pointer-events: none;
  user-select: none;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  transform: var(--t, none);
  transform-origin: center;
}

/* ============================================
   1. THE CUT-OUTS
   The outer node is placed and rotated; the inner node carries the picture.
   ============================================ */
html.fun-mode.fun-v3 .v3-flora { overflow: visible; }
/* The cut-outs are still. They used to sway on a slow hinge; the owner's
   call is that they sit like the pasted paper they are, and the only thing
   moving on the page is the swarm. */
html.fun-mode.fun-v3 .v3-flora > i {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--img) center / 100% 100% no-repeat;
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .06));
}
html.fun-mode.fun-v3 .v3-flora.is-flip > i { transform: scaleX(-1); }

/* ============================================
   1b. THE BUTTERFLIES
   Nothing here moves on its own any more. The owner's direction for this
   pass: "I want the butterflies to be where they are, but when you kind of
   touch them with the mouse they fly away somewhere random. Things are
   stationary or 'normal' until you interact with them."

   So the ambient drift is GONE — not reduced, deleted — and so is the
   idle wingbeat. A pinned specimen on a page is still. Everything it does
   is now the consequence of something you did: alive.js hit-tests the
   pointer against the artwork's own alpha and launches the thing.
   ============================================ */
html.fun-mode.fun-v3 .v3-fly > b {
  position: absolute;
  inset: 0;
  display: block;
  transform: rotate(var(--r, 0deg));
  /* The perspective that makes the wing fold real rather than a squash.
     Without it rotateY is mathematically identical to scaleX(cos t) — an
     orthographic projection has no foreshortening — which is exactly the
     cheap effect this replaces. Set per element in JS from the butterfly's
     own width (~2.6x), because a fixed px perspective is a different lens
     on a 54px butterfly than on a 152px one. */
  perspective: var(--persp, 300px);
}
/* will-change only while it is actually flying. On seven permanently
   promoted layers it costs memory the whole session for motion that
   happens for one second at a time. */
html.fun-mode.fun-v3 .v3-fly.is-flying > b { will-change: transform; }

/* ---- THE WING RIG ----
   Three layers cut out of ONE scan, no new artwork.

   Every one of the seven is a specimen pinned open and photographed square
   on, so each has a real body axis and mirror symmetry about it. That was
   measured, not assumed: rotating each scan through +/-60deg and scoring
   mirror correlation about every candidate axis puts all seven at 0.92-0.97
   once the tilt is taken out (b1 +1.0deg, b2 +3.5, b3 -29.5, b4 +15.5,
   b5 -37.0, b6 +1.0, b7 -29.5). b3 and b5 were expected to be three-quarter
   views and are not — they are plan views rotated on the page, which is a
   different problem and a solvable one.

   So: `s` is the left wing, `u` the right wing, `i` the body. Each is the
   same background image clipped to a half-plane cut along the measured
   axis, and the two wings rotate in 3D about that axis with the body
   sitting on top, unmoved. The antennae and the abdomen no longer squash,
   which was most of what made the old whole-body scaleX read as fake.

   clip-path, transform-origin and the rotate3d axis vector are all written
   by JS from the measured table, since they differ per scan. */
html.fun-mode.fun-v3 .v3-fly > b > i,
html.fun-mode.fun-v3 .v3-fly > b > s,
html.fun-mode.fun-v3 .v3-fly > b > u {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--img) center / 100% 100% no-repeat;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .12));
  backface-visibility: hidden;
  text-decoration: none;   /* s and u are being borrowed as bare boxes */
}
/* the body is the strip along the axis and it never moves — it is what the
   wings are hinged to */
html.fun-mode.fun-v3 .v3-fly > b > i { z-index: 2; }
html.fun-mode.fun-v3 .v3-fly > b > s,
html.fun-mode.fun-v3 .v3-fly > b > u { z-index: 1; }

/* Until the rig is built (or if a scan is missing from the table) `i` is
   still the whole butterfly, so a failure here degrades to the plain
   cut-out rather than to a body with no wings. */
html.fun-mode.fun-v3 .v3-fly:not(.has-rig) > b > s,
html.fun-mode.fun-v3 .v3-fly:not(.has-rig) > b > u { display: none; }

/* ============================================
   2. THE ROSE CURSOR
   Only where there is a real pointer — a touch device gets its own cursor
   back and no bees. The art is the rose cut into an arrow silhouette, given
   a white keyline with a fainter dark one outside it (built by
   tools/fun-v3/build_cursor.py) — without an outline it vanished against
   the page's own off-white. The hotspot is the arrow's tip, as a real
   cursor's is, and that tip is what the bees are drawn to.
   ============================================ */
@media (hover: hover) and (pointer: fine) {
  html.fun-mode.fun-v3,
  html.fun-mode.fun-v3 body,
  html.fun-mode.fun-v3 main,
  html.fun-mode.fun-v3 .site-footer,
  html.fun-mode.fun-v3 section,
  html.fun-mode.fun-v3 div,
  html.fun-mode.fun-v3 p,
  html.fun-mode.fun-v3 span,
  html.fun-mode.fun-v3 h1,
  html.fun-mode.fun-v3 h2,
  html.fun-mode.fun-v3 h3,
  html.fun-mode.fun-v3 li,
  html.fun-mode.fun-v3 dt,
  html.fun-mode.fun-v3 dd,
  html.fun-mode.fun-v3 img,
  html.fun-mode.fun-v3 video {
    cursor: url('/assets/fun-v3/cursor/rose.png') 0 1, auto;
    cursor: -webkit-image-set(url('/assets/fun-v3/cursor/rose.png') 1x, url('/assets/fun-v3/cursor/rose@2x.png') 2x) 0 1, auto;
    cursor: image-set(url('/assets/fun-v3/cursor/rose.png') 1x, url('/assets/fun-v3/cursor/rose@2x.png') 2x) 0 1, auto;
  }
  /* Anything you can click gets the url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 46 46'%3E%3Cpath d='M17 26V9a4 4 0 0 1 8 0v10a3 3 0 0 1 6 0v2a3 3 0 0 1 6 0v3a2.7 2.7 0 0 1 5.4 0V30c0 7.2-5 12-12 12h-5c-4 0-6.2-2-8.2-5l-6-9a3.3 3.3 0 0 1 5.6-3.5L17 27z' fill='%23ff4686' stroke='%23000' stroke-width='2.4' stroke-linejoin='round' stroke-linecap='round'/%3E%3C/svg%3E") 18 8, pointer, not the rose. The first version
     put the rose on links too with `pointer` only as a fallback, so nothing
     on the page ever changed shape over a link — the owner's note: "the
     cursor needs to change when something is clickable again." This is the
     same pink pointing hand fun mode uses on every other page (fun.css), so
     the signal is one signal site-wide. The descendant selector matters:
     the rose rule above targets span, img, p and the rest by element, and
     those sit INSIDE links, so without it the text of a link showed the
     rose while its padding showed the hand. */
  html.fun-mode.fun-v3 :is(a, button, [role="button"], label, summary, .thought-link, .fx-mood-btn, .nav-toggle),
  html.fun-mode.fun-v3 :is(a, button, [role="button"], label, summary, .thought-link, .fx-mood-btn, .nav-toggle) * {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 46 46'%3E%3Cpath d='M17 26V9a4 4 0 0 1 8 0v10a3 3 0 0 1 6 0v2a3 3 0 0 1 6 0v3a2.7 2.7 0 0 1 5.4 0V30c0 7.2-5 12-12 12h-5c-4 0-6.2-2-8.2-5l-6-9a3.3 3.3 0 0 1 5.6-3.5L17 27z' fill='%23ff4686' stroke='%23000' stroke-width='2.4' stroke-linejoin='round' stroke-linecap='round'/%3E%3C/svg%3E") 18 8, pointer;
  }
  /* a disabled control is not clickable, and must not claim to be */
  html.fun-mode.fun-v3 .thought-link.is-disabled,
  html.fun-mode.fun-v3 [aria-disabled="true"],
  html.fun-mode.fun-v3 :disabled { cursor: default; }
  html.fun-mode.fun-v3 input,
  html.fun-mode.fun-v3 textarea {
    cursor: url('/assets/fun-v3/cursor/rose.png') 0 1, text;
    cursor: image-set(url('/assets/fun-v3/cursor/rose.png') 1x, url('/assets/fun-v3/cursor/rose@2x.png') 2x) 0 1, text;
  }
}

/* ============================================
   3. THE SWARM
   A fixed layer over the page (under the nav), one node per bee. alive.js
   writes a transform on each frame; the only thing CSS does is the buzz —
   a fast, tiny wing-blur wobble on the inner node, on its own clock per
   bee so the five never beat together.
   ============================================ */
/* The layer is anchored to the DOCUMENT, not the viewport: a fixed layer
   made the bees ride along with the scroll as though pinned to the glass.
   Absolute with no positioned ancestor resolves against the initial
   containing block, i.e. the document's own origin, so scrolling now moves
   the page past the bees. alive.js sets the height to the document's. */
html.fun-mode.fun-v3 .v3-swarm {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 90;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity .6s ease;
}
html.fun-mode.fun-v3 .v3-swarm.is-on { opacity: 1; }
/* DEPTH. Every bee carries a z between -1 (far) and +1 (near) that it drifts
   through slowly, and the three cues that read as distance are all here as
   variables alive.js writes: size (in the transform it already builds), a
   little defocus, and a shadow that gets softer and fainter the further off
   the bee is. Without this the five sat on one plane and read as five
   stickers at different sizes. The blur ceiling is 0.55px — enough to push
   a bee back, not enough to look like an effect. */
html.fun-mode.fun-v3 .v3-bee {
  position: absolute;
  top: 0;
  left: 0;
  will-change: transform;
  /* the lens for the wing fold, set per bee in JS from its own width. A bee
     renders at 24-54px, so a fixed px perspective would be a wildly
     different lens on the smallest and the largest. */
  perspective: var(--persp, 120px);
  filter: blur(var(--zb, 0px))
          drop-shadow(0 var(--sy, 2px) var(--sr, 2px) rgba(0, 0, 0, var(--so, .16)));
}
/* ---- THE BEE WING RIG ----
   The same one-scan-three-layers idea as the butterflies, measured the same
   way: mirror correlation about candidate axes puts the body line at
   49.4-50.8% on all five scans (scores 0.80-0.94), and they are all
   photographed square-on with the body vertical, so a plain rotateY about
   that line is the fold.

   One thing is different and it matters. A butterfly IS its wings; a bee has
   legs and antennae sticking out well beyond them, and a half-plane split
   would swing those round with the wing. So the wing layers are a half-plane
   AND a horizontal band — the band found per scan by looking for rows where
   pale translucent membrane extends far from the body (wing) rather than
   dark thin shapes (leg). The body layer takes everything else as one
   H-shaped polygon, so legs and antennae never move.

   THE STROBE PROBLEM. A real bee beats at ~200Hz. Anything near half the
   frame rate aliases, and a wing snapping between folded and open on
   alternate frames looks broken rather than fast. This page has a further
   constraint the diagnosis turned up: with video on screen Chrome runs the
   whole page at 30Hz, so 30fps is the rate to stay clean at, not 60.

   So the wing does NOT try to beat at bee speed. It oscillates through a
   partial arc at 4-6.5Hz — at least 4.6 samples per cycle even at 30fps —
   and the speed is carried by what a camera would actually see: the wing
   held semi-transparent and stretched along its stroke, which is what a
   fast wing looks like photographed. Blur, not frame-rate.
   ============================================ */
html.fun-mode.fun-v3 .v3-bee > i,
html.fun-mode.fun-v3 .v3-bee > s,
html.fun-mode.fun-v3 .v3-bee > u {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--img) center / 100% 100% no-repeat;
  text-decoration: none;
}
/* the body carries the streak — the whole-animal stretch along the flight
   path. The old sub-pixel scaleY buzz is gone: it existed to suggest a
   wingbeat on a scan whose wings could not move, and now they can. Keeping
   both would be two different accounts of the same thing. */
html.fun-mode.fun-v3 .v3-bee > i {
  z-index: 2;
  transform: scaleY(calc(1 + var(--str, 0)));
}
html.fun-mode.fun-v3 .v3-bee > s,
html.fun-mode.fun-v3 .v3-bee > u {
  z-index: 1;
  /* the blur. A wing moving far faster than the shutter is a translucent
     fan, not a sharp shape — this is the doctrine's "subtle blur encodes
     speed" applied to the one thing on the page genuinely moving too fast
     to resolve. alive.js writes --wo from how hard the bee is working. */
  opacity: var(--wo, .72);
  will-change: transform;
}
/* Before the rig is built, `i` is still the whole bee, so a missing scan
   degrades to the plain cut-out rather than to a bee with no wings. */
html.fun-mode.fun-v3 .v3-bee:not(.has-rig) > s,
html.fun-mode.fun-v3 .v3-bee:not(.has-rig) > u { display: none; }

/* ============================================
   4. PHONES: smaller, fewer, no cursor and no swarm
   ============================================ */
@media (max-width: 760px) {
  /* The cut-outs are NOT scaled here any more. alive.js now shrinks their
     width and their offsets by one factor together, which is the only way
     a placement measured in real pixels at 1440 can survive a 390px screen
     — a CSS scale shrank the artwork but left the offsets at full size. */
  /* the butterflies come down with everything else, but less far: they are
     already the smallest things on the page and .72 turned them into
     smudges. Origin follows the edge each one is pinned to, so scaling
     pulls it toward its own corner rather than off the section. */
  /* var(--t) FIRST so a flight can move it. Without composing here the
     phone rule replaced the whole transform and a launched butterfly on a
     390px screen went precisely nowhere. --t is initialised on every fly
     node in JS, so there is always something for the shorthand to read. */
  html.fun-mode.fun-v3 .v3-fly { transform: var(--t, translate3d(0,0,0)) scale(.84); transform-origin: center; }
  /* A phone's sections are short, so a stem placed behind a cover would
     stand up past the section's top and paint over the masthead (a later
     sibling paints over an earlier one). That is a VERTICAL problem, and
     the clip has to be vertical only.

     `overflow: clip` on both axes was wrong and it broke V2: the flattened
     paper beds under every cover (fun-v2/stickers.js) are composed wider
     than the cover they lie under — the BuildBook one is 819px under a
     342px section — so clipping the section to its own box cut the whole
     pile away and left the two archive covers sitting on bare paper with
     nothing behind them. `visible` paired with `clip` is the one mixed
     pair CSS allows to stand (any other mix would compute to `auto` and
     add a scroller), so the beds spill sideways again while the garden
     still cannot climb out of the top of its section.

     The small archive covers are dropped from the rule entirely. They are
     hosts so their z-indexes resolve locally, but the reference gives them
     no cut-outs of their own, so there was never anything here to clip. */
  html.fun-mode.fun-v3 .case-study.v3-host {
    overflow-x: visible;
    overflow-y: clip;
  }
}

/* ============================================
   5. REDUCED MOTION: the garden stays, nothing moves, no swarm
   ============================================ */
@media (prefers-reduced-motion: reduce) {
  html.fun-mode.fun-v3 .v3-bee > i { animation: none !important; }
  html.fun-mode.fun-v3 .v3-swarm { display: none; }
  /* THE FLIGHT DOES NOT HAPPEN under reduced motion. A butterfly launching
     itself across the viewport is a large object travelling a long way at
     speed, which is squarely what this preference is asking not to see,
     and there is no "gentler" version of crossing the page.

     What it gets instead is the same event answered in place: touch one and
     it opens and closes its wings once, slowly, and stays exactly where it
     is. The cause is still legible — you touched it, it responded, it is
     alive — and nothing traverses. alive.js reads the same media query and
     branches to `startle` instead of `launch`.

     The base tilt is kept, so nothing jumps. There is no ambient drift left
     to switch off in either mode: it was deleted, not reduced. */
  html.fun-mode.fun-v3 .v3-fly > b { transform: rotate(var(--r, 0deg)); }
}

/* ============================================
   1c. THE POINTER FIELD
   The owner's note: "think of like a mesh on the image and when the mouse
   went over certain spots it moved just ever so much."

   That is what --t carries on every cut-out and every butterfly. alive.js
   runs one spring per node, driven by how close the pointer came and from
   which side, and the node yields a few pixels away from it and springs
   home. There is no CSS here beyond the variable the base [data-v3] rule
   already reads — the amplitude, the falloff and the settle are all in JS,
   where they can be interrupted mid-flight and re-aimed from the value
   actually on screen rather than from a keyframe's idea of it.
   ============================================ */

/* ============================================
   1d. THE COVERS ANSWER THE POINTER
   Desktop had no press feedback at all: styles.css puts
   `.case-media-cover:active { transform: scale(.985) }` inside
   `@media (max-width: 700px)`, so a mouse got nothing but `opacity: .88`
   on hover — a dim, which is the cheapest signal there is and says
   "disabled" as readily as it says "live".

   So, fun-v3 only and fine-pointer only: the FRAME never moves (its box is
   content and content boxes are off limits), but the photograph inside it
   is displaced a few pixels toward the pointer and lifted by a hair. The
   frame clips it — `.case-media-cover.has-photo { overflow: hidden }` — so
   the scale exists only to keep the displaced edges covered, never to grow
   anything. Everything is a spring in JS, so it can be grabbed and
   reversed mid-move; these two rules only declare where the values land.
   ============================================ */
@media (hover: hover) and (pointer: fine) {
  html.fun-mode.fun-v3 .case-media-cover.has-photo .cover-img {
    transform: translate3d(var(--cx, 0px), var(--cy, 0px), 0) scale(var(--cs, 1));
    transform-origin: center;
  }
  /* the press. 0.994, not the mobile rule's 0.985: a finger covers what it
     presses and needs the movement to be visible around it, a cursor does
     not. Same direction, a third of the distance. */
  html.fun-mode.fun-v3 .case-media-cover {
    transform: scale(var(--cpress, 1));
    transform-origin: center;
  }
  /* the hover dim goes with it — the displacement IS the answer now, and a
     dim on top of it reads as two unrelated things happening at once */
  html.fun-mode.fun-v3 a.case-media:hover { opacity: 1; }
}

/* The scroll-to-top button was audited and left almost alone. Its
   enter/exit is already right: it rises 10px in and sinks 10px back out
   along the same path, which is the spatial consistency rule, and it is a
   real translucent layer with content moving under it rather than an opaque
   strip. The one thing it had no answer for was the press — there is no
   `:active` rule on it anywhere in styles.css — so it gets one, and only
   one. 0.93 with a 90ms transition: a 44px control has to move
   proportionally more than a 700px cover for the press to register at all,
   and 90ms is short enough to feel like contact rather than an animation.
   `translateY(0)` is restated because the show/hide rule owns the same
   property and a bare scale() would drop the button back down 10px. */
html.fun-mode.fun-v3 .scroll-top-btn.visible:active {
  transform: translateY(0) scale(.93);
  transition-duration: 90ms;
}
@media (prefers-reduced-motion: reduce) {
  /* a press is contact, not travel — it stays, just shallower */
  html.fun-mode.fun-v3 .scroll-top-btn.visible:active { transform: translateY(0) scale(.97); }
}

/* ============================================
   6. THE FLIGHT LAYER
   A launched butterfly is moved onto this for the duration and put back on
   landing. It has to exist because of a rule three sections up: on a phone
   every case study is clipped on its vertical axis, so a butterfly leaving
   its own section would be sheared off at the boundary and the whole
   gesture would be invisible on the device that needs it most.

   Anchored to the DOCUMENT, like the swarm and for the same reason — as a
   fixed layer the butterflies would ride along with the scroll instead of
   staying on the page they landed on. `overflow: clip` is the safety net,
   not the mechanism: the landing test already keeps every butterfly a full
   half-width inside the viewport, and this guarantees that even a bad frame
   mid-flight cannot add a pixel to the document's width. z-index 89 puts it
   under the swarm (90) and over the page: a flying thing passes in front of
   the covers, and the bees still own the top of the stack.
   ============================================ */
html.fun-mode.fun-v3 .v3-flight {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* 100% of its OWN root, not of the document. An absolutely positioned
     child contributes nothing to its container's height, so unlike the old
     body-level layer this needs no JS sizing and cannot feed back into the
     document height. */
  height: 100%;
  z-index: 5;
  pointer-events: none;
  overflow: clip;
}
/* Making the root a containing block is done in JS, not here: it has to
   depend on what the root actually COMPUTES, and a class-based guard got it
   wrong (see layerFor in alive.js). */

/* ============================================
   7. THE POLLINATORS
   A second, separate population of bees that work the flowers and ignore
   the cursor entirely. Same layer arrangement as the swarm and the flight
   layer — anchored to the DOCUMENT, so they stay on the page they are
   working rather than riding along with the scroll — but under both of
   them, because a bee on a flower is part of the garden and the cursor's
   own bees are the thing in front of you.

   They reuse .v3-bee for the artwork and the buzz, and add only a fade:
   when the flowers a bee is working scroll away, it fades out and is
   re-seeded near the viewport rather than flying a journey nobody can
   watch. 0.5s is slow enough not to blink and fast enough not to be caught
   hovering over nothing.
   ============================================ */
html.fun-mode.fun-v3 .v3-pollen {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 88;
  pointer-events: none;
  overflow: clip;
}
html.fun-mode.fun-v3 .v3-pol { transition: opacity .5s ease; }

/* Reduced motion: no pollinators, for the same reason there is no swarm.
   Several small objects travelling continuously across the page with no
   prompting is the definition of what this preference turns off, and
   unlike the butterflies there is no in-place equivalent of foraging —
   a bee that does not move between flowers is not doing anything. The
   flowers keep their own stillness, which is what they had before. */
@media (prefers-reduced-motion: reduce) {
  html.fun-mode.fun-v3 .v3-pollen { display: none; }
}

/* ============================================
   8. THE AIRSHIP IS DRIVEN HERE, NOT KEYFRAMED

   V2 flies the R-100 on FOUR independent 34s CSS animations — the hull, the
   two mirrored photographs crossfading in opposite lockstep, and the label
   swapping sides. That arrangement produced the owner's complaint ("it
   rises fairly quick, then pauses, then descends quickly and then pauses"),
   and it is not tunable away: `ease-in-out` between corner keyframes takes
   the velocity to zero at every corner, and the turn is a held dwell from
   42% to 50% that exists purely to give the crossfade somewhere to happen.

   It also produced the one bug that shipped: under reduced motion only the
   HULL's animation was cancelled, so a stationary ship silently flipped to
   its mirror image at 14.3s. Four clocks can disagree. One cannot.

   So all four are switched off here and alive.js drives the lot from a
   single rAF clock. This is scoped to `.fun-v3` and lives in the local
   layer: fun-v2/screens.css is deployed and stays as it is until the owner
   decides what ships. Specificity beats V2's rules (three classes to two)
   and alive.css loads after screens.css, so the `none` wins on both counts.
   ============================================ */
html.fun-mode.fun-v3 .fx-cta,
html.fun-mode.fun-v3 .fx-cta::before,
html.fun-mode.fun-v3 .fx-cta::after,
html.fun-mode.fun-v3 .fx-cta-label { animation: none !important; }

/* The two photographs are now opacity-only, driven by a pair of variables
   that alive.js writes in ONE block on ONE frame. There is no transition on
   them on purpose: the two cutouts are mirror images, so their transparent
   margins do not line up, and anything other than an atomic swap shows the
   far photo through the near one's gaps. That was V2's original bug and the
   reason it uses steps(1) — the requirement survives the rewrite. */
html.fun-mode.fun-v3 .fx-cta::before { opacity: var(--faceA, 1); }
html.fun-mode.fun-v3 .fx-cta::after  { opacity: var(--faceB, 0); }

/* The label rides the hull and has to be on the correct side of whichever
   photograph is showing. Same single frame, same block in alive.js: the
   class and the two variables are set together, so they cannot desync. */
html.fun-mode.fun-v3 .fx-cta .fx-cta-label { left: 52%; right: auto; }
html.fun-mode.fun-v3 .fx-cta.is-rev .fx-cta-label { left: auto; right: 48%; }
@media (max-width: 900px) {
  /* the phone keeps V2's centred label: at that size the ship never turns
     (the driver runs it with zero traverse) so there is no side to swap */
  html.fun-mode.fun-v3 .fx-cta .fx-cta-label,
  html.fun-mode.fun-v3 .fx-cta.is-rev .fx-cta-label { left: 50%; right: auto; }
}

/* Reduced motion: alive.js never builds the ship, so no transform is ever
   written and it rests exactly where the grid puts it, on face A, facing
   the way it starts. The `animation: none` above is what stops V2's clocks
   from running underneath — which is the production bug, fixed here a
   second time by construction rather than by remembering to list all four. */

/* ============================================
   9. THE BUTTERFLIES LIVE ON THE LAYER, PERMANENTLY

   The owner found a real defect: b3 rests at `bottom: -14px` on the
   masthead, so its lower wing hangs into the Rounds section below — and a
   later sibling section paints over an earlier one whatever z-index the
   butterfly carries, because every V3 host is `isolation: isolate` and a
   z:3 inside the masthead cannot rise above anything inside Rounds. V2's
   newspaper strip at the top of Rounds painted straight over the wing.

   Nudging b3 would have moved the symptom, so all seven now live on the
   flight layer all the time, in document coordinates, above every section
   and the footer and below the nav (which is z-index 100).

   Order against the other two layers: pollinators 88, butterflies 89, the
   cursor swarm 90. A pollinator is working a flower and belongs in the
   garden; a butterfly rests on top of the page; the cursor's own bees are
   the thing in front of you and stay on top. So a bee crossing a butterfly
   passes in front only if it is one of the cursor's.
   ============================================ */

/* The ANCHOR: a childless clone left in the original section, carrying the
   same classes and inline style so it lays out in exactly the place the
   butterfly used to. It is what "home" is read from on every measure pass,
   which is why nothing about home can go stale. It has no children, so it
   paints nothing; `visibility: hidden` is belt and braces, and it must keep
   its BOX (not `display: none`) or there would be nothing to measure. */
html.fun-mode.fun-v3 .v3-anchor {
  visibility: hidden !important;
  pointer-events: none;
}

/* A freed butterfly is positioned entirely by --t in document coordinates,
   so the phone rule's scale(.84) must not apply to it: the node is given
   its already-scaled RENDERED size instead. Without this the translate
   would have to offset by the unscaled box while the visible box was 84% of
   it, which is the kind of arithmetic that is right until a breakpoint
   changes and then silently is not. */
html.fun-mode.fun-v3 .v3-fly.is-free {
  transform: var(--t, translate3d(0, 0, 0));
  transform-origin: center;
}
@media (max-width: 760px) {
  html.fun-mode.fun-v3 .v3-fly.is-free {
    transform: var(--t, translate3d(0, 0, 0));
  }
}
