/*
  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
  Skip and reduced motion 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);
}
@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%,-50%) 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 — Skip, reduced motion 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)}}

/* Skip — appears at 1s. Skips the ANIMATION, never the checkbox. */
.gv-skip{position:absolute;right:18px;bottom:calc(18px + env(safe-area-inset-bottom));
         background:rgba(255,255,255,.75);border:1px solid var(--line);border-radius:980px;
         padding:9px 18px;font-size:13px;font-weight:500;color:var(--ink-2);cursor:pointer;
         opacity:0;pointer-events:none;transition:opacity .25s ease,background .16s ease}
.gv-skip.show{opacity:1;pointer-events:auto}
.gv-skip:hover{background:#fff}
.gv-skip:focus-visible{outline:2px solid var(--blue);outline-offset:3px}

/* ================= 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}
}
