@charset "utf-8";

/* ===========================================================================
   Lumi — lumi.seluba.ch
   The page is one continuous tonal ramp: a dark room at the top, daylight at
   the bottom. Every colour comes from the product photography in
   Photos/Output — a warm, low-saturation sand family around hue 45°.
   =========================================================================== */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}

@font-face {
  font-family: "Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}

:root {
  /* tonal ramp, dark -> light */
  --c0: #050505;
  --c1: #0b0a08;
  --c2: #201d17;
  --c3: #4a4434;
  --c4: #8c8469;
  --c5: #a79e84;
  --c6: #bdb59b;
  --c7: #d6cfb7;
  --c75: #dfd8c2;
  --c8: #e9e3ce;

  /* Signal colours, taken from the LEDs and the brass/copper hardware. These
     are the values for the dark half of the page; .on-light darkens them so
     they keep their weight against sand. */
  --cu: #d79b55;
  --sig-red: #d4564a;
  --sig-green: #5aa86a;
  --p1: #d4564a;
  --p2: #5b92d4;
  --p3: #5aa86a;
  --p4: #d9a03c;

  --fg: #f2ede0;
  --dim: #98917f;
  /* How present an unlit pod is in a pictogram. It needs more weight against
     the dark band than it does against sand. */
  --pod-idle: 0.36;
  --rule: rgba(242, 237, 224, 0.13);

  --ui: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --mono: "Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas,
    "Liberation Mono", monospace;

  --pad: clamp(20px, 5vw, 64px);
  --title-gap: clamp(2.1rem, 4.8vw, 3.3rem);
  --sec-y: clamp(5.5rem, 13vh, 11rem);
  --wrap: 1180px;
}

.on-light {
  color: var(--fg);
  --fg: #171611;
  --dim: #5b5646;
  --rule: rgba(23, 22, 17, 0.16);
  --pod-idle: 0.2;
  --cu: #8a551b;
  --sig-red: #b1362b;
  --sig-green: #3f7f4d;
  --p1: #b1362b;
  --p2: #2f5f96;
  --p3: #3f7f4d;
  --p4: #a8761b;
}

/* --------------------------------------------------------------- base ---- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--c0);
  color: var(--fg);
  font-family: var(--ui);
  font-size: clamp(1rem, 0.94rem + 0.2vw, 1.0625rem);
  line-height: 1.55;
  font-weight: 400;
  letter-spacing: -0.006em;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
picture,
canvas,
svg {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

a {
  color: inherit;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.22em;
}

a:hover {
  text-decoration-color: currentColor;
}

:focus-visible {
  outline: 2px solid var(--cu);
  outline-offset: 3px;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* The project's own mark, taken from Lumi.svg: the brush wordmark alone, in
   currentColor. The pebble and the sakura are left off — as a signature next
   to Inter it reads as a maker's mark rather than a sticker. */
.lumi-mark {
  width: 100%;
  height: auto;
  display: block;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--c8);
  color: #171611;
  padding: 0.7rem 1.1rem;
}

.skip:focus {
  left: 0;
}

/* A trace of grain keeps the long gradients from banding on 8-bit displays. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background-image: url("../img/noise.png");
  background-size: 64px 64px;
  opacity: 0.05;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.mono {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

/* --------------------------------------------------------- top bar ------- */

.bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  padding: 0.9rem var(--pad);
  padding-top: max(0.9rem, env(safe-area-inset-top));
  color: #f2ede0;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.5s ease, transform 0.5s ease;
  pointer-events: none;
}

.bar.show {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.bar.on-light {
  color: #171611;
}

.bar__mark {
  display: block;
  width: 3.1rem;
}

/* ------------------------------------------------------------- hero ------ */

.hero {
  background: var(--c0);
}

.hero__track {
  height: 340vh;
  position: relative;
}

.hero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: grid;
}

.hero__stage>* {
  grid-area: 1 / 1;
}

/* Falls off towards the frame, which both reads as lens vignetting and hides
   the edge extension the canvas paints when the scale is capped. */
.hero__stage::after {
  content: "";
  grid-area: 1 / 1;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(ellipse 76% 84% at 50% 54%,
      rgba(5, 5, 5, 0) 52%, rgba(5, 5, 5, 0.62) 100%);
}

/* The stage is clipped dead straight at the bottom of the track, and on a wide
   desktop that cut lands on the render's floor pool — a warm rgb(18,13,1) meeting
   the flat #050505 the next section opens on. The room falls into the page's own
   black over the last stretch of the track instead, so there is nothing to cut.
   Sits on the track, not on the stage, so the stage's overflow never clips it. */
.hero__track::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: clamp(6rem, 16vh, 13rem);
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(to bottom,
      rgba(5, 5, 5, 0) 0%,
      rgba(5, 5, 5, 0.35) 45%,
      rgba(5, 5, 5, 0.78) 75%,
      var(--c0) 100%);
}

.hero__ui {
  position: relative;
  z-index: 3;
  display: grid;
  align-content: center;
  justify-items: center;
  text-align: center;
  padding-inline: var(--pad);
  /* the opening render puts the pod in the lower third, so the type sits above it */
  padding-bottom: clamp(4rem, 38vh, 26rem);
  pointer-events: none;
}

.wordmark {
  margin: 0;
  width: clamp(11rem, 36vw, 42rem);
  color: #f6f2e7;
  opacity: clamp(0, calc((0.30 - var(--p, 0)) / 0.16), 1);
  transform: translate3d(0, calc(var(--p, 0) * -4vh), 0) scale(calc(1 - var(--p, 0) * 0.06));
  filter: drop-shadow(0 0 60px rgba(0, 0, 0, 0.9));
  will-change: opacity, transform;
}

.hero__sub {
  margin: 1.1rem 0 0;
  font-size: clamp(0.95rem, 1.6vw, 1.2rem);
  font-weight: 300;
  letter-spacing: 0.02em;
  color: #cfc8b6;
  opacity: clamp(0, calc((0.24 - var(--p, 0)) / 0.14), 1);
}

.cue {
  position: relative;
  z-index: 3;
  justify-self: center;
  align-self: end;
  margin-bottom: max(2.2rem, env(safe-area-inset-bottom));
  width: 1px;
  height: 54px;
  background: rgba(242, 237, 224, 0.16);
  overflow: hidden;
  opacity: clamp(0, calc((0.14 - var(--p, 0)) / 0.1), 1);
}

.cue i {
  position: absolute;
  inset-inline: 0;
  top: -54px;
  height: 54px;
  background: linear-gradient(to bottom, transparent, #f2ede0);
  animation: cue 2.4s cubic-bezier(0.7, 0, 0.3, 1) infinite;
}

@keyframes cue {
  0% {
    transform: translateY(0);
  }

  60%,
  100% {
    transform: translateY(108px);
  }
}

/* ------------------------------------------------- render sequences ------ */

/* Both children are taken out of flow: with them in flow, `height: 100%` on the
   canvas resolves against a row that is itself sized by the canvas, and the
   browser falls back to the canvas's intrinsic (square) ratio. */
.seq {
  position: relative;
}

.seq>* {
  position: absolute;
  inset: 0;
}

.seq__canvas {
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.45s ease;
}

.seq.ready .seq__canvas {
  opacity: 1;
}

.seq.ready .seq__poster {
  opacity: 0;
}

.seq__poster {
  transition: opacity 0.45s ease 0.05s;
}

.seq__poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero .seq {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.hero .seq__canvas {
  display: block;
}

/* ------------------------------------------------------- sections -------- */

.sec {
  padding-block: var(--sec-y);
  position: relative;
}

/* The exploded view's own section has to stay flat: its frames carry that
   colour baked in, and a gradient behind the sticky stage would slide the seam
   into view. The step out of the hero's black happens in the statement above
   it instead. */
.sec--statement {
  background: linear-gradient(to bottom, var(--c0), var(--c2));
}

.inside {
  background: var(--c2);
}

.sec--spec {
  background: linear-gradient(to bottom, var(--c6), var(--c7));
}

.sec--get {
  background: linear-gradient(to bottom, var(--c7), var(--c75));
}

.foot {
  background: linear-gradient(to bottom, var(--c75), var(--c8));
}

/* Section heights are content-driven, so the joins between them land on
   fractional pixels. Each block's edge is then antialiased on its own and the
   body's near-black shows through the seam as a hairline — invisible while the
   ramp is still dark, a drawn line once it is a mid-tone, which is why it
   reads worst between the exploded view and the photographs. Every block
   overlaps the one above by a pixel; the two colours are identical there, so
   the overlap paints nothing that was not painted already. */
.sec--statement,
.inside,
.sec--modes,
.fade,
.sec--made,
.sec--spec,
.sec--get,
.foot {
  margin-top: -1px;
}

/* ------------------------------------------------------- crossover ------- */

/* The three blocks between the exploded view and the photographs are where the
   page changes side. Matching colours at each join is not enough: the eye reads
   a change of *rate* as an edge, so three straight ramps of different pitch
   would band, with the fade standing between them as its own stripe.
   The stops across all three are therefore one curve, sampled per block. It
   leaves the flat dark of .inside at a standstill, is fastest halfway down the
   fade, and eases out into .sec--spec — a slope in L* per 100px of
   0 -> 5.2 -> 7.5 -> 3.1 -> 0.8, continuous throughout.
   The four anchors are fixed by contrast, not by taste: c3 at the modes/fade
   join is as light as that section's dim type can sit on (3.1:1), and c4 at the
   fade/photographs join is as dark as the photographs' body type can sit on
   (4.9:1). Only the shape between them is free.
   Intermediate stops are literals on purpose: they are points on the c2-c6
   path, not palette entries of their own. */

.sec--modes {
  background: linear-gradient(to bottom,
      var(--c2) 0%,
      #211e17 12%,
      #231f19 25%,
      #26221b 38%,
      #2a271e 50%,
      #302c22 62%,
      #383327 75%,
      #403b2d 88%,
      var(--c3) 100%);
}

/* Tall on purpose: the same tonal step over a longer fall is a gentler one,
   and this is the one part of the page with nothing to read. */
.fade {
  height: clamp(12rem, 38vh, 26rem);
  background: linear-gradient(to bottom,
      var(--c3) 0%,
      #524b3a 12%,
      #5a5441 25%,
      #645d49 38%,
      #6e6751 50%,
      #777058 62%,
      #80785f 75%,
      #877f65 88%,
      var(--c4) 100%);
}

.sec--made {
  background: linear-gradient(to bottom,
      var(--c4) 0%,
      #958d73 12%,
      #9e967b 25%,
      #a59d83 38%,
      #aca489 50%,
      #b2aa8f 62%,
      #b6ae94 75%,
      #bab298 88%,
      var(--c6) 100%);
}


.say {
  margin: 0;
  font-size: clamp(2.1rem, 6.6vw, 5.2rem);
  font-weight: 250;
  letter-spacing: -0.036em;
  line-height: 1.02;
  text-wrap: balance;
}

.eyebrow {
  margin: 0 0 var(--title-gap);
  color: var(--dim);
}

/* Where the wrap is a grid, its row gap is already that distance, so the
   heading drops its margin instead of stacking a second one on top. */
.sec--spec>.wrap>.eyebrow,
.sec--get>.wrap>.eyebrow {
  margin-bottom: 0;
}

.cap {
  margin-top: 1rem;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.6875rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--dim);
}

/* ------------------------------------------------------------ inside ----- */

.inside__track {
  height: 300vh;
  position: relative;
}

.inside__stage {
  position: sticky;
  top: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-items: center;
  padding-block: 3rem;
}

.inside__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  column-gap: clamp(2rem, 5vw, 5rem);
  row-gap: var(--title-gap);
}

/* The title sits above both columns, where the other sections' eyebrows sit. */
.inside__grid>.eyebrow {
  grid-column: 1 / -1;
  margin: 0;
}

.inside__art {
  justify-self: center;
  width: 100%;
  max-width: 480px;
}

.inside__art .seq {
  aspect-ratio: 1000 / 1500;
  /* The render carries the section colour, but lossy encoding shifts a flat
     field by a level or two. A soft edge means that never reads as a seam. */
  -webkit-mask-image: radial-gradient(farthest-side at 50% 50%, #000 84%, transparent 100%);
  mask-image: radial-gradient(farthest-side at 50% 50%, #000 84%, transparent 100%);
}

.inside__art .seq__canvas {
  width: 100%;
  height: 100%;
}

.layers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  align-self: center;
}

.layers__i {
  display: grid;
  grid-template-columns: 2.6rem 1fr;
  grid-template-areas: "n t" ". d";
  gap: 0 1rem;
  padding: clamp(0.5rem, 1.4vw, 0.95rem) 0;
  border-top: 1px solid var(--rule);
  /* The script drives this from the scroll; these are the values for when it
     cannot (no JS, reduced motion), where the rows simply stand there. */
  opacity: 1;
}

/* The lens, the body and the base carry no number: they are what the numbered
   layers sit between, not steps of their own. */
.layers__i--plain .layers__t {
  font-size: clamp(0.95rem, 1.7vw, 1.15rem);
}

.layers__i--plain .layers__t,
.layers__i--plain .layers__d {
  color: var(--dim);
}

.layers__i:last-child {
  border-bottom: 1px solid var(--rule);
}

.layers__n {
  grid-area: n;
  color: var(--cu);
  align-self: baseline;
}

.layers__t {
  grid-area: t;
  font-size: clamp(1rem, 1.9vw, 1.3rem);
  font-weight: 300;
  letter-spacing: -0.02em;
}

.layers__d {
  grid-area: d;
  color: var(--dim);
  margin-top: 0.22rem;
}

/* ------------------------------------------------------------- photos ---- */

.grid-photos {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(1.2rem, 3.4vw, 3rem) clamp(1.2rem, 3vw, 2.6rem);
}

.ph {
  margin: 0;
}

/* The frames are the only hard rectangles left on a page of round parts. */
.ph img {
  width: 100%;
  border-radius: clamp(4px, 0.8vw, 10px);
}

.ph--1,
.ph--2,
.ph--3 {
  grid-column: span 2;
}

.ph--4,
.ph--5 {
  grid-column: span 3;
}

/* ------------------------------------------------------------- modes ----- */

.modes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2.2rem, 4vw, 3.4rem) clamp(1.5rem, 3vw, 2.6rem);
}

@media (min-width: 621px) {
  .modes {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 1000px) {
  .modes {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

.mode {
  text-align: center;
}

.mode__art {
  width: clamp(5rem, 12vw, 9.5rem);
  margin: 0 auto 1.3rem;
  color: var(--fg);
}

.mode__t {
  margin: 0;
  font-size: clamp(1rem, 1.8vw, 1.2rem);
  font-weight: 400;
  letter-spacing: -0.018em;
}

.mode__d {
  margin: 0.35rem 0 0;
  color: var(--dim);
  font-size: 0.9375rem;
}

.pict {
  width: 100%;
  height: auto;
  overflow: visible;
}

.pod-body {
  fill: none;
  stroke: currentColor;
  stroke-opacity: var(--pod-idle);
  stroke-width: 1.5;
}

.pod-led {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.14;
  stroke-width: 4.2;
}

.pod-led.on {
  stroke-opacity: 1;
}

.pod-led.f2 {
  stroke-opacity: 0.55;
}

.pod-led.f3 {
  stroke-opacity: 0.28;
}

.pod-led.warm {
  stroke: var(--cu);
}

.pod-led.green {
  stroke: var(--sig-green);
}

.pod-led.red {
  stroke: var(--sig-red);
}

.pod-led.p1 {
  stroke: var(--p1);
}

.pod-led.p2 {
  stroke: var(--p2);
}

.pod-led.p3 {
  stroke: var(--p3);
}

.pod-led.p4 {
  stroke: var(--p4);
}

.pict-path {
  stroke: currentColor;
  stroke-opacity: 0.34;
  stroke-width: 1.4;
  stroke-dasharray: 3 4;
  stroke-linecap: round;
}

.pict-head {
  fill: currentColor;
  fill-opacity: 0.5;
}

.modes__note {
  margin: clamp(2rem, 4vw, 3rem) auto 0;
  color: var(--dim);
  font-size: clamp(0.85rem, 1.2vw, 0.95rem);
  text-align: center;
  text-wrap: balance;
}

/* -------------------------------------------------------------- spec ----- */

/* Extra air where the photographs hand over to the drawings. Free tonally:
   the padding lengthens the section its gradient runs over, which makes that
   ramp gentler rather than steeper. */
.sec--spec {
  padding-top: calc(var(--sec-y) * 1.4);
}

.sec--spec .wrap {
  display: grid;
  gap: var(--title-gap);
}

/* The front view's drawing carries clear space above the circle worth 13% of its
   own height, so the shared gap reads far wider under this title than under the
   others. Spec takes that space back off the top. The box is square, so its
   height is --view-w and the whole correction follows the column: no magic
   number to retune when the layout or the wrap changes. */
.sec--spec .views {
  margin-top: calc(-0.13 * var(--view-w));
}

.sec--spec .spec__grid {
  margin-top: clamp(2rem, 4.6vw, 3.9rem);
}

.views {
  --views-gap: clamp(1.5rem, 4vw, 3.5rem);
  --view-w: calc((100% - 2 * var(--views-gap)) / 3);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--views-gap);
  align-items: end;
}

.view {
  margin: 0;
}

.view__box {
  position: relative;
  aspect-ratio: var(--ar, 1);
}

.view__box picture,
.view__box img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.view__dims {
  position: absolute;
  inset: 0;
  color: #2c2a20;
}

.view__dims svg {
  width: 100%;
  height: 100%;
}

.view .cap {
  margin-top: 0.7rem;
  text-align: center;
}

/* Drawn at three-up, so each view lands around 340 px wide: the annotations
   are sized in viewBox units to survive that reduction. */
.dim-line {
  stroke: currentColor;
  stroke-opacity: 0.55;
  stroke-width: 4.5;
}

.dim-ext {
  stroke: currentColor;
  stroke-opacity: 0.3;
  stroke-width: 3;
}

.dim-tick {
  fill: currentColor;
  fill-opacity: 0.55;
}

.dim-text {
  fill: currentColor;
  fill-opacity: 0.85;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 52px;
  letter-spacing: 0.06em;
}

.spec__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.spec__col .eyebrow {
  margin-bottom: 1.1rem;
}

.spec {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.spec tr {
  border-top: 1px solid var(--rule);
}

.spec tr:last-child {
  border-bottom: 1px solid var(--rule);
}

.spec th {
  text-align: left;
  font-weight: 400;
  color: var(--dim);
  padding: 0.68rem 1rem 0.68rem 0;
  white-space: nowrap;
  vertical-align: baseline;
}

.spec td {
  text-align: right;
  padding: 0.68rem 0;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  vertical-align: baseline;
}

/* --------------------------------------------------------------- get ----- */

.sec--get .wrap {
  display: grid;
  gap: var(--title-gap);
}

.gets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 2.4vw, 1.6rem);
}

.get {
  display: grid;
  align-content: center;
  gap: 0.45rem;
  min-height: clamp(6.5rem, 13vw, 9rem);
  padding: clamp(1.1rem, 3vw, 1.9rem);
  border: 1px solid var(--rule);
  text-decoration: none;
  transition: border-color 0.35s ease, background-color 0.35s ease;
}

.get:hover,
.get:focus-visible {
  border-color: color-mix(in srgb, currentColor 42%, transparent);
  background: color-mix(in srgb, currentColor 4%, transparent);
}

.get__t {
  font-size: clamp(1.1rem, 2.2vw, 1.5rem);
  font-weight: 300;
  letter-spacing: -0.02em;
}

.get__go {
  font-style: normal;
  color: var(--cu);
  margin-left: 0.25em;
  display: inline-block;
  transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.get:hover .get__go {
  transform: translate(0.12em, -0.12em);
}

.get:nth-child(2):hover .get__go {
  transform: translateY(0.16em);
}

.get__d {
  color: var(--dim);
}

/* ------------------------------------------------------------- footer ---- */

.foot {
  padding-block: clamp(4rem, 10vh, 8rem) clamp(2.5rem, 6vh, 4rem);
}

.foot__grid {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: end;
  gap: clamp(1.4rem, 4vw, 3rem);
}

.foot__mark {
  margin: 0;
  width: clamp(8rem, 19vw, 11.5rem);
}

.foot__note {
  margin: 0;
  color: var(--dim);
  font-size: 0.9375rem;
}

/* ------------------------------------------------------------ reveal ----- */

.reveal {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition:
    opacity 0.95s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--i, 0) * 85ms),
    transform 0.95s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--i, 0) * 85ms);
}

.reveal.in {
  opacity: 1;
  transform: none;
}

.no-js .reveal {
  opacity: 1;
  transform: none;
}

.no-js .seq__canvas {
  display: none;
}

.no-js .bar {
  opacity: 1;
  pointer-events: auto;
}

.no-js .layers__i {
  opacity: 1;
}

.no-js .wordmark,
.no-js .hero__sub,
.no-js .cue {
  opacity: 1;
}

/* ---------------------------------------------------------- lightbox ----- */

.ph__zoom {
  display: block;
  cursor: zoom-in;
  border-radius: clamp(4px, 0.8vw, 10px);
}

.ph__zoom:focus-visible {
  outline: 2px solid var(--cu);
  outline-offset: 4px;
}

/* The dialog's own box is the viewport; the padding inside it is what keeps
   the photograph off the edges. Sized here because a modal <dialog> defaults
   to shrink-to-fit with a margin, not to the full screen. */
.lb {
  cursor: zoom-out;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}

.lb::backdrop {
  background: rgba(5, 5, 5, 0.92);
}

.lb__fig {
  --lb-pad: clamp(0.75rem, 3vw, 3rem);
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: var(--lb-pad);
}

/* Dropped from the box tree so the image is the grid item itself, rather than
   a wrapper sized to the photograph with the image loose inside it. */
.lb__fig picture {
  display: contents;
}

/* The intrinsic width and height ride along on the clone, so both axes have to
   be released before the photograph will fit itself to the screen. The height
   is capped against the viewport, not the parent: the row the image sits in is
   content-sized, so a percentage there measures the photograph against itself
   and holds nothing back. dvh so a phone's retracting toolbar cannot crop it. */
.lb__fig img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: calc(100vh - 2 * var(--lb-pad));
  max-height: calc(100dvh - 2 * var(--lb-pad));
  border-radius: clamp(4px, 0.8vw, 10px);
}

.lb__x {
  position: absolute;
  top: clamp(0.5rem, 2vw, 1.4rem);
  right: clamp(0.5rem, 2vw, 1.4rem);
  z-index: 2;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(242, 237, 224, 0.1);
  color: var(--fg);
  cursor: pointer;
}

.lb__x:hover {
  background: rgba(242, 237, 224, 0.2);
}

.lb__x:focus-visible {
  outline: 2px solid var(--cu);
  outline-offset: 3px;
}

.lb__x::before,
.lb__x::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1.05rem;
  height: 1.5px;
  background: currentColor;
}

.lb__x::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.lb__x::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

/* -------------------------------------------------------- responsive ----- */

@media (max-width: 860px) {

  /* Stacked, the drawing and its eight-row key have to share one screen: the
     sticky stage is taller than the viewport otherwise and the last rows are
     never reachable. */
  .inside__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1.1rem, 4vw, 1.6rem);
  }

  .inside__stage {
    align-content: center;
    padding-block: 1.5rem;
  }

  .inside__art {
    max-width: min(62vw, 230px);
  }

  .layers__i {
    padding-block: 0.38rem;
  }

  .layers__t {
    font-size: 1rem;
  }

  .layers__d {
    font-size: 0.625rem;
    margin-top: 0.15rem;
  }

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

  .foot__grid {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
  }
}

/* On a short phone the drawing gives up height before the key does. */
@supports (height: 100svh) {
  @media (max-width: 860px) {
    .inside__art {
      max-width: min(62vw, 230px, calc((100svh - 36.5rem) / 1.5));
    }
  }

  @media (max-width: 620px) {
    .inside__art {
      max-width: min(66vw, 230px, calc((100svh - 23.5rem) / 1.5));
    }
  }
}

@media (max-width: 820px) {
  .views {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 6vw, 3rem);
  }

  .view {
    max-width: 30rem;
    margin-inline: auto;
  }

  /* Stacked, a view is the column wide until it hits its own 30rem cap, and
     the correction follows it either way. */
  .views {
    --view-w: min(30rem, 100%);
  }
}

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

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

  .ph--1,
  .ph--2,
  .ph--3,
  .ph--4,
  .ph--5 {
    grid-column: auto;
  }

  .hero__track {
    height: 300vh;
  }

  .inside__track {
    height: 260vh;
  }

  /* One line per layer: the detail moves alongside the name instead of under
     it, which is what lets all eight rows share the screen with the drawing. */
  .layers__i {
    grid-template-columns: 2.1rem auto 1fr;
    grid-template-areas: "n t d";
    align-items: baseline;
    gap: 0 0.6rem;
    padding-block: 0.34rem;
  }

  .layers__d {
    margin-top: 0;
  }

  .spec th {
    white-space: normal;
  }

  /* five items in two columns: let the last one have the full row */
  .mode:last-child {
    grid-column: 1 / -1;
  }

  .mode:last-child .mode__art {
    width: clamp(5rem, 26vw, 7rem);
  }
}

/* --------------------------------------------------- reduced motion ------ */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
  }

  .cue {
    display: none;
  }

  .hero__track {
    height: 100svh;
  }

  .inside__track {
    height: auto;
  }

  .inside__stage {
    position: static;
    min-height: 0;
    padding-block: var(--sec-y);
  }

  .wordmark,
  .hero__sub {
    opacity: 1 !important;
    transform: none !important;
  }

  .layers__i {
    opacity: 1;
  }

  .seq__canvas {
    display: none;
  }

  .seq__poster {
    opacity: 1 !important;
  }
}

@media (prefers-contrast: more) {
  :root {
    --dim: #cfc8b6;
    --rule: rgba(242, 237, 224, 0.3);
  }

  .on-light {
    --dim: #2c2a21;
    --rule: rgba(23, 22, 17, 0.34);
  }
}