/* Records / post-workout Report.
   Two pages, one layout: progress.html is Records (period picker + Packs/Steps/Active
   Time), records.html is the report that lands right after a workout (date + session
   stats). Everything from Session Highlights down is identical, so it lives here once.

   Geometry is the Figma frame's, shifted up 44px: the status bar is a sibling of the
   hero, not inside it, so hiding it on mobile moves the whole hero up as one piece
   instead of leaving the absolutely-placed number and gauge behind. */

.rp-screen { background: var(--color-neutral-100); }
/* The bar's glyphs are white, which is right on the hero's red band and invisible the
   moment the page scrolls that band away — everything under it is the near-white page.
   Inverting the same asset turns all twelve white fills black; the background comes with
   it so cards do not run under the time. .scrolled is set in scores.js. */
/* Red, not transparent: the wash (.rp-hero-bg) is what covers this strip, and while it is
   travelling into place its top edge is below y0 — the page's own near-white showed
   through above the status bar for those few hundred ms and read as a broken frame. The
   hero's band is #FA3030 here anyway, so a fixed red under the bar is simply what is
   already meant to be there. */
/* --bar is 0 while the hero's red is behind the bar and 1 once the page's own neutral
   is, set from the scroll position in scores.js. Everything here reads that one number,
   so the bar cannot be a different colour from what it is sitting on — which is what the
   old threshold switch did for 400px of scroll. Red is a layer over the neutral rather
   than a second background, so no colour function is needed and it composites exactly. */
.rp-screen .status-bar { background: var(--color-neutral-100); }
.rp-screen .status-bar::before {
  content: "";
  position: absolute; inset: 0;
  background: #FA3030;
  opacity: calc(1 - var(--bar, 0));
  pointer-events: none;
}
.rp-screen .statusbar-svg { filter: invert(var(--barg, 0)); }
/* The bar is opaque, so cards passing under it were cut off along a straight line at its
   foot. A tail of the same colour carries that edge out over the next 18px instead — the
   same treatment the Packs feed's bar has. It fades in with --bar: over the hero there is
   no edge to hide and the tail only smeared the app bar passing underneath it. */
.rp-screen .status-bar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 18px;
  opacity: var(--bar, 0);
  background: linear-gradient(180deg, var(--color-neutral-100), rgba(242,242,242,0));
  pointer-events: none;
}

/* ── Hero ──────────────────────────────────────────────────────────────────
   Red-to-white wash exported from the design (the frame's 블러 layer); every
   text element sits on top of it as real text. */
/* A red band pinned to the top of the hero. The wash above it RIDES UP into place on
   arrival (rp-wash-rise starts it 110px low), and for those 660ms the top of the screen
   was the page's near-white neutral — with the status bar's white glyphs on it, which is
   why the time and battery vanished. This band never moves, so there is no frame where
   the bar has nothing to sit on. It stops well above the gauge, so the cream is untouched. */
.rp-hero {
  position: relative; height: 462px; flex: none;
  background: linear-gradient(180deg, #FA3030 0 96px, rgba(250,48,48,0) 190px);
}
/* 블러 (729:8538): a 513 x 528 vector turned -72.06°, whose bounding box sits at
   (-142, -177) in the frame's content box — page (-142, -133) with the 44px status row.
   Turned, that box measures 660 x 651, and the export already has the rotation baked in,
   so the artwork just goes in that box. The turn is what puts the radial's core in the
   UPPER RIGHT: the red stays up there while the cream sweeps in from the lower left, and
   at the gauge the warm band is about as wide as the arc with the page's own neutral
   showing past both ends of it. */
.rp-hero-bg {
  position: absolute;
  top: -133px;
  left: -142px;
  /* Stretched down from the export's 651: at 651 the wash had already gone to the page's
     near-white by the time it reached the gauge, so the arc — white on near-white — read
     as a stub around the marker instead of a full sweep. The box is pulled DOWNWARD only
     (top is untouched), which moves the falloff past the arc without opening a gap at the
     top of the frame. */
  width: 660px; height: 730px;
  object-fit: fill;
  z-index: 0;
  pointer-events: none;
  /* Core colour behind the artwork so the top corners it does not reach close at the
     colour they already are, stopping before the falloff so it never tints the cream. */
  background: linear-gradient(180deg, #FA3030 0 26%, rgba(250,48,48,0) 42%);
}
/* On a phone our status bar is gone (main.css), so the hero starts 44px higher — but the
   wash is placed against .rp-screen, not the hero, so it stayed put and sat 44px low
   against the gauge and the number. Shifted up to match. */
html.installed .rp-hero-bg { top: -177px; }

/* Arriving from the recap, the wash RIDES UP into place: on the recap it fills the screen,
   and here it has travelled up to sit as the band at the top. Held off
   until the page transition has finished — during it the browser is animating a snapshot
   taken at load, so anything that ran here would already be over by the time it shows.
   The offset is the RESTING state (.rp-draw, set before the first paint), not just the
   animation's from-frame: with it only in the keyframe the wash sat at rest for a frame,
   jumped 110px the instant .rp-rise landed, and glided back. That jump was the 둠칫. */
.rp-draw .rp-hero-bg { transform: translateY(110px); }
.rp-rise .rp-hero-bg { animation: rp-wash-rise .72s cubic-bezier(.2,.8,.25,1) both; }
@keyframes rp-wash-rise { from { transform: translateY(110px); } to { transform: none; } }
/* z-index only. This used to also set `position: relative`, which outranked the
   absolute placement below (`.rp-hero > *:not(...)` beats a bare `.rp-big`) and dropped
   the number, caption and gauge back into flow, each shoved down by its own `top`. */
/* Excluding the wash matters: a bare `.rp-hero > *` also matched it, and since that
   rule comes later it beat the z-index: 0 above — the wash then painted over the body
   and hid the first row of card labels. */
.rp-hero > *:not(.rp-hero-bg) { z-index: 2; }
.rp-hero > .app-bar, .rp-hero > .rp-daynav, .rp-hero > .rp-appbar, .rp-hero > .rp-date { position: relative; }

.rp-hero .app-bar { height: 66px; box-sizing: border-box; }
.rp-title { margin: 0; font-family: var(--font-supreme-bold); font-size: 26px; line-height: 1.3; letter-spacing: -1px; color: #fff; }
/* 24x24 is the FRAME, not the glyph. Both assets carry preserveAspectRatio="none",
   so stretching them to fill 24x24 blew the calendar up and fattened the kebab's dots
   into ovals — the glyph keeps its own size inside the frame instead. Selectors carry
   .app-bar so they outrank main.css's blanket 24x24 on app-bar action icons. */
.rp-hero .app-bar .actions .ic { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.rp-hero .app-bar .actions img { display: block; }
.rp-hero .app-bar .actions .ic-cal { width: 19.4px; height: 19.3px; }
/* kebab-dots.svg is the vertical ⋮ (4x18); the design uses ⋯ */
.rp-hero .app-bar .actions .ic-kebab { width: 4px; height: 18px; transform: rotate(90deg); }
/* Every hero icon is drawn white over the red wash, whatever fill its file ships with
   — brightness(0) flattens the artwork, invert(1) turns it white. */
.rp-hero .actions img, .rp-daynav img, .rp-appbar img { filter: brightness(0) invert(1); }

/* Records: ‹ Today › */
.rp-daynav { display: flex; align-items: center; justify-content: space-between; height: 56px; padding: 0 16px; }
.rp-daynav img { width: 24px; height: 24px; display: block; opacity: .9; }
.rp-chip {
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(0,0,0,0.2);
  font-family: var(--font-supreme-bold);
  font-size: 13px;
  letter-spacing: -0.2px;
  color: #fff;
}
/* Report: the session date, centred on its own 33px row */
.rp-date { display: flex; align-items: center; justify-content: center; height: 33px; }
.rp-date span { font-family: var(--font-supreme); font-size: 15px; letter-spacing: -0.5px; color: rgba(255,255,255,0.92); }

/* Headline number. The unit and sign are Supreme; only the numerals are OffBit. */
.rp-big {
  position: absolute;
  top: 172px; left: 0; right: 0;
  margin: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
  color: #fff;
}
.rp-big .sign { font-family: var(--font-supreme-bold); font-size: 22px; letter-spacing: -0.5px; padding-bottom: 12px; }
/* 54 / 110% / -2%, per Figma. It used to be 72 to match the recap's score — same number,
   next screen — but the report is the sized-down one, so only this end moved. */
.rp-big .n { font-family: var(--font-offbit); font-weight: 700; font-size: 54px; line-height: 1.1; letter-spacing: -1.08px; }
.rp-big .u { font-family: var(--font-supreme-bold); font-size: 22px; letter-spacing: -0.5px; padding-bottom: 8px; }
.rp-cap {
  position: absolute;
  top: 248px; left: 0; right: 0;
  margin: 0;
  text-align: center;
  font-family: var(--font-supreme);
  font-size: 13px;
  letter-spacing: -0.4px;
  color: rgba(255,255,255,0.88);
}

/* Gauge: the top slice of the design's 522x377 ellipse, so the curve keeps its very
   flat rise. Stroke fades out at both ends the way the artwork does. */
.rp-gauge-wrap { position: absolute; top: 308px; left: 0; width: 360px; height: 130px; }
.rp-gauge { position: absolute; inset: 0; width: 100%; height: 100%; }

/* The marker is glass: 18% white over a real blur of the wash behind it, with the rim
   catching light top-left and bottom-right. It is HTML rather than an SVG node purely
   because SVG has no backdrop filter — gauge.js positions it in the gauge's own
   coordinates, which are 1:1 with pixels here. */
.arc-dot {
  position: absolute;
  left: 208.5px; top: 7.5px;
  width: 39px; height: 39px;
  border-radius: 50%;
  background: rgba(255,255,255,0.18);
  /* The whole point of the glass is that the track is still there underneath, only
     softened. 4px, not more: the line is 6px thick and the disc is 39px across, so a
     deeper blur dissolves it completely and the marker goes back to being an opaque
     white pebble. Checked at a true 3x magnification (blur scaled with it), where the
     band can be seen entering the disc and coming out the other side. */
  -webkit-backdrop-filter: blur(2.6px) saturate(1.06);
  backdrop-filter: blur(2.6px) saturate(1.06);
  /* Thickness: light pooling just inside the top edge, and the disc casting a faint
     shadow of its own. Any more white than this and the band stops showing through. */
  box-shadow:
    inset 0 1px 2px rgba(255,255,255,.28),
    0 3px 10px rgba(120,20,10,.10);
  pointer-events: none;
}
/* Two rims, not one: a bright hairline catching the light top-left and bottom-right,
   and a wider, softer halo just inside it — a single 1px ring reads as a border, and a
   border is what makes glass look like a flat disc with an outline. */
.arc-dot::before,
.arc-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}
/* A hairline, lit on the diagonal: the top-left quadrant and the bottom-right one, dark
   on the other two. That is where a lens catches light and where it lets it out, and it
   is what stops the ring reading as a drawn circle. Conic because those are positions on
   the circumference, not the ends of a linear ramp — 0deg is straight up, so 315 is the
   top-left arc and 135 the bottom-right one.
   Nothing else: the inner sheen that used to sit under the crown is gone. Blurred and
   3px wide, it read as a second, thick, badly-drawn border rather than as light. */
.arc-dot::before {
  padding: 0.7px;
  background: conic-gradient(from 0deg,
    rgba(255,255,255,.30) 0deg,
    rgba(255,255,255,.06) 45deg,
    rgba(255,255,255,.34) 90deg,
    rgba(255,255,255,.96) 135deg,
    rgba(255,255,255,.34) 180deg,
    rgba(255,255,255,.06) 225deg,
    rgba(255,255,255,.34) 270deg,
    rgba(255,255,255,.98) 315deg,
    rgba(255,255,255,.30) 360deg);
}
.arc-dot span {
  position: absolute;
  left: 50%; top: 50%;
  width: 15.17px; height: 15.17px;
  margin: -7.58px 0 0 -7.58px;
  border-radius: 50%;
  background: #fff;
}
/* The label under the marker counts the whole way, so only the headline waits — it
   joins a moment after the marker sets off and lands with it (gauge.js). */
.rp-big .n { transition: opacity .22s ease; }
.rp-big .n.wait { opacity: 0; }

/* Report entrance, in order: the curve draws itself left to right, and only when it has
   finished does the marker set off — with the score sliding up under it at the same
   moment. Records (progress.html) has none of these classes and is unaffected.
   rp-draw is the resting state and goes on straight away so nothing flashes; rp-play
   starts the clock, and waits for the page transition (see records.html). */
/* rp-draw is set by an inline script in records.html's <head>, BEFORE the body is parsed
   — it used to be set at the end of the body, and on a heavy arrival the first painted
   frame showed the headline at full size before the class landed and hid it again. That
   is why the number appeared, vanished, and came back. It stays a class rather than the
   default because Records (progress.html) shares this file and reveals none of it. */
.rp-draw .arc-line { stroke-dasharray: 100; stroke-dashoffset: 100; }
/* Two clearly different weights, because they are two beats: the track draws itself in
   first as a faint guide, and the tail that follows the marker is the one with substance.
   At the artwork's own 0.85 the two were near enough the same line. */
.arc-line { stroke-opacity: .42; }
.rp-draw .arc-dot, .rp-draw #arcVal, .rp-draw .arc-prog { opacity: 0; }
/* Nothing on the gauge exists before the gauge does. The caption comes up as the track
   draws, and the two end labels RIDE THAT DRAW — 0 as the line leaves the left end, 100
   as it reaches the right — so the scale arrives as one thing with the track it belongs
   to. They used to wait until after it had finished (.62/.76), which put them on top of
   the marker's beat instead and left the track drawing to nothing at either end. */
.rp-draw .rp-cap, .rp-draw .arc-end { opacity: 0; }
.rp-play .rp-cap { animation: rp-cap-in .42s cubic-bezier(.2,.85,.3,1) .12s both; }
@keyframes rp-cap-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.arc-end { transform-box: fill-box; transform-origin: center; }
.rp-play .arc-end.l { animation: arc-end-l .38s cubic-bezier(.2,.85,.3,1) .16s both; }
.rp-play .arc-end.r { animation: arc-end-r .38s cubic-bezier(.2,.85,.3,1) .46s both; }
@keyframes arc-end-l { from { opacity: 0; transform: translateX(-7px); } to { opacity: 1; transform: none; } }
@keyframes arc-end-r { from { opacity: 0; transform: translateX(7px); }  to { opacity: 1; transform: none; } }
.rp-draw .rp-big { opacity: 0; transform: translateY(22px); transition: opacity .4s ease, transform .52s cubic-bezier(.2,.8,.3,1); }
.rp-play .arc-line { animation: arc-draw .6s cubic-bezier(.4,0,.2,1) both; }
@keyframes arc-draw { to { stroke-dashoffset: 0; } }
.rp-landed .arc-dot, .rp-landed #arcVal, .rp-landed .arc-prog { opacity: 1; transition: opacity .3s ease; }
.rp-landed .rp-big { opacity: 1; transform: translateY(0); }
/* NEW Best already has an entrance of its own further down; on the Report it just has to
   wait for the gauge instead of running off page load, so its clock is held here.
   Held PAUSED rather than overridden to opacity 0 — that keyframe has no `to`, so its
   end state is whatever the element's own opacity is, and forcing that to 0 made it
   animate from nothing to nothing. Paused with `both` fill already shows the `from`. */
.rp-draw .rp-stat .delta.best { animation-play-state: paused; }
.rp-landed .rp-stat .delta.best { animation-play-state: running; animation-delay: .85s; }

/* Same hold for the metric row. Its animations ran on page load, which on the Report is
   behind the transition's frozen snapshot, so the cards were already settled by the time
   the page was shown and the wash then slid up on its own a beat later.
   Released on .rp-play, not .rp-rise: the wash gets its 350ms head start (records.html)
   and the row comes in behind it, along with the caption and the gauge's track.
   Held with play-state rather than by moving the animation onto the class: Records
   (progress.html) has none of these and its row must still arrive normally. */
.rp-draw .rp-body > .rp-stats,
.rp-draw .rp-stats .rp-stat { animation-play-state: paused; }
.rp-play .rp-body > .rp-stats,
.rp-play .rp-stats .rp-stat { animation-play-state: running; }

/* ── Body ──────────────────────────────────────────────────────────────────
   Fades out of the hero wash so there is no seam where the artwork stops. */
/* No top padding: .rp-hero is exactly as tall as the design's hero (462px once the
   44px status bar is taken off), so the body already starts on the right line. */
/* z-index: the hero wash is 523px tall inside a 462px hero, so it bleeds 61px into the
   body on purpose (the fade has to finish somewhere). Without this it painted over the
   first row of cards and swallowed their labels. */
.rp-body { position: relative; z-index: 1; padding: 0 0 24px; }
/* Day / Week / Month / 6 Month */
.rp-seg { display: flex; gap: 4px; margin: 0 12px; padding: 4px; border-radius: 999px; background: rgba(255,255,255,0.55); }
.rp-seg button {
  border: none;
  cursor: pointer;
  background: none;
  flex: 1;
  height: 36px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  text-decoration: none;
  font-family: var(--font-supreme);
  font-size: 14px;
  letter-spacing: -0.4px;
  color: var(--color-neutral-500);
}
.rp-seg button.sel {
  background: #fff;
  color: var(--color-black);
  font-family: var(--font-supreme-bold);
}

/* Metric row — node 653:8925. Cards are translucent white over the wash, not solid;
   label and value block are 18px apart with the value pinned to the bottom; the unit
   letters are small grey Supreme, not OffBit. Packs/Steps are fixed widths and the
   last cell takes the slack. */
.rp-stats { display: flex; gap: 3px; align-items: center; margin: 8px 12px 0; }
/* Each cell rises into place, still one after the other left to right. Short and
   shallow — 14px and 80ms apart reads as settling, not as three things flying in. */
.rp-stats .rp-stat { animation: rp-stat-in .42s cubic-bezier(.2,.85,.3,1) both; }
.rp-stats .rp-stat:nth-child(1) { animation-delay: .06s; }
.rp-stats .rp-stat:nth-child(2) { animation-delay: .14s; }
.rp-stats .rp-stat:nth-child(3) { animation-delay: .22s; }
@keyframes rp-stat-in { from { opacity: 0; transform: translateY(14px); } }
/* 8px below the period picker on Records; flush to the top on the Report, which has none. */
.rp-stats:first-child { margin-top: 0; }
.rp-stat {
  flex: 1;
  min-width: 0;
  height: 120px;
  box-sizing: border-box;
  padding: 12px 16px;
  background: rgba(255,255,255,0.8);
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: center;
}
/* Packs and Steps are only as wide as their own number plus 20 either side — a fixed
   88 cut "13,119" off. Active Time has no width of its own and takes what is left. */
.rp-stat.fit { flex: 0 0 auto; padding-left: 20px; padding-right: 20px; }
.rp-stat .lab { margin: 0; font-family: var(--font-supreme); font-size: 13px; line-height: 1.2; letter-spacing: -0.5px; color: var(--color-neutral-500); white-space: nowrap; }
.rp-stat .val {
  flex: 1;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  justify-content: flex-end;
}
.rp-stat .delta { margin: 0; font-family: var(--font-supreme-medium); font-size: 14px; line-height: 1.4; letter-spacing: -0.56px; color: var(--color-neutral-400); white-space: nowrap; }
.rp-stat .delta.best {
  color: var(--color-red);
  animation: rp-best-in .5s cubic-bezier(.2,.85,.3,1) .95s both;
}
@keyframes rp-best-in { from { opacity: 0; transform: translateY(10px); } }
.rp-stat .num { margin: 0; display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
/* The NUMERAL is what centres in the cell, not the numeral+unit group — so the pair has
   to measure the number alone while the unit still hangs off its right edge, 4px away.
   The unit is a zero-width box whose glyph simply overflows: it contributes nothing to
   the width (its 4px offset is cancelled by the same negative margin) yet it is still
   in flow, so baseline alignment places it — no magic vertical constant to keep in step
   with the two font sizes. Only when the pair is alone: Active Time is "1h 43m", two
   pairs in one line, and a zero-width `h` would sit on top of the 43. */
.rp-stat .num .pair { display: inline-flex; align-items: baseline; gap: 4px; }
.rp-stat .num .pair:only-child .u { display: inline-block; width: 0; min-width: 0; margin-right: -4px; }
.rp-stat .num .n { font-family: var(--font-offbit); font-weight: 700; font-size: 28px; line-height: 1.2; letter-spacing: -1.5px; color: var(--color-black); }
.rp-stat .num .u { font-family: var(--font-supreme); font-size: 12px; line-height: 1.2; letter-spacing: -0.5px; color: var(--color-neutral-300); }

/* ── Sections ──────────────────────────────────────────────────────────────
   48px between blocks, title then 8px then content — the design's rhythm. */
.rp-sec { margin: 48px 0 0; padding: 0 12px; }
.rp-sec > h2 {
  margin: 0 0 8px;
  height: 26px;
  display: flex;
  align-items: center;
  font-family: var(--font-supreme-bold);
  font-size: 18px;
  line-height: 1.2;
  letter-spacing: -0.54px;
  color: var(--color-black);
}

/* Session Highlights */
.rp-pair { display: flex; gap: 4px; }
.rp-hcard {
  flex: 1;
  height: 166px;
  box-sizing: border-box;
  padding: 16px;
  background: #fff;
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.rp-hcard .top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.rp-hcard .cap { margin: 0 0 4px; font-family: var(--font-supreme); font-size: 13px; line-height: 16px; letter-spacing: -0.3px; color: var(--color-neutral-500); }
.rp-hcard .val { margin: 0; font-family: var(--font-supreme-bold); font-size: 20px; line-height: 24px; letter-spacing: -0.6px; color: var(--color-black); }
/* Badge is a circle plus an arrow, not one flat asset, so the arrow can arrive on its
   own beat. Down uses Neutral/200 rather than 100 — 100 all but disappears on the white
   card. */
.rp-hcard .badge {
  width: 32px; height: 32px; flex: none;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.rp-hcard .badge.up { background: var(--color-red); }
.rp-hcard .badge.down { background: var(--color-neutral-200); }
.rp-hcard .badge svg { width: 32px; height: 32px; display: block; opacity: 0; }
/* Each arrow enters along the direction it points, once, when the pair is scrolled to.
   The travel-out-and-return loop is gone: on a report that is read once it just kept
   flickering in the corner of the eye. */
/* Gated on .run, which scores.js adds when the pair is scrolled to. On a fixed delay
   from page load the whole loop had already run and the arrows were sitting mid-cycle —
   or gone — by the time anyone looked at them. */
/* The arrows keep their own trigger even though the section around them now arrives with
   the metric row. The two are not the same moment: the section's HEADING is on screen
   when the report lands, but the pair of cards is entirely below the fold — measured, 0%
   of it is in view at rest — so putting the arrows on the load clock spent them where
   nobody could see them, and by the time the page scrolled down they were already
   sitting there. .run comes from scores.js when the pair is scrolled to, so they play
   once, on the way in. */
.rp-pair.run .rp-hcard .badge.up svg   { animation: arrow-up-in .46s cubic-bezier(.2,.85,.3,1) .12s both; }
.rp-pair.run .rp-hcard .badge.down svg { animation: arrow-down-in .46s cubic-bezier(.2,.85,.3,1) .26s both; }
/* Explicit end frames. With only a `from`, the `to` is the element's OWN opacity — which
   is 0 while it waits for .run, so the arrows animated 0 to 0 and never appeared. */
@keyframes arrow-up-in   { from { opacity: 0; transform: translate(-7px, 7px); }  to { opacity: 1; transform: none; } }
@keyframes arrow-down-in { from { opacity: 0; transform: translate(-7px, -7px); } to { opacity: 1; transform: none; } }
.rp-hcard .desc { margin: 0; font-family: var(--font-supreme); font-size: 13px; line-height: 16px; letter-spacing: -0.3px; color: #A3A3A3; }

/* Landing Balance. The heat is a CSS blob, not baked artwork, so its red core sits
   where the Left/Right split puts it (--heat-x, set from the readings) and can drift.
   Footprints are the design's silhouette used as a mask; crosshair and dots are CSS;
   the readings are live text. */
.rp-balance {
  position: relative;
  width: 336px; height: 166px;
  border-radius: 20px;
  overflow: hidden;
  background: #EEFDFB;
}
/* Ellipse 31265, exported with its own 34 layer blur baked in. The CSS radial it
   replaces could not hold the shape — the design's blob is an off-centre egg, not a
   circle, and its core (218.7, 178.2 in the artwork) is what has to land on the split. */
.rp-balance .lb-heat {
  position: absolute;
  top: 50%;
  left: var(--heat-x, 50%);   /* starts centred; travels once the card is seen */
  width: 326px; height: 357px;
  margin: -172px 0 0 -218.7px;   /* puts the artwork's own core on that point */
  background: url('../assets/photos/landing-gradient.svg') no-repeat center / 100% 100%;
  /* ONE journey, then a breath. `left` carries the core out to the stronger foot when the
     card is reached — that is the whole move, and it ends there.
     What sat on top of it was lb-drift, running `alternate`. Alternate plays the keyframes
     BACKWARDS on every other pass, so the blob walked its whole path in reverse every six
     seconds; that is the going-back-and-forth. It is gone. lb-breathe cannot do it — it
     only swells and settles, about the artwork's own core rather than the box centre, so
     it reads as heat spreading rather than as the picture being moved. 3.5% of a 326px
     blob is ~11px of halo under a 68px blur: enough that the card is not frozen, small
     enough that nothing travels. */
  transform-origin: 218.7px 172px;
  animation: lb-breathe 6s cubic-bezier(.45, 0, .55, 1) infinite;
  /* The wash ARRIVES: invisible until the card is scrolled to, then it comes up while it
     slides off centre. Both front-loaded, so the movement starts with the readings under
     it rather than after them. */
  opacity: 0;
  transition: left 1s cubic-bezier(.2, .8, .3, 1), opacity .45s ease-out;
}
.rp-sec.run .rp-balance .lb-heat { opacity: 1; }
/* Nothing travels here — the halo widens and comes back, and that is all it does. Sine
   easing rather than ease-in-out so there is no flat spot at either end, and 6s so the
   two seconds the tour spends on this card is a third of a pass: visibly not still,
   nowhere near a full cycle. Slightly less vertically than horizontally, so it opens
   along the foot rather than as a circle. */
@keyframes lb-breathe {
  0%   { scale: 1 1; }
  50%  { scale: 1.035 1.02; }
  100% { scale: 1 1; }
}
/* The two readings arrive WITH the wash's move, not after it — left first, then right,
   the order the crosshair is read in. The left one is on the same clock as the travel
   above so the blob is visibly sliding while the number comes up under it. */
.rp-balance .lb-read { opacity: 0; }
.rp-sec.run .rp-balance .lb-read { opacity: 1; animation: lb-read-in .5s cubic-bezier(.2,.85,.3,1) both; }
.rp-sec.run .rp-balance .lb-read.l { animation-delay: .04s; }
.rp-sec.run .rp-balance .lb-read.r { animation-delay: .18s; }
@keyframes lb-read-in { from { opacity: 0; transform: translateY(10px); } }
/* The dotted prints, used as artwork rather than as a mask — the dots and their inner
   shading are in the file. Group 2147228966: 71px in, 25.3 down, 195.6 across. */
.rp-balance .lb-foot {
  position: absolute;
  top: 25.3px;
  width: 48px; height: 114.4px;
  background: no-repeat center / contain;
}
.rp-balance .lb-foot.l { left: 71px; background-image: url('../assets/icons/foot-l-dots.svg'); }
.rp-balance .lb-foot.r { left: 218.6px; background-image: url('../assets/icons/foot-r-dots.svg'); }
.rp-balance .lb-cross-h, .rp-balance .lb-cross-v { position: absolute; opacity: .75; }
.rp-balance .lb-cross-h { left: 18px; right: 18px; top: 83px; height: 1px; background: repeating-linear-gradient(to right, #fff 0 2px, transparent 2px 5px); }
.rp-balance .lb-cross-v { left: 168px; top: 14px; width: 1px; height: 128px; background: repeating-linear-gradient(to bottom, #fff 0 2px, transparent 2px 5px); }
/* One element paints all five crosshair dots via box-shadow copies. */
.rp-balance .lb-dots {
  position: absolute;
  left: 165px; top: 12px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: #fff;
  opacity: .9;
  box-shadow: 0 126px 0 #fff, -135px 69px 0 #fff, 137px 69px 0 #fff, 1px 69px 0 #fff;
}
.rp-balance .lb-read { position: absolute; bottom: 12px; display: flex; flex-direction: column; gap: 4px; z-index: 2; }
.rp-balance .lb-read.l { left: 12px; align-items: flex-start; }
.rp-balance .lb-read.r { right: 12px; align-items: flex-end; }
.rp-balance .lb-read p { margin: 0; }
.rp-balance .lb-read .k { font-family: var(--font-supreme); font-size: 10px; line-height: 15px; color: var(--color-neutral-500); }
.rp-balance .lb-read .v { font-family: var(--font-supreme-bold); font-size: 13px; line-height: 1.2; letter-spacing: -0.2px; color: #141A1A; }
/* The card's inner white halo, which softens the wash into its corners. */
.rp-balance::after { content: ""; position: absolute; inset: 0; border-radius: 20px; box-shadow: inset 0 0 70px 10px #fff; pointer-events: none; }

/* ── Performance Scores ────────────────────────────────────────────────────
   The graph artwork is a 3x PNG exported straight off the Figma frame, with the white
   matte stripped back out so the guides can sit behind it. It was rebuilt as SVG once;
   Figma's progressive blur does not survive an SVG export — it flattens to one uniform
   value — so the raster export is the only thing that carries the design exactly.

   Every top/left below is in CARD coordinates, read off the Figma frame, and each art
   box is the node's own render bounds rather than a hand-cut band. Anything that has to
   move on its own beat stays a live layer: the number, the label, the marker, the dashed
   guides, and the whole Stability card.
   Layer order: guides 0, art 1, Stability's bar 2, number/label/marker 3. The guides are
   the back of the card — the graph crosses over them, never under. */
.rp-scores { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.sc {
  position: relative;
  height: 166px;
  background: #fff;
  border-radius: 20px;
  overflow: hidden;
  --d: calc(var(--i) * 200ms);
}
.sc-n {
  position: absolute; top: 12px; left: 0; right: 0;
  margin: 0; text-align: center;
  font-family: var(--font-offbit); font-weight: 700;
  font-size: 28px; line-height: 38px; letter-spacing: -0.56px;   /* 28 / 38 / -2% */
  color: var(--color-black);
  opacity: 0;
  z-index: 3;
}
.sc-l {
  position: absolute; bottom: 12px; left: 0; right: 0;
  margin: 0; text-align: center;
  font-family: var(--font-supreme); font-size: 13px; line-height: 16px; letter-spacing: -0.3px;
  color: var(--color-neutral-500);
  z-index: 3;
}
.sc-art { position: absolute; left: 0; display: block; z-index: 1; }
.sc.ag .sc-art { top: 6.67px;  width: 166px; height: 159.33px; }
.sc.en .sc-art { top: 82.83px; width: 120px; height: 23px; }
.sc.rh .sc-art { top: 33.75px; width: 166px; height: 108px; }

/* Stability is drawn live, because it is the only card whose parts move independently.
   Straight off the frame: 1.6px ticks 7.6px apart, 32 tall, in two banks of eleven at
   x -1.8 and x 90.2, with the 6x45 bar parked in the gap between them at x 79.
   The blur belongs to the CARD WALLS, not to the field. The sharp copy stops short of
   each wall and a blurred copy carries the last few ticks out through it — blurring the
   whole field, which is what this did before, just erased the pattern. */
/* Starts OFF the card, not at x 0: the bar has to arrive, so it must not be sitting at
   the left edge waiting for its cue. 6px wide at -10px puts it fully outside the clip. */
@property --pass { syntax: '<length>'; inherits: true; initial-value: -10px; }
/* ONE field, wall to wall. It used to be two banks with a 14px hole where the bar parks,
   so before the bar arrived there was a gap in the pattern and the bar looked like it was
   being slotted into a space left for it. The pitch is unbroken now and the bar simply
   passes over it. */
.st-ticks {
  position: absolute; z-index: 1;
  left: -1.8px; top: 67.96px; width: 169.6px; height: 32px;
  color: #FFE7D0;
  clip-path: inset(0 100% 0 0);
}
.st-ticks::before, .st-ticks::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(90deg, currentColor 0 1.6px, transparent 1.6px 7.6px);
}
.st-ticks::after { filter: blur(1.6px); }
/* Everything the bar has already passed is red. */
.st-ticks.hot { color: #FCACAC; }
/* The sharp copy stops short of both walls; the blurred copy carries the last few ticks
   out through them, so the blur belongs to the walls and not to the field. */
.st-ticks::before {
  -webkit-mask-image: linear-gradient(90deg, transparent 0 8px, #000 18px, #000 calc(100% - 18px), transparent calc(100% - 8px));
          mask-image: linear-gradient(90deg, transparent 0 8px, #000 18px, #000 calc(100% - 18px), transparent calc(100% - 8px));
}
.st-ticks::after {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6px 13px, transparent 21px, transparent calc(100% - 21px), #000 calc(100% - 13px) calc(100% - 6px), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 6px 13px, transparent 21px, transparent calc(100% - 21px), #000 calc(100% - 13px) calc(100% - 6px), transparent 100%);
}
.st-bar {
  position: absolute; z-index: 2;
  left: 0; top: 60.96px;
  width: 6px; height: 45px;
  background: linear-gradient(180deg, #FA3030 0%, #FE8C4A 100%);
  transform: translateX(var(--pass));
}
/* The bar's leading edge IS where the field turns red, so one variable drives both. */
.st-ticks.hot { clip-path: inset(0 calc(100% - var(--pass) - 1.8px) 0 0); }

/* Stability's order: every tick flicks in left to right in the DEFAULT colour, then the
   bar comes in from off the left edge, eases onto its mark, and turns the field red
   behind it as it goes.
   The wipe is on the default field only. Run it on .hot as well and its end frame —
   inset(0 0 0 0) — outranks the --pass clip below, which painted the whole left bank red
   before the bar had moved at all. .hot is driven by --pass and nothing else. */
.rp-scores.run .st-ticks.f { animation: sc-grow .34s linear var(--d) both; }
.rp-scores.run .sc.st { animation: st-pass .62s cubic-bezier(.12,.78,.22,1) calc(var(--d) + 440ms) both; }
@keyframes st-pass { from { --pass: -10px; } to { --pass: 79px; } }

/* The grey guides: the back layer of the card, and on their own beat — the graph draws,
   then the guide extends, then the marker lands. Rhythm's three follow the wave left to
   right, in the order the wave was drawn. */
.sc-dash {
  position: absolute; z-index: 0; width: 1.4px;                 /* Figma stroke 1.4 */
  background: repeating-linear-gradient(180deg, currentColor 0 1.8px, transparent 1.8px 3.6px);
  /* Faded at BOTH ends — a guide that meets type at full strength reads as a line through
     it. How much of each end goes is per card, below. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 22% 60%, transparent 94%);
          mask-image: linear-gradient(180deg, transparent 0, #000 22% 60%, transparent 94%);
  clip-path: inset(0 0 100% 0);
}
/* Agility's guide starts ABOVE the dome (the arc sits at y59-99) rather than inside it,
   and its top fade is short so it is actually visible up there. */
/* Everything on this card lines up on the dome's apex, measured off the artwork itself:
   the ridge peaks at card x 80.7, y 79.8. The guide is 1.4 wide, so left = 80.7 - 0.7.
   It starts BELOW the marker and fades in over the first 12% — above the triangle it must
   not exist at all. z-index 0 keeps it the back layer, under the arc. */
/* Straight off agility-1.svg: the guide is the path M82 70.9736 L82 143.974, so x is 82
   (less half the 1.4 stroke) and it runs 73px. Its stroke is a gradient — #D0D0D0 held to
   y86.8, then ramping to white (i.e. gone, on a white card) by y131.6 — which is the mask
   below. The gap at 9-16% is where the arc crosses: the SVG paints the graph OVER the
   guide, and our export is only 63% opaque there, so the guide is cut instead. */
.sc.ag .sc-dash {
  left: 81.3px; top: 70.97px; height: 73px;
  color: #D0D0D0;
  -webkit-mask-image: linear-gradient(180deg, #000 0 7%, transparent 9% 16%, #000 19% 22%, transparent 83%);
          mask-image: linear-gradient(180deg, #000 0 7%, transparent 9% 16%, #000 19% 22%, transparent 83%);
}
/* Rhythm's three run down onto the wave and the label, so their lower halves go early. */
/* The wave's three crests sit at card x 34.7 / 82.7 / 130.0, tops at y 67.1 — measured
   off the artwork, not eyeballed. left = crest - 0.7 (half the 1.4 stroke), so each guide
   runs down the exact centre of its hump. */
/* rhythm.svg: three paths at x 130 / 83 / 35, each M{x} 35.96 L{x} 136.96 — so 101 tall
   from y35.96. Their stroke gradient is white at the top, #DBDBDB at 24.3% and white
   again by y127.8; that is the mask. The gap at 30-36% is the crest crossing. */
.sc.rh .sc-dash {
  top: 35.96px; height: 101px;
  color: #DBDBDB;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 22% 27%, transparent 30% 36%, #000 39% 60%, transparent 91%);
          mask-image: linear-gradient(180deg, transparent 0, #000 22% 27%, transparent 30% 36%, #000 39% 60%, transparent 91%);
}
.sc.rh .sc-dash.a { left: 34.3px; }
.sc.rh .sc-dash.b { left: 82.3px; }
.sc.rh .sc-dash.c { left: 129.3px; }
/* Endurance's guide is the remainder of the track, so it runs sideways. */
.sc-track {
  position: absolute; z-index: 0;
  left: 108px; top: 90.75px; width: 53px; height: 7px;
  border-radius: 3.5px;
  background: #F6F6F6;
  -webkit-mask-image: linear-gradient(90deg, #000 0 86%, transparent 100%);
          mask-image: linear-gradient(90deg, #000 0 86%, transparent 100%);
  clip-path: inset(0 100% 0 0);
}
.rp-scores.run .sc-dash,
.rp-scores.run .sc-track { animation: sc-grow .38s cubic-bezier(.3,.7,.3,1) calc(var(--d) + 560ms) both; }
.rp-scores.run .sc.rh .sc-dash.b { animation-delay: calc(var(--d) + 660ms); }
.rp-scores.run .sc.rh .sc-dash.c { animation-delay: calc(var(--d) + 760ms); }
@keyframes sc-grow { to { clip-path: inset(0 0 0 0); } }

/* Marker: drawn, not baked, so it stays crisp over the blurred art. */
.sc-tri { position: absolute; z-index: 3; width: 11px; height: 9.97px; opacity: 0; }
.sc-tri svg { display: block; width: 100%; height: 100%; }
/* Centred on what each one marks, and parked just clear of it: the dome's apex (x 80.7,
   y 79.8) and the endurance bar's tip (x 111.7, bar top y 90.8). The glyph's point is
   8.2px down the 9.97 box, so `top` is the point's y minus that. */
/* The marker paths in the SVGs span x 78.29-85.71 (Agility) and 104.29-111.71
   (Endurance) — centres 82 and 108. Endurance's point is pulled out to 112 instead: the
   bar rect is x5 w107, so its end IS 112, and the reference has the point sitting on that
   end rather than 4px inside it. Points at y 62.5 and y 81.2. Our glyph is
   11 wide and its point is 8.2 down its 9.97 box, so: left = centre - 5.5, top = point
   - 8.2. Only the glyph grew; the anchors are the design's. */
.sc.ag .sc-tri { left: 76.5px; top: 54.3px; }
.sc.en .sc-tri { left: 106.5px; top: 73.1px; }

/* Left-to-right wipe for the bar and the wave. */
.sc.en .sc-art, .sc.rh .sc-art { clip-path: inset(0 100% 0 0); }
.rp-scores.run .sc.en .sc-art, .rp-scores.run .sc.rh .sc-art { animation: sc-wipe .6s cubic-bezier(.3,.7,.3,1) var(--d) both; }
@keyframes sc-wipe { to { clip-path: inset(0 0 0 0); } }

/* Agility instead sweeps around the dome's centre, so the arc appears to be drawn. The
   centre sits at 95.4% down the art box — that is the ellipse's own centre, not the
   box's, which is why the number is not a round one. */
@property --sweep { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
/* Two masks, intersected: the sweep that draws the arc, and a fade that ends the glow
   inside the art box. The art is 159.33 tall at top 6.67 in a 166 card, so its bottom
   edge IS the card wall — overflow:hidden was slicing the blur off in a straight line
   instead of letting it die away. The arc itself sits at 33-58% of the box, well above
   where this starts. */
.sc.ag .sc-art {
  /* The sweep only. The export used to carry a flat 13/255 matte all the way to its own
     box edges, and THAT straight line at the card wall was the "cut blur" — it has been
     subtracted out of the file itself, so the arc now falls to nothing on its own and
     needs no edge fades at all. */
  -webkit-mask-image: conic-gradient(from 270deg at 50% 95.4%, #000 0deg, #000 var(--sweep), transparent var(--sweep));
          mask-image: conic-gradient(from 270deg at 50% 95.4%, #000 0deg, #000 var(--sweep), transparent var(--sweep));
}
/* Rhythm's wave genuinely runs off both sides (its outer humps are drawn at x -37 and
   154), so unlike Agility it does need its side walls softened. */
.sc.rh .sc-art {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
}
.rp-scores.run .sc.ag .sc-art { animation: sc-sweep .62s cubic-bezier(.3,.7,.3,1) var(--d) both; }
@keyframes sc-sweep { from { --sweep: 0deg; } to { --sweep: 180deg; } }

/* Order per card: graph draws, guide extends, then the marker drops in quickly on the
   guide's heel. The number never counts, it rises in last. */
.rp-scores.run .sc-tri { animation: sc-fade .24s ease calc(var(--d) + 880ms) both; }
@keyframes sc-fade { to { opacity: 1; } }
.rp-scores.run .sc-n { animation: sc-num .34s ease calc(var(--d) + 1020ms) both; }
@keyframes sc-num { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ── Recommendation ────────────────────────────────────────────────────────
   Carried over unchanged from the previous Records page — the design reuses it. */
/* One card per flick, the way the Packs feed does it — scroll-snap-stop is the part
   that stops a hard swipe skimming past two of them. 26px sides, not 12: that is
   (360-308)/2, so a centred card rests exactly at scrollLeft 0, 316 and the end. */
.rec-scroll {
  display: flex; gap: 8px; overflow-x: auto;
  /* Each card is its own height: the default stretch made the one-line card as tall as
     the wrapped ones and left a band of empty white under its button. */
  align-items: flex-start;
  /* 12px, matching .rp-sec — the first card's left edge lines up with the heading above
     it. It was 26 ((360-308)/2) to CENTRE a card; snapping to start instead is what lets
     the row keep one-card-per-flick while sitting on the section's own margin. */
  margin: 0 -12px -20px; padding: 0 12px 24px;
  scroll-padding-left: 12px;
  scroll-snap-type: x mandatory;
}
.rec-card { scroll-snap-align: start; scroll-snap-stop: always; }
/* Same arrival as the sections. The row's own padding-bottom (taken back with a negative
   margin) is what gives a rising card somewhere to rise FROM — a horizontal scroller
   clips vertically too. */
.rec-scroll .rec-card { opacity: 0; }
.rec-scroll.run .rec-card { opacity: 1; animation: rp-rise .5s cubic-bezier(.2,.85,.3,1) both; }
.rec-scroll.run .rec-card:nth-child(2) { animation-delay: .08s; }
.rec-scroll.run .rec-card:nth-child(3) { animation-delay: .16s; }
@media (prefers-reduced-motion: reduce) { .rec-scroll { scroll-snap-type: none; } }
/* No entrance here on purpose: .rec-scroll is a horizontal scroller, so its overflow
   clips vertically too and a rising card gets its top sliced off mid-animation. */
.rec-scroll::-webkit-scrollbar { display: none; }
.rec-card {
  flex: 0 0 308px;
  background: #fff;
  border-radius: 40px;
  padding: 4px;
  text-decoration: none;
  box-sizing: border-box;
}
.rec-thumb {
  position: relative;
  height: 189px;
  border-radius: 38px;
  background-size: cover;
  background-position: center;
  padding: 16px;
  box-sizing: border-box;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
}
.rec-thumb { -webkit-mask-image: -webkit-radial-gradient(#fff, #000); }   /* see packs.css */
.rec-thumb.rec-dark::before { content: ""; position: absolute; inset: 0; border-radius: 38px; background: rgba(0,0,0,0.2); }
.rec-badge {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  border-radius: 80px;
  background: rgba(255,255,255,0.9);
  color: var(--color-neutral-600);
  font-family: var(--font-supreme);
  font-size: 12px;
  letter-spacing: -0.5px;
  box-shadow: 0 0 12px rgba(0,0,0,0.12);
}
.rec-badge .badge-fire { width: 11px; height: 13px; display: block; flex: none; }
/* A chip that belongs to its own picture rather than a white one laid on top: the shot's
   dominant colour at 80% with a #ffffff 20% layer over it — the same two-layer build the
   Packs badges use. --tint is per card, set in the markup from the file's own pixels. */
.rec-badge-tint {
  background: linear-gradient(rgba(255,255,255,0.2), rgba(255,255,255,0.2)),
              rgba(var(--tint), 0.8);
  /* neutral-600 was chosen against a white chip; on the tan it measures under 3:1 and
     the label went soft. Near-black against the rendered rgb(205,168,131) is about 8:1. */
  color: #141A1A;
}

/* 653:8191, straight off the frame: info 300x78, 16px padding; the text column is 160
   wide with the title at 18/22 and the meta row 16 tall, 4 between them; the button is a
   fixed 108x36 parked at x176 (so 16 from the right wall). */
/* 16 on the sides and top, 20 at the foot, 20 minimum between the text and the button.
   No fixed height: a title that wraps to two lines makes the container — and the card —
   taller by exactly that line. One line comes out at 16 + 22 + 4 + 16 + 20 = 78, which is
   the frame's own height (653:8191).
   flex-basis 0 on the text column, not 160: its min-content would otherwise set the card's
   automatic minimum size and push the card past 308. */
.rec-info { display: flex; align-items: flex-start; gap: 20px; padding: 16px 16px 20px; }
.rec-txt { flex: 1 1 0; min-width: 0; }
.rec-title { margin: 0 0 4px; font-family: var(--font-supreme-bold); font-size: 18px; line-height: 22px; letter-spacing: -0.54px; color: var(--color-black); }
.rec-meta { margin: 0; font-family: var(--font-supreme); font-size: 13px; line-height: 16px; letter-spacing: -0.5px; color: var(--color-neutral-500); }
.rec-start {
  flex: none;
  width: 108px; height: 36px;
  box-sizing: border-box;
  margin-top: 3px;   /* the frame parks it at y19 in a 78 box, 3 below the text's top */
  display: flex; align-items: center; justify-content: center; gap: 4px;
  background: var(--color-red);
  color: #fff;
  border-radius: 999px;
  padding: 0;
  font-family: var(--font-supreme-bold);
  font-size: 14px;
  letter-spacing: -0.5px;
}
.rec-start img { width: 18px; height: 18px; filter: brightness(0) invert(1); }

/* Report app bar: back, the pack pill, share. */
.rp-appbar { display: flex; align-items: center; justify-content: space-between; height: 66px; padding: 0 16px; box-sizing: border-box; }
.rp-appbar .icon-btn { background: none; border: none; padding: 0; display: block; cursor: pointer; }
.rp-appbar img { width: 24px; height: 24px; display: block; }
/* Share is an 18 glyph in a 24 frame in the design, not a 24 glyph. */
.rp-appbar .ic-share { width: 18px; height: 18px; margin: 3px; }
.rp-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 12px;
  box-sizing: border-box;
  border-radius: 999px;
  background: rgba(0,0,0,0.2);
  text-decoration: none;
  font-family: var(--font-supreme-bold);
  font-size: 15px;
  letter-spacing: -0.5px;
  color: #fff;
}
.rp-pill img { width: 16px; height: 16px; filter: brightness(0) invert(1); }

/* Sections rise in once on arrival, staggered — same curve as the Packs and Library
   lists so every list screen in the app enters the same way. */
.rp-body > .rp-seg,
.rp-body > .rp-stats { animation: rp-rise .42s cubic-bezier(.2,.85,.3,1) both; }
.rp-body > *:nth-child(2) { animation-delay: .05s; }
.rp-body > *:nth-child(3) { animation-delay: .10s; }
/* Everything past the first screen arrives when it is scrolled to instead. On the load
   stagger they had all risen while the hero was still being looked at, so scrolling down
   revealed sections that had already finished arriving. .run comes from scores.js. */
/* opacity has to be restored on .run as well as animated: rp-rise only declares a `from`,
   so its `to` is whatever the element's own style says — which, with 0 as the resting
   value, meant it animated 0 to 0 and the section never appeared. */
.rp-body > .rp-sec { opacity: 0; }
.rp-body > .rp-sec.run { opacity: 1; animation: rp-rise .5s cubic-bezier(.2,.85,.3,1) both; }
/* The first section is already on screen when the report lands, so hiding it left a
   headed-but-empty gap that read as a broken page. Its title stays put and only the two
   cards under it arrive. Every section below keeps the whole-block slide. */
.rp-body > .rp-sec:first-of-type { opacity: 1; animation: none; }
/* On the Report the first section is Session Highlights, and it does not stay put — it
   comes in ON THE SAME CLOCK as the metric row above it, .30s continuing that row's own
   .06/.14/.22 stagger so the four read as one arrival. Holding back only its two cards
   was the half-measure this replaces: the heading appeared on time and then sat over an
   empty white card until the section was scrolled to.
   Down here rather than beside the metric row's rules because the `opacity: 0` reveal
   above has the same specificity and has to be beaten. opacity is restated because
   rp-stat-in declares only a `from`, so its `to` would be that 0. */
.rp-body > .rp-sec:first-of-type:has(> .rp-pair) {
  opacity: 1;
  animation: rp-stat-in .42s cubic-bezier(.2,.85,.3,1) .30s both;
}
.rp-draw .rp-body > .rp-sec:first-of-type:has(> .rp-pair) { animation-play-state: paused; }
.rp-play .rp-body > .rp-sec:first-of-type:has(> .rp-pair) { animation-play-state: running; }
@keyframes rp-rise { from { opacity: 0; transform: translateY(18px); } }

/* Day picker arrows. The forward arrow is dead on today — there is no tomorrow to
   show — and the period picker is buttons now, not links. */
.rp-daynav .nav-day { background: none; border: none; padding: 0; cursor: pointer; display: block; }
.rp-daynav .nav-day[disabled] { opacity: .35; cursor: default; }
/* Nothing above should move for anyone who asked it not to — show the end state. */
@media (prefers-reduced-motion: reduce) {
  .rp-balance .lb-heat { animation: none; opacity: 1; }
  .rp-balance .lb-read { opacity: 1; animation: none; }
  .rp-scores .sc-n, .rp-scores .sc-tri { opacity: 1; animation: none; }
  .rp-hcard .badge svg { opacity: 1; transform: none; animation: none; }
  .rec-badge { opacity: 1; transform: none; animation: none; }
  .rp-body > .rp-sec,
  .rp-body > .rp-sec:first-of-type:has(> .rp-pair) { opacity: 1; animation: none; }
  .rp-scores .sc-art { clip-path: none; -webkit-mask-image: none; mask-image: none; animation: none; }
  .rp-scores .sc.st { --pass: 79px; animation: none; }
  .rp-scores .sc-dash, .rp-scores .sc-track, .rp-scores .st-ticks { clip-path: none; animation: none; }
  .rp-scores .st-ticks.hot { clip-path: inset(0 calc(100% - 80.8px) 0 0); }
  .rp-stats .rp-stat { animation: none; }
}
