/* ─── 01 · the read-out ──────────────────────────────────────────────────
   A sticky stage the page scrubs through: the schematic draws itself, the
   measured figures arrive, the judging criteria stack up, and the whole thing
   resolves into the struck card the rail then carries away.

   Every stage is driven by a custom property set from js/blueprint.js
   (--draw, --dim, --spec, --val, --verdict, --card), each 0→1. Nothing here
   animates on its own clock — the scroll is the timeline.
   ───────────────────────────────────────────────────────────────────────── */

.bp {
  position: relative;
  height: 460vh;
}

.bp__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  padding-inline: var(--gut);
}

/* The atmosphere is free to be loud elsewhere, but a technical drawing only
   works on a quiet ground — this holds the centre band down so the linework
   always reads, whatever the field is doing behind it. */
.bp__stage::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(
    62% 46% at 50% 48%,
    rgba(0, 0, 0, 0.82),
    rgba(0, 0, 0, 0.45) 55%,
    transparent 78%
  );
}

.bp__stage > * {
  position: relative;
  z-index: 1;
}

.bp__head {
  position: absolute;
  left: var(--gut);
  right: var(--gut);
  top: 0;
}

/* ── the plate ───────────────────────────────────────────────────────────── */
.bp__plate {
  position: absolute;
  left: 50%;
  top: 50%;
  /* has to clear both side panels — they sit in the gutters */
  width: min(760px, 48vw);
  transform: translate(-50%, -54%) scale(calc(1 - var(--card, 0) * 0.14));
  opacity: calc(1 - var(--card, 0));
  color: var(--bone);
}

/* ── the artwork ──
   Supplied as filled black line art, so it is recoloured with invert() and
   revealed with a clip wipe rather than a stroke-dash trace — you cannot
   "draw on" a filled shape, only uncover it. */
.bp__art {
  position: relative;
  clip-path: inset(0 calc((1 - var(--draw, 0)) * 100%) 0 0);
}

.bp__art img {
  display: block;
  width: 100%;
  height: auto;
  filter: invert(1)
    drop-shadow(0 0 10px rgba(59, 232, 196, 0.35))
    drop-shadow(0 0 34px rgba(59, 232, 196, 0.14));
}

/* the plotter head: a bright edge riding the front of the wipe. It is a
   sibling of .bp__art, not a child — the clip that reveals the artwork would
   otherwise cut the marker in half. */
.bp__scan {
  position: absolute;
  top: -3%;
  /* the artwork is 934 of the plate's 1234 units tall */
  height: 82%;
  left: calc(var(--draw, 0) * 100%);
  width: 2px;
  background: linear-gradient(
    180deg,
    transparent,
    var(--mint) 22%,
    #ffffff 50%,
    var(--mint) 78%,
    transparent
  );
  box-shadow: 0 0 18px 2px rgba(59, 232, 196, 0.7);
  /* only while it is actually travelling */
  opacity: calc(min(var(--draw, 0), 1 - var(--draw, 0)) * 6);
  pointer-events: none;
}

.bp__dims {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  margin-top: -1%;
}

/* the construction grid is the faintest thing on screen — it is scaffolding,
   not content */
.bp-grid {
  color: var(--bone);
  opacity: calc(var(--dim, 0) * 0.16);
  /* dashes are in user units, and this viewBox is 3459 wide */
  stroke-dasharray: 14 40;
}

.bp-line {
  /* dasharray + dashoffset are written per-line from JS, using each path's
     measured length. Stroke widths are user units so they scale with the
     viewBox — non-scaling-stroke would render them at device size and go
     five times too heavy here. */
  filter: drop-shadow(0 0 6px rgba(59, 232, 196, 0.35));
}

.bp-dim {
  color: var(--mint);
  opacity: var(--dim, 0);
}

.bp-wheel {
  color: var(--bone);
}

.bp__dimlabel {
  position: absolute;
  color: var(--mint);
  opacity: var(--dim, 0);
  transition: opacity 0.2s linear;
  white-space: nowrap;
}

/* positioned against the plate, which is artwork + dimension strip */
.bp__dimlabel--wb  { left: 52.7%; top: 77%; transform: translateX(-50%); }
.bp__dimlabel--len { left: 50%;   top: 88%; transform: translateX(-50%); }

/* ── the side panels ─────────────────────────────────────────────────────── */
.bp__panel {
  position: absolute;
  top: 50%;
  width: clamp(190px, 20vw, 280px);
  transform: translateY(-50%);
}

.bp__panel--spec { left: var(--gut); }
.bp__panel--val  { right: var(--gut); }

.bp__panel-title {
  color: var(--dim);
  padding-bottom: 0.75rem;
  border-bottom: var(--rule) solid var(--hair-strong);
  margin-bottom: 0.35rem;
  opacity: clamp(0, calc(var(--spec, 0) * 8), 1);
}

.bp__panel--val .bp__panel-title {
  opacity: clamp(0, calc(var(--val, 0) * 8), 1);
}

.bp__row,
.bp__val li {
  /* each line arrives a beat after the one above it */
  --a: clamp(0, calc(var(--spec, 0) * 7 - var(--n)), 1);
  opacity: var(--a);
  transform: translateY(calc((1 - var(--a)) * 12px));
}

.bp__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: var(--rule) solid var(--hair);
}

.bp__row dt { color: var(--dim); }
.bp__row dd { color: var(--bone); text-align: right; }

.bp__note {
  margin-top: 1rem;
  opacity: clamp(0, calc(var(--spec, 0) * 4 - 2.2), 1);
}

/* ── the judging ladder ──────────────────────────────────────────────────── */
.bp__val {
  list-style: none;
  counter-reset: v;
}

.bp__val li {
  --a: clamp(0, calc(var(--val, 0) * 6 - var(--n)), 1);
  counter-increment: v;
  position: relative;
  padding: 0.65rem 0 0.65rem 1.9rem;
  border-bottom: var(--rule) solid var(--hair);
}

.bp__val li::before {
  content: counter(v, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.72rem;
  font-family: var(--mono);
  font-size: 0.56rem;
  letter-spacing: 0.16em;
  color: var(--mint);
}

.bp__val b {
  display: block;
  color: var(--bone);
  font-weight: 400;
  margin-bottom: 0.2rem;
}

.bp__val span {
  color: var(--dim);
  font-size: 0.8rem;
  line-height: 1.4;
}

/* ── the verdict ─────────────────────────────────────────────────────────── */
.bp__verdict {
  position: absolute;
  left: 50%;
  bottom: 6vh;
  transform: translateX(-50%);
  text-align: center;
  opacity: calc(var(--verdict, 0) * (1 - var(--card, 0)));
}

.bp__verdict > .mono {
  color: var(--dim);
}

.bp__figure {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3rem, 7vw, 6rem);
  line-height: 0.85;
  color: var(--bone);
  font-variant-numeric: tabular-nums;
  padding-top: 0.06em;
}

.bp__tier {
  color: var(--t-rare);
}

/* ── the object it becomes ───────────────────────────────────────────────── */
.bp__card {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(220px, 23vw, 310px);
  transform: translate(-50%, -50%) scale(calc(0.72 + var(--card, 0) * 0.28));
  opacity: var(--card, 0);
  pointer-events: none;
  perspective: 1400px;
}

/* --card strikes the blank; --flip turns it over. Two faces on one card, so
   the turn reads as the same object rather than a crossfade between two. The
   26deg at rest is the last of the strike, unwinding as the card lands. */
.bp__flip {
  position: relative;
  transform-style: preserve-3d;
  transform: rotateY(
    calc((1 - var(--card, 0)) * 26deg + var(--flip, 0) * 180deg)
  );
}

.bp__face {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.bp__face--back {
  position: absolute;
  inset: 0;
  transform: rotateY(180deg);
}

/* Safari keeps painting a hidden backface through an ancestor that has its own
   compositing (the tier frame's blend layers), so fade the outgoing face out
   across the half-turn as well as relying on backface-visibility. */
.bp__face--front { opacity: calc(1 - clamp(0, (var(--flip, 0) - 0.46) * 24, 1)); }
.bp__face--back  { opacity: clamp(0, (var(--flip, 0) - 0.5) * 24, 1); }

@media (prefers-reduced-motion: reduce) {
  .bp__flip { transform: none; }
  .bp__face--front { display: none; }
  .bp__face--back { position: relative; transform: none; opacity: 1; }
}

.bp__scrollcue {
  position: absolute;
  left: 50%;
  bottom: 2.5vh;
  transform: translateX(-50%);
  color: var(--dimmer);
  opacity: calc(1 - var(--draw, 0) * 3);
}

/* ── narrow screens: the panels sit under the plate ──────────────────────── */
@media (max-width: 980px) {
  .bp { height: 400vh; }

  .bp__plate {
    top: 30%;
    width: 92vw;
  }

  .bp__panel {
    top: auto;
    bottom: 12vh;
    width: 44vw;
    transform: none;
  }

  .bp__panel--spec { left: var(--gut); }
  .bp__panel--val  { right: var(--gut); }

  .bp__val span { display: none; }
  .bp__note { display: none; }
  .bp__verdict { bottom: 2vh; }
}

/* ── reduced motion: show the finished state, no scrub ───────────────────── */
@media (prefers-reduced-motion: reduce) {
  .bp { height: auto; }

  .bp__stage {
    position: static;
    height: auto;
    padding-block: 4rem;
    display: grid;
    gap: 2.5rem;
  }

  .bp__head,
  .bp__plate,
  .bp__panel,
  .bp__verdict,
  .bp__card {
    position: static;
    transform: none;
    opacity: 1;
    width: auto;
  }

  .bp__card { width: min(300px, 70vw); justify-self: center; }
  .bp__scrollcue { display: none; }
  .bp-line { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
  .bp-grid, .bp-dim, .bp__dimlabel { opacity: 0.5; }
  .bp__row, .bp__val li { --a: 1; }
}
