Skip to content

Repository files navigation

3pi.tv

The site for 3rd Party Influence — live minimal techno on Eurorack modular synths.

Static SvelteKit. Migrated off Notion + Super.so in September 2026.

Why it moved

Super rendered the Notion pages, but gave headings ids like id="block-293a7c7350e6802aab17da71dfcbba24" — the raw Notion block UUID. Unguessable, meaningless, and it changes if the block is recreated, so a section could never be linked to. Here the same heading is #aaa11-martin-gordon, generated by rehype-slug, and every heading carries a visible-on-hover anchor link.

Other things that came with the move: custom <head> and per-page OG tags, a real 404 page, gigs held as data instead of retyped in two places, and builds that fail loudly on a broken link or a mistyped image id.

Nothing runs on a server

adapter-static prerenders every route to a plain HTML file. Node is used at build time only. The build/ directory is the whole site — deploy it to any static host.

Commands

npm install
npm run dev       # regenerates images, then starts the dev server
npm run build     # -> build/   (runs `npm run images` first via prebuild)
npm run preview   # serve build/ locally
npm run images    # regenerate static/images/ from media/

Layout

src/
  content/           the pages, as markdown - this is what you edit
    index.md                                 -> /
    module-co2e.md                           -> /module-co2e
    artists-activists-and-audiences/
      aaa-upcoming.md                        -> /artists-activists-and-audiences/aaa-upcoming
  lib/
    components/      Photo, Embed, Columns, NextGig, GigList, Nav, Footer, Seo
    data/
      gigs.json      every gig, past and upcoming - single source of truth
      nav.json       the main nav, in order
      links.json     social URLs
      photos.json    per-image alt, caption and photographer credit
      photo-sizes.json  GENERATED by npm run images
  routes/
    +page.svelte     renders content/index.md
    [...path]/       renders everything else, including nested pages
media/
  web/               1920px sources scraped from the old site (committed)
  originals/         drop high-res here later; wins automatically. See its README.
scripts/
  build-images.mjs   generates responsive avif/webp/jpg variants
  migration/         one-shot scripts that pulled the content out of Super
static/images/       GENERATED - gitignored

Editing

Text

Edit the markdown in src/content/. Headings become anchors automatically, so ## Some Section is linkable at #some-section. Renaming a heading changes its anchor — if a link to it exists somewhere, update that too.

Adding a gig

One entry in src/lib/data/gigs.json:

{
  "date": "2026-11-14",
  "parts": [
    { "text": "BMS61", "url": "https://berlinmodularsociety.com/" },
    { "text": "Klunkerkranich", "url": "https://klunkerkranich.org/" },
    { "text": "Berlin" }
  ],
  "note": "Closing set",
  "sets": [
    { "time": "22:00", "what": "3rd Party Influence", "where": "Rooftop" }
  ]
}

parts is the title, joined with commas. Any part can carry a url, so links live in the title itself - "BMS61" points at Berlin Modular Society, the venue at the venue. There is no separate row of links.

note is one short line under the date. sets renders as set times: each takes time and what, plus optional who (who is playing, if it is not the band), where (which room) and url (a link on the act, e.g. ambienttaichi.com).

Only date and parts are required. Anything dated today or later shows as upcoming; the rest fall into the archive. The homepage "Next gig" section and /gig-archive both read this file, so they cannot disagree.

Dates are evaluated at build time, then re-checked in the browser after hydration - so a gig will not sit there stale between deploys. If you want the served HTML itself always current, schedule a periodic rebuild on your host.

Adding a photo

Filenames carry the rights holder as a prefix: <rights-holder>_<description>.<ext>. Use 3pi_ for the band's own photos, a slugified name for anyone else's, or unknown_ if you genuinely do not know. See COPYRIGHT.md.

  1. Put the file in media/web/ (or media/originals/ if it's high-res), named with the id you want, e.g. 3pi_bms61-closing-set.jpg.
  2. Add an entry to src/lib/data/photos.json, keyed by the id:
    "3pi_bms61-closing-set": {
      "id": "3pi_bms61-closing-set",
      "file": "3pi_bms61-closing-set.jpg",
      "alt": "3PI playing the closing set at BMS61",
      "caption": "Closing set at BMS61, Klunkerkranich",
      "captionHtml": "Closing set at BMS61, Klunkerkranich",
      "credit": null,
      "creditUrl": null
    }
    Set credit whenever someone else took it — it renders under the photo.
  3. Reference it: <Photo id="3pi_bms61-closing-set" />
  4. npm run images

A <Photo> with an unknown id fails the build rather than shipping a broken image.

Embeds

<Embed src="https://www.youtube.com/embed/..." /> — YouTube, SoundCloud, Bandcamp, Mixcloud and Google Calendar/Forms all work. Audio players get a fixed height, video gets 16:9.

Deploying

Build command npm run build, publish directory build.

Keep the existing URLs. Every slug from the Super site is preserved, including the nested /artists-activists-and-audiences/aaa-upcoming.

GitHub Pages (configured)

.github/workflows/deploy.yml builds on every push to main and publishes build/ as a Pages artifact. Do not use a docs/ folder — that is the older deploy-from-branch method and would mean committing 57 MB of generated images on every content change. build/ and static/images/ stay gitignored.

One-time setup: Settings → Pages → Source → GitHub Actions. Then push to main.

Three files exist purely for Pages, and the site breaks without them:

File Why
static/.nojekyll Without it Jekyll strips _app/ — the leading underscore — and every stylesheet and script 404s
static/CNAME The custom domain. Delete this file if you are not using one
build/404.html Generated by adapter: adapter({ fallback: '404.html' }). Pages serves it for unknown paths, and it boots the app so +error.svelte renders

The workflow caches static/images/, keyed on the contents of media/. Without that, every deploy re-encodes 531 image variants with sharp, which dominates the build time. Change a photo and only that photo is re-encoded.

workflow_dispatch is enabled so you can rebuild from the Actions tab without pushing — useful because gig dates are evaluated at build time.

Pointing 3pi.tv at it

The CNAME file only tells GitHub which domain to answer for. You still have to move DNS off Vercel. For an apex domain, replace the existing records with GitHub's Pages A/AAAA records (confirm the current values in GitHub's "Managing a custom domain" docs before changing anything), then tick Enforce HTTPS in Settings → Pages once the certificate is issued. That is the actual cutover from the Super site — do it last, once you have checked the Pages URL.

If you deploy to a project path instead

Serving from username.github.io/3rdPartyInfluence rather than a custom domain needs kit.paths.base set, and every absolute path in the project prefixed with it — the nav, footer, breadcrumb, favicon, the /images/... srcsets in Photo.svelte, and the two internal links inside the markdown. It is not just a config line. A custom domain, or a <username>.github.io repo, avoids the whole problem.

Alternatives

Cloudflare Pages and Netlify take the same npm run build / build pair. They give you control over cache headers and, on Cloudflare, on-the-fly image resizing. Neither is necessary — the responsive variants are pre-generated — but for a site that is mostly photographs they are a better long-term home than Pages.

Images

npm run images reads each source once and writes AVIF, WebP and a JPEG/PNG fallback at 640 / 1280 / 1920px, plus the intrinsic dimensions into photo-sizes.json so every <img> ships width/height and never causes layout shift.

Generated files are gitignored. Only the sources in media/ are committed.

Licence

Split, because this repo holds both software and a band's content:

  • Code — AGPLv3. See LICENSE.
  • Content — text, gig data, and all photographs — all rights reserved.

17 of the photographs were taken by four other photographers and are not ours to relicense. Full detail and per-photographer credits in COPYRIGHT.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages