@font-face {
  font-family: "Avenir LT Pro";
  src: url("../fonts/avenir-lt-pro-book.otf") format("opentype");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

:root {
  --paper: #fbf5f3;
  --ink: #000;
  --blue: #156082;
  --rust: #b45a37;
  --blush: #dab0a7;
  --graphite: #595959;
  --hairline: rgba(89, 89, 89, 0.28);
  --content: 1080px;

  /* Derived from the six above rather than picked: the pressed blue, the white
     that cards sit on where paper would be too warm, and the one green — all
     three were literals scattered through the file. */
  --blue-deep: #0f4a64;
  --surface: #fff;
  --green: #0a7a37;

  /* The house curve: quick to leave, long to settle. */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Avenir LT Pro", Avenir, "Century Gothic", Arial, sans-serif;
  font-size: 18px;
  line-height: 1.48;
  text-rendering: optimizeLegibility;
}

button,
input {
  font: inherit;
}

img {
  display: block;
  height: auto;
  max-width: 100%;
}

p,
h1,
h2,
h3 {
  margin-top: 0;
}

h1,
h2,
h3 {
  font-weight: 400;
}

a {
  color: var(--blue);
}

.skip-link {
  position: fixed;
  z-index: 100;
  top: 8px;
  left: 8px;
  padding: 8px 12px;
  background: var(--ink);
  color: var(--paper);
  transform: translateY(-160%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* --- Buttons ------------------------------------------------------------ */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
  font-size: 16px;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.button:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.button-primary {
  background: var(--blue);
  color: var(--paper);
}

.button-primary:hover {
  background: var(--blue-deep);
}

.button-secondary {
  border-color: var(--hairline);
  background: transparent;
  color: var(--blue);
}

.button-secondary:hover {
  border-color: var(--blue);
}

.button-quiet {
  border-color: var(--hairline);
  background: transparent;
  color: var(--graphite);
  font-size: 14px;
  padding: 6px 14px;
}

.button-quiet:hover {
  border-color: var(--graphite);
  color: var(--ink);
}

/* --- Landing ------------------------------------------------------------ */

/* --- Locked: one screen, the brand against the way in ------------------- */

/* No scrolling and no brochure. Arriving is a single held frame: the render on
   the left at full height, the key on the right, and what this page does about
   security stated along the foot. */
.landing-page.is-locked {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

.landing-page.is-locked main {
  display: flex;
  flex: 1 1 auto;
}

.entry {
  display: grid;
  flex: 1 1 auto;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr);
  width: 100%;
}

.entry-plate {
  position: relative;
  overflow: hidden;
  margin: 0;
  border-right: 1px solid var(--hairline);
}

/* Cropped from the sides rather than the top: the wordmark sits in the middle
   of a 2:1 render, which is exactly what a tall column keeps. */
.entry-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* --- Unlocked: the hero as a band --------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
}

.hero-image {
  display: block;
  width: 100%;
  height: clamp(540px, 70vh, 780px);
  max-height: none;
  object-fit: cover;
  object-position: center 46%;
}

.hero-plate {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  border-top: 1px solid var(--hairline);
  background: linear-gradient(
    to bottom,
    rgba(251, 245, 243, 0.58),
    var(--paper)
  );
  backdrop-filter: blur(14px);
}

.hero-plate-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 40px;
  width: min(calc(100% - 40px), var(--content));
  margin: 0 auto;
  padding: clamp(18px, 2.2vw, 26px) 0;
}

.hero-copy .eyebrow {
  margin-bottom: 9px;
}

.hero-copy h1 {
  margin: 0;
  color: var(--rust);
  font-size: clamp(42px, 5vw, 64px);
  line-height: 0.98;
  letter-spacing: -0.035em;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 10px;
}

.hero-actions form {
  display: flex;
  margin: 0;
}

.landing-pitch-link {
  gap: 8px;
}

.landing-pitch-link .bi {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  fill: currentColor;
}

.landing-body {
  width: min(calc(100% - 40px), var(--content));
  margin: 0 auto;
  padding: 0 0 72px;
}

@media (max-width: 700px) {
  .hero-plate-inner {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
    padding: 18px 0;
  }

  .hero-copy h1 {
    font-size: clamp(38px, 12vw, 52px);
  }

  .hero-actions {
    justify-content: flex-start;
  }
}

/* --- The gate ----------------------------------------------------------- */

.eyebrow {
  margin-bottom: 10px;
  color: var(--rust);
  font-size: 13px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.gate h1,
.unlocked h1 {
  margin-bottom: 16px;
  font-size: clamp(34px, 3.6vw, 46px);
  line-height: 1.04;
  letter-spacing: -0.026em;
}

.gate-intro {
  max-width: 52ch;
  color: var(--graphite);
}

/* Held on the optical centre of its half rather than pinned to the top: the
   key is the only thing this screen asks for, so it sits where the eye lands. */
.entry .gate {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(30px, 6vh, 76px) clamp(24px, 4vw, 68px);
}

.entry .gate h1 {
  margin-bottom: 18px;
  font-size: clamp(38px, 4.2vw, 58px);
}

.gate-form {
  position: relative;
  margin-top: 30px;
}

.gate-label {
  display: block;
  margin-bottom: 8px;
  color: var(--graphite);
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.gate-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.gate-field {
  position: relative;
  display: flex;
  flex: 1 1 240px;
}

/* Sized as a flex item, not with a width: the admin gate puts this straight
   into `.gate-row` beside its button, while the landing wraps it in
   `.gate-field` so the sweep has something to sit against. Both work. */
.gate-input {
  flex: 1 1 240px;
  padding: 11px 14px;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  background: var(--surface);
  color: var(--ink);
  letter-spacing: 0.16em;
}

.gate-input:focus-visible {
  border-color: var(--blue);
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

.gate-input[aria-invalid="true"] {
  border-color: var(--rust);
}

/* Runs only while the key is in flight — a real wait, not a decoration. */
.gate-sweep {
  position: absolute;
  right: 1px;
  bottom: 1px;
  left: 1px;
  height: 2px;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: left;
}

.gate-form.is-verifying .gate-sweep {
  animation: gate-sweep 0.85s var(--ease) infinite;
}

@keyframes gate-sweep {
  0% {
    transform: scaleX(0);
    transform-origin: left center;
  }

  49.9% {
    transform: scaleX(1);
    transform-origin: left center;
  }

  50% {
    transform: scaleX(1);
    transform-origin: right center;
  }

  100% {
    transform: scaleX(0);
    transform-origin: right center;
  }
}

/* Used by the admin gate, which states its own posture inline rather than in a
   spec strip. */
.gate-footnote {
  margin: 14px 0 0;
  color: var(--graphite);
  font-size: 14px;
}

.gate.has-error .gate-form {
  animation: gate-shake 0.34s ease-out;
}

@keyframes gate-shake {
  20% {
    transform: translateX(-4px);
  }

  45% {
    transform: translateX(3px);
  }

  70% {
    transform: translateX(-2px);
  }
}

.notice {
  margin: 0 0 18px;
  padding: 11px 14px;
  border-left: 3px solid var(--rust);
  background: rgba(218, 176, 167, 0.32);
  font-size: 16px;
}

.notice-error {
  border-left-color: var(--rust);
  background: rgba(218, 176, 167, 0.32);
}

/* Being sent back to the gate is not a failure, and should not be dressed as
   one — this is the blue, explanatory half of the pair. */
.notice-info {
  border-left-color: var(--blue);
  background: rgba(21, 96, 130, 0.07);
}

/* --- Landing, unlocked -------------------------------------------------- */

.panel {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 26px 26px 22px;
  border: 1px solid var(--hairline);
  background: var(--surface);
}

.demo-entry-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
}

.demo-entry-actions .button {
  min-height: 46px;
  font-size: 18px;
}

.demo-entry-actions .button-quiet {
  color: var(--graphite);
}

.demo-entry-actions .bi {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  margin-right: 8px;
  fill: currentColor;
}

.landing-intro {
  max-width: 760px;
  margin: 0 0 18px;
}

.landing-lead {
  width: min(100%, var(--content));
  margin: 0 auto 34px;
}

/* --- The stage rail ----------------------------------------------------- */

/* A non-interactive table of contents. Its metrics deliberately echo the demo
   progress bar without implying that the individual previews are controls. */
.stage-rail-label {
  margin-bottom: 10px;
  color: var(--graphite);
  font-size: 18px;
  letter-spacing: normal;
}

.stage-rail {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
  margin: 0 0 28px;
  padding: 0;
  list-style: none;
}

.stage {
  min-width: 0;
}

.stage-item {
  display: flex;
  flex-direction: column;
  height: 100%;
  color: var(--graphite);
  font-size: 18px;
}

.stage-media {
  display: block;
  aspect-ratio: 10 / 9;
  overflow: hidden;
}

.stage-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.stage-copy {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 10px 4px 4px;
}

.stage-number {
  display: inline-flex;
  flex: 0 0 26px;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  font-size: inherit;
  line-height: 1;
}

.stage-label {
  min-width: 0;
  line-height: 1.2;
}

/* --- Demo chrome -------------------------------------------------------- */

/* Three tracks with the outer two equal, so the progress sits on the centre of
   the viewport and stays there whatever the logo measures. The third track is
   empty — the turn arrows that used to fill it now live at the page edges. */
.top-bar {
  position: sticky;
  z-index: 20;
  top: 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
  min-height: 78px;
  padding: 6px clamp(18px, 3vw, 38px);
  border-bottom: 1px solid var(--hairline);
  background: rgba(251, 245, 243, 0.94);
  backdrop-filter: blur(14px);
}

.top-bar-home {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  color: var(--ink);
  font-size: 17px;
  text-decoration: none;
}

.top-bar-logo {
  display: block;
  width: auto;
  height: 44px;
}

/* The other half of the morph: the landing page's `.stage-rail` carries this
   same name, so arriving here turns the preview into the real bar rather than
   replacing it. */
/* Centred rather than start-aligned. At full width the middle grid track is
   content-sized and this changes nothing; once the bar narrows and the track
   becomes the leftover space, it is what keeps the five steps on the centre
   instead of pushing them against the logo. */
.progress ol {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.progress a {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 58px;
  padding: 5px 11px;
  border-radius: 2px;
  color: var(--graphite);
  font-size: 14px;
  text-decoration: none;
}

.progress a:hover {
  background: rgba(218, 176, 167, 0.25);
}

.progress-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  font-size: inherit;
  line-height: 1;
}

.progress .is-current a {
  color: var(--ink);
  font-size: 18px;
}

.progress .is-current .progress-number {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--paper);
}

.progress .is-done .progress-number {
  border-color: var(--blue);
  color: var(--blue);
}

.progress-marker {
  display: flex;
  flex: 0 0 54px;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 50px;
}

.progress-thumb {
  display: flex;
  flex: 0 0 100%;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

.progress-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Stable identities let a same-document slide change interpolate every marker
   and label instead of replacing the active image and type in one frame. */
.progress li:nth-child(1) .progress-marker { view-transition-name: k4-marker-1; }
.progress li:nth-child(2) .progress-marker { view-transition-name: k4-marker-2; }
.progress li:nth-child(3) .progress-marker { view-transition-name: k4-marker-3; }
.progress li:nth-child(4) .progress-marker { view-transition-name: k4-marker-4; }
.progress li:nth-child(5) .progress-marker { view-transition-name: k4-marker-5; }

.progress li:nth-child(1) .progress-label { view-transition-name: k4-label-1; }
.progress li:nth-child(2) .progress-label { view-transition-name: k4-label-2; }
.progress li:nth-child(3) .progress-label { view-transition-name: k4-label-3; }
.progress li:nth-child(4) .progress-label { view-transition-name: k4-label-4; }
.progress li:nth-child(5) .progress-label { view-transition-name: k4-label-5; }

/* --- Page turns --------------------------------------------------------- */

/* Two instruments clamped to the edges of the viewport. Collapsed they are a
   drawn arrow and nothing else; hovered or focused they open to name the page
   they lead to, so the visitor knows where the gesture goes before making it.
   The document reserves the gutter they sit in, so they never cover text. */
.page-turn {
  position: fixed;
  z-index: 15;
  top: 50%;
  display: flex;
  align-items: center;
  height: 148px;
  padding: 0 15px;
  border: 1px solid var(--hairline);
  background: rgba(251, 245, 243, 0.7);
  backdrop-filter: blur(16px);
  color: var(--graphite);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.page-turn-previous {
  left: 0;
  border-left: 0;
  border-radius: 0 2px 2px 0;
  transform: translateY(-50%);
}

.page-turn-next {
  right: 0;
  flex-direction: row-reverse;
  border-right: 0;
  border-radius: 2px 0 0 2px;
  transform: translateY(-50%);
}

.page-turn:hover,
.page-turn:focus-visible {
  border-color: var(--blue);
  background: rgba(251, 245, 243, 0.94);
  color: var(--ink);
  outline: 0;
}

/* The tick against the screen edge: an indicator that arrives with the hover
   rather than a border that was always there. */
.page-turn::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--blue);
  opacity: 0;
  transition: opacity 0.06s ease;
}

.page-turn-previous::before {
  left: 0;
}

.page-turn-next::before {
  right: 0;
}

.page-turn:hover::before,
.page-turn:focus-visible::before {
  opacity: 1;
}

.page-turn-glyph {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
}

.page-turn-arrow {
  display: block;
  width: 40px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: square;
  transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.page-turn-previous .page-turn-arrow {
  transform: scaleX(-1);
}

/* A stub of shaft at rest, drawn out to full length on hover — the arrow
   lengthens towards the page rather than simply lighting up. The offset is
   negative so the visible part stays attached to the head; positive would
   leave it stranded at the tail. */
.page-turn-shaft {
  stroke-dasharray: 27;
  stroke-dashoffset: -16;
  transition: stroke-dashoffset 0.05s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.page-turn:hover .page-turn-shaft,
.page-turn:focus-visible .page-turn-shaft {
  stroke-dashoffset: 0;
}

.page-turn:hover .page-turn-head,
.page-turn:focus-visible .page-turn-head {
  stroke: var(--blue);
}

/* A 0fr → 1fr track, so the label widens the tile instead of being clipped by
   a width nobody can animate to. */
.page-turn-meta {
  display: grid;
  grid-template-columns: 0fr;
  align-content: center;
  opacity: 0;
  transition: grid-template-columns 0.22s cubic-bezier(0.2, 0.7, 0.2, 1),
    opacity 0.13s ease;
}

.page-turn:hover .page-turn-meta,
.page-turn:focus-visible .page-turn-meta {
  grid-template-columns: 1fr;
  opacity: 1;
}

.page-turn-meta > span {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.page-turn-card {
  display: flex;
  min-width: 112px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.page-turn-thumb {
  flex: 0 0 auto;
  width: 112px;
  height: 98px;
  object-fit: contain;
}

.page-turn-title {
  font-size: 16px;
  line-height: 1.15;
  text-align: center;
}

.page-turn-previous .page-turn-meta > span {
  padding-left: 15px;
}

/* Both sides read the same way: the text is pinned to the edge the tile grows
   from, so widening the tile uncovers it. Right-aligning the next label would
   instead expose the end of the word first, and reads backwards. */
.page-turn-next .page-turn-meta > span {
  padding-right: 15px;
}

/* The destination animates after direct navigation. This does not depend on
   cross-document view-transition support and never holds the outgoing page. */
@keyframes k4-document-enter-next {
  from {
    opacity: 0.32;
    transform: translate3d(28px, 0, 0);
  }
}

@keyframes k4-document-enter-previous {
  from {
    opacity: 0.32;
    transform: translate3d(-28px, 0, 0);
  }
}

@keyframes k4-document-leave-next {
  to {
    opacity: 0;
    transform: translate3d(-20px, 0, 0);
  }
}

@keyframes k4-document-leave-previous {
  to {
    opacity: 0;
    transform: translate3d(20px, 0, 0);
  }
}

html[data-slide-direction="next"] .demo-page main {
  animation: k4-document-enter-next 100ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

html[data-slide-direction="previous"] .demo-page main {
  animation: k4-document-enter-previous 100ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

.demo-page #main {
  view-transition-name: k4-document;
}

html[data-dynamic-transition] .demo-page main {
  animation: none;
}

@keyframes k4-progress-marker-out {
  to { opacity: 0; transform: scale(0.68); }
}

@keyframes k4-progress-marker-in {
  from { opacity: 0; transform: scale(0.68); }
}

@keyframes k4-progress-label-out {
  to { opacity: 0; transform: scale(0.9); }
}

@keyframes k4-progress-label-in {
  from { opacity: 0; transform: scale(0.9); }
}

:root[data-dynamic-transition]::view-transition-old(root),
:root[data-dynamic-transition]::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

:root[data-dynamic-transition][data-slide-direction="next"]::view-transition-old(k4-document) {
  animation: k4-document-leave-next 60ms cubic-bezier(0.4, 0, 1, 1) both;
}

:root[data-dynamic-transition][data-slide-direction="next"]::view-transition-new(k4-document) {
  animation: k4-document-enter-next 100ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

:root[data-dynamic-transition][data-slide-direction="previous"]::view-transition-old(k4-document) {
  animation: k4-document-leave-previous 60ms cubic-bezier(0.4, 0, 1, 1) both;
}

:root[data-dynamic-transition][data-slide-direction="previous"]::view-transition-new(k4-document) {
  animation: k4-document-enter-previous 100ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

::view-transition-group(k4-marker-1),
::view-transition-group(k4-marker-2),
::view-transition-group(k4-marker-3),
::view-transition-group(k4-marker-4),
::view-transition-group(k4-marker-5),
::view-transition-group(k4-label-1),
::view-transition-group(k4-label-2),
::view-transition-group(k4-label-3),
::view-transition-group(k4-label-4),
::view-transition-group(k4-label-5) {
  animation-duration: 180ms;
  animation-timing-function: cubic-bezier(0.16, 0.9, 0.24, 1);
}

::view-transition-old(k4-marker-1),
::view-transition-old(k4-marker-2),
::view-transition-old(k4-marker-3),
::view-transition-old(k4-marker-4),
::view-transition-old(k4-marker-5) {
  animation: k4-progress-marker-out 100ms ease-out both;
}

::view-transition-new(k4-marker-1),
::view-transition-new(k4-marker-2),
::view-transition-new(k4-marker-3),
::view-transition-new(k4-marker-4),
::view-transition-new(k4-marker-5) {
  animation: k4-progress-marker-in 180ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

::view-transition-old(k4-label-1),
::view-transition-old(k4-label-2),
::view-transition-old(k4-label-3),
::view-transition-old(k4-label-4),
::view-transition-old(k4-label-5) {
  animation: k4-progress-label-out 100ms ease-out both;
}

::view-transition-new(k4-label-1),
::view-transition-new(k4-label-2),
::view-transition-new(k4-label-3),
::view-transition-new(k4-label-4),
::view-transition-new(k4-label-5) {
  animation: k4-progress-label-in 180ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

/* --- Document ----------------------------------------------------------- */

.document {
  width: min(calc(100% - 40px), var(--content));
  margin: 0 auto;
  padding: 34px 0 72px;
}

.document h1 {
  margin-bottom: 24px;
  font-size: clamp(38px, 4.2vw, 54px);
  line-height: 1.05;
  letter-spacing: -0.025em;
}

.document-head-with-status {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}

.document-head-with-status h1 {
  margin-bottom: 0;
}

.document[data-page-slug="protocol-walkthrough"] .primary-section {
  padding-top: 24px;
}

.document[data-page-slug="protocol-walkthrough"] .console {
  margin-top: 0;
}

.placeholder-note {
  display: flex;
  align-items: baseline;
  gap: 10px;
  max-width: 760px;
  margin-bottom: 26px;
  padding: 10px 14px;
  border-left: 3px solid var(--rust);
  background: rgba(218, 176, 167, 0.26);
  color: var(--graphite);
  font-size: 15px;
}

.badge {
  flex: none;
  padding: 2px 8px;
  background: var(--rust);
  color: var(--paper);
  font-size: 12px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.document h2 {
  margin-bottom: 14px;
  padding-left: 42px;
  color: var(--blue);
  font-size: clamp(25px, 2.4vw, 32px);
  line-height: 1.16;
}

.document h3 {
  margin-bottom: 12px;
  padding-left: 42px;
  color: var(--blue);
  font-size: 20px;
  line-height: 1.38;
}

.primary-section {
  padding: 22px 0 28px;
}

.opening-section {
  padding-bottom: 38px;
}

.lead,
.intro-copy,
.subpoint-body {
  max-width: 930px;
  padding-left: 42px;
}

.lead {
  margin-bottom: 20px;
}

.intro-copy p,
.subpoint p {
  margin-bottom: 17px;
}

.sources-section p {
  color: var(--graphite);
  font-size: 15px;
}

.subpoints {
  display: grid;
  gap: 36px;
  margin-top: 44px;
}

/* Carried over from the standalone `index.html`, where these rules were left
   behind in the port: only the 820px override for `.worlds-divider` made it
   into this file, and without a `display: grid` to override it did nothing.
   The Impagliazzo's Worlds divider has been rendering as a bare paragraph
   since. */
.worlds-transition {
  padding-top: 8px;
}

.worlds-divider {
  display: grid;
  grid-template-columns: minmax(32px, 1fr) auto minmax(32px, 1fr);
  align-items: center;
  gap: 18px;
  margin: 0 0 22px;
  color: var(--blue);
  font-size: 20px;
  line-height: 1.38;
  text-align: center;
}

.worlds-divider::before,
.worlds-divider::after {
  height: 1px;
  background: rgba(21, 96, 130, 0.36);
  content: "";
}

/* --- Gallery: two columns of photographs ------------------------------- */

.gallery-section {
  padding-top: 8px;
}

.gallery {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px;
}

.gallery-column {
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.gallery-head {
  padding-bottom: 12px;
  border-bottom: 2px solid var(--blue);
}

.gallery-column:last-child .gallery-head {
  border-bottom-color: var(--rust);
}

.document .gallery-title {
  margin: 0 0 8px;
  padding-left: 0;
  color: var(--blue);
  font-size: 24px;
}

.gallery-column:last-child .gallery-title {
  color: var(--rust);
}

.gallery-note {
  margin: 0;
  color: var(--graphite);
  font-size: 15px;
}

.gallery-item {
  margin: 0;
}

.gallery-item img {
  width: 100%;
  border: 1px solid var(--hairline);
}

.gallery-item figcaption {
  margin-top: 10px;
  color: var(--graphite);
  font-size: 14px;
}

/* --- Overhead comparison: a three-stage chain over its bar charts ------- */

.figure-overhead {
  margin-top: 12px;
}

/* Stage, arrow, stage, arrow, stage. The charts sit on the row beneath and
   each spans the stages it describes. */
.overhead {
  display: grid;
  /* The last stage runs wide so the two charts below come out the same width:
     the left one also spans the arrow column and two gaps. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 2.5fr);
  align-items: end;
  gap: 22px 26px;
  width: min(100%, 980px);
  margin: 0 auto;
}

.overhead-stage {
  display: flex;
  flex-direction: column;
}

/* Same size as a gallery column title, so the two pages read as a set. */
.overhead-label {
  margin-bottom: 14px;
  color: var(--rust);
  font-size: 24px;
  letter-spacing: 0.02em;
  text-align: center;
}

/* A fixed-height media box so three very different aspect ratios still sit on
   a common baseline. */
.overhead-media {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 190px;
}

.overhead-media img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
}

.overhead-arrow {
  position: relative;
  width: 34px;
  height: 3px;
  margin: 0 0 92px;
  background: var(--graphite);
}

.overhead-arrow span {
  position: absolute;
  top: 50%;
  right: -1px;
  width: 0;
  height: 0;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  border-left: 13px solid var(--graphite);
  transform: translateY(-50%);
}

/* A chart covering n stages also covers the n-1 arrows between them. */
.overhead-chart {
  padding-top: 4px;
}

.overhead-note {
  margin: 12px 0 0;
  color: var(--graphite);
  font-size: 18px;
}

.overhead-chart[data-spans="1"] { grid-column: span 1; }
.overhead-chart[data-spans="2"] { grid-column: span 3; }
.overhead-chart[data-spans="3"] { grid-column: span 5; }

/* Keeps the following chart off the arrow column so it lands under its stage. */
.overhead-spacer {
  grid-column: span 1;
}

.overhead-metrics {
  display: block;
  width: 100%;
}

/* --- Module cost curves: two charts of the same market, side by side ----- */

.figure-curves {
  margin-top: 18px;
}

.cost-curves {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 26px 30px;
  width: min(100%, 980px);
  margin: 0 auto;
}

.cost-curve svg {
  display: block;
  width: 100%;
  height: auto;
}

/* --- Further applications: three transparent product studies ------------ */

.figure-applications {
  margin-top: 30px;
  padding-left: 42px;
}

.applications-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 42px);
}

.application-column {
  min-width: 0;
}

.application-media {
  display: flex;
  height: clamp(190px, 20vw, 250px);
  align-items: center;
  justify-content: center;
  margin-bottom: 24px;
}

.application-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.document h3.application-title {
  margin-bottom: 10px;
  padding-left: 0;
  color: var(--blue);
  font-size: 20px;
}

.application-column-ai .application-media img {
  width: 88%;
  height: 88%;
}

.application-description {
  margin: 0;
}

/* --- Figures ------------------------------------------------------------ */

.figure {
  margin: 8px 0 0;
}

.figure figcaption {
  margin-top: 14px;
  color: var(--graphite);
  font-size: 15px;
  text-align: center;
}

/* Figure 1: the adversarial channel. */

.adversary-diagram {
  position: relative;
  display: grid;
  grid-template-columns:
    minmax(190px, 1fr)
    minmax(200px, 0.72fr)
    minmax(220px, 1.05fr);
  align-items: center;
  gap: 26px;
  width: min(100%, 980px);
  height: 315px;
  margin: 2px auto 0;
}

.diagram-party {
  position: relative;
  z-index: 2;
  display: flex;
  height: 315px;
  align-self: end;
  flex-direction: column;
  justify-content: flex-end;
  text-align: center;
}

.diagram-party p {
  margin-bottom: 8px;
  font-size: 19px;
}

.diagram-user img {
  width: auto;
  height: 240px;
  margin: 0 auto;
  object-fit: contain;
}

.diagram-device img {
  width: auto;
  height: 240px;
  margin: 0 auto;
  object-fit: contain;
}

.channel-line {
  position: absolute;
  z-index: 0;
  top: 65%;
  right: 35%;
  left: 32%;
  height: 3px;
  background: var(--graphite);
}

.channel-line::before,
.channel-line::after {
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  content: "";
  transform: translateY(-50%);
}

.channel-line::before {
  left: -1px;
  border-right: 13px solid var(--graphite);
}

.channel-line::after {
  right: -1px;
  border-left: 13px solid var(--graphite);
}

.adversary-node {
  position: relative;
  z-index: 3;
  align-self: center;
  margin-bottom: 14px;
  text-align: center;
}

.adversary-node p {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--graphite);
  background: rgba(218, 176, 167, 0.58);
  font-size: 18px;
  line-height: 1.1;
}

.adversary-node span {
  position: relative;
  display: block;
  width: 52px;
  height: 52px;
  margin: 24px auto 0;
  outline: 3px solid var(--paper);
  background: var(--rust);
  box-shadow: 0 -24px 0 -22px var(--graphite);
}

.adversary-node span::before {
  position: absolute;
  bottom: 100%;
  left: 50%;
  width: 3px;
  height: 24px;
  background: var(--graphite);
  content: "";
  transform: translateX(-50%);
}

/* Figure 2: the protocol ladder. */

/* Figures 2 and 3 carry their own colours and type sizes as SVG presentation
   attributes so the same markup renders in the PDF fallback, whose SVG reader
   ignores CSS. Only layout belongs here — anything else would override them. */

.ladder,
.pipeline,
.tracks {
  display: block;
  width: min(100%, 980px);
  height: auto;
  margin: 10px auto 0;
}

/* --- Certification landscape ------------------------------------------ */

.figure-certification-map {
  margin-top: 18px;
}

.certification-intro {
  max-width: 930px;
  padding-left: 42px;
}

.certification-intro > p {
  margin: 0;
}

.certification-map {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-top: clamp(36px, 4.4vw, 56px);
  overflow: visible;
  container-type: inline-size;
}

.certification-canvas {
  position: absolute;
  inset: 0;
}

.certification-legend {
  position: absolute;
  top: -4.25%;
  left: 42px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 0;
  color: var(--graphite);
  font-size: inherit;
  line-height: inherit;
}

.certification-legend span {
  display: flex;
  align-items: center;
  gap: 8px;
}

.certification-legend img {
  display: block;
  width: 26px;
  height: 26px;
  object-fit: contain;
}

.certification-graph {
  position: absolute;
  top: 2%;
  left: 34%;
  width: 34%;
  height: 96%;
  object-fit: contain;
}

.certification-device {
  position: absolute;
  z-index: 3;
  width: 17%;
  aspect-ratio: 10 / 9;
  object-fit: contain;
  transform: translate(-50%, -50%);
}

.certification-device-open {
  top: 8%;
  left: 63%;
}

.certification-device-drive {
  top: 68%;
  left: 44.5%;
}

.certification-node {
  position: absolute;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 10%;
  color: var(--ink);
  font-size: clamp(9px, 1.16cqw, 13px);
  line-height: 1.12;
  text-align: center;
  transform: translate(-50%, -50%);
}

.certification-node strong {
  font-weight: 600;
}

/* Node labels are pinned to the measured centres of the rendered balls. */
.certification-node-emc { top: 25.6%; left: 52.5%; }
.certification-node-rohs { top: 41.6%; left: 39.8%; }
.certification-node-erp { top: 41.5%; left: 64.9%; }
.certification-node-cra { top: 67.3%; left: 60%; }
.certification-node-eucc {
  top: 91.4%;
  left: 37.5%;
  color: #fff;
  text-shadow: 0 1px 2px rgba(55, 22, 12, 0.36);
}

.certification-copy {
  position: absolute;
  z-index: 4;
  color: var(--ink);
  font-size: clamp(10px, 1.25cqw, 14px);
  line-height: 1.32;
}

.certification-copy h3 {
  display: none;
}

.certification-copy p {
  margin: 0;
}

.certification-copy .certification-owner {
  margin-top: 0.7cqw;
  color: var(--blue);
  font-size: clamp(9px, 1.05cqw, 12px);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.certification-copy-emc {
  top: 23.5%;
  left: 46%;
  width: 38%;
  text-align: right;
  transform: translate(-100%, -50%);
}

.certification-copy-rohs {
  top: 41.6%;
  left: 33%;
  width: 29%;
  text-align: right;
  transform: translate(-100%, -50%);
}

.certification-copy-erp {
  top: 41.5%;
  left: 71%;
  width: 27%;
  transform: translateY(-50%);
}

.certification-copy-cra {
  top: 67.3%;
  left: 66.5%;
  width: 30%;
  transform: translateY(-50%);
}

.certification-copy-eucc {
  top: 91.4%;
  left: 44%;
  width: 38%;
  transform: translateY(-50%);
}

.certification-copy-eucc .certification-owner {
  color: var(--rust);
}

.figure-lean-certificate {
  margin-top: 24px;
}

.lean-certificate {
  position: relative;
  max-width: 760px;
  min-height: 220px;
  padding: 36px 150px 36px 42px;
  border: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.32);
}

.lean-certificate::before {
  position: absolute;
  top: 12px;
  right: 12px;
  bottom: 12px;
  left: 12px;
  border: 1px solid rgba(21, 96, 130, 0.22);
  content: "";
  pointer-events: none;
}

.lean-certificate-kicker {
  margin: 0 0 16px;
  color: var(--blue);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.lean-certificate-title {
  margin: 0 0 14px;
  font-size: clamp(28px, 4vw, 42px);
  line-height: 1.05;
}

.lean-certificate-copy {
  max-width: 520px;
  margin: 0;
  color: var(--graphite);
}

.lean-certificate-seal {
  position: absolute;
  top: 50%;
  right: 48px;
  display: grid;
  width: 76px;
  height: 76px;
  border: 1px solid var(--blue);
  border-radius: 50%;
  color: var(--blue);
  font-size: 34px;
  place-items: center;
  transform: translateY(-50%);
}

@media (max-width: 800px) {
  .certification-intro {
    padding-left: 0;
  }

  .certification-map {
    aspect-ratio: auto;
    margin-top: 0;
    overflow: visible;
    container-type: normal;
  }

  .certification-legend {
    position: static;
    margin-bottom: 20px;
  }

  .certification-legend img {
    width: 22px;
    height: 22px;
  }

  .certification-canvas {
    position: relative;
    width: min(100%, 520px);
    aspect-ratio: 1 / 1;
    margin: 14px auto 22px;
  }

  .certification-graph {
    top: 2%;
    left: 20%;
    width: 60%;
    height: 96%;
  }

  .certification-device {
    width: 29.5%;
  }

  .certification-device-open {
    top: 8%;
    left: 71%;
  }

  .certification-device-drive {
    top: 68%;
    left: 38.5%;
  }

  .certification-node {
    width: 18%;
    font-size: clamp(9px, 2.4vw, 12px);
  }

  .certification-node-emc { top: 25.6%; left: 52.7%; }
  .certification-node-rohs { top: 41.6%; left: 30.2%; }
  .certification-node-erp { top: 41.5%; left: 74.5%; }
  .certification-node-cra { top: 67.3%; left: 66%; }
  .certification-node-eucc { top: 91.4%; left: 26.2%; }

  .certification-copy {
    position: static;
    width: 100%;
    margin: 0;
    padding: 18px 0;
    border-top: 1px solid var(--hairline);
    font-size: 15px;
    text-align: left;
    transform: none;
  }

  .certification-copy h3 {
    display: block;
    margin: 0 0 8px;
    font-size: 18px;
  }

  .certification-copy h3 span {
    color: var(--graphite);
    font-weight: 400;
  }

  .lean-certificate {
    min-height: 0;
    padding: 32px;
  }

  .lean-certificate-seal {
    display: none;
  }
}

/* --- Protocol walkthrough console ---------------------------------------
 *
 * One step per screen. Only the current step is in the DOM's flow; the rail
 * above carries the sense of where the visitor is, so nothing is lost by
 * hiding the rest.
 */

.console {
  width: min(100%, 980px);
  margin: 26px auto 0;
}

/* --- Mode chooser --- */

.console-chooser {
  padding: 0;
}

.console-modes {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px;
  margin-bottom: 0;
}

.console-mode {
  gap: 20px;
  padding: 18px;
  border: 1px solid var(--hairline);
  background: transparent;
}

.console-mode.is-unavailable {
  opacity: 0.62;
}

.console-mode-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.console-mode-head .gallery-title {
  margin: 0;
}

.console-mode-head .button {
  flex: none;
}

.console-mode-media {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 925 / 791;
  overflow: hidden;
}

.console-mode-media img {
  display: block;
  width: 90%;
  height: 90%;
  object-fit: contain;
}

.console-mode[data-mode-option="hardware"] .console-mode-media img {
  object-fit: cover;
  object-position: center top;
}

.console-hint {
  margin-bottom: 16px;
  color: var(--graphite);
  font-size: 14px;
}

/* --- Running banner --- */

.console-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
  padding: 11px 16px;
  border-left: 3px solid var(--blue);
  background: rgba(21, 96, 130, 0.07);
  font-size: 15px;
}

.console-banner[hidden] {
  display: none;
}

.console-banner.is-hardware {
  border-left-color: var(--rust);
  background: rgba(218, 176, 167, 0.3);
}

.console-banner .button {
  margin-left: auto;
}

.console-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--blue);
  animation: console-pulse 1.9s ease-in-out infinite;
}

.console-banner.is-hardware .console-dot {
  background: var(--rust);
}

@keyframes console-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.28; }
}

/* --- The stage: the product, opened ------------------------------------
   One picture, kept for the whole run. It arrives as the sealed drive, the
   casing goes transparent, and the board is drawn *onto* that casing — the
   photograph is the background, so the drawing has none of its own. Steps
   three and four then mark a component, fly into it, and hand over to the
   photograph of the real board.

   Colours follow the product render: the host's world in grey, Klein 4's in
   rust, and the boundary between them a pair of diodes. --- */

.stage {
  --dia-cool: #d5d3d2;
  --dia-cool-edge: #b3afad;
  --dia-trace: #cfccca;
  --dia-warm-trace: #dda27c;

  position: relative;
  margin: 0 0 30px;
}

/* Height is the whole opening: the product at full size through upload, then
   a compact persistent view once the backup step begins. */
.stage-frame {
  position: relative;
  height: 66vh;
  min-height: 380px;
  overflow: hidden;
  border: 0;
  background: transparent;
  transition: height 0.55s var(--ease), min-height 0.55s var(--ease);
}

.stage.is-docked .stage-frame {
  height: 400px;
  min-height: 0;
}

.dia {
  display: block;
  width: 100%;
  height: 100%;
}

/* Step one is an ordinary host path. It owns a separate, transparent scene so
   the security module does not appear until the boundary before step two. */
.dia-upload-scene,
.dia-module-scene {
  transition: opacity 0.24s ease;
}

.dia-module-scene {
  opacity: 0;
}

.stage:not(.is-upload) .dia-upload-scene {
  opacity: 0;
}

/* The host scene leaves on its own, before the columns are allowed to resize.
   Fading it out at the same time as the layout moves slid it towards the centre
   on its way out, which read as the graphic travelling rather than leaving. */
.stage.is-handing-off .dia-upload-scene {
  opacity: 0;
}

.stage:not(.is-upload) .dia-module-scene {
  opacity: 1;
}

.dia-upload-link {
  fill: none;
  stroke: var(--dia-cool-edge);
  stroke-width: 9;
  stroke-linecap: round;
}

/* --- The file being uploaded ---
   The chosen file crossing the link as itself. Hidden until a choice is made,
   and only ever one glyph shown.

   Rust, not blue, and not for want of consistency with the flows: the file is
   hot. It is live data on the host's own path, with nothing protecting it yet —
   which is the whole reason the next four steps exist. The palette already reads
   rust as the warm side, so the glyph says "unprotected" before its shape says
   what kind of file it is. */
.dia-parcel {
  offset-path: path("M336 334 L604 334");
  offset-distance: 0%;
  /* Upright the whole way. The route is a straight line, but rotating with it
     would still tip the glyph if the route ever bends. */
  offset-rotate: 0deg;
  opacity: 0;
}

.dia-parcel-glyph {
  display: none;
}

.dia-parcel-body {
  fill: var(--surface);
  stroke: var(--rust);
  stroke-width: 2;
}

/* An object's own edge, at the weight of the card it sits on. */
.dia-parcel-edge {
  fill: none;
  stroke: var(--rust);
  stroke-width: 2;
  stroke-linejoin: round;
}

/* Creases and rules inside it, lighter — the distinction a drawing makes
   between the outline of a thing and the lines across its face. */
.dia-parcel-mark {
  fill: none;
  stroke: var(--rust);
  stroke-width: 1.6;
  stroke-linecap: square;
  opacity: 0.75;
}

.dia-parcel-accent {
  fill: var(--rust);
  stroke: none;
}

.dia-parcel-accent-fill {
  fill: var(--rust);
  opacity: 0.16;
}

/* One crossing per upload, at a constant speed: the file leaves the laptop,
   travels, and arrives. It does not pulse, because a file is one thing. */
.stage.is-sending .dia-parcel {
  animation: dia-parcel-run 1.15s cubic-bezier(0.32, 0, 0.68, 1) both;
}

@keyframes dia-parcel-run {
  0% {
    offset-distance: 0%;
    opacity: 0;
  }

  10% {
    opacity: 1;
  }

  86% {
    opacity: 1;
  }

  100% {
    offset-distance: 100%;
    opacity: 0;
  }
}

.dia-upload-label {
  fill: #3f3b39;
  font-family: "Avenir LT Pro", Avenir, "Century Gothic", Arial, sans-serif;
  font-size: 25px;
  text-anchor: middle;
}

.dia-upload-laptop,
.dia-nas {
  filter: none;
}

/* The NAS before anything has reached it: there, but holding nothing. Almost all
   of its colour is taken out and it is set back into the paper, so what arrives
   with the file is the render's own warmth rather than a highlight laid over it.
   Once it has the file it keeps it — this state is never returned to, because on
   the rig the data does not leave either. */
.dia-upload-nas {
  filter: saturate(0.1) brightness(1.08) opacity(0.45);
  transition: filter 1s ease;
}

.stage.is-delivered .dia-upload-nas {
  filter: none;
}

/* --- The opening --- */

/* The sealed drive and the bare casing are the same crop of the same render,
   so one dissolves into the other without anything moving. A lid that slid
   away would be a lid; this is the same object, seen through. */
.dia-sealed,
.dia-casing {
  transition: opacity 0.5s var(--ease);
}

.dia-casing {
  opacity: 0;
}

.stage.is-opening .dia-sealed,
.stage.is-drawing .dia-sealed,
.stage.is-docked .dia-sealed {
  opacity: 0;
}

/* Far enough back to read as a housing, far enough down to read through. */
.stage.is-opening .dia-casing,
.stage.is-drawing .dia-casing,
.stage.is-docked .dia-casing {
  opacity: 0.4;
}

/* Everything drawn on the casing waits for the casing — but only the drawing.
   The two photographs and the focus rings run their own states, and a blanket
   `> *` reveal was quietly overriding all three of them: the sealed product was
   being held at full opacity behind the bare casing, and both focus rings were
   permanently on. Neither was visible until something dimmed the casing. */
.dia-scene > *:not(.dia-sealed, .dia-casing, .dia-reticle) {
  opacity: 0;
}

.stage.is-drawing .dia-scene > *:not(.dia-sealed, .dia-casing, .dia-reticle),
.stage.is-docked .dia-scene > *:not(.dia-sealed, .dia-casing, .dia-reticle) {
  opacity: 1;
}

/* --- The board --- */

.dia-block {
  stroke-width: 1.5;
}

.dia-cool .dia-block {
  fill: var(--dia-cool);
  stroke: var(--dia-cool-edge);
}

.dia-warm .dia-block {
  fill: var(--blush);
  stroke: var(--rust);
}

/* Outside the housing, so it is drawn on paper rather than on the casing. */
.dia-outside .dia-block {
  fill: var(--surface);
  stroke: var(--graphite);
}

.dia-nas-label {
  font-size: 22px;
}

.dia-label {
  fill: #3f3b39;
  font-family: "Avenir LT Pro", Avenir, "Century Gothic", Arial, sans-serif;
  font-size: 26px;
  text-anchor: middle;
}

.dia-warm .dia-label {
  fill: #3f3b39;
}

/* Wide and round-capped: these are traces on a board, not arrows in a diagram,
   and nothing about them should imply a direction on its own. */
.dia-trace,
.dia-feed {
  fill: none;
  stroke-width: 9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dia-trace {
  stroke: var(--dia-trace);
}

/* The link the host reads the selected drive over — past the diodes, all the
   way down to the multiplexer. */
.dia-trace-link,
.dia-trace-in {
  stroke: var(--dia-cool-edge);
}

.dia-feed {
  stroke: var(--dia-warm-trace);
}

.dia-diode-bar {
  stroke: var(--dia-cool-edge);
  stroke-width: 9;
  stroke-linecap: round;
}

.dia-diode {
  fill: var(--rust);
  stroke: none;
}

.dia-drive-body {
  fill: #eceae9;
  stroke: var(--dia-cool-edge);
  stroke-width: 1.5;
}

.dia-drive-plate {
  fill: var(--dia-cool);
  stroke: none;
}

.dia-drive-hair {
  stroke: var(--dia-cool-edge);
  stroke-width: 1.5;
}

.dia-ellipsis {
  fill: var(--dia-cool-edge);
}

/* The drive the multiplexer has taken off the reachable channel, and the wire
   that used to reach it. It stays this way, because it stays this way. */
.dia-drive {
  transition: opacity 0.9s var(--ease);
}

.stage.is-cold .dia-drive[data-drive="1"] {
  opacity: 0.22;
}

/* The final read begins at this isolated drive. Restore it before the route
   starts so the animation never appears to emerge from a disabled object. */
.stage.is-cold.flow-read .dia-drive[data-drive="1"] {
  opacity: 1;
  transition: none;
}

/* --- Drawing itself --- */

@keyframes dia-draw {
  from {
    stroke-dashoffset: 1;
  }
}

@keyframes dia-fade {
  from {
    opacity: 0;
  }
}

.stage.is-drawing .dia-block,
.stage.is-drawing .dia-trace,
.stage.is-drawing .dia-feed,
.stage.is-drawing .dia-diode-bar,
.stage.is-drawing .dia-drive-body,
.stage.is-drawing .dia-drive-hair {
  stroke-dasharray: 1;
  animation: dia-draw 0.4s var(--ease) both;
}

.stage.is-drawing .dia-diode,
.stage.is-drawing .dia-drive-plate,
.stage.is-drawing .dia-ellipsis,
.stage.is-drawing .dia text {
  animation: dia-fade 0.25s ease both;
}

/* Two at once on a block: the fill has to arrive with its outline, or it sits
   on the casing before it has been drawn. */
.stage.is-drawing .dia-block {
  animation: dia-draw 0.4s var(--ease) both, dia-fade 0.28s ease both;
}

.stage.is-drawing .dia-trace {
  animation-delay: 0.24s;
}

.stage.is-drawing .dia-feed {
  animation-delay: 0.36s;
}

.stage.is-drawing .dia-diode-bar,
.stage.is-drawing .dia-diode {
  animation-delay: 0.5s;
}

.stage.is-drawing .dia-drive-body,
.stage.is-drawing .dia-drive-hair,
.stage.is-drawing .dia-drive-plate,
.stage.is-drawing .dia-ellipsis {
  animation-delay: 0.65s;
}

.stage.is-drawing .dia text {
  animation-delay: 0.82s;
}

/* --- What a step moves ---
   A short dash running a route that is already drawn. It appears only while a
   step is actually running: a route lit before the button is pressed would be
   describing something that has not happened. */
/* One packet, and only ever one.

   `pathLength="1"` normalises every route, and the gap has to be longer than
   the route itself — not merely long enough to complete the period. With a
   0.05 / 0.95 pair the pattern repeats exactly once per path, so as the packet
   left one end the next repetition appeared at the other: a second packet
   setting off just as the first arrived, held there by `fill: both` until the
   flow was switched off. A gap of 1.95 puts the next repetition off the far end
   of the path, where it cannot be drawn. */
.dia-flow {
  fill: none;
  stroke: var(--blue);
  stroke-width: 8;
  stroke-linecap: round;
  stroke-dasharray: 0.05 1.95;
  stroke-dashoffset: -1;
  opacity: 0;
  transition: opacity 0.35s ease;
}

/* All four routes travel the same way — 0 to -1 — so one dash convention covers
   every one of them and none of them can wrap. */
@keyframes dia-run {
  from {
    stroke-dashoffset: 0;
  }

  to {
    stroke-dashoffset: -1;
  }
}

.stage.flow-multiplex .flow-multiplex {
  opacity: 1;
  animation: dia-run 2.6s linear infinite;
}

@keyframes dia-backup-run {
  from { stroke-dashoffset: 0; }
  to { stroke-dashoffset: -1; }
}

/* One packet, one ordered journey: NAS → SATA → controller → multiplexer →
   drive. It completes before the simulated backup reports success. */
.stage.flow-backup .flow-backup {
  opacity: 1;
  animation: dia-backup-run 1.2s linear both;
}

@keyframes dia-read-run {
  0% { stroke-dashoffset: 0; opacity: 1; }
  90% { opacity: 1; }
  100% { stroke-dashoffset: -1; opacity: 0; }
}

/* The final packet retraces the backup journey towards the NAS, but crosses
   the left-hand diode channel rather than the middle host link. */
.stage.flow-read .flow-read {
  opacity: 1;
  animation: dia-read-run 1.2s linear infinite;
}

/* The module's own traffic is its colour, not the host's. Scope this to the
   route itself: the stage carries the same state class, and setting a stroke
   there would be inherited by every label in the SVG during the animation. */
.dia-flow.flow-backup,
.dia-flow.flow-multiplex {
  stroke: var(--rust);
}

/* --- Taking aim, then flying in --- */

/* Three beats, and each one is allowed to finish before the next starts: the
   part is named, then approached, then arrived at. Rushing any of them is what
   made the flight read as a swap — the viewer never saw where they were going.

   Beat one: a viewfinder locks on. Corner brackets rather than a dashed box,
   because brackets read as an instrument aiming and a dashed box reads as a
   selection. */
.dia-reticle {
  fill: none;
  stroke: var(--blue);
  stroke-width: 3.5;
  stroke-dasharray: 14 10;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: 50% 50%;
  /* Arrives from outside the part and settles onto it, which reads as an
     instrument locking on rather than a box switching itself on. */
  transform: scale(1.5);
  transition: opacity 0.28s ease, transform 0.52s cubic-bezier(0.16, 0.9, 0.24, 1);
}

.stage.aim-mux [data-reticle="mux"],
.stage.aim-diode [data-reticle="diode"] {
  opacity: 1;
  transform: scale(1);
  animation: dia-reticle 2.4s linear infinite;
}

@keyframes dia-reticle {
  to {
    stroke-dashoffset: -48;
  }
}

/* Beat one also empties the board around the target. Only the drawing falls
   back — the product underneath it is what the viewer is orienting by, so it
   holds exactly the weight it had. Dimming the casing here is what exposed the
   sealed render behind it.

   One rule over `.dia-scene > *`, deliberately shaped like the staged reveal
   further up, because it has to outrank it. Naming the traces and feeds directly
   gave three classes against that rule's four, so they lost and stayed at full
   strength: the board dimmed its blocks and kept every orange line lit.

   The multiplexer step keeps its block and nothing else. The diode step keeps the
   diode it is about to test plus the two lines that carry that diode's read up to
   the disk controller — the parts `dia-tested` marks in the markup. */
.dia-part,
.dia-trace,
.dia-feed {
  transition: opacity 0.42s ease;
}

.stage.aim-mux
  .dia-scene
  > *:not(.dia-sealed, .dia-casing, .dia-reticle, [data-part="mux"]),
.stage.aim-diode
  .dia-scene
  > *:not(.dia-sealed, .dia-casing, .dia-reticle, [data-part="diode"], .dia-tested) {
  opacity: 0.16;
}

/* Beat two: the frame is the window, and the drawing is scaled inside it about
   the point the step is about. Nothing in the script knows these numbers. */
.dia-scene {
  transform-box: view-box;
  transform-origin: 50% 50%;
  /* Symmetric on purpose. A transition uses the same curve in both directions,
     so an asymmetric one cannot be its own reverse: with a fast-out, slow-in
     curve the approach and the retreat had different shapes. This one is a
     mirror of itself, which is what makes the way out the way in backwards.

     No delay on the way back in either. The delay below belongs to the flight
     only: carried here it also postponed the board's return, so what came back
     was already shrinking before it was fully visible. */
  transition: transform 1.15s cubic-bezier(0.45, 0, 0.55, 1), opacity 0.45s ease;
}

/* The point the view turns about is its own state, held for as long as the view
   is anywhere near the part — through the aim, the push in, and the whole pull
   back out.

   `transform-origin` is not an animatable part of `transform`, and it is not in
   the transition above: it changes in one frame. Carrying it on `aim-*`/`close-*`
   therefore meant that letting go of those to zoom out snapped the origin to the
   middle of the board on the very first frame, and the scale then ran down about
   a point the view had never been at. The zoom out has to be the zoom in
   backwards, which means the origin cannot move while it happens. */
.stage.on-mux .dia-scene {
  transform-origin: 294px 563px;
}

.stage.on-diode .dia-scene {
  transform-origin: 235px 382px;
}

/* A held lean towards the part while the reticle settles on it. */
.stage.aim-mux .dia-scene,
.stage.aim-diode .dia-scene {
  transform: scale(1.16);
}

/* The zoom belongs to `close-*`, not to `is-closing-in`. That is what lets the
   board stay exactly where the flight left it once the photograph goes: the
   camera is still on the part, at the scale it arrived at, so what is revealed
   behind the departing photograph is the view the visitor already had. Tying
   the scale to `is-closing-in` snapped the board back to the whole module the
   instant the photograph began to fade. */
.stage.close-mux .dia-scene {
  transform: scale(3.6);
}

.stage.close-diode .dia-scene {
  transform: scale(4.4);
}

/* Fading out is the flight's own business and is held back until the push in is
   under way. Fading back in is not: the board has to be there, whole and still
   at the scale it was left at, before it starts moving again. */
.stage.is-closing-in .dia-scene {
  opacity: 0;
  transition: transform 1.15s cubic-bezier(0.45, 0, 0.55, 1), opacity 0.55s ease 0.35s;
}

/* Beat three: the photograph carries on the movement the drawing was making. It
   grows into place as the view pushes in and shrinks away as the view pulls back
   out — the same direction, the same curve, the same duration as `.dia-scene`
   above. What was wrong before was not that it scaled but that it scaled the
   *other way*: it shrank to its resting size while the board behind it was still
   growing, which is two zooms disagreeing rather than one continuing. */
/* Both directions have to be *seen*, which is a question of when the fade
   happens relative to the scale rather than of the scale itself. Growing while
   still invisible and shrinking after already being invisible is the same as not
   scaling at all: on the way in the fade is held back so the photograph is
   already on screen for most of its growth, and on the way out it lags so the
   photograph is watched shrinking before it goes. */
.stage-close {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: transparent;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.78);
  transition: opacity 0.5s ease 0.22s, transform 0.8s cubic-bezier(0.45, 0, 0.55, 1);
}

.stage.is-closing-in .stage-close {
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.55s ease 0.42s,
    transform 1.1s cubic-bezier(0.45, 0, 0.55, 1) 0.2s;
}

.stage-close-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  width: 100%;
  height: 100%;
  margin: 0;
}

.stage-crossfade {
  position: relative;
  min-height: 0;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.stage-crossfade img {
  width: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* The diode board carries the image's own aspect ratio rather than letting the
   photograph letterbox inside a wider box. That is what makes the percentages
   below land on the silkscreen: the element's box and the painted photograph
   are the same rectangle. `flex: 1 1 auto` in this column gives it a definite
   height, from which the ratio derives the width. */
.stage-diode-board {
  /* Measured off data-diode-cutout.webp: the cathode bar of the silkscreen
     diode symbol, as fractions of the photograph. Nudge these, not the rules. */
  --diode-x: 34.7%;
  --diode-y: 63%;
  --diode-tilt: 6deg;

  /* Long enough to read as a wall across the trace rather than as a highlight on
     the silkscreen. It is centred on the cathode bar, so it still belongs to the
     symbol — it simply extends past it far enough that what stops the message is
     never in doubt. */
  --diode-height: 26%;

  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  margin: 0 auto;
  aspect-ratio: 2208 / 1368;
  max-width: 100%;
}

.stage-diode-board img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The board takes the hit. A jolt on the thing being struck is what makes the
   force land somewhere — without it the message stops and nothing else in the
   frame acknowledges that anything happened. Small and quick, and damped: five
   diminishing throws over four hundred milliseconds. Anything larger stops
   reading as an impact and starts reading as an earthquake. */
.stage-diode-board.is-jolted {
  animation: close-jolt 0.4s cubic-bezier(0.3, 0, 0.4, 1);
}

@keyframes close-jolt {
  0% {
    transform: translate(0, 0);
  }

  12% {
    transform: translate(-6px, 2px);
  }

  28% {
    transform: translate(4px, -3px);
  }

  46% {
    transform: translate(-3px, 2px);
  }

  64% {
    transform: translate(2px, -1px);
  }

  82% {
    transform: translate(-1px, 1px);
  }

  100% {
    transform: translate(0, 0);
  }
}

/* Both frames are the same board but for the lit channel, so fading one in
   reads as the channel switching over. */
.stage-crossfade img + img {
  position: absolute;
  inset: 0;
  margin: auto;
  opacity: 1;
  transition: opacity 0.9s ease;
}

.stage-crossfade img + img.is-hidden {
  opacity: 0;
}

/* --- A write, turned back -----------------------------------------------
   The text itself travels from the real input field. Only the diode boundary
   lives on the photograph, appearing at impact and lingering just long enough
   to make the rejection unambiguous. */
/* The refusal happens on the component that performs it. This sits exactly on
   the cathode bar of the silkscreen diode — the short stroke the triangle points
   at — and takes its lean from the board's perspective, so it reads as that mark
   lighting up rather than as a line laid over the photograph. */
.stage-barrier {
  position: absolute;
  top: var(--diode-y);
  left: var(--diode-x);
  width: 4px;
  height: var(--diode-height);
  margin-left: -2px;
  border-radius: 2px;
  background: var(--rust);
  opacity: 0;
  transform: translateY(-50%) rotate(var(--diode-tilt));
  transform-origin: center;
}

/* Raised while the write is still on its way: faint, but standing. The full
   transform is repeated in every keyframe — animating `transform` replaces it
   outright, so dropping the offset and the lean here would move the mark off
   the symbol for the duration of the animation. */
.stage-barrier.is-arming {
  animation: close-arm 0.42s var(--ease) forwards;
}

@keyframes close-arm {
  from {
    opacity: 0;
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(0.24);
  }

  to {
    opacity: 0.4;
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1);
  }
}

/* Continues from the armed state rather than from nothing, so the line hardens
   under the impact instead of appearing at it. */
@keyframes close-strike {
  0% {
    opacity: 0.4;
    box-shadow: none;
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1);
  }

  /* Struck: the mark hardens, throws a short bloom, and takes the load along
     its own length rather than swelling in every direction. */
  9% {
    opacity: 1;
    box-shadow: 0 0 22px 7px rgba(180, 90, 55, 0.5);
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1.22) scaleX(1.8);
  }

  26% {
    opacity: 1;
    box-shadow: 0 0 12px 3px rgba(180, 90, 55, 0.28);
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1.04) scaleX(1.1);
  }

  55% {
    opacity: 0.9;
    box-shadow: 0 0 8px 2px rgba(180, 90, 55, 0.16);
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1) scaleX(1);
  }

  100% {
    opacity: 0;
    box-shadow: none;
    transform: translateY(-50%) rotate(var(--diode-tilt)) scaleY(1) scaleX(1);
  }
}

.console-projectile {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  max-width: min(280px, 42vw);
  overflow: hidden;
  padding: 4px 10px;
  border: 1px solid var(--rust);
  border-radius: 2px;
  background: rgba(251, 245, 243, 0.97);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
  color: var(--ink);
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  text-overflow: ellipsis;
  /* The edge that meets the boundary. Compressing about the centre would pull
     that edge back off the diode exactly as it is supposed to be held against
     it. */
  transform-origin: left center;
  white-space: nowrap;
}

/* --- Progress rail --- */

/* Deliberately the same widget as `.progress` in the page bar, value for value:
   the same gap, the same marker box, the same 24px counter, the same 14 → 18px
   step up on the current item and the same blue. The steps are not links, so
   the only rule not carried over is the hover background.

   What was here before stretched the current item with `flex-grow` and dimmed
   the rest to 0.5 — that made the rail read as a different component sitting
   under the one at the top of the page. */
/* One line, always, and centred on it — the page bar above never wraps and is
   centred on the viewport, so a rail that broke onto a second row read as a
   different kind of thing. Where the five steps do not fit, the labels give way
   by truncating rather than by folding the rail in half. */
.console-rail {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin: 0 0 24px;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--hairline);
  list-style: none;
}

.console-rail-step {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  height: 58px;
  padding: 5px 11px;
  border-radius: 2px;
  color: var(--graphite);
  font-size: 14px;
  transition: color 180ms var(--ease), font-size 180ms var(--ease);
}

/* The step you are on is the one that has to be readable, so it is the one that
   never gives up characters. */
.console-rail-step.is-current {
  flex-shrink: 0;
  color: var(--ink);
  font-size: 18px;
}

/* The box the page bar reserves for its marker, so the counters line up on the
   same rhythm whether or not the active one is carrying an image. */
.console-rail-marker {
  display: flex;
  flex: 0 0 54px;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 50px;
}

.console-rail-dot {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  font-size: inherit;
  line-height: 1;
  transition: background-color 180ms var(--ease), border-color 180ms var(--ease),
              color 180ms var(--ease), font-size 180ms var(--ease);
}

.console-rail-step.is-current .console-rail-dot {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--paper);
  font-size: 14px;
}

.console-rail-step.is-done .console-rail-dot {
  border-color: var(--blue);
  color: var(--blue);
}

.console-rail-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The walkthrough rail mirrors the top bar: each marker and label keeps a
   stable identity while the active item changes size and state. */
:root[data-console-transition] .console-rail-step:nth-child(1) .console-rail-marker { view-transition-name: k4-console-marker-1; }
:root[data-console-transition] .console-rail-step:nth-child(2) .console-rail-marker { view-transition-name: k4-console-marker-2; }
:root[data-console-transition] .console-rail-step:nth-child(3) .console-rail-marker { view-transition-name: k4-console-marker-3; }
:root[data-console-transition] .console-rail-step:nth-child(4) .console-rail-marker { view-transition-name: k4-console-marker-4; }
:root[data-console-transition] .console-rail-step:nth-child(5) .console-rail-marker { view-transition-name: k4-console-marker-5; }

:root[data-console-transition] .console-rail-step:nth-child(1) .console-rail-label { view-transition-name: k4-console-label-1; }
:root[data-console-transition] .console-rail-step:nth-child(2) .console-rail-label { view-transition-name: k4-console-label-2; }
:root[data-console-transition] .console-rail-step:nth-child(3) .console-rail-label { view-transition-name: k4-console-label-3; }
:root[data-console-transition] .console-rail-step:nth-child(4) .console-rail-label { view-transition-name: k4-console-label-4; }
:root[data-console-transition] .console-rail-step:nth-child(5) .console-rail-label { view-transition-name: k4-console-label-5; }

:root[data-console-transition]::view-transition-old(root),
:root[data-console-transition]::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-group(k4-console-marker-1),
::view-transition-group(k4-console-marker-2),
::view-transition-group(k4-console-marker-3),
::view-transition-group(k4-console-marker-4),
::view-transition-group(k4-console-marker-5),
::view-transition-group(k4-console-label-1),
::view-transition-group(k4-console-label-2),
::view-transition-group(k4-console-label-3),
::view-transition-group(k4-console-label-4),
::view-transition-group(k4-console-label-5) {
  animation-duration: 180ms;
  animation-timing-function: cubic-bezier(0.16, 0.9, 0.24, 1);
}

::view-transition-old(k4-console-marker-1),
::view-transition-old(k4-console-marker-2),
::view-transition-old(k4-console-marker-3),
::view-transition-old(k4-console-marker-4),
::view-transition-old(k4-console-marker-5) {
  animation: k4-progress-marker-out 100ms ease-out both;
}

::view-transition-new(k4-console-marker-1),
::view-transition-new(k4-console-marker-2),
::view-transition-new(k4-console-marker-3),
::view-transition-new(k4-console-marker-4),
::view-transition-new(k4-console-marker-5) {
  animation: k4-progress-marker-in 180ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

::view-transition-old(k4-console-label-1),
::view-transition-old(k4-console-label-2),
::view-transition-old(k4-console-label-3),
::view-transition-old(k4-console-label-4),
::view-transition-old(k4-console-label-5) {
  animation: k4-progress-label-out 100ms ease-out both;
}

::view-transition-new(k4-console-label-1),
::view-transition-new(k4-console-label-2),
::view-transition-new(k4-console-label-3),
::view-transition-new(k4-console-label-4),
::view-transition-new(k4-console-label-5) {
  animation: k4-progress-label-in 180ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

/* The persistent product view and the current action each keep their own
   column. Component close-ups replace only the product view, never the action. */
.console-workspace {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.9fr);
  align-items: start;
  gap: 32px;
  transition: grid-template-columns 320ms var(--ease);
}

/* File selection needs more room for the three customer scenarios. Both
   layouts keep the same pair of grid tracks so browsers interpolate between
   them rather than snapping when the walkthrough advances. */
.console.is-upload-step .console-workspace {
  grid-template-columns: minmax(0, 1.2fr) minmax(280px, 1.05fr);
}

.console-workspace .stage {
  margin-bottom: 0;
}

.console-actions {
  min-width: 0;
}

.console-workspace .console-file-options {
  grid-template-columns: minmax(0, 1fr);
}

.console-workspace .console-file-option {
  min-height: 0;
}

/* --- One step's screen --- */

.console-screen {
  /* Screens swap in place; a short fade keeps the change from being abrupt. */
  animation: console-screen-in 0.32s ease both;
}

@keyframes console-screen-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.console-screen-prompt {
  max-width: 760px;
  margin-bottom: 26px;
  color: var(--graphite);
  font-size: 16px;
}

.console-screen-body {
  position: relative;
  display: grid;
  gap: 30px;
}

.console-controls {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 18px;
}

.document-head-with-status .console-banner {
  flex: none;
  margin: 0;
  padding-block: 8px;
}

/* The upload choice is presentation-only: all three files run the same
   protocol step and nothing after upload branches on the selected label. */
.console-file-picker {
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
}

.console-file-picker .console-label {
  margin-bottom: 8px;
  color: var(--ink);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.48;
  text-transform: none;
}

.console-file-options {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.console-file-option {
  display: flex;
  min-height: 142px;
  flex-direction: column;
  align-items: start;
  justify-content: space-between;
  gap: 18px;
  padding: 16px;
  border: 1px solid var(--hairline);
  background: transparent;
  font-size: 15px;
  line-height: 1.35;
}

.console-file-copy,
.console-file-option p {
  margin: 0;
}

.console-file-copy {
  display: grid;
  gap: 7px;
}

.console-file-head {
  display: flex;
  width: 100%;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.console-file-head .button {
  flex: none;
}

.console-file-option .console-file-title {
  color: var(--ink);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.48;
  text-transform: none;
}

/* Match the compact owner labels in the certification diagram. */
.console-file-option .console-file-meta {
  color: var(--blue);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 1.32;
  text-transform: uppercase;
}

.console-file-option .console-file-risk {
  color: var(--rust);
}

.console-file-option .console-file-customer {
  color: var(--ink);
}

@media (max-width: 620px) {
  .console-file-options {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
}

/* --- Diode entry --- */

.console-diode-entry {
  display: flex;
  flex-direction: column;
  gap: 9px;
  width: 100%;
}

.console-label {
  color: var(--graphite);
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.console-input {
  width: 100%;
  padding: 10px 13px;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
}

.console-input:focus-visible {
  border-color: var(--blue);
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

.console-diode-entry .button {
  align-self: start;
}

/* --- Result and the confirm gate --- */

.console-step-result {
  margin: 0;
  padding-left: 13px;
  border-left: 3px solid var(--blue);
  font-size: 15px;
}

.console-confirm {
  margin: 0;
}

.console-confirm .console-hint {
  margin-bottom: 10px;
}

/* --- Finishing --- */

/* The walkthrough used to end on the words "Walkthrough finished." — the one
   screen an investor is guaranteed to reach, carrying nothing. */
.console-finish {
  padding: 22px 24px 24px;
  border: 1px solid var(--hairline);
  border-top: 3px solid var(--blue);
  background: var(--surface);
}

.console-finish-kicker {
  margin-bottom: 14px;
  color: var(--graphite);
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.console-finish-list {
  display: grid;
  gap: 14px;
  margin: 0 0 22px;
  padding: 0;
  list-style: none;
}

.console-finish-list li {
  position: relative;
  padding-left: 28px;
  font-size: 16px;
}

.console-finish-list li::before {
  position: absolute;
  top: 0;
  left: 0;
  color: var(--blue);
  content: "✓";
}

/* A step that has run and is waiting to be acknowledged: the controls that did
   their work step back so the confirm is the only live thing left. */
.console-screen.is-awaiting .console-controls > [data-action] {
  opacity: 0.55;
}

/* --- Error page --------------------------------------------------------- */

.error-panel {
  width: min(calc(100% - 40px), 640px);
  margin: 0 auto;
  padding: 90px 0;
}

.error-panel h1 {
  margin-bottom: 14px;
  font-size: 72px;
  line-height: 1;
}

.error-panel .button {
  margin-top: 20px;
}

.admin-gate-page .gate-admin {
  width: min(calc(100% - 40px), 560px);
  margin: 0 auto;
  padding: 96px 0;
}

/* --- Responsive --------------------------------------------------------- */

/* Below the width at which the content column leaves a gutter of its own, the
   document gives one up so the arrows still have somewhere to stand. */
@media (max-width: 1220px) {
  .demo-page .document {
    width: min(calc(100% - 176px), var(--content));
  }
}

/* Narrower still and an opened label would reach the text, so the tiles stay
   collapsed and only the arrow answers the hover. */
@media (max-width: 1080px) {
  .page-turn {
    height: 88px;
    padding: 0 11px;
  }

  .page-turn-meta {
    display: none;
  }

  .page-turn-arrow {
    width: 34px;
  }

  .demo-page .document {
    width: min(calc(100% - 148px), var(--content));
  }
}

@media (max-width: 980px) {
  .progress-label {
    display: none;
  }
}

/* Past here the gutters cost more than they are worth, so the pair leaves the
   sides and takes the empty third track of the bar — where a reader's eye is
   already going for the progress. The rail overlays the bar rather than
   sitting in it: the bar's backdrop-filter would otherwise become the
   containing block for anything fixed inside it. */
@media (max-width: 900px) {
  .demo-page .document {
    width: min(calc(100% - 40px), var(--content));
  }

  .page-turns {
    position: fixed;
    z-index: 25;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    height: 78px;
    padding-right: clamp(18px, 3vw, 38px);
  }

  .top-bar {
    padding-right: calc(clamp(18px, 3vw, 38px) + 86px);
  }

  .page-turn {
    position: static;
    height: 36px;
    padding: 0 8px;
    border: 0;
    border-radius: 2px;
    background: transparent;
    backdrop-filter: none;
    transform: none;
  }

  .page-turn:hover,
  .page-turn:focus-visible {
    background: rgba(218, 176, 167, 0.25);
  }

  .page-turn::before {
    display: none;
  }

  .page-turn-arrow {
    width: 27px;
  }
}

/* Too narrow to hold the render beside the key, so the screen becomes a page:
   the plate keeps a band at the top and the gate runs underneath it. */
@media (max-width: 900px) {
  .landing-page.is-locked {
    display: block;
    min-height: 0;
  }

  .landing-page.is-locked main {
    display: block;
  }

  .entry {
    grid-template-columns: minmax(0, 1fr);
  }

  .entry-plate {
    height: 34vh;
    min-height: 200px;
    border-right: 0;
    border-bottom: 1px solid var(--hairline);
  }

  .entry .gate {
    padding: 36px clamp(20px, 4vw, 38px) 48px;
  }

  .entry .gate h1 {
    font-size: clamp(34px, 7vw, 44px);
  }
}

@media (max-width: 820px) {
  .document h2,
  .document h3,
  .lead,
  .intro-copy,
  .subpoint-body,
  .gallery {
    padding-left: 0;
  }

  .gallery {
    grid-template-columns: minmax(0, 1fr);
  }

  /* One chart per row once they would be too narrow to read side by side. */
  .cost-curves {
    grid-template-columns: minmax(0, 1fr);
  }

  .figure-applications {
    padding-left: 0;
  }

  .applications-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 36px;
  }

  .application-media {
    height: 250px;
  }

  /* Stacked, the chain reads downwards: one stage per row, and every chart
     spans the single column. */
  .overhead {
    grid-template-columns: minmax(0, 1fr);
  }

  .overhead-chart[data-spans="1"],
  .overhead-chart[data-spans="2"],
  .overhead-chart[data-spans="3"] {
    grid-column: span 1;
  }

  .overhead-spacer {
    display: none;
  }

  .overhead-arrow {
    width: 3px;
    height: 34px;
    margin: 0 auto;
  }

  .overhead-arrow span {
    top: auto;
    right: auto;
    bottom: -14px;
    left: 50%;
    border-top: 14px solid var(--graphite);
    border-right: 8px solid transparent;
    border-bottom: 0;
    border-left: 8px solid transparent;
    transform: translateX(-50%);
  }


  .worlds-divider {
    gap: 12px;
    margin-bottom: 18px;
  }

  .adversary-diagram {
    grid-template-columns:
      minmax(130px, 1fr)
      minmax(150px, 0.75fr)
      minmax(150px, 1fr);
    gap: 12px;
  }
}

@media (max-width: 620px) {
  body {
    font-size: 17px;
    overflow-x: hidden;
  }

  .stage-rail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .hero-image {
    height: clamp(520px, 72vh, 680px);
    max-height: none;
  }

  /* The logo keeps its place and the steps take the rest, still centred on
     what is left rather than pushed up against it. */
  .top-bar {
    grid-template-columns: auto minmax(0, 1fr);
    gap: 12px;
  }

  .document,
  .demo-page .document {
    width: min(calc(100% - 28px), var(--content));
    padding-top: 26px;
  }

  /* A phone bar has to fit a logo, five steps and the pair, so every one of
     them gives up a little. */
  .top-bar-logo {
    height: 32px;
  }

  .progress ol {
    gap: 2px;
  }

  .progress a {
    padding: 5px 7px;
  }

  .top-bar {
    padding-right: 88px;
  }

  .page-turns {
    padding-right: 14px;
  }

  .page-turn {
    padding: 0 7px;
  }

  .page-turn-arrow {
    width: 24px;
  }

  .adversary-diagram {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    height: auto;
    margin-top: 24px;
  }

  .diagram-party {
    display: flex;
    height: auto;
    flex-direction: column;
    align-items: center;
    width: 100%;
    text-align: center;
  }

  .diagram-party img {
    width: min(72%, 230px);
    height: auto;
    max-height: 150px;
    margin: 0 auto;
    object-fit: contain;
  }

  .diagram-device img {
    order: 1;
  }

  .diagram-device p {
    order: 2;
    margin-top: 4px;
  }

  .adversary-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: min(100%, 280px);
    margin: 2px 0;
  }

  .adversary-node p {
    width: 100%;
  }

  .adversary-node span {
    width: 48px;
    height: 48px;
    margin: 18px auto 0;
    box-shadow: none;
  }

  .adversary-node span::before {
    height: 18px;
  }

  .channel-line {
    top: 24%;
    right: auto;
    bottom: 22%;
    left: 50%;
    width: 3px;
    height: auto;
    transform: translateX(-50%);
  }

  .channel-line::before,
  .channel-line::after {
    left: 50%;
    border-right: 7px solid transparent;
    border-left: 7px solid transparent;
    transform: translateX(-50%);
  }

  .channel-line::before {
    top: -1px;
    border-top: 0;
    border-bottom: 13px solid var(--graphite);
  }

  .channel-line::after {
    top: auto;
    right: auto;
    bottom: -1px;
    border-top: 13px solid var(--graphite);
    border-bottom: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .button {
    transition: none;
  }

  /* The turn still reads, but the arriving document does not move. */
  .demo-page main {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* Gate feedback becomes immediate rather than moving. */
  .gate.has-error .gate-form,
  .gate-form.is-verifying .gate-sweep {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .console-screen,
  .console-dot {
    animation: none;
  }

  .console-rail-step,
  .console-rail-dot {
    transition: none;
  }

  /* The board is simply there, already drawn, at its docked size. Dropping the
     dash array as well as the animation matters: an element left at
     `stroke-dashoffset: 1` with nothing running is an invisible line, and this
     drawing is the whole walkthrough. */
  .stage-frame,
  .console-workspace,
  .dia-sealed,
  .dia-casing,
  .dia-scene,
  .dia-drive,
  .dia-flow,
  .dia-reticle,
  .dia-part,
  .dia-trace,
  .dia-feed,
  .stage-close,
  .stage-crossfade img + img {
    transition: none;
  }

  /* The aim still reads — the target keeps its full weight while the rest of the
     board falls back — but nothing zooms and nothing is thrown. */
  .stage-barrier.is-arming,
  .stage-barrier.is-struck,
  .stage-diode-board.is-jolted,
  .stage.is-sending .dia-parcel {
    animation: none;
  }

  /* The chosen file is still shown, sitting at the NAS end of the link, so the
     upload is stated without anything crossing the screen. Its arrival still
     brings the NAS up to colour — that is a state, not a movement — it simply
     changes without a transition. */
  .stage.is-sending .dia-parcel {
    offset-distance: 100%;
    opacity: 1;
  }

  .dia-upload-nas {
    transition: none;
  }

  .stage-barrier.is-arming,
  .stage-barrier.is-struck {
    opacity: 1;
  }

  .stage .dia-block,
  .stage .dia-trace,
  .stage .dia-feed,
  .stage .dia-diode,
  .stage .dia-diode-bar,
  .stage .dia-drive-body,
  .stage .dia-drive-hair,
  .stage .dia-drive-plate,
  .stage .dia-ellipsis,
  .stage .dia text {
    animation: none;
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }

  /* The route a step uses is still marked — it just does not travel. */
  .stage.flow-backup .flow-backup,
  .stage.flow-multiplex .flow-multiplex,
  .stage.flow-read .flow-read,
  .stage.aim-mux [data-reticle="mux"],
  .stage.aim-diode [data-reticle="diode"] {
    animation: none;
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    opacity: 0.55;
  }

  .stage-barrier.is-struck {
    animation: none;
  }
}

@media (max-width: 900px) {
  .console-workspace,
  .console.is-upload-step .console-workspace {
    grid-template-columns: minmax(0, 1fr);
  }

  .console-modes {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The page bar drops its labels at this width too, keeping only the counters
     and the one you are on. */
  .console-rail-label {
    display: none;
  }

  .console-rail-step.is-current .console-rail-label {
    display: inline;
  }

  .console-rail-marker {
    flex: 0 0 34px;
    width: 34px;
  }
}

@media (max-width: 700px) {
  .document-head-with-status {
    display: block;
  }

  .document-head-with-status .console-banner {
    width: 100%;
    margin-top: 12px;
  }
}

/* --- Error page --------------------------------------------------------- */

.error-panel {
  width: min(calc(100% - 40px), 640px);
  margin: 0 auto;
  padding: 90px 0;
}

.error-panel h1 {
  margin-bottom: 14px;
  font-size: 72px;
  line-height: 1;
}

.error-panel .button {
  margin-top: 20px;
}

.admin-gate-page .gate-admin {
  width: min(calc(100% - 40px), 560px);
  margin: 0 auto;
  padding: 96px 0;
}

/* --- Responsive --------------------------------------------------------- */

/* Below the width at which the content column leaves a gutter of its own, the
   document gives one up so the arrows still have somewhere to stand. */
@media (max-width: 1220px) {
  .demo-page .document {
    width: min(calc(100% - 176px), var(--content));
  }
}

/* Narrower still and an opened label would reach the text, so the tiles stay
   collapsed and only the arrow answers the hover. */
@media (max-width: 1080px) {
  .page-turn {
    height: 88px;
    padding: 0 11px;
  }

  .page-turn-meta {
    display: none;
  }

  .page-turn-arrow {
    width: 34px;
  }

  .demo-page .document {
    width: min(calc(100% - 148px), var(--content));
  }
}

@media (max-width: 980px) {
  .progress-label {
    display: none;
  }
}

/* Past here the gutters cost more than they are worth, so the pair leaves the
   sides and takes the empty third track of the bar — where a reader's eye is
   already going for the progress. The rail overlays the bar rather than
   sitting in it: the bar's backdrop-filter would otherwise become the
   containing block for anything fixed inside it. */
@media (max-width: 900px) {
  .demo-page .document {
    width: min(calc(100% - 40px), var(--content));
  }

  .page-turns {
    position: fixed;
    z-index: 25;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    height: 78px;
    padding-right: clamp(18px, 3vw, 38px);
  }

  .top-bar {
    padding-right: calc(clamp(18px, 3vw, 38px) + 86px);
  }

  .page-turn {
    position: static;
    height: 36px;
    padding: 0 8px;
    border: 0;
    border-radius: 2px;
    background: transparent;
    backdrop-filter: none;
    transform: none;
  }

  .page-turn:hover,
  .page-turn:focus-visible {
    background: rgba(218, 176, 167, 0.25);
  }

  .page-turn::before {
    display: none;
  }

  .page-turn-arrow {
    width: 27px;
  }
}

/* Too narrow to hold the render beside the key, so the screen becomes a page:
   the plate keeps a band at the top and the gate runs underneath it. */
@media (max-width: 900px) {
  .landing-page.is-locked {
    display: block;
    min-height: 0;
  }

  .landing-page.is-locked main {
    display: block;
  }

  .entry {
    grid-template-columns: minmax(0, 1fr);
  }

  .entry-plate {
    height: 34vh;
    min-height: 200px;
    border-right: 0;
    border-bottom: 1px solid var(--hairline);
  }

  .entry .gate {
    padding: 36px clamp(20px, 4vw, 38px) 48px;
  }

  .entry .gate h1 {
    font-size: clamp(34px, 7vw, 44px);
  }
}

@media (max-width: 820px) {
  .document h2,
  .document h3,
  .lead,
  .intro-copy,
  .subpoint-body,
  .gallery {
    padding-left: 0;
  }

  .gallery {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked, the chain reads downwards: one stage per row, and every chart
     spans the single column. */
  .overhead {
    grid-template-columns: minmax(0, 1fr);
  }

  .overhead-chart[data-spans="1"],
  .overhead-chart[data-spans="2"],
  .overhead-chart[data-spans="3"] {
    grid-column: span 1;
  }

  .overhead-spacer {
    display: none;
  }

  .overhead-arrow {
    width: 3px;
    height: 34px;
    margin: 0 auto;
  }

  .overhead-arrow span {
    top: auto;
    right: auto;
    bottom: -14px;
    left: 50%;
    border-top: 14px solid var(--graphite);
    border-right: 8px solid transparent;
    border-bottom: 0;
    border-left: 8px solid transparent;
    transform: translateX(-50%);
  }


  .worlds-divider {
    gap: 12px;
    margin-bottom: 18px;
  }

  .adversary-diagram {
    grid-template-columns:
      minmax(130px, 1fr)
      minmax(150px, 0.75fr)
      minmax(150px, 1fr);
    gap: 12px;
  }
}

@media (max-width: 620px) {
  body {
    font-size: 17px;
    overflow-x: hidden;
  }

  .hero-image {
    height: clamp(520px, 72vh, 680px);
    max-height: none;
  }

  /* The logo keeps its place and the steps take the rest, still centred on
     what is left rather than pushed up against it. */
  .top-bar {
    grid-template-columns: auto minmax(0, 1fr);
    gap: 12px;
  }

  .document,
  .demo-page .document {
    width: min(calc(100% - 28px), var(--content));
    padding-top: 26px;
  }

  /* A phone bar has to fit a logo, five steps and the pair, so every one of
     them gives up a little. */
  .top-bar-logo {
    height: 32px;
  }

  .progress ol {
    gap: 2px;
  }

  .progress a {
    padding: 5px 7px;
  }

  .top-bar {
    padding-right: 88px;
  }

  .page-turns {
    padding-right: 14px;
  }

  .page-turn {
    padding: 0 7px;
  }

  .page-turn-arrow {
    width: 24px;
  }

  .adversary-diagram {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    height: auto;
    margin-top: 24px;
  }

  .diagram-party {
    display: flex;
    height: auto;
    flex-direction: column;
    align-items: center;
    width: 100%;
    text-align: center;
  }

  .diagram-party img {
    width: min(72%, 230px);
    height: auto;
    max-height: 150px;
    margin: 0 auto;
    object-fit: contain;
  }

  .diagram-device img {
    order: 1;
  }

  .diagram-device p {
    order: 2;
    margin-top: 4px;
  }

  .adversary-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: min(100%, 280px);
    margin: 2px 0;
  }

  .adversary-node p {
    width: 100%;
  }

  .adversary-node span {
    width: 48px;
    height: 48px;
    margin: 18px auto 0;
    box-shadow: none;
  }

  .adversary-node span::before {
    height: 18px;
  }

  .channel-line {
    top: 24%;
    right: auto;
    bottom: 22%;
    left: 50%;
    width: 3px;
    height: auto;
    transform: translateX(-50%);
  }

  .channel-line::before,
  .channel-line::after {
    left: 50%;
    border-right: 7px solid transparent;
    border-left: 7px solid transparent;
    transform: translateX(-50%);
  }

  .channel-line::before {
    top: -1px;
    border-top: 0;
    border-bottom: 13px solid var(--graphite);
  }

  .channel-line::after {
    top: auto;
    right: auto;
    bottom: -1px;
    border-top: 13px solid var(--graphite);
    border-bottom: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .button {
    transition: none;
  }

  /* The turn still reads, but the arriving document does not move. */
  .demo-page main {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* Gate feedback becomes immediate rather than moving. */
  .gate.has-error .gate-form,
  .gate-form.is-verifying .gate-sweep {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .page-turn,
  .page-turn::before,
  .page-turn-arrow,
  .page-turn-shaft,
  .page-turn-meta {
    transition: none;
  }

  .page-turn-shaft {
    stroke-dashoffset: 0;
  }
}

@media (max-width: 620px) {
  .progress a {
    height: 40px;
    padding: 4px 3px;
  }

  .progress-marker {
    flex-basis: 32px;
    width: 32px;
    height: 32px;
  }

  .top-bar {
    min-height: 70px;
  }

  .page-turns {
    height: 70px;
  }
}

@media (min-width: 901px) and (max-width: 1100px) {
  .page-turn {
    height: 104px;
  }
}
