/* ============================================================================
   lewonn — poster build.

   Vocabulary taken from the references: a black ground, a tight modular grid
   of bordered and labelled cells, 1-bit dithered imagery, outlined display
   type, and HUD furniture (corner brackets, hatch fills, tick
   rails, wireframe dials). Dense on purpose — the grid is the design.
   ========================================================================== */

@font-face{font-family:Archivo;src:url('assets/archivo-regular.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:Archivo;src:url('assets/archivo-semibold.woff2') format('woff2');font-weight:600;font-display:swap}
@font-face{font-family:Archivo;src:url('assets/archivo-black.woff2') format('woff2');font-weight:900;font-display:swap}
@font-face{font-family:'Plex Mono';src:url('assets/plex-mono.woff2') format('woff2');font-weight:400;font-display:swap}
/* The scrollbar's room is kept whether or not the page is long enough to
   need it yet. Pages are sized against their width, so a scrollbar arriving
   a moment after the first paint made every title jump about one per cent. */
html{scrollbar-gutter:stable}

/* Strict monochrome. --accent is kept as a token because six rules ask for
   emphasis, but it resolves to the ink colour: there is no hue anywhere on
   the site. */
:root{
  color-scheme:dark;
  --bg:#000; --ink:#fff;
  --dim:#9a9a9a; --faint:#787878;
  --line:#2c2c2c; --line-hi:#4a4a4a;
  --accent:#fff;
  --hover:#111;                     /* row and panel hover tint */
  --fade-mid:rgba(0,0,0,.60); --fade-end:#000;
  /* The scrims that keep type legible over the figure. Held as a bare
     triplet because they are used at half a dozen different opacities, and
     a colour cannot carry its own alpha into rgba(). They were #000
     outright, which is correct while the figure is white dust on black and
     paints a dark slab over it the moment it is not. */
  --scrim:0,0,0;
  --stage-ink:#fff; --stage-dim:#b8b8b8;
  --mono:'Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  --sans:Archivo,'Helvetica Neue',Arial,sans-serif;
  --gap:1px;
  --ease:cubic-bezier(.16,1,.3,1);
}

/* The media surfaces -- the dithered plate, the video stage, the plugin
   shell, the cabinet -- stay black in both themes. They are screens set into
   the page, so the one-bit artwork and the white sheen over it keep working
   without being inverted.

   Dark is the site, light is an option. This used to follow the system,
   which on a machine set to light gave the front page a white header over a
   black slab: the figure's canvas is filled with opaque black whatever the
   page around it is doing, and nothing here could reach it. So the theme is
   now something the visitor asks for, the attribute is set in the head
   before the first paint, and the figure is dealt with further down. */
/* Paper, not white: pure #fff read as blinding. Every grey below is set
   against this ground, and the faintest had to darken with it to stay above
   4.5:1. The figure reads --bg and --ink to paint itself, so it follows. */
:root[data-theme="light"]{
  color-scheme:light;
  --bg:#e6e5e2; --ink:#111111;
  --dim:#5c5c5c; --faint:#646464;
  --line:#cfcecb; --line-hi:#a3a2a0;
  --accent:#111111;
  --hover:#dcdbd8;
  --fade-mid:rgba(230,229,226,.60); --fade-end:#e6e5e2;
  --scrim:230,229,226;
  --stage-ink:#111111; --stage-dim:#494949;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--sans);font-size:16px;line-height:1.45;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
:root{--measure:1700px}
/* Wide screens get the 1920 layout, zoomed: a 27" looks like a 24", just larger. */
@media (min-width:2200px){html{zoom:1.15}}
@media (min-width:2400px){html{zoom:1.333}}
@media (min-width:3200px){html{zoom:1.75}}
a{color:inherit;text-decoration:none}
img{max-width:100%;display:block}
::selection{background:var(--ink);color:var(--bg)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.skip{position:absolute;left:-9999px}
.skip:focus{left:1rem;top:1rem;z-index:20;background:var(--ink);color:var(--bg);padding:.6rem 1rem}

/* Full bleed: rules and rails run the whole width of the window while the
   content inside them stays centred and capped, so nothing is framed by a
   margin of the same colour as the page. */
/* The sheet is a centred document; the space beside it is split into two
   grounds so the margin reads as a work surface rather than dead space. */
/* Full bleed, like every other page. The measure still governs where the
   type sits -- .pad centres it -- but the field, the rails and the hairlines
   now run to the window edge instead of stopping in a 1700px box with black
   either side of it. */
/* 100vh on a phone is the viewport with the browser's toolbars pretended
   away, so the page runs under them and jumps when they slide. dvh is the
   space actually there. The vh line stays first for anything too old to
   know the unit. The stage below already did this with svh; these two were
   simply missed. */
.sheet{width:100%;min-height:100vh;min-height:100dvh;
  display:flex;flex-direction:column}
.sheet > main{flex:1}

/* ---- Shared label type ---------------------------------------------------- */
.spacer{flex:1}
.lbl{font-family:var(--mono);font-size:.625rem;letter-spacing:.22em;
  text-transform:uppercase;color:var(--faint);line-height:1}
.lbl.on{color:var(--ink)}
.acc{color:var(--accent)}

/* ---- Rails: the thin strips top and bottom -------------------------------- */
.rail{display:flex;align-items:center;gap:1rem;padding-block:.65rem;
  border-bottom:1px solid var(--line);overflow:hidden;white-space:nowrap}
.rail.bottom{border-bottom:0;border-top:1px solid var(--line)}
.rail .grow{flex:1;min-width:0}
/* A tick rail fills dead horizontal space instead of leaving it empty. */
.ticks{height:9px;background-image:repeating-linear-gradient(to right,
  var(--line-hi) 0 1px,transparent 1px 7px)}

/* ---- Navigation ----------------------------------------------------------- */
.nav{display:flex;flex-wrap:wrap;gap:0;border-bottom:1px solid var(--line)}
.nav a{font-family:var(--mono);font-size:.625rem;letter-spacing:.22em;
  text-transform:uppercase;color:var(--dim);padding:.8rem 1.1rem;
  border-right:1px solid var(--line);transition:background .2s,color .2s}
/* Inverted hovers only where something really hovers: on a touch screen a tap
   leaves :hover stuck, so a toggled-off button stayed white. */
@media(hover:hover){.nav a:hover{background:var(--ink);color:var(--bg)}}
.nav a[aria-current="page"]{color:var(--ink)}
.nav a[aria-current="page"]::before{content:'\25B8\00a0'}


/* ---- Display type --------------------------------------------------------- */
.title{margin:0;font-weight:900;text-transform:uppercase;letter-spacing:-.045em;
  line-height:.85;font-size:clamp(1.9rem,5.2vw,4.6rem)}
.title .out{color:transparent;-webkit-text-stroke:1.5px var(--ink)}
.huge{margin:0;font-weight:900;text-transform:uppercase;letter-spacing:-.05em;
  line-height:.82;font-size:clamp(2.6rem,9vw,7.5rem)}

/* ---- Hatch and wireframe furniture ---------------------------------------- */
.hatch{background-image:repeating-linear-gradient(-45deg,
  var(--ink) 0 3px,transparent 3px 11px);opacity:.85}
.hatch.dim{background-image:repeating-linear-gradient(-45deg,
  var(--line-hi) 0 3px,transparent 3px 11px);opacity:1}
.fade-bar{height:14px;background:linear-gradient(to right,var(--ink),transparent)}

.dial{width:100%;max-width:150px;margin-inline:auto;aspect-ratio:1;display:block}
.dial circle,.dial line,.dial path{fill:none;stroke:var(--ink);stroke-width:1}
.dial .thin{stroke:var(--line-hi)}
.dial .spin{transform-origin:50% 50%;animation:spin 14s linear infinite}
.dial .spin-slow{transform-origin:50% 50%;animation:spin 34s linear infinite reverse}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---- Imagery: 1-bit, never softened --------------------------------------- */
/* Generated at roughly 2x their display width. `pixelated` is deliberately
   NOT used: these are always shown smaller than their natural size, and
   nearest-neighbour downscaling aliases a dither into mush. Smooth
   downscaling keeps the grain and makes the panel lettering readable. */
.bw{width:100%;height:auto;display:block}
.bw-crop{width:100%;object-fit:cover;object-position:center}
.shot{position:relative;overflow:hidden;background:var(--bg)}
/* A scan line passes over the plate, the one moving thing on the sheet. */
.shot::after{content:'';position:absolute;left:0;right:0;height:22%;
  background:linear-gradient(to bottom,transparent,#ffffff14 55%,transparent);
  animation:scan 6.5s var(--ease) infinite}
@keyframes scan{0%{top:-25%}55%,100%{top:105%}}

/* ---- Specs ---------------------------------------------------------------- */
.spec{display:grid;grid-template-columns:auto 1fr;font-family:var(--mono);
  font-size:.625rem;letter-spacing:.14em;text-transform:uppercase;
  font-variant-numeric:tabular-nums}
.spec dt{color:var(--faint);padding-block:.5rem;border-bottom:1px solid var(--line)}
.spec dd{margin:0;color:var(--ink);text-align:right;padding-block:.5rem;
  border-bottom:1px solid var(--line)}
.spec dt:last-of-type,.spec dd:last-of-type{border-bottom:0}

/* ---- Buttons -------------------------------------------------------------- */
.btn{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.22em;
  text-transform:uppercase;padding:1.05rem 1.1rem;
  background:var(--ink);color:var(--bg);
  /* Chamfer: the cut corner the HUD kit uses on every plate. */
  clip-path:polygon(0 0,100% 0,100% calc(100% - 11px),calc(100% - 11px) 100%,0 100%);
  transition:background .25s,color .25s}
.btn:hover{background:var(--accent);color:var(--bg)}
/* Same trap as the cabinet button: a class setting display beats the
   browser's [hidden] rule, so it has to be restated. */
.btn[hidden]{display:none}
.btn.ghost{background:transparent;color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--line-hi)}
@media(hover:hover){.btn.ghost:hover{background:var(--ink);color:var(--bg)}}

/* ---- Big index links ------------------------------------------------------ */
.jump{display:flex;align-items:center;gap:1rem;padding:clamp(.9rem,2.2vw,1.5rem) .9rem;
  font-weight:900;text-transform:uppercase;letter-spacing:-.04em;line-height:.9;
  font-size:clamp(1.5rem,4.2vw,3rem);color:var(--ink);
  transition:background .25s,color .25s,padding-left .35s var(--ease)}
.jump .n{font-family:var(--mono);font-size:.625rem;letter-spacing:.22em;
  font-weight:400;color:var(--faint);align-self:flex-start}
.jump .go{margin-left:auto;font-size:.5em}
.jump.sm{font-size:clamp(1.15rem,2.4vw,1.85rem);padding-block:clamp(.75rem,1.7vw,1.1rem)}
@media(hover:hover){.jump:hover{background:var(--ink);color:var(--bg);padding-left:1.6rem}}
.jump:hover .n{color:var(--bg)}


@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition:none!important}
  .js-anim .cell{opacity:1!important;clip-path:none!important}
}

/* ---- Narrow: the grid folds to fewer columns, never to nothing ------------ */
@media(max-width:900px){
  .s9,.s8,.s7,.s6,.s5{grid-column:span 12}
  .s4,.s3{grid-column:span 6}
  .s2{grid-column:span 4}
}
@media(max-width:520px){
  .s4,.s3,.s2{grid-column:span 12}
  .rail{flex-wrap:wrap;white-space:normal}
  .nav a{flex:1 1 auto;text-align:center}
}


/* ============================================================================
   Home: the wordmark is the page. Letters lean toward the pointer on a spring
   and split into a chromatic echo, carried over from the first build.
   ========================================================================== */
.home{display:flex;flex-direction:column;
  min-height:calc(100vh - 2.5rem);min-height:calc(100dvh - 2.5rem)}
/* The header runs edge to edge: the name top left, the menu top right,
   at the window's own margins. Only the page below sits in the column. */
.top{display:flex;align-items:center;gap:1.5rem;padding-block:.9rem;
  padding-inline:clamp(1rem,2.4vw,2.2rem);
  border-bottom:1px solid var(--line);flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:.6rem;font-weight:900;font-size:1rem;
  letter-spacing:-.02em}
.brand i{width:17px;height:17px;flex:none;rotate:45deg;display:grid;
  grid-template-columns:1fr 1fr;gap:2px;transition:rotate .9s var(--ease)}
.brand i b{background:var(--ink)}
.brand:hover i{rotate:135deg}
/* The header stays in reach: it slides away while you read down the page and
   comes back the moment you scroll up (theme.js sets .away). */
header.top{position:sticky;top:0;z-index:40;background:var(--bg);
  transition:transform .35s var(--ease)}
header.top.away{transform:translateY(-100%)}
@media (prefers-reduced-motion:reduce){header.top{transition:none}}
/* The menu, top right and nowhere else. */
.menu{margin-left:auto;display:flex;flex-wrap:wrap;gap:1.6rem}
.menu a,.menu .theme{font-family:var(--mono);font-size:.625rem;letter-spacing:.22em;
  text-transform:uppercase;color:var(--dim);position:relative;padding-block:.25rem;
  transition:color .25s;display:inline-flex;align-items:center;justify-content:center;
  min-width:24px;min-height:24px}
/* The button borrows the links' rule rather than getting one of its own, so
   it cannot drift away from them. Only the things a button brings with it --
   a background, a border, its own font -- have to be taken back off. */
.menu .theme{background:0;border:0;padding-inline:0;cursor:pointer;
  font-weight:inherit;line-height:inherit}
.menu a::after,.menu .theme::after{content:'';position:absolute;left:0;right:100%;
  bottom:0;height:1px;background:var(--accent);transition:right .4s var(--ease)}
.menu a:hover,.menu .theme:hover{color:var(--ink)}
.menu a:hover::after,.menu .theme:hover::after{right:0}

/* The wordmark sits in a cell on the work index, not on a page of its own. */
.wordstage{position:relative;overflow:hidden;display:flex;flex-direction:column;
  justify-content:center;gap:.9rem;padding:clamp(1.3rem,3vw,2.4rem);min-height:clamp(180px,22vw,300px)}
/* position:relative matters: it makes the word the offsetParent of its own
   letters, so their offsetLeft is measured from the word and not the stage.
   Without it the measured centres are doubled and the pointer never bites. */
.word-wrap{position:relative;display:inline-flex;align-items:flex-start;
  gap:clamp(.5rem,1.2vw,1.1rem);width:max-content}
.word{position:relative;margin:0;display:flex;align-items:baseline;font-weight:900;
  letter-spacing:-.07em;line-height:.85;font-size:clamp(2.8rem,11vw,15rem);
  -webkit-user-select:none;user-select:none;width:max-content}
.word > span{display:inline-block;position:relative;will-change:transform}
/* The l carries its own side space, about 0.057em at this weight, so its ink
   started right of the line beneath it. Pulled back by that much, the name
   and the line start on the same edge. */
.wordstage .word{margin-left:-.057em}
.face{position:relative;display:block;transform-origin:50% 85%;
  backface-visibility:hidden}
/* Two stroked copies, clipped above and below the midline and pushed apart:
   the split reads as signal error rather than a drop shadow. */
.face::before,.face::after{content:attr(data-g);position:absolute;inset:0;
  color:rgba(255,255,255,.07);
  -webkit-text-stroke:1.25px rgba(255,255,255,.82);
  /* Blurred rather than drawn hard: a 1px stroke clipped at the midline reads
     as a second letter behind the first. Softened, the two halves part like
     something thick being pushed aside. */
  /* A soft edge with the bloom behind it, rather than one blurred smear:
     the stroke keeps the letter's shape and the shadow does the glass. */
  filter:blur(.55px) drop-shadow(0 0 7px rgba(255,255,255,.38));
  pointer-events:none;will-change:transform,opacity;
  opacity:var(--echo-opacity,0)}
/* Feathered across the middle instead of cut, so there is no seam where the
   halves meet. */
.face::before{
  -webkit-mask-image:linear-gradient(to bottom,#000 20%,transparent 62%);
  mask-image:linear-gradient(to bottom,#000 20%,transparent 62%);
  transform:translate3d(var(--echo-x,0px),-3px,0) scale(1.018)}
.face::after{
  -webkit-mask-image:linear-gradient(to top,#000 20%,transparent 62%);
  mask-image:linear-gradient(to top,#000 20%,transparent 62%);
  transform:translate3d(calc(var(--echo-x,0px) * -1),3px,0) scale(1.018)}

.home-foot{display:flex;align-items:center;gap:1.5rem;padding-block:.9rem;
  border-top:1px solid var(--line);flex-wrap:wrap}

@media(pointer:coarse){.face::before,.face::after{display:none}}
@media(prefers-reduced-motion:reduce){
  .word > span{transform:none!important}
  .face::before,.face::after{opacity:0!important}
}


/* ============================================================================
   The resolving plate.

   Three registers of the same image are stacked: a coarse dither at rest, a
   finer one under it, and the full continuous-tone plate on top. Radial masks
   centred on the cursor cut holes through the upper layers, so the image
   sharpens wherever you look and falls back to dots everywhere else. The dot
   treatment costs no detail — the detail is simply held one layer down.
   ========================================================================== */
@property --x{syntax:'<percentage>';inherits:true;initial-value:50%}
@property --y{syntax:'<percentage>';inherits:true;initial-value:42%}
/* Registered so the radii can be transitioned rather than snapping open. */
@property --r-clean{syntax:'<length>';inherits:true;initial-value:0px}
@property --r-fine{syntax:'<length>';inherits:true;initial-value:0px}
/* The radii come from script, measured off the plate itself: an element
   that establishes a container cannot query its own size, and a fixed
   clamp left a lens wider than the plate once it moved onto a card. */
.plate{position:relative;overflow:hidden;background:#000;
  --x:50%;--y:42%;--r-clean:0px;--r-fine:0px;
  transition:--r-clean .26s var(--ease),--r-fine .32s var(--ease)}
/* Held down, the lens takes its time opening out to the edges: it is
   meant to feel like something developing, not like a switch. */
/* Its own curve, not the site's: --ease is a hard ease-out that spends most
   of its travel in the first fraction of a second, which reads as a snap
   however long the duration is. This one grows steadily instead. */
.plate.pressing{transition:--r-clean .42s cubic-bezier(.45,.02,.5,1),
  --r-fine .5s cubic-bezier(.45,.02,.5,1)}
/* Closed at rest: the plate holds one uniform dither until it is hovered. */
.plate.live{--r-clean:var(--rc,90px);--r-fine:var(--rf,170px)}
.plate img{display:block;width:100%;height:auto}
.plate .layer{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* The upper layers exist only to be masked. Without mask support they would
   simply cover the dither with the full-colour plate, so they stay hidden
   unless the browser can actually cut a hole in them. */
.plate .fine,.plate .clean{display:none}
@supports (mask-image:radial-gradient(circle 9px at 0 0,#000,transparent)) or
          (-webkit-mask-image:radial-gradient(circle 9px at 0 0,#000,transparent)){
  .plate .fine,.plate .clean{display:block}
  /* Two mask layers, not max() of two radii: the layers composite as a
     union, so each lens opens the drawing on its own terms and neither can
     cancel the other. The scroll's bloom comes first and is pinned to the
     middle of the plate -- it is the drawing arriving, and a visitor moving
     the mouse should not be able to drag it about. The cursor's lens comes
     second and is the only one that follows the pointer. */
  /* The grey register answers to the cursor alone. The scroll used to open
     it from the middle, which swapped the line work there for a smooth
     photograph -- so the drawing's own treatment stopped wherever the scroll
     reached. Now the line work holds edge to edge until the pointer lands. */
  .plate .fine{filter:grayscale(1);
    -webkit-mask-image:
      radial-gradient(circle var(--r-fine) at var(--x) var(--y),#000 0 42%,transparent 100%);
    mask-image:
      radial-gradient(circle var(--r-fine) at var(--x) var(--y),#000 0 42%,transparent 100%)}
  /* One layer, and it is the cursor's. The scroll does not open this
     register at all: colour arrives where you look and nowhere else. */
  .plate .clean{
    -webkit-mask-image:
      radial-gradient(circle var(--r-clean) at var(--x) var(--y),#000 0 46%,transparent 100%);
    mask-image:
      radial-gradient(circle var(--r-clean) at var(--x) var(--y),#000 0 46%,transparent 100%)}
}


/* ---- The field ----------------------------------------------------------
   The first screen is the site's own material rather than a product: the
   cabinet's music as a spectrogram, dithered like everything else. It stays
   black in both themes, as the other media surfaces do, so the type over it
   is white either way. */
/* Sized against the window rather than its own width, so it fills the screen
   on a wide display instead of becoming a letterbox strip. */
/* Holding a finger on the figure is how it is played (the collapse), so a
   long press must not start a text selection or the phone's callout menu. */
.stage{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
.stage{position:relative;isolation:isolate;overflow:hidden;background:#000;
  color:var(--stage-ink);border-bottom:1px solid var(--line);
  /* A phone cannot make the name wider than the screen, so keeping the
     desktop height there strands it in empty black. Taller only once
     there is width for the name to grow into. */
  min-height:var(--stage-h,clamp(300px,46vh,460px));
  display:flex;flex-direction:column;justify-content:flex-end}
/* ---- The descent --------------------------------------------------------
   One backdrop behind the first screen and the work below it, so the figure
   carries on through the scroll rather than stopping at the rail. It is
   masked to fade over the band and be gone by the time the plate arrives:
   the plate is a drawing of its own and does not want a second one behind
   it. The fade is measured in the same units as the stage's height, so the
   solid part always covers the first screen exactly. */
.descent{position:relative;background:var(--bg);
  /* Four fifths of a screen, not all of it. A whole screen gave the figure
     the room it wanted but closed the page off: nothing below the fold
     showed, so there was no reason to scroll. At this height the rail and
     the top edge of the plate sit in view and do the inviting, while the
     figure keeps the full width and nearly all the height it had.
     svh rather than vh so a phone's retracting toolbar does not crop it. */
  --stage-h:76vh;--stage-h:76svh}
.descent > .stage{position:relative;z-index:1}
/* Transparent now: the black belongs to the wrapper, and a background here
   would paint over the very thing it is meant to show. */
.stage{background:0}
/* Full strength through the first screen, then most of the way down at the
   fold and trailing to nothing before the plate. Holding it at full past the
   fold put the figure's heaviest lobes directly behind the rail, where they
   competed with the label instead of leading the eye past it. A mask takes
   alpha, so the middle stop does the dimming and no second overlay is
   needed. */
/* Only as tall as the mask lets anything show. Spanning the whole wrapper
   meant every frame faded and redrew the entire Projects band behind a mask
   that had already taken it to zero -- invisible work, and it cost 24ms a
   frame on a wide window. The height matches the last stop in the gradient
   below, so nothing that is computed goes unseen. */
.field{position:absolute;left:0;right:0;top:0;z-index:0;
  /* Long enough to cover the gap opened above the drawing. Left at 14vh it
     stopped in the middle of that gap and the rest was flat background,
     which is the bland edge this figure was extended to get rid of in the
     first place. */
  height:calc(var(--stage-h) + 24vh)}
/* The fade is a black gradient laid over the canvas, not a mask cut out of
   it. On this background the two are indistinguishable, and the mask was
   not: compositing one over two megapixels every frame doubled the frame
   time, from 12ms to 24ms, for a result the eye cannot tell apart. The
   stops mirror what the mask did -- clear through the first screen, sixty
   per cent black at the fold, solid before the plate. */
/* The fade under the figure ends in whatever the page is, so it is two
   tokens rather than a hard black. The figure itself is inverted in its own
   buffer -- see index.html. A filter on this wrapper did the whole job in
   one line and was measured at 161.6 frames a second down to 62.1: the
   compositor re-filtered a one-and-a-half megapixel canvas on every frame.
   An exclusive-or against the level costs nothing and is invisible in the
   frame time. */
.field::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to bottom,transparent 0,
    transparent calc(var(--stage-h) - 8vh),var(--fade-mid) var(--stage-h),
    var(--fade-end) calc(var(--stage-h) + 24vh))}

.field canvas{display:block;width:100%;height:100%}
/* The name carries its own shadow now (see index.html), so no band is laid
   across the stage. The
   line under it gets a tight shadow of the same colour for the same job. */
.word.back{position:absolute;left:0;top:0;z-index:-1;pointer-events:none}
.word.back .face{color:transparent;text-shadow:0 0 .02em rgb(var(--scrim)),
  0 0 .05em rgb(var(--scrim)),0 0 .1em rgba(var(--scrim),.8)}
.word.back .face::before,.word.back .face::after{display:none}

/* The lift is in vh, but the stage is 46vh on a phone and 64vh above that,
   so the same vh figure lifts the cluster a third of the way up a short
   stage and only a quarter of the way up a tall one -- which on a phone put
   the type in the middle of the frame, where the scrim is weakest and the
   figure is brightest. Bring the phone back to the same fraction. */
@media(max-width:700px){
  .stage-copy{padding-bottom:clamp(2rem,19vh,12rem)}
}

/* The cluster is the name, then a line under it, and the two have to read as
   one thing standing in the frame rather than as a caption stuck to a word.
   At 2559 wide the wordmark sets at 176px and the gap beneath it computed to
   14px, which is less air than sits between two lines of body text: the
   sentence read as if it had fallen against the name. Both measures are set
   against the viewport's height so they grow with the space they divide. */
/* The name sits on the page's one left edge, the same as everything under
   it. It is still off the figure's axis -- a column's left edge is well
   left of centre -- so the type keeps something to push against. */
.stage-copy{position:relative;z-index:2;justify-content:flex-end;
  align-items:flex-start;text-align:left;gap:clamp(1.5rem,3.4vh,3.2rem);
  /* Sat on the floor of the stage the cluster had seven times as much room
     above it as below, which reads as falling out of the frame rather than
     standing in it. Lifted to roughly two above to one below: off centre,
     because a block centred exactly in a frame looks low, but composed. The
     measure is in vh because the stage is, so the ratio holds as the window
     changes. */
  /* Measured against the stage, which is now a whole screen rather than
     two thirds of one. The old figure was tuned for the shorter stage and
     left the cluster sitting at nearly four to one -- the room above it
     grew and the room below it did not. */
  /* Retuned for a stage that is no longer a whole screen. At 24vh the
     space below the cluster was nearly equal to the space above it on a
     short laptop window -- 1.14 to 1, which reads as centred, and a block
     centred in a frame looks low. */
  padding-bottom:clamp(2.5rem,18vh,18rem)}
.stage-line{margin:0;max-width:56ch;color:var(--stage-dim);line-height:1.7;
  text-shadow:0 1px 3px rgb(var(--scrim)),0 0 12px rgb(var(--scrim)),0 0 28px rgb(var(--scrim));
  font-size:clamp(1rem,1.4vw,1.2rem)}
.stage-line .acc{color:var(--stage-ink)}
/* The five things the line can say are stacked in one grid cell, so the
   paragraph is as tall as the longest of them and nothing below it moves
   when they change. Laying them out in flow and hiding four would make the
   name jump every seven seconds. */
.stage-line{display:grid}
.say{grid-area:1/1;opacity:0;transition:opacity .7s var(--ease),visibility .7s}
.say.on{opacity:1}
.say:not(.on){visibility:hidden}
/* The hidden link reads as the line it is: no underline, only the pointer. */
.say a{color:inherit;text-decoration:none}
@media(prefers-reduced-motion:reduce){
  .say{transition:none}
}


/* The route out, repeated where the reading ends. The rail carries the same
   link, but on a wide screen the rail's copy is more than a thousand pixels
   from the last row, so finishing the list left you with nowhere to go but
   back up to the top right. Same words as the rail on purpose: one label per
   destination, wherever it appears. */

/* ---- From the log -------------------------------------------------------
   A date column and a body column, hairline between rows. The date is set in
   the mono face so a row with a real date and a row labelled by subject line
   up on the same left edge and read as the same kind of thing. */
.log-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.log-row{position:relative;display:grid;
  grid-template-columns:minmax(8.5rem,10rem) minmax(0,1fr);
  gap:clamp(1rem,2.4vw,2.4rem);
  padding-block:clamp(1.3rem,2.6vw,2rem);
  border-top:1px solid var(--line)}
.log-row:first-child{border-top:0}
/* The whole row is the target, not just the title: a link the width of two
   words inside a row four inches tall is a smaller thing to hit than it
   looks. The title keeps the href so it still reads as the link. */
.log-link::after{content:'';position:absolute;inset:0}
.log-row{transition:padding-left .3s var(--ease)}
/* Pointed at, a bar grows down the left edge and the row steps in past it.
   No tint and no underline: the row is the target, and the bar says so. */
.log-row::after{content:'';position:absolute;left:0;top:.6rem;bottom:.6rem;width:3px;
  background:var(--ink);transform:scaleY(0);transition:transform .3s var(--ease)}
.log-row:hover,.log-row:focus-within{padding-left:1rem}
.log-row:hover::after,.log-row:focus-within::after{transform:scaleY(1)}
.log-when{padding-top:.35rem;color:var(--faint);white-space:nowrap}
.log-text{min-width:0}
.log-title{margin:0 0 .5rem;font-size:clamp(1.05rem,1.5vw,1.35rem);
  line-height:1.25}
.log-link{color:var(--ink);text-decoration:none}
.log-text p{margin:0;color:var(--dim);max-width:62ch;line-height:1.7}
@media(max-width:700px){
  .log-row{grid-template-columns:minmax(0,1fr);gap:.55rem}
  .log-when{padding-top:0}
}

@media (prefers-reduced-motion:reduce){
  .t-lead .plate{--r-fine:200vmax;--r-clean:200vmax}
}

.reach p{margin:0 0 1.2rem;color:var(--dim);max-width:52ch}
.reach-row{display:flex;flex-wrap:wrap;align-items:center;gap:1.4rem}


.plate-note{display:flex;align-items:center;gap:.8rem;padding:.65rem .85rem;
  border-top:1px solid var(--line)}

@media(prefers-reduced-motion:reduce){
  .plate:not(.live){animation:none}
}


/* ============================================================================
   One continuous page. Sections are separated by hairlines and announced by a
   thin label rail — no boxed cells, no decorative filler panels.
   ========================================================================== */
.pad{padding-inline:max(clamp(1rem,3.5vw,2.75rem),
                       calc((100% - var(--measure)) / 2))}
.band{border-top:1px solid var(--line)}
/* A second band of cards under the first needs to read as its own group,
   not as more of the one above it. */
.band-next{margin-top:clamp(1.8rem,4.5vw,3.2rem)}

/* The label sits left, the link right. A tick rail used to hold them
   apart with flex:1; it read as a row of pipes in the first screen and
   the gap does the same job with nothing in it. */
.band-rail{display:flex;align-items:center;justify-content:space-between;
  gap:1rem;min-height:38px;
  border-bottom:1px solid var(--line);white-space:nowrap;overflow:hidden}
/* The first screen is not framed. Its bottom rule sat directly over the
   Projects heading and read as a strip with the rule beneath it, and without
   that the side rules just stopped in mid-air where the screen ends. */
.front .stage{border:0}
.band-in{padding-block:clamp(2.25rem,6vw,4.75rem)}

/* ---- Identity -------------------------------------------------------- */
.intro{position:relative;display:flex;flex-direction:column;gap:1.1rem;
  padding-block:clamp(3rem,8vw,6rem)}
/* No ch cap here: the ch unit ignores letter-spacing, and this text is set
   with .22em of it, so a ch-based width wraps far earlier than it looks. */
.intro p{margin:0;max-width:44rem}

/* ---- Product: the plate sits beside its specification ----------------- */
/* The first track is the plate's own width, so no slack opens up between the
   image and the specification when the window is wide. */
.unit{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.62fr);
  gap:clamp(1.5rem,4vw,3rem);align-items:start}
/* Capped well under the source width so the plate is never upscaled. */
.plate-wrap{max-width:880px;width:100%;position:relative;
  border:1px solid var(--line)}
.unit-side{display:flex;flex-direction:column;gap:1.1rem}
.unit-side h2{margin:0;font-weight:900;text-transform:uppercase;
  letter-spacing:-.05em;line-height:.85;font-size:clamp(2.4rem,5.5vw,4rem)}
.unit-side .blurb{margin:0;color:var(--dim);font-size:.9375rem;max-width:42ch}
.buttons{display:flex;flex-direction:column;gap:1px;margin-top:.4rem}

/* ---- One link list, used on every page -------------------------------- */
.links{border-top:1px solid var(--line)}
.links a{display:flex;align-items:center;gap:1rem;
  padding-block:clamp(.95rem,2.4vw,1.5rem);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),
                     calc((100% - var(--measure)) / 2));
  border-bottom:1px solid var(--line);
  font-weight:900;text-transform:uppercase;letter-spacing:-.04em;line-height:.95;
  font-size:clamp(1.35rem,3.6vw,2.6rem);color:var(--ink);
  transition:background .3s var(--ease),color .3s var(--ease),
             padding-left .45s var(--ease)}
.links a .n{font-family:var(--mono);font-size:.625rem;letter-spacing:.22em;
  font-weight:400;color:var(--faint);align-self:center;min-width:2.2em}
.links a .go{margin-left:auto;font-size:.5em;color:var(--faint)}
@media(hover:hover){.links a:hover{background:var(--ink);color:var(--bg);
  padding-left:calc(max(clamp(1rem,3.5vw,2.75rem),
                    calc((100% - var(--measure)) / 2)) + .9rem)}}
.links a:hover .n,.links a:hover .go{color:var(--bg)}

/* ---- Sub-page furniture, sharing the same system ---------------------- */
.page-head{padding-block:clamp(2rem,5vw,3.25rem) clamp(1.25rem,2.5vw,1.75rem)}
.page-head h1{margin:0;font-weight:900;text-transform:uppercase;
  letter-spacing:-.05em;line-height:.88;font-size:clamp(2.6rem,8vw,5.5rem)}
.page-head p{margin:1.15rem 0 0;color:var(--dim);max-width:62ch}
.page-head .status{margin:0 0 1rem;color:var(--dim)}

/* A dl carries a default margin, which pushed this box below the one
   next to it. */
.specs-block{margin:0;display:grid;grid-template-columns:auto minmax(0,1fr);gap:0 1.25rem;
  border:1px solid var(--line);padding:clamp(1.1rem,3vw,1.6rem);
  font-family:var(--mono);font-size:.625rem;letter-spacing:.14em;
  text-transform:uppercase;font-variant-numeric:tabular-nums;align-content:start}
.specs-block dt{color:var(--faint);padding-block:.55rem;border-bottom:1px solid var(--line)}
.specs-block dd{margin:0;color:var(--ink);text-align:right;padding-block:.55rem;
  border-bottom:1px solid var(--line);overflow-wrap:anywhere}
.specs-block dt:last-of-type,.specs-block dd:last-of-type{border-bottom:0}
.specs-block dd.spec-description{text-transform:none}

/* Left at the default stretch: these sit side by side and reading as one pair
   matters more than each ending where its own content does. Their insides are
   pinned to the top, so the taller one only adds empty space at the bottom. */
.grid-2{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,290px),1fr));
  gap:clamp(1.25rem,3vw,2rem)}
.traits{display:grid;gap:1px;background:var(--line);border:1px solid var(--line);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,180px),1fr))}
.traits div{background:var(--bg);padding:clamp(1rem,2.5vw,1.4rem);
  transition:background .3s var(--ease)}
.traits div:hover{background:var(--hover)}
.traits h3{margin:0 0 .45rem;font-family:var(--mono);font-size:.625rem;
  letter-spacing:.22em;text-transform:uppercase;color:var(--faint);font-weight:400}
.traits p{margin:0;font-weight:600}

/* Architecture: open decision columns, with the same display type, fine
   rules and quieter body copy as the rest of the site. */
.arch-why .traits{background:none;border:0;gap:clamp(1.5rem,3.5vw,3.5rem);
  grid-template-columns:repeat(3,minmax(0,1fr))}
.arch-why .traits > div{min-width:0;padding:0;background:none;transition:none}
.arch-why .traits h3{font-family:var(--sans);font-weight:900;
  font-size:clamp(1.4rem,2vw,2rem);line-height:1.05;letter-spacing:-.035em;
  text-transform:uppercase;color:var(--ink);text-wrap:balance;min-height:2.1em;
  margin:0 0 1.1rem;padding-bottom:1.1rem;border-bottom:1px solid var(--line-hi)}
.arch-why .traits p{font-weight:400;font-size:1rem;line-height:1.7;
  color:var(--dim);max-width:46ch;margin:0}
@media(max-width:900px){
  .arch-why .traits{grid-template-columns:1fr;gap:2rem}
  .arch-why .traits h3{font-size:1.6rem;min-height:0;padding-bottom:1rem;margin-bottom:1rem}
}
.panel{border:1px solid var(--line);padding:clamp(1.25rem,3.5vw,2rem);
  display:flex;flex-direction:column;gap:1.1rem;align-items:flex-start}
.panel h2{margin:0;font-weight:900;text-transform:uppercase;letter-spacing:-.04em;
  line-height:1;font-size:clamp(1.5rem,3.5vw,2.1rem)}
.note{color:var(--dim);border-left:2px solid var(--line-hi);padding-left:1rem;margin:0}

/* Projects as tiles: the project's art centred over its name. Pointed at, a
   tile washes in the project's own colour (--glow, set on each tile), its
   border and tag take the colour, a shine rises behind the art and the art
   grows a touch. The gap keeps them two tiles, not one box with a line
   through it, and the margin keeps the section's rule off their tops. */
.repos{display:grid;gap:clamp(1.5rem,3vw,2.5rem);margin-top:clamp(1.25rem,2.5vw,2rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr))}
.repo{position:relative;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;
  gap:.55rem;text-align:center;min-height:clamp(24rem,40vw,34rem);overflow:hidden;isolation:isolate;
  border:1px solid var(--line-hi);padding:clamp(1.2rem,2.5vw,2rem);transition:border-color .5s var(--ease)}
.repo::before{content:'';position:absolute;inset:0;z-index:-1;opacity:0;transition:opacity .5s var(--ease);
  background:linear-gradient(to bottom,color-mix(in srgb,var(--glow) 20%,var(--bg)),var(--bg) 72%)}
.repo:hover,.repo:has(:focus-visible){border-color:var(--glow)}
.repo:hover::before,.repo:has(:focus-visible)::before{opacity:1}
.repo-art{position:absolute;inset:0;z-index:-1;margin:0;display:grid;place-items:center;
  padding-bottom:clamp(7rem,12vw,10rem)}
.repo-art::after{content:'';position:absolute;inset:0;z-index:-1;opacity:0;transition:opacity .5s var(--ease);
  background:radial-gradient(circle at 50% 36%,color-mix(in srgb,var(--glow) 24%,transparent),transparent 62%)}
.repo:hover .repo-art::after,.repo:has(:focus-visible) .repo-art::after{opacity:1}
.repo-art img{display:block;width:clamp(7rem,14vw,12rem);height:auto;transition:transform .5s var(--ease)}
.repo-art.pixel img{image-rendering:pixelated}
.repo-art.tall img{width:clamp(10rem,20vw,17rem)}   /* the cabinet is narrow in its square */
.repo:hover .repo-art img,.repo:has(:focus-visible) .repo-art img{transform:scale(1.05)}

/* The whole card goes to the project. A link cannot contain another link, so
   the name carries the href and stretches an invisible cover over the card;
   the repository link sits above that cover and keeps its own destination.
   Screen readers and the keyboard get the name as the link text, which is
   better than any label that could have gone in the corner. */
.repo-link{color:inherit;text-decoration:none}
.repo-link::after{content:'';position:absolute;inset:0}
.repo-link:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
.repo-links{position:relative;z-index:1;align-self:center}
.repo-head{display:flex;flex-direction:column;align-items:center;gap:.5rem}
.repo-head h2{margin:0;font-weight:900;text-transform:uppercase;
  letter-spacing:-.04em;font-size:clamp(2.4rem,5vw,4.6rem);line-height:.86}
.repo-head p{margin:0;color:var(--faint)}
/* Status sits with the repo name so "in development" cannot be missed. */
.tag{font-family:var(--mono);font-size:.5625rem;letter-spacing:.2em;
  text-transform:uppercase;padding:.35em .7em;margin-left:auto;
  border:1px solid var(--line-hi);color:var(--dim);white-space:nowrap}
.tag.dev{border-color:var(--accent);color:var(--accent)}
.repo .tag{position:absolute;top:clamp(1rem,2.2vw,1.8rem);right:clamp(1.2rem,2.5vw,2rem);margin:0;
  background:var(--bg);transition:border-color .5s}
.repo:hover .tag,.repo:has(:focus-visible) .tag{background:transparent;
  border-color:color-mix(in srgb,var(--glow) 60%,var(--line-hi))}
.repo-desc{margin:0 auto;color:var(--dim);max-width:62ch}
/* ---- Links and calls to action -------------------------------------------
   The markup across the site has always asked for these by name; the rules
   were never written, so every one of them was rendering as body text -- the
   download button included, which computed to a plain inline span with no
   border and no padding.

   Two weights only. .cta is the one thing a visitor is meant to do on a
   page: a solid block of ink that hollows out when the pointer is on it.
   .inline is everything else -- a link inside a sentence, or the second
   action on a card -- and stays quiet enough to read past. */
.mono{font-family:var(--mono)}

.inline{color:var(--ink);text-decoration:none;
  border-bottom:1px solid var(--line-hi);padding-bottom:.08em;
  transition:color .22s var(--ease),border-color .22s var(--ease)}
.inline:hover{border-bottom-color:var(--ink)}

/* The markup already draws the line: a link written as a label carries .mono,
   one written inside a sentence does not. Keying off that beats guessing from
   ancestors -- the colophon sits inside a .lbl and its links are still prose. */
.inline.mono{border-bottom-color:transparent;color:var(--dim);
  font-size:.625rem;letter-spacing:.2em;text-transform:uppercase;
  padding-bottom:.18rem}
.inline.mono:hover{color:var(--ink);border-bottom-color:var(--ink)}

.cta{display:inline-flex;align-items:center;gap:.6rem;
  font-family:var(--mono);font-size:.625rem;letter-spacing:.2em;
  text-transform:uppercase;text-decoration:none;white-space:nowrap;
  padding:.72rem 1.05rem;border:1px solid var(--ink);
  background:transparent;color:var(--ink);
  transition:background .22s var(--ease),color .22s var(--ease)}
@media(hover:hover){.cta:hover{background:var(--ink);color:var(--bg)}}
.cta.primary{background:var(--ink);color:var(--bg)}
.cta.primary:hover{background:transparent;color:var(--ink)}

/* A call to action standing on its own gets a little more presence than one
   sharing a card with a second link. */
.cta-row{margin:1.4rem 0 0;display:flex;flex-wrap:wrap;gap:.8rem}
.cta-row .cta{font-size:.6875rem;padding:.95rem 1.4rem}
@media(max-width:360px){.cta-row .cta{padding-inline:.65rem}}

.arrow{font-size:1.15em;line-height:0}

/* ---- The cabinet's volume -----------------------------------------------
   A native range input, restyled. The track and thumb have to be drawn
   twice, once for WebKit and once for Gecko, because the two engines expose
   different pseudo-elements and neither tolerates the other's rule in the
   same selector list -- one unknown pseudo-element voids the whole block. */
/* The buttons are display:flex, so each one claims a line of its own; the
   row has to be made by their container or the slider lands underneath,
   indented by whatever margin it was given. */
.arcade-controls{display:flex;align-items:center;flex-wrap:wrap;
  gap:clamp(.9rem,2vw,1.6rem)}
.vol{display:inline-flex;align-items:center;gap:.8rem}
.vol-read{min-width:2.2ch;text-align:right;color:var(--dim)}
.vol-range{-webkit-appearance:none;appearance:none;width:clamp(90px,18vw,150px);
  height:18px;background:0;cursor:pointer;margin:0}
.vol-range:disabled{cursor:not-allowed;opacity:.45}
.vol-range:focus-visible{outline:2px solid var(--ink);outline-offset:4px}

.vol-range::-webkit-slider-runnable-track{height:3px;background:var(--line-hi)}
.vol-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:13px;height:13px;margin-top:-5px;border:0;border-radius:50%;
  background:var(--ink)}
.vol-range::-moz-range-track{height:3px;background:var(--line-hi)}
.vol-range::-moz-range-thumb{width:13px;height:13px;border:0;border-radius:50%;
  background:var(--ink)}

.repo-links{margin:.9rem 0 0;display:flex;flex-wrap:wrap;gap:1.4rem;align-items:center}

.stage{border:1px solid var(--line)}
.stage-body{background:#000;overflow:hidden}
.stage-body img{width:100%;height:auto}
.showcase{display:flex;flex-direction:column;gap:1rem}
.showcase video{width:100%;height:auto;aspect-ratio:16/9;display:block;
  background:#000;border:1px solid var(--line)}
.showcase-side{display:flex;flex-wrap:wrap;gap:.6rem 2rem;align-items:baseline}
.showcase-side p{margin:0;color:var(--dim)}

/* Native <details> so it opens with a keyboard and without script. */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;list-style:none;display:flex;align-items:center;
  gap:1rem;padding-block:clamp(.95rem,2.2vw,1.25rem);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),
                     calc((100% - var(--measure)) / 2));
  font-weight:600;font-size:clamp(.95rem,1.6vw,1.05rem);
  transition:background .25s var(--ease)}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:'+';margin-left:auto;color:var(--faint);
  font-family:var(--mono);font-size:1.1rem}
.faq details[open] summary::after{content:'\2212'}
.faq summary:hover{background:var(--hover)}
/* Opening slides the answer down and fades it in; closing folds it back.
   Where the height cannot be animated it simply opens, as before. */
.faq details{interpolate-size:allow-keywords}
.faq details::details-content{block-size:0;overflow:clip;opacity:0;
  transition:block-size .4s var(--ease),opacity .3s ease,content-visibility .4s allow-discrete}
.faq details[open]::details-content{block-size:auto;opacity:1}
.faq details[open] .a{animation:faq-in .45s var(--ease)}
@keyframes faq-in{from{transform:translateY(-.45rem)}}
.faq summary::after{transition:transform .35s var(--ease)}
.faq details[open] summary::after{transform:rotate(180deg)}
/* The gutter padding grows with the viewport while a max-width does not, so
   with the usual border-box the two eventually meet and the content box is
   squeezed to nothing -- past about 2200px every word landed on its own line.
   Measuring the cap against the content box instead keeps the answer 74
   characters wide and still starts it under the question. */
.faq .a{box-sizing:content-box;margin:0;
  padding-block:clamp(.35rem,1vw,.6rem) clamp(1.5rem,3.2vw,2.1rem);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),
                     calc((100% - var(--measure)) / 2));
  color:var(--dim);max-width:74ch;line-height:1.85}
/* A path is long enough to wrap, and an inline background stops at the break
   by default -- the second half arrives with no padding and sits flush
   against its own edge. Cloning the decoration gives every fragment the
   whole box. */
.faq .a code{font-family:var(--mono);font-size:.85em;color:var(--ink);
  background:var(--hover);padding:.3em .55em;
  -webkit-box-decoration-break:clone;box-decoration-break:clone}




/* ---- Elsewhere: embedded players and annotated links ---------------------- */
.embed{border:1px solid var(--line);background:#000}
.embed iframe{display:block;width:100%;border:0}
/* SoundCloud's widget ships light and has no dark parameter. Inverting it and
   rotating the hue back returns a dark player with artwork roughly intact. */
.embed.flip iframe{filter:invert(1) hue-rotate(180deg)}
.embed-note{margin:.75rem 0 0;color:var(--faint)}

/* A link can carry one short line under its name without becoming a card. */
.links .lk{display:flex;flex-direction:column;gap:.35rem;min-width:0}
.links .lk em{font-style:normal;font-family:var(--mono);font-size:.625rem;
  letter-spacing:.2em;text-transform:uppercase;color:var(--faint);
  font-weight:400;letter-spacing:.18em}
.links a:hover .lk em{color:var(--bg)}

/* Editorial rows. On Elsewhere the links are the content rather than a jump
   menu, so they carry real metadata and stop shouting: medium weight in
   sentence case, the domain set in mono, and a rule that draws itself across
   the row instead of inverting the whole thing. Scoped to .ed so the jump
   lists on the home and 404 pages keep the louder treatment. */
.links.ed a{display:grid;grid-template-columns:2.6rem minmax(0,1fr) auto;
  align-items:baseline;gap:0 1.4rem;position:relative;
  padding-block:clamp(1.15rem,2.6vw,1.65rem);
  font-weight:400;text-transform:none;letter-spacing:normal;
  line-height:1.05;font-size:1rem;transition:none}
.links.ed a::after{content:"";position:absolute;bottom:-1px;height:1px;width:0;
  left:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2));
  right:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2));
  background:var(--ink);transition:width .5s var(--ease)}
.links.ed a:hover::after{width:calc(100% - 2 * max(clamp(1rem,3.5vw,2.75rem),
  calc((100% - var(--measure)) / 2)))}
/* Neutralise the inverting hover the base rows use. */
.links.ed a:hover{background:none;color:var(--ink);
  padding-left:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2))}
.links.ed a:hover .n,.links.ed a:hover .lk em{color:var(--faint)}

.links.ed .n{align-self:center}
.links.ed .lk{gap:.5rem}
.links.ed .lk strong{font-size:clamp(1.25rem,2.6vw,1.65rem);font-weight:700;
  letter-spacing:-.022em;line-height:1.05}
.links.ed .rt{display:flex;align-items:center;gap:1.1rem;align-self:center}
.links.ed .dm{font-family:var(--mono);font-size:.62rem;letter-spacing:.14em;
  color:var(--faint);white-space:nowrap;transition:color .3s var(--ease)}
.links.ed a .go{margin-left:0;font-size:.95rem;color:var(--faint);
  transition:transform .4s var(--ease),color .3s var(--ease)}
.links.ed a:hover .go{transform:translate(3px,-3px);color:var(--ink)}
.links.ed a:hover .dm{color:var(--dim)}

/* Elsewhere ranks itself: the type steps down as the links matter less, so
   importance is legible without another rail announcing it. */
.links.ed.t2 .lk strong{font-size:clamp(1rem,1.7vw,1.15rem)}
.links.ed.t2 a{padding-block:clamp(.85rem,1.9vw,1.15rem)}
.links.ed.t2 .lk{gap:.4rem}

/* The domain is supporting detail; drop it before it crowds the name. */
@media(max-width:760px){.links.ed .dm{display:none}}

/* The bottom tier: present, findable, plainly not the point. */
.minor{border-top:1px solid var(--line);
  padding-block:clamp(1.1rem,2.4vw,1.6rem);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2));
  display:flex;flex-wrap:wrap;align-items:baseline;gap:.55rem 1.5rem}
.minor > span{color:var(--faint)}
.minor a{font-family:var(--mono);font-size:.7rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--dim);text-decoration:none;
  border-bottom:1px solid var(--line);padding-bottom:.15rem;
  transition:color .25s var(--ease),border-color .25s var(--ease)}
.minor a:hover{color:var(--ink);border-color:var(--ink)}








/* ---- The cabinet ---------------------------------------------------------- */
.arcade{position:relative;border:1px solid var(--line);background:#000;
  aspect-ratio:8/5;max-width:680px;width:100%;margin-inline:auto}
.arcade iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
/* Nothing is fetched until the button is pressed. */
/* The game screen stays black in both themes, so its Play control stays light. */
.arcade-start{--ink:#fff;--faint:#9a9a9a;--accent:#fff;position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:.8rem;background:#000;
  border:0;cursor:pointer;color:var(--ink);width:100%;
  transition:background .25s var(--ease)}
.arcade-start:hover{background:#0b0b0b}
/* A class setting display beats the UA sheet's [hidden] rule, so the
   button would otherwise stay pinned over a game already running. */
.arcade-start[hidden]{display:none}
.arcade-start .big{font-weight:900;text-transform:uppercase;letter-spacing:-.03em;
  font-size:clamp(1.6rem,4vw,2.6rem);line-height:1}
.keys{max-width:680px;margin:1.1rem auto 0;display:grid;gap:.45rem 1.5rem;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,190px),1fr))}
.keys div{display:flex;gap:.7rem;align-items:baseline}
.keys b{font-family:var(--mono);font-size:.625rem;letter-spacing:.14em;
  color:var(--ink);font-weight:400;border:1px solid var(--line-hi);
  padding:.2em .5em;white-space:nowrap}
.arcade-foot{max-width:680px;margin:1rem auto 0}
/* ---- The cabinet's panels -----------------------------------------------
   The keys, the controls and the notice were one undivided pile under the
   screen. Named groups with a rule running off each heading, the same way
   the page's own rails read, so the eye can find the thing it came for. */
.cab-group{margin-top:clamp(1.5rem,3.6vw,2.4rem)}
.cab-head{display:flex;align-items:center;gap:.9rem;margin:0 0 .9rem;
  font-weight:400;color:var(--faint)}
.cab-head::after{content:'';flex:1;height:1px;background:var(--line)}
.cab-note{margin:clamp(1.5rem,3.6vw,2.4rem) 0 0;line-height:1.9}

/* Fullscreen. The frame is sized by an aspect ratio and a max width, and
   both have to go or the game sits in a small box in the middle of a black
   screen. The canvas inside already fills its own document. */
.arcade:fullscreen{max-width:none;width:100vw;height:100vh;aspect-ratio:auto;
  border:0}
.arcade:fullscreen iframe{inset:0;width:100%;height:100%}
.doom-mobile{display:none}
@media(max-width:760px),(pointer:coarse){
  .doom-desktop{display:none}
  .doom-mobile{display:block}
}

/* ---- The opening ---------------------------------------------------------
   The figure is thrown rather than faded in, and the name waits for it. For
   the first half second the canvas holds a single white point; the name
   arrives as the shock front passes it and the line follows once the figure
   has something like its shape.

   Animations with both-fill, not classes a script adds and takes away
   again. A script that died half way through its work could leave a class
   on for good, and the name is the one thing on this page that must not be
   able to go missing. The whole block hangs off .js as well, so a page
   whose script never ran paints the name immediately.

   Opacity and transform only: both are composited, and this is the second
   of the page's life where the canvas is working hardest. */
@media (prefers-reduced-motion:no-preference){
  .js .wordstage .word{
    animation:strike 1.05s cubic-bezier(.16,.84,.3,1) .42s both}
  .js .wordstage .stage-line{animation:settle 1.1s var(--ease) .82s both}
}
@keyframes strike{
  from{opacity:0;transform:translateY(30px) scale(.955)}
  60%{opacity:1}
  to{opacity:1;transform:none}
}
@keyframes settle{
  from{opacity:0;transform:translateY(14px)}
  to{opacity:1;transform:none}
}


/* ---- Arrival -------------------------------------------------------------
   Sections rise as you reach them, rows staggering by their index. The index
   is set from script so the markup stays free of presentational numbering.

   Everything here hangs off .js, which a script in the head adds. Only
   script ever removes the opacity again, so without that gate a page whose
   script had not run -- a 404 on site.js, a syntax error, a blocked inline
   script -- held its sections at nought opacity for good. The comment that
   used to sit here claimed anything unobserved stayed visible. It did not:
   the stylesheet hid it and nothing was coming to bring it back. */
@media (prefers-reduced-motion:no-preference){
  .js .rise{opacity:0;transform:translateY(15px);
    transition:opacity .7s var(--ease),transform .7s var(--ease);
    transition-delay:calc(var(--i,0) * 55ms)}
  .js .rise.in{opacity:1;transform:none}
}

/* ---- Scroll-linked arrival ----------------------------------------------
   Where the browser has scroll timelines, the travel above is replaced by
   the same movement driven from the scroll position rather than from a
   timer. The difference is that it runs both ways: a row halfway up the
   screen is halfway arrived, and winding the wheel back winds it back.

   Each element carries its own view() timeline, so the movement staggers
   itself by geometry: a row lower down the page is simply further from
   being arrived. The --i index stays, because the fade is still the
   observer's and still wants its delay.

   Durations are longhand on purpose: the animation shorthand resets
   animation-duration to 0s, and a progress-driven animation with a zero
   duration snaps instead of travelling. auto means "the whole range".
   Firefox has none of this yet and keeps the transition above. */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    /* The scroll drives the movement. It does NOT drive the opacity: a
       progress animation finishes only if the scroll can reach the end of
       its range, and the last element on a page cannot. Measured, the
       closing row of index.html reaches 31.8% of a cover range at maximum
       scroll, and anything flush with the foot of the document reaches
       about 4%. Opacity stays with the observer above, which always
       arrives; a movement left a pixel short is invisible, a paragraph
       left at three quarters opacity is a bug.

       cover rather than entry. entry ends the moment the element has
       finished entering, which for a 154px row is 154px of scroll, all of
       it in the bottom of the window: filmed, every row was already settled
       before it reached anywhere a reader looks. cover keeps the travel on
       screen, and the keyframes below finish early so the foot of a page
       still comes to rest. */
    /* fill-mode is forwards, not both, and that is a correctness choice
       rather than a stylistic one. A progress-driven animation filling
       BACKWARDS paints its from-state during the before phase -- and a
       timeline that has gone stale reports the before phase for an element
       the scroll is long past, which left figures sitting 64px out of place
       on voidworm.html about one load in five. With forwards there is no
       fill before the range at all, so a stale before phase leaves the
       element exactly where the stylesheet already put it. The pre-offset
       is lost, but it was never seen: the range opens at entry 0%, with the
       element still below the fold and held at nought opacity.

       The ranges stop short of the full cover for the same reason, and
       the keyframes stop shorter still.

       A running transform animation makes its element a containing block
       for anything absolutely positioned inside it, so a card whose link
       overlay covers it should be the animated element itself -- as the log
       rows and the VOIDWORM copy column are. */
    .js .rise{transform:none;
      transition:opacity .7s var(--ease);
      transition-delay:calc(var(--i,0) * 55ms);
      animation-name:travel;animation-duration:auto;animation-fill-mode:forwards;
      animation-timing-function:linear;animation-timeline:view();
      animation-range:cover 0% cover 46%}

    /* A second register, moving against the first. The row rises; the date
       comes in from the left across the same travel, so the two halves of a
       row converge instead of arriving as one slab. The date column holds no
       absolutely positioned link, so transforming it contains nothing. */
    .js .log-when{
      animation-name:slide;animation-duration:auto;animation-fill-mode:forwards;
      animation-timing-function:linear;animation-timeline:--row;
      animation-range:cover 0% cover 46%}


    /* Released at the foot of the page. A scroll animation finishes only if
       the scroll can reach the end of its range, and the last element on a
       page cannot -- architecture.html ends flush against its final riser,
       which measured 57px adrift. Everything here drops its animation and
       eases to rest instead of snapping. It cannot cut the movement short:
       by the time it fires there is no scrolling left to move against. */
    .js .rise.settled,
    .js .log-row.settled .log-when{animation-name:none;transform:none;
      transition:transform .5s var(--ease)}
    .js .log-row.settled::before{animation-name:none;transform:scaleX(1)}

    /* And the hairline between the rows draws itself across as the row
       arrives, left to right, instead of being there all along. The border
       is what it replaces, so the border goes transparent rather than the
       rule being drawn twice.

       The rule reads the ROW's timeline, not its own. A 1px-tall element has
       a 1px view range, so pointing these at view() finished them before
       they could be seen: scaleX measured 1 at every scroll position. Rows
       run 153-172px against any screen this site is read on, so the range
       is always reachable and the rule always finishes drawn. */
    .js .log-row{border-top-color:transparent;view-timeline-name:--row}
    .js .log-row::before{content:'';position:absolute;left:0;right:0;top:-1px;
      height:1px;background:var(--line);transform-origin:left center;
      animation-name:draw;animation-duration:auto;animation-fill-mode:forwards;
      animation-timing-function:linear;animation-timeline:--row;
      animation-range:cover 0% cover 46%}
    .js .log-row:first-child::before{content:none}
  }
}
/* Each of these finishes at roughly three quarters of its range and holds.
   The flat tail is not padding: it is what lets a cover range be used at all.
   An element at the foot of a page never reaches the end of its range -- the
   closing row of index.html gets 31.8% of the way at maximum scroll -- but it
   does reach the point where the movement is over, so it still comes to rest
   in the right place. */
@keyframes travel{
  0%{transform:translateY(64px)}
  74%{transform:none}
  100%{transform:none}
}
@keyframes slide{
  0%{transform:translateX(-30px)}
  74%{transform:none}
  100%{transform:none}
}
@keyframes draw{
  0%{transform:scaleX(0)}
  70%{transform:scaleX(1)}
  100%{transform:scaleX(1)}
}

/* ---- Colophon ------------------------------------------------------------
   Licence text has to be present and linkable, but it should not be the
   loudest thing on the page. The credit line stays visible; the detail
   folds away. */
.colophon{border-top:1px solid var(--line);margin-top:1.6rem;padding-top:1.1rem;
  max-width:78ch}
.colophon summary{cursor:pointer;list-style:none;display:flex;gap:.8rem;
  align-items:center;color:var(--dim)}
.colophon summary::-webkit-details-marker{display:none}
.colophon summary::after{content:"+";margin-left:auto;color:var(--faint)}
.colophon[open] summary::after{content:"\2212"}
.colophon summary:hover{color:var(--ink)}
.colophon .body p{margin:1rem 0 0;line-height:1.95}

/* ---- Footer --------------------------------------------------------------
   Elsewhere used to be a page holding seven links. It is now this: the same
   destinations, on every page, reachable from anywhere instead of from one
   dead end. Quiet on purpose -- these are where the work is kept, not what
   the site is for. */
.foot{border-top:1px solid var(--line);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2));
  padding-block:clamp(1.5rem,3.2vw,2.2rem);
  display:flex;flex-wrap:wrap;align-items:flex-start;gap:1.3rem 3rem}
/* The name and the copyright are the last thing on every page, on every
   screen: under the links, the publisher's line under the name. */
.foot-id{order:1;flex:1 1 100%;display:flex;flex-direction:column;align-items:flex-start;gap:.5rem;
  padding-top:1.1rem;padding-right:3.2rem;border-top:1px solid var(--line)}   /* clear of the fixed return-to-top arrow */

/* Grouped by what each link is for, not by whose logo is on it. A visitor
   looking for a build should not have to know that it lives on GitHub, and
   a profile that is not a release channel should not sit next to one.
   Three equal columns. Each category sits above a fine rule, with its links
   below it. The rules share a length and the gaps share a width. */
.foot-links{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));column-gap:2rem;row-gap:1.6rem;
  margin-left:auto;width:min(44rem,100%)}
.foot-group{display:flex;flex-direction:column;align-items:flex-start;gap:.45rem;
  padding-top:.9rem}
/* The heading stands further off its links than they stand off each other. */
/* Quiet labels, bright links: the weight is on what can be clicked. */
.foot-group > .lbl{display:flex;flex-direction:column;align-items:flex-start;gap:.65rem;width:100%;
  color:var(--faint);font-size:.625rem;letter-spacing:.22em;margin-bottom:.55rem}
.foot-group > .lbl::after{content:'';display:block;width:100%;height:1px;flex-shrink:0;background:var(--line-hi)}
.foot-links a{font-family:var(--mono);font-size:.625rem;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink);padding-bottom:.18rem;line-height:1.3;
  border-bottom:1px solid transparent;
  transition:color .22s var(--ease),border-color .22s var(--ease)}
.foot-links a:hover{color:var(--ink);border-bottom-color:var(--ink)}
.foot-links .sep{color:var(--faint);user-select:none}

@media(max-width:760px){
  .foot{flex-direction:column;gap:2rem}
  .foot-id{flex-direction:column;justify-content:flex-start;align-self:stretch}
  .foot-links{margin-left:0;width:100%;grid-template-columns:repeat(2,minmax(0,1fr))}
  .foot-group{gap:0}
  .foot-links a{display:flex;align-items:center;min-height:44px;padding-block:0}
}

/* ---- Numbered flow -------------------------------------------------------
   The signal path reads as an ordered list because it is one. The step
   number sits in its own column so the prose keeps a straight left edge. */
.flow{list-style:none;margin:0;padding:0;counter-reset:step}
.flow > li{display:grid;grid-template-columns:3.4rem minmax(0,1fr);
  gap:0 1.6rem;padding-block:clamp(1.2rem,2.6vw,1.9rem);
  border-bottom:1px solid var(--line)}
.flow > li:last-child{border-bottom:0}
.flow .step{font-family:var(--mono);font-size:.625rem;letter-spacing:.2em;
  color:var(--faint);padding-top:.45rem}
.flow h2{margin:0;font-size:clamp(1.15rem,2.2vw,1.5rem);font-weight:700;
  letter-spacing:-.02em;line-height:1.1}
.flow p{margin:.6rem 0 0;color:var(--dim);max-width:62ch}
.flow p.lbl{color:var(--faint);line-height:1.9;max-width:none}

/* The four chains, side by side, because they run side by side. */
.quad{display:grid;gap:1px;background:var(--line);margin-top:1.1rem;
  border:1px solid var(--line);
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
.quad > div{background:var(--bg);padding:1rem 1.05rem 1.15rem}
.quad p{margin:.5rem 0 0;font-size:.92rem}

@media(max-width:640px){
  .flow > li{grid-template-columns:2.6rem minmax(0,1fr);gap:0 1rem}
}

/* ---- Notes entries -------------------------------------------------------
   A date column beside the prose, so the page scans as a history without
   needing headings to carry the dates. */
.entry{display:grid;grid-template-columns:11rem minmax(0,1fr);gap:0 2.2rem;
  padding-block:clamp(1.5rem,3vw,2.2rem);border-bottom:1px solid var(--line)}
.entry:last-of-type{border-bottom:0}
.entry-meta{display:flex;flex-direction:column;gap:.5rem;padding-top:.3rem}
.entry-meta .ver{font-family:var(--mono);font-size:1rem;color:var(--ink);
  letter-spacing:.06em}
.entry-meta time{font-family:var(--mono);font-size:.625rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--faint)}
.entry-body{max-width:64ch}
.entry-body h2{margin:0 0 .7rem;font-size:clamp(1.1rem,2vw,1.4rem);
  font-weight:700;letter-spacing:-.02em;line-height:1.15}
.entry-body p{margin:0 0 .9rem;color:var(--dim)}
.entry-body p:last-child{margin-bottom:0}
.entry-body strong{color:var(--ink);font-weight:600}
/* Release files stay readable and separately tappable when the list wraps. */
.entry-body .entry-files{line-height:2.4}
.entry-files .inline{display:inline-flex;align-items:center;min-height:24px;white-space:nowrap}

.cost{width:100%;border-collapse:collapse;margin:.2rem 0 1.1rem;
  font-family:var(--mono);font-size:.625rem;letter-spacing:.1em;
  text-transform:uppercase}
.cost th,.cost td{text-align:left;padding:.55rem .8rem .55rem 0;
  border-bottom:1px solid var(--line);color:var(--dim);font-weight:400}
.cost th{color:var(--faint)}
.cost td:first-child{color:var(--ink)}

@media(max-width:700px){
  .entry{grid-template-columns:minmax(0,1fr);gap:.8rem}
  .entry-meta{flex-direction:row;align-items:baseline;gap:1rem}
}

/* Both plugins in one list, newest first, each entry named for its plugin;
   the filter narrows it to one. Without the script the filter stays hidden
   and the whole list shows. */
.entry-prod{font-weight:900;text-transform:uppercase;letter-spacing:-.01em;line-height:1;
  font-size:clamp(1rem,1.4vw,1.25rem);color:var(--ink)}
.log-stream .entry-meta{gap:.45rem}
.log-filter{display:flex;gap:.4rem}
.log-filter[hidden]{display:none}
.log-filter button{font:400 .625rem var(--mono);letter-spacing:.2em;text-transform:uppercase;cursor:pointer;
  background:none;color:var(--dim);border:1px solid var(--line-hi);padding:.5rem .7rem .5rem .9rem;
  transition:background .2s,color .2s}
.log-filter button:hover{color:var(--ink)}
.log-filter button[aria-pressed="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.log-filter button:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
.log-stream[data-show="voidworm"] .entry:not([data-prod="voidworm"]),
.log-stream[data-show="phoqer"] .entry:not([data-prod="phoqer"]){display:none}
@media(max-width:700px){
  .log-stream .band-rail{flex-wrap:wrap}
  .log-stream .entry-meta{flex-wrap:wrap}
}

/* ---- The views ----------------------------------------------------------
   Four interface shots, stacked and scrolled through. Each one settles into
   place as it arrives: it rises a little, scales up from just under full
   size, and fades in. The image is capped so a whole interface fits on a
   screen without filling it edge to edge. */
.views{display:grid;gap:clamp(2.2rem,4.5vw,4rem);justify-items:center}
.views figure{margin:0;width:min(100%,820px);display:flex;
  flex-direction:column;gap:.9rem}
.views img{width:100%;height:auto;display:block;background:#000;
  border:1px solid var(--line)}
.views figcaption{color:var(--faint);transition:color .25s var(--ease)}
.views figure:hover figcaption{color:var(--ink)}

@media (prefers-reduced-motion:no-preference){
  .views figure{opacity:0;transform:translateY(34px) scale(.975);
    transition:opacity .85s var(--ease),transform .85s var(--ease)}
  .views figure.in{opacity:1;transform:none}
}

/* ---- Video player --------------------------------------------------------
   The native controls stay in the markup and are only swapped out once the
   script has run, so the clip is playable either way. These are built from
   the same parts as the rest of the page: hairlines, mono labels, no colour.
   [hidden] is restated on every control because each sets a display of its
   own, which otherwise beats the browser rule. */
.player{position:relative;background:#000;border:1px solid var(--line);
  overflow:hidden}
.player video{display:block;width:100%;height:auto}
/* Fullscreen: no frame (its 1px lines showed down the edges) and the film fills
   the screen, letterboxed rather than cropped. */
/* Release the page's reserved scrollbar space while the player is fullscreen:
   otherwise even fixed insets and viewport units leave a strip at the right. */
html:has(.player:fullscreen){scrollbar-gutter:auto}
.player:fullscreen{border:0;position:fixed;inset:0;width:auto;height:auto;display:flex;align-items:center;justify-content:center}
.player:fullscreen video{width:100%;height:100%;object-fit:contain}
.player:fullscreen .pb:focus-visible{outline:0}
.player:fullscreen .player-bar{border-bottom:1px solid var(--line-hi)}
.player [hidden]{display:none}

/* The opening state: one target over the whole frame -- a round Play,
   the length and a warning about the sound under it. */
.player-open{position:absolute;inset:0;display:grid;place-items:center;
  gap:1.1rem;background:rgba(0,0,0,.55);border:0;cursor:pointer;color:var(--ink);
  align-content:center;transition:background .3s var(--ease)}
.player-open:hover{background:rgba(0,0,0,.34)}
.player-open .lbl{color:var(--ink);letter-spacing:.24em}
.player-open .tri{position:relative;width:5.2rem;height:5.2rem;border:1px solid var(--ink);border-radius:50%;
  transition:background .25s}
.player-open .tri::after{content:'';position:absolute;left:50%;top:50%;translate:-35% -50%;border-style:solid;
  border-width:.8rem 0 .8rem 1.3rem;border-color:transparent transparent transparent var(--ink)}
@media(hover:hover){.player-open:hover .tri{background:var(--ink)}}
.player-open:hover .tri::after{border-left-color:var(--bg)}

/* Play / pause in the bar: a triangle drawn with borders, two bars when on. */
.pb-play .tri{width:0;height:0;border-style:solid;border-width:9px 0 9px 14px;
  border-color:transparent transparent transparent currentColor}
.pb-play.on .tri{border-width:0;width:12px;height:15px;border-left:4px solid currentColor;border-right:4px solid currentColor}

/* The bar: the same hardware strip as the hero's controls, docked to the
   frame's foot, cells sharing hairlines. It gets out of the way while the
   clip plays and comes back on hover or pause. */
/* The controls sit on the film, which is dark in either theme, so they keep the
   dark theme's colours: in light mode they had turned to ink on the black strip. */
.player-bar,.player-open{--ink:#fff;--bg:#000;--dim:#9a9a9a;--faint:#787878;--line-hi:#4a4a4a;color:#fff}
.player-bar{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:stretch;height:3.4rem;
  background:rgb(0 0 0 / .8);border-top:1px solid var(--line-hi);
  opacity:0;transition:opacity .28s var(--ease)}
.player:hover .player-bar,.player.paused .player-bar,
.player-bar:focus-within{opacity:1}

.pb{display:flex;align-items:center;justify-content:center;width:3.4rem;padding:0;margin:0;background:0;border:0;
  border-left:1px solid var(--line-hi);color:var(--ink);cursor:pointer;transition:background .2s,color .2s}
@media(hover:hover){.pb:hover{background:var(--ink);color:var(--bg)}}
.pb:focus-visible{outline:1px solid var(--ink);outline-offset:-4px}
.pb svg{width:1.05rem;height:1.05rem;display:block}
.pb-play{border-left:0;border-right:1px solid var(--line-hi)}
.pb-mute .x{display:none}
.pb-mute.off .x{display:block}
.pb-mute.off .w{display:none}
.pb-mute.off{color:var(--faint)}
/* Volume: only the speaker shows. Pointed at, a column opens upward out of
   the speaker's own cell and the level draws up inside it: a hairline rail,
   the filled part in ink, a small pill for the knob as in Settings, the
   number above. The column also draws the cell's two side lines, top to
   bottom, so the column and the cell are one unbroken shape at any screen
   scaling: closed, only its cell-high foot shows, and that foot is the cell. */
.pb-volwrap{--cell:3.4rem;position:relative;display:flex;flex:0 0 var(--cell);border-left:1px solid transparent}
.pb-volwrap + .pb-full{border-left-color:transparent}
.pb-volwrap .pb{flex:1;border-left:0;position:relative;z-index:1;background:transparent}
.pb-volpop{position:absolute;left:-1px;right:-1px;top:-9rem;bottom:0;box-sizing:border-box;
  display:flex;flex-direction:column;align-items:center;gap:.7rem;padding:.8rem 0 calc(var(--cell) + 1rem);
  background:#000;border:1px solid var(--line-hi);border-bottom:0;
  clip-path:inset(9rem -1px -1px);transition:clip-path .32s cubic-bezier(.4,0,.2,1)}   /* closed: exactly the cell, under the bar's line */
/* Keep the clip outside the perimeter: clipping on the border itself dims
   its pixels at fractional positions, unlike the bar's other 1px lines. */
.pb-volnum{font-size:.5625rem;letter-spacing:.1em;color:var(--dim);opacity:0;transition:opacity .2s}
.pb-vol{position:relative;flex:1;width:1.6rem;cursor:pointer;touch-action:none;outline:0;visibility:hidden}
.pb-vol:focus-visible{outline:1px solid var(--ink);outline-offset:3px}
.pb-vol i{position:absolute;left:50%;display:block}
.pb-vol .rail{top:0;bottom:0;width:1px;margin-left:-.5px;background:var(--line-hi)}
.pb-vol .fill{bottom:0;height:calc(var(--v,100) * 1%);width:2px;margin-left:-1px;background:var(--ink);transform-origin:bottom;
  transform:scaleY(0);transition:transform .45s var(--ease) .1s}
.pb-vol .knob{bottom:calc(var(--v,100) * 1%);width:12px;height:6px;margin:0 0 -3px -6px;box-sizing:border-box;
  background:var(--bg);border:1px solid var(--ink);border-radius:999px;opacity:0;transition:opacity .25s .25s}
/* the bar's top line, covered where column and cell meet; it fades back with the fold */
.pb-volwrap::before{content:'';position:absolute;left:0;right:0;top:-1px;height:1px;background:#000;z-index:2;
  opacity:0;transition:opacity .22s linear}
@media(hover:hover){
  .pb-volwrap:hover .pb-volpop,.pb-volwrap:has(:focus-visible) .pb-volpop{clip-path:inset(-1px);transition:clip-path .38s var(--ease)}
  .pb-volwrap:hover .pb-vol,.pb-volwrap:has(:focus-visible) .pb-vol{visibility:visible}
  .pb-volwrap:hover .pb-vol .fill,.pb-volwrap:has(:focus-visible) .pb-vol .fill{transform:none}
  .pb-volwrap:hover :is(.knob,.pb-volnum),.pb-volwrap:has(:focus-visible) :is(.knob,.pb-volnum){opacity:1}
  .pb-volwrap:hover::before,.pb-volwrap:has(:focus-visible)::before{opacity:1;transition:opacity 0s}
  .pb-volwrap:hover .pb-mute{color:var(--ink)}
}
/* where the volume cannot be set there is no column, so the cell keeps ordinary lines */
.pb-volpop[hidden]{display:none}
.pb-volwrap:has(.pb-volpop[hidden]){border-left-color:var(--line-hi)}
.pb-volwrap:has(.pb-volpop[hidden]) + .pb-full{border-left-color:var(--line-hi)}
/* Fullscreen shows the way back out once it is full: corners pointing in. */
.pb-full .out{display:none}
.pb-full.on .in{display:none}.pb-full.on .out{display:block}
@media(max-width:620px){.pb-volwrap{--cell:2.9rem}}

/* Seek: a 4px rounded bar, the played part solid; its round head shows
   only while it is pointed at or dragged. */
.pb-track{position:relative;flex:1;align-self:center;height:4px;border-radius:2px;margin:0 1.1rem;min-width:60px;cursor:pointer;touch-action:none;
  background:rgb(255 255 255 / .12)}
.pb-track::before{content:'';position:absolute;inset:-1rem 0}
.pb-track:focus-visible{outline:1px solid var(--ink);outline-offset:8px}
.pb-buf,.pb-played{position:absolute;left:0;top:0;bottom:0;width:0;border-radius:2px}
.pb-buf{background:var(--dim);opacity:.35}
.pb-played{background:var(--ink)}
.pb-played::after{content:'';position:absolute;right:-7px;top:50%;width:14px;height:14px;margin-top:-7px;border-radius:50%;
  background:var(--ink);transform:scale(0);transition:transform .2s var(--ease)}
.pb-track:hover .pb-played::after,.pb-track:active .pb-played::after,.pb-track:focus-visible .pb-played::after{transform:scale(1)}
.pb-time{align-self:center;padding:0 1.1rem;white-space:nowrap;color:var(--dim);font-variant-numeric:tabular-nums}

@media(max-width:620px){
  .player-bar{height:2.9rem}
  .pb{width:2.9rem;min-width:44px;flex-shrink:0}
  .pb-track{margin:0 .6rem}
  .pb-track::before{inset:-20px 0}
  .pb-time{padding:0 .4rem;font-size:.6875rem;letter-spacing:0}
  .player-open .tri{width:4rem;height:4rem}
}
@media(max-width:360px){
  .pb-track{min-width:44px;margin-inline:.35rem}
  .pb-time{padding-inline:.3rem}
  .pb-time .pb-duration{display:none}
}


/* ==========================================================================
   Cybercore, made of this site's own material. Under the first screen: a
   poster grid on black, white only, line objects, type set in outline.
   The first screen -- the figure and the name -- is not touched, and the
   deck starts below the figure's fade so nothing sits on top of it. No
   flicker anywhere: motion is slow, continuous, scrolled or played.
   ========================================================================== */
:root{--gut:14px}

.deck{container-type:inline-size;
  padding-top:clamp(4rem,26vh,22rem);padding-bottom:clamp(3rem,7vw,6rem)}

/* ---- Title: the publisher, full width, and an outlined line under it --- */
.deck-title{margin:0 0 clamp(1.4rem,3cqi,3rem)}
/* The front page's heading: the publisher's name and what it makes on one
   line, a rule under both. It introduces the work rather than outshouting
   it; the big outlined pair is kept for the Lab further down. */
.deck > .deck-title{position:relative;display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;
  gap:.6rem 2rem;padding-bottom:1rem}
.deck > .deck-title::after{content:'';position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--line-hi);
  transform-origin:left}
/* On arrival the rule draws left to right and the two lines rise out of it,
   the name first. Only once script has marked the page, so nothing hides
   without it. */
.rev-on .deck > .deck-title:not(.shown)::after{transform:scaleX(0)}
.rev-on .deck > .deck-title:not(.shown) > *{opacity:0;transform:translateY(.6em);clip-path:inset(0 0 100% 0)}
.rev-on .deck > .deck-title::after{transition:transform 1.1s cubic-bezier(.6,0,.2,1)}
.rev-on .deck > .deck-title > *{transition:opacity .7s var(--ease),transform .9s var(--ease),clip-path .9s var(--ease)}
.rev-on .deck > .deck-title > .dt-name{transition-delay:.25s}
.rev-on .deck > .deck-title > .dt-sub{transition-delay:.5s}
.dt-name{margin:0;font-weight:900;text-transform:uppercase;letter-spacing:-.035em;line-height:.88;
  white-space:nowrap;font-size:6cqi}
.dt-sub{margin:0;font-family:var(--mono);font-size:.75rem;letter-spacing:.22em;text-transform:uppercase;color:var(--dim)}
@media (max-width:899px){.dt-name{font-size:12cqi}}
.dt-big{margin:0;font-weight:900;text-transform:uppercase;line-height:.84;
  letter-spacing:-.035em;font-size:18.6cqi;white-space:nowrap}
.dt-out{margin:.35em 0 0;font-weight:900;text-transform:uppercase;line-height:1;
  letter-spacing:.01em;font-size:4.6cqi;white-space:nowrap;
  color:transparent;-webkit-text-stroke:1.4px var(--ink);
  /* The outline fills in as the page is scrolled: a solid half of a
     double-width background, clipped to the letters, slides across. */
  background:linear-gradient(90deg,var(--ink) 0 50%,transparent 50%) 100% 0/200% 100% no-repeat;
  -webkit-background-clip:text;background-clip:text}

/* ---- The grid --------------------------------------------------------- */
.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  column-gap:var(--gut);row-gap:clamp(3rem,7cqi,7.5rem)}
.tile{position:relative;min-width:0;margin:0;color:var(--ink);text-decoration:none}
.t-lead{grid-column:1/span 6;grid-row:1/span 2;align-self:center}
.t-copy{grid-column:8/span 5;grid-row:1/span 2}
/* PHOQER: VOIDWORM's row mirrored, the particulars on the left. Both rows
   are a six-column window, a column of air and five of particulars. */
.t-lead.t-phq{grid-column:7/span 6;grid-row:3/span 2;align-self:center}
.t-copy.t-phq{grid-column:1/span 5;grid-row:3/span 2;
  padding-left:0;padding-right:clamp(.4rem,1.2cqi,1.4rem)}
/* Between VOIDWORM and PHOQER: extra room, and a hairline across the whole
   grid halfway through it, so the two read as two things. */
.t-phq{margin-top:clamp(3rem,8cqi,8rem)}
.t-copy.t-phq{position:relative}
.t-copy.t-phq::before{content:'';position:absolute;left:0;width:100cqi;height:1px;background:var(--line-hi);
  top:calc((clamp(3rem,8cqi,8rem) + clamp(3rem,7cqi,7.5rem)) / -2)}
/* The four line objects: one row of equal framed squares, read as a set. */
.t-waves{grid-column:1/span 3;grid-row:5}
.t-globe{grid-column:4/span 3;grid-row:5}
.t-sun{grid-column:7/span 3;grid-row:5}
.t-pills{grid-column:10/span 3;grid-row:5}
/* A clear break wherever the subject changes -- VOIDWORM, the PHOQER row,
   the Lab, the Log -- on top of the grid's own row gap. */
.t-waves,.t-globe,.t-sun,.t-pills{margin-top:clamp(2.5rem,6cqi,6.5rem)}
.lab,.t-log{margin-top:clamp(3.5rem,9cqi,9rem)}
/* Drawings, not text: a double click must not paint them with the selection. */
.t-waves,.t-globe,.t-sun,.t-pills{-webkit-user-select:none;user-select:none}
/* Pictures are not dragged out of the page by a long press or a click-and-hold:
   too many of them are things to press (the deck, the plate, the scopes). */
img,svg{-webkit-user-drag:none;user-drag:none}
/* Nothing you press or that labels something can be highlighted by a stray
   click-and-drag: controls, labels, dates and the poster-size titles. The
   reading text -- descriptions, the Log, the FAQ answers -- stays selectable,
   so it can still be copied. */
:is(button,[role="button"],[role="slider"],input[type="range"],.top,.menu,.nav,.prefs,.lbl,.tag,.player,.vw,.vy-deck,.vy-dl,
    .lab-grid,.vc-stage,.vc-dots,.band-rail,.log-filter,.entry-meta,.log-when,time,.foot-group > .lbl,.repo-head p,
    h1,h2,.poster,.dt-out,.lab-big,.vy-name,.wordstage,.t-head,.vc-cap h3,.story-cap h3,.entry-prod,.sig,.mkeys,.mseals,.dlb){
  -webkit-user-select:none;user-select:none}
.vy-body,.vy-note{-webkit-user-select:text;user-select:text}   /* reading text inside the plugin block */
.t-waves,.t-globe,.t-sun,.t-pills{aspect-ratio:1;box-sizing:border-box;
  border:1px solid var(--line);padding:clamp(1rem,2cqi,2rem)}
.t-log{grid-column:1/-1}

/* ---- VOIDWORM: the plate, and its particulars beside it ---------------- */
.t-lead .plate{position:relative;z-index:1}
.t-copy{display:flex;flex-direction:column;justify-content:center;gap:2rem;
  gap:clamp(2rem,4cqi,4rem);padding-left:clamp(.4rem,1.2cqi,1.4rem)}
.t-copy h2{margin:0;font-weight:900;text-transform:uppercase;line-height:.9;
  letter-spacing:-.03em;font-size:clamp(2rem,4.4cqi,4.6rem)}
.lede-link{color:inherit;text-decoration:none}
.lede-link::after{content:'';position:absolute;inset:0}
.t-copy .pitch{margin:1rem 0 0;color:var(--dim);font-size:clamp(1rem,1.25cqi,1.25rem);
  line-height:1.5;max-width:30ch}
.t-copy dl{margin:0;display:grid;grid-template-columns:auto 1fr;
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.14em;text-transform:uppercase}
.t-copy dt,.t-copy dd{padding-block:.7rem;border-top:1px solid var(--line)}
.t-copy dt{color:var(--faint)}
.t-copy dd{margin:0;text-align:right;color:var(--ink)}
/* The whole block is the way in (the script hands its clicks to the name):
   pointing anywhere on the window or the text outlines the name. */
.t-copy,.t-lead{cursor:pointer}
.t-copy:hover h2,.t-lead:hover + .t-copy h2{color:transparent;-webkit-text-stroke:1.2px var(--ink)}

/* ---- Line objects ------------------------------------------------------ */
.t-waves svg,.t-sun svg,.t-globe svg{display:block;width:100%;height:100%}
.t-waves path{fill:none;stroke:var(--ink);stroke-width:2;
  vector-effect:non-scaling-stroke;transform-box:fill-box;transform-origin:center;
  transition:scale .6s var(--ease)}
/* Sound-like: under the pointer the lines swell, the middle ones most. */
.t-waves:hover path{scale:1 1.9}
.t-waves:hover path:nth-child(3),.t-waves:hover path:nth-child(4){scale:1 2.6}
.t-sun circle,.t-sun line{fill:none;stroke:var(--ink);stroke-width:1.6;
  vector-effect:non-scaling-stroke}
.t-sun .core{fill:var(--ink);stroke:none}

/* The globe: an attractor in a round frame with an orbit ring round it. The
   ring is drawn in difference, so it shows over the black globe and over
   the page in either theme. */
.t-globe .orb{position:absolute;inset:9%;border-radius:50%;overflow:hidden;
  background:#000;box-shadow:0 0 0 1.5px var(--ink)}
.t-globe .orb img{display:block;width:100%;height:100%;object-fit:cover}
/* Light theme: the one-bit orb turns to dark lines on the paper instead of a black disc.
   The picture alone is inverted and multiplied onto the disc's own paper colour, so
   the inside matches the page exactly (blending against the page itself left it
   white, the tile being its own layer). The ring is plain ink: drawn in difference
   it came out white wherever it ran past the disc. */
:root[data-theme="light"] .t-globe .orb{background:var(--bg);isolation:isolate;box-shadow:0 0 0 1.5px var(--ink)}
:root[data-theme="light"] .t-globe .orb img{filter:invert(1);mix-blend-mode:multiply}
:root[data-theme="light"] .t-globe svg{mix-blend-mode:normal}
:root[data-theme="light"] .t-globe ellipse{stroke:var(--ink)}
.t-globe svg{position:absolute;inset:0;pointer-events:none;mix-blend-mode:difference}
.t-globe ellipse{fill:none;stroke:#fff;stroke-width:1.6;
  vector-effect:non-scaling-stroke;stroke-dasharray:6 7}

/* PHOQER's window goes through its three seals, Burp, Squeal and Groan, as
   if their tabs were being clicked: the other two are laid over the first
   and cross-fade in turn. */
.seals img + img{position:absolute;inset:0;width:100%;height:100%;opacity:0}
@media (prefers-reduced-motion:no-preference){
  .seals img:nth-child(2){animation:seal-2 12s infinite}
  .seals img:nth-child(3){animation:seal-3 12s infinite}
}
@keyframes seal-2{0%,30%{opacity:0}33%,66%{opacity:1}69%,100%{opacity:0}}
@keyframes seal-3{0%,63%{opacity:0}66%,97%{opacity:1}100%{opacity:0}}

/* What's next: a stack of pill outlines breathing, the last one named. */
.t-pills{display:flex;flex-direction:column;justify-content:center}
.pl{display:block;height:clamp(2.2rem,3.4cqi,3.4rem);border:2px solid var(--ink);
  border-radius:999px;margin-top:calc(clamp(2.2rem,3.4cqi,3.4rem) * -.42)}
.pl:first-child{margin-top:0}
.pl.on{position:relative;margin-top:1rem;display:flex;align-items:center;
  justify-content:center;gap:.7rem;background:var(--bg);font-weight:900;
  text-transform:uppercase;letter-spacing:.32em;padding-left:.32em;
  font-size:clamp(.9rem,1.4cqi,1.35rem)}
.pl.on i{font-style:normal;letter-spacing:0}
.t-pills .lbl{margin-top:1rem;text-align:center;color:var(--dim);text-wrap:balance}
@media(hover:hover){.t-pills:hover .pl.on{background:var(--ink);color:var(--bg)}}


/* ---- Log --------------------------------------------------------------- */
.t-log{display:flex;flex-direction:column}
.t-head{margin:0 0 .9rem;font-weight:900;text-transform:uppercase;letter-spacing:-.01em;
  line-height:1;font-size:clamp(1.25rem,2cqi,2.2rem)}
.t-log .log-row{grid-template-columns:minmax(6.5rem,8.5rem) minmax(0,1fr);padding-block:.95rem}
.t-log .log-text p{margin:.35rem 0 0;font-size:.875rem;line-height:1.55}
.t-log .log-title{margin:0;font-weight:900;text-transform:uppercase;
  font-size:clamp(.95rem,1.25cqi,1.25rem)}
.t-log > .inline{margin-top:auto;padding-top:1.2rem}

/* ---- Scopes: three instruments of their own ---------------------------- */
/* ---- Lab: the three scopes and their panel, one row of four ---------- */
.lab{margin-top:clamp(3.5rem,9cqi,9rem)}
.lab-big{margin:0;font-weight:900;text-transform:uppercase;line-height:.84;
  letter-spacing:-.035em;font-size:12cqi}
.lab .dt-out{font-size:3.2cqi;margin-bottom:clamp(1.5rem,3cqi,3rem)}
.lab-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--gut)}
.lab-grid .scope,.lab-panel{grid-column:span 3}
/* What a drag and a tap do, along each scope's foot: for a few seconds after
   the sound first comes on (.hint), and whenever a scope is pointed at. */
.scope[data-hint]::after{content:attr(data-hint);position:absolute;left:0;right:0;bottom:0;padding:.45rem .5rem;
  font-family:var(--mono);font-size:.5625rem;letter-spacing:.12em;text-transform:uppercase;line-height:1.4;
  color:var(--dim);background:var(--bg);text-align:center;opacity:0;transition:opacity .4s;pointer-events:none}
.lab-grid.hint .scope::after,.scope:hover::after,.scope:focus-visible::after{opacity:1}
/* The fourth square: the sound switch and how to play. */
.lab-panel{aspect-ratio:1;box-sizing:border-box;border:1px solid var(--line);
  padding:clamp(1rem,1.6cqi,1.6rem);display:flex;flex-direction:column;
  justify-content:space-between;font-family:var(--mono);font-size:.6875rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
.lab-panel p{margin:0;line-height:1.7}
.lab-panel .snd{align-self:flex-start;margin:0;font-size:.8rem;padding:.7em 1.1em}
.lab-btns{display:flex;flex-wrap:wrap;gap:.5rem}
.lab-help{margin:.8rem 0 0;color:var(--dim);font-family:var(--mono);font-size:.6875rem}
.snd:disabled{opacity:.4;cursor:default}
.log-grid{margin-top:clamp(3.5rem,9cqi,9rem)}
.scope{position:relative;display:block;width:100%;aspect-ratio:1;padding:0;
  border:1px solid var(--line);background:none;color:var(--ink);cursor:pointer;
  font:inherit;touch-action:pan-y}
.scope:hover,.scope:focus-visible{border-color:var(--line-hi)}
.scope:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
.scope svg{display:block;width:100%;height:100%}
.scope path{fill:none;stroke:var(--ink);stroke-width:1.7;
  vector-effect:non-scaling-stroke;stroke-linejoin:round}
.scope .p0{opacity:1}
.scope .p1{opacity:.38}
.scope .p2{opacity:.14}
.scope .ring{fill:none;stroke:var(--line-hi);stroke-width:1;
  vector-effect:non-scaling-stroke}
.scope[data-mode="xy"] path{stroke-width:1}
.scope[data-mode="xy"] .ring{stroke-width:.6}
.scope .axis{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}
.scope-read{position:absolute;left:0;right:0;top:0;display:flex;
  justify-content:space-between;padding:.7rem .8rem;font-family:var(--mono);
  font-size:.625rem;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);
  pointer-events:none}
.scope-read b{font-weight:400;color:var(--ink)}
/* The sound switch, framed so it reads as a control; lit while the arp
   plays. */
.snd{font:inherit;letter-spacing:inherit;text-transform:inherit;color:var(--ink);
  background:0;border:1px solid var(--line-hi);cursor:pointer}
.snd[aria-pressed="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.snd:focus-visible{outline:2px solid var(--ink);outline-offset:3px}

/* ---- The sign-off: the name drawn in outline, a line under it that keeps
   asking something different. Script arms it; without script the name and
   the first line simply stand there. */
.sig{margin-top:clamp(3.5rem,9cqi,9rem);min-height:clamp(10rem,20cqi,18rem)}
.sig svg{display:block;width:min(40rem,70%);overflow:visible}
.sig text{font-family:Archivo,sans-serif;font-weight:900;font-size:220px;letter-spacing:-14px;
  fill:transparent;stroke:var(--ink);stroke-width:1.6}
.sig.armed text{stroke-dasharray:1400;stroke-dashoffset:1400}
.sig.go text{animation:sig-draw 2.4s cubic-bezier(.4,0,.2,1) forwards}
@keyframes sig-draw{to{stroke-dashoffset:0}}
.sig-line{margin:1.4rem 0 0;font-size:clamp(1rem,1.6cqi,1.4rem);color:var(--dim);transition:opacity .35s}
.sig-line .ans{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--ink)}
@media(hover:hover){.sig-line .ans:hover{background:var(--ink);color:var(--bg)}}
.sig-line.out{opacity:0}
[data-motion="off"] .sig.armed text,[data-motion="reduced"] .sig.armed text{stroke-dashoffset:0}
@media (prefers-reduced-motion:reduce){
  .sig.armed text{stroke-dashoffset:0}.sig.go text{animation:none}.sig-line{transition:none}
}

/* ---- Motion: slow and continuous, never a blink ------------------------ */
@media (prefers-reduced-motion:no-preference){
  .t-waves path{animation:drift 5.5s linear infinite}
  .t-waves path:nth-child(even){animation-duration:7.5s;animation-direction:reverse}
  .t-sun .rays{transform-origin:50px 50px;animation:turn 48s linear infinite}
  .t-globe .orb img{animation:turn 140s linear infinite}
  .pl:not(.on){animation:breathe 3.6s ease-in-out infinite}
  .pl:nth-child(2){animation-delay:-.5s}.pl:nth-child(3){animation-delay:-1s}
  .pl:nth-child(4){animation-delay:-1.5s}.pl:nth-child(5){animation-delay:-2s}
  .pl:nth-child(6){animation-delay:-2.5s}
  .pl.on i{display:inline-block;animation:turn 6s linear infinite}
  @supports (animation-timeline:view()){
    .dt-out{animation:fill linear both;animation-timeline:view();
      animation-range:cover 12% cover 48%}
  }
}
@keyframes drift{to{transform:translateX(-100px)}}
@keyframes turn{to{transform:rotate(360deg)}}
@keyframes breathe{50%{transform:translateY(-5px)}}
@keyframes fill{from{background-position:100% 0}to{background-position:0 0}}

/* ---- Narrow screens: two columns --------------------------------------- */
@media (max-width:899px){
  .grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .t-lead,.t-copy,.t-globe,.t-pills,.t-log{grid-column:1/-1;grid-row:auto}
  .t-waves{grid-column:1;grid-row:auto}
  .t-sun{grid-column:2;grid-row:auto}
  /* Too many pills for half a phone: full width, as tall as they need. */
  .t-pills{aspect-ratio:auto}
  .t-copy{padding-left:0}
  .t-lead.t-phq,.t-copy.t-phq{grid-column:1/-1;grid-row:auto}
  /* One column, and each plugin reads name, window, particulars: the copy
     box steps aside (display:contents) so its two halves can sit either
     side of the window. VOIDWORM's three, then PHOQER's, then the rest.
     The halves hug the window rather than taking the grid's full row gap. */
  .grid > *{order:10}
  .t-copy{display:contents}
  .t-copy > div{grid-column:1/-1;position:relative}
  .t-copy:not(.t-phq) > div:first-child{order:1}.t-lead:not(.t-phq){order:2}.t-copy:not(.t-phq) > div:last-child{order:3}
  .t-copy.t-phq > div:first-child{order:4}.t-lead.t-phq{order:5;margin-top:0}.t-copy.t-phq > div:last-child{order:6}
  .t-copy > div:first-child{margin-bottom:calc(1.4rem - clamp(3rem,7cqi,7.5rem))}
  .t-copy > div:last-child{margin-top:calc(1.6rem - clamp(3rem,7cqi,7.5rem))}
  /* The line between the two now hangs off PHOQER's name, halfway up the gap. */
  .t-copy.t-phq::before{content:none}
  .t-copy.t-phq > div:first-child{margin-top:clamp(3rem,8cqi,8rem)}
  .t-copy.t-phq > div:first-child::before{content:'';position:absolute;left:0;right:0;height:1px;background:var(--line-hi);
    top:calc((clamp(3rem,8cqi,8rem) + clamp(3rem,7cqi,7.5rem)) / -2)}
  .lab-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .lab-grid .scope,.lab-panel{grid-column:auto}
  .lab .dt-out{font-size:6cqi;white-space:normal;text-wrap:balance}
  /* Half a phone is about 170px: labels and the panel set small enough to
     hold one line and one square. */
  .scope-read{padding:.5rem .55rem;font-size:.5625rem;letter-spacing:.08em;white-space:nowrap}
  .lab-panel{padding:.7rem;font-size:.5625rem;letter-spacing:.08em}
  .lab-panel p{line-height:1.7}
  .lab-panel .snd{font-size:.625rem;padding:.55em .8em}
  .lab-btns{flex-wrap:nowrap;gap:.35rem}
  .keep .long{display:none}
  /* Its labels take the panel's small size instead of their own wide
     tracking, the two controls get shallower, and the footnote goes, so all
     of it fits inside the square instead of spilling out of the bottom. */
  /* Held to the same square as the scopes beside it (overflow is what lets
     aspect-ratio win over the content's height); the tempo box takes
     whatever room is left and its drawing shrinks to fit. */
  .lab-panel{gap:.45rem;overflow:hidden;justify-content:flex-start}
  .lab-panel .lbl{font-size:inherit;letter-spacing:inherit}
  .lab-panel > .lbl:last-child{display:none}
  .lab-panel .roll,.lab-panel .lvl{padding:.4rem}
  .lab-panel .roll{flex:1 1 0;min-height:0;display:flex;flex-direction:column}
  .lab-panel .roll svg{flex:1 1 0;min-height:0;height:auto}
  .lab-panel .lvl svg{height:.6rem}
  .lab-panel .roll .lbl,.lab-panel .lvl .lbl{margin-top:.35rem}
}

/* ---- The other pages, in the poster's language ------------------------- */
/* The page's name at poster size: fitted to the column per page (the
   measured width of each title sets --fit), never bigger than LWNX DSP on
   the front page, with the deck's outlined line under it. */
.page-head{container-type:inline-size}
.page-head h1.poster{font-size:var(--fit,10cqi);line-height:.84;
  letter-spacing:-.035em;white-space:nowrap}
.page-head .dt-out{font-size:var(--fit,4.6cqi);margin:.4em 0 0;max-width:none;
  color:transparent}
/* At the top of a page the line is already past its fill range when the
   page opens, so it would arrive solid. Here it stays an outline. */
.page-head .dt-out{animation:none}
/* Section labels set as headings, as the deck sets LOG. */
.band-rail .on{font-family:var(--sans);font-weight:900;text-transform:uppercase;
  letter-spacing:-.01em;line-height:1;font-size:clamp(1.15rem,1.8vw,1.9rem);
  color:var(--ink)}
.band-rail{min-height:0;padding-block:clamp(1.2rem,2.4vw,2rem) .8rem;align-items:baseline}
/* Room between the end of a page and the footer's links. (The front page
   ends on its own sign-off and keeps its spacing.) */
main:not(.front){padding-bottom:clamp(4rem,10vw,8rem)}

/* ---- Between pages ------------------------------------------------------ */
/* Native cross-document view transitions: pages cross-fade instead of
   cutting, the header holds still, and the VOIDWORM name and drawing on the
   front page travel into the VOIDWORM page's title and first screenshot.
   Browsers without it simply load the page as before. */
@view-transition{navigation:auto}
.top{view-transition-name:top}
.vt-title{view-transition-name:vw-title;width:fit-content}
/* The title travels as a picture of its box, so the box has to be the word:
   full-width boxes of different shapes stretched the word on the way and
   dropped it into place with a jolt at the end. Centred where it is centred. */
.vy-name.vt-title{margin-inline:auto}
/* To or from any page but these two, nothing travels: the page just crossfades (theme.js). */
.vt-plain .vt-title,.vt-plain .vt-shot{view-transition-name:none}
.vt-shot{view-transition-name:vw-shot}
/* Long enough to read as a move rather than a cut, and eased hard at both
   ends, so any hitch while the new page settles falls where the motion is
   slow and is not seen. */
::view-transition-group(*){animation-duration:.6s;
  animation-timing-function:cubic-bezier(.65,0,.25,1)}
/* On the VOIDWORM page the window overlaps the big title from above. The
   travelling copies must keep that order the whole way, or the title is
   drawn over the window for the length of the move and drops behind it
   with a jump when the page takes over. The header stays on top of both. */
::view-transition-group(vw-title){z-index:1}
::view-transition-group(vw-shot){z-index:2}
::view-transition-group(top){z-index:3}
/* Only these two snapshots use the fixed-size transform animation. */
.vt-scale-title::view-transition-group(vw-title),
.vt-scale-shot::view-transition-group(vw-shot){transform-origin:0 0}
.vt-scale-title::view-transition-old(vw-title),.vt-scale-title::view-transition-new(vw-title),
.vt-scale-shot::view-transition-old(vw-shot),.vt-scale-shot::view-transition-new(vw-shot){
  width:100%;height:100%;object-fit:fill;
}

/* Arriving from a page with no window to travel from (Projects, the Log),
   the window used to fade in from clear, and the title under it showed
   through while it did; told not to animate at all, it popped in, or sat
   frozen on the next page when leaving. */
/* So both still fade, in order: arriving, the window first and the title
   once it is solid; leaving, the title first, then the window. Nothing pops,
   nothing shows through, and nothing is left standing on the next page. */
::view-transition-new(vw-shot):only-child{animation:vt-in .22s ease-out both}
::view-transition-new(vw-title):only-child{animation:vt-in .3s ease-out .26s both}
::view-transition-old(vw-title):only-child{animation:vt-out .18s ease-in both}
::view-transition-old(vw-shot):only-child{animation:vt-out .12s ease-out both}   /* a still picture: gone quickly, never left standing */
@keyframes vt-in{from{opacity:0}}
@keyframes vt-out{to{opacity:0}}
@media (prefers-reduced-motion:reduce){
  @view-transition{navigation:none}
}

/* ---- VOIDWORM in the page ------------------------------------------------ */
/* The plugin's own face, with each control's exported strip laid over it.
   Dimmed until Play, so it does not pretend to answer before it can. */
.vw{width:min(100%,920px);margin-inline:auto}
.vw-bar{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem 1rem;margin-bottom:1rem}
.vw-bar .lbl{color:var(--faint)}
.vw-bypass[aria-pressed="true"]{background:var(--ink);color:var(--bg)}
.vw-stage{position:relative;background:#000;border:1px solid var(--line)}
.vw-poster{display:block;width:100%;height:auto;transition:opacity .4s var(--ease)}
.vw:not(.vw-on) .vw-poster{opacity:.55}
.vw-art,.vw-hit{position:absolute;display:block}
.vw-art{background-repeat:no-repeat;pointer-events:none}
.vw-hit{cursor:ns-resize;touch-action:none;border-radius:50%}
.vw-hit[role="switch"]{cursor:pointer;border-radius:10px}
.vw-hit:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* The pages, meters and readouts exported from the editor. The stage is a
   size container so the readout's type scales with the window it sits in. */
.vw-stage{container-type:inline-size}
.vw-page,.vw-meter,.vw-readout{position:absolute;display:block;pointer-events:none}
.vw-page[hidden],.vw-art[hidden],.vw-hit[hidden]{display:none}
.vw-meter{background-size:100% 100%}
.vw-readout{display:flex;align-items:center;justify-content:center;border-radius:2px;
  font:600 1.4cqi/1 Bahnschrift,'DIN Alternate','Roboto Condensed','Arial Narrow',sans-serif;
  font-variant-numeric:tabular-nums}
.vw-hit.vw-tab{cursor:pointer;border-radius:4px}

/* ---- The VOIDWORM page ------------------------------------------------
   The hero is the plugin itself: outlined giant type behind the window, the
   window grey like every picture of it on the site, and the window is also
   the Play button -- press it and the real engine comes up in colour. Below:
   three smaller views, the reactors, the video, and everything to get it. */
.vy-hero{padding-top:clamp(1.2rem,3vw,2.2rem);text-align:center}
.vy-tags{margin:0;color:var(--dim);display:flex;flex-wrap:wrap;justify-content:center;gap:.2rem .6rem}
.vy-stack{position:relative;container-type:inline-size;margin-top:clamp(.6rem,2vw,1.4rem);
  padding-inline:max(clamp(1rem,3.5vw,2.75rem),env(safe-area-inset-left))}
.vy-name{margin:0;font-weight:900;text-transform:uppercase;line-height:.8;letter-spacing:-.035em;
  font-size:15.6cqi;white-space:nowrap;color:transparent;-webkit-text-stroke:max(1px,.16cqi) var(--ink)}
/* Black behind the window: it overlaps the title, and until the picture has
   drawn the title showed through where the window was about to be. */
.vy-product{margin:-4.2cqi auto 0;width:min(74%,920px);position:relative;background:#000;
  filter:grayscale(1) contrast(1.04) brightness(.95)
    drop-shadow(0 0 3.2cqi rgba(255,255,255,.10)) drop-shadow(0 2.4cqi 5cqi rgba(0,0,0,.9));
  transition:filter .6s cubic-bezier(.2,.7,.2,1)}
/* Black and white while it is idle -- before Play, after Stop, or bypassed --
   and in its own colour only while it is sounding. The whole window turns,
   controls, meters and readouts with it. */
.vz.vw-playing:not(.vw-bypassed) .vy-product{filter:grayscale(0) contrast(1) brightness(1)
    drop-shadow(0 0 3.2cqi rgba(255,255,255,.10)) drop-shadow(0 2.4cqi 5cqi rgba(0,0,0,.9))}
.vz .vy-product .vw-poster{display:block;width:100%;height:auto;opacity:1}
.vz{width:auto}
.vz .vw-bar{margin:0}
/* Bypass reads as a switch: it says which way it is. */
.vy-copy{display:grid;justify-items:center;padding-block:clamp(2.2rem,5vw,3.6rem) clamp(2.6rem,6vw,4.6rem)}
/* The line under the name: the site's display type, set like a headline,
   with the description quieter beneath it. */
.vy-sub{margin:0;font-size:clamp(1.7rem,4.2vw,3.4rem);font-weight:900;text-transform:uppercase;
  line-height:.95;letter-spacing:-.02em;color:var(--ink);text-wrap:balance}
.vy-body{margin:clamp(.9rem,1.8vw,1.3rem) 0 0;max-width:48ch;color:var(--dim);
  font-size:clamp(1rem,1.25vw,1.15rem);line-height:1.6;text-wrap:balance}
/* The hero's controls, style C: one hardware strip -- Play, the loop it
   runs, and Bypass (only while it plays) -- sharing hairlines, with the
   download as a solid block under it. The script writes Play / Stop /
   Loading / Needs https into the play cell, so it keeps a word. */
.vz .vy-cta.vw-bar{display:grid;gap:.6rem;width:min(100%,38rem);margin:clamp(1.8rem,3.4vw,2.5rem) auto 0}
.vy-deck{display:grid;grid-template-columns:auto minmax(0,1fr) auto;height:3.4rem;border:1px solid var(--line-hi)}
.vy-deck button{appearance:none;display:flex;align-items:center;justify-content:center;gap:.7rem;margin:0;padding:0 1.3rem;
  background:transparent;border:0;color:var(--ink);cursor:pointer;font-family:var(--mono);font-size:.6875rem;
  letter-spacing:.22em;text-transform:uppercase;transition:background .2s,color .2s}
@media(hover:hover){.vy-deck button:hover{background:var(--ink);color:var(--bg)}}
.vy-deck button:focus-visible{outline:1px solid var(--ink);outline-offset:-4px}
.vz .vw-play{min-width:8.8rem;border-right:1px solid var(--line-hi)}
.vz .vw-play::before{content:'';width:0;height:0;border-style:solid;border-width:.45em 0 .45em .75em;
  border-color:transparent transparent transparent currentColor}
.vz .vw-play[aria-pressed="true"]::before{width:.72em;height:.72em;border:0;background:currentColor}
.vz .vw-bypass,.vz .vw-bypass[aria-pressed="true"]{border-left:1px solid var(--line-hi);background:transparent;color:var(--dim)}
.vz .vw-bypass[aria-pressed="true"]{color:var(--ink)}
/* A switch, so pointing at it must not look like it is on: a faint tint, not the white fill. */
@media(hover:hover){.vz .vw-bypass:hover{background:var(--hover);color:var(--ink)}}
.vz .vw-bypass[hidden]{display:none}
.vz .vw-bypass::before{content:'';width:2.1em;height:1.15em;border-radius:1em;border:1px solid var(--line-hi);box-sizing:border-box;
  background:radial-gradient(circle at .55em 50%,var(--dim) .32em,transparent .36em);transition:background .25s var(--ease),border-color .25s}
.vz .vw-bypass[aria-pressed="true"]::before{border-color:currentColor;
  background:radial-gradient(circle at 1.55em 50%,currentColor .32em,transparent .36em)}
/* The loop cell: a dotted line that runs while the plugin sounds and
   stops dead when it is bypassed. */
.vy-loop{display:flex;align-items:center;gap:.9rem;min-width:0;padding:0 1.1rem;font-family:var(--mono);font-size:.625rem;
  letter-spacing:.22em;text-transform:uppercase;color:var(--faint);white-space:nowrap}
.vy-loop b{font-weight:400}
.vy-loop i{flex:1;height:1px;background:repeating-linear-gradient(90deg,var(--line-hi) 0 2px,transparent 2px 5px)}
.vz.vw-playing .vy-loop{color:var(--dim)}
.vz.vw-playing:not(.vw-bypassed) .vy-loop i{background-image:repeating-linear-gradient(90deg,var(--ink) 0 2px,transparent 2px 5px);
  animation:vy-run .6s linear infinite}
@keyframes vy-run{to{background-position:5px 0}}
/* Once the plugin has sounded, the line becomes an equalizer of its output,
   with the "Arp loop" label gone and room left on both sides. */
.vy-loop:has(i.eq){padding:0 1.6rem}
.vy-loop:has(i.eq) b{display:none}
.vy-loop i.eq{height:1.7rem;background:none!important;animation:none;color:var(--faint);transition:color .6s var(--ease)}
.vy-loop i.eq canvas{display:block;width:100%;height:100%}
.vz.vw-playing:not(.vw-bypassed) .vy-loop i.eq{color:var(--ink)}
.vz.vw-playing.vw-bypassed .vy-loop i.eq{color:var(--dim)}
@media (prefers-reduced-motion:reduce){.vz .vy-loop i{animation:none}}
.vy-dl{display:flex;align-items:center;justify-content:center;gap:.6rem;height:3.4rem;border:1px solid var(--ink);
  background:var(--ink);color:var(--bg);font-family:var(--mono);font-size:.6875rem;letter-spacing:.22em;text-transform:uppercase;
  transition:background .2s,color .2s}
.vy-dl:hover{background:transparent;color:var(--ink)}
.vy-note{margin:1.3rem 0 0;color:var(--faint)}
/* The deck, turned by dragging sideways with the mouse or a finger: the cards
   follow the hand and settle on the nearest one when let go. Cards stand in 3D
   and turn away as they leave; the front one wakes up in colour with the
   plugin's purple under it. Per frame only transform and opacity change
   (no box-reflect, no animated filters): each card carries a grey copy
   under the colour one and fades between. */
/* Contain the side cards without making the gallery a scroll container. */
#inside{overflow-x:clip}
.vc-pin{display:flex;flex-direction:column}
.vc{display:flex;flex-direction:column;outline:0;padding:1rem 0 clamp(2.5rem,6vw,4.5rem)}
.vc-stage{--w:min(58vw,820px,calc((100svh - 25rem) / .9));position:relative;cursor:grab;touch-action:pan-y;height:calc(var(--w) * .9);perspective:1600px;user-select:none}
.vc-glow{position:absolute;left:50%;top:calc(var(--w) * .5);width:calc(var(--w) * 1.5);aspect-ratio:2;translate:-50% -30%;pointer-events:none;
  background:radial-gradient(closest-side,rgb(140 60 255 / .42),rgb(140 60 255 / .12) 55%,transparent);will-change:opacity}
.vc-card{position:absolute;left:50%;top:0;width:var(--w);margin-left:calc(var(--w) / -2);
  display:flex;flex-direction:column;padding:0;border:1px solid var(--line-hi);border-radius:14px;overflow:hidden;background:#050505;color:var(--ink);
  will-change:transform,opacity;transition:border-color .4s;cursor:pointer}
.vc-card[data-front]{border-color:rgb(255 255 255 / .75);cursor:zoom-in}
.vc-card[data-front] .vc-pic{position:relative;display:block}
.vc-pic img{display:block;width:100%;height:auto;pointer-events:none}
.vc-pic .grey{filter:grayscale(1) brightness(.5)}
.vc-pic .col{position:absolute;inset:0}

.vc-cap{display:grid;gap:.8rem;justify-items:center;text-align:center;min-height:5.6rem;align-content:start;max-width:40rem;margin:0 auto}
.vc-cap h3{margin:0;font-weight:900;text-transform:uppercase;font-size:clamp(1.8rem,4vw,3rem);line-height:.9;letter-spacing:-.02em}
.vc-cap p{margin:0;color:var(--dim);max-width:34ch}
.vc-cap.in > *{animation:vc-in .6s var(--ease) both}
.vc-cap.in > :nth-child(2){animation-delay:.04s}.vc-cap.in > :nth-child(3){animation-delay:.08s}
@keyframes vc-in{from{opacity:0;transform:translateY(.6rem)}}

/* Progress: one segment per card, filling as the scroll walks the deck. */
.vc-dots{display:flex;justify-content:center;gap:.5rem;margin:1.4rem auto 0}
.vc-dots button{all:unset;cursor:pointer;width:2.6rem;padding:.7rem 0;position:relative}
.vc-dots button::before,.vc-dots i{content:'';position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;background:var(--line-hi)}
.vc-dots i{background:var(--ink);transform-origin:left;transform:scaleX(0)}
.vc-dots button:focus-visible{outline:1px solid var(--ink);outline-offset:2px}

@media (max-width:760px){.vc-stage{--w:min(80vw,calc((100svh - 21rem) / .9))}}
.vc-stage.dragging{cursor:grabbing}
.vc-stage.dragging .vc-card{cursor:grabbing}
/* the name and its line sit above the deck now, with room under them */
.vc-cap{margin-bottom:clamp(1.6rem,3.5vh,2.4rem)}
.vc-zoom{border:0;padding:0;background:transparent;max-width:min(94vw,1200px)}
.vc-zoom[open]{animation:vc-pop .4s var(--ease)}
@keyframes vc-pop{from{opacity:0;scale:.94}}
.vc-zoom::backdrop{background:rgba(0,0,0,.9)}
.vc-zoom img{display:block;width:100%;height:auto;border-radius:12px;cursor:zoom-out}
.vx-reactors{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));border-top:1px solid var(--line)}
.vx-reactors li{padding:1.3rem 1.2rem 1.5rem 0;border-bottom:1px solid var(--line)}
.vx-reactors h3{margin:.45rem 0 .35rem;font-size:clamp(1.4rem,2.4vw,2rem);font-weight:900;text-transform:uppercase}
.vx-reactors p{margin:0;color:var(--dim);max-width:30ch}
/* The video is grey until it runs, then the colour comes in. */
.vx .player video{filter:grayscale(1) contrast(1.06);transition:filter .9s var(--ease)}
.vx .player.vx-colour video{filter:none}
.vx-get{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);
  grid-template-areas:"faq specs" "faq download";grid-template-rows:auto 1fr;
  column-gap:clamp(1.5rem,4vw,3rem);row-gap:1.5rem;align-items:start}
.vx-get > .faq{grid-area:faq}
.vx-get > .specs-block{grid-area:specs}
.vx-download{grid-area:download}
@media (max-width:900px){.vx-get{grid-template-columns:minmax(0,1fr);grid-template-rows:auto;
  grid-template-areas:"download" "faq" "specs";gap:clamp(1.5rem,4vw,3rem)}}
/* Phones: the window runs edge to edge, and each knob takes a touch half as
   big again as its drawing, so a finger can find it. */
@media (max-width:760px){.vy-tags{font-size:.5rem;letter-spacing:.14em}
  .vz .vy-product{width:100vw;margin-left:calc(50% - 50vw);margin-top:-3.6cqi}
  .vw-hit[role="slider"]{transform:scale(1.55)}}
@media (max-width:480px){.vy-loop b{display:none}.vz .vw-play{min-width:7.4rem}.vy-deck button{padding:0 1rem}}

/* ---- The VOIDWORM card: the plugin itself, grey until pointed at -------
   It replaces the line-drawing plate and its lens. Grey so it sits in the
   page's black and white; the plugin's own colour comes in on hover or
   keyboard focus nearby, over the whole window at once. */
.t-lead .plate img{filter:grayscale(1) contrast(1.04) brightness(.95);
  transition:filter .6s cubic-bezier(.2,.7,.2,1)}
.t-lead .plate:hover img,.grid:has(.t-copy:focus-within) .t-lead .plate img{filter:none}
@media (max-width:899px){.t-pills{margin-top:clamp(2.5rem,10vw,4.5rem)}}

/* The riff roll: the lab's fourth window, the riff drawn as it plays. */
.lab-panel .roll,.lab-panel .lvl{border:1px solid var(--line-hi);padding:.5rem;cursor:ew-resize;touch-action:none}
.lab-panel .lvl svg{display:block;width:100%;height:.9rem;fill:var(--ink)}
.lab-panel .lvl .lbl{display:block;margin-top:.45rem}
.lab-panel .roll svg{display:block;width:100%;height:2.2rem;fill:var(--ink)}
.lab-panel .roll[data-idle] svg{opacity:.35}
.lab-panel .roll .lbl{display:block;margin-top:.45rem}
.lab-panel .roll:focus-visible,.lab-panel .lvl:focus-visible{outline:2px solid var(--ink);outline-offset:3px}


/* The plugin windows arrive: each floats up out of a soft blur as its tile
   comes into view (index.html marks it .shown, and unmarks it once it is
   back below the screen, so it plays again on the way down). */
.rev-on .t-lead .plate{transition:transform 2s cubic-bezier(.2,.8,.2,1),opacity 1.6s,filter 1.6s}
.rev-on .t-lead .plate:not(.shown){opacity:0;transform:translateY(70px);filter:blur(8px)}

/* ---- Settings ------------------------------------------------------------
   The panel under the header's Settings button: colour, motion, volume. */
.prefs{position:absolute;top:100%;right:max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2));z-index:41;
  width:min(22rem,calc(100vw - 2rem));padding:1rem 1.1rem;background:var(--bg);border:1px solid var(--line-hi);
  display:flex;flex-direction:column;gap:1rem;box-sizing:border-box}
.prefs-head{display:none;justify-content:space-between;align-items:center;font-family:var(--mono);font-size:.6875rem;letter-spacing:.22em;text-transform:uppercase;color:var(--dim)}
.prefs-x{background:0;border:0;color:var(--ink);font-size:1.4rem;line-height:1;cursor:pointer;padding:0 .2rem}
.pref{display:grid;grid-template-columns:5.5rem 1fr;align-items:center;gap:.4rem .6rem}
.pref .lbl{color:var(--faint)}
.pref-opts{display:flex;gap:.3rem}
.pref-opts button{flex:1;font-family:var(--mono);font-size:.625rem;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);
  background:0;border:1px solid var(--line-hi);padding:.55em .5em;cursor:pointer}
.pref-opts button:hover{color:var(--ink);border-color:var(--ink)}
.pref-opts button[aria-pressed="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.pref-vol{align-items:center;gap:.6rem}
.pref-vol input{flex:1;accent-color:var(--ink)}
.pref-vol output{font-family:var(--mono);font-size:.625rem;letter-spacing:.1em;min-width:2.6em;text-align:right;color:var(--dim)}
.pref-note{display:none;grid-column:2;margin:0;color:var(--faint);font-size:.75rem;line-height:1.45}
.menu .theme .gear{display:block;width:15px;height:15px;overflow:visible;transition:transform .5s var(--ease)}
.menu .theme .gear path{stroke:currentColor;stroke-width:2.2;fill:none}
.menu .theme .gear circle{stroke:currentColor;stroke-width:1.2;fill:none}
.menu .theme:hover .gear,.menu .theme[aria-expanded="true"] .gear{transform:rotate(45deg)}
.menu .theme::after{content:none}

.pref-opts .pi{display:none}
.pref-opts .pi svg{width:16px;height:16px;display:block;overflow:visible}
.pref-opts .pi path,.pref-opts .pi circle{fill:none;stroke:currentColor;stroke-width:1.3;stroke-linecap:round}
.pref-opts .pi .f{fill:currentColor;stroke:none}
/* The panel is built like the plugins' own controls, kept small: a label on
   the left of each row, three square cells sharing hairlines on the right
   (the word is the tooltip), volume as a thin row under them. The chosen cell
   lights its mark and gets a bar along its foot. It eases open. */
.prefs{width:auto;min-width:0;padding:.35rem .9rem .55rem;gap:0;border-color:var(--line-hi);
  box-shadow:0 18px 44px rgba(0,0,0,.5)}
.pref{display:grid;grid-template-columns:4.4rem auto;align-items:center;gap:0 .6rem;padding:.3rem 0}
.pref + .pref{border-top:1px solid var(--line)}
.pref .lbl{font-size:.5625rem;letter-spacing:.24em;color:var(--faint)}
.pref-opts{display:flex;gap:0;border:1px solid var(--line-hi);justify-self:start}
.pref-opts button{position:relative;flex:0 0 auto;width:2.3rem;height:2.1rem;padding:0;border:0;border-right:1px solid var(--line-hi);
  display:grid;place-items:center;color:var(--faint);background:none;transition:color .2s,background .2s}
.pref-opts button:last-child{border-right:0}
.pref-opts button:hover{background:var(--hover);color:var(--dim)}
.pref-opts button[aria-pressed="true"]{background:none;color:var(--ink);border-color:var(--line-hi)}
.pref-opts button::after{content:'';position:absolute;left:.45rem;right:.45rem;bottom:-1px;height:2px;background:var(--ink);
  transform:scaleX(0);transition:transform .22s var(--ease)}
.pref-opts button[aria-pressed="true"]::after{transform:none}
.pref-opts .pt{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.pref-opts .pi{display:block}
.pref-opts .pi svg{width:15px;height:15px}
.pref-vol{border:0;align-items:center;gap:.55rem;height:2.1rem;width:calc(3 * 2.3rem + 2px);justify-content:space-between}
.pref-vol output{color:var(--dim);font-size:.5625rem;letter-spacing:.14em;min-width:2.8em;text-align:right}
.pref-vol output svg{display:block;width:14px;height:14px;margin-left:auto;fill:none;stroke:currentColor;stroke-width:1.3;stroke-linejoin:round;stroke-linecap:round}
.pref-vol input{-webkit-appearance:none;appearance:none;flex:1;min-width:0;height:16px;background:none;cursor:pointer;margin:0}
.pref-vol input::-webkit-slider-runnable-track{height:1px;background:var(--line-hi)}
.pref-vol input::-moz-range-track{height:1px;background:var(--line-hi)}
.pref-vol input::-webkit-slider-thumb{-webkit-appearance:none;box-sizing:border-box;width:6px;height:12px;margin-top:-5.5px;background:var(--bg);border:1px solid var(--ink);border-radius:999px;box-shadow:none}
.pref-vol input::-moz-range-thumb{box-sizing:border-box;width:6px;height:12px;background:var(--bg);border:1px solid var(--ink);border-radius:999px;box-shadow:none}


/* The window is a glass capsule out of the gear: it slides out sideways, to the left of
   the gear, inside the header line, over the menu while it is open; glass,
   with the page blurred behind; colour | motion | volume */
.prefs{position:absolute;top:50%;translate:0 -50%;left:auto;width:auto;
  right:calc(max(clamp(1rem,3.5vw,2.75rem),calc((100% - var(--measure)) / 2)) + 1.9rem);
  flex-direction:row;align-items:center;gap:0;padding:.22rem;border:1px solid rgba(255,255,255,.14);border-radius:999px;
  background:rgba(14,14,14,.6);-webkit-backdrop-filter:blur(16px) saturate(1.2);backdrop-filter:blur(16px) saturate(1.2);box-shadow:none;
  /* shut: folded into the gear and out of reach */
  visibility:hidden;opacity:0;pointer-events:none;clip-path:inset(0 0 0 100% round 999px);
  transition:clip-path .45s cubic-bezier(.65,0,.35,1),opacity .3s ease .12s,visibility 0s linear .45s}
/* open: the glass unfolds leftward out of the gear, the controls glide in after
   it one by one; shutting runs the same way back */
.prefs.open{visibility:visible;opacity:1;pointer-events:auto;clip-path:inset(0 0 0 0 round 999px);
  transition:clip-path .6s cubic-bezier(.22,1,.36,1),opacity .2s ease,visibility 0s}
.pref{--pref-space:.2rem;grid-template-columns:auto;padding:0 var(--pref-space);opacity:0;transform:translateX(1.4rem);
  transition:transform .35s cubic-bezier(.65,0,.35,1),opacity .25s ease}
.prefs.open .pref{opacity:1;transform:none;transition:transform .6s cubic-bezier(.22,1,.36,1),opacity .4s ease}
.prefs.open .pref:nth-of-type(1){transition-delay:.08s}.prefs.open .pref:nth-of-type(2){transition-delay:.14s}.prefs.open .pref:nth-of-type(3){transition-delay:.2s}
.prefs:not(.open) .pref:nth-of-type(3){transition-delay:0s}.prefs:not(.open) .pref:nth-of-type(2){transition-delay:.04s}.prefs:not(.open) .pref:nth-of-type(1){transition-delay:.08s}
.pref .lbl{display:none}
.pref + .pref{border-top:0;border-left:1px solid rgba(255,255,255,.14);padding-left:calc(var(--pref-space) + .15rem)}
.pref:not(:last-child){padding-right:calc(var(--pref-space) + .15rem)}
.pref-opts{border:0;gap:.1rem}
.pref-opts button{border:0;width:1.9rem;height:1.9rem;border-radius:50%}
.pref-opts button:hover{background:rgba(255,255,255,.1)}
.pref-opts button[aria-pressed="true"]{background:rgba(255,255,255,.18);color:var(--ink)}
.pref-opts button::after{display:none}
.pref-vol{width:7rem;gap:.55rem;padding:0 .55rem 0 .45rem}
/* Folded: only the chosen cell shows; .pick opens the group so the others
   slide out beside it, and the capsule grows leftward with them. */
.pref-opts button{transition:width .4s cubic-bezier(.22,1,.36,1),opacity .25s ease,background .2s,color .2s}
.pref:not(.pick) .pref-opts button:not([aria-pressed="true"]){width:0;opacity:0;pointer-events:none}
.pref:not(.pick) .pref-opts:not(.pref-vol){gap:0}
.pref.pick .pref-opts button[aria-pressed="true"]{background:rgba(255,255,255,.26)}
/* While it is open, the menu links step aside under it instead of showing through. */
.top .menu > a{transition:opacity .3s ease,transform .45s cubic-bezier(.22,1,.36,1),filter .3s ease}
.prefs-open .top .menu > a{opacity:0;transform:translateX(-.8rem);filter:blur(3px);pointer-events:none}
:root[data-theme="light"] .prefs{background:rgba(240,239,236,.7);border-color:rgba(0,0,0,.12)}
:root[data-theme="light"] .pref + .pref{border-left-color:rgba(0,0,0,.12)}
:root[data-theme="light"] .pref-opts button[aria-pressed="true"]{background:rgba(0,0,0,.1)}
@media(max-width:520px){.pref-opts button{width:1.7rem;height:1.7rem}.pref-vol{width:4.6rem}}


/* Phones: the same one-line pill as on desktop, beside the gear, a little
   larger for fingers. It stops short of the name on the left. */
@media(max-width:520px),(pointer:coarse) and (max-width:900px){
  .prefs{right:2.8rem;padding:.22rem}
  .pref-opts button{width:2.45rem;height:2.45rem}
  .pref-vol{width:6.6rem;height:2.45rem;gap:.5rem;touch-action:none}
  /* the script drags it from anywhere on the strip; a bigger thumb to see */
  .pref-vol input{height:2.45rem;touch-action:none;pointer-events:none}
  .pref-vol input::-webkit-slider-thumb{width:10px;height:18px;margin-top:-8.5px}
  .pref-vol input::-moz-range-thumb{width:10px;height:18px}
}



/* Phone labels gain a little reading room; the approved hero tagline keeps
   its own small scale. Desktop typography stays as designed. */
@media(max-width:760px){
  .lbl:not(.vy-tags),.nav a,.menu a,.jump .n,.links a .n,.traits h3,.tag,
  .inline.mono,.cta,.keys b,.foot-links a,.flow .step,.entry-meta time,
  .cost,.log-filter button,.spec,.specs,.scope-read,.lab-panel,
  .lab-panel .snd,.scope[data-hint]::after,.pref-vol output{font-size:.6875rem}
  .foot-group > .lbl{font-size:.75rem}
  .scope-read{flex-wrap:wrap;gap:.15rem .3rem;white-space:normal}
  .scope[data-mode="wave"] .scope-read span,.scope[data-mode="wave"] .scope-read b{background:var(--bg)}
  .lab-panel{padding:.5rem;gap:.3rem}
  .lab-panel .snd{padding:.25em .2em;letter-spacing:.02em;white-space:nowrap}
  .lab-btns .snd:not(.keep){min-width:0;flex:1;white-space:normal;line-height:1.15}
  .lab-btns .keep{flex-shrink:0}
  .lab-panel .roll,.lab-panel .lvl{padding:.35rem}
  .lab-panel .roll{min-height:2.4rem}
  .lab-panel .roll-gesture{display:none}
  .lab-panel .lvl svg{height:.5rem}
  .lab-panel .roll .lbl,.lab-panel .lvl .lbl{margin-top:.25rem}
}

/* Sections reached directly are readable at once; ordinary scrolling keeps
   its existing fade, stagger and scroll-linked movement. */
.js .rise.rise-now,.js .rise.rise-now .log-when{
  opacity:1;transform:none;animation:none;transition:none;transition-delay:0s}
.js .log-row.rise-now::before{animation:none;transform:scaleX(1)}

/* Motion off: nothing animates or eases, and pages change without the
   travelling transition. The scripts read the same attribute and hold still. */
[data-motion="off"] *,[data-motion="off"] *::before,[data-motion="off"] *::after,
[data-motion="reduced"] *,[data-motion="reduced"] *::before,[data-motion="reduced"] *::after{
  animation:none!important;transition:none!important;scroll-behavior:auto!important}
[data-motion="off"]::view-transition-group(*),[data-motion="off"]::view-transition-old(*),[data-motion="off"]::view-transition-new(*),
[data-motion="reduced"]::view-transition-group(*),[data-motion="reduced"]::view-transition-old(*),[data-motion="reduced"]::view-transition-new(*){animation:none!important}
