/* ═══════════════════════════════════════════════════════════════════
   theme: artiphon
   Amber CRT phosphor on warm near-black, with nixie orange for dates and
   figures. Carried over from the v3 site design, which suits this subject
   far better than it ever suited a homepage: the project is 2021 consumer
   synth hardware, an STM32 running bare metal, and firmware pulled out of
   an installer. The look is derived from the thing itself.
   ═══════════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@400;600;800&family=Chakra+Petch:wght@300;400;500;600&family=VT323&display=swap');

:root {
  --ink:    #0A0806;
  --panel:  #14100B;
  --panel2: #1C1710;
  --edge:   #241E17;
  --edge-2: #1A150F;
  --text:   #EBE2D3;
  --text-2: #B2A491;
  --text-3: #8B7F6E;
  --nixie:  #FF7A28;
  --glow:   #FFA53F;
  --glow-h: #FFDCB0;
  --glow-rgb: 255,165,63;
  --pad: clamp(1.3rem, 5vw, 3rem);
  --max: 60rem;
  --disp: "Big Shoulders Display", Impact, sans-serif;
  --body: "Chakra Petch", system-ui, sans-serif;
  --tty:  "VT323", "Courier New", monospace;
}

body {
  background: var(--ink);
  color: var(--text);
  font: 300 clamp(.98rem, .3vw + .92rem, 1.06rem)/1.66 var(--body);
  position: relative;
}
/* CRT line structure and a warm vignette. Fixed so it reads as the surface
   you are looking through rather than something on the page. */
body::before, body::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 50;
}
body::before {
  background: repeating-linear-gradient(0deg, rgba(0,0,0,.22) 0 1px, transparent 1px 3px);
  opacity: .5;
}
body::after {
  background:
    radial-gradient(140% 115% at 50% 10%, transparent 46%, rgba(0,0,0,.5) 100%),
    linear-gradient(180deg, rgba(0,0,0,.3), transparent 15%);
}
.band, .pr, .pr__foot { position: relative; z-index: 2; }

/* ── phosphor traces ────────────────────────────────────────────────
   Burn-in: a disassembly listing left on the tube long enough to ghost
   into it. Text from themes/artiphon.backdrop.txt, set in VT323, which
   is a DEC VT terminal face and so the right period for the subject.

   Real text rather than a generated image, because a webfont is the
   whole point here and an SVG used as a CSS background cannot load one.

   The listing is idiomatic bare-metal Cortex-M4 - reset, clock, pad
   scan, SAI with DMA, a mix loop, an image CRC check. It is scenery
   that suits the subject, not a dump of anyone's firmware. */
.backdrop {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
  user-select: none; -webkit-user-select: none;
  /* hold the listing off the reading column. In rem, not percentages: the
     column is a fixed 60rem centred, so percentage stops would only line
     up at one window width. */
  mask-image: var(--trace-mask);
  -webkit-mask-image: var(--trace-mask);
}
:root {
  --trace-mask: linear-gradient(90deg,
    #000 0,
    #000 calc(50% - 33rem),
    rgba(0,0,0,.10) calc(50% - 29rem),
    rgba(0,0,0,.10) calc(50% + 29rem),
    #000 calc(50% + 33rem),
    #000 100%);
}
.backdrop pre {
  margin: 0; padding: 1.2rem 1.6rem;
  height: 100%;
  /* Columns must be wider than the longest line: white-space is pre, so a
     narrow column would let lines run out into the next one. column-fill is
     auto so columns fill left to right, which is why the listing has to be
     long enough to reach the last one - the rightmost column is a margin the
     reader actually sees, and a short listing would leave it blank. */
  columns: 24rem; column-gap: 3rem; column-fill: auto;
  font: 400 clamp(.82rem, .5vw + .6rem, 1rem)/1.42 var(--tty);
  white-space: pre;
  color: var(--nixie);
  opacity: .17;
  text-shadow: 0 0 6px rgba(var(--glow-rgb), .55), 0 0 18px rgba(var(--glow-rgb), .3);
  /* the faintest drift, so it reads as persistence rather than wallpaper */
  animation: traceDrift 34s ease-in-out infinite alternate;
}
@keyframes traceDrift {
  from { transform: translateY(0) }
  to   { transform: translateY(-14px) }
}

a { color: var(--glow); }
::selection { background: var(--nixie); color: #14100B; }
:focus-visible { outline: 2px solid var(--glow); outline-offset: 2px; }

/* band */
.band {
  background: linear-gradient(180deg, #262019 0%, var(--panel) 62%, #0d0a07 100%);
  border-bottom: 1px solid #000;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 4px 18px rgba(0,0,0,.7);
  font: 400 .84rem/1 var(--tty); letter-spacing: .1em;
}
.band a { color: var(--text-2); text-decoration: none; }
.band a:hover { color: var(--glow); text-shadow: 0 0 8px rgba(var(--glow-rgb), .6); }
.band__home {
  font: 800 1.15rem/1 var(--disp) !important;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--glow) !important;
  text-shadow: 0 0 6px rgba(var(--glow-rgb), .5);
}

/* head */
.pr__name {
  font: 800 clamp(2.2rem, 6vw, 3.6rem)/.94 var(--disp);
  letter-spacing: .01em; text-transform: uppercase;
  color: var(--glow);
  text-shadow: 0 0 10px rgba(var(--glow-rgb), .35), 0 0 30px rgba(var(--glow-rgb), .18);
}
.pr__sum { color: var(--text-2); font-size: 1.06em; }
.pr__meta { border-top: 1px solid var(--edge); font: 400 .84rem/1 var(--tty); letter-spacing: .12em; }
.pr__fields { color: var(--text-3); }
.pr__links a {
  font: 500 .74rem/1 var(--body); letter-spacing: .16em; text-transform: uppercase;
  border-bottom: 1px solid var(--edge); padding-bottom: 3px; text-decoration: none;
}

.pr > section { border-top: 1px solid var(--edge-2); }
.pr > section > h2 {
  font: 400 .92rem/1 var(--tty); letter-spacing: .24em;
  text-transform: uppercase; color: var(--text-3);
  display: flex; align-items: center; gap: .8rem;
}
.pr > section > h2::before {
  content: ""; width: 2rem; height: 3px; background: var(--nixie);
  box-shadow: 0 0 8px rgba(255,122,40,.8);
}

/* status */
.st { font: 400 .84rem/1 var(--tty); letter-spacing: .1em; color: var(--text-2); }
.st::before { background: var(--text-3); }
.st--run::before {
  background: var(--glow-h);
  box-shadow: 0 0 4px var(--glow-h), 0 0 13px rgba(var(--glow-rgb), .85);
}
.st--bench::before { background: var(--nixie); box-shadow: 0 0 10px rgba(255,122,40,.7); }
.st--dead::before { background: #3a2a24; }

/* reference */
.pr__ref p, .pr__ref li { color: var(--text-2); }
.tbl table { font-size: .9em; }
.tbl th {
  font: 400 .82rem/1.4 var(--tty); letter-spacing: .14em; text-transform: uppercase;
  color: var(--nixie); border-bottom: 1px solid var(--edge);
}
.tbl td { border-bottom: 1px solid var(--edge-2); color: var(--text-2); }
.tbl tr:hover td { background: rgba(255,255,255,.022); }

/* files: a rack of labelled slots */
.dl li {
  border: 1px solid #000; margin-bottom: .5rem; padding: .8rem 1rem;
  background: linear-gradient(168deg, var(--panel2), var(--panel) 60%, #0d0a07);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
}
.dl a { text-decoration: none; }
.dl__label { font: 500 .82rem/1 var(--body); letter-spacing: .12em; text-transform: uppercase; color: var(--glow); }
.dl__size { font: 400 .84rem/1 var(--tty); color: var(--text-3); }
.dl__desc { font-size: .92em; color: var(--text-3); }

/* log */
.ent { border-bottom: 1px dashed var(--edge); }
.ent__when time {
  display: inline-block; padding: .3rem .5rem;
  font: 400 .95rem/1 var(--tty); letter-spacing: .08em; color: var(--nixie);
  border: 1px solid rgba(255,122,40,.3); background: rgba(255,122,40,.07);
}
.ent__body h3 {
  font: 600 clamp(1.24rem, 2.2vw, 1.6rem)/1.14 var(--disp);
  letter-spacing: .015em; text-transform: uppercase;
}
.ent__body h3 a { color: var(--text); }
.ent__body h3 a:hover { color: var(--glow); text-shadow: 0 0 9px rgba(var(--glow-rgb), .5); }
.ent__body p, .ent__body li { color: var(--text-2); }
.ent__body strong { color: var(--text); font-weight: 600; }
.ent__body h4 {
  font: 600 .8rem/1.3 var(--body); letter-spacing: .16em;
  text-transform: uppercase; color: var(--text);
}
.ent__body li::marker { color: var(--nixie); }
code {
  font: 400 .92em/1.4 var(--tty); color: var(--glow);
  background: rgba(var(--glow-rgb), .08);
  border: 1px solid rgba(var(--glow-rgb), .2); padding: .05em .35em;
}
pre { background: #0d0a07; border: 1px solid #000; box-shadow: inset 0 1px 0 rgba(255,255,255,.04); }
pre code { border: 0; background: none; padding: 0; font-size: .92rem; line-height: 1.6; }
.pr__empty { color: var(--text-3); font: 400 .95rem/1.6 var(--tty); letter-spacing: .1em; }

.pr__foot {
  border-top: 1px solid var(--edge); font: 400 .86rem/1.6 var(--tty);
  letter-spacing: .12em; color: var(--text-3);
}
.pr__foot a { color: var(--text-2); text-decoration: none; }
.pr__foot a:hover { color: var(--glow); }

/* ── nixie readout ──────────────────────────────────────────────────
   The figures strip becomes a bank of tubes, one per digit. Carried over
   from the v3 homepage where it was a vanity stat bar; here the numbers
   are load-bearing (devices in scope, objects still to rescue, KB of
   flash left), so the tubes are illustrating something real.

   Each tube: translucent envelope so its own halo shows through, an
   offset ghost digit behind via text-shadow, and an anode mesh in front. */
.figs { gap: 1.6rem 2rem; }
/* the bank sits centred over its caption. A single tube left-aligned under a
   wide caption read as though it had drifted off the end of the label. */
.figs li { align-items: center; text-align: center; }

/* ── the plinth ──────────────────────────────────────────────────────
   Each triplet is mounted on a wooden base, the way a nixie clock is built:
   the tubes are socketed into it and their pins disappear below the top
   surface. Drawn as a lit front edge over a face that falls away, so it reads
   as a solid seen slightly from above rather than as a brown stripe. */
.figs b {
  position: relative;
  gap: .3rem; justify-content: center; align-items: flex-end;
  padding-bottom: .8rem;
}
.figs b::after {
  content: ""; position: absolute;
  left: -.42rem; right: -.42rem; bottom: 0; height: .88rem;
  background:
    /* varnish catching the room light along the front edge */
    linear-gradient(180deg, rgba(255,226,184,.16) 0 2px, transparent 2px),
    /* grain: uneven horizontal figure, not a ruled pattern. A near-vertical
       repeating gradient crossed with the block gradient made brickwork. */
    repeating-linear-gradient(179deg,
      transparent 0 2px, rgba(24,12,3,.16) 2px 3px, transparent 3px 7px,
      rgba(24,12,3,.10) 7px 8px, transparent 8px 13px),
    linear-gradient(180deg, #5A3E22 0 3px, #402B16 3px 58%, #2C1C0C 100%);
  box-shadow:
    inset 0 -2px 0 0 rgba(0,0,0,.42),
    0 2px 0 0 #23150A,
    0 5px 12px rgba(0,0,0,.62);
}

.figs b i {
  position: relative;
  display: grid; place-items: center;
  /* Narrower and taller, closer to an IN-18: the envelope is a tall cylinder,
     not a squat one. The digit centres on the tube's mid-height, which is
     where it sits in the reference; the dome takes the space above it and the
     pin assembly the space below. */
  width: 1.95rem; height: 4.5rem;
  /* Digits have no descenders, so the ink sits above the centre of the line
     box: centring the box left the numeral measurably 1.9% high. This puts
     the ink itself on the tube's mid-height. */
  padding-top: .18rem;
  z-index: 0;                    /* scope the glass layers to this tube */
  /* the condensed display face, because a nixie numeral is tall and narrow:
     the body face at this size was too wide to fill the envelope and left
     the digit spanning only four hexagons */
  font: 600 2.5rem/1 var(--disp);
  font-variant-numeric: tabular-nums;
  color: #FFE2C0;
  border-radius: 44% 44% 15% 15% / 21% 21% 6% 6%;
  border: 1px solid rgba(206,224,232,.24);
  background:
    radial-gradient(118% 62% at 50% 6%,  rgba(232,248,255,.15), transparent 54%),
    radial-gradient(92% 70% at 50% 64%,  rgba(255,110,24,.11), transparent 72%),
    /* the pin assembly, glowing where the cathode leads come through */
    radial-gradient(58% 9% at 50% 88%, rgba(255,132,44,.34), transparent 74%),
    repeating-linear-gradient(90deg,
      transparent 0 3px, rgba(214,182,150,.13) 3px 4px, transparent 4px 7px),
    linear-gradient(176deg, rgba(150,186,190,.16) 0%, rgba(58,78,84,.10) 34%, rgba(8,11,15,.62) 100%);
  background-repeat: no-repeat, no-repeat, no-repeat, repeat-x, no-repeat;
  background-position: 0 0, 0 0, 0 0, 50% 90%, 0 0;
  background-size: auto, auto, auto, 62% 7%, auto;
  /* The wide halo layers were filling the whole window with orange, which
     left the stroke no lighter than its own glow and unreadable through the
     mesh. Kept tight, so the cathode reads as a bright wire in a haze. */
  text-shadow:
    2px 3px 0 rgba(214,196,172,.07),
    0 0 1px #FFF3E4, 0 0 2px #FFDCB4, 0 0 5px #FFA054,
    0 0 10px #FF7A1F, 0 0 19px rgba(255,106,21,.48), 0 0 34px rgba(255,96,10,.24);
  box-shadow:
    /* glass is thicker at the silhouette, so the walls brighten inward */
    inset  3px 0 6px rgba(226,244,255,.09),
    inset -3px 0 6px rgba(226,244,255,.06),
    inset 0 2px 0   rgba(232,248,255,.26),      /* crisp lit dome rim */
    inset 0 0 22px  rgba(255,122,40,.19),       /* discharge in the envelope */
    inset 0 -9px 14px rgba(0,0,0,.7),
    0 0 26px rgba(255,116,26,.24),
    0 3px 0 -2px #322a20, 0 6px 0 -3px #1e1811; /* pin base, stepped in */
  filter: brightness(var(--peak, .9));
  animation: nxFlick var(--dur, 6.3s) var(--delay, 0s) steps(1) infinite;
}
/* Per-tube character, so a bank never flickers in lockstep. --spec shifts the
   highlight sideways: adjacent tubes in a real bank do not catch the light at
   the same angle, and identical highlights are what make a row read as CSS.

   Period and phase come off --k, the digit's index across the whole strip,
   which the build emits. nth-child alone is not enough: it restarts at every
   figure, so once every figure carried three digits, all four first tubes
   drew the same cycle and the strip beat in columns. Both numbers are
   irrational-ish multiples so no two of twelve tubes share a period. */
.figs b i {
  --dur:   calc(5.4s + var(--k, 0) * 0.43s);
  --delay: calc(var(--k, 0) * -0.79s);
}
/* peak output and highlight offset cycle every five, which nth-child gives
   for free and which does not need to be unique to read as a real bank */
.figs b i:nth-child(5n+1) { --peak: .92; --spec:  0%; }
.figs b i:nth-child(5n+2) { --peak: .84; --spec:  3%; }
.figs b i:nth-child(5n+3) { --peak: .97; --spec: -3%; }
.figs b i:nth-child(5n+4) { --peak: .87; --spec:  4.5%; }
.figs b i:nth-child(5n)   { --peak: .81; --spec: -4.5%; }
@keyframes nxFlick {
  0%, 88%, 100% { filter: brightness(calc(var(--peak, .9)      )) }
  89%           { filter: brightness(calc(var(--peak, .9) * .74)) }
  91%           { filter: brightness(calc(var(--peak, .9) * .96)) }
  93%           { filter: brightness(calc(var(--peak, .9) * .83)) }
}

/* The specular highlight, in three parts rather than one shape.

   It was a single elongated ellipse the height of the envelope, rotated off
   vertical. That tapers at both ends, which was the point, but once the tube
   became tall and narrow it rendered as a drawn line with an even width down
   its whole length. A cylinder does throw a roughly straight highlight, so
   the geometry was not the problem: the problem was that it had no falloff
   along its length and no beginning.

   So: a bright core high on the barrel where the light actually catches, a
   soft body under it fading out well before the base, and an arc across the
   dome struck from a ring centred above the tube, so the gleam starts by
   following the curve instead of simply beginning. --spec slides all three
   together, keeping the tube's reflection internally consistent. */
.figs b i::before {
  content: ""; position: absolute; inset: 0; z-index: 3;
  border-radius: inherit; pointer-events: none;
  background:
    /* the arc over the dome, kept modest so it does not become a band */
    radial-gradient(ellipse 46% 26% at calc(50% + var(--spec, 0%)) -6%,
      transparent 56%, rgba(255,255,255,.24) 70%,
      rgba(255,255,255,.05) 80%, transparent 91%),
    /* the hot spot where the light actually catches, high on the barrel */
    radial-gradient(ellipse 3.4% 8% at calc(15% + var(--spec, 0%)) 33%,
      rgba(255,255,255,.80), rgba(255,255,255,.30) 58%, transparent 92%),
    /* drawn down into a thin line: still crisp, but tapering at both ends
       rather than holding one width the length of the envelope */
    radial-gradient(ellipse 1.9% 27% at calc(15.5% + var(--spec, 0%)) 46%,
      rgba(255,255,255,.52) 0 38%, rgba(255,255,255,.16) 68%, transparent 95%),
    /* and the haze around it, which is what stops it reading as a stroke */
    radial-gradient(ellipse 8% 30% at calc(17% + var(--spec, 0%)) 48%,
      rgba(255,255,255,.15), rgba(255,255,255,.05) 46%, transparent 86%);
}
/* Front surface of the glass: everything between the eye and the digit. The
   soft gradients alone read as plastic; it is the hard off-vertical streak
   above plus these edge cues that make it glass. Front-to-back order. */
.figs b i::after {
  content: ""; position: absolute; inset: 0; z-index: 2; border-radius: inherit;
  background:
    /* the dimmer reflection off the far wall of the glass */
    radial-gradient(ellipse 5% 22% at calc(85% + var(--spec, 0%)) 48%,
      rgba(255,255,255,.11), transparent 78%),
    /* just enough crown sheen to round the dome */
    radial-gradient(ellipse 66% 18% at 50% 2%, rgba(226,242,255,.06), transparent 74%),
    /* thicker glass at the silhouettes */
    linear-gradient(90deg,  rgba(224,242,255,.26) 0 1.5px, transparent 1.5px),
    linear-gradient(270deg, rgba(224,242,255,.17) 0 1.5px, transparent 1.5px),
    /* the dark room reflected low, which is what makes the upper glass read
       as transmitting rather than painted */
    radial-gradient(ellipse 116% 34% at 50% 92%, rgba(4,8,13,.46), transparent 72%),
    /* The anode mesh, over the lit digit and under the reflections above,
       which is where it physically sits: the honeycomb grid stands between
       the cathodes and the front of the glass, so a numeral is always read
       through it. Confined to the digit window rather than repeated, so no
       mesh runs across the dome or the pin base where there is none.
       Crossed gradients could only ever give squares, hence a real panel. */
    url("/assets/nixie-grid.svg");
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat,
                     no-repeat, no-repeat;
  background-position: 0 0, 0 0, 0 0, 0 0, 0 0, 50% 50%;
  background-size: auto, auto, auto, auto, auto, 74% 61%;
}
.figs li span {
  font: 400 .84rem/1.5 var(--tty); letter-spacing: .2em;
  text-transform: uppercase; color: var(--text-3);
}

/* ── valve dividers ─────────────────────────────────────────────────
   A row of small tubes with pulsing filaments between sections. */
.pr > section + section::before {
  content: ""; display: block;
  height: 2.4rem; margin: -1rem 0 2rem;
  background-image:
    radial-gradient(circle at 50% 60%, rgba(255,122,40,.75) 0 1px, transparent 1.6px),
    linear-gradient(180deg, rgba(96,108,120,.10), rgba(9,11,14,.5));
  background-repeat: repeat-x, repeat-x;
  background-size: 3.5rem 100%, 1.1rem 1.85rem;
  background-position: center, center;
  opacity: .5;
}

/* a slow phosphor sweep, so the page reads as a live screen */
.pr::after {
  content: ""; position: fixed; left: 0; right: 0; height: 40vh;
  pointer-events: none; z-index: 40;
  background: linear-gradient(180deg, transparent, rgba(var(--glow-rgb), .035), transparent);
  animation: sweep 9s linear infinite;
}
@keyframes sweep { from { top: -42vh } to { top: 102vh } }

@media (prefers-reduced-motion: reduce) {
  /* keep each tube's own output; the keyframes have no forwards fill */
  .figs b i { animation: none; filter: brightness(var(--peak, .9)); }
  .pr::after { display: none; }
  .backdrop { animation: none; }
}
