Colophon

A colophon documents how something was made. This one covers the decisions, constraints, and thinking behind this site.

What This Is

  • A fictional band site for Terror Ride, West Seattle noise metal, est. 2009
  • Built as a portfolio artifact to demonstrate product thinking, modern CSS/JS, and full-stack serverless on a zero-budget constraint
  • Hard constraint: no CSS or JS frameworks. Every byte is intentional
  • Deployed on Vercel free tier. Auto-deploys from GitHub on merge to master

Tech Stack

  • WebGL shader: domain-warped Voronoi lava cracks. Custom fragment shader, 60fps, half-res on mobile. This is the site's visual identity.
  • Vercel serverless: Node.js API functions at /api/*. CommonJS, no build step. Covers polls, complaints, song requests, subscriptions, email campaigns, visitor counts, QR scan tracking, NFC magnet logging, and OG image generation.
  • Upstash Redis: serverless-compatible key-value store via Vercel integration. Stores poll vote counts, email subscriber set, song request list, scan and magnet counters, and campaign send history.
  • Resend: transactional and campaign email. Sends Narc Certifications on complaint submit, confirmations on song requests, and subscriber blast campaigns. onboarding@resend.dev delivers to verified account only. Custom domain required for real delivery to subscribers.
  • Vercel Analytics: pageview tracking via /_vercel/insights/script.js. Script on all pages. Enabled in Vercel dashboard. Free tier: 2,500 events/month.
  • Leaflet.js: interactive map on the Banned page. CartoDB dark tiles, no API key required.
  • HFucktura: self-hosted typeface via @font-face. Heavy and Thin weights. The band's whole typographic identity in two files.
  • CSS custom properties: one token (--dark-card) controls every glass panel. Change one value, update the whole site.
  • Vanilla JS: no framework. Everything lives in one script.js, organized by feature function.
  • WebMCP: W3C draft standard (Feb 2026). Registers 7 structured tools via navigator.modelContext.registerTool() so AI agents can vote on polls, request songs, file complaints, and query merch without scraping the DOM. Polyfill via @mcp-b/global covers all browsers. Chrome 146+ has native support.
  • llms.txt: plain-text site manifest at /llms.txt. Written in band voice as an in-universe archive document. Explains Greg to the machines. Discoverable via robots.txt LLMs-txt directive.

Key Decisions

  • Shader over photo or video: a photo is static, a video is 10-30MB. A WebGL shader is 2KB, runs at 60fps, and creates something no stock library has.
  • CSS cross-document view transitions: @view-transition { navigation: auto } makes a multi-page site feel like a single-page app. Zero JS. Locks the nav with view-transition-name: top-nav so it never flickers.
  • Mobile menu + view transitions: view transitions capture a bitmap of the old page before navigation fires. If the menu is open, the old-page screenshot includes it. Fix: instant-close the menu (disable CSS transition, remove .open) on nav link click so the screenshot is menu-free. visibility: hidden on the closed state removes it from the render tree entirely, preventing subpixel bleed at the bottom viewport edge.
  • Transparent nav via scroll-driven animation: animation-timeline: scroll(root) fades the nav from transparent to black over the first 80px of scroll. Pure CSS with a JS fallback for Chrome iOS, which reports support but doesn't execute it.
  • OOUX object model first: defined Band, Member, Song, Show, Article, Complaint, Poll, and Merch Item as discrete objects before writing a line of HTML. Every page maps cleanly to objects and actions.
  • Glass recipe as a single token: backdrop-filter: blur(12px) + rgba(0,0,0,0.78) + border: 1px solid #222 + border-radius: 4px. Applied uniformly so white text passes WCAG AA against the lava background on every card.
  • Email opt-in as checkbox, not a separate form: the user already entered their email in the complaint form. A checkbox reuses it with zero extra friction. Fires a silent POST to /api/subscribe on submit if checked.
  • html vs body background trap: CSS propagates body's background to the viewport only when html has no background. Adding background-color to html to fix a white navigation flash caused body's background to paint as a stacked element, directly over the z-index: -1 WebGL canvas, hiding it. Fix: set background on html only, remove it from body.
  • Deliberate misspellings, no em-dashes: band copy uses 1-2 subtle human-looking typos per update (transposed letters, wrong homophone) and avoids em-dashes entirely. Makes AI-assisted copy read as typed, not generated.

Features

  • Geolocation banner: detects the West Seattle bounding box. Locals see "You already know." Everyone else sees "You're not even from here." Result cached in localStorage so the permission prompt fires once.
  • Visitor counter: glass widget on the homepage. Logs each visit as homie or outsider via geolocation and /api/visits. Displays the split with a bar and deadpan caption. The band monitors this ratio.
  • Sticky page label: IntersectionObserver watches the page <h1> against a -64px root margin. When the title scrolls behind the nav, a label slides in below it showing the current page. Disappears when the H1 returns to view.
  • Logo blood bleed: on tap or click, a dark red circle expands from the exact touch point via clip-path animation, then fades. Three-part fix required to suppress iOS long-press context menu.
  • Cursor fire trail: canvas overlay, pointer-events: none. Ember particles spawn on mousemove, burst on mousedown. Skips touch-only devices via compound hover check.
  • Pop-up poll: appears once per session after 12 seconds. Three questions, weekly rotation. Votes hit a serverless function and persist in Redis. Results render as animated bars.
  • Homepage poll results: "The People Have Spoken" section. Featured poll rotates weekly via Date.now() modulo. Hides silently if the API is down.
  • News carousel: CSS scroll-snap with IntersectionObserver-driven active states. Active card glows green and scales up. Full ARIA carousel pattern. Responsive: 1 card mobile, 2 tablet, 3-column grid desktop.
  • Noise complaint generator: fake Seattle.gov iframe with a working form. Feels government-issued. Submissions stored in Redis, trigger a Narc Certification email via Resend.
  • Email subscriber list and campaigns: opt-in checkbox on the complaint form. /api/subscribe stores emails to a Redis set (SADD auto-deduplicates). Admin dashboard composes and sends blast campaigns via Resend. Unsubscribe link in every email.
  • Song request and admin: form on the music page submits to a Redis list via /api/songs. Admin dashboard at /admin.html shows all requests and allows fulfillment with one click, which sends a confirmation email via Resend.
  • Banned map: Leaflet.js map of venues Terror Ride is no longer welcome at. Each pin is a documented incident.
  • Dynamic OG images: /api/og edge function generates social preview images at request time. Title and tag passed as URL params. HFucktura rendered via Satori. Font fetched from /fonts/HFucktura-Heavy.otf with graceful fallback.
  • NFC magnet campaign: /magnet.html is dual-mode. With ?id= param it's a geo-aware landing page that logs the scan and knows if you're in West Seattle. Without param it's an admin dashboard with per-magnet stats, total counts, and a 30-second auto-refresh activity feed.
  • QR guerilla marketing: physical QR codes on coozies, stickers, and posters redirect through /api/scan, which tracks source and total scans in Redis before redirecting. /print.html generates Avery-ready QR sheets for 6 West Seattle drop locations.
  • WebMCP tools: 7 registered tools exposed to AI agents: get_band_info, get_merch, vote_poll, get_poll_results, request_song, file_noise_complaint, get_scan_stats. Declarative attributes on the song request form let agents infer its schema from HTML alone.

Object Model

Designed using OOUX. Object-Oriented UX. Objects and their actions defined before any wireframing.

  • Band: name, bio, members, location, est. date, share
  • Member: name, role, photo, view
  • Song: title, album, stream links, play, request
  • Show: date, venue, location, get directions
  • Article: title, date, tag, body, read, share
  • Complaint: type, address, message, submit
  • Poll: question, answers, counts, vote, view results
  • Merch Item: name, price, availability, buy
  • Subscriber: email, subscribe, unsubscribe
  • Campaign: subject, body, sent count, compose, send
  • Scan: source, count, track
  • Magnet: id, geo result, count, track, dashboard