/*
  BoundBio — entry gate as vial sequence (presentation)
  ========================================================
  Pairs with gate-vial.js. All legal behaviour stays in gate.js; this file
  is choreography only.

  ── The read ────────────────────────────────────────────────────────────
  The label comes OFF the vial and is handed to the visitor. The vial is
  never discarded: it descends, turns to show its blank back label, holds
  centre at its settled size, and stays visible — softened and dimmed —
  behind the label that lifts away from it. On enter the label reattaches
  and the vial rises out of frame carrying it. The sequence closes the way
  it opened.

  ── One timeline, not four phases ───────────────────────────────────────
  Every animation starts together and is placed by its own delay, so
  neighbours OVERLAP and nothing cuts:

      descent      0 → 700
      rotation   580 → 1330     (starts 120ms before the settle resolves)
      peel      1200 → 1820     (starts 130ms before rotation finishes)
      text      1600 → 1900
      interactive ~1820ms

  Class `.p-run` starts it; `.p-cert` is the settled end state (also what
  reduced motion and the keyboard path jump straight to). Every curve is eased.

  ── Resolution ──────────────────────────────────────────────────────────
  The vial no longer scales up. At its settled size it renders ~157 CSS px
  wide against 365px of actual vial in the source: 1:1 or better at DPR2.
*/

.gv{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;
    background:var(--page-grad,linear-gradient(180deg,#FBFDFF,#E4F1F9 55%,#F7FBFD));overflow:hidden}
.gv-scene{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}

/* ---- Contact shadow ---- */
.gv-shadow{position:absolute;left:50%;top:50%;width:min(160px,38vw);height:22px;
           transform:translate(-50%,calc(-50% + 60px)) scaleX(2.4) scaleY(1.6);
           background:radial-gradient(ellipse at center,rgba(16,52,74,.28),rgba(16,52,74,0) 68%);
           opacity:0;will-change:transform,opacity;pointer-events:none}

/* ---- Vial ----
   The wrapper carries the dim/soften once the label lifts. Keeping the
   filter OFF .gv-vial matters: a filter makes an element a containing
   block, which would flatten the 3D its faces rotate in. */
.gv-vialwrap{position:absolute;left:50%;top:50%;transform:translate(-50%,calc(-50% - 112px));
             will-change:opacity,filter}
.gv-vial{position:relative;width:min(215px,50vw);aspect-ratio:500/900;
         perspective:1000px;
         /* The wrapper does the centring; this translate is purely the fall,
            so 0 is "at rest in the middle" rather than a second -50%. */
         transform:translateY(-160%);
         will-change:transform;backface-visibility:hidden}
/* NO backface-visibility here. The back face is deliberately shown at
   rotateY(180deg) — hiding backfaces would make it invisible exactly when
   it is meant to be facing us. The cross-fade owns face visibility. */
.gv-face{position:absolute;inset:0;will-change:transform,opacity}
.gv-face img{width:100%;height:100%;object-fit:contain;display:block}
.gv-face-back{opacity:0;transform:rotateY(180deg)}

.gv-fx{position:absolute;inset:0;overflow:hidden;pointer-events:none;
       -webkit-mask-image:var(--gv-mask);mask-image:var(--gv-mask);
       -webkit-mask-size:100% 100%;mask-size:100% 100%;
       -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.gv-face-back .gv-fx{-webkit-mask-image:var(--gv-mask-back);mask-image:var(--gv-mask-back)}
.gv-spec{position:absolute;left:-40%;top:0;width:180%;height:100%;opacity:0;
         background:linear-gradient(102deg,transparent 40%,rgba(255,255,255,.5) 47%,
                    rgba(255,255,255,.8) 50%,rgba(255,255,255,.5) 53%,transparent 60%);
         will-change:transform,opacity;mix-blend-mode:screen}
.gv-edge{position:absolute;top:0;height:100%;width:30%;opacity:0;will-change:opacity}
.gv-edge-l{left:0;background:linear-gradient(90deg,rgba(9,34,50,.42),transparent)}
.gv-edge-r{right:0;background:linear-gradient(270deg,rgba(9,34,50,.42),transparent)}

/* ================= DESCENT (0 → 700ms) ================= */
/* Position only — no scale — so the bitmap is never resampled mid-fall. */
@keyframes gvDescend{
  0%  {transform:translateY(-160%) rotateZ(-1.6deg)}
  55% {transform:translateY(-38%)  rotateZ(-0.9deg)}
  74% {transform:translateY(4.5%)  rotateZ(0.6deg)}
  87% {transform:translateY(-2.6%) rotateZ(-0.28deg)}
  95% {transform:translateY(1.2%)  rotateZ(0.12deg)}
  100%{transform:translateY(0)     rotateZ(0deg)}
}
.gv.p-run .gv-vial{animation:gvDescend 700ms cubic-bezier(.3,.72,.32,1) both}

@keyframes gvShadowIn{
  0%  {opacity:0;  transform:translate(-50%,calc(-50% + 60px)) scaleX(2.4) scaleY(1.6)}
  55% {opacity:.5; transform:translate(-50%,calc(-50% + 60px)) scaleX(1.7) scaleY(1.2)}
  74% {opacity:.9; transform:translate(-50%,calc(-50% + 60px)) scaleX(1.0) scaleY(.85)}
  100%{opacity:.8; transform:translate(-50%,calc(-50% + 60px)) scaleX(1.12) scaleY(.95)}
}

/* ================= ROTATION (580 → 1330ms) ================= */
/* Overlaps the settle by 120ms. The vial holds its size — it does NOT push
   in and does NOT leave. Neither face reaches edge-on. */
@keyframes gvTurnFront{from{transform:rotateY(0deg)}   to{transform:rotateY(88deg)}}
@keyframes gvTurnBack {from{transform:rotateY(92deg)}  to{transform:rotateY(180deg)}}
@keyframes gvFadeFront{0%{opacity:1}47%{opacity:.92}50%{opacity:.34}53%{opacity:0}100%{opacity:0}}
@keyframes gvFadeBack {0%{opacity:0}47%{opacity:0}50%{opacity:.34}53%{opacity:.92}100%{opacity:1}}
.gv.p-run .gv-face-front{animation:gvTurnFront 750ms cubic-bezier(.36,.02,.22,1) 580ms both,
                         gvFadeFront 750ms cubic-bezier(.36,.02,.22,1) 580ms both}
.gv.p-run .gv-face-back {animation:gvTurnBack  750ms cubic-bezier(.36,.02,.22,1) 580ms both,
                         gvFadeBack  750ms cubic-bezier(.36,.02,.22,1) 580ms both}

@keyframes gvSpec{
  0%{opacity:0;transform:translateX(-26%)} 22%{opacity:.45;transform:translateX(-13%)}
  47%{opacity:.95;transform:translateX(0%)} 50%{opacity:1;transform:translateX(2%)}
  53%{opacity:.95;transform:translateX(4%)} 78%{opacity:.5;transform:translateX(16%)}
  100%{opacity:0;transform:translateX(27%)}
}
@keyframes gvEdgeL{0%{opacity:0}50%{opacity:.95}100%{opacity:0}}
@keyframes gvEdgeR{0%{opacity:.12}50%{opacity:.95}100%{opacity:.12}}
.gv.p-run .gv-spec  {animation:gvSpec  750ms cubic-bezier(.36,.02,.22,1) 580ms both}
.gv.p-run .gv-edge-l{animation:gvEdgeL 750ms cubic-bezier(.36,.02,.22,1) 580ms both}
.gv.p-run .gv-edge-r{animation:gvEdgeR 750ms cubic-bezier(.36,.02,.22,1) 580ms both}

/* ================= LABEL PEEL (1200 → 1820ms) ================= */
/* Starts 130ms before the rotation finishes, so the label is already
   detaching while the vial is still coming to rest on its back face. The
   vial stays put and softens behind it — the source of the label, not a
   discarded intro. */
@keyframes gvVialRecede{
  from{opacity:1;   filter:blur(0px)   saturate(1)}
  to  {opacity:.82; filter:blur(2px) saturate(.95)}
}
.gv.p-run .gv-vialwrap{animation:gvVialRecede 620ms cubic-bezier(.4,0,.3,1) 1200ms both}
@keyframes gvShadowFade{to{opacity:.22}}
.gv.p-run .gv-shadow{animation:gvShadowIn 700ms cubic-bezier(.3,.72,.32,1) both,
                     gvShadowFade 620ms cubic-bezier(.4,0,.3,1) 1200ms both}

/* The label STARTS matched to the label band on the vial — small, low over
   the glass, curved, no separation shadow — and lifts toward the viewer,
   flattening as it comes. */
.gv .gv-label.gate-card{
  position:absolute !important;left:50% !important;top:50% !important;
  width:min(430px,88vw) !important;max-width:none !important;
  background:rgba(252,251,249,.97) !important;
  border:1px solid rgba(11,27,39,.10) !important;border-radius:10px !important;
  padding:26px 24px 22px !important;overflow:hidden !important;
  transform-origin:50% 42%;
  will-change:transform,opacity,box-shadow;
  opacity:0;box-shadow:0 0 0 rgba(11,60,90,0) !important;
  transform:translate(-50%,calc(-50% + 78px)) scale(.46) rotateX(7deg);
}
/* 100% lands on calc(-50% + 92px), NOT -50% (fixed 2026-09-06). It used
   to end dead-centre while the settled .gv.p-cert rule below holds the
   card 92px lower — so the moment the T_INTERACTIVE timer swapped p-run
   for p-cert (peelStart 1200 + peelDur 620 = 1820ms) the card jumped 92px
   down in a single frame, with animation:none and nothing to transition
   it. That is the "card renders in one place, then shifts lower" report:
   a real discontinuity, not an animation caught mid-frame. Measured
   directly — card top -331 at the peel's own end frame vs -239 settled,
   a 92px drop.
   +92px is the intended resting position, not -50%: the settled p-cert
   rule AND the prefers-reduced-motion fallback at the bottom of this file
   both independently place the card there, and it's what leaves the
   blurred vial visible above the card rather than covered by it. So the
   animation's endpoint was the outlier and is what moves here; the
   position the visitor ends up looking at is unchanged. */
@keyframes gvPeel{
  0%  {opacity:0;   transform:translate(-50%,calc(-50% + 78px)) scale(.46) rotateX(7deg);
       box-shadow:0 0 0 rgba(11,60,90,0)}
  18% {opacity:1}
  100%{opacity:1;   transform:translate(-50%,calc(-50% + 92px)) scale(1) rotateX(0deg);
       box-shadow:0 26px 70px rgba(11,60,90,.28)}
}
.gv.p-run .gv-label.gate-card{animation:gvPeel 620ms cubic-bezier(.22,.85,.28,1) 1200ms both}

/* Curvature: wrapped around glass at the start, flattening as it rises. */
.gv .gv-label.gate-card::before{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:10px;z-index:1;
  background:linear-gradient(90deg,rgba(9,34,50,.30),rgba(9,34,50,0) 22%,
             rgba(255,255,255,.35) 50%,rgba(9,34,50,0) 78%,rgba(9,34,50,.30));
  opacity:0;
}
@keyframes gvFlatten{0%{opacity:1}100%{opacity:0}}
.gv.p-run .gv-label.gate-card::before{animation:gvFlatten 620ms cubic-bezier(.22,.85,.28,1) 1200ms both}

/* Panel contents arrive once the label is nearly settled. */
.gv-panel{opacity:0;position:relative;z-index:2}
@keyframes gvTextIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.gv.p-run .gv-panel{animation:gvTextIn 300ms cubic-bezier(.2,.8,.2,1) 1600ms both}

/* Settled end state — reduced motion, the keyboard path and the natural end
   all land here, so nothing depends on an animation having run to completion. */
.gv.p-cert .gv-vial{animation:none;transform:translateY(0)}
.gv.p-cert .gv-face-front{animation:none;opacity:0;transform:rotateY(88deg)}
.gv.p-cert .gv-face-back {animation:none;opacity:1;transform:rotateY(180deg)}
.gv.p-cert .gv-spec,.gv.p-cert .gv-edge{animation:none;opacity:0}
.gv.p-cert .gv-vialwrap{animation:none;opacity:.82;filter:blur(2px) saturate(.95)}
.gv.p-cert .gv-shadow{animation:none;opacity:.22;transform:translate(-50%,calc(-50% + 60px)) scaleX(1.12) scaleY(.95)}
.gv.p-cert .gv-label.gate-card{animation:none;opacity:1;
  transform:translate(-50%,calc(-50% + 92px)) scale(1) rotateX(0deg);
  box-shadow:0 26px 70px rgba(11,60,90,.28) !important}
.gv.p-cert .gv-label.gate-card::before{animation:none;opacity:0}
.gv.p-cert .gv-panel{animation:none;opacity:1;transform:none}

/* ---- Certification content ----
   ≥15px and AA against the label surface — a legal floor, not a style
   choice. Real DOM text; never baked into an image. */
.gv-label .gv-mark{height:22px;width:auto;display:block;margin:0 auto 16px;opacity:.9}
.gv-eyebrow{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--blue-deep);
            font-weight:600;text-align:center;margin-bottom:14px}
.gv-check{display:flex;align-items:flex-start;gap:12px;cursor:pointer;min-height:44px;
          padding:6px 8px;margin:0 -8px;border-radius:10px;transition:background .16s ease}
.gv-check:hover{background:rgba(30,143,196,.07)}
.gv-check:active{background:rgba(30,143,196,.12)}
.gv-check input{margin-top:1px;width:24px;height:24px;flex:none;accent-color:var(--blue);
                cursor:pointer;transition:transform .12s ease}
.gv-check input:hover{transform:scale(1.08)}
.gv-check input:active{transform:scale(.92)}
.gv-check input:focus-visible{outline:2px solid var(--blue);outline-offset:3px;border-radius:4px}
.gv .gv-check span{font-size:15px !important;line-height:1.5;color:var(--ink)}

.gv-enter{width:100%;margin-top:16px;background:var(--ink);color:#fff;border:none;border-radius:980px;
          padding:15px;font-size:15px;font-weight:600;cursor:pointer;
          transition:background .18s ease,opacity .18s ease,transform .12s ease,box-shadow .18s ease}
.gv-enter:disabled{opacity:.35;cursor:not-allowed}
.gv-enter:not(:disabled):hover{background:var(--blue-deep);box-shadow:0 6px 18px rgba(11,109,158,.32)}
.gv-enter:not(:disabled):active{transform:scale(.975);box-shadow:0 2px 8px rgba(11,109,158,.28)}
.gv-enter:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
.gv-fine{font-size:11.5px;color:var(--ink-2);text-align:center;margin-top:12px;line-height:1.5}
.gv-fine a, .gv-fine .policy-link{color:var(--blue);text-decoration:underline;font-weight:600;display:inline-block;padding:5px 2px;margin:-5px -2px}

/* Check feedback fires on the input's own frame. */
.gv-check input.is-punch{animation:gvPunch .26s ease-out}
@keyframes gvPunch{0%{transform:scale(1)}45%{transform:scale(1.3)}100%{transform:scale(1)}}
.gv .gv-label.gate-card::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:var(--blue);transform:scaleX(0);transform-origin:left center;pointer-events:none;z-index:3}
.gv .gv-label.sweeping::after{animation:gvSweep .5s ease-out forwards}
@keyframes gvSweep{to{transform:scaleX(1)}}

/* .gv-skip removed 2026-09-09 with the Skip button itself — see gate-vial.js. */

/* ================= EXIT ================= */
/* The label reattaches — descends to the vial, curvature returning, shadow
   collapsing — and then the vial rises out of frame carrying it. */
.gv.gate-exit{pointer-events:none;background:transparent;transition:background .5s ease .35s}
@keyframes gvReattach{
  from{opacity:1;transform:translate(-50%,-50%) scale(1) rotateX(0deg);
       box-shadow:0 26px 70px rgba(11,60,90,.28)}
  to  {opacity:0;transform:translate(-50%,calc(-50% + 78px)) scale(.46) rotateX(7deg);
       box-shadow:0 0 0 rgba(11,60,90,0)}
}
.gv.gate-exit .gv-label.gate-card{animation:gvReattach 340ms cubic-bezier(.5,.02,.72,.4) both !important}
@keyframes gvFlattenBack{from{opacity:0}to{opacity:1}}
.gv.gate-exit .gv-label.gate-card::before{animation:gvFlattenBack 340ms ease both !important}
@keyframes gvTextOut{to{opacity:0}}
.gv.gate-exit .gv-panel{animation:gvTextOut 200ms ease both !important}
@keyframes gvVialReturn{to{opacity:1;filter:blur(0px) saturate(1)}}
.gv.gate-exit .gv-vialwrap{animation:gvVialReturn 300ms ease 120ms both !important}
@keyframes gvRise{from{transform:translateY(0)}to{transform:translateY(-170%)}}
.gv.gate-exit .gv-vial{animation:gvRise 520ms cubic-bezier(.5,.02,.7,.35) 340ms both !important}
@keyframes gvShadowOut{to{opacity:0;transform:translate(-50%,calc(-50% + 60px)) scaleX(2.2) scaleY(1.5)}}
.gv.gate-exit .gv-shadow{animation:gvShadowOut 400ms ease 340ms both !important}

/* ================= REDUCED MOTION ================= */
@media (prefers-reduced-motion: reduce){
  .gv *{animation:none !important;transition:none !important}
  .gv .gv-vial{transform:translateY(0)}
  .gv .gv-face-front{opacity:0}
  .gv .gv-face-back{opacity:1;transform:rotateY(180deg)}
  .gv .gv-vialwrap{opacity:.82;filter:blur(2px)}
  .gv .gv-shadow{opacity:.22}
  .gv .gv-label.gate-card{opacity:1;transform:translate(-50%,calc(-50% + 92px)) scale(1) rotateX(0deg);
                          box-shadow:0 26px 70px rgba(11,60,90,.28) !important}
  .gv .gv-label.gate-card::before{opacity:0}
  .gv .gv-panel{opacity:1;transform:none}
  .gv.gate-exit{opacity:0}
}
