/*
Glass badge — the frosted plate with a dot, a number and a caption that sits over a
photograph.

It was written inside the hero and is a component now because the metrics block (block 04)
uses the same one at a different size. That is the whole reason it exists as a file: a
second badge that merely looked the same would drift the moment either was touched, the way
the buttons drifted before they were one component.

What belongs to the component and is not to be re-implemented in a block:

    the glass and its edge      the plate, the blur, the hairline ring
    the dot and its glow
    the type                    two lines, their sizes and tracking
    the float                   the constant drift over the photo
    the entrance                fade + settle out of an enlargement, once
    the counter                 the Counter component, wired up by badge.js — the badge
                                sets when it runs, not how

What belongs to the block: where the badge sits, which rung it takes, and — for a
collection of them — nothing else, because the ordinal that staggers them is stamped by the
component's own script.

    <div class="badge badge--lg my-block__badge badge--pending">
      <span class="badge__dot" aria-hidden="true"></span>
      <span class="badge__text">
        <span class="badge__value">41</span>
        <span class="badge__label">Biological age</span>
      </span>
    </div>

--- Sizes -------------------------------------------------------------------------------

A size modifier is required, and it names the rung the badge takes on a wide screen. Every
rung steps down one below --bp-sm, because a badge shrinks with the thing it sits on:

    .badge--md    the hero          md above --bp-sm, sm below
    .badge--lg    the metrics tag   lg above --bp-sm, md below

A rung is a full set of values and not a scale factor — see the note in tokens.css. The
mapping below is deliberately mechanical: every line is a token reference, so a number has
exactly one home and the media query moves the whole set at once rather than a chosen few.

--- Without the script ------------------------------------------------------------------

The final state is the default. Without JS the badge is at its own size, fully visible, and
the number in the markup is its real value; the script only hides what it then reveals and
counts the number up from zero. The pending state is behind `.js`, which is set on <html>
in <head> before the first paint.
*/

/*
Frosted glass. -webkit-backdrop-filter is mandatory: Safari before 18 only knows the
prefixed form, and without it the badge there becomes a flat translucent fill. What gets
blurred is the backdrop under the badge — that is what keeps the white text readable both
over light areas of the photo and over the overhang past its edge, where the page canvas is
what sits underneath.

The shape is a squircle and arrives one of two ways, depending on the rung; see the two
blocks further down. border-radius is declared here and is the fallback for both.

position: relative is the component's default so the hairline ring has something to be
absolute against. A block that pins the badge over a photo overrides it with absolute and
loses nothing.
*/
.badge {
  /*
  When this badge arrives: its own delay plus its place in a collection times the step.
  Gathered into one property because it is used by three animations and by the counter,
  and because with two animations already on the element an animation-delay list would
  silently decide by position which number belongs to which.
  */
  --badge-in-at: calc(var(--badge-in-delay) + var(--badge-index, 0) * var(--badge-in-step));

  /*
  The number is the Counter component (assets/js/counter.js), and this is where the badge
  tells it when to run: the same delay, the same step and the same ordinal the entrance
  uses, so the figure starts moving exactly as the plate arrives. Started on the number
  entering the viewport instead, half the count would run while the badge is still
  transparent and it would surface already half way up.

  Three properties rather than the one --badge-in-at above, because getComputedStyle does
  not evaluate a calc() in an unregistered custom property — it does resolve var(), which
  is what lets these point at the badge's own tokens.
  */
  --count-delay: var(--badge-in-delay);
  --count-step: var(--badge-in-step);
  --count-index: var(--badge-index, 0);

  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--badge-gap-x);
  width: var(--badge-w);
  height: var(--badge-h);
  padding: var(--badge-pad-y) var(--badge-pad-r) var(--badge-pad-y) var(--badge-pad-l);
  border-radius: var(--badge-radius);
  background: var(--badge-bg);
  color: var(--surface);
  -webkit-backdrop-filter: blur(var(--badge-blur));
  backdrop-filter: blur(var(--badge-blur));

  /*
  The float follows a closed path across two axes. The keyframes are closed (the last
  equals the first), so the animation runs infinite without alternate: alternate would
  replay the same path backwards and the movement would read as a pendulum.

  The translate property moves, not transform: transform is busy with the entrance (scale)
  at the same time, and two animations on one property would not compose.
  */
  animation: badge-float var(--badge-float-dur) ease-in-out var(--badge-float-delay)
    infinite;
}

@keyframes badge-float {
  0% {
    translate: 0 calc(var(--badge-float-y) * -1);
  }

  25% {
    translate: var(--badge-float-x) calc(var(--badge-float-y) * -0.3);
  }

  50% {
    translate: calc(var(--badge-float-x) * 0.35) var(--badge-float-y);
  }

  75% {
    translate: calc(var(--badge-float-x) * -1) calc(var(--badge-float-y) * 0.25);
  }

  100% {
    translate: 0 calc(var(--badge-float-y) * -1);
  }
}

/*
The entrance. The fade and the zoom are two animations, not one. Sharing a duration and an
easing is exactly what made the zoom-out invisible: --badge-in-ease is front-loaded, so the
scale was practically finished while the badge was still transparent, and nothing was left
to watch by the time it became opaque. Split apart, opacity is done inside
--badge-in-fade-dur and the scale goes on settling in plain sight for the rest of
--badge-in-dur.

They compose because they animate different properties — the same reason the float, which
owns translate, can run alongside both.

The delay is the instance's own plus the ordinal times the step. The ordinal is written by
badge.js onto every badge it finds in a group, so a collection staggers by a rule rather
than by a list of delays: a fifth card added by the client falls into the sequence with
nothing to edit. The hero has two badges placed by hand, sets the delay on each of them and
leaves the step at zero.
*/
@keyframes badge-fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes badge-zoom-in {
  from {
    scale: var(--badge-in-scale);
  }

  to {
    scale: 1;
  }
}

.js .badge--pending {
  opacity: 0;
  scale: var(--badge-in-scale);
}

.badge--in {
  animation:
    badge-float var(--badge-float-dur) ease-in-out var(--badge-float-delay) infinite,
    badge-fade-in var(--badge-in-fade-dur) var(--badge-in-ease) var(--badge-in-at) both,
    badge-zoom-in var(--badge-in-dur) var(--badge-in-zoom-ease) var(--badge-in-at) both;
}

.badge__dot {
  flex: 0 0 auto;
  width: var(--badge-dot);
  height: var(--badge-dot);
  border-radius: 50%;
  background: var(--surface);

  /* The glow ring from filter1_d: even thickness, no blur. */
  box-shadow: 0 0 0 var(--badge-dot-ring) var(--badge-dot-ring-color);
}

.badge__text {
  display: flex;
  flex-direction: column;
  align-items: var(--badge-align);
  min-width: 0;
}

.badge__value {
  font-size: var(--badge-fs-value);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--badge-ls-value);
  line-height: var(--badge-lh-value);
}

.badge__label {
  margin-top: var(--badge-gap-y);
  font-size: var(--badge-fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--badge-ls-label);
  line-height: var(--badge-lh-label);
  text-transform: uppercase;
}

/*
── Rung md: the hero's plate ─────────────────────────────────────────────────────────────

A fixed box, its text set against the right edge, clipped by a contour taken out of the
Figma export verbatim.

The hairline edge is drawn as a ring rather than as a border or an inset shadow: both of
those are built on a rounded rectangle, while the plate is a squircle that starts curving
17.6px before the corner. An edge on radius 10 would fall outside it and be cut away by
clip-path at all four corners — it would disappear exactly where the shape reads. So the
ring is one contour of two subpaths under the evenodd rule.

The pseudo-element is absolutely positioned and therefore does not become a flex item of
the badge; otherwise space-between would spread the dot and the text around it. And the
fill is only switched on where the contour actually parses: without the guard, a dropped
clip-path would flood the whole badge.
*/
.badge--md {
  --badge-w: var(--badge-md-w);
  --badge-h: var(--badge-md-h);
  --badge-radius: var(--badge-md-radius);
  --badge-clip: var(--badge-md-clip);
  --badge-ring-clip: var(--badge-md-ring-clip);
  --badge-pad-l: var(--badge-md-pad-l);
  --badge-pad-r: var(--badge-md-pad-r);
  --badge-pad-y: var(--badge-md-pad-y);
  --badge-dot: var(--badge-md-dot);
  --badge-dot-ring: var(--badge-md-dot-ring);
  --badge-fs-value: var(--badge-md-fs-value);
  --badge-ls-value: var(--badge-md-ls-value);
  --badge-lh-value: var(--badge-md-lh-value);
  --badge-fs-label: var(--badge-md-fs-label);
  --badge-ls-label: var(--badge-md-ls-label);
  --badge-lh-label: var(--badge-md-lh-label);
  --badge-gap-x: var(--badge-md-gap-x);
  --badge-gap-y: var(--badge-md-gap-y);
  --badge-align: var(--badge-md-align);

  clip-path: var(--badge-clip);
}

.badge--md::after {
  content: "";
  position: absolute;
  z-index: var(--z-raised);
  inset: 0;
  clip-path: var(--badge-ring-clip);
  pointer-events: none;
}

@supports (clip-path: path(evenodd, "M0 0Z")) {
  .badge--md::after {
    background: var(--badge-border-color);
  }
}

/*
── Rung lg: the metrics tag ──────────────────────────────────────────────────────────────

The plate hugs its label and reads from the left, because here the label is the long part
and it differs from card to card. That rules out the md rung's contour: path() is absolute
geometry and cannot follow a width the text decides.

So this corner is a border-radius with corner-shape, and the two numbers were fitted
against the md contour rather than guessed — superellipse(1.8) at the equivalent radius
reproduces it to within a third of a level per pixel, where a plain border-radius is six
times further out. Where corner-shape is unsupported the declaration is dropped and an
ordinary rounded rectangle at that radius remains, which is the same fallback the md rung
already has.

Because this corner *is* a border radius, the hairline edge can be an ordinary inset
shadow: it follows the superellipse and stays even through the corners, which was checked
on a stand rather than assumed.
*/
.badge--lg {
  --badge-w: var(--badge-lg-w);
  --badge-h: var(--badge-lg-h);
  --badge-radius: var(--badge-lg-radius);
  --badge-corner: var(--badge-lg-corner);
  --badge-pad-l: var(--badge-lg-pad-l);
  --badge-pad-r: var(--badge-lg-pad-r);
  --badge-pad-y: var(--badge-lg-pad-y);
  --badge-dot: var(--badge-lg-dot);
  --badge-dot-ring: var(--badge-lg-dot-ring);
  --badge-fs-value: var(--badge-lg-fs-value);
  --badge-ls-value: var(--badge-lg-ls-value);
  --badge-lh-value: var(--badge-lg-lh-value);
  --badge-fs-label: var(--badge-lg-fs-label);
  --badge-ls-label: var(--badge-lg-ls-label);
  --badge-lh-label: var(--badge-lg-lh-label);
  --badge-gap-x: var(--badge-lg-gap-x);
  --badge-gap-y: var(--badge-lg-gap-y);
  --badge-align: var(--badge-lg-align);

  corner-shape: var(--badge-corner);
  box-shadow: inset 0 0 0 var(--badge-border-w) var(--badge-border-color);
}

/*
Below --bp-sm every rung steps down one. md becomes the 73x34 plate; lg keeps its hugging
box and its own line heights but takes md's type and dot — the spec states that plainly,
the metrics badge on the mobile frame is the hero's desktop badge one for one.
--bp-sm
*/
@media (max-width: 700px) {
  .badge--md {
    --badge-w: var(--badge-sm-w);
    --badge-h: var(--badge-sm-h);
    --badge-radius: var(--badge-sm-radius);
    --badge-clip: var(--badge-sm-clip);
    --badge-ring-clip: var(--badge-sm-ring-clip);
    --badge-pad-l: var(--badge-sm-pad-l);
    --badge-pad-r: var(--badge-sm-pad-r);
    --badge-pad-y: var(--badge-sm-pad-y);
    --badge-dot: var(--badge-sm-dot);
    --badge-dot-ring: var(--badge-sm-dot-ring);
    --badge-fs-value: var(--badge-sm-fs-value);
    --badge-ls-value: var(--badge-sm-ls-value);
    --badge-lh-value: var(--badge-sm-lh-value);
    --badge-fs-label: var(--badge-sm-fs-label);
    --badge-ls-label: var(--badge-sm-ls-label);
    --badge-lh-label: var(--badge-sm-lh-label);
    --badge-gap-x: var(--badge-sm-gap-x);
    --badge-gap-y: var(--badge-sm-gap-y);
    --badge-align: var(--badge-sm-align);
    --badge-float-x: var(--badge-sm-float-x);
    --badge-float-y: var(--badge-sm-float-y);
  }

  .badge--lg {
    --badge-radius: var(--badge-lgs-radius);
    --badge-pad-l: var(--badge-lgs-pad-l);
    --badge-pad-r: var(--badge-lgs-pad-r);
    --badge-pad-y: var(--badge-lgs-pad-y);
    --badge-dot: var(--badge-md-dot);
    --badge-dot-ring: var(--badge-md-dot-ring);
    --badge-fs-value: var(--badge-md-fs-value);
    --badge-ls-value: var(--badge-md-ls-value);
    --badge-lh-value: var(--badge-lgs-lh-value);
    --badge-fs-label: var(--badge-md-fs-label);
    --badge-ls-label: var(--badge-md-ls-label);
    --badge-lh-label: var(--badge-lgs-lh-label);
    --badge-gap-x: var(--badge-lgs-gap-x);
    --badge-float-x: var(--badge-sm-float-x);
    --badge-float-y: var(--badge-sm-float-y);
  }
}

/*
── Rung lg held at its small step ────────────────────────────────────────────────────────

A modifier and not a rung of its own: it takes lg and pins it to what lg becomes below
--bp-sm, at every width. The service pages' portrait tiles need exactly that set — value at
20/2 on 26 of leading, label at 12/1.68 on 15.6, a 10px dot — and they need it on a computer,
where the media query above is not in force.

Written as a compound selector so that it beats the media query by specificity rather than by
its place in this file: the two say the same thing where they overlap, and if they ever stop
saying the same thing, this one is the one the modifier asked for.

The declarations repeat the media query's because CSS has no way to name a set of them and
apply it twice. Every line is a token reference, so the numbers still live in exactly one
place — the lg-sm rung in tokens.css — and this is the mechanical mapping the whole file is
written in.
*/
.badge--lg.badge--lg-sm {
  --badge-radius: var(--badge-lgs-radius);
  --badge-pad-l: var(--badge-lgs-pad-l);
  --badge-pad-r: var(--badge-lgs-pad-r);
  --badge-pad-y: var(--badge-lgs-pad-y);
  --badge-dot: var(--badge-md-dot);
  --badge-dot-ring: var(--badge-md-dot-ring);
  --badge-fs-value: var(--badge-md-fs-value);
  --badge-ls-value: var(--badge-md-ls-value);
  --badge-lh-value: var(--badge-lgs-lh-value);
  --badge-fs-label: var(--badge-md-fs-label);
  --badge-ls-label: var(--badge-md-ls-label);
  --badge-lh-label: var(--badge-lgs-lh-label);
  --badge-gap-x: var(--badge-lgs-gap-x);

  /*
  The float goes with the plate and not with the viewport: 11px of drift on a plate of this
  height is a jump, which is why the small step carries its own amplitude at all.
  */
  --badge-float-x: var(--badge-sm-float-x);
  --badge-float-y: var(--badge-sm-float-y);
}

/*
No motion means the final state immediately: the badge in place, at its own size, with its
number already at its value. The rules are spelled out rather than relying on the global
duration clamp in base.css — on a closed-loop animation, clamping the duration leaves the
badge at its last keyframe, that is offset by --badge-float-y.
*/
@media (prefers-reduced-motion: reduce) {
  .badge,
  .js .badge--pending,
  .badge--in {
    opacity: 1;
    translate: none;
    scale: none;
    transform: none;
    animation: none;
  }
}
