:root {
  /* palette */
  --white: #ffffff;
  --black: #000000;
  --grey-950: #1f2326;   /* the drawing sheet's frame and furniture: ink, a shade lighter */
  --grey-900: #2f373c;   /* small text on paper */
  --grey-500: #8f979c;   /* construction lines, labels */
  --grey-450: #7d868c;   /* construction lines on the dark ground */
  --grey-300: #a3abb0;   /* small text on the dark ground */
  --ink-05: rgba(16, 19, 21, 0.05);
  --ink-06: rgba(16, 19, 21, 0.06);
  --ink-09: rgba(16, 19, 21, 0.09);
  --ink-11: rgba(16, 19, 21, 0.11);
  --paper-05: rgba(255, 255, 255, 0.05);
  --paper-06: rgba(255, 255, 255, 0.06);
  --paper-09: rgba(255, 255, 255, 0.09);
  --paper-11: rgba(255, 255, 255, 0.11);

  /* roles: paper, white ground and black ink (default) */
  --bg: var(--white);
  --fg: var(--black);
  --line: var(--grey-500);
  --grid: var(--ink-06);          /* the intro's grid */
  --paper-grid: var(--ink-05);    /* the contact sheet's paper */
  --paper-grid-major: var(--ink-09);
  --sheet: var(--grey-950);       /* the sheet's frame, zones and title block */
  --slogan: var(--grey-900);
  --small: clamp(11px, 0.85vw, 13px);   /* corner mark, contact link */
  --slogan-gap: clamp(27px, 4.25vw, 59px);
  --slogan-size: clamp(18px, 1.55vw, 26px);   /* the formula */
  --tagline-size: clamp(14px, 1.3vw, 22px);   /* the negative's line: a sentence, set smaller than the formula */
  --mono: 'Google Sans Code', 'SF Mono', 'JetBrains Mono', 'Fira Code', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}

/* space: black ground, white ink (?theme=dark) */
:root[data-theme="dark"] {
  --bg: var(--black);
  --fg: var(--white);
  --line: var(--grey-450);
  --grid: var(--paper-06);
  --paper-grid: var(--paper-05);
  --paper-grid-major: var(--paper-09);
  --sheet: #e2e5e7;
  --slogan: var(--grey-300);
}

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  overflow: hidden;
}

/* The surprise: a while after the intro the whole page inverts for a few
   seconds and back. One filter on body flips canvas, logo, type and vignette
   together, so the dark moment is the exact negative of the paper look. */
body {
  transition: filter 2s ease-in-out;
}

body.flip {
  filter: invert(1) brightness(1.5);
}

body {
  font-family: var(--mono);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- layers, back to front ------------------------------------------ */

/* Breathing: once the intro is done, the stage (field + logo) zooms in very
   slowly about the infinity's centre with a slight drift left, then eases
   back, on a 40 s cycle. The origin is set by field.js from the logo's
   measured position. Delete the animation line to turn it off. */
#stage {
  position: fixed;
  inset: 0;
  transform-origin: var(--ox, 50%) var(--oy, 50%);
  will-change: transform;
}

body.done #stage {
  animation: breathe 40s ease-in-out infinite;
}

@keyframes breathe {
  0%, 100% { transform: scale(1) translateX(0); }
  50%      { transform: scale(1.12) translateX(-2.5vw); }
}

@media (prefers-reduced-motion: reduce) {
  body.done #stage { animation: none; }
}

#field {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
}

#grid {
  position: fixed;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background-image:
    linear-gradient(to right,  var(--grid) 0.6px, transparent 0.6px),
    linear-gradient(to bottom, var(--grid) 0.6px, transparent 0.6px);
  background-size: 48px 48px;
  background-position: center center;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 46%, #000 20%, transparent 100%);
          mask-image: radial-gradient(ellipse 70% 70% at 50% 46%, #000 20%, transparent 100%);
}

/* Edge fade. The softening of streaks near the frame is done in field.js
   (wider, dimmer strokes); a backdrop blur here cost half the frame rate. */
#edge {
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 82% 82% at 50% 50%, transparent 48%, color-mix(in srgb, var(--bg) 55%, transparent) 80%, color-mix(in srgb, var(--bg) 90%, transparent) 100%);
}

/* The lift that puts the logo-plus-slogan group at the optical centre sits
   here, not on the logo, so the logo's only transform is the trip's zoom
   and its layout box (which transforms never touch) is what field.js
   measures.

   A share of the height, so the mark lands in the same place ratio-wise on
   every screen: 46.5% of the frame, a little above the middle but not much.
   It was half the slogan block once, which centres the logo-plus-slogan
   group — but the block is clamped in pixels and the frame is not, so on a
   phone, where the gap and the slogan sit at their minimums while the
   viewport is as tall as a laptop's, the same lift was a smaller share of
   the frame and the mark read low. Then it was the larger of the two, which
   left short windows on the old rule and tall ones on the new; one rule is
   easier to trust and easier to tune. dvh, not vh: a phone's vh is the
   height with the browser toolbar hidden, and this layer is inset: 0, which
   is the height with it showing. */
main {
  --lift: -3.5dvh;
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  transform: translateY(var(--lift));
  pointer-events: none;
}

/* Narrow frames. Both type tokens sit at their clamp minimums here — a phone
   is nowhere near the vw terms — and those minimums, set for a laptop's
   smallest window, read heavy against a 240px mark. Both lines come down
   about a tenth. The mark comes up a little with them: lighter type under it
   leaves more empty paper below, which makes it read low. A width query, not
   a pointer one, because what is small is the frame; and after the rules it
   overrides, since they carry the same weight and the last one wins. */
@media (max-width: 700px) {
  :root {
    --slogan-size: 16px;
    --tagline-size: 12.5px;
  }

  main {
    --lift: -4.3dvh;
  }
}

/* ---- logo ------------------------------------------------------------- */

/* The logo sits at the optical centre: the logo-plus-slogan group is centred,
   so the logo itself rides half a slogan above true centre. */
#logo {
  position: relative;
  margin: 0;
  width: clamp(240px, 34vw, 620px);
  /* the warp zooms the logo into the C's hole (543/948 across, half down) */
  transform-origin: 57.28% 50%;
  transition: transform 0.9s cubic-bezier(0.6, 0, 1, 0.4), opacity 0.6s ease-in 0.15s;
  pointer-events: auto;
}

/* Leaving for the contact sheet: the logo (slogan with it) is swallowed by
   its own infinity while it fades. Coming back it eases in from the same. */
body.away #logo {
  transform: scale(3.4);
  opacity: 0;
  pointer-events: none;
}

body.returning #logo {
  transition: transform 1.2s cubic-bezier(0.1, 0.7, 0.2, 1), opacity 0.9s ease-out;
}


#logo svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

#construction {
  stroke: var(--line);
  opacity: 0;
  transition: opacity 0.7s ease;
}

#construction text {
  fill: var(--line);
}

#strokes {
  stroke: var(--fg);
}

#final {
  fill: var(--fg);
}

#strokes .s {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

#final {
  opacity: 0;
}

.c-draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

/* ---- slogan ----------------------------------------------------------- */

/* Absolutely placed so it does not pull the logo off dead centre. */
/* Centred on the logo's middle rather than laid across its width: the box
   is only as wide as the line. text-align on a full-width box centres a line
   that fits, but a line wider than the box is not centred at all — Chrome
   puts the whole overflow on the end side, so the negative's sentence (284px
   against a 240px logo on a phone) sat 22px right of the mark. */
#slogan {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 100vw;
  top: calc(100% + var(--slogan-gap));
  margin: 0;
  text-align: center;
  color: var(--slogan);
  font-size: var(--slogan-size);
  font-weight: 500;
  letter-spacing: 0.1em;
  line-height: 1.4;
  min-height: 1.4em;
  white-space: pre;
}

/* The caret hangs off the right of the text span, so it takes no part in
   centring and sits on the text's own baseline. */
#slogan-line {
  position: relative;
  display: inline-block;
}

/* the exponent and the infinity, sized without changing the line box */
#slogan sup {
  font-size: 0.6em;
  line-height: 0;
  vertical-align: 0.6em;
  letter-spacing: 0.04em;
}

#slogan .inf {
  font-size: 1.45em;
  line-height: 0;
  vertical-align: -0.06em;
}

#caret {
  position: absolute;
  left: calc(100% + 0.12em);
  /* On the middle of the ink, not of the line box: glyphs sit on the
     baseline with the line's leading above them, so the box's middle is
     high. Measured against the rendered ink, the caret was 0.13em above the
     formula's middle and 0.16em above the sentence's; this splits them,
     leaving the formula 0.8px low and the sentence 0.3px high. */
  top: calc(50% + 0.045em);
  width: 0.55em;
  height: 0.95em;
  transform: translateY(-50%);
  background: var(--slogan);
  opacity: 0;
}

/* The negative's line is a sentence where the paper's is a formula: 29
   characters against 15, and 20.3em wide with the slogan's tracking. So it
   is set about 30% smaller than the formula at every width — enough that it
   reads as a caption rather than a second headline, and that it stays on one
   line on the narrowest phone (284px of a 360px screen). The swap is
   invisible: the class turns over while the slogan is erased. */
body.flip #slogan {
  font-size: var(--tagline-size);
}

body.typing #caret {
  animation: blink 1s steps(2, start) infinite;
}

@keyframes blink {
  to { opacity: 1; }
}

/* ---- corner mark ------------------------------------------------------ */

/* Both corner marks sit a little higher on touch screens, clear of the
   gesture bar: Android reports no safe-area inset for it, so a fixed lift
   plus half of whatever inset the phone does report. */
:root {
  --corner-bottom: clamp(12px, 1.8vw, 24px);
}

@media (hover: none), (pointer: coarse) {
  :root {
    --corner-bottom: calc(26px + env(safe-area-inset-bottom, 0px) / 4);
  }
}

#corner {
  position: fixed;
  left: clamp(16px, 2vw, 28px);
  bottom: var(--corner-bottom);
  margin: 0;
  color: var(--fg);
  font-size: var(--small);
  letter-spacing: 0.08em;
  opacity: 0;
  transition: opacity 1.2s ease;
  pointer-events: none;
  user-select: text;
}

body.done #corner { opacity: 1; }

/* the way in to the contact sheet: bottom right, the corner mark's mirror,
   on a faint tinted box so it reads as a button (as plain text nobody knew
   it was one) */
#contact-link {
  position: fixed;
  right: clamp(16px, 2vw, 28px);
  bottom: calc(var(--corner-bottom) - 0.3em);   /* the box's padding, so the text stays on the corner mark's line */
  padding: 0.4em 0.9em;
  color: var(--fg);
  font-size: var(--small);
  letter-spacing: 0.12em;
  line-height: 1;
  text-decoration: none;
  opacity: 0;
  transition: opacity 1.2s ease, color 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
  pointer-events: none;
  background-color: var(--ink-06);
}

body.done #contact-link {
  opacity: 1;
  pointer-events: auto;
}

#contact-link:hover,
#contact-link:focus-visible {
  color: var(--bg);
  background: var(--fg);
  border-color: var(--fg);
  outline: none;
}

body.busy #contact-link,
body.contact #contact-link {
  opacity: 0;
  pointer-events: none;
  transition-duration: 0.3s;
}

/* ---- contact sheet ---------------------------------------------------- */

/* Engineering paper across the whole screen, fading to plain white toward
   the edges, and on it a white drawing sheet laid out after ISO 5457: a
   frame that draws itself, grid reference zones (1–4 across, A–E down) in
   the margin, centring marks on the four edges, a title block bottom right. Labels in the same small caps as
   the construction drawing, ruled lines to write on. The sheet's own
   furniture (frame, zones, title block) is ink a shade lighter; the form's
   text and button are full ink. */
#contact {
  position: fixed;
  inset: 0;
  z-index: 4;
  display: grid;
  place-items: center;
  /* room for the small logo above; less below */
  padding: clamp(64px, 8vh, 80px) 4vw clamp(16px, 6vh, 56px);
  box-sizing: border-box;
  overflow: auto;
  background: var(--bg);
  opacity: 0;
  transition: opacity 0.6s ease-out;   /* quick, so the sheet is seen while it is still moving fast */
  user-select: text;
}

#contact[hidden] { display: none; }

body.contact #contact { opacity: 1; }
body.back #contact { opacity: 0; transition: opacity 0.5s ease-in; }

/* the paper: a fine grid with a heavier line every fifth, fading out
   toward the edges like the intro's grid */
#contact::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right,  var(--paper-grid-major) 0.6px, transparent 0.6px),
    linear-gradient(to bottom, var(--paper-grid-major) 0.6px, transparent 0.6px),
    linear-gradient(to right,  var(--paper-grid) 0.6px, transparent 0.6px),
    linear-gradient(to bottom, var(--paper-grid) 0.6px, transparent 0.6px);
  background-size: 120px 120px, 120px 120px, 24px 24px, 24px 24px;
  background-position: center center;
  /* fades from the middle outward, gone well before the edges */
  -webkit-mask-image: radial-gradient(ellipse 66% 66% at 50% 48%, #000 0%, rgba(0, 0, 0, 0.6) 45%, transparent 100%);
          mask-image: radial-gradient(ellipse 66% 66% at 50% 48%, #000 0%, rgba(0, 0, 0, 0.6) 45%, transparent 100%);
}

.sheet {
  --rule: color-mix(in srgb, var(--fg) 22%, transparent);   /* the ruled lines to write on */
  --margin: 18px;   /* the zone strip between the sheet edge and the frame */
  --pad-x: clamp(36px, 3.8vw, 46px);
  position: relative;
  width: min(480px, 100%);
  container-type: inline-size;   /* so the A4 cap below can follow the real width */
  margin: 0;
  /* the vertical paddings give way on a short window; SEND and the title
     block share the bottom band, side by side */
  padding: clamp(26px, 5vh, 46px) var(--pad-x) clamp(26px, 7vh, 64px);
  box-sizing: border-box;
  background-color: var(--bg);
  /* it comes toward you: from 35% over 0.95 s, fast at first and easing out
     as it arrives, so at the handover it moves at something like the speed
     of the last streaks fading behind it (40% over 1.2 s started too small;
     1.6 s with a soft curve was too smooth) */
  transform: scale(0.42);
  transition: transform 0.95s cubic-bezier(0.16, 0.9, 0.4, 1);
}

/* the frame proper, inside the zone strip */
.sheet::before {
  content: '';
  position: absolute;
  inset: var(--margin);
  border: 1px solid var(--sheet);
  pointer-events: none;
}

/* grid reference zones in the strip: numbers across, letters down */
.zone {
  position: absolute;
  display: flex;
  font-size: 8px;
  letter-spacing: 0.1em;
  color: var(--sheet);
  pointer-events: none;
}

.zone span {
  flex: 1;
  display: grid;
  place-items: center;
}

.zone.top,
.zone.bottom { left: var(--margin); right: var(--margin); height: var(--margin); }
.zone.top { top: 0; }
.zone.bottom { bottom: 0; }
.zone.top span + span,
.zone.bottom span + span { border-left: 1px solid var(--sheet); }

.zone.left,
.zone.right { top: var(--margin); bottom: var(--margin); width: var(--margin); flex-direction: column; }
.zone.left { left: 0; }
.zone.right { right: 0; }
.zone.left span + span,
.zone.right span + span { border-top: 1px solid var(--sheet); }

/* the frame draws itself, as the construction did */
.sheet .frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  fill: none;
  stroke: var(--sheet);
  stroke-width: 1;
}

.sheet .frame rect {
  x: 0.5px;
  y: 0.5px;
  width: calc(100% - 1px);
  height: calc(100% - 1px);
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 0.9s linear 0.2s;
}

body.contact .sheet { transform: none; }
body.contact .sheet .frame rect { stroke-dashoffset: 0; }

/* leaving (back or send): the sheet recedes, and the stars carry on the motion */
body.back .sheet {
  transform: scale(0.7);
  transition: transform 0.5s ease-in;
}

.sheet-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.sheet h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.3em;
  color: var(--fg);
}

.sheet-meta,
.fld .lbl,
.title-block {
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--fg);
}

.sheet-meta,
.title-block { color: var(--sheet); }

.fld {
  display: block;
  margin-top: clamp(14px, 3vh, 30px);
}

.fld .lbl {
  display: block;
  margin-bottom: 6px;
}

.fld input,
.fld textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 4px 0;
  border: 0;
  border-bottom: 1px solid var(--fg);
  border-radius: 0;
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.7;
  letter-spacing: 0.02em;
  outline: none;
  transition: border-color 0.3s ease;
}

.fld input::placeholder,
.fld textarea::placeholder { color: var(--line); opacity: 0.6; }

.fld input:focus,
.fld textarea:focus { border-bottom-color: var(--fg); }

/* ruled paper: one line per row of text, scrolling with it */
.fld textarea {
  resize: none;
  /* the message takes the room the viewport gives it (height, not
     min-height, so it can go under its rows). The sheet's other rows and
     the section's paddings scale a little with the window themselves, so
     this is 92vh less a constant. The cap keeps the sheet no taller than
     an A4 is to its width (1:√2), whatever the width: √2 times the sheet's
     own width (100cqw is its content box, so the side padding is added
     back), less the other rows. */
  height: clamp(6em, calc(92vh - 350px), calc(1.414 * (100cqw + 2 * var(--pad-x)) - 325px));
  padding-top: 0;
  border-bottom: 0;   /* the last rule is the line */
  line-height: 1.7em;
  background-image: repeating-linear-gradient(to bottom, transparent 0, transparent calc(1.7em - 1px), var(--rule) calc(1.7em - 1px), var(--rule) 1.7em);
  background-attachment: local;
}

/* The honeypot (SPEC §9.3). Off the left of the sheet rather than
   display:none or hidden, both of which a careful bot skips: this one is in
   the layout, has a name worth filling in, and no person will ever see it.
   Overflow to the left is not scrollable, so it adds no scrollbar. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.fld-error {
  min-height: 1.4em;
  margin: 10px 0 0;
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--slogan);
}

.sheet-foot {
  margin-top: clamp(8px, 2vh, 18px);
}

.sheet button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.22em;
  padding: 10px 20px;
  border: 1px solid var(--fg);
  border-radius: 0;
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.sheet button svg,
#back svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

#back svg { width: 12px; height: 12px; }

/* the arrow shoots off: four lengthening dashes, then the shaft. The word
   is there for screen readers (.sr-only); bring it back by removing that
   class. While a message is in flight the button greys out and the arrow
   gives way to a turning dashed circle, a construction circle on the go. */
.sheet button svg { width: 56px; height: 14px; }

.sheet button:disabled { opacity: 0.45; cursor: default; }

.sheet button .spin {
  display: none;
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  border: 1.4px dashed currentColor;
  border-radius: 50%;
  animation: spin 1.4s linear infinite;
}

.sheet button.sending svg { display: none; }
.sheet button.sending .spin { display: block; }

@keyframes spin {
  to { transform: rotate(360deg); }
}

.sheet button:hover,
.sheet button:focus-visible {
  background: var(--fg);
  color: var(--bg);
  outline: none;
}

/* the drawing's title block, in the frame's bottom right corner */
.title-block {
  position: absolute;
  right: var(--margin);
  bottom: var(--margin);
  display: grid;
  grid-template-columns: auto auto;
  border-top: 1px solid var(--sheet);
  border-left: 1px solid var(--sheet);
  font-size: 8px;
  letter-spacing: 0.14em;
  white-space: nowrap;
  pointer-events: none;
}

.title-block span {
  padding: 4px 8px;
  border-right: 1px solid var(--sheet);
  border-bottom: 1px solid var(--sheet);
}

#back,
#mini-logo {
  position: fixed;
  top: clamp(14px, 2vw, 28px);
  color: var(--slogan);
  text-decoration: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  -webkit-user-select: none;
          user-select: none;
}

#back {
  left: clamp(14px, 2vw, 28px);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: clamp(11px, 0.85vw, 13px);
  letter-spacing: 0.12em;
}

#mini-logo {
  left: 50%;
  width: clamp(72px, 6.75vw, 112px);
  transform: translateX(-50%);
}

#mini-logo svg {
  display: block;
  width: 100%;
  height: auto;
  fill: var(--fg);
}

/* on a very short window the sheet needs the top; the logo goes */
@media (max-height: 520px) {
  #mini-logo { display: none; }
}

body.contact #back { opacity: 0.6; transition: opacity 0.8s ease 0.8s; }
body.contact #mini-logo { opacity: 0.9; transition: opacity 1s ease 1s; }
body.contact #back:hover,
body.contact #back:focus-visible { opacity: 1; transition-delay: 0s; outline: none; }
body.contact #mini-logo:hover { opacity: 1; transition-delay: 0s; }

/* ---- the toast ------------------------------------------------------- */

/* Ink on paper (the negative inverts it with everything else), bottom
   centre where the eye already is after SEND. It rises from below the edge
   and later goes back the same way. */
#toast {
  position: fixed;
  left: 50%;
  bottom: calc(clamp(22px, 4vh, 40px) + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  max-width: calc(100vw - 32px);
  padding: 12px 18px;
  box-sizing: border-box;
  background: var(--fg);
  color: var(--bg);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-align: center;
  transform: translate(-50%, calc(100% + 60px));
  opacity: 0;
  pointer-events: none;
  transition: transform 0.45s ease-in, opacity 0.35s ease-in;
}

#toast.show {
  transform: translate(-50%, 0);
  opacity: 1;
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1), opacity 0.4s ease-out;
}

/* ---- after the intro -------------------------------------------------- */

body.done #strokes,
body.done #grid { display: none; }

body.done #final,
body.done #field { opacity: 1; }

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

@media (prefers-reduced-motion: reduce) {
  #strokes, #grid { display: none; }
  #final, #field { opacity: 1; }
  #logo, body.returning #logo { transition: opacity 0.4s ease; }
  body.away #logo { transform: none; }
  .sheet, body.back .sheet { transform: none; transition: none; }
  .sheet .frame rect { transition: none; }
  .sheet button .spin { animation: none; }
  #toast { transition: opacity 0.3s ease; transform: translate(-50%, 0); }
}
