/* ============================================================================
   SMASHING GOOD TIME — "SIGNAL"

   A dark instrument panel rather than a page. The work is presented as ten
   numbered plates down a single column, alternating side to side, each one
   held in accent duotone until you point at it and it comes up to full colour.

   Zero network dependencies, and still not a single texture file: the bloom,
   the grid, the grain, the scanlines, the crop marks and the iris are all
   generated here in CSS and SVG. The only downloads are one mono face and the
   work itself.

   1  tokens                7  ticker
   2  skeleton              8  index + spine
   3  the field + console   9  plates, flood
   4  utilities            10  footer
   5  typography           11  calm toggle
   6  hero                 12  reveal, motion, a11y, print
   ========================================================================== */

/* ---------------------------------------------------------------- 1. tokens */
:root {
  /* the void */
  --void:      #05060b;
  --void-2:    #090b13;
  --void-3:    #0d1019;

  /* glass */
  --pane:      rgba(255, 255, 255, .034);
  --pane-2:    rgba(255, 255, 255, .012);
  --line:      rgba(255, 255, 255, .105);
  --line-soft: rgba(255, 255, 255, .06);
  --line-hot:  rgba(255, 255, 255, .24);

  /* light */
  --fg:        #eef1fa;
  --fg-2:      #a8afc6;
  --fg-3:      #737b95;

  /* the loud electric set — carried over from the paper era, it was always
     screen colour pretending to be marker */
  --pink:      #ff2e88;
  --cyan:      #00e5ff;
  --violet:    #9b30ff;
  --yellow:    #ffd900;
  --lime:      #7fff2a;
  --orange:    #ff7a18;
  --red:       #ff2b2b;
  /* lifted off the old #2b4cff: an accent doubles as a button fill here, and
     the darker blue could not carry dark label text at readable contrast */
  --blue:      #4f7cff;

  --accent:    var(--cyan);          /* per-plate override, set inline */

  /* two faces: the reader's own display sans, and the machine hand */
  --f-sans: 'Segoe UI Variable Display', system-ui, -apple-system, 'Segoe UI',
            Roboto, 'Helvetica Neue', Arial, sans-serif;
  --f-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', Consolas,
            'SF Mono', Menlo, monospace;

  --body-size: 1rem;
  --body-lh:   1.66;
  --measure:   58ch;

  --gut:  clamp(1.1rem, 4vw, 3rem);
  --maxw: 1280px;
  --rad:  2px;                        /* machined; nothing wobbles */

  /* the backdrop effects calm mode switches off */
  --grain-o: .2;
  --scan-o:  .42;
  --bloom-o: 1;
}

/* Calm mode. The handwriting is gone, so legibility is no longer a font
   problem — it is a noise problem. This kills the grain, the scanlines, the
   drifting bloom and every glow, holds the chromatic split still, pushes the
   dim text up to full contrast and makes the glass opaque. */
[data-calm='on'] {
  --grain-o: 0;
  --scan-o:  0;
  --bloom-o: .3;
  --fg-2:    #dfe4f1;
  --fg-3:    #aeb5c9;
  --pane:    rgba(255, 255, 255, .07);
  --pane-2:  rgba(255, 255, 255, .045);
  --line:    rgba(255, 255, 255, .2);
  --line-soft: rgba(255, 255, 255, .13);
}

/* -------------------------------------------------------------- 2. skeleton */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 3rem;
  background: var(--void);
  color-scheme: dark;
  scrollbar-color: rgba(255, 255, 255, .16) transparent;
  scrollbar-width: thin;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  min-height: 100%;
  font-family: var(--f-sans);
  font-size: var(--body-size);
  line-height: var(--body-lh);
  font-synthesis-weight: none;
  color: var(--fg-2);
  background: var(--void);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--cyan); color: #04121a; }

/* --------------------------------------------------------- 3. the field
   Four fixed layers under everything: bloom, grid, scanlines, grain. */
.field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
}

.field__bloom {
  position: absolute;
  border-radius: 50%;
  filter: blur(76px);
  opacity: var(--bloom-o);
  will-change: transform;
}
.field__bloom:nth-child(1) {
  width: 46vw; height: 46vw; top: -18vw; left: -10vw;
  background: radial-gradient(circle, rgba(155, 48, 255, .30), transparent 66%);
  animation: drift-a 34s ease-in-out infinite alternate;
}
.field__bloom:nth-child(2) {
  width: 40vw; height: 40vw; top: -6vw; right: -8vw;
  background: radial-gradient(circle, rgba(255, 46, 136, .22), transparent 66%);
  animation: drift-b 41s ease-in-out infinite alternate;
}
.field__bloom:nth-child(3) {
  width: 52vw; height: 52vw; bottom: -30vw; left: 22vw;
  background: radial-gradient(circle, rgba(0, 229, 255, .15), transparent 68%);
  animation: drift-c 53s ease-in-out infinite alternate;
}
@keyframes drift-a { to { transform: translate3d(9vw, 7vh, 0)   scale(1.18); } }
@keyframes drift-b { to { transform: translate3d(-8vw, 11vh, 0) scale(.86); } }
@keyframes drift-c { to { transform: translate3d(12vw, -9vh, 0) scale(1.12); } }
@media (prefers-reduced-motion: reduce) { .field__bloom { animation: none !important; } }

/* the engineering grid — 1px rules on a 76px pitch, bright enough to be read
   through the bloom, faded out downward */
.field__grid {
  position: absolute;
  inset: 0;
  --g: rgba(190, 214, 255, .10);
  background-image:
    linear-gradient(to right,  var(--g) 1px, transparent 1px),
    linear-gradient(to bottom, var(--g) 1px, transparent 1px);
  background-size: 76px 76px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 52%, transparent 88%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 52%, transparent 88%);
}

.field__scan {
  position: absolute;
  inset: 0;
  opacity: var(--scan-o);
  background-image: repeating-linear-gradient(
    to bottom, rgba(0, 0, 0, .34) 0 1px, transparent 1px 3px);
}

/* sensor grain, generated by feTurbulence — no image file */
.field__grain {
  position: absolute;
  inset: -50%;
  opacity: var(--grain-o);
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='220' height='220' filter='url(%23n)'/></svg>");
}
@media (prefers-reduced-motion: no-preference) {
  .field__grain { animation: jitter 620ms steps(3, end) infinite; }
}
@keyframes jitter {
  0%   { transform: translate3d(0, 0, 0); }
  33%  { transform: translate3d(-1.5%, 1%, 0); }
  66%  { transform: translate3d(1%, -1.5%, 0); }
}

/* corner brackets — the whole page held in a viewfinder */
.hud {
  position: fixed;
  inset: 13px;
  z-index: 30;
  pointer-events: none;
  border: 1px solid var(--line);
  -webkit-mask:
    linear-gradient(#000, #000) top    left  / 30px 30px no-repeat,
    linear-gradient(#000, #000) top    right / 30px 30px no-repeat,
    linear-gradient(#000, #000) bottom left  / 30px 30px no-repeat,
    linear-gradient(#000, #000) bottom right / 30px 30px no-repeat;
  mask:
    linear-gradient(#000, #000) top    left  / 30px 30px no-repeat,
    linear-gradient(#000, #000) top    right / 30px 30px no-repeat,
    linear-gradient(#000, #000) bottom left  / 30px 30px no-repeat,
    linear-gradient(#000, #000) bottom right / 30px 30px no-repeat;
}
@media (max-width: 760px) { .hud { display: none; } }

/* The console: the viewfinder made live. A hairline along its top edge fills
   as you travel the index, and a timecode in the bottom corner maps your
   position onto the film's own runtime — 5:06, thirty-eight pieces. It only
   moves when you do, so calm mode keeps it; without the script it never
   appears at all. */
.console {
  position: fixed;
  inset: 13px;
  z-index: 30;
  pointer-events: none;
  display: none;
}
.js-live .console { display: block; }
.console__beam {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--cyan), var(--violet), var(--pink));
  opacity: .5;
  transform: scaleX(0);
  transform-origin: left;
}
.console__read {
  position: absolute;
  left: 15px; bottom: 9px;
  font-size: .56rem;
  letter-spacing: .22em;
  color: var(--fg-3);
}
.console__read b { font-weight: 400; color: var(--fg-2); }
.console__read i { font-style: normal; color: var(--line-hot); padding: 0 .3em; }
@media (max-width: 760px) { .console { display: none !important; } }

.layer { position: relative; z-index: 2; }

/* ------------------------------------------------------- 4. utilities */
.wrap {
  width: min(var(--maxw), 100% - (var(--gut) * 2));
  margin-inline: auto;
}

/* the machine hand: every label, count, spec and control on the page */
.mono {
  font-family: var(--f-mono);
  font-size: .72rem;
  letter-spacing: .17em;
  text-transform: uppercase;
  line-height: 1.5;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 99;
  font-family: var(--f-mono);
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: .8em 1.3em;
  background: var(--cyan);
  color: #04121a;
  text-decoration: none;
}
.skip:focus { left: 1rem; top: 1rem; }

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 4px;
}

/* the live dot, in the rail and on every plate */
.dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--lime);
  vertical-align: baseline;
}
@media (prefers-reduced-motion: no-preference) {
  .dot { animation: ping 2.4s cubic-bezier(.2, .7, .3, 1) infinite; }
}
@keyframes ping {
  0%        { box-shadow: 0 0 0 0 rgba(127, 255, 42, .5); }
  70%, 100% { box-shadow: 0 0 0 8px rgba(127, 255, 42, 0); }
}
/* pending: a hollow ring, holding still */
.dot--soon {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--fg-3);
  animation: none;
}

/* a label + hairline + count, used above the index and the footer */
.section {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  color: var(--fg-3);
  padding: clamp(1.6rem, 4vw, 3rem) 0 0;
}
.section__idx { color: var(--cyan); white-space: nowrap; }
.section__bar {
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--line-hot), transparent);
}

/* ------------------------------------------------------------ 5. typography */
h1, h2, h3 {
  font-family: var(--f-sans);
  font-weight: 800;
  line-height: 1;
  margin: 0;
  color: var(--fg);
  letter-spacing: -.035em;
}

/* ----------------------------------------------------------------- 6. hero */
.hero {
  position: relative;
  padding: clamp(1.4rem, 3vw, 2.4rem) 0 clamp(2.4rem, 6vw, 4.5rem);
}

.rail {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-wrap: wrap;
  padding: 0 0 1rem;
  color: var(--fg-3);
  border-bottom: 1px solid var(--line-soft);
}
.rail__sep { color: var(--line-hot); }
.rail__id  { color: var(--fg-2); }
.rail__spacer { flex: 1; }

.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
  padding-top: clamp(2rem, 6vw, 4.5rem);
}
.hero__left { position: relative; }
@media (max-width: 940px) {
  .hero__inner { grid-template-columns: minmax(0, 1fr); }
  .hero__iris  { justify-self: start; }
}

/* mono set on its side in the margin — only where there is margin to set it in */
.hero__edge {
  display: none;
  position: absolute;
  top: .4rem;
  left: -2.9rem;
  writing-mode: vertical-rl;
  letter-spacing: .34em;
  color: var(--fg-3);
  font-size: .62rem;
}
.hero__edge::after {
  content: '';
  display: block;
  width: 1px;
  height: 5rem;
  margin: 1rem auto 0;
  background: linear-gradient(to bottom, var(--line-hot), transparent);
}
@media (min-width: 1420px) { .hero__edge { display: block; } }

/* the wordmark: solid light with an RGB split behind it, the way a bad capture
   card renders a bright title, stepped down the page */
.mark {
  font-size: clamp(3rem, 11.5vw, 8.8rem);
  text-transform: uppercase;
  letter-spacing: -.055em;
  line-height: .84;
  font-weight: 800;
}
.mark__line {
  position: relative;
  display: block;
  width: max-content;
  max-width: 100%;
  color: var(--fg);
  text-shadow: 0 0 46px rgba(160, 200, 255, .2);
}
@media (min-width: 760px) {
  .mark__line:nth-child(2) { margin-left: .5em; }
  .mark__line:nth-child(3) { margin-left: 1em; }
}
.mark__line::before,
.mark__line::after {
  content: attr(data-t);
  position: absolute;
  inset: 0;
  z-index: -1;                       /* behind the glyphs, so they fringe */
  opacity: .82;
  pointer-events: none;
}
.mark__line::before { color: var(--pink); transform: translate(-.022em,  .011em); }
.mark__line::after  { color: var(--cyan); transform: translate( .022em, -.011em); }

/* the split slips every few seconds, then snaps back */
@media (prefers-reduced-motion: no-preference) {
  .mark__line:nth-child(1)::before { animation: slip-l  7.3s infinite; }
  .mark__line:nth-child(1)::after  { animation: slip-r  7.3s infinite; }
  .mark__line:nth-child(2)::before { animation: slip-l  9.7s infinite; }
  .mark__line:nth-child(2)::after  { animation: slip-r  9.7s infinite; }
  .mark__line:nth-child(3)::before { animation: slip-l 12.1s infinite; }
  .mark__line:nth-child(3)::after  { animation: slip-r 12.1s infinite; }
}
@keyframes slip-l {
  0%, 88%, 100% { transform: translate(-.022em,  .011em); }
  89%           { transform: translate(-.13em,  -.02em); }
  92%           { transform: translate( .05em,   .03em); }
  95%           { transform: translate(-.08em,   0); }
}
@keyframes slip-r {
  0%, 88%, 100% { transform: translate( .022em, -.011em); }
  89%           { transform: translate( .12em,   .02em); }
  92%           { transform: translate(-.06em,  -.03em); }
  95%           { transform: translate( .09em,   0); }
}
[data-calm='on'] .mark__line::before,
[data-calm='on'] .mark__line::after { animation: none; opacity: .5; }

.hero__blurb {
  margin: clamp(1.6rem, 3vw, 2.4rem) 0 0;
  max-width: 42ch;
  font-size: clamp(1.02rem, 1.6vw, 1.16rem);
  color: var(--fg-2);
}
.hero__blurb em { color: var(--fg); font-style: normal; }

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
  margin: 1.9rem 0 0;
  padding: 0;
  list-style: none;
  font-size: .66rem;
  letter-spacing: .15em;
}
.hero__meta li {
  color: var(--fg-3);
  padding: .55em .8em;
  border: 1px solid var(--line-soft);
  background: var(--pane-2);
}
.hero__meta b { color: var(--fg); font-weight: 400; }

/* the iris — the drawn eye, remade as a lens */
.hero__iris {
  width: clamp(190px, 26vw, 330px);
  height: auto;
  overflow: visible;
  filter: drop-shadow(0 0 34px rgba(0, 229, 255, .2));
}
.hero__iris .ring-a, .hero__iris .ring-b, .hero__iris .ring-c,
.hero__iris .nodes,  .hero__iris .blades, .hero__iris .core {
  transform-origin: 100px 100px;
}
@media (prefers-reduced-motion: no-preference) {
  .hero__iris .ring-a { animation: spin 38s linear infinite; }
  .hero__iris .ring-b { animation: spin 26s linear infinite reverse; }
  .hero__iris .ring-c { animation: spin 9s  linear infinite; }
  .hero__iris .nodes  { animation: spin 17s linear infinite reverse; }
  .hero__iris .blades { animation: stop-down 7s ease-in-out infinite alternate; }
  .hero__iris .core   { animation: bloom 3.4s ease-in-out infinite; }
}
@keyframes spin      { to { transform: rotate(360deg); } }
@keyframes stop-down { from { transform: scale(1); } to { transform: scale(.93) rotate(6deg); } }
@keyframes bloom     { 50% { opacity: .55; transform: scale(1.14); } }

.hero__cue {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  margin-top: clamp(1.8rem, 4vw, 3rem);
  color: var(--fg-3);
  text-decoration: none;
  transition: color .2s ease;
}
.hero__cue:hover { color: var(--cyan); }
.hero__cue-arrow { display: inline-block; }
@media (prefers-reduced-motion: no-preference) {
  .hero__cue-arrow { animation: nudge 1.9s ease-in-out infinite; }
}
@keyframes nudge { 50% { transform: translateY(5px); } }

/* --------------------------------------------------------------- 7. ticker
   A full-bleed band of running mono. Two identical runs, translated by half
   the track, so the loop has no seam. */
.ticker {
  position: relative;
  overflow: hidden;
  padding: .95rem 0;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(180deg, rgba(255, 255, 255, .035), transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}
.ticker__track { display: flex; width: max-content; }
@media (prefers-reduced-motion: no-preference) {
  .ticker__track { animation: run 38s linear infinite; }
}
@keyframes run { to { transform: translateX(-50%); } }

.ticker__run {
  font-family: var(--f-mono);
  font-size: .76rem;
  letter-spacing: .3em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg-3);
}
.ticker__run i { font-style: normal; padding: 0 .2em; }
.ticker__run i:nth-of-type(4n+1) { color: var(--pink); }
.ticker__run i:nth-of-type(4n+2) { color: var(--cyan); }
.ticker__run i:nth-of-type(4n+3) { color: var(--violet); }
.ticker__run i:nth-of-type(4n)   { color: var(--lime); }

/* ------------------------------------------------------ 8. index + spine */
.index {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  padding-bottom: clamp(3rem, 8vw, 6rem);
}
.spine { display: none; }

@media (min-width: 1120px) {
  .index {
    grid-template-columns: 80px minmax(0, 1fr);
    gap: clamp(1rem, 3vw, 2.5rem);
  }
  .spine {
    display: flex;
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    align-items: center;
  }
}

.spine__list {
  list-style: none;
  margin: 0; padding: 0;
  display: flex;
  flex-direction: column;
  gap: .1rem;
}
.spine__list a {
  position: relative;
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .34rem 0;
  color: var(--fg-3);
  text-decoration: none;
  font-size: .62rem;
  letter-spacing: .1em;
  transition: color .25s ease;
}
.spine__list a::before {
  content: '';
  width: 15px; height: 1px;
  background: currentColor;
  opacity: .55;
  transition: width .3s cubic-bezier(.2, .7, .3, 1), opacity .3s ease;
}
.spine__list b { font-weight: 400; }
/* the name rides out of the margin on hover; still read aloud when it doesn't */
.spine__list span {
  position: absolute;
  left: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%) translateX(-5px);
  white-space: nowrap;
  padding: .5em .7em;
  font-size: .6rem;
  letter-spacing: .13em;
  color: var(--fg);
  background: rgba(9, 11, 19, .94);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease, transform .25s cubic-bezier(.2, .7, .3, 1);
}
.spine__list a:hover,
.spine__list a:focus-visible { color: var(--accent); }
.spine__list a:hover::before,
.spine__list a:focus-visible::before { width: 26px; opacity: 1; }
.spine__list a:hover span,
.spine__list a:focus-visible span { opacity: 1; transform: translateY(-50%) translateX(0); }

.spine__list .is-current a { color: var(--accent); }
.spine__list .is-current a::before { width: 32px; opacity: 1; }

/* ---------------------------------------------------------------- 9. plates */
.plates { min-width: 0; }

.plate {
  --pad: clamp(2.6rem, 6.5vw, 6rem);
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
  grid-template-areas: 'media type';
  gap: clamp(1.5rem, 4vw, 4.5rem);
  align-items: center;
  padding: var(--pad) 0;
}
.plate__media { grid-area: media; }
.plate__type  { grid-area: type; }
/* the divider between plates: a hairline that burns out toward the margin */
.plate + .plate::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(to right,
              rgba(255,255,255,.16), rgba(255,255,255,.05) 62%, transparent);
}
.plate:nth-of-type(even)::before {
  background: linear-gradient(to left,
              rgba(255,255,255,.16), rgba(255,255,255,.05) 62%, transparent);
}

/* every other plate turns around */
.plate:nth-of-type(even) {
  grid-template-columns: minmax(0, .94fr) minmax(0, 1.06fr);
  grid-template-areas: 'type media';
}

@media (max-width: 860px) {
  .plate,
  .plate:nth-of-type(even) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'media' 'type';
    gap: 1.6rem;
  }
}

/* ---- the still ---------------------------------------------------------- */
.plate__media {
  position: relative;
  isolation: isolate;               /* keep the duotone blend off the page */
  overflow: hidden;
  margin: 0;
  aspect-ratio: var(--ar, 16 / 9);
  background: var(--void-3);
  border: 1px solid var(--line-soft);
  border-radius: var(--rad);
  transition: border-color .4s ease, box-shadow .4s ease;
}
.plate__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  display: block;
  /* --tone is set per plate, measured off each still, so a near-black UI
     screenshot and a sunlit village both land in the same tonal band before
     the duotone below caps them */
  filter: grayscale(1) contrast(1.08) brightness(var(--tone, .8));
  transform: scale(1.03);
  transition: filter .65s ease, transform 1s cubic-bezier(.2, .7, .3, 1);
}

/* A true two-point duotone. `darken` clamps every white in the photo down to
   a mid accent, `lighten` lifts every black up to a near-black accent, and the
   greyscale image is remapped between the two. Tinting with a single `color`
   layer instead would leave the lightness alone, and the bright stills — a
   sunlit village, a white page — came back as flat neon. */
.plate__wash,
.plate__tone {
  position: absolute;
  inset: 0;
  transition: opacity .65s ease;
  pointer-events: none;
}
.plate__wash {                              /* highlight cap */
  background: color-mix(in srgb, var(--accent) 54%, #0d1018);
  mix-blend-mode: darken;
}
.plate__tone {                              /* shadow lift */
  background: color-mix(in srgb, var(--accent) 14%, #060810);
  mix-blend-mode: lighten;
}
/* scanlines, a dark foot and a corner glow, over the top of the duotone */
.plate__media::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    repeating-linear-gradient(to bottom, rgba(0,0,0,.24) 0 1px, transparent 1px 3px),
    linear-gradient(to top, rgba(5,6,11,.72), transparent 46%),
    radial-gradient(120% 90% at 100% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 60%);
  transition: opacity .6s ease;
}

/* crop marks, pushed outward when you point at the plate */
.plate__marks {
  position: absolute;
  inset: 11px;
  z-index: 2;
  pointer-events: none;
  border: 1px solid rgba(255, 255, 255, .38);
  transition: inset .45s cubic-bezier(.2, .7, .3, 1), border-color .45s ease;
  -webkit-mask:
    linear-gradient(#000, #000) top    left  / 22px 22px no-repeat,
    linear-gradient(#000, #000) top    right / 22px 22px no-repeat,
    linear-gradient(#000, #000) bottom left  / 22px 22px no-repeat,
    linear-gradient(#000, #000) bottom right / 22px 22px no-repeat;
  mask:
    linear-gradient(#000, #000) top    left  / 22px 22px no-repeat,
    linear-gradient(#000, #000) top    right / 22px 22px no-repeat,
    linear-gradient(#000, #000) bottom left  / 22px 22px no-repeat,
    linear-gradient(#000, #000) bottom right / 22px 22px no-repeat;
}

/* the affordance that says the still is a door */
.plate__peek {
  position: absolute;
  left: 14px; bottom: 14px;
  z-index: 3;
  font-size: .62rem;
  letter-spacing: .18em;
  padding: .6em .85em;
  color: #04070d;
  background: var(--accent);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .35s ease, transform .35s cubic-bezier(.2, .7, .3, 1);
  pointer-events: none;
}

/* hover / focus: the plate comes up to full colour */
.plate:hover .plate__media img,
.plate:focus-within .plate__media img { filter: none; transform: scale(1.07); }
.plate:hover .plate__wash, .plate:hover .plate__tone,
.plate:focus-within .plate__wash, .plate:focus-within .plate__tone { opacity: 0; }
.plate:hover .plate__media::after,
.plate:focus-within .plate__media::after { opacity: .5; }
.plate:hover .plate__marks,
.plate:focus-within .plate__marks { inset: 17px; border-color: var(--accent); }
.plate:hover .plate__media,
.plate:focus-within .plate__media {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 30px 70px -40px #000,
              0 0 46px -20px color-mix(in srgb, var(--accent) 85%, transparent);
}
.plate:hover .plate__peek,
.plate:focus-within .plate__peek { opacity: 1; transform: translateY(0); }

/* not out yet: hold it back, and hatch the still so the state is visible
   without reading a word */

.plate--soon .plate__media::after {
  background:
    repeating-linear-gradient(135deg, rgba(5,6,11,.34) 0 2px, transparent 2px 9px),
    repeating-linear-gradient(to bottom, rgba(0,0,0,.24) 0 1px, transparent 1px 3px),
    linear-gradient(to top, rgba(5,6,11,.74), transparent 46%);
}
.plate--soon:hover .plate__media img {
  filter: grayscale(1) contrast(1.08) brightness(var(--tone, .8));
  transform: scale(1.03);
}
.plate--soon:hover .plate__wash,
.plate--soon:hover .plate__tone { opacity: 1; }
.plate--soon:hover .plate__media { border-color: var(--line); box-shadow: none; }
.plate--soon:hover .plate__marks { inset: 11px; border-color: rgba(255,255,255,.38); }

/* ---- the flood ----------------------------------------------------------
   Where the duotone lifts, the pointer does the lifting. A second copy of
   the still sits above the wash in full colour, clipped to a pool of light
   that opens from wherever the cursor touched the plate — you develop the
   print with your own hand — and drains back to that point when it leaves.
   Keyboard focus opens it from the centre.

   Armed by the script as .js-bloom, only on machines with a real pointer
   and a browser that can transition a registered custom property. Everyone
   else keeps the plain hover reveal above, untouched. */
@property --r { syntax: '<length>'; inherits: false; initial-value: 0px; }

.plate__flood {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: none;
  pointer-events: none;
  --rmax: 1600px;               /* reachable before any pointer has set it */
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--mx, 50%) var(--my, 50%),
                      #000 0 55%, transparent 98%);
          mask-image: radial-gradient(circle var(--r) at var(--mx, 50%) var(--my, 50%),
                      #000 0 55%, transparent 98%);
  transition: --r .7s cubic-bezier(.4, 0, .6, 1);       /* the drain */
}
.js-bloom .plate:not(.plate--soon) .plate__flood { display: block; }
.plate__flood img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  display: block;
  transform: scale(1.03);
  transition: transform 1s cubic-bezier(.2, .7, .3, 1);
}
/* the same scan texture, dark foot and corner glow the hovered still wears,
   so the lit pool matches the plate around it */
.plate__flood::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(to bottom, rgba(0,0,0,.12) 0 1px, transparent 1px 3px),
    linear-gradient(to top, rgba(5,6,11,.36), transparent 46%),
    radial-gradient(120% 90% at 100% 0%, color-mix(in srgb, var(--accent) 11%, transparent), transparent 60%);
}
.js-bloom .plate:is(:hover, :focus-within) .plate__flood {
  --r: var(--rmax);
  transition: --r 1.05s cubic-bezier(.22, .61, .21, 1);  /* the bloom */
}
/* under the flood, hover keeps its hands off the duotone — the light does
   the revealing, and it still ends at the same full-colour plate */
.js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__media > img {
  filter: grayscale(1) contrast(1.08) brightness(var(--tone, .8));
}
.js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__flood img { filter: none; }
.js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__wash,
.js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__tone { opacity: 1; }
.js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__media::after { opacity: 1; }

/* text picked up inside a plate is highlighted in that plate's own colour */
.plate ::selection { background: var(--accent); color: #04070d; }

/* ---- the type ----------------------------------------------------------- */
.plate__type { min-width: 0; }

/* The plate number, set as an outline. It shares the type cell as a second
   grid item — not an absolute child of it — so that .plate stays the
   containing block for the stretched link below. Kept to the trailing edge:
   the copy is left-aligned in both orientations, so that is the one corner
   guaranteed to be empty. On the reversed plates it runs past the column and
   tucks in behind the still, which is the point. */
.plate__num {
  grid-area: type;
  justify-self: end;
  align-self: start;
  margin-top: -.4em;
  z-index: -1;
  font-size: clamp(4.6rem, 10vw, 10rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.06em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .15);
  pointer-events: none;
  transition: -webkit-text-stroke-color .4s ease;
}
@media (max-width: 860px) { .plate__num { margin-top: -.2em; } }
.plate:hover .plate__num {
  -webkit-text-stroke-color: color-mix(in srgb, var(--accent) 60%, transparent);
}

.plate__status { color: var(--fg-3); margin: 0; }
.plate__status .on { color: var(--lime); }
.plate__status i,
.plate__kicker i { font-style: normal; color: var(--line-hot); padding: 0 .35em; }

.plate__title {
  margin: .7rem 0 0;
  font-size: clamp(2rem, 4.2vw, 3.5rem);
  text-transform: uppercase;
  letter-spacing: -.042em;
  line-height: .94;
  color: var(--fg);
}
@supports (-webkit-background-clip: text) {
  .plate__title {
    background-image: linear-gradient(176deg, #ffffff, #97a0ba);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    transition: background-image .4s ease;
  }
  .plate:hover .plate__title {
    background-image: linear-gradient(176deg, #ffffff,
                      color-mix(in srgb, var(--accent) 78%, #ffffff));
  }
}
.plate__open { color: inherit; text-decoration: none; }
/* One link, stretched across the plate so clicking anywhere on it opens the
   project. Inset by the plate's own padding, so the empty band between two
   plates belongs to neither of them. */
.plate__open::after {
  content: '';
  position: absolute;
  inset: var(--pad) 0;
  z-index: 4;
}

.plate__kicker {
  margin: .75rem 0 0;
  color: color-mix(in srgb, var(--accent) 80%, var(--fg));
  font-size: .7rem;
  letter-spacing: .16em;
}

.plate__desc {
  margin: 1.1rem 0 0;
  max-width: var(--measure);
  color: var(--fg-2);
  font-size: .97rem;
}
.plate__desc em { color: var(--fg); font-style: normal; }

.specs {
  margin: 1.3rem 0 0;
  padding: 0;
  list-style: none;
  color: var(--fg-3);
  font-size: .63rem;
  letter-spacing: .16em;
  line-height: 2;
}
.specs li { display: inline; }
.specs li:not(:last-child)::after {
  content: '/';
  color: var(--line-hot);
  padding: 0 .7em;
}

.plate__cta {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  flex-wrap: wrap;
  margin: 1.7rem 0 0;
}
.go {
  display: inline-flex;
  align-items: center;
  gap: .7em;
  padding: .95em 1.35em;
  font-size: .68rem;
  letter-spacing: .17em;
  color: var(--fg);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  border-radius: var(--rad);
  transition: color .28s ease, background-color .28s ease, box-shadow .28s ease,
              border-color .28s ease;
}
.go i { font-style: normal; transition: transform .28s cubic-bezier(.2, .7, .3, 1); }
.plate:hover .go {
  color: #04070d;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 34px -8px var(--accent);
}
.plate:hover .go i { transform: translate(3px, -3px); }

/* the secondary link has to sit above the stretched one to stay clickable */
.alt {
  position: relative;
  z-index: 5;
  color: var(--fg-3);
  text-decoration: none;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--line);
  transition: color .2s ease, border-color .2s ease;
}
.alt:hover { color: var(--fg); border-color: var(--fg); }

/* -------------------------------------------------------------- 10. footer */
.footer {
  position: relative;
  overflow: hidden;
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 5rem;
  border-top: 1px solid var(--line-soft);
}
/* the initials, set enormous and hollow, half off the corner */
.footer__ghost {
  position: absolute;
  right: -.06em;
  bottom: -.34em;
  font-size: clamp(9rem, 26vw, 22rem);
  font-weight: 800;
  letter-spacing: -.07em;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .07);
  pointer-events: none;
  user-select: none;
  transition: -webkit-text-stroke-color 1.2s ease;
}
/* the initials warm faintly while you are down here — a room noticing you */
.footer:hover .footer__ghost { -webkit-text-stroke-color: rgba(0, 229, 255, .14); }

/* the sign-off wears the same RGB fringe as the wordmark up top, held still —
   the two ends of the page are cut from the same signal */
.footer__mark {
  position: relative;
  width: max-content;
  max-width: 100%;
  margin-top: clamp(1.4rem, 3vw, 2.2rem);
  font-size: clamp(2.4rem, 7vw, 5rem);
  text-transform: uppercase;
  letter-spacing: -.05em;
}
.footer__mark::before,
.footer__mark::after {
  content: attr(data-t);
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .55;
  pointer-events: none;
}
.footer__mark::before { color: var(--pink); transform: translate(-.02em,  .012em); }
.footer__mark::after  { color: var(--cyan); transform: translate( .02em, -.012em); }
[data-calm='on'] .footer__mark::before,
[data-calm='on'] .footer__mark::after { opacity: .35; }
/* the sign-off above this was removed, so the lead now carries the gap the
   heading used to hold off the section rule */
.footer__lead {
  margin: clamp(1.4rem, 3vw, 2.2rem) 0 0;
  max-width: 40ch;
  color: var(--fg-2);
}

.social {
  display: flex; flex-wrap: wrap; gap: .55rem;
  list-style: none; padding: 0; margin: 2rem 0 0;
}
.social a {
  display: inline-flex; align-items: center; gap: .6em;
  font-size: .7rem;
  letter-spacing: .15em;
  text-decoration: none;
  color: var(--fg-2);
  padding: .85em 1.15em;
  background: var(--pane-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--rad);
  transition: color .2s ease, border-color .2s ease, box-shadow .2s ease,
              transform .2s ease;
}
.social a:hover {
  color: var(--fg);
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--sc) 60%, transparent);
  box-shadow: 0 0 28px -8px var(--sc);
}
.social svg { width: 1.15em; height: 1.15em; fill: currentColor; }
.social a:hover svg { fill: var(--sc); }

.colophon {
  position: relative;
  margin-top: 3rem;
  max-width: 60ch;
  color: var(--fg-3);
  font-size: .82rem;
  line-height: 1.8;
}

/* --------------------------------------------------------- 11. calm toggle */
.calm-toggle {
  position: fixed;
  right: clamp(.9rem, 2vw, 1.9rem);
  bottom: clamp(.9rem, 2vw, 1.9rem);
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-size: .64rem;
  letter-spacing: .16em;
  padding: .85em 1.05em;
  cursor: pointer;
  color: var(--fg-2);
  background: rgba(9, 11, 19, .78);
  -webkit-backdrop-filter: blur(12px);
          backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: var(--rad);
  transition: color .2s ease, border-color .2s ease;
}
.calm-toggle:hover { color: var(--fg); border-color: var(--line-hot); }
.calm-toggle::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--fg-3);
  box-shadow: 0 0 0 1px var(--line);
}
.calm-toggle[aria-pressed='true'] {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--yellow) 50%, transparent);
}
.calm-toggle[aria-pressed='true']::before {
  background: var(--yellow);
  box-shadow: 0 0 12px 0 var(--yellow);
}

/* Calm mode promises nothing moving, so it has to reach the pieces that move
   on their own clock too — the ticker, the iris, the drifting backdrop, the
   live dot, and the scroll-driven drift on the numbers and the stills. */
[data-calm='on'] .ticker__track,
[data-calm='on'] .field__bloom,
[data-calm='on'] .field__grain,
[data-calm='on'] .hero__iris *,
[data-calm='on'] .hero__cue-arrow,
[data-calm='on'] .dot,
[data-calm='on'] .plate__num,
[data-calm='on'] .plate__media img { animation: none !important; }

/* ------------------------------------- 12. reveal, motion, a11y, print */
/* Applied only once JS has confirmed it can un-hide these. No JS, no observer,
   script blocked — every plate is simply visible. Content never needs script. */
@media (prefers-reduced-motion: no-preference) {
  .js-anim .reveal { opacity: 0; }
  .js-anim .reveal.seen { opacity: 1; transition: opacity .5s ease; }

  /* the still wipes open from the outside edge */
  .js-anim .reveal .plate__media { clip-path: inset(0 100% 0 0); }
  .js-anim .plate:nth-of-type(even) .plate__media { clip-path: inset(0 0 0 100%); }
  .js-anim .reveal.seen .plate__media {
    clip-path: inset(0 0 0 0);
    transition: clip-path .95s cubic-bezier(.16, .84, .34, 1) .05s,
                border-color .4s ease, box-shadow .4s ease;
  }

  /* the wipe's leading edge carries a scanning beam in the plate's accent,
     timed to the clip above so the two read as one gesture */
  .js-anim .reveal .plate__media::before {
    content: '';
    position: absolute;
    top: 0; bottom: 0; left: -3px;
    width: 2px;
    z-index: 2;
    background: var(--accent);
    box-shadow: 0 0 18px 1px var(--accent);
    opacity: 0;
    pointer-events: none;
  }
  .js-anim .reveal.seen .plate__media::before {
    animation: beam-sweep .95s cubic-bezier(.16, .84, .34, 1) .05s;
  }
  .js-anim .plate:nth-of-type(even).reveal.seen .plate__media::before {
    animation-name: beam-sweep-r;
  }

  /* Then the type climbs in behind it, line by line — except the title, which
     only fades. A transform on it would make the title the containing block
     for the stretched link inside it, shrinking the plate's click area to the
     title's own box for as long as the transform is set. The exclusions sit
     in :where() so they weigh nothing — bare :not(.plate__title) counts as a
     class and this rule would outrank the .seen rule below for good. */
  .js-anim .reveal .plate__type > *:where(:not(.plate__title)):where(:not(.plate__cta)) {
    opacity: 0;
    transform: translateY(18px);
  }
  /* Same reason for the call to action: a transform on it makes it a stacking
     context, which would trap the Source link's z-index below the stretched
     one and swallow its clicks for the length of the transition. */
  .js-anim .reveal .plate__title,
  .js-anim .reveal .plate__cta { opacity: 0; }
  .js-anim .reveal.seen .plate__type > * {
    opacity: 1;
    transform: none;
    transition: opacity .5s ease, transform .65s cubic-bezier(.2, .7, .3, 1);
  }
  .js-anim .reveal.seen .plate__type > :nth-child(2) { transition-delay: .08s; }
  .js-anim .reveal.seen .plate__type > :nth-child(3) { transition-delay: .14s; }
  .js-anim .reveal.seen .plate__type > :nth-child(4) { transition-delay: .20s; }
  .js-anim .reveal.seen .plate__type > :nth-child(5) { transition-delay: .26s; }
  .js-anim .reveal.seen .plate__type > :nth-child(6) { transition-delay: .32s; }
}

/* Scroll-driven flourishes where the browser can do them natively: the plate
   number drifts against the scroll, and the still pans inside its own crop.
   Nothing here is load-bearing — browsers without it get a static plate. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .plate__num {
      animation: num-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    .plate__media img {
      animation: pan linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }
}
@keyframes num-drift { from { translate: 0 34px; } to { translate: 0 -34px; } }
@keyframes pan       { from { object-position: 50% 38%; } to { object-position: 50% 62%; } }
@keyframes beam-sweep   { 0% { left: -3px;  opacity: .95; } 85% { opacity: .75; } 100% { left: 100%; opacity: 0; } }
@keyframes beam-sweep-r { 0% { left: 100%; opacity: .95; } 85% { opacity: .75; } 100% { left: -3px;  opacity: 0; } }

@media (prefers-contrast: more) {
  :root {
    --fg-2: #e7ebf6;
    --fg-3: #bcc3d6;
    --line: rgba(255, 255, 255, .3);
    --line-soft: rgba(255, 255, 255, .2);
    --grain-o: 0;
    --scan-o: 0;
  }
  .plate__media img { filter: none; }
  .plate__flood { display: none !important; }
  .js-bloom .plate:not(.plate--soon):is(:hover, :focus-within) .plate__media > img { filter: none; }
  .plate__wash, .plate__tone { opacity: .4; }
  .plate__num, .footer__ghost { -webkit-text-stroke-color: rgba(255, 255, 255, .3); }
}

@media print {
  .field, .hud, .console, .calm-toggle, .ticker, .spine, .hero__cue, .rail,
  .footer__ghost, .plate__marks, .plate__peek, .plate__flood { display: none; }
  html, body { background: #fff; color: #000; }
  h1, h2, .plate__title, .mark__line { color: #000; }
  @supports (-webkit-background-clip: text) {
    .plate__title { background: none; -webkit-text-fill-color: #000; }
  }
  .mark__line::before, .mark__line::after,
  .footer__mark::before, .footer__mark::after { display: none; }
  .index { display: block; }
  .plate { page-break-inside: avoid; border-top: 1px solid #999; }
  .plate__wash, .plate__tone, .plate__media::after { display: none; }
  .plate__media img { filter: none; }
  .plate__num { -webkit-text-stroke-color: #bbb; }
  .plate__desc, .colophon, .hero__blurb { color: #222; }
}
