Overview
Coast Internet Radio is a small station in Newry run by Jim Parr. It plays country and Irish country to real listeners every day. I worked with Jim to replace the older website with a more modern, mobile-friendly service while preserving the station's identity and giving him practical control over day-to-day content.
How we collaborated
Jim supplied the stakeholder view: what listeners ask for, which information changes, and what he needs to manage without calling a developer. I translated those needs into designs and technical options, explained constraints, released changes, and responded to feedback from the live service. I am the only developer in the repository, so this is stakeholder collaboration rather than shared-code collaboration.
The problem nobody could see
The obvious job was a site that worked properly on a phone. The real problem was underneath it. The station's audio stream and its now-playing feed are both served over plain HTTP, and the website is HTTPS. Browsers block that as mixed content, so the player simply does not play. No amount of layout work fixes it.
So the first thing I built was not a page. Two Cloudflare Workers sit in front of the station's source and re-serve the stream and the metadata over HTTPS. The site only ever talks to the workers, never to the origin. That is the decision the whole project rests on.
The player
The player polls the metadata worker every ten seconds for the current track, what is coming up and what has recently played. It uses the Media Session API, so the lock screen controls on a phone work like a real radio app instead of a browser tab. If the feed goes quiet the site shows a calm placeholder rather than an error, because a listener does not care whose fault it is.
Giving Jim the keys
Jim needed to change the homepage himself without asking me, so I built him an admin area on Netlify Functions. The password is hashed with scrypt. The session is a signed HttpOnly cookie, every write carries a CSRF token, requests are checked for same origin, and six failed logins locks the address out for fifteen minutes. Content, playlists and feedback live in Netlify Blobs, and a scheduled function records the play history every minute.
Analytics I wrote instead of installed
Jim wanted to know what listeners actually use. I did not want to hand a small station's audience to a third party to find out, so the analytics are first party. Event names and their fields are allowlisted, so anything unexpected is dropped rather than stored. Addresses are hashed and the raw IP is never written down. There is a consent banner, and choosing not to be counted is a real option rather than a dark pattern.
What went wrong
Checking the live site in a browser console, I found the page was quietly breaking its own security policy. The site allows its inline scripts by hash. I had edited the script that applies a saved theme before the first paint, which changed its hash, and the header had never been updated to match, so the browser was blocking it. Every returning listener using dark mode, larger text or high contrast was getting a flash of the wrong theme.
Adding the right hash was a one-line fix. That was not the interesting part. I wrote a check that recomputes the hash of every inline script and compares it to the header, and put it in the build, so the next time I edit one of those scripts the build fails instead of the listener finding out.
What I would do next
There are no end-to-end tests for the admin area yet, so I still click through it by hand before a deploy, which is exactly the kind of thing that gets skipped when you are tired. The analytics also aggregate on read, which is fine at this size and will stop being fine if the station grows.