RDO–091.7 / FIELD MANUAL ← BACK TO TRANSMISSION

How toHijackthe Air

The complete technical field manual for “Brutalist Broadcast” — a pirate-radio zine built as a single static HTML page with GSAP, ScrollTrigger and two raw canvases. No build step. No framework. No license.

014
FORMAT1 × HTML FILE
LIBRARIESGSAP + SCROLLTRIGGER
CANVASES2 (GRAIN / WAVE)
SECTIONS8 + HERO + FOOTER
IMAGESZERO
FONTSANTON / PLEX MONO
BUILD STEPNONE
DECLASSIFIED
01Concept & Art DirectionWhy it looks like this

The premise

An unlicensed FM station that prints its own zine. Pirate radio and brutalism share one ethic: show the structure. Nothing is decorated — every border is a real 1px rule, every section is a labelled slab (“01 / MANIFESTO”), and the only image on the whole site is noise the page generates itself.

The discipline is the trick. Brutalism fails when it's sloppy; it lands when the grid is merciless and the type is set with care. So: one display face, one mono face, five colors, tabular numerals everywhere a frequency appears, and all “chaos” (scramble, glitch, static) is tightly quantized so it reads as signal interference, not bugs.

Palette — “Newsprint & Siren”

INK#0E0D0A
PAPER#E9E4D5
SIGNAL#FF3000
HAZE#A49C88
BONE#F4F0E4

Warm paper instead of white, warm near-black instead of #000 — the multiply-blended grain canvas ties them together like cheap offset printing. SIGNAL red is rationed: needle, stamps, one word per paragraph.

Typography

AntonDisplay — all caps, line-height .84–.9
IBM Plex Mono 400/500/700Everything else — 10–14px, tracked wide
091.7font-variant-numeric: tabular-nums

Scale contrast does the heavy lifting: hero lines at clamp(4.5rem, 17.5vw, 17rem) against 10px tracked mono labels. Alternating solid / outlined (-webkit-text-stroke) / red lines keeps a three-line stack from becoming a wall.

Motion rules

Every ease is power3/power4 — nothing linear except marquees and scrubbed values (which inherit their easing from your scroll finger). Idle motion (waveform, grain, blinking ON–AIR dot, marquees) keeps the page alive between interactions. Everything respects prefers-reduced-motion.

02Tech BreakdownReal code from the page

Stack

Static HTML + CSS + vanilla JS. Two pinned CDN scripts: GSAP 3.12.5 and ScrollTrigger. Two <canvas> elements (grain overlay, hero oscilloscope). Google Fonts. That's the whole stack.

The pinned tuner

The centerpiece: scrolling ~2600px sweeps the FM band 88.0→108.0. The section is pinned; a scrubbed tween drives one number, and everything — the giant readout, the tick-strip transform, station lock states — derives from it. One source of truth, zero drift.

index.html — tuner scrub
// scroll progress → frequency → everything else
var fobj = {f: 88};
gsap.to(fobj, {
  f: 108, ease: "none",
  onUpdate: function(){ setFreq(fobj.f); },
  scrollTrigger: {
    trigger: "#tuner-wrap",
    start: "top top",
    end: "+=2600",
    pin: true, scrub: 0.4
  }
});

function setFreq(f){
  // tabular-nums readout: "091.7"
  track.style.transform =
    "translateX(" + (-(f-88)*90) + "px)";
  stationData.forEach(function(st){
    st.el.classList.toggle("live",
      Math.abs(st.f - f) < 0.45);
  });
}

The tick strip is ~1800px of absolutely-positioned 1px <i> elements generated once at load (a tick every 0.2 MHz, taller every 0.5 and 1.0). Moving it is a single transform — cheap, 60fps.

Velocity skew

Hero lines shear with scroll speed — the classic “paper being dragged” feel, done with quickSetter so no tweens are allocated per scroll event.

index.html — kinetic type
var setters = gsap.utils.toArray(".hline")
  .map(el => gsap.quickSetter(el, "skewX", "deg"));
var clamp = gsap.utils.clamp(-8, 8);

ScrollTrigger.create({
  onUpdate: self => {
    var s = clamp(self.getVelocity() / -260);
    setters.forEach(set => set(s));
  }
});

Offset-print grain

No noise texture is downloaded. A 128×128 canvas is refilled with random luminance ~11 times per second, tiled across a half-resolution fixed canvas, and blended with mix-blend-mode: multiply at 7% opacity. It reads as cheap paper, and it costs almost nothing.

index.html — grain
function makeNoise(){
  var img = tctx.createImageData(128, 128);
  for (var i=0; i<img.data.length; i+=4){
    var v = (Math.random()*255)|0;
    img.data[i]=img.data[i+1]=img.data[i+2]=v;
    img.data[i+3]=255;
  }
  tctx.putImageData(img, 0, 0);
}
// throttled to ~11fps — film flicker, not strobe
if (t - last > 90){ makeNoise(); tile(); }

Text scramble (no plugin)

GSAP's ScrambleText is a paid Club plugin — this site ships a 15-line free one. Characters resolve left to right; the charset is themed (▓▒░#%/ + the station's call digits).

index.html — scramble
var CHARS = "▓▒░#%/\\|<>+=*XZKQ0917";
function scramble(el){
  var t = el.dataset.txt, frame = 0;
  var iv = setInterval(function(){
    frame++;
    el.textContent = t.split("").map((ch,i) =>
      frame > (i/t.length)*11 || ch === " "
        ? ch
        : CHARS[(Math.random()*CHARS.length)|0]
    ).join("");
    if (frame >= 14){ clearInterval(iv);
      el.textContent = t; }
  }, 30);
}

The mixing desk

Section 06 is a six-channel console built from borders and rectangles. Each channel is 12 VU segments (the top three turn SIGNAL red), a draggable fader, and a mute. One gsap.ticker callback, throttled to ~18fps, wobbles every channel around its fader level with a per-channel sine phase — so the desk looks like it's passing programme audio, not white noise.

index.html — VU ticker + fader
// pseudo-audio: level × slow sine × jitter
var wob = 0.62
  + 0.28 * Math.abs(Math.sin(t*c.speed + c.phase))
  + (Math.random()*0.14 - 0.07);
paint(c, c.lvl * wob);  // lights round(x*12) segs

// faders are real pointer-event drags
c.fader.addEventListener("pointerdown", function(e){
  var r = c.fader.getBoundingClientRect();
  c.lvl = 1 - (e.clientY - r.top) / r.height;
  knob.style.top = ((1 - c.lvl) * 100) + "%";
});

Muting all six flips the status line to OUTPUT: DEAD AIR (COWARD). Every good console judges its operator.

Boot sequence

An ink-black overlay plays a 2.3s receiver boot: the frequency counter eases 088.0→091.7 with power3.inOut, checklist lines print, then the overlay slides up with power4.inOut while the hero lines rise from below with a 0.09s stagger. It doubles as a font-loading mask — no flash of unstyled text, ever.

Everything else

Marquees: CSS translateX(-50%) keyframes on duplicated content — GSAP is saved for what needs easing. Manifesto: text is split into word-spans at runtime, then a scrubbed stagger lifts opacity .14→1. Archive cards: red wipe panels on cubic-bezier(.7,0,.2,1); clicking one “plays the tape” — the card inverts to ink and a 4-bar CSS equalizer starts (one card at a time, keyboard-operable). Back-page small ads: a 3×2 classifieds grid with ink-invert hovers and rubber stamps. Scrollspy: one ScrollTrigger per section toggles the red underline in the nav. Waveform: three sine-product lines with per-vertex jitter on gsap.ticker. DPR capped at 2. No AI-generated assets — every pixel is procedural.

03Replicate ItTen steps, no build tools
  1. Pick a hostile-but-warm palette: near-black ink, aged paper, one siren accent. Put them in CSS custom properties and never type a hex twice.
  2. Load one condensed display face (Anton) and one mono (IBM Plex Mono). Ban every other font.
  3. Build the chrome first: a fixed 44px top bar, 1px-ruled section slabs, each with a numbered header row. The grid IS the design.
  4. Set the hero as 3 stacked clamp() lines — solid / outlined / accent. Add side annotations in 11px tracked mono.
  5. Add idle life: CSS marquees, a blinking ON–AIR dot, a UTC clock, a jittered sine waveform canvas.
  1. Wire GSAP + ScrollTrigger from CDN. Scrub small xPercent drifts on the hero lines; add the velocity-skew snippet.
  2. Build one pinned centerpiece where scroll drives a single number and the whole section derives from it (our FM tuner).
  3. Give every interactive row a hover state that inverts to ink and fires a themed text-scramble.
  4. Add one hardware toy per issue — ours is a six-channel mixing desk: VU segments on a throttled ticker, pointer-event faders, mute buttons with consequences.
  5. Cover the load with a boot overlay that ends by sliding away and staggering the hero in. First impression = choreography.
  6. Gate all motion behind prefers-reduced-motion, cap devicePixelRatio at 2, and test at 375px — brutalism is not an excuse for broken mobile.

AI assets used: none. Everything on both pages is procedural code — type, rules, noise, waveforms.