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.devdelivers 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/globalcovers 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 viarobots.txtLLMs-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 withview-transition-name: top-navso 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: hiddenon 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/subscribeon submit if checked. - html vs body background trap: CSS propagates
body's background to the viewport only whenhtmlhas no background. Addingbackground-colortohtmlto fix a white navigation flash causedbody's background to paint as a stacked element, directly over thez-index: -1WebGL canvas, hiding it. Fix: set background onhtmlonly, remove it frombody. - 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
localStorageso 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:
IntersectionObserverwatches the page<h1>against a-64pxroot 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-pathanimation, then fades. Three-part fix required to suppress iOS long-press context menu. - Cursor fire trail: canvas overlay,
pointer-events: none. Ember particles spawn onmousemove, burst onmousedown. 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/subscribestores 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.htmlshows 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/ogedge 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.otfwith graceful fallback. - NFC magnet campaign:
/magnet.htmlis 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.htmlgenerates 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