/* ==========================================================================
   Conversion Tracking preview — 16.4s
   --------------------------------------------------------------------------
   Register a tracking site end to end: open Setup, name the site, add a domain,
   create it, take the script, wait for the analytics to load, then read them.

   The typing
   --------------------------------------------------------------------------
   No font is matched and no text is drawn. Each field has two crops of the SAME
   capture (tracking4b): `-full` as shot, and `-empty` with the value's bounding
   box repainted in the field's own interior colour — exact, because both
   interiors measured stddev 0.00. On focus the field cuts to `-empty`, then
   `-full` is revealed left to right with `clip-path` in discrete `steps()`, a
   caret riding the edge. Real glyphs, uncovered rather than synthesised.

   The reveal stops at the value's right edge, not the field's — the Site Name
   value is 179px inside a 1244px field, and its placeholder is LONGER than the
   value. Wiping the whole field would erode the placeholder and send the caret
   racing six times past the end of the text.

   The loading beat
   --------------------------------------------------------------------------
   There is no capture of the app's real loading state, so this is the one place
   something is invented. It is kept to the smallest possible claim: the real
   analytics frame is shown blurred and dimmed while a plain spinner turns, then
   resolves to sharp. No skeleton rows, no fake copy, nothing that asserts a
   layout the product may not have. Swap in a real capture and the spinner and
   the filter track can both go.

   Beats
   --------------------------------------------------------------------------
      0.00 -  2.44%   hold Conversion Tracking, empty
      2.44 -  6.40%   zoom in 1.9x toward 'Setup Conversion Tracking'
      6.40 -  9.45%   cursor travels to it
      9.45 - 10.67%   press + click ring
     10.67 - 14.33%   zoom back out
     13.42 - 16.77%   crossfade to the Create Tracking Site dialog
     16.77 - 18.60%   cursor to the Site Name field
     18.60 - 22.87%   type 'Tracking Web'
     22.87 - 24.09%   cursor to the Allowed Domains field
     24.09 - 28.96%   type 'https://scanova.io'
     28.96 - 31.40%   cursor to + Add
     31.40 - 32.62%   press + click ring
     32.62 - 33.35%   chip appears, counter 1/5, Create enables  (hard cut)
     33.35 - 36.71%   cursor to Create
     36.71 - 37.93%   press + click ring
     37.93 - 41.59%   crossfade to Site Created Successfully
     41.59 - 47.38%   hold — the tracking script and its toggles
     47.38 - 49.82%   cursor to Done
     49.82 - 51.04%   press + click ring
     51.04 - 53.17%   analytics arrive, blurred, spinner turning
     53.17 - 57.74%   loading
     57.74 - 60.49%   resolve — blur clears, spinner fades
     60.49 - 63.54%   hold, top of the analytics
     63.54 - 70.85%   scroll down to engagement, events and the funnel
     70.85 - 76.34%   hold scrolled
     76.34 - 82.44%   scroll back up
     82.44 - 94.63%   hold at the top, 2.0s
     94.63 - 98.29%   crossfade back to the start
     98.29 -  100 %   hold, loop

   Why the chip is a cut and not a crossfade
   --------------------------------------------------------------------------
   Adding the chip makes the dialog 60px taller and it stays centred — the top
   moves up 30px and the bottom down 30px, both measured pixel-exact. A crossfade
   through that shows a double image of the whole dialog. The app reflows
   instantly, so this does too.

   Layer order, bottom to top
   --------------------------------------------------------------------------
     .ct-a          Conversion Tracking, empty. Always opaque; the loop returns
                    to it by fading everything above away.
     .ct-m-blank    the dialog with both fields empty
     .ct-field-*    the two typing fields, over the dialog
     .ct-m-added    chip added, Create enabled
     .ct-success    Site Created Successfully
     .ct-analytics  sticky bar + the scrolling body (filtered while loading)
     .ct-loader     the spinner
     cursor
   ========================================================================== */

.ct-preview {
  /* ---- from build.py ---- */
  --ct-duration: 16.4s;
  --ct-aspect: 1200 / 840;
  --ct-sticky-h: 6.9461%;
  --ct-window-top: 6.7066%;
  --ct-scroll: -42.1678%;        /* 1136px of the strip's own 2694px */

  --ct-setup-x: 90.19%;  --ct-setup-y: 11.92%;
  --ct-add-x: 63.03%;    --ct-add-y: 55.81%;
  --ct-create-x: 63.16%; --ct-create-y: 66.47%;
  --ct-done-x: 63.54%;   --ct-done-y: 84.73%;

  --ct-name-l: 13.747%;  --ct-name-t: 42.575%;
  --ct-name-w: 52.137%;  --ct-name-h: 4.132%;
  --ct-name-reveal: 16.56%;
  --ct-name-hide: 83.44%;        /* 100% - reveal, for clip-path's right inset */
  --ct-dom-l: 13.412%;   --ct-dom-t: 53.353%;
  --ct-dom-w: 46.354%;   --ct-dom-h: 5.090%;
  --ct-dom-reveal: 24.32%;
  --ct-dom-hide: 75.68%;

  /* 'Setup Conversion Tracking' ends at 99.3% of the frame, so the zoom has to
     anchor almost on the right edge or 1.9x clips its label. */
  --ct-zoom-x: 99%;      --ct-zoom-y: 12%;
  --ct-park-x: 60%;      --ct-park-y: 40%;
  --ct-zoom: 1.9;        --ct-zoom-inv: 0.5263;

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

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

.ct-stage {
  position: absolute;
  inset: 0;
  transform-origin: var(--ct-zoom-x) var(--ct-zoom-y);
  animation: ct-zoom var(--ct-duration) infinite;
  will-change: transform;
}

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

.ct-m-blank  { opacity: 0; animation: ct-dialog  var(--ct-duration) infinite; }
.ct-m-added  { opacity: 0; animation: ct-added   var(--ct-duration) infinite; }
.ct-success  { opacity: 0; animation: ct-success var(--ct-duration) infinite; }

/* --- the two typing fields ------------------------------------------------ */

.ct-field { position: absolute; opacity: 0; }
.ct-field img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.ct-field-name {
  left: var(--ct-name-l); top: var(--ct-name-t);
  width: var(--ct-name-w); height: var(--ct-name-h);
  animation: ct-field-name var(--ct-duration) infinite;
}
.ct-field-dom {
  left: var(--ct-dom-l); top: var(--ct-dom-t);
  width: var(--ct-dom-w); height: var(--ct-dom-h);
  animation: ct-field-dom var(--ct-duration) infinite;
}

.ct-field-name .ct-full { animation: ct-type-name var(--ct-duration) infinite; }
.ct-field-dom  .ct-full { animation: ct-type-dom  var(--ct-duration) infinite; }

.ct-caret {
  position: absolute;
  top: 18%; bottom: 18%;
  width: 2px;
  background: #f4f7fb;
  opacity: 0;
}
.ct-field-name .ct-caret { animation: ct-caret-name var(--ct-duration) infinite; }
.ct-field-dom  .ct-caret { animation: ct-caret-dom  var(--ct-duration) infinite; }

/* --- analytics: sticky bar + scrolling body ------------------------------- */

.ct-analytics {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Blurred and dimmed on arrival, resolving as the data "loads". */
  animation: ct-analytics var(--ct-duration) infinite,
             ct-resolve   var(--ct-duration) infinite;
  will-change: opacity, filter;
}
.ct-window {
  position: absolute;
  top: var(--ct-window-top); left: 0; right: 0; bottom: 0;
  overflow: hidden;
}
.ct-strip {
  position: absolute; top: 0; left: 0;
  width: 100%; height: auto; display: block;
  animation: ct-scroll var(--ct-duration) infinite;
  will-change: transform;
}
.ct-sticky {
  position: absolute; top: 0; left: 0;
  width: 100%; height: var(--ct-sticky-h);
  object-fit: fill; display: block;
}

/* --- loading spinner ------------------------------------------------------
   The only invented element in this preview. Deliberately plain — it asserts
   nothing about the product's own loading UI. */

.ct-loader {
  position: absolute;
  /* inset:0 + margin:auto centres exactly on both axes and leaves `transform`
     free for the spin. Percentage margins resolve against the containing
     block's WIDTH on both axes, so the obvious top:50%/margin-top:-N% route
     mis-centres vertically on a non-square frame. */
  inset: 0;
  margin: auto;
  width: 7.5%; aspect-ratio: 1;
  border-radius: 50%;
  /* A masked conic gradient rather than a border: border-width does not accept
     percentages, so `border: 0.5% solid` is an invalid declaration and the whole
     shorthand is dropped — which left this rendering as an invisible empty div.
     This scales with the frame and needs no fixed units. */
  background: conic-gradient(from 0deg,
    rgb(255 255 255 / 0%) 0deg,
    rgb(255 255 255 / 10%) 90deg,
    rgb(255 255 255 / 95%) 330deg,
    rgb(255 255 255 / 0%) 360deg);
  -webkit-mask: radial-gradient(closest-side, transparent 63%, #000 65%);
  mask: radial-gradient(closest-side, transparent 63%, #000 65%);
  opacity: 0;
  animation: ct-loader-fade var(--ct-duration) infinite,
             ct-spin 0.85s linear infinite;
}

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

.ct-cursor-track {
  position: absolute; inset: 0; width: 100%; height: 100%;
  animation: ct-cursor var(--ct-duration) infinite;
  will-change: transform; pointer-events: none;
}
.ct-cursor-scale {
  position: absolute; top: 0; left: 0;
  animation: ct-counterscale var(--ct-duration) infinite;
  transform-origin: 0 0;
}
.ct-arrow {
  display: block; width: var(--ct-cursor-size); height: auto;
  transform-origin: 0 0;
  animation: ct-press var(--ct-duration) infinite;
  filter: drop-shadow(0 1px 3px rgb(0 0 0 / 55%));
  overflow: visible;
}
.ct-ring {
  position: absolute; top: 0; left: 0;
  width: var(--ct-ring-size); height: var(--ct-ring-size);
  margin: calc(var(--ct-ring-size) / -2) 0 0 calc(var(--ct-ring-size) / -2);
  border-radius: 50%;
  border: 2.5px solid rgb(var(--ct-ring-color) / 90%);
  background: radial-gradient(circle, rgb(var(--ct-ring-color) / 30%) 0%,
                                      rgb(var(--ct-ring-color) / 0%) 70%);
  box-shadow: 0 0 0 2px rgb(255 255 255 / 22%);
  opacity: 0;
  animation: ct-ring var(--ct-duration) infinite;
}

/* ==========================================================================
   Keyframes
   ========================================================================== */

@keyframes ct-zoom {
  0%       { transform: scale(1);              animation-timing-function: linear; }
  2.439%   { transform: scale(1);              animation-timing-function: cubic-bezier(.32,0,.24,1); }
  6.402%   { transform: scale(var(--ct-zoom)); animation-timing-function: linear; }
  10.671%  { transform: scale(var(--ct-zoom)); animation-timing-function: cubic-bezier(.32,0,.24,1); }
  14.329%  { transform: scale(1); }
  100%     { transform: scale(1); }
}

@keyframes ct-counterscale {
  0%       { scale: 1;                  animation-timing-function: linear; }
  2.439%   { scale: 1;                  animation-timing-function: cubic-bezier(.32,0,.24,1); }
  6.402%   { scale: var(--ct-zoom-inv); animation-timing-function: linear; }
  10.671%  { scale: var(--ct-zoom-inv); animation-timing-function: cubic-bezier(.32,0,.24,1); }
  14.329%  { scale: 1; }
  100%     { scale: 1; }
}

@keyframes ct-dialog {
  0%, 13.415%  { opacity: 0; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  16.768%      { opacity: 1; }
  53.500%      { opacity: 1; animation-timing-function: steps(1, start); }
  53.600%      { opacity: 0; }
  100%         { opacity: 0; }
}

/* A cut, not a dissolve — the dialog physically reflows when the chip lands. */
@keyframes ct-added {
  0%, 32.622%  { opacity: 0; animation-timing-function: linear; }
  33.354%      { opacity: 1; }
  53.500%      { opacity: 1; animation-timing-function: steps(1, start); }
  53.600%      { opacity: 0; }
  100%         { opacity: 0; }
}

@keyframes ct-success {
  0%, 37.927%  { opacity: 0; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  41.585%      { opacity: 1; }
  53.500%      { opacity: 1; animation-timing-function: steps(1, start); }
  53.600%      { opacity: 0; }
  100%         { opacity: 0; }
}

@keyframes ct-analytics {
  0%, 49.817%  { opacity: 0; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  53.171%      { opacity: 1; }
  94.634%      { opacity: 1; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  98.293%      { opacity: 0; }
  100%         { opacity: 0; }
}

/* Arrives blurred and dim, clears once "loaded". */
@keyframes ct-resolve {
  0%, 57.744%  { filter: blur(10px) brightness(0.45);
                 animation-timing-function: cubic-bezier(.32,0,.24,1); }
  60.488%      { filter: blur(0) brightness(1); }
  100%         { filter: blur(0) brightness(1); }
}

@keyframes ct-loader-fade {
  0%, 50.400%  { opacity: 0; }
  52.000%      { opacity: 1; }
  57.744%      { opacity: 1; animation-timing-function: cubic-bezier(.4,0,.2,1); }
  59.400%      { opacity: 0; }
  100%         { opacity: 0; }
}

@keyframes ct-spin { to { transform: rotate(360deg); } }

/* Down, hold, back up, then a 2s rest at the top. */
@keyframes ct-scroll {
  0%, 63.537%  { transform: translateY(0);                animation-timing-function: cubic-bezier(.36,0,.22,1); }
  70.854%      { transform: translateY(var(--ct-scroll)); animation-timing-function: linear; }
  76.341%      { transform: translateY(var(--ct-scroll)); animation-timing-function: cubic-bezier(.36,0,.22,1); }
  82.439%      { transform: translateY(0); }
  100%         { transform: translateY(0); }
}

/* The field group appears the instant the field takes focus. */
/* These overlay the typed values onto m-blank's empty fields. They may only be
   cut once m-added — which has the same text baked in — is FULLY opaque, at
   33.354%. Cutting at 32.700% as this did left 0.107s where the overlay was gone
   and m-added was 8% in, so the text the user just typed dipped to near-nothing
   and came back. Cut the outgoing layer after the incoming one covers it, never
   before. */
@keyframes ct-field-name {
  0%, 18.300%  { opacity: 0; }
  18.400%      { opacity: 1; animation-timing-function: linear; }
  33.400%      { opacity: 1; animation-timing-function: steps(1, start); }
  33.500%      { opacity: 0; }
  100%         { opacity: 0; }
}
@keyframes ct-field-dom {
  0%, 23.900%  { opacity: 0; }
  24.000%      { opacity: 1; animation-timing-function: linear; }
  33.400%      { opacity: 1; animation-timing-function: steps(1, start); }
  33.500%      { opacity: 0; }
  100%         { opacity: 0; }
}

/* steps() makes the reveal advance character by character. */
@keyframes ct-type-name {
  0%, 18.598%  { clip-path: inset(0 100% 0 0); animation-timing-function: steps(12, end); }
  22.866%      { clip-path: inset(0 var(--ct-name-hide) 0 0); }
  100%         { clip-path: inset(0 var(--ct-name-hide) 0 0); }
}
@keyframes ct-type-dom {
  0%, 24.085%  { clip-path: inset(0 100% 0 0); animation-timing-function: steps(18, end); }
  28.963%      { clip-path: inset(0 var(--ct-dom-hide) 0 0); }
  100%         { clip-path: inset(0 var(--ct-dom-hide) 0 0); }
}

@keyframes ct-caret-name {
  0%, 18.500%  { left: 0%; opacity: 0; }
  18.598%      { left: 0%; opacity: 1; animation-timing-function: steps(12, end); }
  22.866%      { left: var(--ct-name-reveal); opacity: 1; }
  23.900%      { left: var(--ct-name-reveal); opacity: 0; }
  100%         { left: var(--ct-name-reveal); opacity: 0; }
}
@keyframes ct-caret-dom {
  0%, 24.000%  { left: 0%; opacity: 0; }
  24.085%      { left: 0%; opacity: 1; animation-timing-function: steps(18, end); }
  28.963%      { left: var(--ct-dom-reveal); opacity: 1; }
  29.800%      { left: var(--ct-dom-reveal); opacity: 0; }
  100%         { left: var(--ct-dom-reveal); opacity: 0; }
}

@keyframes ct-cursor {
  0%, 2.439%   { transform: translate(var(--ct-park-x), var(--ct-park-y));     animation-timing-function: cubic-bezier(.42,0,.22,1); }
  9.451%       { transform: translate(var(--ct-setup-x), var(--ct-setup-y));   animation-timing-function: linear; }
  14.329%      { transform: translate(var(--ct-setup-x), var(--ct-setup-y));   animation-timing-function: cubic-bezier(.4,0,.3,1); }
  18.598%      { transform: translate(18%, 44.6%);                             animation-timing-function: linear; }   /* Site Name field */
  22.866%      { transform: translate(18%, 44.6%);                             animation-timing-function: cubic-bezier(.4,0,.3,1); }
  24.085%      { transform: translate(17%, 55.4%);                             animation-timing-function: linear; }   /* domain field */
  28.963%      { transform: translate(17%, 55.4%);                             animation-timing-function: cubic-bezier(.42,0,.22,1); }
  31.402%      { transform: translate(var(--ct-add-x), var(--ct-add-y));       animation-timing-function: linear; }
  33.354%      { transform: translate(var(--ct-add-x), var(--ct-add-y));       animation-timing-function: cubic-bezier(.42,0,.22,1); }
  36.707%      { transform: translate(var(--ct-create-x), var(--ct-create-y)); animation-timing-function: linear; }
  41.585%      { transform: translate(var(--ct-create-x), var(--ct-create-y)); animation-timing-function: cubic-bezier(.4,0,.3,1); }
  45.500%      { transform: translate(var(--ct-park-x), var(--ct-park-y));     animation-timing-function: linear; }
  47.378%      { transform: translate(var(--ct-park-x), var(--ct-park-y));     animation-timing-function: cubic-bezier(.42,0,.22,1); }
  49.817%      { transform: translate(var(--ct-done-x), var(--ct-done-y));     animation-timing-function: linear; }
  53.171%      { transform: translate(var(--ct-done-x), var(--ct-done-y));     animation-timing-function: cubic-bezier(.4,0,.3,1); }
  57.744%      { transform: translate(var(--ct-park-x), var(--ct-park-y)); }
  100%         { transform: translate(var(--ct-park-x), var(--ct-park-y)); }
}

@keyframes ct-press {
  0%, 9.150%   { scale: 1; }
  9.451%       { scale: 0.82; }
  10.671%      { scale: 1; }
  31.100%      { scale: 1; }
  31.402%      { scale: 0.82; }
  32.622%      { scale: 1; }
  36.400%      { scale: 1; }
  36.707%      { scale: 0.82; }
  37.927%      { scale: 1; }
  49.510%      { scale: 1; }
  49.817%      { scale: 0.82; }
  51.037%      { scale: 1; }
  100%         { scale: 1; }
}

@keyframes ct-ring {
  0%, 9.350%   { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  9.451%       { opacity: 0.85; scale: 0.30; }
  12.500%      { opacity: 0;    scale: 1; }
  31.300%      { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  31.402%      { opacity: 0.85; scale: 0.30; }
  34.451%      { opacity: 0;    scale: 1; }
  36.600%      { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  36.707%      { opacity: 0.85; scale: 0.30; }
  39.756%      { opacity: 0;    scale: 1; }
  49.710%      { opacity: 0;    scale: 0.30; animation-timing-function: cubic-bezier(.2,.7,.3,1); }
  49.817%      { opacity: 0.85; scale: 0.30; }
  52.866%      { opacity: 0;    scale: 1; }
  100%         { opacity: 0;    scale: 1; }
}

/* --- paused / reduced motion --------------------------------------------- */

.ct-preview.is-paused .ct-stage,
.ct-preview.is-paused .ct-layer,
.ct-preview.is-paused .ct-field,
.ct-preview.is-paused .ct-field img,
.ct-preview.is-paused .ct-caret,
.ct-preview.is-paused .ct-analytics,
.ct-preview.is-paused .ct-strip,
.ct-preview.is-paused .ct-loader,
.ct-preview.is-paused .ct-cursor-track,
.ct-preview.is-paused .ct-cursor-scale,
.ct-preview.is-paused .ct-arrow,
.ct-preview.is-paused .ct-ring {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .ct-stage, .ct-layer, .ct-field, .ct-field img, .ct-caret,
  .ct-analytics, .ct-strip, .ct-loader, .ct-cursor-track, .ct-cursor-scale,
  .ct-arrow, .ct-ring {
    animation: none !important;
  }
  .ct-m-blank, .ct-m-added, .ct-success, .ct-field, .ct-loader { opacity: 0; }
  .ct-analytics    { opacity: 1; filter: none; }   /* the payoff, already loaded */
  .ct-cursor-track { display: none; }
}
