Context
The hub everything else points to
Rádio Alvorada TV needed a credible public presence: not a landing page, but a daily-use media home for Brazilian gospel audiences. Radio runs continuously; TV and on-demand video are separate deploys in the same monorepo — this case study is the website layer only.
The design problem was cognitive distance: listeners think “the radio is on,” while the system juggles Shoutcast streams, metadata polling, timezone math, YouTube embed rules, and install prompts — all without feeling like a utility app duct-taped to a blog.
My role and constraints
Designing a media platform around one persistent truth: the radio is live
I owned the product architecture, information architecture, interaction design, frontend implementation, deployment model, and the operational surfaces that keep the site trustworthy after launch. This was not a static brand site - it is a broadcast product where the homepage, program schedule, content library, PWA install prompt, community actions, and bottom radio player all have to agree about what the station is doing now.
The main constraint was that the public website should feel editorial and calm while still behaving like a live media app. A typical approach would make the player dominant and push content into secondary pages. I took the opposite route: the site remains readable, but a single global audio state follows the user quietly through every page.
Audience constraint
Visitors arrive for live radio, programs, devotionals, prayer, music, and videos. The design cannot assume a single task; it must help people keep listening while they decide what to do next.
Technical constraint
The audio stream comes from an external Shoutcast provider. The website can control playback, metadata, fallbacks, and UI state — but not the upstream encoder or CDN behavior.
Operational constraint
The site needed to be deployable as a standalone Vercel project inside a monorepo, with absolute media URLs so DNS migration would not break the stream.
SEO constraint
A gospel radio site has to be discoverable by program names, devotional content, music, podcasts, and local broadcast intent — not only by the station brand.
Architecture
How the pieces connect
FIG 1 — The website never hosts audio bytes. It orchestrates a single player, two stream paths, and shared metadata.
When direct Shoutcast fails cross-origin, the same-origin proxy preserves Icy-MetaData for track titles.
Browser (Next.js on Vercel) ├── Global HTMLAudioElement (PlayerProvider) │ ├── Direct: stm1.ninjascast.com:7026/stream │ └── Fallback: /api/radio/stream (same-origin proxy) ├── /api/radio/now-playing → Shoutcast currentsong (edge cache 8s) ├── Schedule engine → America/Sao_Paulo slot matching ├── Static content registry (CMS-ready TypeScript) └── PWA manifest + pass-through service worker Separate monorepo apps (not this deploy): ├── video.radioalvoradatv.com.br → custom OBS/HLS viewer └── audio.radioalvoradatv.com.br → self-hosted Icecast tool
Technical deep dive
Persistent radio player
One audio element, two modes
PlayerProvider owns a single HTMLAudioElement for live radio and seekable podcast episodes. Switching modes updates src, metadata, and UI chrome — the bar at the bottom never unmounts, so playback survives navigation.
Under the hood: live streams use preload="none" and Shoutcast's icy metadata headers; podcasts use YouTube-sourced episodes with duration and seek support.
Proxy fallback chain
First attempt: direct connection to NEXT_PUBLIC_RADIO_STREAM_URL. On network error, flip useProxy=true and retry via /api/radio/stream.
Why: some mobile browsers treat cross-origin live audio differently. The proxy forwards Range requests and Icy-MetaData headers so track titles still update.
Resilience without drama
Exponential reconnect (up to 6 attempts, 1.5s → 12s cap) on MEDIA_ERR_NETWORK. Twenty-second stall detection. Resume on visibilitychange when the tab returns. Screen reader announcements via a live region so state changes aren't silent.
FIG 2 — Proxy fallback is not the happy path. It is the resilience path when mobile browsers block cross-origin live audio.
Exponential reconnect and visibility resume sit on top of this chain so brief drops do not end sessions.
Schedule engine
São Paulo airtime, not server UTC
Programs like Ainda é Tempo and Graça Abundante recur weekly. The engine uses Intl.DateTimeFormat with America/Sao_Paulo — not the developer's laptop timezone — and walks up to seven days of slots to find what is live now and what is next.
In plain terms: when no scheduled slot matches, the UI falls back to “continuous broadcast” copy — because the Shoutcast stream never stops, even between named programs. That honesty prevents the false “offline” state that kills trust on 24/7 radio.
Information architecture
A content hub that behaves like a broadcast product
The website organizes devotional content, podcasts, program pages, music, clips, and articles through a typed content registry. That matters because the future CMS migration should not require redesigning the mental model. Each item already has a slug, category, source channel, media type, and canonical detail page, so the interface can grow from static TypeScript data into a headless CMS without changing how audiences browse.
The live radio player is deliberately not buried inside a route. It is part of the site shell. In hiring terms, this is the product decision I would want to see from a senior designer-engineer: make the primary behavior persistent, then let all secondary content orbit around it.
PWA
Installable, but not annoying
The service worker is intentionally non-caching — it exists so Chrome meets installability criteria, not to offline the gospel catalog. Real install UX is gated: user must scroll or tap, wait 30 seconds, and hasn't dismissed the prompt in 14 days.
Design trade-off: aggressive install prompts convert short-term; they erode trust on a faith community site. Engagement gating aligns prompt timing with actual interest.
Content layer
CMS-ready without a CMS yet
Sermons, podcasts, articles, and media clips live in typed TypeScript registries with shared ContentItem shapes — slug collisions resolved at build time, YouTube IDs validated for embeddability. Lazy YouTube modals defer iframe weight until the user chooses to watch.
SEO: JSON-LD for Organization, WebSite, RadioStation; dynamic OG images; sitemap includes every content slug. Legacy paths (/musica/:slug, etc.) 301 to canonical /conteudo/:slug.
Outcome
What shipped — and why it matters
The result is a production gospel media platform on Vercel where the radio experience remains stable across navigation, the schedule uses Brazilian broadcast time rather than server assumptions, and the content model is ready for a CMS when the organization needs editorial scale.
- Production site on Vercel with DNS migrated from legacy host — stream URLs are absolute, migration-safe
- Radio follows users across every page without breaking autoplay policy
- Schedule + metadata visible in player chrome and homepage
- Typed content registry creates a safe path toward headless CMS integration
- Install prompt respects user engagement instead of interrupting first-time visitors
Hiring signal
What this case study demonstrates
This work demonstrates end-to-end ownership of a real production product - not just visual composition. It shows I can translate an audience and operating reality into information architecture, streaming behavior, player resilience, metadata/schedule systems, SEO foundations, PWA affordances, and a deployable Next.js application.
If you're hiring for a remote Design Engineer, Frontend Developer, Full Stack Developer, or Product Engineer role, this is the pattern I bring: reduce the number of mental models users must hold while keeping the technical model explicit enough to survive production.
