/* ==========================================================================
   Health Center preview
   --------------------------------------------------------------------------
   A seven-beat loop over four screenshot layers, including a real scroll and
   two zoom-in-to-click moments.

   Deliberately self-contained rather than sharing shared/preview.css. That
   stylesheet encodes the two-layer click-and-crossfade timeline the Dark Mode
   previews are built on and signed off against; this sequence is a different
   shape. The duplicated cursor primitives are cheaper than reworking a verified
   file. Worth factoring a shared base out of both once a third shape appears.

   Beats — 12.6s, trimmed from a first pass at 15s
   --------------------------------------------------------------------------
      0.00 -  3.57%   hold the QR Codes list
      3.57 -  9.52%   zoom in 1.9x toward the Health Center button
      9.52 - 14.68%   cursor travels to it
     14.68 - 16.27%   press + click ring
     16.27 - 21.43%   zoom back out
     19.84 - 24.21%   crossfade to the Health Center page
     24.21 - 29.76%   hold — health bar and the five summary cards
     29.76 - 38.49%   scroll down 682px to the Health monitor grid
     38.49 - 45.24%   hold scrolled
     45.24 - 52.78%   scroll back up
     52.78 - 55.16%   settle
     55.16 - 61.11%   zoom in 1.9x toward 'Open top issue'
     61.11 - 65.87%   cursor travels to it
     65.87 - 67.46%   press + click ring
     67.46 - 72.62%   zoom back out
     71.03 - 75.40%   the issue drawer opens
     75.40 - 89.68%   hold the drawer
     89.68 - 94.05%   drawer closes
     94.05 - 98.02%   crossfade back to the QR Codes list
     98.02 -  100 %   hold, loop

   Why transform-origin is animated
   --------------------------------------------------------------------------
   The two buttons are far apart in x. Zooming 1.9x about the Health Center
   button holds only x 37.9-90.5% on screen, and 'Open top issue' runs from
   87.1% to 98.55% — mostly outside it. So each zoom beat needs its own anchor.

   The anchors are not simply the button centres. Anchoring on 'Open top issue's
   centre keeps the frame out to 96.6% only, which clipped its label; it is
   anchored near the corner instead. build.py checks both anchors against the
   buttons' measured extents, so this class of mistake fails the build rather
   than shipping.

   The origin only ever moves during stretches where scale is 1, where changing
   it has no visual effect. The value at 100% matches 0%, so the loop is
   seamless. A sweep of the whole loop confirms the two never overlap.

   Layer order, bottom to top
   --------------------------------------------------------------------------
     .hc-a        frame A, the QR Codes list. Always opaque; everything else
                  fades away over it at the end of the loop.
     .hc-page     the Health Center page, faded in over A. Contains:
                    .hc-window   clipped viewport, starts below the top bar
                      .hc-strip  the tall stitched content, translated in Y
                    .hc-topbar   the app's fixed top bar, drawn over the window
     .hc-d        frame D, the drawer open with its dimmed background
     cursor       pointer, click ring
   ========================================================================== */

.hc-preview {
  /* ---- from build.py; regenerate the layers and these move together ---- */
  --hc-duration: 12.6s;
  --hc-aspect: 1200 / 832;
  --hc-topbar-h: 7.7844%;      /* bar layer, 4px taller than the real bar so it
                                  always covers the scroll window's top edge */
  --hc-window-top: 7.5449%;    /* the real bar height, 126 of 1670 */
  --hc-scroll: -30.6379%;      /* 682px of the strip's own 2226px */
  --hc-hc-x: 80.00%;           /* 'Health Center' button, frame A */
  --hc-hc-y: 10.96%;
  --hc-issue-x: 92.82%;        /* 'Open top issue' button, frame B */
  --hc-issue-y: 10.96%;
  /* The second zoom's anchor is NOT that button's centre. The button runs to
     98.55% of the frame, and anchoring on its centre at 1.9x only holds the
     frame out to 96.6% — which clipped the label to "Open top issi". Anchored
     near the corner instead, so the whole button stays in shot. build.py
     validates both anchors against the buttons' measured extents. */
  --hc-zoom2-x: 98%;
  --hc-zoom2-y: 6%;
  --hc-park-x: 55%;            /* idle position; visible under both zooms */
  --hc-park-y: 45%;
  /* Where the pointer rests once the drawer is open. The drawer's left edge is
     at 58.26%, so anything past that would sit on top of the panel's own text —
     which is what a real cursor would do, but it obscures the thing we're
     showing. It drifts onto the dimmed background instead. */
  --hc-rest-x: 42%;
  --hc-rest-y: 38%;
  --hc-zoom: 1.9;
  --hc-zoom-inv: 0.5263;

  --hc-cursor-size: 18px;
  --hc-ring-size: 30px;
  --hc-ring-color: 216 60 45;
  --hc-radius: 12px;

  position: relative;
  width: 100%;
  aspect-ratio: var(--hc-aspect);
  overflow: hidden;
  border-radius: var(--hc-radius);
  background: #0e1116;
  box-shadow:
    0 0 0 1px rgb(0 0 0 / 30%),
    0 12px 32px -12px rgb(0 0 0 / 45%);
  contain: paint;
}

.hc-stage {
  position: absolute;
  inset: 0;
  /* Overridden by the keyframes; this is the loop's starting origin. Anchoring
     on the button means the control never moves on screen while the surrounding
     UI grows away from it. */
  transform-origin: var(--hc-hc-x) var(--hc-hc-y);
  animation: hc-zoom var(--hc-duration) infinite;
  will-change: transform;
}

/* --- flat full-frame layers ---------------------------------------------- */

.hc-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.hc-d {
  opacity: 0;
  animation: hc-drawer var(--hc-duration) infinite;
  will-change: opacity;
}

/* --- the Health Center page: fixed top bar + scrolling content ----------- */

.hc-page {
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: hc-page-in var(--hc-duration) infinite;
  will-change: opacity;
}

.hc-window {
  position: absolute;
  top: var(--hc-window-top);
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;   /* this is what makes the scroll a scroll */
}

.hc-strip {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;       /* natural height; the translate below is a % of it */
  display: block;
  animation: hc-scroll var(--hc-duration) infinite;
  will-change: transform;
}

.hc-topbar {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--hc-topbar-h);
  object-fit: fill;
  display: block;
}

/* --- cursor -------------------------------------------------------------- */

.hc-cursor-track {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  animation: hc-cursor var(--hc-duration) infinite;
  will-change: transform;
  pointer-events: none;
}

.hc-cursor-scale {
  position: absolute;
  top: 0;
  left: 0;
  animation: hc-cursor-counterscale var(--hc-duration) infinite;
  transform-origin: 0 0;
}

.hc-arrow {
  display: block;
  width: var(--hc-cursor-size);
  height: auto;
  transform-origin: 0 0;
  animation: hc-press var(--hc-duration) infinite;
  filter: drop-shadow(0 1px 3px rgb(0 0 0 / 55%));
  /* The tip is at viewBox 0,0 — this element's own origin — which is what puts
     it exactly on the control. The outer half of the stroke must not clip. */
  overflow: visible;
}

.hc-ring {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--hc-ring-size);
  height: var(--hc-ring-size);
  margin: calc(var(--hc-ring-size) / -2) 0 0 calc(var(--hc-ring-size) / -2);
  border-radius: 50%;
  border: 2.5px solid rgb(var(--hc-ring-color) / 90%);
  background: radial-gradient(circle,
    rgb(var(--hc-ring-color) / 30%) 0%,
    rgb(var(--hc-ring-color) / 0%) 70%);
  box-shadow: 0 0 0 2px rgb(255 255 255 / 22%);
  opacity: 0;
  animation: hc-ring var(--hc-duration) infinite;
}

/* ==========================================================================
   Keyframes. Timing functions declared inside a keyframe govern the segment
   that starts there, which is how one animation gets per-segment easing.
   ========================================================================== */

@keyframes hc-zoom {
  /* --- first zoom, anchored on the Health Center button --- */
  0%       { transform: scale(1);              transform-origin: var(--hc-hc-x) var(--hc-hc-y);
             animation-timing-function: linear; }
  3.571%   { transform: scale(1);              transform-origin: var(--hc-hc-x) var(--hc-hc-y);
             animation-timing-function: cubic-bezier(.32,0,.24,1); }
  9.524%   { transform: scale(var(--hc-zoom)); transform-origin: var(--hc-hc-x) var(--hc-hc-y);
             animation-timing-function: linear; }
  16.270%  { transform: scale(var(--hc-zoom)); transform-origin: var(--hc-hc-x) var(--hc-hc-y);
             animation-timing-function: cubic-bezier(.32,0,.24,1); }
  21.429%  { transform: scale(1);              transform-origin: var(--hc-hc-x) var(--hc-hc-y); }

  /* origin walks to the second button here — scale is 1, so nothing moves */
  29.762%  { transform: scale(1);              transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y); }

  /* --- second zoom, anchored on 'Open top issue' --- */
  55.159%  { transform: scale(1);              transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y);
             animation-timing-function: cubic-bezier(.32,0,.24,1); }
  61.111%  { transform: scale(var(--hc-zoom)); transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y);
             animation-timing-function: linear; }
  67.460%  { transform: scale(var(--hc-zoom)); transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y);
             animation-timing-function: cubic-bezier(.32,0,.24,1); }
  72.619%  { transform: scale(1);              transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y); }
  94.048%  { transform: scale(1);              transform-origin: var(--hc-zoom2-x) var(--hc-zoom2-y); }

  /* and back, so 100% matches 0% and the loop is seamless */
  100%     { transform: scale(1);              transform-origin: var(--hc-hc-x) var(--hc-hc-y); }
}

/* Mirrors hc-zoom's scale exactly, so the pointer keeps a constant on-screen
   size through both zooms. */
@keyframes hc-cursor-counterscale {
  0%       { scale: 1;                  animation-timing-function: linear; }
  3.571%   { scale: 1;                  animation-timing-function: cubic-bezier(.32,0,.24,1); }
  9.524%   { scale: var(--hc-zoom-inv); animation-timing-function: linear; }
  16.270%  { scale: var(--hc-zoom-inv); animation-timing-function: cubic-bezier(.32,0,.24,1); }
  21.429%  { scale: 1;                  animation-timing-function: linear; }
  55.159%  { scale: 1;                  animation-timing-function: cubic-bezier(.32,0,.24,1); }
  61.111%  { scale: var(--hc-zoom-inv); animation-timing-function: linear; }
  67.460%  { scale: var(--hc-zoom-inv); animation-timing-function: cubic-bezier(.32,0,.24,1); }
  72.619%  { scale: 1; }
  100%     { scale: 1; }
}

@keyframes hc-page-in {
  0%,
  19.841%  { opacity: 0; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  24.206%  { opacity: 1; }
  94.048%  { opacity: 1; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  98.016%  { opacity: 0; }
  100%     { opacity: 0; }
}

/* Eased at both ends like a trackpad flick coming to rest, not a linear crawl. */
@keyframes hc-scroll {
  0%,
  29.762%  { transform: translateY(0);                animation-timing-function: cubic-bezier(.36,0,.22,1); }
  38.492%  { transform: translateY(var(--hc-scroll)); animation-timing-function: linear; }
  45.238%  { transform: translateY(var(--hc-scroll)); animation-timing-function: cubic-bezier(.36,0,.22,1); }
  52.778%  { transform: translateY(0); }
  100%     { transform: translateY(0); }
}

@keyframes hc-drawer {
  0%,
  71.032%  { opacity: 0; animation-timing-function: cubic-bezier(.32,0,.24,1); }
  75.397%  { opacity: 1; }
  89.683%  { opacity: 1; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  94.048%  { opacity: 0; }
  100%     { opacity: 0; }
}

@keyframes hc-cursor {
  0%,
  9.524%   { transform: translate(var(--hc-park-x), var(--hc-park-y));   animation-timing-function: cubic-bezier(.42,0,.22,1); }
  14.683%  { transform: translate(var(--hc-hc-x), var(--hc-hc-y));       animation-timing-function: linear; }
  21.429%  { transform: translate(var(--hc-hc-x), var(--hc-hc-y));       animation-timing-function: cubic-bezier(.4,0,.3,1); }
  29.762%  { transform: translate(var(--hc-park-x), var(--hc-park-y));   animation-timing-function: linear; }
  /* still through the scroll, and through the second zoom-in */
  61.111%  { transform: translate(var(--hc-park-x), var(--hc-park-y));   animation-timing-function: cubic-bezier(.42,0,.22,1); }
  65.873%  { transform: translate(var(--hc-issue-x), var(--hc-issue-y)); animation-timing-function: linear; }
  67.460%  { transform: translate(var(--hc-issue-x), var(--hc-issue-y)); animation-timing-function: cubic-bezier(.4,0,.3,1); }
  75.397%  { transform: translate(var(--hc-rest-x), var(--hc-rest-y));   animation-timing-function: linear; }
  89.683%  { transform: translate(var(--hc-rest-x), var(--hc-rest-y));   animation-timing-function: cubic-bezier(.4,0,.3,1); }
  94.048%  { transform: translate(var(--hc-park-x), var(--hc-park-y)); }
  100%     { transform: translate(var(--hc-park-x), var(--hc-park-y)); }
}

@keyframes hc-press {
  0%,
  14.300%  { scale: 1; }
  15.000%  { scale: 0.82; }
  16.270%  { scale: 1; }
  65.500%  { scale: 1; }
  66.200%  { scale: 0.82; }
  67.460%  { scale: 1; }
  100%     { scale: 1; }
}

@keyframes hc-ring {
  0%,
  14.580%  { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  14.683%  { opacity: 0.85; scale: 0.30; }
  18.651%  { opacity: 0;    scale: 1; }
  65.770%  { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  65.873%  { opacity: 0.85; scale: 0.30; }
  69.841%  { opacity: 0;    scale: 1; }
  100%     { opacity: 0;    scale: 1; }
}

/* --- paused, set by the page's IntersectionObserver ---------------------- */

.hc-preview.is-paused .hc-stage,
.hc-preview.is-paused .hc-page,
.hc-preview.is-paused .hc-strip,
.hc-preview.is-paused .hc-d,
.hc-preview.is-paused .hc-cursor-track,
.hc-preview.is-paused .hc-cursor-scale,
.hc-preview.is-paused .hc-arrow,
.hc-preview.is-paused .hc-ring {
  animation-play-state: paused;
}

/* --- reduced motion: hold the Health Center page as a still -------------- */

@media (prefers-reduced-motion: reduce) {
  .hc-stage, .hc-page, .hc-strip, .hc-d,
  .hc-cursor-track, .hc-cursor-scale, .hc-arrow, .hc-ring {
    animation: none !important;
  }
  .hc-page          { opacity: 1; }
  .hc-d             { opacity: 0; }
  .hc-cursor-track  { display: none; }
}
