/* emircelet.com — dark-first, engineering-notebook restraint.
   One accent (emerald). Mono for every measured value. */

:root {
  --ink: #e6eae8;
  --ink-2: #a2acA8;
  --ink-3: #6e7a76;
  --bg: #070a09;
  --bg-2: #0e1412;
  --bg-3: #151d1a;
  --line: #1e2a26;
  --line-2: #2b3a34;
  --accent: #35c98d;
  --accent-dim: #229268;
  --grid: rgba(53, 201, 141, 0.055);
  --grid-major: rgba(53, 201, 141, 0.095);
  --blueprint: rgba(53, 201, 141, 0.21);

  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter,
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono",
    "Roboto Mono", Menlo, Consolas, monospace;

  --measure: 34rem;
  --pad: clamp(1.25rem, 4vw, 3rem);
  --gap: clamp(2.5rem, 7vw, 6rem);
}

:root[data-theme="light"] {
  --ink: #101614;
  --ink-2: #465350;
  --ink-3: #74817d;
  --bg: #f8faf9;
  --bg-2: #eef2f0;
  --bg-3: #e3e9e6;
  --line: #d7dedb;
  --line-2: #bcc7c3;
  --accent: #0d7a55;
  --accent-dim: #0a6144;
  --grid: rgba(7, 92, 63, 0.075);
  --grid-major: rgba(7, 92, 63, 0.125);
  --blueprint: rgba(7, 92, 63, 0.30);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ink: #101614;
    --ink-2: #465350;
    --ink-3: #74817d;
    --bg: #f8faf9;
    --bg-2: #eef2f0;
    --bg-3: #e3e9e6;
    --line: #d7dedb;
    --line-2: #bcc7c3;
    --accent: #0d7a55;
    --accent-dim: #0a6144;
    --grid: rgba(7, 92, 63, 0.075);
  --grid-major: rgba(7, 92, 63, 0.125);
  --blueprint: rgba(7, 92, 63, 0.30);
  }
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* Engineering paper: fine 28px grid with a heavier line every fifth division. */
body {
  margin: 0;
  background-color: var(--bg);
  background-image:
    linear-gradient(var(--grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-major) 1px, transparent 1px),
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 140px 140px, 140px 140px, 28px 28px, 28px 28px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

/* ---------- decorative blueprint backdrop ---------- */

.blueprint {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  color: var(--blueprint);
  /* Two dials. --conf wakes the drawing over fifteen seconds but only to a level
     that stays behind the writing; --bold is the loud setting and is only reached
     after three minutes on the page or on reaching the bottom of it. No transition
     here — both values already step in small increments, and a transition on top
     restarts on every tick and pins the property at its start value. */
  opacity: calc(0.30 + 0.22 * var(--conf, 0) + 0.48 * var(--bold, 0));
}

/* nothing runs in a tab nobody is looking at */
.bp-idle, .bp-idle * { animation-play-state: paused !important; }
.blueprint .bp { position: absolute; height: auto; }
.bp-tr { top: -6vh;  right: -8vw;  width: min(46vw, 620px); }
.bp-bl { bottom: 6vh; left: -6vw;  width: min(34vw, 440px); }
.bp-mr { top: 46vh;  right: 2vw;   width: min(15vw, 190px); opacity: .75; }
.bp-pipe { top: 4vh; left: 3vw; height: 92vh; width: auto; }

/* sheet furniture */
.bp-sheet, .bp-zones {
  inset: 1.2vh 1vw; width: 98vw; height: 97.6vh;
  opacity: calc(0.3 + 0.35 * var(--bold, 0));
}
.bp-title {
  right: 1.6vw; bottom: 2.4vh; width: min(19vw, 250px); opacity: .85;
}

.blueprint svg { stroke-width: calc(0.95 + 0.4 * var(--bold, 0)); }
.bp-t { font-family: var(--mono); font-size: 13px; fill: currentColor; stroke: none; }
.bp-t-sm { font-size: 11px; opacity: .8; }

/* ---- scroll-reactive motion -------------------------------------------
   --sp is 0 at the top of the page and 1 at the bottom, set in base.html.
   Everything below is a pure function of it: no timers, no easing, so the
   drawing responds directly to the reader rather than animating at them. */

/* rotation, all about each drawing's own centre */
.spin-cw        { transform: rotate(calc(var(--sp, 0) * 320deg)); }
.spin-cw-slow   { transform: rotate(calc(var(--sp, 0) * 130deg)); }
.spin-ccw-slow  { transform: rotate(calc(var(--sp, 0) * -95deg)); }
.spin-cw, .spin-cw-slow, .spin-ccw-slow {
  transform-origin: center; transform-box: fill-box;
}

/* assembles itself as the reader descends */
.draw {
  stroke-dasharray: 1600;
  stroke-dashoffset: calc(1600 * (1 - clamp(0, var(--sp, 0) * 2.2, 1)));
}

/* dashes travelling along a path: current in a loom, chain on a sprocket,
   airflow over a wing. Continuous, so there is life on a static screen. */
@keyframes bp-flow { to { stroke-dashoffset: -240; } }
.flow { animation: bp-flow 3s linear infinite; }

@keyframes bp-pulse { 0%, 100% { opacity: .12; } 50% { opacity: 1; } }
.pulse { animation: bp-pulse 2.6s ease-in-out infinite; }

/* Anything with a --t threshold fades in when the reader passes that point.
   Used for the assembly crowd, network nodes and plotted data points. */
.stagger { opacity: clamp(0, calc((var(--sp, 0) - var(--t, 0)) * 9), 1); }

/* a full-bleed composition rather than a corner vignette */
.bp-full { inset: 0; width: 100%; height: 100%; }

/* flux lines fade up as the motor spins faster */
.flux { opacity: calc(0.25 + var(--sp, 0) * 0.75); }

/* rack driven out by its pinion */
.rack { transform: translateX(calc(var(--sp, 0) * -26px)); }

.kart-drive { transform: translateX(calc(var(--sp, 0) * 15vw)); }

.climb {
  transform: translateY(calc(560px - var(--sp, 0) * 545px));
  transform-box: view-box;
}

.fly {
  transform: translate(calc(var(--sp, 0) * 7vw), calc(var(--sp, 0) * -8vh))
             rotate(calc(var(--sp, 0) * -10deg));
}

/* ---- per-theme motion ---- */

/* stage: curtains part, beams sweep */
.curtain-l { transform: translateX(calc(var(--sp, 0) * -92px)); }
.curtain-r { transform: translateX(calc(var(--sp, 0) * 92px)); }
.beam { transform-origin: top center; transform-box: fill-box; }
.beam-1 { transform: rotate(calc(-8deg + var(--sp, 0) * 14deg)); }
.beam-2 { transform: rotate(calc(6deg - var(--sp, 0) * 12deg)); }
.beam-3 { transform: rotate(calc(-4deg + var(--sp, 0) * 10deg)); }

/* growth: the terminating flatline draws only after the curve has climbed */
.draw2 {
  stroke-dasharray: 120;
  stroke-dashoffset: calc(120 * (1 - clamp(0, (var(--sp, 0) - 0.55) * 4, 1)));
}
.synapse { stroke-dasharray: 90; stroke-dashoffset: calc(90 * (1 - clamp(0, var(--sp, 0) * 1.8, 1))); }

/* launch: the vehicle climbs its trajectory and sheds stages */
.rocket {
  transform: translate(calc(110px + var(--sp, 0) * 730px),
                       calc(500px - var(--sp, 0) * 452px))
             rotate(calc(-6deg + var(--sp, 0) * 42deg));
  transform-box: fill-box;
  transform-origin: center;
}
.stage-1 {
  transform: translate(calc(240px + var(--sp, 0) * 150px), calc(430px + var(--sp, 0) * 190px))
             rotate(calc(var(--sp, 0) * 120deg));
  opacity: clamp(0, calc((var(--sp, 0) - 0.28) * 5), 0.8);
  transform-box: fill-box;
  transform-origin: center;
}
.stage-2 {
  transform: translate(calc(470px + var(--sp, 0) * 130px), calc(300px + var(--sp, 0) * 240px))
             rotate(calc(var(--sp, 0) * -150deg));
  opacity: clamp(0, calc((var(--sp, 0) - 0.55) * 5), 0.8);
  transform-box: fill-box;
  transform-origin: center;
}

/* lab: bubbles rise continuously, the phase boundary settles as you read */
@keyframes bp-rise {
  0%   { transform: translateY(0); opacity: 0; }
  15%  { opacity: .9; }
  100% { transform: translateY(-190px); opacity: 0; }
}
.bubble { animation: bp-rise 4.4s linear infinite; animation-delay: var(--d, 0s); }
.phase { transform: translateY(calc(var(--sp, 0) * 26px)); }
.level { transform: translateY(calc(var(--sp, 0) * -14px)); }

/* crowd: the lectern rises into place */
.podium { transform: translateY(calc((1 - var(--sp, 0)) * 40px)); }

/* ---- travellers: sprites that ride a motion path across the sheet ---------
   The pack appears only once the drawing has some confidence, and only while the
   reader is in the act it belongs to. Speed steps down through the tiers rather
   than varying continuously, because changing animation-duration restarts it. */

.traveller {
  offset-rotate: auto;
  opacity: 0;
}
/* The pack rides the elevation profile itself — the same curve the page plots as
   the course. Extended past both ends so riders enter and leave off-sheet. */
.riders .traveller {
  offset-path: path("M-140 316 C 60 300 180 250 240 180 C 320 196 400 212 440 130 C 520 150 600 170 640 236 C 720 258 800 280 860 292 C 920 288 1000 292 1160 300");
  animation: ride 30s linear infinite;
  animation-delay: var(--o, 0s);
}
.riders .traveller,
.runner { stroke-width: 1.9; }
@keyframes ride { from { offset-distance: 0%; } to { offset-distance: 100%; } }

[data-conf="1"] .riders .traveller { animation-duration: 26s; }
[data-conf="2"] .riders .traveller { animation-duration: 21s; }
[data-conf="3"] .riders .traveller { animation-duration: 17s; }

/* Visible only in their own section, and only once the drawing has warmed up.
   Keyed by section name, not by act index: the About page gained a section and
   every positional act after it fired one heading too early. */
.blueprint[data-sec="endurance"] .riders .traveller {
  opacity: clamp(0, calc((var(--conf, 0) - 0.04) * 1.6 + var(--bold, 0) * 0.35), 0.95);
}
.riders .wheel { animation: bp-roll 1.1s linear infinite; transform-box: fill-box;
  transform-origin: center; }
@keyframes bp-roll { to { transform: rotate(360deg); } }
[data-conf="0"] .riders .wheel { animation-duration: 1.9s; }
[data-conf="3"] .riders .wheel { animation-duration: 0.8s; }

/* swim leg: three swimmers crawling the waterline, arms windmilling */
.swimmers .traveller {
  offset-path: path("M-120 338 C 120 332 300 346 520 338 C 700 330 860 344 1160 337");
  animation: ride 38s linear infinite;
  animation-delay: var(--o, 0s);
  stroke-width: 1.8;
}
.blueprint[data-sec="endurance"] .swimmers .traveller {
  opacity: clamp(0, calc((var(--conf, 0) - 0.04) * 1.6 + var(--bold, 0) * 0.35), 0.9);
}
.swimmers .arm {
  animation: bp-crawl 1.5s linear infinite;
  transform-box: fill-box; transform-origin: 0% 100%;
}
@keyframes bp-crawl { to { transform: rotate(360deg); } }
.swimmers .kick { animation: bp-kick 0.5s ease-in-out infinite alternate;
  transform-box: fill-box; transform-origin: right center; }
@keyframes bp-kick { from { transform: rotate(-16deg); } to { transform: rotate(16deg); } }
[data-conf="0"] .swimmers .arm { animation-duration: 2.6s; }
[data-conf="3"] .swimmers .arm { animation-duration: 1.1s; }

.waterline { animation: bp-flow 6s linear infinite; }
.waterline.w2 { animation-duration: 9s; animation-direction: reverse; }

/* underwater act: a diver working along the dive profile */
.diver {
  offset-path: path("M1160 372 C 940 366 880 470 760 476 C 620 482 560 430 420 434 C 280 438 220 500 100 496 C 40 494 -40 470 -140 440");
  offset-rotate: auto;
  animation: ride 52s linear infinite;
  stroke-width: 1.8;
  opacity: 0;
}
.blueprint[data-sec="underwater"] .diver,
.blueprint[data-finale="1"] .diver {
  opacity: clamp(0, calc((var(--conf, 0) - 0.04) * 2.4), 0.95);
}
.diver .fin { animation: bp-fin 1.3s ease-in-out infinite alternate;
  transform-box: fill-box; transform-origin: right center; }
@keyframes bp-fin { from { transform: rotate(-18deg); } to { transform: rotate(18deg); } }
.dbubble { animation: bp-drift-up 3.4s linear infinite; animation-delay: var(--o, 0s);
  transform-box: fill-box; opacity: 0; }
@keyframes bp-drift-up {
  0%   { transform: translate(0, 0) scale(0.6); opacity: 0; }
  18%  { opacity: .85; }
  100% { transform: translate(14px, -46px) scale(1.5); opacity: 0; }
}

/* the runner takes over where the pack exits */
.blueprint[data-sec="endurance"] .runner {
  opacity: clamp(0, calc((var(--conf, 0) - 0.2) * 2), 0.9);
}
.runner {
  offset-path: path("M-140 316 C 60 300 180 250 240 180 C 320 196 400 212 440 130 C 520 150 600 170 640 236 C 720 258 800 280 860 292 C 920 288 1000 292 1160 300");
  animation: ride 44s linear infinite;
  animation-delay: -22s;
  offset-rotate: 0deg;
  opacity: 0;
}
.runner .leg-a { animation: bp-stride 0.62s ease-in-out infinite alternate;
  transform-box: fill-box; transform-origin: top center; }
.runner .leg-b { animation: bp-stride 0.62s ease-in-out infinite alternate-reverse;
  transform-box: fill-box; transform-origin: top center; }
@keyframes bp-stride { from { transform: rotate(-24deg); } to { transform: rotate(24deg); } }

/* ---- ambient vocabulary: motion that never stops -------------------------
   Speed steps with the confidence tiers, so a page is nearly still on arrival
   and running properly once the reader has settled. */

@keyframes bp-turn { to { transform: rotate(360deg); } }
.amb-spin, .amb-spin-r { transform-box: fill-box; transform-origin: center;
  animation: bp-turn 14s linear infinite; }
.amb-spin-r { animation-direction: reverse; animation-duration: 18s; }
[data-conf="0"] .amb-spin { animation-duration: 34s; }
[data-conf="0"] .amb-spin-r { animation-duration: 42s; }
[data-conf="3"] .amb-spin { animation-duration: 7s; }
[data-conf="3"] .amb-spin-r { animation-duration: 9s; }

/* three-phase commutation: each coil takes its turn, forever */
@keyframes bp-comm { 0%, 66% { opacity: .18; } 12%, 30% { opacity: 1; } }
.comm-1, .comm-2, .comm-3 { animation: bp-comm 2.1s linear infinite; }
.comm-2 { animation-delay: -1.4s; }
.comm-3 { animation-delay: -0.7s; }

/* an inspection head sweeping a line */
@keyframes bp-sweep { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(340px); } }
.sweep { animation: bp-sweep 4.5s ease-in-out infinite; transform-box: view-box; }

/* parts travelling a conveyor and looping round */
@keyframes bp-belt { to { transform: translateX(-260px); } }
.conveyor { animation: bp-belt 9s linear infinite; transform-box: view-box; }

/* a banner or gantry moving in a draught */
@keyframes bp-sway { 0%, 100% { transform: rotate(-3.2deg); } 50% { transform: rotate(3.2deg); } }
.sway { animation: bp-sway 9s ease-in-out infinite; transform-box: fill-box;
  transform-origin: top center; }

/* an indicator lamp, and a slower fault lamp */
@keyframes bp-blink { 0%, 44% { opacity: .12; } 50%, 94% { opacity: 1; } }
.tick { animation: bp-blink 1.9s steps(1, end) infinite; }
.fault { animation: bp-blink 1.1s steps(1, end) infinite; }

/* a plotter pen tracking back and forth over its chart */
@keyframes bp-pen { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(560px); } }
.pen { animation: bp-pen 11s ease-in-out infinite; transform-box: view-box; }

/* a magnetic stirrer bar */
@keyframes bp-stir { to { transform: rotate(-360deg); } }
.stir { animation: bp-stir 0.9s linear infinite; transform-box: fill-box;
  transform-origin: center; }

/* ballot papers coming down */
@keyframes bp-fall {
  0%   { transform: translateY(-40px) rotate(0deg); opacity: 0; }
  12%  { opacity: .9; }
  100% { transform: translateY(430px) rotate(220deg); opacity: 0; }
}
.paper { animation: bp-fall 9s linear infinite; animation-delay: var(--o, 0s);
  transform-box: view-box; opacity: 0; }

/* ---- act milestones ------------------------------------------------------ */

/* go-kart: the belt lets go at "What broke" and the kart coasts down */
.blueprint[data-act="3"] .belt,
.blueprint[data-act="4"] .belt,
.blueprint[data-act="5"] .belt {
  transform: translate(90px, 210px) rotate(150deg);
  opacity: 0;
  transition: transform 1.4s cubic-bezier(.3,1.4,.6,1), opacity 1.6s ease 0.4s;
}
.belt { transform-box: view-box; }

/* formula student: the loom goes dark when the car does not run */
.blueprint[data-act="3"] .energised,
.blueprint[data-act="4"] .energised { opacity: .12; animation-play-state: paused; }
.fault { visibility: hidden; }
.blueprint[data-act="3"] .fault,
.blueprint[data-act="4"] .fault { visibility: visible; }

/* imeche: the chain lets go on the way down */
.blueprint[data-act="3"] .chain-drop,
.blueprint[data-act="4"] .chain-drop,
.blueprint[data-act="5"] .chain-drop,
.blueprint[data-act="6"] .chain-drop {
  transform: translateY(300px); opacity: 0;
  transition: transform 1.8s ease-in, opacity 1.8s ease-in;
}
.chain-drop { transform-box: view-box; transition: transform .6s, opacity .6s; }

/* rcsf: a second vehicle leaves the pad once the first has gone */
.relaunch { opacity: 0; }
.blueprint[data-act="2"] .relaunch,
.blueprint[data-act="3"] .relaunch,
.blueprint[data-finale="1"] .relaunch { opacity: .9; animation: bp-relaunch 9s ease-in infinite; }
@keyframes bp-relaunch {
  0%   { transform: translate(0, 0); opacity: 0; }
  10%  { opacity: .9; }
  100% { transform: translate(150px, -420px); opacity: 0; }
}
.relaunch { transform-box: fill-box; transform-origin: center; }

/* ---- bees: BLang's mark, loose on its own page ---------------------------- */
.bee { offset-rotate: auto; stroke-width: 1.6; opacity: 0;
  animation: ride var(--dur, 26s) linear infinite; animation-delay: var(--o, 0s); }
.bee-1 { offset-path: path("M60 480 C 240 300 380 520 560 360 C 700 240 860 420 980 250 C 820 160 620 300 460 190 C 300 90 120 300 60 480"); }
.bee-2 { offset-path: path("M900 140 C 700 260 760 460 560 520 C 380 574 220 440 140 300 C 80 190 280 120 460 160 C 640 200 800 60 900 140"); }
.bee-3 { offset-path: path("M300 220 C 460 140 640 220 740 340 C 820 440 660 540 500 500 C 340 460 200 420 240 320 C 262 268 280 240 300 220"); }
.blueprint .bee { opacity: clamp(0, calc((var(--conf, 0) - 0.05) * 1.5 + var(--bold, 0) * 0.35), 0.9); }
.bee .body { fill: currentColor; fill-opacity: .55; }
.bee .stripe { stroke: var(--bg); stroke-width: 2.1; opacity: .85; }
.bee .wing { fill: currentColor; fill-opacity: .22; }
.bee .wing { animation: bp-wing 0.18s ease-in-out infinite alternate;
  transform-box: fill-box; transform-origin: bottom center; }
@keyframes bp-wing { from { transform: scaleY(0.35); } to { transform: scaleY(1); } }

/* ---- finale: the last stretch of a long page ------------------------------
   Everything below only runs once the reader is past 82% of the page. */

/* go-kart: it comes off the leash, drifts, and lays smoke */
.kart-drift { opacity: 0; offset-rotate: auto; }
.blueprint[data-finale="1"] .kart-drift {
  opacity: 1;
  offset-path: path("M120 520 C 60 400 140 250 320 210 C 500 170 560 300 700 300 C 840 300 920 190 940 300 C 960 420 820 520 640 500 C 480 482 360 560 240 560 C 170 560 150 550 120 520");
  animation: ride 9s cubic-bezier(.5,0,.5,1) infinite;
}
/* the body hangs out of line with its direction of travel — that is the drift */
.blueprint[data-finale="1"] .kart-body {
  animation: bp-slide 2.2s ease-in-out infinite alternate;
  transform-box: fill-box; transform-origin: center;
}
@keyframes bp-slide { from { transform: rotate(-26deg); } to { transform: rotate(24deg); } }
.smoke { opacity: 0; transform-box: fill-box; transform-origin: center; }
.blueprint[data-finale="1"] .smoke {
  animation: bp-smoke 2.4s ease-out infinite; animation-delay: var(--o, 0s);
}
@keyframes bp-smoke {
  0%   { transform: translate(0,0) scale(0.3); opacity: .75; }
  100% { transform: translate(-90px,-46px) scale(3.4); opacity: 0; }
}

/* about: the pack comes back for the finish, and the leader pops a wheelie.
   Without this they stay gated to the Endurance act and the wheelie is invisible. */
.blueprint[data-finale="1"] .riders .traveller,
.blueprint[data-finale="1"] .runner {
  opacity: clamp(0, calc(var(--conf, 0) * 2), 1);
}
.blueprint[data-finale="1"] .riders .traveller:first-child {
  animation: ride 30s linear infinite, bp-wheelie 2.6s ease-in-out infinite;
  animation-delay: var(--o, 0s), 0s;
}
@keyframes bp-wheelie {
  0%, 30%   { transform: rotate(0deg); }
  55%, 80%  { transform: rotate(-26deg); }
  100%      { transform: rotate(0deg); }
}

/* formula student: the car the loom was built for finally gets a lap */
.circuit { opacity: 0; }
.blueprint[data-finale="1"] .circuit { opacity: .85; }
.racecar { opacity: 0; offset-rotate: auto; stroke-width: 1.8; }
.blueprint[data-finale="1"] .racecar {
  opacity: 1;
  offset-path: path("M180 470 C 120 430 130 350 200 330 C 280 306 300 250 260 210 C 220 170 250 110 320 110 C 420 110 470 170 540 176 C 620 182 660 130 730 140 C 810 152 840 220 800 270 C 760 320 690 300 640 330 C 590 360 610 420 560 452 C 500 490 420 470 350 486 C 280 500 230 502 180 470");
  animation: ride 5.5s linear infinite;
}
.blueprint[data-finale="1"] .racecar-2 { animation-delay: -2.4s; opacity: .6; }

/* biodiesel: the flask finally lets go */
.blast { opacity: 0; transform-box: fill-box; transform-origin: center; }
.blueprint[data-finale="1"] .blast {
  animation: bp-blast 3.2s ease-out infinite; animation-delay: var(--o, 0s);
}
@keyframes bp-blast {
  0%   { transform: scale(0.1); opacity: 0; }
  8%   { opacity: 1; }
  100% { transform: scale(2.6); opacity: 0; }
}
.blueprint[data-finale="1"] .flask-shake {
  animation: bp-shake 0.32s linear infinite; transform-box: view-box;
}
@keyframes bp-shake {
  0%,100% { transform: translate(0,0); } 25% { transform: translate(-3px,2px); }
  50% { transform: translate(3px,-2px); } 75% { transform: translate(2px,3px); }
}

/* student council: the hall gets to its feet */
.blueprint[data-finale="1"] .ovation { opacity: 1; }
.ovation { opacity: 0; }
.ovation .arm-up { transform-box: fill-box; }
.ovation .arm-l { transform-origin: 100% 100%; }
.ovation .arm-r { transform-origin: 0% 100%; }
.blueprint[data-finale="1"] .ovation .arm-l {
  animation: bp-cheer-l 0.66s ease-in-out infinite alternate;
}
.blueprint[data-finale="1"] .ovation .arm-r {
  animation: bp-cheer-r 0.66s ease-in-out infinite alternate;
}
@keyframes bp-cheer-l { from { transform: rotate(14deg); } to { transform: rotate(-16deg); } }
@keyframes bp-cheer-r { from { transform: rotate(-14deg); } to { transform: rotate(16deg); } }

/* motor: it is asked for everything it has */
.blueprint[data-finale="1"] .amb-spin { animation-duration: 1.6s; }
.blueprint[data-finale="1"] .amb-spin-r { animation-duration: 2.1s; }
.blueprint[data-finale="1"] .flux { opacity: 1; }

/* imeche: the climber makes a run for the top and the buzzer loses its mind */
.blueprint[data-finale="1"] .climb {
  animation: bp-sprint 3.4s cubic-bezier(.6,0,.3,1) infinite;
}
@keyframes bp-sprint {
  0%   { transform: translateY(430px); }
  55%  { transform: translateY(10px); }
  70%  { transform: translateY(24px); }
  100% { transform: translateY(430px); }
}
.blueprint[data-finale="1"] .tick { animation-duration: 0.26s; }

/* pirelli: the tyre is spun up and lights up the contact patch */
.blueprint[data-finale="1"] .spin-cw { animation: bp-turn 0.9s linear infinite;
  transform-box: fill-box; transform-origin: center; }
.blueprint[data-finale="1"] .sweep { animation-duration: 0.7s; }
.blueprint[data-finale="1"] .conveyor { animation-duration: 2.6s; }
.patch-smoke { opacity: 0; transform-box: fill-box; transform-origin: center; }
.blueprint[data-finale="1"] .patch-smoke {
  animation: bp-smoke 2s ease-out infinite; animation-delay: var(--o, 0s);
}

/* DI: every beam swings to centre and the prop flies in on its wire */
.blueprint[data-finale="1"] .beam-1 { animation: bp-converge-l 2.8s ease-in-out infinite; }
.blueprint[data-finale="1"] .beam-3 { animation: bp-converge-r 2.8s ease-in-out infinite; }
@keyframes bp-converge-l { 0%,100% { transform: rotate(-8deg); } 50% { transform: rotate(18deg); } }
@keyframes bp-converge-r { 0%,100% { transform: rotate(4deg); } 50% { transform: rotate(-20deg); } }
.prop-fly { opacity: 0; transform-box: fill-box; transform-origin: center; }
.blueprint[data-finale="1"] .prop-fly {
  opacity: .9; animation: bp-flyin 4.2s ease-in-out infinite;
}
@keyframes bp-flyin {
  0%   { transform: translateY(-380px) rotate(-8deg); }
  45%, 65% { transform: translateY(0) rotate(3deg); }
  100% { transform: translateY(-380px) rotate(-8deg); }
}

/* biodesign: every cut fires in sequence and the line runs away with itself */
.blueprint[data-finale="1"] .cut-mark {
  animation: bp-cut 1.6s ease-in-out infinite; animation-delay: var(--o, 0s);
}
@keyframes bp-cut {
  0%, 60%, 100% { opacity: .35; transform: scaleY(1); }
  20%, 35%      { opacity: 1;   transform: scaleY(1.14); }
}
.cut-mark { transform-box: fill-box; transform-origin: top center; }

/* rcsf: the pad does not stay quiet */
.blueprint[data-finale="1"] .relaunch { animation-duration: 3.4s; }

/* blang: the swarm goes up a gear */
.blueprint[data-finale="1"] .bee { animation-duration: 7s; }
.blueprint[data-finale="1"] .bee .wing { animation-duration: 0.09s; }

/* home, cv, contact: the idle machine is finally given some throttle */
.blueprint[data-finale="1"] .spin-cw-slow {
  animation: bp-turn 2.4s linear infinite; transform-box: fill-box; transform-origin: center;
}

/* the sheet border breathes very slightly, so the page never feels frozen */
@keyframes bp-drift { 0%, 100% { opacity: .38; } 50% { opacity: .62; } }
.bp-zones { animation: bp-drift 11s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .blueprint { --sp: 0.25 !important; }
  .flow, .pulse, .bp-zones { animation: none; }
}

.site-head, main, .site-foot { position: relative; z-index: 1; }

@media (max-width: 60rem) {
  .bp-mr { display: none; }
  .bp-tr { width: 70vw; right: -22vw; }
  .bp-bl { width: 58vw; left: -20vw; }
}
@media (max-width: 34rem) { .blueprint { display: none; } }

a { color: inherit; text-decoration-color: var(--line-2); text-underline-offset: 3px; }
a:hover { text-decoration-color: var(--accent); }

h1, h2, h3, h4 { line-height: 1.12; letter-spacing: -0.02em; font-weight: 600; margin: 0; }
h1 { font-size: clamp(2.1rem, 5.5vw, 3.4rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2rem); }
h3 { font-size: 1.15rem; }
p { margin: 0 0 1.1em; }

.mono, .meta, figcaption, .spec, .tag, .kicker, code {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
}

.kicker {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-3);
  display: block;
  margin-bottom: 0.9rem;
}

/* ---------- shell ---------- */

.wrap { max-width: 78rem; margin: 0 auto; padding-inline: var(--pad); }
.prose { max-width: var(--measure); }
.prose a { color: var(--accent); }

.skip {
  position: absolute; left: -9999px; top: 0; background: var(--accent);
  color: var(--bg); padding: 0.6rem 1rem; z-index: 100;
}
.skip:focus { left: 0; }

.site-head {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(12px);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  border-bottom: 1px solid var(--line);
}
.site-head .wrap {
  display: flex; align-items: center; gap: 1.5rem;
  height: 60px;
}
/* Six-slot stator mark beside the name. The steel takes currentColor so it
   follows the theme; the copper and bore take the accent. */
.wordmark {
  display: inline-flex; align-items: center; gap: 0.6rem;
  margin-right: auto; text-decoration: none;
  color: var(--ink);
}
.wordmark .mark {
  display: block; width: 28px; height: 28px; flex: none;
  transition: color 0.18s ease, transform 0.4s ease;
}
.wordmark-name {
  font-weight: 600; letter-spacing: -0.03em; font-size: 1.02rem;
  white-space: nowrap;
}
.wordmark:hover .mark { color: var(--accent); }
.wordmark:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
@media (prefers-reduced-motion: no-preference) {
  .wordmark:hover .mark { transform: rotate(30deg); }
}
.site-nav { display: flex; gap: 1.4rem; align-items: center; }
.site-nav a {
  font-family: var(--mono); font-size: 0.78rem; text-decoration: none;
  color: var(--ink-2); padding: 0.25rem 0;
  border-bottom: 1px solid transparent;
}
.site-nav a:hover, .site-nav a[aria-current="page"] {
  color: var(--ink); border-bottom-color: var(--accent);
}
.theme-toggle {
  background: none; border: 1px solid var(--line-2); color: var(--ink-2);
  font-family: var(--mono); font-size: 0.7rem; cursor: pointer;
  padding: 0.3rem 0.55rem; border-radius: 3px; line-height: 1;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--accent); }

main { display: block; }
section { padding-block: var(--gap); }
.rule { border: 0; border-top: 1px solid var(--line); margin: 0; }

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

.hero { padding-block: clamp(3.5rem, 11vw, 8rem) clamp(2.5rem, 7vw, 5rem); }
.hero h1 { max-width: 18ch; }
.hero .lede {
  max-width: 44ch; margin-top: 1.6rem; font-size: clamp(1.02rem, 1.6vw, 1.18rem);
  color: var(--ink-2);
}
.hero .lede strong { color: var(--ink); font-weight: 500; }
.hero-meta {
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.6rem; margin-top: 2.4rem;
  font-family: var(--mono); font-size: 0.78rem; color: var(--ink-3);
}
.hero-meta span::before { content: "— "; color: var(--line-2); }

/* ---------- project cards ---------- */

.cards { display: grid; gap: 2.5rem 1.75rem; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); }
.card { display: flex; flex-direction: column; text-decoration: none; }
/* `display` on a class outranks the UA's [hidden] rule, so filtering needs this
   or hiding a card has no visible effect. */
.card[hidden] { display: none; }
.card-media {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--line);
}
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s cubic-bezier(0.2, 0, 0, 1); }
.card:hover .card-media img { transform: scale(1.035); }
/* Card with no photograph yet: centre the organisation mark on a slightly raised
   panel so the tile reads as designed rather than missing. */
.card-media.is-logo {
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--bg-3), var(--bg-2) 70%);
  padding: 1.5rem;
}
.card-media.is-logo .org-logo { margin: 0; display: block; }
.card-media.is-logo .org-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.card:hover .card-media.is-logo .org-logo img { transform: none; }

.card-media.is-empty::after {
  content: "no image yet"; position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 0.72rem; color: var(--ink-3);
}
.card h3 { margin-top: 1rem; font-size: 1.05rem; }
.card:hover h3 { color: var(--accent); }
.card p { margin: 0.45rem 0 0.7rem; color: var(--ink-2); font-size: 0.94rem; }
.card .meta { color: var(--ink-3); margin-top: auto; }

.tags { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 2.5rem; }
.tag {
  border: 1px solid var(--line-2); border-radius: 999px; padding: 0.28rem 0.75rem;
  color: var(--ink-2); background: none; cursor: pointer;
}
.tag[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.filter-count { color: var(--ink-3); align-self: center; margin-left: 0.5rem; }

/* ---------- article ---------- */

.article-head { padding-block: clamp(2.5rem, 7vw, 5rem) 2.5rem; }
.article-head h1 { max-width: 20ch; }
.article-head .standfirst {
  max-width: 46ch; margin-top: 1.3rem; font-size: 1.1rem; color: var(--ink-2);
}

/* Monochrome marks ship as black artwork and are inverted for the dark ground.
   Colour marks (.org-logo without .inv) are left exactly as the owner supplies them. */
.org-logo { margin: 0 0 1.4rem; }
.org-logo img { height: var(--logo-h, 34px); width: auto; }
.org-logo.inv img { filter: invert(1); }
:root[data-theme="light"] .org-logo.inv img { filter: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .org-logo.inv img { filter: none; }
}

/* Marks supplied with a baked-in solid field are shown as a deliberate badge tile
   rather than an accidental rectangle floating on the page ground. */
.org-logo.tile img {
  border-radius: 8px;
  background: #000;
  border: 1px solid var(--line);
}

/* Marks drawn as black artwork with knocked-out letterforms need a light ground
   behind them; inverting such a mark flips the whole lock-up and reads as broken. */
.org-logo.plate img {
  background: #fff;
  border-radius: 5px;
  padding: 7px 11px;
}

/* Two-file marks: a light-ground original and a recoloured dark-ground copy.
   Swapping files rather than filtering keeps brand colours (e.g. UCLR's pink) intact. */
.org-logo .on-light { display: none; }
.org-logo .on-dark { display: block; }
:root[data-theme="light"] .org-logo .on-light { display: block; }
:root[data-theme="light"] .org-logo .on-dark { display: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .org-logo .on-light { display: block; }
  :root:not([data-theme="dark"]) .org-logo .on-dark { display: none; }
}

.factbar {
  display: grid; gap: 1px; background: var(--line);
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  border: 1px solid var(--line); margin-top: 2.5rem;
}
.factbar div { background: var(--bg); padding: 0.9rem 1rem; }
.factbar dt { font-family: var(--mono); font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--ink-3); }
.factbar dd { margin: 0.35rem 0 0; font-family: var(--mono); font-size: 0.95rem; color: var(--ink); }

.body h2 { margin-top: 3.5rem; }
.body h2 + p { margin-top: 1rem; }
.body h3 { margin-top: 2.2rem; margin-bottom: 0.5rem; }
.body ul, .body ol { max-width: var(--measure); padding-left: 1.15rem; margin: 0 0 1.2em; }
.body li { margin-bottom: 0.4em; }
.body strong { font-weight: 600; }

figure { margin: 2.5rem 0; }
figure img, figure video { width: 100%; border: 1px solid var(--line); background: var(--bg-2); }
figcaption { color: var(--ink-3); margin-top: 0.7rem; max-width: 52ch; }
figcaption b { color: var(--ink-2); font-weight: 500; }

.fig-pair { display: grid; gap: 1rem; grid-template-columns: 1fr 1fr; }
@media (max-width: 34rem) { .fig-pair { grid-template-columns: 1fr; } }

/* Balanced gallery: the browser evens the column heights, so mixed orientations
   stack flush rather than leaving one side short. */
.fg { columns: 2; column-gap: 1rem; }
.fg-item { break-inside: avoid; margin-bottom: 1rem; }
.fg-item:last-child { margin-bottom: 0; }
@media (max-width: 34rem) { .fg { columns: 1; } }

/* ---------- desktop article layout ----------------------------------------
   Below 62rem everything stays in one stacked column (the mobile treatment).
   Above it the article becomes a text column with a media gutter: prose holds a
   readable measure, portrait media sits beside it rather than being enlarged to
   the full width, and landscape media spans the column. */

.sec { display: block; }
.sec-media > figure:first-child { margin-top: 0; }

@media (min-width: 62rem) {
  .body,
  .article-head {
    max-width: 68rem;
    margin-inline: auto;
  }

  /* Each h2-delimited section is a text column plus a media column. Grid, not
     float, so a figure sits level with the prose it belongs to instead of landing
     wherever source order put it. */
  .sec {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 21rem;
    column-gap: 3rem;
    align-items: start;
  }
  .sec.no-media { grid-template-columns: minmax(0, 1fr); }
  .sec + .sec { margin-top: 3.25rem; }

  /* readable measure for words; figures fill their column */
  .sec-text > p,
  .sec-text > ul,
  .sec-text > ol,
  .sec-text > blockquote,
  .sec-text > .broke,
  .sec-text > .note,
  .sec-text > .spec-table { max-width: 40rem; }
  .sec-text > figure { max-width: 100%; margin: 2rem 0; }
  .sec-text > h2:first-child { margin-top: 0; }

  /* Portrait and square media, stacked tight at the top of the section. The height
     cap stops one very tall image from running far past the prose beside it; the
     figure keeps its aspect ratio and centres in the column. */
  .sec-media > figure { margin: 0 0 1.25rem; }
  .sec-media > figure:last-child { margin-bottom: 0; }
  .sec-media img,
  .sec-media video {
    max-height: 26rem;
    width: auto;
    max-width: 100%;
    margin-inline: auto;
  }

  /* a section with no media lets its landscape figures use the full width */
  .no-media .sec-text > .fig-wide {
    margin-inline: calc(-1 * clamp(0rem, 4vw, 4rem));
  }
}

@media (max-width: 61.99rem) {
  .fig-wide { margin-inline: calc(-1 * clamp(0rem, 6vw, 6rem)); }
}

.broke {
  border-left: 2px solid var(--accent); background: var(--bg-2);
  padding: 1.2rem 1.4rem; margin: 2.5rem 0; max-width: var(--measure);
}
.broke .kicker { color: var(--accent); margin-bottom: 0.6rem; }
.broke p:last-child { margin-bottom: 0; }

.spec-table { width: 100%; border-collapse: collapse; margin: 2rem 0; max-width: 44rem; }
.spec-table th, .spec-table td {
  text-align: left; padding: 0.6rem 1rem 0.6rem 0;
  border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 0.82rem;
  vertical-align: top;
}
.spec-table th { color: var(--ink-3); font-weight: 400; width: 40%; }
.spec-table td { color: var(--ink); }

.note {
  font-family: var(--mono); font-size: 0.76rem; color: var(--ink-3);
  border: 1px dashed var(--line-2); padding: 0.8rem 1rem; margin: 2rem 0;
  max-width: var(--measure);
}

/* ---------- lists / cv ---------- */

.entries { display: grid; gap: 0; }
.entry { display: grid; grid-template-columns: 9rem 1fr; gap: 1.5rem;
  padding-block: 1.6rem; border-top: 1px solid var(--line); }
.entry:last-child { border-bottom: 1px solid var(--line); }
.entry > .when { font-family: var(--mono); font-size: 0.76rem; color: var(--ink-3); padding-top: 0.2rem; }
.entry h3 { font-size: 1.02rem; }
.entry .where { color: var(--ink-2); font-size: 0.92rem; margin: 0.15rem 0 0.5rem; }
.entry ul { margin: 0.5rem 0 0; padding-left: 1.1rem; color: var(--ink-2); font-size: 0.94rem; }
@media (max-width: 40rem) { .entry { grid-template-columns: 1fr; gap: 0.5rem; } }

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

.site-foot { border-top: 1px solid var(--line); padding-block: 3rem 4rem; margin-top: var(--gap); }
.site-foot .wrap { display: flex; flex-wrap: wrap; gap: 1.5rem 3rem; align-items: baseline; }
.site-foot a { color: var(--ink-2); }
.site-foot a:hover { color: var(--accent); }
.site-foot .mono { color: var(--ink-3); }
.foot-links { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-left: auto; }

/* ---------- next/prev ---------- */

.pager { display: flex; justify-content: space-between; gap: 1rem;
  border-top: 1px solid var(--line); padding-top: 1.5rem; margin-top: 4rem; }
.pager a { text-decoration: none; font-family: var(--mono); font-size: 0.8rem; color: var(--ink-2); }
.pager a:hover { color: var(--accent); }

/* ==========================================================================
   Phones. The stacked single column below 62rem was always the base layout;
   what follows fixes what actually broke at 390px rather than redesigning it.
   ========================================================================== */

@media (max-width: 40rem) {
  /* The header row did not fit: wordmark + four links + toggle came to 407px in
     a 390px viewport, so the toggle was clipped off the right edge. */
  :root { --pad: clamp(1rem, 4vw, 3rem); }

  .site-head .wrap { gap: 0.85rem; height: 56px; }
  .wordmark { gap: 0.4rem; }
  .wordmark .mark { width: 22px; height: 22px; }
  .wordmark-name { font-size: 0.92rem; }
  .site-nav { gap: 0.85rem; }
  .site-nav a {
    font-size: 0.72rem;
    padding-block: 1.1rem;        /* 44px touch target inside a 56px bar */
  }
  .theme-toggle { padding: 0.35rem 0.5rem; font-size: 0.68rem; }

  /* nothing may exceed the content column */
  .fig-wide { margin-inline: 0; }
  figure { margin: 1.75rem 0; }
  .fig-pair { gap: 0.6rem; }

  /* denser rhythm: less air between blocks on a small screen */
  section { padding-block: clamp(2rem, 9vw, 3rem); }
  .hero { padding-block: 2.5rem 2rem; }
  .hero-meta { gap: 0.4rem 1rem; margin-top: 1.6rem; }
  .cards { gap: 2rem 1rem; }
  .article-head { padding-block: 2rem 1.5rem; }

  /* fact bars and spec tables read better as two tight columns than four thin */
  .factbar { grid-template-columns: repeat(2, 1fr); }
  .spec-table th, .spec-table td { font-size: 0.76rem; }
  .spec-table th { width: 44%; }

  /* filter chips need to be thumb-sized */
  .tag { padding: 0.45rem 0.85rem; }
  .filter-count { margin-left: 0; width: 100%; }

  .pager { flex-direction: column; gap: 0.75rem; }
  .site-foot .wrap { flex-direction: column; gap: 1rem; }
  .foot-links { margin-left: 0; }
}

/* The backdrop was switched off entirely below 34rem, so a phone saw none of it.
   It comes back — one composition only, no sheet furniture, and quieter, because
   a small screen has no room for a drawing to argue with the text. */
@media (max-width: 34rem) {
  .blueprint { display: block; }
  .bp-sheet, .bp-zones, .bp-title, .bp-mr, .bp-bl { display: none; }
  .blueprint {
    opacity: calc(0.22 + 0.16 * var(--conf, 0) + 0.34 * var(--bold, 0));
  }
  .bp-tr { top: -4vh; right: -22vw; width: 78vw; }
  .bp-pipe { left: -2vw; height: 88vh; }
}
