/*
 * The hero demo. Loaded by index.html alone.
 *
 * Three windows and a mouse, on a stage drawn at a fixed 1080x640 and scaled to
 * whatever room the page has. Everything inside the stage is therefore written
 * in plain design pixels — no clamps, no percentages, no breakpoints — which is
 * what makes the cursor's coordinates and the windows' geometry stable enough to
 * choreograph. hero.js sets --s to the scale factor; the media ladder below is
 * only for the still frame someone with no JavaScript sees.
 *
 * WHAT IS REAL AND WHAT IS NOT
 *
 *  - The ReviewFlow windows are the product. The palette, the type sizes, the
 *    148px record button, the 42px title strip, the 500x600 window and the
 *    312x156 recording panel in the bottom-right corner are all lifted from
 *    src/renderer/styles.css and src/shared/constants.ts. If the app is
 *    re-skinned, the block marked THE PRODUCT is what needs copying across.
 *
 *  - The app being reviewed is not a product. It is skeleton bars and empty
 *    cards in cool grey, chosen to sit as far from ReviewFlow's warm paper as
 *    the page allows: the visitor must never wonder which of the two things on
 *    screen is being sold. Its greys match site/shots/capture-*.svg, which were
 *    drawn for the same purpose.
 *
 * THE FIVE FAULTS
 *
 * Each is one custom property on .app, and each fix is that property changing —
 * so a fault is a number to adjust, a fix is a transition, and nothing has to
 * be kept in step by hand. See THE FAULTS below.
 */

.demo {
  margin: 2rem 0 0;
}

/*
 * The box the stage is scaled into. Clipping here is load-bearing: if the scale
 * is ever wrong — a browser without ResizeObserver, a stylesheet that arrives
 * before the script — the stage crops instead of pushing the page sideways.
 *
 * THE ROOM BELOW THE STAGE
 *
 * The frame is 1080x736 while the stage is 1080x640, so there are 96 design
 * pixels of nothing under the drawing. That space is for the shadows.
 *
 * Every window is placed against the stage's floor — ReviewFlow's 600px window
 * sits at y=24 and the agent's parked box ends at 624, both 16px clear of it —
 * and their shadows need far more than 16px. `0 34px 64px -28px` reaches about
 * 70px below the box it belongs to: the offset carries it down 34, the spread
 * pulls 28 back, and the blur is the rest. Add the entrance and exit
 * transforms, which drop a window another 18 to 30px while it fades, and the
 * worst case is 72px below the stage. Every one of them was being cut in a
 * straight line at 640.
 *
 * 96 rather than 72 because the tail of a Gaussian has no edge, and a hard cut
 * through even a two-percent shadow is visible as a line.
 *
 * Height rather than a clip margin: a margin large enough for 72px would let
 * the shadow paint over the sentence under the frame, which sits 28px below it.
 * Room inside the frame pushes that sentence down instead, which is what the
 * drawing wanted all along. It costs nothing elsewhere — hero.js only ever
 * reads the frame's width, so --s and the 103rem cap are untouched.
 *
 * The clip margin that remains is for the sides, where the worst reach is the
 * agent window's 22px past the right edge, and for the top, where nothing
 * reaches at all. `overflow: hidden` stays above `clip` as the fallback for
 * anything that does not know it.
 */
.demo-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 736;
  overflow: hidden;
  overflow: clip;
  overflow-clip-margin: 32px;
  /* The stage's own windows carry the shadows; this only rounds the crop. */
  border-radius: 14px;
}

.stage {
  position: absolute;
  left: 0;
  top: 0;
  width: 1080px;
  height: 640px;
  transform: scale(var(--s));
  transform-origin: 0 0;

  /*
   * THE PRODUCT — ReviewFlow's derived tokens.
   *
   * The nine palette values are already in styles.css, copied there from the
   * app. These are the ramp the app computes from them, reproduced here so the
   * ReviewFlow windows use the app's own greys rather than the site's.
   */
  --ink-2: color-mix(in srgb, var(--ink) 88%, var(--bg));
  --ink-3: color-mix(in srgb, var(--ink) 74%, var(--bg));
  --ink-4: color-mix(in srgb, var(--ink) 66%, var(--bg));
  --ink-faint: color-mix(in srgb, var(--ink) 40%, var(--bg));
  --accent-ink: color-mix(in srgb, var(--accent), var(--ink) 34%);
  --accent-tint: color-mix(in srgb, var(--accent) 9%, var(--bg));
  --accent-soft: color-mix(in srgb, var(--accent) 28%, var(--bg));
  --surface-hover: color-mix(in srgb, var(--surface), var(--ink) 5%);
  --ink-hover: color-mix(in srgb, var(--ink) 88%, var(--accent));
  --folder-back: color-mix(in srgb, var(--folder), var(--ink) 26%);
  --radius-card: 14px;
  --radius-inner: 11px;
  --radius-pill: 999px;
  --app-font: Nunito, system-ui, -apple-system, "Helvetica Neue", sans-serif;

  /* The app being reviewed. Cool, quiet, and nothing like the above. */
  --g-page: #f6f7f9;
  --g-panel: #ffffff;
  --g-rail: #eceef2;
  --g-line: #dfe3ea;
  --g-bar: #d7dce4;
  --g-bar-2: #c3cad5;
  --g-bar-3: #e3e7ee;
  --g-ctl: #cfd5de;
  --g-text: #8a93a3;

  /*
   * Motion. Two curves and four durations, because a demo where every
   * transition takes the same time reads as a slideshow: the eye needs to be
   * told which movements are the point.
   */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-cur: cubic-bezier(0.5, 0.04, 0.15, 1);
  --t-fast: 180ms;
  --t-mid: 380ms;
  --t-slow: 640ms;
  --t-morph: 720ms;
}

/*
 * Approximate, and only ever seen without JavaScript. hero.js measures the
 * frame and sets --s exactly, on load and on every resize.
 */
.demo { --s: 0.3; }
@media (min-width: 26rem) { .demo { --s: 0.36; } }
@media (min-width: 34rem) { .demo { --s: 0.47; } }
@media (min-width: 44rem) { .demo { --s: 0.62; } }
@media (min-width: 54rem) { .demo { --s: 0.78; } }
@media (min-width: 62rem) { .demo { --s: 0.86; } }

.demo-note {
  margin: 0.9rem 0 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ink-55);
  max-width: var(--measure);
}

/* Once the animation is running it says all of this and better. */
.demo[data-live] .demo-note {
  display: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==================================================================== *
 * GEOMETRY
 *
 * Every window's box is four custom properties, and a phase is a set of new
 * values for them. Nothing else moves windows, so retiming or repositioning a
 * beat means editing numbers in this block.
 * ==================================================================== */

.stage {
  /* The browser: desktop by default, phone width under [data-mobile]. */
  --bw-x: 20px;
  --bw-y: 16px;
  --bw-w: 900px;
  --bw-h: 584px;

  /* ReviewFlow. WINDOW_SIZE is 500x600; OVERLAY_SIZE is 312x156, margin 24. */
  --rf-x: 748px;
  --rf-y: 464px;
  --rf-w: 312px;
  --rf-h: 156px;

  --cc-x: 588px;
  --cc-y: 140px;
  --cc-w: 460px;
  --cc-h: 330px;
}

/* Centred on the desktop window it shrank out of, not pinned to one side. */
.stage[data-mobile] {
  --bw-x: 270px;
  --bw-w: 400px;
}

/* Before recording: the full window, floating over the browser's right side. */
.stage[data-rf="off"],
.stage[data-rf="full"] {
  --rf-x: 548px;
  --rf-y: 24px;
  --rf-w: 500px;
  --rf-h: 600px;
}

/* After: the same window again, moved left to leave the agent somewhere to be. */
.stage[data-rf="review"],
.stage[data-rf="exit"] {
  --rf-x: 40px;
  --rf-y: 24px;
  --rf-w: 500px;
  --rf-h: 600px;
}

/* Done editing, and out of the way of the app it just fixed. */
.stage[data-cc="aside"] {
  --cc-x: 676px;
  --cc-y: 348px;
  --cc-w: 392px;
  --cc-h: 276px;
}

/* ==================================================================== *
 * THE GENERIC BROWSER
 * ==================================================================== */

.bw {
  position: absolute;
  left: var(--bw-x);
  top: var(--bw-y);
  width: var(--bw-w);
  height: var(--bw-h);
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  background: var(--g-page);
  box-shadow:
    0 24px 50px -26px rgba(46, 38, 33, 0.42),
    0 3px 10px -4px rgba(46, 38, 33, 0.12);
  transition:
    left var(--t-morph) var(--ease),
    width var(--t-morph) var(--ease),
    opacity var(--t-slow) ease;
}

/*
 * Pushed back for the two beats that are about ReviewFlow and the agent rather
 * than about the app: the write-up, and the hand-off. It returns to full
 * strength for the fixes, which are the one thing the visitor must not miss —
 * which is why this keys off the phase and not off ReviewFlow's shape.
 */
.stage[data-phase="write"] .bw,
.stage[data-phase="hand"] .bw {
  opacity: 0.45;
}

.bw-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 14px;
  background: #f0f2f5;
  border-bottom: 1px solid var(--g-line);
  border-radius: 12px 12px 0 0;
}

/*
 * Grey, not the real traffic-light colours. Three coloured dots are the
 * brightest thing in a picture this pale, and they would be the brightest thing
 * on a stage whose one accent colour belongs to ReviewFlow.
 */
.bw-dots {
  display: flex;
  gap: 6px;
  flex: none;
}

.bw-dots i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #d5dae1;
}

.bw-nav {
  display: flex;
  gap: 10px;
  flex: none;
}

.bw-nav i {
  width: 7px;
  height: 7px;
  border-left: 1.5px solid #b9c1cc;
  border-bottom: 1.5px solid #b9c1cc;
  transform: rotate(45deg);
}

.bw-nav i:last-child {
  transform: rotate(-135deg);
}

.bw-url {
  flex: 1;
  max-width: 260px;
  margin: 0 auto;
  padding: 5px 12px;
  background: var(--g-panel);
  border: 1px solid var(--g-line);
  border-radius: var(--radius-pill);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.4;
  color: var(--g-text);
  text-align: center;
}

/*
 * clip-path rather than overflow, because the phone beat needs the clip open on
 * one side only. A negative inset extends the clip outwards, so the row that is
 * too wide for the screen can be seen leaving it — which is the whole point of
 * that beat, and something `overflow: hidden` would quietly delete.
 */
.bw-view {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--g-page);
  border-radius: 0 0 12px 12px;
  clip-path: inset(0 0 0 0);
  transition: clip-path var(--t-morph) var(--ease);
}

.stage[data-mobile] .bw-view {
  clip-path: inset(0 -300px 0 0);
}

/* Where the screen ends. Only drawn when there is a screen edge to overrun. */
.bw-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  border-right: 2px dashed var(--accent);
  opacity: 0;
  transition: opacity var(--t-mid) ease;
}

.stage[data-mobile] .bw-edge {
  opacity: 0.65;
}

/* ==================================================================== *
 * THE SKELETON APP
 * ==================================================================== */

.app {
  display: flex;
  height: 100%;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ---- THE FAULTS. Five numbers, one per issue. ---- */
  --cta-drop: 13px; /* 1. the button sits below the heading beside it */
  --gap-a: 7px; /* 2. the gaps in the card row disagree */
  --gap-b: 35px;
  --pad-tight: 4px; /* 3. one card is short of internal padding */
  --ctl-odd: 46px; /* 4. one control in the group is taller */
  --tbl-min: 520px; /* 5. this row cannot fit a phone and does not adapt */
}

/* ---- and the fixes, applied one at a time by hero.js ---- */
.app.fix-align { --cta-drop: 0px; }
.app.fix-gaps { --gap-a: 18px; --gap-b: 18px; }
.app.fix-pad { --pad-tight: 16px; }
.app.fix-ctl { --ctl-odd: 32px; }
.app.fix-ovf { --tbl-min: 0px; }

/*
 * The dip the loop restores the faults behind. Three hundred milliseconds of
 * nothing is cheaper than showing five fixes being undone, which would read as
 * the demo breaking the app it just finished repairing.
 */
.app {
  transition: opacity 300ms ease;
}

.app.is-swap {
  opacity: 0;
}

.ap-side {
  flex: none;
  width: 96px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 14px 11px;
  background: var(--g-rail);
  border-right: 1px solid var(--g-line);
}

.ap-logo {
  width: 24px;
  height: 24px;
  margin-bottom: 12px;
  border-radius: 7px;
  background: var(--g-ctl);
}

.ap-nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ap-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 7px;
  border-radius: 7px;
}

.ap-item i {
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--g-ctl);
}

.ap-item b {
  height: 7px;
  width: 36px;
  border-radius: 4px;
  background: var(--g-bar);
}

.ap-item:nth-child(3) b { width: 28px; }
.ap-item:nth-child(4) b { width: 40px; }
.ap-item:nth-child(5) b { width: 31px; }

.ap-item.is-on {
  background: var(--g-panel);
}

.ap-item-foot {
  margin-top: auto;
}

.ap-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ap-top {
  flex: none;
  display: flex;
  align-items: center;
  gap: 14px;
  height: 42px;
  padding: 0 18px;
  background: var(--g-panel);
  border-bottom: 1px solid var(--g-line);
}

.ap-search {
  width: 150px;
  height: 22px;
  margin-left: auto;
  border-radius: var(--radius-pill);
  background: var(--g-page);
  border: 1px solid var(--g-line);
}

.ap-avatar {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--g-ctl);
}

/*
 * Overflow hidden, and the body slides inside it. On the phone the stack is
 * taller than the screen, so the demo scrolls to the row it wants to talk about
 * — which is what a person reviewing on a phone does. hero.js measures the
 * offset rather than guessing it.
 */
.ap-body {
  flex: 1;
  min-height: 0;
  padding: 18px;
  --app-roll: 0px;
}

.ap-roll {
  display: flex;
  flex-direction: column;
  gap: 16px;
  transform: translateY(var(--app-roll));
  transition: transform var(--t-morph) var(--ease);
}

/* ---- skeleton bars ---- */

.sk {
  height: 9px;
  border-radius: 4px;
  background: var(--g-bar-3);
}

.sk-xs { width: 34px; }
.sk-s { width: 62px; }
.sk-m { width: 96px; }
.sk-l { width: 168px; }

/* ---- fault 1: the heading and its call to action ---- */

.ap-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex: none;
}

.ap-head-txt {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

/*
 * The only readable word in the app, and it is there so the visitor can stop
 * wondering what they are looking at rather than start reading.
 */
.ap-h {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--g-text);
}

.ap-cta {
  flex: none;
  width: 112px;
  height: 32px;
  border-radius: 8px;
  background: var(--g-ctl);
  transform: translateY(var(--cta-drop));
  transition: transform var(--t-slow) var(--ease);
}

/* ---- faults 2 and 3: the card row ---- */

.ap-cards {
  display: flex;
  align-items: stretch;
  flex: none;
}

/*
 * A percentage basis, not zero.
 *
 * Under `flex-basis: 0` with border-box sizing, a flex item's basis is floored
 * at its own padding — so the card with less padding came out twenty pixels
 * narrower than its neighbours, and fault 3 read as a width problem rather than
 * a padding one. A percentage basis puts the padding inside the box, which
 * leaves all three the same width and makes the only visible difference the one
 * the reviewer is talking about.
 */
.ap-card {
  flex: 1 1 33.333%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 16px;
  background: var(--g-panel);
  border: 1px solid var(--g-line);
  border-radius: 10px;
  transition:
    margin var(--t-slow) var(--ease),
    padding var(--t-slow) var(--ease);
}

.ap-card i {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--g-bar);
}

.ap-card .sk-m {
  height: 12px;
  background: var(--g-bar-2);
}

.ap-card:nth-child(2) { margin-left: var(--gap-a); }
.ap-card:nth-child(3) { margin-left: var(--gap-b); }

.ap-card-tight {
  padding: var(--pad-tight);
}

/* ---- fault 4: the control group ---- */

.ap-ctl {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

.ap-seg {
  flex: none;
  display: flex;
  height: 32px;
  border-radius: 8px;
  background: var(--g-rail);
  border: 1px solid var(--g-line);
  overflow: hidden;
}

.ap-seg i {
  width: 34px;
  border-right: 1px solid var(--g-line);
}

.ap-seg i:first-child { background: var(--g-panel); }
.ap-seg i:last-child { border-right: 0; }

.ap-field {
  flex: none;
  display: flex;
  align-items: center;
  width: 132px;
  height: 32px;
  padding: 0 11px;
  background: var(--g-panel);
  border: 1px solid var(--g-ctl);
  border-radius: 8px;
  transition: height var(--t-slow) var(--ease);
}

.ap-field-odd {
  width: 108px;
  height: var(--ctl-odd);
}

.ap-go {
  flex: none;
  width: 88px;
  height: 32px;
  border-radius: 8px;
  background: var(--g-ctl);
}

/* ---- fault 5: the row that will not fit a phone ---- */

.ap-tbl {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: var(--tbl-min);
  transition: min-width var(--t-slow) var(--ease);
}

.ap-tr {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 30px;
  padding: 0 12px;
  background: var(--g-panel);
  border: 1px solid var(--g-line);
  border-radius: 7px;
}

.ap-tr i {
  flex: 1;
  height: 8px;
  border-radius: 4px;
  background: var(--g-bar-3);
}

.ap-tr i:first-child { flex: 1.6; }
.ap-tr i:last-child { flex: 0.6; }

.ap-th {
  background: var(--g-page);
  border-color: transparent;
}

.ap-th i {
  background: var(--g-bar);
}

/* ---- the phone. Everything here responds properly except .ap-tbl ---- */

.stage[data-mobile] .ap-side {
  display: none;
}

.stage[data-mobile] .ap-cards {
  flex-direction: column;
}

.stage[data-mobile] .ap-card:nth-child(2),
.stage[data-mobile] .ap-card:nth-child(3) {
  margin-left: 0;
  margin-top: 10px;
}

.stage[data-mobile] .ap-ctl {
  flex-wrap: wrap;
}

.stage[data-mobile] .ap-search {
  width: 96px;
}

/* ==================================================================== *
 * THE PRODUCT — ReviewFlow
 *
 * One box for both shapes. The app resizes its own window when recording
 * starts, so the demo does the same rather than swapping in a second window,
 * and the two sets of contents cross-fade inside it: the full window fades out
 * in a fifth of a second while the box takes three quarters of one to travel,
 * so nothing is ever seen reflowing at a width it was not designed for.
 * ==================================================================== */

.rf {
  position: absolute;
  left: var(--rf-x);
  top: var(--rf-y);
  width: var(--rf-w);
  height: var(--rf-h);
  z-index: 3;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid color-mix(in srgb, var(--ink) 14%, var(--bg));
  border-radius: 12px;
  font-family: var(--app-font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  box-shadow:
    0 34px 64px -28px rgba(46, 38, 33, 0.5),
    0 5px 14px -6px rgba(46, 38, 33, 0.18);
  transition:
    left var(--t-morph) var(--ease),
    top var(--t-morph) var(--ease),
    width var(--t-morph) var(--ease),
    height var(--t-morph) var(--ease),
    opacity var(--t-slow) ease,
    transform var(--t-slow) var(--ease),
    box-shadow var(--t-mid) ease;
}

.stage[data-rf="off"] .rf {
  opacity: 0;
  transform: translateY(18px) scale(0.96);
}

.stage[data-rf="exit"] .rf {
  opacity: 0;
  transform: translate(-46px, 30px) scale(0.96);
}

/*
 * A capture landing. The one signal the recording panel gives while it is
 * working, and deliberately the smallest one available: the window it lives in
 * takes the accent for a moment. Anything larger would be a second product
 * telling you about itself while you are trying to review something else.
 */
.rf.is-caught {
  border-color: var(--accent);
  box-shadow:
    0 0 0 3px var(--accent-tint),
    0 34px 64px -28px rgba(46, 38, 33, 0.5);
}

.rf-app {
  position: absolute;
  left: 0;
  top: 0;
  width: 500px;
  height: 600px;
  display: flex;
  flex-direction: column;
  transition: opacity var(--t-fast) ease;
}

.rf-ov {
  position: absolute;
  left: 0;
  top: 0;
  width: 312px;
  height: 156px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  opacity: 0;
  transition: opacity var(--t-fast) ease;
}

.stage[data-rf="full"] .rf-ov,
.stage[data-rf="review"] .rf-ov,
.stage[data-rf="exit"] .rf-ov {
  opacity: 0;
}

.stage[data-rf="full"] .rf-app,
.stage[data-rf="review"] .rf-app,
.stage[data-rf="exit"] .rf-app {
  opacity: 1;
}

.stage[data-rf="ov"] .rf-app {
  opacity: 0;
}

/* Arrives once the box has almost finished shrinking, never during. */
.stage[data-rf="ov"] .rf-ov {
  opacity: 1;
  transition-delay: 420ms;
}

/* ---- the app's own title strip ---- */

.rf-title {
  flex: none;
  display: flex;
  align-items: center;
  gap: 9px;
  height: 42px;
  padding-left: 14px;
  background: var(--bg);
}

.rf-mark {
  width: 19px;
  height: 19px;
  flex: none;
}

.rf-name {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.015em;
  color: var(--ink-2);
}

.rf-gear {
  display: grid;
  place-items: center;
  width: 38px;
  height: 34px;
  margin-left: auto;
  margin-right: 8px;
  border-radius: 8px;
  color: var(--ink-4);
}

.rf-gear svg { display: block; }

/* ---- views ---- */

.rf-view {
  position: absolute;
  left: 0;
  right: 0;
  top: 42px;
  bottom: 0;
  padding: 30px 38px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 190ms ease;
}

/*
 * Out, then in, rather than a cross-fade. Two views crossing at half opacity
 * put one paragraph on top of another, which is the first thing anyone would
 * notice in a still of this moment.
 */
.rf[data-view="idle"] .rf-idle,
.rf[data-view="working"] .rf-working,
.rf[data-view="review"] .rf-review {
  opacity: 1;
  transition-duration: 300ms;
  transition-delay: 190ms;
}

.rf-h1 {
  margin: 0;
  font-size: 29px;
  line-height: 1.18;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.rf-h2 {
  margin: 0;
  font-size: 22px;
  line-height: 1.25;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.rf-lede {
  margin: 8px 0 0;
  color: var(--ink-4);
  font-size: 14.5px;
  line-height: 1.55;
}

/* ---- before recording ---- */

.rf-ring {
  position: relative;
  display: grid;
  place-items: center;
  margin: 22px auto 0;
  width: 148px;
  height: 148px;
}

.rf-record {
  display: grid;
  place-items: center;
  width: 148px;
  height: 148px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--accent);
  transition: transform var(--t-fast) ease, background var(--t-fast) ease;
}

.rf-record svg { display: block; }

/* The press, and the ring the app throws off as the recording begins. */
.rf-ring.is-hit .rf-record {
  transform: scale(0.97);
  background: var(--ink-hover);
}

.rf-ring.is-hit::after {
  content: "";
  position: absolute;
  width: 148px;
  height: 148px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  animation: rf-launch 460ms ease-out forwards;
}

@keyframes rf-launch {
  from { transform: scale(0.96); opacity: 0.55; }
  to { transform: scale(1.34); opacity: 0; }
}

.rf-last {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 22px;
}

.rf-rule {
  height: 1px;
  background: var(--line);
}

.rf-label {
  color: var(--ink-4);
  font-size: 13px;
  font-weight: 600;
}

.rf-litem {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 9px 14px;
  border-radius: var(--radius-inner);
}

.rf-ltitle {
  font-size: 14px;
  color: var(--ink-2);
}

.rf-lmeta {
  font-size: 11.5px;
  color: var(--ink-4);
}

/* ---- while it is written up ---- */

.rf-steps {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.rf-steps li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 14px;
  border-radius: var(--radius-inner);
  font-size: 14.5px;
  color: var(--ink-faint);
}

.rf-mark-step {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 2px solid var(--line);
  border-radius: 50%;
  font-size: 12px;
  font-weight: 800;
}

.rf-steps li.done { color: var(--ink-4); }

.rf-steps li.done .rf-mark-step {
  border-color: transparent;
  background: var(--surface);
  color: var(--ink-3);
}

.rf-steps li.done .rf-mark-step::after { content: "\2713"; }

.rf-steps li.active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 700;
}

.rf-steps li.active .rf-mark-step {
  border-width: 2.5px;
  border-color: var(--accent-soft);
  border-top-color: var(--accent);
  animation: rf-spin 900ms linear infinite;
}

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

.rf-track {
  margin-top: 32px;
  height: 5px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  overflow: hidden;
}

.rf-fill {
  display: block;
  width: 12%;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width 900ms ease;
}

.rf[data-view="working"] .rf-fill { width: 84%; }

/* ---- the finished review ---- */

.rf-review {
  display: flex;
  flex-direction: column;
  padding: 30px 38px 0;
}

.rf-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}

.rf-back,
.rf-new {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-radius: var(--radius-pill);
  font-weight: 700;
}

.rf-back {
  padding: 10px 18px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13.5px;
}

.rf-new {
  padding: 9px 14px;
  background: var(--ink);
  color: var(--on-ink);
  font-size: 12.5px;
}

.rf-new svg { color: var(--accent); flex: none; }
.rf-back svg { flex: none; }

.rf-scroll {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/*
 * Moved rather than scrolled: a real scrollbar would need a real scroll event,
 * and the demo has no pointer. The duration is long and the curve is nearly
 * linear, because this is the beat where the visitor counts the captures.
 */
.rf-roll {
  transform: translateY(var(--roll, 0px));
  transition: transform 3400ms cubic-bezier(0.4, 0, 0.5, 1);
}

.rf-rtitle {
  margin: 0 0 6px;
  font-size: 25px;
  font-weight: 650;
  line-height: 1.24;
  letter-spacing: 0;
}

.rf-moments {
  display: flex;
  flex-direction: column;
  gap: 26px;
  margin: 26px 0 0;
}

.rf-moment {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.rf-mtime {
  color: var(--ink-4);
  font-size: 12px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

.rf-mtext {
  margin: 0;
  font-size: 16px;
  line-height: 1.68;
}

.rf-mtext mark {
  background: var(--accent-tint);
  color: inherit;
  border-radius: 3px;
  padding: 0.1em 0.16em;
  margin: 0 -0.04em;
  text-decoration: underline 2px var(--accent-soft);
  text-underline-offset: 3px;
}

.rf-shot {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-inner);
}

/*
 * Sticky in the app for the same reason it is pinned here: a long review would
 * otherwise scroll the folder — the point of the whole screen — out of reach.
 */
.rf-dock {
  flex: none;
  padding: 16px 0 20px;
  background: linear-gradient(180deg, transparent, var(--bg) 30%);
}

.rf-drag {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-card);
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--ink) 7%, transparent),
    0 6px 16px -6px color-mix(in srgb, var(--ink) 22%, transparent);
  transition: border-color var(--t-fast) ease, transform var(--t-fast) ease, opacity var(--t-mid) ease;
}

.rf-drag.is-lift {
  border-color: var(--accent);
  transform: translateY(-1px);
}

/* Once it has been picked up, the card is empty — the folder is on the cursor. */
.rf-drag.is-gone {
  opacity: 0.4;
}

.rf-folder {
  flex: none;
  display: block;
  transform: rotate(-4deg);
  filter: drop-shadow(0 2px 3px color-mix(in srgb, var(--ink) 22%, transparent));
  transition: transform var(--t-fast) ease, opacity var(--t-fast) ease;
}

.rf-drag.is-lift .rf-folder { transform: rotate(-7deg) translateY(-1px); }
.rf-drag.is-gone .rf-folder { opacity: 0; }

.rf-fback { fill: var(--folder-back); }
.rf-ffront { fill: var(--folder); }

.rf-dlabel {
  flex: 1;
  min-width: 0;
  color: var(--ink-2);
  font-size: 13.5px;
  font-weight: 700;
}

.rf-dacts {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12.5px;
  font-weight: 700;
  font-style: normal;
}

.rf-dacts i { color: var(--ink-4); font-style: normal; }
.rf-dacts b { color: var(--accent-ink); font-weight: 700; }

/* ---- the recording panel ---- */

.rf-ov-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.rf-live {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--accent-ink);
  font-size: 12.5px;
  font-weight: 800;
}

.rf-dot {
  position: relative;
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}

.rf-dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border: 1px solid var(--accent-soft);
  border-radius: 50%;
  animation: rf-pulse 2s ease-out infinite;
}

@keyframes rf-pulse {
  from { transform: scale(1); opacity: 0.45; }
  70% { transform: scale(1.7); opacity: 0; }
  to { opacity: 0; }
}

.rf-timer {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 16px;
  font-weight: 500;
}

.rf-ov-meter {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: var(--radius-inner);
  background: var(--surface);
}

.rf-meter {
  flex: 1;
  min-width: 0;
  height: 8px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface), var(--ink) 8%);
  overflow: hidden;
}

.rf-meter i {
  display: block;
  width: var(--lvl, 8%);
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width 70ms linear;
}

.rf-hint {
  flex: none;
  color: var(--ink-4);
  font-size: 11.5px;
  font-weight: 700;
}

.rf-stop {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  padding: 11px 16px;
  background: var(--ink);
  color: var(--on-ink);
  border-radius: var(--radius-pill);
  font-size: 13.5px;
  font-weight: 700;
  transition: transform var(--t-fast) ease;
}

.rf-stop i {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--accent);
}

.rf-stop.is-hit { transform: scale(0.96); }

/* ==================================================================== *
 * THE CODING AGENT
 *
 * A terminal, simplified to the two facts that matter here: it accepts a
 * folder, and it edits files. Warm dark rather than a blue-black, so it reads
 * as belonging to the same page.
 * ==================================================================== */

.cc {
  position: absolute;
  left: var(--cc-x);
  top: var(--cc-y);
  width: var(--cc-w);
  height: var(--cc-h);
  z-index: 4;
  overflow: hidden;
  background: #251e19;
  border-radius: 12px;
  opacity: 0;
  transform: translateY(22px) scale(0.97);
  box-shadow:
    0 34px 60px -26px rgba(46, 38, 33, 0.6),
    0 4px 12px -6px rgba(46, 38, 33, 0.3);
  transition:
    left var(--t-morph) var(--ease),
    top var(--t-morph) var(--ease),
    width var(--t-morph) var(--ease),
    height var(--t-morph) var(--ease),
    opacity var(--t-slow) ease,
    transform var(--t-slow) var(--ease);
}

.stage[data-cc="on"] .cc,
.stage[data-cc="aside"] .cc {
  opacity: 1;
  transform: none;
}

.cc-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 13px;
  background: #302720;
}

.cc-dots {
  display: flex;
  gap: 6px;
  flex: none;
}

.cc-dots i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #4c3f36;
}

.cc-title {
  font-family: var(--mono);
  font-size: 11px;
  color: #9b8b7e;
}

.cc-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.6;
  color: #d3c7bc;
}

/*
 * The prompt it sits at while it has been given nothing.
 *
 * This is what the window shows on arrival. It used to show the drop target with
 * a faded folder and the review's name already in it, which said the terminal
 * had the folder before anybody handed it over — and made the drag that follows
 * a formality.
 */
.cc-idle {
  display: flex;
  align-items: center;
  gap: 9px;
  color: #9b8b7e;
}

.cc-caret {
  width: 7px;
  height: 14px;
  background: #6b5b50;
  animation: cc-blink 1.1s steps(1) infinite;
}

@keyframes cc-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/*
 * The target exists only once something is over it, which is how a drop target
 * behaves anywhere else. Its contents — the folder and its name — wait until the
 * folder has actually landed, so at `over` this is an empty outline: somewhere
 * for the thing on the cursor to go, and no claim to already have it.
 */
.cc-drop {
  display: none;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1.5px dashed #4c3f36;
  border-radius: 10px;
  color: #9b8b7e;
  transition: border-color var(--t-fast) ease, background var(--t-fast) ease, color var(--t-fast) ease;
}

.cc-drop svg { flex: none; }

.cc[data-drop="over"] .cc-idle,
.cc[data-drop="done"] .cc-idle {
  display: none;
}

.cc[data-drop="over"] .cc-drop,
.cc[data-drop="done"] .cc-drop {
  display: flex;
}

/* The cursor is over it with the folder in hand, and the box is still empty. */
.cc[data-drop="over"] .cc-drop {
  border-color: var(--accent);
  background: rgba(255, 90, 43, 0.1);
  color: #e8d9cd;
}

/* `visibility`, not `display`: the outline has to keep the size it will fill. */
.cc[data-drop="over"] .cc-drop > * {
  visibility: hidden;
}

.cc[data-drop="done"] .cc-drop {
  border-style: solid;
  border-color: #4c3f36;
  background: rgba(255, 255, 255, 0.03);
  color: #e8d9cd;
}

.cc-log {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.cc-log p {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 9px;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--t-mid) ease, transform var(--t-mid) var(--ease);
}

.cc-log p.on {
  opacity: 1;
  transform: none;
}

.cc-log em {
  font-style: normal;
  color: #96887c;
}

.cc-cue { color: #96887c; }
.cc-ok { color: var(--accent); }

.cc-run {
  flex: none;
  width: 10px;
  height: 10px;
  border: 1.5px solid #4c3f36;
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: rf-spin 800ms linear infinite;
}

/* Finished lines stop advertising that they are working. */
.cc-log p.done .cc-run {
  border-color: #4c3f36;
  animation: none;
}

/* ==================================================================== *
 * WHAT THE DEMO DOES WITH THE MOUSE
 * ==================================================================== */

.cur {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 9;
  transform: translate(var(--cx, 900px), var(--cy, 300px));
  transition: transform var(--cms, 900ms) var(--ease-cur);
  filter: drop-shadow(0 2px 3px rgba(46, 38, 33, 0.3));
}

.cur svg { display: block; }

/* A press, shown as a ring leaving the point rather than as a moving cursor. */
.cur::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 2px;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  opacity: 0;
}

.cur.is-click::after {
  animation: cur-click 420ms ease-out;
}

@keyframes cur-click {
  from { transform: scale(0.6); opacity: 0.9; }
  to { transform: scale(3.4); opacity: 0; }
}

/*
 * The capture marker. The ring, the four ticks and the dot are what
 * cursor-overlay.ts burns into every screenshot ReviewFlow produces, so the
 * capture the visitor watches happen looks like the capture they will open
 * later.
 */
.mark {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 7;
  transform: translate(calc(var(--mx, 0px) - 60px), calc(var(--my, 0px) - 60px));
  opacity: 0;
}

.mark svg { display: block; }

/*
 * Long enough to be read. At 900ms it was at full strength for four hundred of
 * them, which is a flicker rather than a beat — and this is the moment the
 * whole product is about.
 */
.mark.on {
  animation: mark-in 1500ms var(--ease) forwards;
}

@keyframes mark-in {
  0% { opacity: 0; scale: 1.35; }
  13% { opacity: 1; scale: 1; }
  80% { opacity: 1; scale: 1; }
  100% { opacity: 0; scale: 1.05; }
}

/* The region that went into the picture. Sized by hero.js from the real target. */
.frame {
  position: absolute;
  left: var(--fx, 0px);
  top: var(--fy, 0px);
  width: var(--fw, 0px);
  height: var(--fh, 0px);
  z-index: 2;
  border: 2px solid var(--accent);
  border-radius: 8px;
  opacity: 0;
  pointer-events: none;
}

.frame.on {
  animation: frame-in 1600ms var(--ease) forwards;
}

@keyframes frame-in {
  0% { opacity: 0; scale: 1.03; }
  11% { opacity: 1; scale: 1; }
  78% { opacity: 1; }
  100% { opacity: 0; }
}

/*
 * The reviewer's voice. ReviewFlow's ink rather than the app's grey, because
 * the words belong to the product: they are the thing being recorded, and on a
 * stage this pale a dark pill is the only place the eye will go.
 */
.say {
  position: absolute;
  left: var(--sx, 0px);
  top: var(--sy, 0px);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  background: var(--ink);
  color: var(--on-ink);
  border-radius: var(--radius-pill);
  font-family: var(--app-font);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  box-shadow: 0 12px 26px -12px rgba(46, 38, 33, 0.55);
  opacity: 0;
  transform: translateY(6px) scale(0.97);
  transform-origin: 20% 100%;
  transition:
    opacity var(--t-mid) ease,
    transform var(--t-mid) var(--ease),
    left var(--t-slow) var(--ease),
    top var(--t-slow) var(--ease);
}

.say.on {
  opacity: 1;
  transform: none;
}

.say-mic {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--accent);
}

.say-mic svg { display: block; }

.say-lvl {
  flex: none;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 15px;
}

.say-lvl i {
  width: 2px;
  height: 4px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0.75;
}

.say.on .say-lvl i {
  animation: say-lvl 780ms ease-in-out infinite;
}

.say-lvl i:nth-child(2) { animation-delay: 120ms; }
.say-lvl i:nth-child(3) { animation-delay: 250ms; }
.say-lvl i:nth-child(4) { animation-delay: 90ms; }
.say-lvl i:nth-child(5) { animation-delay: 320ms; }

@keyframes say-lvl {
  0%, 100% { height: 4px; }
  50% { height: 13px; }
}

/* Word by word, so it reads as speech arriving rather than a label appearing. */
.say-text span {
  display: inline-block;
  opacity: 0;
  transform: translateY(3px);
  animation: say-word 260ms var(--ease) forwards;
}

@keyframes say-word {
  to { opacity: 1; transform: none; }
}

/* The folder, once it is on the cursor. */
.ghost {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 9px;
  background: var(--surface-2);
  border: 1.5px solid var(--accent);
  border-radius: 10px;
  font-family: var(--app-font);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-2);
  white-space: nowrap;
  box-shadow: 0 10px 22px -10px rgba(46, 38, 33, 0.5);
  transform: translate(var(--gx, 0px), var(--gy, 0px)) rotate(-3deg);
  transition: transform var(--cms, 900ms) var(--ease-cur), opacity var(--t-fast) ease;
  opacity: 0;
  pointer-events: none;
}

.ghost::after {
  content: "review-spacing-and-alignment";
  font-family: var(--mono);
  font-weight: 500;
  color: var(--ink-4);
}

.ghost.on { opacity: 1; }


/* ==================================================================== *
 * THE NUMBERED MARKERS
 *
 * What ReviewFlow has taken note of, left where the pointer was when it took
 * note. They accumulate through the review — one, two, three, four, five —
 * because the claim being made is not that a screenshot was taken but that
 * nobody had to stop and take one.
 *
 * Each is a child of the element it is about, positioned by an offset inside it
 * rather than by a point on the stage. That is what lets a mark stay on the
 * thing it marked while the browser narrows to a phone, the page scrolls under
 * it and the fault underneath it is repaired.
 *
 * At the end they are the agent's checklist: the mark opens into the sentence
 * that was said about it, ticks, and leaves as the fault goes.
 * ==================================================================== */

.ap-head,
.ap-cards,
.ap-card,
.ap-field,
.ap-tbl {
  position: relative;
}

/*
 * A point, not a box. Both children hang off it, so scaling it on the way in
 * scales them around the exact spot that was pointed at.
 */
.pin {
  position: absolute;
  /*
   * Percentages, resolved against the element this mark belongs to — so it
   * reflows and resizes with that element instead of holding a position that was
   * only ever true at one window width. hero.js sets them.
   */
  left: var(--px, 0%);
  top: var(--py, 0%);
  width: 0;
  height: 0;
  z-index: 3;
  opacity: 0;
  transform: scale(0.3);
  pointer-events: none;
}

/* Lands with a small overshoot, so a capture is felt as well as seen. */
.pin.on {
  opacity: 1;
  transform: none;
  animation: pin-in 460ms var(--ease);
}

@keyframes pin-in {
  0% { opacity: 0; transform: scale(0.3); }
  58% { opacity: 1; transform: scale(1.16); }
  100% { opacity: 1; transform: scale(1); }
}

/*
 * The dot, centred on the point — inside the capture ring, which is still
 * closing when it arrives. Ringed in the app's own panel colour so it reads as
 * something resting on the page rather than a hole cut in it.
 */
.pin-no {
  position: absolute;
  left: -11px;
  top: -11px;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-ink);
  font-family: var(--app-font);
  font-size: 11.5px;
  font-weight: 800;
  line-height: 1;
  box-shadow:
    0 0 0 2px var(--g-panel),
    0 2px 7px -1px color-mix(in srgb, var(--ink) 45%, transparent);
}

/*
 * What was said about it, opening above the dot rather than across it.
 *
 * Above, because everything this beat is about is underneath — including a
 * button whose entire contribution is to be seen moving upwards, into wherever
 * a note placed beside the dot would have been.
 */
.pin-say {
  position: absolute;
  bottom: 22px;
  right: -11px;
  max-width: 0;
  padding: 5px 0;
  overflow: hidden;
  white-space: nowrap;
  background: var(--ink);
  border-radius: var(--radius-pill);
  color: var(--on-ink);
  font-family: var(--app-font);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.45;
  opacity: 0;
  box-shadow: 0 4px 13px -3px color-mix(in srgb, var(--ink) 45%, transparent);
  transition:
    max-width 400ms var(--ease),
    padding 400ms var(--ease),
    opacity 240ms ease 90ms;
}

/* Opens rightwards instead, where there is no room to its left. */
.pin-left .pin-say {
  right: auto;
  left: -11px;
}

.pin.is-open .pin-say {
  max-width: 320px;
  padding: 5px 13px;
  opacity: 1;
}

/*
 * The tick, and the mark leaving behind it. The fault is fixed on the same beat,
 * so the delay here is the pause between "this one" and the next: long enough to
 * see the tick land, short enough that five of them still read as one pass down
 * a list.
 */
.pin.is-done .pin-no {
  color: transparent;
}

.pin.is-done .pin-no::after {
  content: "\2713";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--on-ink);
  font-size: 12.5px;
  font-weight: 800;
}

.pin.is-done {
  opacity: 0;
  transform: scale(0.94);
  transition:
    opacity 380ms ease 640ms,
    transform 380ms ease 640ms;
}

/* ==================================================================== *
 * THE STILL FRAME
 *
 * What the markup renders on its own: mid-review, one remark captured. The
 * animation never returns to this state — hero.js sets data-phase the moment it
 * takes over — so this is what someone with no JavaScript, or with reduced
 * motion asked for, keeps.
 * ==================================================================== */

/*
 * Issue two's moment, because it is the one that carries the most: a mark
 * already sitting above from the issue before it, a second one just landed where
 * the pointer is, the region that went into the picture outlined, and the words
 * that earned it still up. Every number below is the value the running demo
 * computes for that beat, so the still and the animation agree.
 */

/* Settled off the mark it just earned, as the running demo does. */
.stage:not([data-phase]) .cur {
  --cx: 656px;
  --cy: 200px;
  opacity: 1;
}

.stage:not([data-phase]) .say {
  --sx: 250px;
  --sy: 88px;
  opacity: 1;
  transform: none;
}

.stage:not([data-phase]) .mark {
  --mx: 642px;
  --my: 188px;
  opacity: 1;
}

.stage:not([data-phase]) .frame {
  --fx: 127px;
  --fy: 165px;
  --fw: 782px;
  --fh: 113px;
  opacity: 1;
}

/*
 * Two marks, not one. A still with a single mark on it says a screenshot was
 * taken; a still with an earlier one still sitting there says they are being
 * collected, which is the part worth a still. Offsets, because that is how the
 * running demo places them — see THE NUMBERED MARKERS.
 */
.stage:not([data-phase]) .pin[data-n="1"] {
  --px: 92.71%;
  --py: 82.17%;
  opacity: 1;
  transform: none;
}

.stage:not([data-phase]) .pin[data-n="2"] {
  --px: 66.15%;
  --py: 15.66%;
  opacity: 1;
  transform: none;
}

/*
 * The recording panel, which is where the geometry defaults already sit. Its
 * contents have to be swapped for the full window's here as well: without this
 * the panel shows the top inch of a 500px-wide window, which is a title bar and
 * nothing else.
 */
.stage:not([data-phase]) .rf-app {
  opacity: 0;
}

.stage:not([data-phase]) .rf-ov {
  opacity: 1;
}

/* Mid-sentence, since the still frame is of somebody talking. */
.stage:not([data-phase]) .rf-meter i {
  width: 46%;
}

/* ==================================================================== *
 * SMALLER SCREENS
 *
 * The stage keeps its aspect ratio and shrinks, and nothing inside it reflows:
 * one drawing, scaled. Below about a phone's width the app's 14px type is too
 * small to read, so the sentence under the stage is brought back to carry the
 * words while the animation carries the shapes.
 * ==================================================================== */

@media (max-width: 40rem) {
  .demo[data-live] .demo-note {
    display: block;
  }
}

/* ==================================================================== *
 * REDUCED MOTION
 *
 * hero.js checks the same query and never starts, so the stage keeps the still
 * frame above. Everything here is belt and braces: the CSS animations that run
 * without any script at all — the recording dot, the spinner — are the ones
 * this has to stop.
 * ==================================================================== */

@media (prefers-reduced-motion: reduce) {
  .stage,
  .stage *,
  .stage *::before,
  .stage *::after {
    animation: none !important;
    transition: none !important;
  }
}
