.hero-field,
.hero-knight {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* The field stays invisible until its first WebGL frame is ready, then fades
   in over flat ink, which is also what it leaves behind if WebGL is missing. */
.hero-field { opacity: 0; transition: opacity 1.4s ease; }
.hero.field-on .hero-field { opacity: 1; }
html.reduced .hero-field,
html.static-render .hero-field { transition: none; }

/* The interlude between D4 and E5. Without JS, under ?static=1, with reduced
   motion or with MOTION.morph off (.is-static) it is the finished SVG figure.
   morph.js adds .is-live and draws the same figure in particles on the canvas,
   landing exactly where the hidden SVG sits. On desktop the block is one
   viewport tall and its stage sticks at the centre for 40svh of the scroll,
   so the figure holds still while it changes. No overflow on this block or
   its ancestors: that would break the sticky stage. */
.morph {
  --morph-h: 80svh;
  --stage-h: var(--morph-h);
  position: relative;
  height: var(--morph-h);
}
@media (min-width: 768px) {
  html.js:not(.static-render):not(.reduced) .morph:not(.is-static) { --morph-h: 100svh; --stage-h: 60svh; }
  html.js:not(.static-render):not(.reduced) .morph:not(.is-static) .morph-stage {
    position: sticky;
    top: calc(50svh - var(--stage-h) / 2);
  }
}
.morph-stage { position: relative; height: var(--stage-h); overflow: hidden; }
/* Centred on the figure and only as wide as its flights need: fewer pixels per frame. */
.morph-canvas {
  display: none;
  position: absolute;
  inset: 0;
  width: min(100%, calc(var(--stage-h) * 1.25));
  height: 100%;
  margin-inline: auto;
}
.morph.is-live .morph-canvas { display: block; }
.morph-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  height: 100%;
  padding-inline: var(--margin);
}
/* 12/11: the viewBox adds half a unit around the 11-unit figure */
.morph-figure {
  display: block;
  flex: none;
  width: auto;
  height: min(54svh, calc(var(--stage-h) - 56px), calc((100vw - 2 * var(--margin)) * 12 / 11));
  aspect-ratio: 11 / 12;
  overflow: visible;
}
.morph-grid { stroke: var(--gold); }
.morph-outline { stroke: var(--cream); }
.morph-caption {
  color: var(--cream);
  font: 400 14px/1.4 var(--mono);
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums lining-nums;
}
.morph.is-live .morph-figure,
.morph.is-live:not(.is-done) .morph-caption { visibility: hidden; }

@media (max-width: 767px) {
  .morph { --morph-h: min(80svh, 140vw); }
  .morph-caption { font-size: 13px; }
}

/* styles.css kills animation under reduced motion but not in static mode;
   screenshot captures must be byte-stable, so static gets the same. */
.static-render * { animation: none !important; }
