/* rx-block: rx-stats */

/* --- Stats band ------------------------------------------------------------
   Two halves that do NOT share a grid logic. The intro is ordinary flow; the
   right-hand field is a fixed-aspect canvas carrying five arcs and four
   metrics pinned to points on those arcs.

   FIELD COORDINATES. Every number on the right-hand side comes from one box:
   Figma x 600..1512, y 4230..4800 — 912 x 570. Percentages are of that box,
   and cqw is 1/912 of its width, so 69px of Figma type is 7.5658cqw. Nothing
   here is eyeballed: change the field's width and the arcs, the dots and the
   figures all rescale together and the dots stay on their circles.

   The circles: r = 1152.454, centres all at field y = 313.876, x stepping
   162.815 from -1074.034. Only about 56 degrees of each is ever inside the
   section, which is what makes them read as near-vertical arcs rather than
   circles. The section clips them (overflow: hidden below).
   --------------------------------------------------------------------------- */

.rx-stats {
  /* THE ARCS RUN PAST THIS SECTION ON PURPOSE, so nothing may crop them here.
     They are 236.842% of the field tall and their fade sits at their own ends,
     OUTSIDE the section — which is the whole mechanism: a neighbour set to
     overlap covers that faded region, and without an overlap the fade shows
     against the page. Crop them at the section and the fade has nowhere to
     happen, which is what `overflow: clip` did.

     Nothing is needed in its place: _base.css clips the page's horizontal
     overflow on .body-wrapper, so the arcs cannot widen the document, and
     `visible` creates no scroll container — the metrics' view() drift still
     resolves to the page, which is why the old rule used `clip` over `hidden`.

     PAINT ORDER IS THE OTHER HALF. _utilities.css raises every section with
     `.rx-section:not(:has(.rx-rings)) { z-index: 1 }`; left raised, this block
     would paint its arcs OVER a neighbour instead of behind it. Dropping back
     to `auto` puts the neighbours, still at 1, on top at both ends — the block
     above wins despite being earlier in the DOM, and the block below keeps
     winning as it already did. Do NOT reach for z-index: -1: _utilities.css
     records it measured on /solutions/industry/healthcare 2026-09-08, where a
     section at -1 fell below its own ancestor divs' background layer and hit
     testing followed paint order, so every click inside it hit HubSpot's
     div.hs_cos_wrapper. Lower nothing; stop raising this one. */
  overflow: visible;

  /* The arcs need room above and below the metrics — in the design they run
     226px clear of the card above and 285px clear of the card below, and that
     empty sweep is most of what the section is. Percentages of the section's
     own width, so the margin around the composition scales with it rather
     than staying a fixed band that the arcs outgrow.
     Written as the section's own padding VARIABLES, not as padding, so an
     editor's spacing choice on the module still lands: a chosen value sets
     the same property and simply replaces this default. */
  /* THE SECTION IS A FULL VIEWPORT TALL. That is the design: the arcs sweep
     an empty stage and the metrics sit in the middle of it, so the block is
     sized by the viewport rather than by its own contents plus padding.

     The padding is now only a floor for short viewports — the min-height does
     the work and the content is centred in whatever is left. The old
     percentage padding (226/285 of 1512) was standing in for the height and
     got the proportion right only at one window size. */
  /* THE SECTION IS A FULL VIEWPORT TALL, and the arc layer very nearly fills
     it: measured at 1512x1400, the arcs are 236.842% of a 570 field = 1350
     against a 1400 section. So the clip below is not what shapes the ends of
     the drawing — the fade on .rx-stats__arcs is. */
  /* 100svh — EXCEPT where the arc drawing cannot fill it.

     The drawing is locked 1:1 to the field so the dots stay on their lines,
     and the field is capped at its column, so the drawing's height is fixed by
     the section's WIDTH: 236.842% of a 912:570 field that is 60.317% of the
     section wide, i.e. 1.48026 x 0.60317 = 89.285% of the width. Once the
     window is taller than that, a 100svh stage outgrows its own arcs and they
     end short of both neighbours. Measured 2026-09-14 on a copy of the live
     /labor-market-index with the draw forced to its end frame:

       window      live gap top/bottom    capped
       1025x1204   75 / 71                0 / 1
       1100x1400   154 / 120              0 / 2
       1180x1204   22 / 1                 0 / 1
       1512x900    unchanged — section stays 900
       2041x1204   unchanged — section stays 1204

     Lengthening the arcs instead is not available: filling 1025x1204 needs a
     124 degree sweep against the drawn 70, and 1025x1600 needs more than the
     circle's own radius.

     vw includes the scrollbar, so the cap runs a few px generous; the 96px
     neighbour overlap absorbs it. Below 1025 the section already drops the
     stage for `auto`. */
  min-block-size: min(100svh, 89.285vw);
  display: grid;
  align-content: center;

  --rx-section-pt: var(--rx-space-9);
  --rx-section-pb: var(--rx-space-9);


  /* --- Motion ------------------------------------------------------------
     The arcs draw from the top down; each one drops its dot at the point
     where its metric lives, and the figure follows a beat later. The four
     go left to right, --rx-stats-step apart, which is also the order the
     arcs cross the frame.

     WHAT MOVES WHAT. Three layers, on three CSS properties that compose
     rather than overwrite each other, which is what lets each one be timed
     and driven independently:

       .rx-stats__dot    `transform` — a rotation about its circle's centre.
                         This is the entrance: real travel along the real arc,
                         not an approximation of it.
       .rx-stats__value  `transform` — fades in while sliding OUT from the
       .rx-stats__label                dot, so the figure reads as issued by
                                       its marker.
       .rx-stats__item   `translate`  — the scroll drift, carrying the dot and
                                       its figure together.

     An earlier pass rode the whole item in on the arc's chord so the dot and
     type travelled as one rigid block. It was off the line, visibly: a chord
     across 22 degrees departs from the arc by a 21px sagitta at the midpoint,
     which is small against the 440px of travel but not small against a 30px
     dot sitting on a hairline. Rotating the item instead would put the type
     on a 22 degree tilt. Hence the split — only the dot rotates. */
  --rx-stats-step: 150ms;
  --rx-stats-draw: 1200ms;
  --rx-stats-ride-dur: 1000ms;
  --rx-stats-ride-lag: 100ms;
  --rx-stats-text-dur: 620ms;
  --rx-stats-text-lag: 430ms;
  /* How far back toward the dot the type starts. It travels OUT from there to
     its resting place, so the figure reads as being issued by its marker
     rather than fading up in position. Short on purpose — the dot has just
     crossed 440px, and type that moves as far as the thing that delivered it
     stops looking delivered. */
  --rx-stats-text-from: 2.2cqw;
  /* How far back up its own arc a dot starts, in degrees. One value for all
     four: they are on circles of the same radius, so the same angle is the
     same distance travelled. */
  --rx-stats-sweep: -22deg;
  /* The marker wakes up: it travels as a bare core ring and only takes its
     outer ring once it has arrived, so a dot in flight reads as dormant and a
     dot on station reads as live. */
  --rx-stats-open-dur: 520ms;
  --rx-stats-open-at: 900ms;
  /* The shared decelerate curve. An alias, not a copy: re-point this if the
     counter should ease differently from the rest of the site. */
  --rx-stats-ease: var(--rx-ease-out);

  /* --- The marker's activated ring ---------------------------------------
     Modelled on the button halo in _buttons.css: a thin ring that grows OUT
     of the shape it belongs to and stops. Same idea, smaller numbers — the
     button's 9px spread and 2px stroke are sized for a 45px pill and read as
     a heavy collar around a 15px dot.

     Figma draws this ring at 30.5px with a 2px #2a2a2a stroke. Both are
     reduced here on purpose: at the drawn weight the ring is the loudest
     thing in the block, competing with the figures it is meant to be
     annotating. The colour goes to the gold the arcs are already drawn in,
     so the marker belongs to its line rather than to the type.

     --spread is the halo's own token name: how far the ring parks from the
     core it grew out of. */
  --rx-stats-dot-core: 1.5998cqw;   /* 14.59 — the dormant ring, Figma's */
  --rx-stats-dot-spread: 0.44cqw;   /* ~4px, against the button's 9 */
  --rx-stats-dot-ring: 0.11cqw;     /* ~1px, against Figma's 2 */
  --rx-stats-dot-halo: var(--rx-gold-base);
  /* No overshoot: the button's halo eases out and stops, and the marker is
     quoting that gesture. */
  --rx-stats-ease-open: var(--rx-ease-out);
}

/* --- The arcs FADE OUT AT THEIR OWN ENDS, outside the section ----------------
   The arcs run 236.842% of the field tall, so they extend well past this
   section at both ends — and the fade sits at THOSE ends, out in the
   neighbouring blocks' territory. That placement is the whole mechanism:

     - a neighbour set to overlap covers the faded region, so the reader never
       sees the arcs thin out at all (Figma 3440:548);
     - with no overlap, the fade shows against the page and the drawing has no
       edge — there is no line where it stops, it just stops being there.

   Which is why .rx-stats must not crop them. An earlier pass clipped the arcs
   at the section and put the fade at the SECTION's edges, measured in JS. That
   is a different drawing: the fade then happens inside the block, so a
   neighbour can never hide it and the arcs still END at the section's floor —
   which read as a cut wherever a block followed immediately.

   THE LENGTH IS ONE OVERLAP, derived rather than transcribed: the fade exists
   to be covered by an overlapping neighbour, so it spans what an overlap
   travels — `.rx-section--pt-overlap` / `--pb-overlap` both move by
   --rx-space-9, as does arc_tuck. Retune the spacing scale and they stay in
   step. 3440:548 is the CHECK, not the source: measured there the fade runs
   ~120 in its own 1512x3096 coordinates against our 96.

   ONLY THE FLAT SET IS MASKED. The stacked arcs are short sags sitting inside
   the field, so they never reach the section's edges and there is nothing to
   fade; they still bleed sideways on purpose. */
.rx-stats__arcs--flat {
  /* One overlap — a LENGTH, not a share of this box, because what it has to
     agree with is a spacing token in rem. */
  --rx-stats-arc-fade: var(--rx-space-9);
  /* Prefixed as well as not — Safari only dropped the -webkit- prefix at 16.4,
     and an unprefixed-only mask on an older WebKit is dropped outright, which
     loses the fade rather than degrading it. Same note as _logo-row.css, the
     theme's other edge fade and the shape this copies. */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--rx-stats-arc-fade),
    black calc(100% - var(--rx-stats-arc-fade)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--rx-stats-arc-fade),
    black calc(100% - var(--rx-stats-arc-fade)),
    transparent 100%
  );
}

/* --- Tucking the faded ends under a neighbour -------------------------------
   The fade is self-sufficient — it reaches zero at the screen edge, so there is
   never a cut to hide. But where the block above or below carries its own panel
   or card, the design puts that surface OVER the faded end (Figma 3440:548), so
   the reader never sees the arcs thin out at all.

   THE CONTROL LIVES HERE, NOT ON THE NEIGHBOUR. The shared `space_top` /
   `space_bottom` overlap choices move THIS block over its neighbour and raise it
   with `z-index: 1` — the opposite relationship — and `makeSectionStyles({
   overlap: false })` drops them entirely for any block with no opaque surface to
   overlap with. So a neighbour cannot be relied on to offer one, and this block
   can only depend on itself.

   PAINT ORDER IS ALREADY SOLVED IN _utilities.css; this only has to stop opting
   out of it. That file raises every section with
   `.rx-section:not(:has(.rx-rings)) { z-index: 1 }` so that a rings host, left
   at `auto`, sits under its neighbours. The stats band is currently caught by
   that rule and raised, which is why it paints over anything it is pulled into.
   Dropping back to `z-index: auto` puts the neighbours — still at 1 — on top, at
   BOTH edges: the block above wins despite being earlier in the DOM, and the
   block below keeps winning as it already did.

   DO NOT REACH FOR `z-index: -1` HERE. It was the obvious first answer and it is
   wrong: _utilities.css records it measured on /solutions/industry/healthcare,
   2026-09-08 — a section at -1 lands below its own ancestor divs' background
   layer, and hit testing follows paint order, so every click inside it hit
   HubSpot's div.hs_cos_wrapper instead of the control. It reads as a dead JS
   driver and is not one. Lower nothing; stop raising this one.

   The specificity has to clear (0,2,0) to beat that rule, hence the three
   classes. */
/* Three classes because _utilities.css's raise is (0,2,0) and a tie would
   leave this depending on stylesheet order. Applies always, not only when
   tucking: the arcs leave the section at BOTH ends on every page, so they must
   always paint behind whatever they run into. */
.rx-section.rx-stats.rx-stats {
  z-index: auto;
}

.rx-stats--tuck-top,
.rx-stats--tuck-both { margin-block-start: calc(var(--rx-space-9) * -1); }

.rx-stats--tuck-bottom,
.rx-stats--tuck-both { margin-block-end: calc(var(--rx-space-9) * -1); }

/* The field bleeds to the section's right edge, exactly as it does in the
   design (600..1512 of a 1512 frame). That is why this is not a container:
   constraining it to the measure would shrink the whole construction and the
   figures with it. */
.rx-stats__inner {
  container-type: inline-size;
  display: grid;
  grid-template-columns: 39.683% 60.317%;
  align-items: start;
}

.rx-stats__intro {
  /* Figma puts the copy at x=173 of 1512; expressed against the section width
     so the relationship to the arcs holds at any size; the gutter is the floor. */
  padding-left: max(var(--rx-gutter), 11.442cqw);
  padding-right: var(--rx-space-6);
  /* CENTRED ON THE METRICS AREA, not placed at a drawn offset. This used to be
     `padding-top: 11.97cqw` — Figma's heading 181 below the field top — which
     fixes the intro's TOP, so its centre moved with however many lines the copy
     wrapped to. Measured on a copy of the live /labor-market-index, intro centre
     against field centre: 1512 Narrow +118, Default +69, Wide/Full +32;
     2041 Narrow +84, Default +45, Wide +5, Full -19. The Intro width choice made
     it worse, because every option wraps differently. Centring holds for any
     copy, width choice or screen. Below 1025 the intro and field are separate
     rows, so this has no effect there. */
  align-self: center;
}

.rx-stats__eyebrow { margin-bottom: var(--rx-space-3); }
/* Intro width. Default is the drawn measure and is unchanged; the other
   choices set the same two variables. Every width is capped at 100% of the
   intro column, so Wide and Full stop at the column edge and can never reach
   the metrics beside it — measured on /labor-market-index 2026-09-14, the
   column's usable width is 329px at 1280, 395px at 1512 and 544px at 2041,
   and the drawn 12ch heading sat at 333px in all three, leaving ~211px unused
   at 2041.

   The heading measure is in ch because the drawn one was; the body measure is
   in cqw (of the section) because Figma's 303-of-1512 is a share of the frame
   and has to hold its relationship to the arcs as the page scales.

   Below 1025 the stacked layout sets its own widths and ignores these. */
.rx-stats {
  --rx-stats-heading-w: 12ch;
  --rx-stats-body-w: 20.04cqw;
}
.rx-stats--intro-wide   { --rx-stats-heading-w: 17ch; --rx-stats-body-w: 26cqw; }
.rx-stats--intro-full   { --rx-stats-heading-w: 100%; --rx-stats-body-w: 100%; }

.rx-stats__heading { max-width: min(var(--rx-stats-heading-w), 100%); }
.rx-stats__lead,
.rx-stats__copy {
  margin-top: var(--rx-space-5);
  /* Figma sets the measure at 303 of 1512 — narrow enough that the copy sits
     as a block against the heading rather than running under the arcs. */
  max-width: min(var(--rx-stats-body-w), 100%);
}

/* Stated rather than inherited, so the Copy size choice has something to
   multiply. --rx-text-base IS what body sets, so with no class this computes to
   the 16 it already inherited. The lead below keeps its own larger size. */
.rx-stats__copy {
  font-size: calc(var(--rx-text-base) * var(--rx-copy-scale, 1));
}

.rx-stats__lead {
  font-size: var(--rx-text-lg);
  line-height: var(--rx-leading-normal);
  color: var(--rx-text);
}

.rx-stats__actions { margin-top: var(--rx-space-6); }

/* --- The field ------------------------------------------------------------- */

.rx-stats__field--orbit {
  position: relative;
  container-type: inline-size;
  /* THE ARCS MUST SPAN THE BLOCK, top edge to bottom edge.

     They cannot be stretched or scaled to do it: the dots sit on the arcs only
     because the arc box is exactly 96.491% x 236.842% of the field, which maps
     the viewBox 1:1 onto field coordinates. Touch that mapping and the dots
     come off the lines. So the FIELD has to be big enough instead:

       arc span   = 2.36842 * fieldHeight  >=  sectionHeight
       fieldWidth = 1.6 * fieldHeight      >=  0.6756 * sectionHeight

     and the section is 100svh, so the field is at least 67.55svh wide. This is
     a MINIMUM. It was first written as max-inline-size — a maximum — which
     guaranteed the opposite of the requirement, and made the lines stop further
     short the taller the window got.

     Overflowing the column to the right is correct: the design crops the arcs
     at the frame edge, which is why only ~56 degrees of each circle is ever in
     view. .rx-stats already clips. */
  /* Capped at the column so the minimum can never exceed the screen: 67.55svh
     is 511px on a 757-tall phone, which pushed the whole field out of a 500px
     viewport. The arcs give up their full sweep before the layout does. */
  min-inline-size: min(67.55svh, 100%);

  /* THE DASH HAS TO BE AT LEAST THE ARC'S ON-SCREEN LENGTH, which grows with
     the field. Written as max(1000px, 160cqw).

     A plain 1000 was shipped on the reading that pathLength="1000" normalises
     the dash, making 1000 the whole path at any size. Measured 2026-09-14 on a
     copy of the live /labor-market-index with motion on and the draw forced to
     its end frame: the computed dasharray is `1000px` at every width, and the
     painted arc stops short of the block below — 1px at 1512, 21px at 2041,
     247px at 2560. The gap is identical with the edge fade removed, so it is
     the dash, not the mask. The arc's on-screen length is its 1408 viewBox
     units x (field width / 912), so a fixed number is right at one width.

     160cqw is that length plus ~4% (1408 / 912 = 154.4cqw; cqw here is the
     field's, the nearest container). Measured with it: 1px at 1280, 1512,
     2041 and 2560. The 1000px floor keeps the old value wherever 160cqw would
     fall below it — the phone's stacked field (739px), where the stacked
     arcs already paint edge to edge at 1000.

     NEVER CHECK THIS WITH --force-prefers-reduced-motion. The reduced-motion
     path removes the dasharray entirely, so the gap cannot appear and every
     width reads as correct.

     A separate limit, not this dash: on a narrow, very tall window the field
     is capped at its column and the arcs are physically shorter than the
     section. With an effectively infinite dash and no fade, 1025x1204 still
     stops 57px short and 1100x1204 10px; at 800 and 900 tall, and from 1180
     wide up, they meet. */
  --rx-stats-arc-len: max(1000px, 160cqw);
  /* Locks the coordinate space: with the ratio fixed, a percentage of height
     and a percentage of width are two views of the same number, so the arcs
     (sized off height) and the dots (sized off width) cannot drift apart. */
  aspect-ratio: 912 / 570;
}

/* Sized and placed so the viewBox maps 1:1 onto field coordinates:
   x -140..740 is -15.351%..81.140% of 912, y -360..990 is -63.158%..236.842%
   of 570. The arcs run well outside the field on every side; the section
   clips them, which is what Figma's frame does too. */
.rx-stats__field--orbit .rx-stats__arcs {
  position: absolute;
  left: -15.351%;
  top: -63.158%;
  width: 96.491%;
  height: 236.842%;
  overflow: visible;
  pointer-events: none;
}

.rx-stats__arcs--stacked { display: none; }

.rx-stats__arc {
  fill: none;
  stroke: var(--rx-gold-t1);
  stroke-width: 1;
  /* The arcs are hairlines in the design and must stay hairlines however far
     the field is scaled, so the stroke opts out of the viewBox transform. */
  vector-effect: non-scaling-stroke;
}

/* ONE PATH SHAPE, PLACED BY TRANSLATE. The markup emits circle 0's path once
   per arc; this steps each copy along by its index. A CSS transform on an SVG
   child resolves in USERSPACE units, so it is carried by the viewBox's stretch
   exactly as a hardcoded coordinate would be — which is the whole point: the
   pitch can vary with the metric count without any path data changing.

   The arcs always span the SAME x range whatever the count (pitch * N is
   constant), so the flat viewBox never changes and the composition keeps its
   width. Only the spacing inside it opens or closes. */
.rx-stats__arcs--flat .rx-stats__arc {
  transform: translateX(calc(var(--rx-stats-k, 0) * var(--rx-stats-arc-pitch, 162.815) * 1px));
}

.rx-stats__arcs--stacked .rx-stats__arc {
  /* The stacked pitch is a fixed 178 design units — the band grows with the
     count instead, which the svg's viewBox height tracks (see module.html). */
  transform: translateY(calc(var(--rx-stats-k, 0) * 178px));
}

/* The arc pitch per count: 651.26 (the drawn span, 4 gaps of 162.815) over N,
   so N metrics get N+1 arcs across the same field. At four this IS 162.815. */
.rx-stats__field--n2 { --rx-stats-arc-pitch: 325.630; }
.rx-stats__field--n3 { --rx-stats-arc-pitch: 217.087; }
.rx-stats__field--n4 { --rx-stats-arc-pitch: 162.815; }
.rx-stats__field--n5 { --rx-stats-arc-pitch: 130.252; }

/* --- Metrics --------------------------------------------------------------- */

/* Per-metric label size. A MULTIPLIER, not three font sizes: the label is set
   in three different units across the two layouts (--rx-text-sm in the plain
   fallback, cqw in the drawn field, design-pixel mu when stacked), and each of
   those is load-bearing — a literal size here would strand the label outside
   the coordinate space its layout is built in. One ratio rides all three.

   Set per metric so a long label can be stepped down on its own; the default
   is the drawn 18 and costs nothing. */
.rx-stats__item { --rx-stats-cap: 1; }
.rx-stats__item--cap-small { --rx-stats-cap: 0.85; }
.rx-stats__item--cap-large { --rx-stats-cap: 1.2; }

.rx-stats__list { list-style: none; margin: 0; padding: 0; }

/* THE FALLBACK NEEDS A DESKTOP LAYOUT TOO. fields.js caps the repeater at 2-5,
   so every count an editor can reach is drawn — but content saved before that
   cap can still carry one metric or six, and without this the items were bare
   block <li>s stacked down the right-hand column with no arcs, which is what a
   three-metric band looked like at every width above 1024. A plain grid is the
   right answer for a count the composition does not cover; being unreachable
   is not a reason to leave it broken. */
.rx-stats__field:not(.rx-stats__field--orbit) .rx-stats__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--rx-space-8) var(--rx-space-7);
}

.rx-stats__field--orbit .rx-stats__list {
  position: absolute;
  inset: 0;
}

.rx-stats__field--orbit .rx-stats__item {
  position: absolute;
  width: 25.192cqw;   /* 229.75 */
}

/* Figma group origins, as percentages of the field. Each is the top-left of
   the group; the dot centre sits 15.25 in and down from it, and the type
   hangs off that.

   Each item carries the numbers its motion is built from, all derived from
   where it sits on its own circle:

     --rx-stats-org      where that circle's CENTRE falls, expressed in the
                         dot's own coordinates. The dot rotates about this
                         point, which is what puts its travel exactly on the
                         arc rather than near it.
     --rx-stats-tx/ty    the unit tangent at rest — the direction the arc
                         actually runs through this point.
     --rx-stats-amp      how far it drifts along that tangent on scroll. All
                         four POSITIVE, so the group moves as a shoal in one
                         direction; the rates differ so it does not read as
                         one rigid block sliding. The spread between the
                         largest and smallest is ~12px against gaps of 150px
                         and more, so no two can close on each other. */
.rx-stats__field--n4 .rx-stats__item--1 {
  left: 6.798%;  top: 53.670%;
  --rx-stats-org: -126.238cqw -0.800cqw;
  --rx-stats-tx: -0.00634;   --rx-stats-ty: 0.99998;
  --rx-stats-amp: 0.9cqw;
}

.rx-stats__field--n4 .rx-stats__item--2 {
  left: 22.699%; top: 14.773%;
  --rx-stats-org: -124.285cqw 23.510cqw;
  --rx-stats-tx: 0.18578;    --rx-stats-ty: 0.98259;
  --rx-stats-amp: 1.6cqw;
}

.rx-stats__field--n4 .rx-stats__item--3 {
  left: 42.545%; top: 65.118%;
  --rx-stats-org: -126.280cqw -7.955cqw;
  --rx-stats-tx: -0.06288;   --rx-stats-ty: 0.99802;
  --rx-stats-amp: 2.2cqw;
}

.rx-stats__field--n4 .rx-stats__item--4 {
  left: 59.986%; top: 31.923%;
  --rx-stats-org: -125.867cqw 12.792cqw;
  --rx-stats-tx: 0.10112;    --rx-stats-ty: 0.99488;
  --rx-stats-amp: 1.3cqw;
}

/* --- The counts Figma does not draw ----------------------------------------
   Everything below is GENERATED from the same circle family, not transcribed.
   The rhythm the drawn four establish is the thing being kept:

     - metrics alternate between a LOW band and a HIGH band;
     - each band drifts DOWN across the composition;
     - the band endpoints are the drawn y values (53.670/65.118 low,
       14.773/31.923 high), so a derived metric is always INTERPOLATED between
       two drawn positions and never extrapolated past them;
     - x then falls out of the circle equation, which is what keeps the dot on
       its arc — the same construction the drawn four satisfy.

   Feeding N=4 back through it reproduces the drawn rules above to ~1.5px,
   which is inside the sub-3px nudge Figma's own dots carry (recorded in the
   stacked block below). The drawn four are still spelled out literally rather
   than generated, so the shipped home page cannot move by even that much.

   --rx-stats-amp is the scroll drift, and it stays a set of differing rates in
   the drawn 0.9-2.2cqw range for the same reason it does at four: equal rates
   read as one rigid block sliding. */

.rx-stats__field--n2 .rx-stats__item--1 {
  left: 6.924%;  top: 53.670%;
  --rx-stats-org: -126.363cqw -0.800cqw;
  --rx-stats-tx: -0.00633;   --rx-stats-ty: 0.99998;
  --rx-stats-amp: 0.9cqw;
}

.rx-stats__field--n2 .rx-stats__item--2 {
  left: 40.425%; top: 14.773%;
  --rx-stats-org: -124.159cqw 23.511cqw;
  --rx-stats-tx: 0.18605;    --rx-stats-ty: 0.98254;
  --rx-stats-amp: 1.6cqw;
}

.rx-stats__field--n3 .rx-stats__item--1 {
  left: 6.924%;  top: 53.670%;
  --rx-stats-org: -126.363cqw -0.800cqw;
  --rx-stats-tx: -0.00633;   --rx-stats-ty: 0.99998;
  --rx-stats-amp: 0.9cqw;
}

.rx-stats__field--n3 .rx-stats__item--2 {
  left: 28.523%; top: 14.773%;
  --rx-stats-org: -124.159cqw 23.511cqw;
  --rx-stats-tx: 0.18605;    --rx-stats-ty: 0.98254;
  --rx-stats-amp: 1.6cqw;
}

.rx-stats__field--n3 .rx-stats__item--3 {
  left: 54.282%; top: 65.118%;
  --rx-stats-org: -126.115cqw -7.955cqw;
  --rx-stats-tx: -0.06295;   --rx-stats-ty: 0.99802;
  --rx-stats-amp: 2.2cqw;
}

.rx-stats__field--n5 .rx-stats__item--1 {
  left: 6.924%;  top: 53.670%;
  --rx-stats-org: -126.363cqw -0.800cqw;
  --rx-stats-tx: -0.00633;   --rx-stats-ty: 0.99998;
  --rx-stats-amp: 0.9cqw;
}

.rx-stats__field--n5 .rx-stats__item--2 {
  left: 19.002%; top: 14.773%;
  --rx-stats-org: -124.159cqw 23.511cqw;
  --rx-stats-tx: 0.18605;    --rx-stats-ty: 0.98254;
  --rx-stats-amp: 1.6cqw;
}

.rx-stats__field--n5 .rx-stats__item--3 {
  left: 35.414%; top: 59.394%;
  --rx-stats-org: -126.290cqw -4.377cqw;
  --rx-stats-tx: -0.03464;   --rx-stats-ty: 0.99940;
  --rx-stats-amp: 2.2cqw;
}

.rx-stats__field--n5 .rx-stats__item--4 {
  left: 49.123%; top: 31.923%;
  --rx-stats-org: -125.716cqw 12.792cqw;
  --rx-stats-tx: 0.10123;    --rx-stats-ty: 0.99486;
  --rx-stats-amp: 1.3cqw;
}

.rx-stats__field--n5 .rx-stats__item--5 {
  left: 63.804%; top: 65.118%;
  --rx-stats-org: -126.115cqw -7.955cqw;
  --rx-stats-tx: -0.06295;   --rx-stats-ty: 0.99802;
  --rx-stats-amp: 1.9cqw;
}

/* The marker, in two parts, because it has two states.

     ::after   the CORE — a small ring, Figma's r 6.29488 stroked 2. This is
               the dormant state, and all there is while the dot is in
               flight. The arc runs straight through it.
     ::before  the HALO — the activated state. Grows out of the core the way
               the button's halo grows out of its pill, and brings the page
               ground with it as a fill, so a live dot sits ON its line where
               a dormant one is still just a bead threaded onto it.

   ::before is declared first so it paints under the core.

   The element itself is a ZERO-SIZED anchor pinned at the dot's centre, with
   both rings centred on it. That is what lets the halo change size without
   moving anything: with a box, growing the ring would have to grow the box,
   and the centre would drift by half the difference. It also makes the
   rotation origin below a straight vector from the centre of the dot to the
   centre of its circle, with no half-widths folded in. */
.rx-stats__dot {
  position: absolute;
  /* 15.2529 of Figma's group origin, in field units. */
  left: 1.6725cqw;
  top: 1.6725cqw;
  width: 0;
  height: 0;
  /* The circle's centre, ~126cqw away off the left of the field. The dot
     rotates about it, so its entrance IS the arc. */
  transform-origin: var(--rx-stats-org);
}

.rx-stats__dot::before,
.rx-stats__dot::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  box-sizing: border-box;
  border-radius: 50%;
  /* The box is zero-sized, so its origin IS the dot centre; this pulls each
     ring back by half its own size to sit on it. `translate` rather than a
     transform, so the halo's growth can use `scale` without disturbing it. */
  translate: -50% -50%;
}

.rx-stats__dot::after {
  width: var(--rx-stats-dot-core);
  height: var(--rx-stats-dot-core);
  border: var(--rx-stats-dot-ring) solid var(--rx-brown-base);
}

/* Sized at its FINAL diameter — core plus the spread on each side — and grown
   into place with `scale`, from exactly the core's own size to full. That is
   the halo motion: the ring starts flush around the core and pushes outward.
   Scaling from zero instead would read as a dot popping, not a ring opening. */
.rx-stats__dot::before {
  width: calc(var(--rx-stats-dot-core) + var(--rx-stats-dot-spread) * 2);
  height: calc(var(--rx-stats-dot-core) + var(--rx-stats-dot-spread) * 2);
  border: var(--rx-stats-dot-ring) solid var(--rx-stats-dot-halo);
  background: var(--rx-gold-t5);
}

.rx-stats__value {
  margin: 0;
  color: var(--rx-heading);
  font-size: var(--rx-text-4xl);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.rx-stats__label {
  margin: var(--rx-space-2) 0 0;
  max-width: 18ch;
  font-size: calc(var(--rx-text-sm) * var(--rx-stats-cap, 1));
  font-weight: 500;
  line-height: 1.22;
  color: var(--rx-brown-base);
}

/* In the field the type is absolutely placed, not stacked: Figma's boxes are
   cap-trimmed, so each top is the cap line. The offsets below convert that to
   a normal text box — for Inter at line-height L the cap sits
   (L - 1.2104)/2 + 0.2418 em below the box top: 12.19px at 69/1.08, 4.44px at
   18/1.22. */
.rx-stats__field--orbit .rx-stats__value,
.rx-stats__field--orbit .rx-stats__label {
  position: absolute;
  left: 5.3457cqw;   /* 48.753 */
  margin: 0;
}

.rx-stats__field--orbit .rx-stats__value {
  top: 0.3355cqw;
  font-size: 7.5658cqw;   /* 69 */
  white-space: nowrap;
}

.rx-stats__field--orbit .rx-stats__label {
  top: 8.3125cqw;
  width: 19.846cqw;      /* 181 */
  max-width: none;
  font-size: calc(1.9737cqw * var(--rx-stats-cap, 1));  /* 18 */
}

/* --- Entrance --------------------------------------------------------------
   The section rides the global reveal driver for its TRIGGER only: it takes
   [data-rx-reveal] so the driver stamps [data-rx-revealed] when the block
   comes into view, and then opts out of the driver's own fade-and-scale,
   which would otherwise move the whole field as one lump and fight the
   transforms below.

   Everything is scoped to [data-rx-reveal-ready], the flag main.js only sets
   once it is actually running. No JS, or reduced motion, and the arcs are
   drawn, the dots are parked and the figures are up — the finished state is
   the default. ------------------------------------------------------------ */

[data-rx-reveal-ready] .rx-stats__inner[data-rx-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
  will-change: auto;
}

/* Each element's own place in the sequence. --rx-stats-i is set per item and
   per arc in the markup. */
.rx-stats__arc,
.rx-stats__item {
  --rx-stats-delay: calc((var(--rx-stats-i, 1) - 1) * var(--rx-stats-step));
}

/* --- Hidden state ---------------------------------------------------------
   What every animated part looks like before its turn comes. Kept as its own
   block rather than folded into the keyframes because `animation-fill-mode:
   backwards` only covers an element that HAS an animation — and below 1025
   they are all switched off, where these have to hold anyway. */

[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__arc {
  stroke-dashoffset: var(--rx-stats-arc-len);
}

[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__dot {
  transform: rotate(var(--rx-stats-sweep));
}

[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__dot::before {
  opacity: 0;
  scale: calc(var(--rx-stats-dot-core) / (var(--rx-stats-dot-core) + var(--rx-stats-dot-spread) * 2));
}

[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__dot::after,
[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__intro {
  opacity: 0;
}

[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__value,
[data-rx-reveal-ready] .rx-stats__inner:not([data-rx-revealed]) .rx-stats__label {
  opacity: 0;
  transform: translateX(calc(var(--rx-stats-text-from) * -1));
}

/* --- Entrance ------------------------------------------------------------- */

[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__arc {
  animation: rx-stats-draw var(--rx-stats-draw) var(--rx-stats-ease)
             var(--rx-stats-delay) both;
}

/* The dot rides in on its own circle — a rotation about a centre ~126cqw off
   to the left. Nothing else rotates, so nothing else has to be held level. */
[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__dot {
  animation: rx-stats-ride var(--rx-stats-ride-dur) var(--rx-stats-ease)
             calc(var(--rx-stats-delay) + var(--rx-stats-ride-lag)) both;
}

[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__dot::after {
  animation: rx-stats-fade var(--rx-stats-text-dur) var(--rx-stats-ease)
             calc(var(--rx-stats-delay) + var(--rx-stats-ride-lag)) both;
}

/* Activation. Held until the ride is over, so the ring opens on a dot that
   has already stopped — the two never overlap, which is what makes the
   arrival read as an event rather than as part of the travel. */
[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__dot::before {
  animation: rx-stats-open var(--rx-stats-open-dur) var(--rx-stats-ease-open)
             calc(var(--rx-stats-delay) + var(--rx-stats-open-at)) both;
}

/* The figure is issued BY the marker: it fades up while sliding out from the
   dot to its resting place. The label follows a beat later and travels a
   shorter distance, so the two do not move as one slab. */
[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__value {
  animation: rx-stats-emerge var(--rx-stats-text-dur) var(--rx-stats-ease)
             calc(var(--rx-stats-delay) + var(--rx-stats-text-lag)) both;
}

/* Shorter travel than the figure — the two arrive from the same place but
   not in lockstep. */
.rx-stats__label { --rx-stats-text-from: 1.4cqw; }

[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__label {
  animation: rx-stats-emerge var(--rx-stats-text-dur) var(--rx-stats-ease)
             calc(var(--rx-stats-delay) + var(--rx-stats-text-lag) + 90ms) both;
}

[data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__intro {
  animation: rx-stats-rise var(--rx-stats-text-dur) var(--rx-stats-ease) both;
}

@keyframes rx-stats-draw {
  /* --rx-stats-arc-len, NOT 1000: vector-effect: non-scaling-stroke puts the
     dash in screen pixels, so pathLength does not apply. See the field. */
  from { stroke-dasharray: var(--rx-stats-arc-len); stroke-dashoffset: var(--rx-stats-arc-len); }
  to   { stroke-dasharray: var(--rx-stats-arc-len); stroke-dashoffset: 0; }
}

@keyframes rx-stats-ride {
  from { transform: rotate(var(--rx-stats-sweep)); }
  to   { transform: rotate(0deg); }
}

@keyframes rx-stats-open {
  /* Out of the core, not out of nothing: the start scale is the ratio of the
     core to the halo's full diameter, so the ring begins life flush around
     the dormant dot. */
  from { opacity: 0; scale: calc(var(--rx-stats-dot-core) / (var(--rx-stats-dot-core) + var(--rx-stats-dot-spread) * 2)); }
  to   { opacity: 1; scale: 1; }
}

@keyframes rx-stats-emerge {
  from { opacity: 0; transform: translateX(calc(var(--rx-stats-text-from) * -1)); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes rx-stats-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes rx-stats-rise {
  from { opacity: 0; transform: translateY(0.6em); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --- Scroll drift ----------------------------------------------------------
   Once it has arrived, each metric keeps creeping along its own arc as the
   page moves — the dot and its figure together, since this sits on the item
   that holds both. --rx-stats-tx/ty is the unit tangent at rest, so the
   direction is the one the arc actually runs in; over this few pixels the
   tangent and the curve are the same line.

   All four go the SAME WAY and at four different rates. Rest is the midpoint
   of the travel, so the block is at its designed position when it is centred
   in the viewport and leans a little either side of that.

   This animates `translate`, NOT `transform` — the dot's entrance owns
   `transform` and the halo's growth owns `scale`. Three separate properties,
   composed by the browser, so one element can run animations on two different
   timelines and a pseudo-element can run a third without any of them
   clobbering another.

   No JS: a view() timeline is scroll position by definition, so it tracks the
   page and reverses with it. Where the timeline is unsupported the metrics
   simply sit still, which is the same thing the reduced-motion branch does. */

@supports (animation-timeline: view()) {
  .rx-stats__field--orbit .rx-stats__item {
    animation: rx-stats-drift linear both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
}

@keyframes rx-stats-drift {
  from {
    translate: calc(var(--rx-stats-tx) * var(--rx-stats-amp) * -1)
               calc(var(--rx-stats-ty) * var(--rx-stats-amp) * -1);
  }
  to {
    translate: calc(var(--rx-stats-tx) * var(--rx-stats-amp))
               calc(var(--rx-stats-ty) * var(--rx-stats-amp));
  }
}

/* --- Responsive ------------------------------------------------------------
   The arcs are a 1512-wide composition and there is no design below it. Under
   1025 the field gives up its coordinate space and the metrics go back to a
   plain staggered grid; the arcs and the dots go with it. */

@media (max-width: 1024px) {
  /* NO VIEWPORT STAGE DOWN HERE. At 1512 the section is 100svh with its content
     centred, because the design is arcs sweeping an empty stage with the
     metrics in the middle of it. The phone frame draws the opposite: the
     composition fills the frame and there is no empty stage to hold.

     Keeping both was what put the metrics through the section below. `center`
     overflows EQUALLY at both ends once the content is taller than the box, so
     the last metric spilled straight past the section's own bottom padding —
     measured 96px into the logo bar. Centring cannot be relied on to respect
     padding; only start alignment can. */
  .rx-stats {
    min-block-size: auto;
    align-content: start;

    /* THE SECTION HAS TO CARRY ITS OWN CLEARANCE HERE, and it overrides the
       page's spacing choice to do it.

       home.html gives this module space_top: "none" / space_bottom: "none",
       and the block after it space_top: "overlap" — a deliberate -96px margin
       (`.rx-section--pt-overlap` in _utilities.css) that pulls the story block
       up over the stats. Both are correct at 1512, where the section is a
       100svh stage with its content centred and hundreds of pixels of empty
       arc-sweep at each end for the overlap to eat into.

       The phone frame has no stage — the composition fills the frame — so
       "none" plus a -96 overlap put the story block straight through the last
       metric. Measured before this: the next section's top edge landed 92px
       ABOVE the fourth caption's floor.

       BOTH ENDS ARE OVERLAPPED, which is easy to miss because the two use
       different modifiers: feature-card above carries space_bottom "overlap"
       (`.rx-section--pb-overlap`, margin-bottom -96) and story-feature below
       carries space_top "overlap" (margin-top -96). Measured before this: the
       intro started 48px above the previous section's floor and the next
       section's top edge landed 92px above the fourth caption's.

       So below 1025 the choice variables are overridden rather than the base
       ones — `--rx-section-pt` alone would still lose to the utility class's
       `--rx-section-pt-choice: 0` — and each end carries one --rx-space-9
       extra, which is exactly what its overlap takes back. */
    --rx-section-pt-choice: calc(var(--rx-section-y) + var(--rx-space-9));
    --rx-section-pb-choice: calc(var(--rx-section-y) + var(--rx-space-9));
  }

  .rx-stats__inner {
    grid-template-columns: minmax(0, 1fr);
    /* The field bleeds to the section edge at desktop, so .rx-stats__inner is
       deliberately not a container there. Once the arcs are gone there is
       nothing to bleed, and the block needs the gutter back. */
    width: min(100% - var(--rx-gutter) * 2, var(--rx-container-max));
    margin-inline: auto;
  }

  .rx-stats__intro {
    padding-left: 0;
    padding-top: 0;
    padding-right: 0;
    margin-bottom: var(--rx-space-8);
  }

  .rx-stats__heading { max-width: none; }
  .rx-stats__lead,
  .rx-stats__copy { max-width: 42ch; }

  /* The drift is the only thing still on the item down here, and it is not
     gated on the reveal flag, so this has to kill it unconditionally rather
     than only in the revealed state. Same specificity as the @supports rule
     that sets it and later in the file. */
  .rx-stats__field--orbit .rx-stats__item {
    animation: none;
    translate: none;
  }

  /* The type is ISSUED BY THE DOT here, exactly as it is at 1512 — so the two
     overrides that used to sit here are gone. They pinned the figure and
     caption in place and swapped their entrance for a plain fade, on the
     reasoning that they could not slide out of a dot that was not drawn. The
     dot IS drawn now, so the desktop rules stand and the type travels out of
     it. --rx-stats-text-from is restated in the field's own unit above, since
     the desktop's 2.2cqw is a share of a 912-wide field, not a 402 one. */

  /* THE STACKED LAYOUT IS DRAWN, from the phone frame 3157:8086. Everything
     below is transcribed from it rather than derived from the desktop
     composition, which is what the first pass did.

     The shape: four arcs sweeping across, one per metric, each metric hung
     BELOW a dot that sits on its own arc — figure and caption centred under
     the dot, not beside it. The four are staggered horizontally, so the eye
     runs down a zigzag rather than a column.

     The field IS the drawn 402-wide frame. Its aspect is the frame's own
     (402 / 677.663, the metric band from dot 1's top to caption 4's floor),
     which is also the arcs' viewBox — so the mapping is 1:1 and every
     percentage below is a drawn coordinate divided by the frame.

     --rx-stats-mu is one design pixel: 1cqw is 1% of the field, and the field
     is the 402 frame, so 100/402 = 0.24876cqw. Capped at 462 so the 69px
     figure does not grow to 130 on a large phone — the drawing scales, and
     stops. */
  .rx-stats__field--orbit {
    /* FULL BLEED, because the drawn frame IS the viewport. The 22.35 of empty
       page each side of the composition is the frame's own margin and is
       already in the coordinates — leaving the field inside .rx-stats__inner's
       gutters charged for it twice and rendered the whole drawing at 87%, so
       the 69px figure came out at 60. Centred by offset rather than by a
       negative percentage margin, which resolves against the wrong box here. */
    position: relative;
    left: 50%;
    translate: -50% 0;
    inline-size: min(100vw, 462px);
    max-inline-size: none;
    min-inline-size: 0;
    /* THE BAND GROWS WITH THE COUNT: 178 design pixels of metric pitch each,
       plus the last one's own 143.663 of height. 677.663 is that at four,
       which is the drawn value. The svg's viewBox height tracks the same
       number (module.html) — the two must agree or the lower arcs map off the
       bottom of the field. Set per count below. */
    aspect-ratio: 402 / 677.663;
    /* The ratio is the DRAWN band and has to stay exact — it is the arcs'
       viewBox and every item percentage resolves against it. But Figma's
       caption box is cap-trimmed at 35 tall where our line box is 43.9, so the
       last caption sits ~7px below the band's floor. Given back as margin,
       which cannot disturb the coordinate space the way padding would:
       percentages on an absolutely positioned child resolve against the
       PADDING box. */
    margin-block-end: calc(12 * var(--rx-stats-mu));
    --rx-stats-mu: 0.24876cqw;
    /* Drawn 23.919 overall (3157:8147). Split across the existing two rings so
       the halo still opens out of the core: 15.9 + 4 either side = 23.9. */
    --rx-stats-dot-core: calc(15.9 * var(--rx-stats-mu));
    --rx-stats-dot-spread: calc(4 * var(--rx-stats-mu));
    --rx-stats-dot-ring: max(1px, calc(1.2 * var(--rx-stats-mu)));
    /* The ride is a rotation about a centre 1149 BELOW the dot, so at this
       radius a small angle is a long, almost flat travel.

       MATCHED TO THE DESKTOP'S PROPORTION, not picked by eye. At 1512 the dot
       swings 22 degrees about a 1151 radius = 442px across a 1512 frame, which
       is 29% of it. 29% of 402 is 117, so the angle here is 117 / 1149 rad =
       5.8 degrees. The first pass used -8, which is 161px — 40% of the frame,
       half again further than 1512 travels proportionally, and that extra
       distance is most of what read as "too fast": the same scroll had to
       carry a longer journey.

       The text is matched the same way. Its 20 is close to the desktop's
       2.2cqw (20px at a 912 field) in ABSOLUTE terms, which is right here
       because the figure is 69px at both sizes — the type does not shrink on
       the phone, so the distance it slides should not either. */
    --rx-stats-sweep: -5.8deg;
    --rx-stats-text-from: calc(20 * var(--rx-stats-mu));

    /* THE STEPS DO NOT OVERLAP HERE, and that is a change of ORDER from 1512.
       Desktop runs text-lag 430 against open-at 900, so the figure is already
       emerging while the dot is still travelling and the halo lands last —
       which works when four metrics are staggered 150ms apart and read as one
       sweep across the band. Stacked, each metric is alone on the screen and
       its own sequence is the whole event, so it plays in three separate
       beats: the dot arrives, the halo confirms it, then the type is issued.

       Measured before this: the figure was at 0.71 opacity by 667ms with the
       dot still 13px short of its resting place and the halo at zero.

         arc     0 -> 700
         ride    50 -> 490      (440ms over 123px)
         halo    490 -> 790     on a dot that has just parked
         figure  580 -> 960
         caption 650 -> 1030    (+70, so the two do not move as one slab)

       AND THE WHOLE THING RESOLVES IN ABOUT A SECOND. The first pass at these
       beats ran to 1660ms, which is longer than a reader takes to scroll a
       metric past: the sequence was still playing when it left the screen, so
       on a normal scroll you never saw it finish. Every duration here is about
       60% of that pass — enough to keep three separate beats, short enough to
       land while the metric is still on screen.

       The ride is shorter than the desktop's 1000ms for a second reason: the
       travel is shorter, 123px against 442. At 1000ms it crawls — 0.12px/ms
       against the desktop's 0.44. At 440 it is 0.28. */
    --rx-stats-draw: 700ms;
    --rx-stats-ride-lag: 50ms;
    --rx-stats-ride-dur: 440ms;
    --rx-stats-open-at: 490ms;
    --rx-stats-open-dur: 300ms;
    --rx-stats-text-lag: 580ms;
    --rx-stats-text-dur: 380ms;
  }

  .rx-stats__field--orbit .rx-stats__list {
    position: absolute;
    inset: 0;
    display: block;
  }

  .rx-stats__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--rx-space-8) var(--rx-space-7);
  }

  /* Each metric is a 181-wide block centred on its own axis, with the dot
     pinned above it. `left` is the drawn text centre as a share of the frame;
     the negative start margin takes it back by half the block, so the block is
     centred on that axis without a `translate` — which the scroll drift owns. */
  .rx-stats__field--orbit .rx-stats__item {
    position: absolute;
    inline-size: calc(181 * var(--rx-stats-mu));
    margin-inline-start: calc(-90.5 * var(--rx-stats-mu));
    text-align: center;
    /* Figure's cap-top is 43.664 below the item's top; a 69/1.08 line box
       overshoots its cap by (1.08 - 1.21) / 2 + .242 = .177em = 12.21. */
    padding-block-start: calc(31.454 * var(--rx-stats-mu));
  }

  .rx-stats__field--n4 .rx-stats__item--1 { left: 63.720%; top:  0.000%; --rx-stats-org: calc(-58.877 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n4 .rx-stats__item--2 { left: 28.071%; top: 26.267%; --rx-stats-org: calc( 84.431 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n4 .rx-stats__item--3 { left: 56.918%; top: 52.533%; --rx-stats-org: calc(-31.535 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n4 .rx-stats__item--4 { left: 38.488%; top: 78.800%; --rx-stats-org: calc( 42.554 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }

  /* --- The counts Figma does not draw, stacked ----------------------------
     Same rhythm rule as the desktop field, on the other axis: odd metrics ride
     RIGHT, even metrics ride LEFT, and each side converges toward the centre
     as the column descends. The endpoints are the drawn lefts (63.720/56.918
     right, 28.071/38.488 left), so nothing is extrapolated.

     x IS NOT DERIVED FROM THE CIRCLES HERE, and that is the difference from
     the desktop field. These arcs are near their apex, where the curve is flat
     — dx goes as sqrt(r^2 - dy^2) with dy within 3 of r, so a 1.8px change in
     radius swings x by 30. Figma's own dots sit at radius 1149.6 to 1152.3
     against a drawn 1152.454, which is invisible on a 24px ring but makes x
     numerically meaningless to solve for. The drawn lefts are composition, not
     geometry, so they are interpolated as composition.

     Feeding N=4 back through this reproduces the four rules above to 0.003%.

     `top` is an even 178 pitch over the band, which is why it changes with the
     count while the pitch does not. */

  .rx-stats__field--n2 { aspect-ratio: 402 / 321.663; }
  .rx-stats__field--n2 .rx-stats__item--1 { left: 63.720%; top:  0.000%; --rx-stats-org: calc(-58.881 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n2 .rx-stats__item--2 { left: 28.071%; top: 55.337%; --rx-stats-org: calc( 84.428 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }

  .rx-stats__field--n3 { aspect-ratio: 402 / 499.663; }
  .rx-stats__field--n3 .rx-stats__item--1 { left: 63.720%; top:  0.000%; --rx-stats-org: calc(-58.881 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n3 .rx-stats__item--2 { left: 28.071%; top: 35.624%; --rx-stats-org: calc( 84.428 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n3 .rx-stats__item--3 { left: 56.918%; top: 71.248%; --rx-stats-org: calc(-31.537 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }

  .rx-stats__field--n5 { aspect-ratio: 402 / 855.663; }
  .rx-stats__field--n5 .rx-stats__item--1 { left: 63.720%; top:  0.000%; --rx-stats-org: calc(-58.881 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n5 .rx-stats__item--2 { left: 28.071%; top: 20.803%; --rx-stats-org: calc( 84.428 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n5 .rx-stats__item--3 { left: 60.319%; top: 41.605%; --rx-stats-org: calc(-45.209 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n5 .rx-stats__item--4 { left: 38.488%; top: 62.408%; --rx-stats-org: calc( 42.551 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }
  .rx-stats__field--n5 .rx-stats__item--5 { left: 56.918%; top: 83.210%; --rx-stats-org: calc(-31.537 * var(--rx-stats-mu)) calc(1149.159 * var(--rx-stats-mu)); }

  /* The dot sits 11.96 down and 11.955 LEFT of the block's centre — it is not
     centred over the figure, and that offset is what puts it on the arc.
     Measured against the circles, the drawn dots land at radius 1149.6 to
     1152.3 against a drawn 1152.454: Figma's own nudge, under 3px on a 1152
     radius, and invisible at a 24px ring. Transcribed rather than re-derived,
     the same tolerance the frame insets already carry. */
  .rx-stats__field--orbit .rx-stats__dot {
    display: block;
    left: calc(50% - 11.955 * var(--rx-stats-mu));
    top: calc(11.96 * var(--rx-stats-mu));
    transform-origin: var(--rx-stats-org);
  }

  /* Figure 69/1.08 SemiBold (3157:8150), caption 18/1.22 Medium (3157:8146),
     both centred and both Dark Brown/Base. The caption's drawn gap under the
     figure comes out at -1.75 once both line boxes' leading is taken off the
     14.84 of ink, so it is zero. */
  .rx-stats__field--orbit .rx-stats__value,
  .rx-stats__field--orbit .rx-stats__label {
    position: static;
    left: auto;
    top: auto;
    width: auto;
    max-width: none;
    white-space: normal;
    text-align: center;
  }

  .rx-stats__field--orbit .rx-stats__value {
    font-size: calc(69 * var(--rx-stats-mu));
    line-height: 1.08;
    letter-spacing: -0.02em;
    white-space: nowrap;
  }

  .rx-stats__field--orbit .rx-stats__label {
    margin-top: 0;
    font-size: calc(18 * var(--rx-stats-mu) * var(--rx-stats-cap, 1));
    font-weight: 500;
    line-height: 1.22;
  }
}

/* The invented vertical scatter that used to live here is gone. It existed to
   stop a plain 2x2 grid reading as a table; the drawn layout staggers the four
   itself, on measured coordinates, so a second scatter on top would fight it.
   It only ever applied to the NON-orbit fallback anyway (any count but four),
   which is a plain grid and can keep it. */
@media (max-width: 1024px) {
  .rx-stats__field:not(.rx-stats__field--orbit) .rx-stats__item--2 { translate: 0 -2.5rem; }
  .rx-stats__field:not(.rx-stats__field--orbit) .rx-stats__item--3 { translate: 0 1.5rem; }
  .rx-stats__field:not(.rx-stats__field--orbit) .rx-stats__item--4 { translate: 0 -1rem; }
}

/* --- Each metric on its OWN trigger ---------------------------------------
   At 1512 the band is one event: the section arrives, the four arcs draw left
   to right --rx-stats-step apart and the figures follow. That works because
   the whole composition is on screen at once.

   Stacked it is not — the four run 678 design pixels down the page, so a
   single block-level trigger fires all four while only the first is in view,
   and the rest have finished before the reader reaches them.

   WHAT THIS IS NOT: the properties are not tied to scroll position. A first
   pass drove them from a view() timeline, which scrubs the whole sequence
   back and forth with the scrollbar and collapses the three steps into one
   continuous smear. Each metric plays the SAME time-based sequence the
   desktop plays; the only thing scroll decides is WHEN it starts.

   main.js stamps [data-rx-stat-in] on each item, and on its matching arc, as
   that item comes into view. The reveal driver cannot do this — it groups by
   closest('[data-rx-reveal-focus], [data-rx-reveal-group]'), so everything in
   a scope shares one trigger, and giving each item its own data-rx-reveal
   would make it its own scope at EVERY width and take the desktop
   choreography apart to fix the phone.

   THE STEPS, in order, and they must not overlap:
     1. the arc draws
     2. the dot rides in ALONG it              (ride-lag, 100ms)
     3. the halo activates on the parked dot   (open-at, 900ms)
     4. the figure emerges out of the dot      (text-lag, 430ms)
        and the caption follows 90ms later
   These are the desktop's own tokens and its own keyframes. The only thing
   dropped is --rx-stats-delay, the left-to-right stagger that separates the
   four at 1512: each metric is its own event here, so it starts at zero. */
@media (max-width: 1024px) {
  /* OFF: the block-level entrance fires all four the moment the section
     arrives, which is the behaviour being replaced. Same specificity as the
     per-item rules below and earlier in the file, so those win. */
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__arcs--stacked .rx-stats__arc,
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__item .rx-stats__dot,
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__item .rx-stats__dot::before,
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__item .rx-stats__dot::after,
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__item .rx-stats__value,
  [data-rx-reveal-ready] .rx-stats__inner[data-rx-revealed] .rx-stats__item .rx-stats__label {
    animation: none;
  }

  /* Hidden until its OWN turn, not until the block's. */
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__arcs--stacked .rx-stats__arc:not([data-rx-stat-in]) {
    stroke-dashoffset: var(--rx-stats-arc-len);
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item:not([data-rx-stat-in]) .rx-stats__dot {
    transform: rotate(var(--rx-stats-sweep));
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item:not([data-rx-stat-in]) .rx-stats__dot::before {
    opacity: 0;
    scale: calc(var(--rx-stats-dot-core) / (var(--rx-stats-dot-core) + var(--rx-stats-dot-spread) * 2));
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item:not([data-rx-stat-in]) .rx-stats__dot::after {
    opacity: 0;
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item:not([data-rx-stat-in]) .rx-stats__value,
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item:not([data-rx-stat-in]) .rx-stats__label {
    opacity: 0;
    transform: translateX(calc(var(--rx-stats-text-from) * -1));
  }

  /* 1. the line */
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__arcs--stacked .rx-stats__arc[data-rx-stat-in] {
    animation: rx-stats-draw var(--rx-stats-draw) var(--rx-stats-ease) 0ms both;
  }

  /* 2. the dot rides along it */
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item[data-rx-stat-in] .rx-stats__dot {
    animation: rx-stats-ride var(--rx-stats-ride-dur) var(--rx-stats-ease)
               var(--rx-stats-ride-lag) both;
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item[data-rx-stat-in] .rx-stats__dot::after {
    animation: rx-stats-fade var(--rx-stats-text-dur) var(--rx-stats-ease)
               var(--rx-stats-ride-lag) both;
  }

  /* 3. the halo activates, on a dot that has already stopped */
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item[data-rx-stat-in] .rx-stats__dot::before {
    animation: rx-stats-open var(--rx-stats-open-dur) var(--rx-stats-ease-open)
               var(--rx-stats-open-at) both;
  }

  /* 4. the type is issued by the dot */
  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item[data-rx-stat-in] .rx-stats__value {
    animation: rx-stats-emerge var(--rx-stats-text-dur) var(--rx-stats-ease)
               var(--rx-stats-text-lag) both;
  }

  [data-rx-reveal-ready] .rx-stats__field--orbit .rx-stats__item[data-rx-stat-in] .rx-stats__label {
    animation: rx-stats-emerge var(--rx-stats-text-dur) var(--rx-stats-ease)
               calc(var(--rx-stats-text-lag) + 70ms) both;
  }
}

/* The phone frame IS the stacked layout — see the drawn block above. Nothing
   extra is needed below 768: the composition scales with the field, which is
   capped, and the only thing that changes is how much margin sits beside it. */

@media (prefers-reduced-motion: reduce) {
  /* The driver stamps [data-rx-revealed] immediately here and never sets the
     ready flag, so nothing above applies — this is the belt to that brace. */
  .rx-stats__arc,
  .rx-stats__item,
  .rx-stats__dot,
  .rx-stats__value,
  .rx-stats__label,
  .rx-stats__intro {
    animation: none !important;
    opacity: 1;
    transform: none;
    translate: none;
    scale: none;
    stroke-dashoffset: 0;
  }

  /* The rings are held on their centre BY a translate, so they cannot join
     the blanket reset above — clearing it would drop both of them down and
     right by half their own size. Only the halo's growth is undone. */
  .rx-stats__dot::before,
  .rx-stats__dot::after {
    animation: none !important;
    opacity: 1;
    scale: none;
  }
}

/* --- Stacked: its own drawing, not the desktop one turned --------------------
   The desktop arcs are five near-vertical sweeps registered to a two-column
   composition. The phone frame (3157:8086) draws four near-horizontal ones
   registered to a staggered single column, so this is a SECOND path set in the
   markup rather than the first one transformed.

   It was `rotate: 90deg` on a stretched copy of the desktop svg for a while,
   and that is what forced the dots to be dropped: preserveAspectRatio="none"
   maps the viewBox linearly onto the box, which is the only reason a dot
   placed by percentage sits ON its arc, and rotating the box destroys that
   mapping.

   Here the field carries the viewBox's own ratio, so the map is 1:1 with no
   stretch at all and the arcs keep their true curvature. overflow stays
   visible: the paths run x -40 to 442 so they bleed past both edges. */
@media (max-width: 1024px) {
  .rx-stats__arcs--flat { display: none; }

  .rx-stats__field--orbit .rx-stats__arcs--stacked {
    display: block;
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    overflow: visible;
    pointer-events: none;
  }
}
