.scenes{position:relative;aspect-ratio:3/2}
/* ⚠️ THE BEATS MUST NOT OVERLAP. Every scene is absolutely stacked on the same
   spot. Cross-fading them — both moving over the same .5s — meant that for half
   a second the outgoing ID card and the incoming face were BOTH on screen, one
   showing through the other. It read as a jerk on entry and as the previous
   image refusing to leave, because that is exactly what was happening.

   So the two halves are staggered and cannot meet: leaving starts immediately
   and takes .26s; arriving WAITS .30s before it begins. The gap is deliberate —
   the panel is briefly empty, which is what makes it read as one thing
   replacing another rather than two things dissolving into each other.

   visibility is switched at the far end of each (0s duration, delayed), so an
   off scene is not merely transparent — it is out of the paint entirely and can
   contribute nothing, not even through a blend mode. */
.scene{position:absolute;inset:0;opacity:0;visibility:hidden;
  transition:opacity .26s cubic-bezier(.4,0,1,1), visibility 0s linear .26s}
.scene.on{opacity:1;visibility:visible;
  transition:opacity .34s cubic-bezier(0,0,.2,1) .30s, visibility 0s linear .30s}
.scene svg{width:100%;height:100%;display:block}

/* ── mesh vocabulary — ONE place, every scene inherits it ───────── */
.grid{fill:none;stroke:url(#gg);stroke-opacity:.34;stroke-width:.55}
/* ── the floor is a REAL 3-D plane (CSS perspective + rotateX), so spinning
   it about its own normal is a true rotation about the vertical axis, not a
   2-D fake. Masked at the edges so it reads as ground, not a turning square. */
.floor{position:absolute;left:0;right:0;bottom:0;height:56%;
  perspective:230px;perspective-origin:50% 100%;pointer-events:none;overflow:hidden}
.floor i{position:absolute;left:-60%;right:-60%;bottom:0;height:300%;transform-origin:50% 100%;
  transform:rotateX(72deg);
  background:
    repeating-linear-gradient(90deg, rgba(91,140,255,.48) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg,  rgba(91,140,255,.34) 0 1px, transparent 1px 40px);
  -webkit-mask-image:linear-gradient(to top,#000 8%,rgba(0,0,0,.55) 52%,transparent 90%);
  mask-image:linear-gradient(to top,#000 8%,rgba(0,0,0,.55) 52%,transparent 90%)}
/* ⚠️ Bound to .scene, NOT .scene.on. A continuous rotation has no end value to
   rest on, so removing it on handover snapped the floor back to zero from
   whatever angle it had reached — the same jerk as the card, under every beat.
   Left running while the scene is hidden it costs nothing (visibility:hidden is
   not painted) and the ground stays continuous from one beat to the next. */
.scene .floor.spin i,.card3 .floor.spin i{animation:floorspin 26s linear infinite}
@keyframes floorspin{from{transform:rotateX(72deg) rotateZ(0deg)}to{transform:rotateX(72deg) rotateZ(360deg)}}

.mline{fill:none;stroke:url(#mg);stroke-opacity:.85;stroke-width:.9}
/* depth bands: nearer geometry is brighter and heavier — this is what
   makes a projected surface read as volume rather than as a pattern */
.mback{fill:none;stroke:#5b8cff;stroke-opacity:.14;stroke-width:.4}
.m0{fill:none;stroke:url(#mg);stroke-opacity:.3;stroke-width:.45}
.m1{fill:none;stroke:url(#mg);stroke-opacity:.6;stroke-width:.6}
.m2{fill:none;stroke:url(#mg);stroke-opacity:.95;stroke-width:.85}
.mdot{fill:#cfe0ff;fill-opacity:.85}
.facefill{fill:#0b1224;fill-opacity:.92}
.edge{fill:none;stroke:#9fc4ff;stroke-opacity:.55;stroke-width:1.1}
.facemark{fill:none;stroke:#cfe0ff;stroke-opacity:.75;stroke-width:.9;stroke-linecap:round}
.contact{fill:#5b8cff;fill-opacity:.15}
.card,.cardin{fill:#0b1224;fill-opacity:.9;stroke:url(#mg);stroke-opacity:.8;stroke-width:1.2}
.rule rect,.mrz rect{fill:#5b8cff;fill-opacity:.3}
.rays line{stroke:#cfe0ff;stroke-width:1;opacity:.5}
.scene.on .rays{animation:raylift 7s ease-out infinite}
@keyframes raylift{0%,44%{opacity:.55}52%{opacity:1}66%{opacity:.75}100%{opacity:.55}}
.shell{fill:#0b1224;stroke:#9fc4ff;stroke-opacity:.9;stroke-width:1.3}
/* the phone in side profile: body, the seam between screen and back, and the
   sliver of screen turned toward the subject */
.edgebody{fill:#0b1224;stroke:#9fc4ff;stroke-opacity:.9;stroke-width:1.4}
.edgeseam{stroke:#9fc4ff;stroke-opacity:.35;stroke-width:.8}
.edgeglass{fill:#34e0a1;fill-opacity:.3}
.lensin{fill:#eaf2ff;fill-opacity:.9}
.glassx{fill:#34e0a1;fill-opacity:.2}
.speaker{fill:#9fc4ff;fill-opacity:.5}
.lens{fill:#0b1224;stroke:#9fc4ff;stroke-opacity:.85;stroke-width:1}
.limbfill{fill:#0b1224;fill-opacity:.97;stroke:#9fc4ff;stroke-opacity:.8;stroke-width:1.2}
.limbline{fill:none;stroke:#9fc4ff;stroke-opacity:.35;stroke-width:.8}
.rings ellipse{fill:none;stroke:#5b8cff;stroke-opacity:.3;stroke-width:.8}
/* the pin's aperture reads as a little map, not a hole punched through it */
.mapbg{fill:#0c1830}
.street{fill:none;stroke:#7fa6ff;stroke-opacity:.85;stroke-width:1.1}
.streetmajor{fill:none;stroke:#a9c6ff;stroke-opacity:.95;stroke-width:1.9}
.block{fill:#5b8cff;fill-opacity:.5}
.mapring{fill:none;stroke:#34e0a1;stroke-opacity:.9;stroke-width:1.5}
.youare{fill:#34e0a1;filter:drop-shadow(0 0 4px rgba(52,224,161,.9))}
.tick{fill:none;stroke:#34e0a1;stroke-width:6;stroke-linecap:round;stroke-linejoin:round}

/* ── shield: metallic material. Same silhouette, different surface. ── */
.metal{fill:url(#steel)}
.brush{fill:url(#brush);mix-blend-mode:overlay;opacity:.55}
.mfaint{stroke-opacity:.16;stroke-width:.6}
.sheen{fill:url(#sheen);opacity:.5}
.edgemetal{fill:none;stroke:url(#rim);stroke-width:1.6}
.wordmark{fill:#e8f1ff;font:700 15px var(--sans);letter-spacing:.06em;text-anchor:middle;
  paint-order:stroke;stroke:rgba(8,12,22,.55);stroke-width:2.6}
.tickmetal{fill:none;stroke:#34e0a1;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;
  filter:drop-shadow(0 0 5px rgba(52,224,161,.55))}
.wire{fill:none;stroke:url(#mg);stroke-opacity:.7;stroke-width:1}
.wirefill{fill:#0b1224;fill-opacity:.85}
.sig{fill:none;stroke:#34e0a1;stroke-opacity:.9;stroke-width:1.6;stroke-linecap:round}


/* the card turns a little to the right while the scan sweeps it, then a
   flash confirms the capture — the whole beat on one 2.4s clock */
/* The card sweeps an ARC: starts at 10 o'clock, turns up through face-on,
   stops at 2 o'clock. One direction. It never completes a revolution, never
   goes edge-on, never shows its back. The reset happens off-screen — the
   animation is bound to .on, so it restarts each time the beat comes round. */
.cardstage{position:absolute;inset:0;perspective:900px;perspective-origin:50% 46%}
/* ⚠️ THE RESTING POSE IS WHERE THE SWEEP ENDS, NOT WHERE IT STARTS.
   The animation is bound to .scene.on, so the moment the beat hands over the
   rule stops matching and the animation — fill and all — is thrown away. The
   transform then falls back to whatever is written HERE. With -58deg written
   here the card SNAPPED 116 degrees back to its opening pose at the exact
   instant of the handover, and it was still on screen for the fade-out, so you
   watched it jerk after it had finished. Writing the closing pose here means
   the fallback is exactly where the sweep left it and there is nothing to snap.
   fill-mode `both` (not `forwards`) also applies the opening pose the instant
   .on lands, so entry cannot flash the closing pose for a frame either. */
.cardspin{position:absolute;inset:0;transform-style:preserve-3d;transform:rotateY(58deg)}
.scene.on .cardspin{animation:cardsweep 5.6s cubic-bezier(.33,0,.3,1) both}
@keyframes cardsweep{
  from{transform:rotateY(-58deg)}   /* 10 o'clock */
  to  {transform:rotateY(58deg)}    /* 2 o'clock — the maximum */
}
.face{position:absolute;inset:0;width:100%;height:100%;
  -webkit-backface-visibility:hidden;backface-visibility:hidden}
.back{transform:rotateY(180deg)}
.stripe{fill:#050912;opacity:.9}
.idflash{fill:#eaf2ff;opacity:0}

/* Motion: the floors turn continuously so the scenes read as dimensional;
   everything else stays still except its own capture beat. */
.scan{fill:#34e0a1;fill-opacity:.9}
.flashfx{opacity:0}
.scene.on .flashfx{animation:pop 2.4s ease-out infinite}
@keyframes pop{0%,44%{opacity:0}52%{opacity:1}66%{opacity:.18}100%{opacity:0}}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}
  .scene{opacity:1}.scene:not(.on){display:none}.flashfx{opacity:1}}


/* ── the face beat ───────────────────────────────────────────────────────
   The supplied picture arrives as a cyan wireframe on its own dark navy
   rectangle. Dropped in as-is it shows a seam where its navy meets the stage's,
   and its background particles read as dirt on the glass — so the background is
   KEYED OUT on luminance and the line-work remapped onto the system's blue.

   ⚠️ BLUE IS THE SUBJECT, MINT IS THE EVENT. An earlier pass ran the ramp
   through #34e0a1, which is where most of a wireframe's lines sit, so the face
   came out mint — wearing the colour this system reserves for the moment of
   capture, leaving the scan nothing to say. Blue carries the face; mint appears
   only in the light crossing it.

   The head is held square to the front and centred. An earlier pass swivelled
   it to carry the liveness idea; it was dropped. */
.facewrap{position:absolute;inset:0;overflow:hidden}

/* Sized to the shield. The image is 3:2 in a 3:2 stage, so one number sets both
   axes: at 59% the whole figure stands about 203px against the shield's 195 — a
   shade OVER it, matching where the shield sits in its own beat. There is still
   air above the crown and a clear band of floor below. The two ends of this
   have both been wrong: 68% filled the panel corner to corner, the crown at the
   header and the neck in the floor grid; 52% cleared it but sat too far back.
   Seated at 38% vertically, centred. */
.facepic,.facelit{position:absolute;inset:0;background-repeat:no-repeat;
  background-position:50% 38%;background-size:59% auto}
.facepic{background-image:var(--face)}

/* The light is masked BY THE FACE, so it paints only where there is face and
   never over open stage — light falling on something, not a bar drawn across it. */
.facelit{
  -webkit-mask-image:var(--face);mask-image:var(--face);
  -webkit-mask-size:59% auto;mask-size:59% auto;
  -webkit-mask-position:50% 38%;mask-position:50% 38%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  background-image:linear-gradient(to bottom,rgba(52,224,161,0) 0%,rgba(52,224,161,.5) 40%,
    rgba(223,246,236,.85) 50%,rgba(52,224,161,.5) 60%,rgba(52,224,161,0) 100%);
  background-size:100% 20%;background-position:50% 8%;mix-blend-mode:screen}

/* A bar a little wider than the face and no wider. One spanning the frame reads
   as a scanner the subject is standing inside; this reads as something looking
   at a face. The head is 29.5% of the frame now, so the bar is 34%. */
.facebar{position:absolute;left:33%;right:33%;top:16%;height:2px;border-radius:2px;
  background:linear-gradient(90deg,rgba(52,224,161,0),#34e0a1 14%,#dff6ec 50%,#34e0a1 86%,rgba(52,224,161,0));
  box-shadow:0 0 11px rgba(52,224,161,.6)}

/* The capture. Fires once, AFTER the second pass finishes — the same beat the
   shield uses, so "captured" looks the same wherever it happens on the page. */
.faceflash{position:absolute;inset:0;opacity:0;pointer-events:none;
  background:radial-gradient(46% 52% at 50% 40%,rgba(234,242,255,.95) 0%,
    rgba(91,140,255,.34) 34%,rgba(91,140,255,0) 100%)}

/* ── one clock, three tracks ──────────────────────────────────────────────
   Down to the chin, back up, then the flash. Every keyframe below is a
   percentage of the same 7s beat, so they cannot drift apart. */
.scene.on .facebar {animation:facebar  7s cubic-bezier(.45,0,.55,1) infinite}
.scene.on .facelit {animation:facelight 7s cubic-bezier(.45,0,.55,1) infinite}
.scene.on .faceflash{animation:faceflash 7s linear infinite}

/* ⚠️ EVERY TRACK ENDS WHERE IT STARTED. raylift ends on .55 as it began, pop
   ends on 0, floorspin ends square — and these do too, so the loop has no seam
   to snap across. An earlier build ran one track one-way and held; on the loop
   it jumped the whole arc, which was the jerk on entry and exit. */
/* pass one: 3→44% down.  pass two: 48→86% up. */
@keyframes facebar{0%,3%{top:16%}44%,48%{top:61%}86%,100%{top:16%}}
@keyframes facelight{0%,3%{background-position:50% 8%}44%,48%{background-position:50% 64%}
  86%,100%{background-position:50% 8%}}
@keyframes faceflash{0%,86%{opacity:0}89%{opacity:1}93%{opacity:.28}97%,100%{opacity:0}}

/* The floor is already under every beat. Here it is dialled back — ground, not
   pattern. It does NOT turn on this beat: the scan is the only motion here, and
   a spinning floor under it would be one movement too many. */
.scene[data-i="1"] .floor i{opacity:.42}
