Web

Elohai

Web

Elohai

Client: Personal / Internal

Astro React Web Audio API Three.js Motion Tailwind CSS Cloudflare Workers TypeScript
Elohai

Challenge

Build a landing page that matches the reverence of the name with the feel of the music itself — devotional and driving at the same time, without either one flattening the other

Solution

Generated the entire look in code rather than shipping artwork — a hero light built from gradients and geometry, wired to an audio analyser so it moves to whatever track the visitor is playing

Result

A live site at elohai-music.com with a real audio-reactive hero and a custom player for five of the project's twelve streaming tracks — no image assets behind any of it, around 90 kB of JavaScript on the critical path, running on Cloudflare's edge with no server code per request

The Challenge

Elohai is Hebrew for my God — a line from Psalm 31, and not a description of God but something said to Him, by a psalmist who is surrounded and worn down and makes the declaration anyway. That is the posture the whole project sits on.

The music is the same idea at volume. Alt-rock worship, mostly 120–128 BPM: palm-muted verses that hold back and break into distorted, anthemic choruses, live drums, layered gang vocals. It is not music for quiet reflection. It's for the drive to work.

So the page had two things to hold at once, and they pull against each other. Reverence wants stillness, restraint, and space. The music wants drive. A site that only does the first reads as a hymn sheet; one that only does the second reads as a band's press kit, and the name stops meaning anything.

There was a practical problem underneath the aesthetic one. The look this project calls for — deep charcoal ground, radiating gold light, Hebrew script, a crown — is the look of a finished piece of album artwork. The obvious way to build that hero is to export the artwork and set it as a background image: a heavy download, fixed forever at whatever the file says, and completely inert. For a site whose entire subject is music, a backdrop that cannot respond to the music is a missed opportunity, not just a heavy one.

Our Solution

A hero light with no image assets

The hero is generated entirely in code: a four-layer radial bloom, volumetric rays built from a repeating conic gradient, twelve-point sacred-geometry rings, a horizon haze, a vignette, and a film grain over the whole thing. There is no photograph and no exported artwork behind any of it.

That buys three things at once. Nothing to download. Colours that follow the theme rather than being baked into a file. And — the reason it was worth doing — a backdrop that can be driven by live values instead of sitting still.

Two details are load-bearing:

  • The rays are masked radially, fading out well before the frame edge. Without that mask, a conic gradient reads as a pinwheel rather than as light.
  • The geometry coordinates are rounded to two decimals. Full-precision floats serialize differently in Node and in the browser — 270.0961894323342 against ...26 — which React reports as a hydration mismatch on every single line.

The soundbars are real

The audio visualisation runs against an AnalyserNode reading the actual file. It is not a sine wave dressed up to look like one. When a track is playing, the light in the hero is moving to that track — the bloom, the rays, and the embers all take their signal from the same analysis.

Getting that to work across a statically-rendered page took some care. The hero light and the player are two separate islands, and islands cannot share React state — so the audio engine is a module singleton living entirely outside React, which both of them subscribe to. That also sidesteps the sharpest edge in the Web Audio API: a MediaElementAudioSourceNode can only ever be created once per audio element, so the element is created imperatively inside the engine where no remount can strand it.

The beat response is a hand-rolled onset envelope over an adaptive baseline, with the analyser's smoothing deliberately set to 0.35 rather than the API default of around 0.8. Measured against a real track, the default averaged the transients away so thoroughly that the beat signal moved through a range of 0.03 — making the visuals less alive during playback than they were sitting idle.

Reverence in the restraint

The tension between devotional and driving gets resolved by holding the palette and the type still while letting the light do the moving. One accent — gold — on a near-black ground, and nothing else competing with it.

The type carries the rest: Cinzel in large-tracked caps for display, Crimson Pro for body, and Frank Ruhl Libre — a Hebrew serif, so it pairs properly and sets niqqud cleanly — for the Hebrew. The tracking is the defining move, and it carries a matching text-indent to cancel the trailing space that letter-spacing adds after the last glyph; without it, centred display type sits visibly off-centre.

Hebrew never appears as decoration. Every instance is shown with its transliteration and its meaning, so it teaches the reader something instead of just looking the part.

The music, one screen down

The page is four sections — Hero → Music → About → Listen — with a fixed nav that reaches any of them in one click. The player sits directly beneath the hero, and the streaming links are one click away from the moment the page loads. Nobody has to scroll through the story to reach a song.

The story is still there, in an About panel of three tabs, and it stays tied to the music rather than sitting beside it: the panel carries a lyric pull-quote that follows whichever track is currently selected, so changing songs changes the words on the page.

Built to be usable, not just watched

  • The hero wordmark, copy, and buttons are static HTML — the page is complete and readable before any JavaScript runs. Only the light is an island.
  • prefers-reduced-motion is honoured throughout: rays and rings stop rotating, embers stop drifting, the light stops pulsing, tab transitions go instant, and smooth scrolling is disabled.
  • The About panel implements the ARIA tabs pattern properly — roving arrow-key navigation, aria-selected, panels wired to their tabs.
  • The player's scrubber is a real role="slider" with arrow-key seeking, not a styled div.
  • Nothing autoplays. Browsers block it without a gesture, and a worship track starting unbidden on a stranger's laptop is a bad first impression regardless.

Performance kept honest

The eager islands total around 90 kB gzipped. Three.js — which drives the drifting embers — is lazily imported so its ~219 kB chunk arrives after first paint rather than blocking it. Audio is set to preload="metadata", so a track's several megabytes only download when someone presses play.

The whole site deploys as an assets-only Cloudflare Worker: no main entry, no server code running per request, files served straight from the edge. Clean URLs are handled at the asset layer, and unknown paths return a real 404 rather than being rewritten to the homepage — this is a multi-section single page, not a client-routed SPA, and serving the homepage for a typo'd URL would wrongly return 200 and confuse crawlers.

The Result

elohai-music.com is live. Five tracks stream in a custom player on the page itself, with the full twelve-song catalogue one click away on Spotify, Apple Music, YouTube, Amazon Music, and Tidal.

The whole design system — the light, the embers, the crown, the rings — ships as code rather than as artwork. That is why a page that looks like this still reaches a readable first paint before its JavaScript arrives, why the hero can move to the music instead of sitting behind it, and why it degrades to something calm and legible for a visitor who has asked their machine to stop moving things.

Technologies Used

  • Astro with selectively hydrated React islands — the hero light eager, the player and About panel deferred until visible
  • Web Audio API with an AnalyserNode and a custom onset-detection envelope, driven by a module-singleton engine shared across islands
  • Three.js for the ember field, lazily imported so it stays off the critical path
  • Motion for the light's animation values and tab transitions
  • Tailwind CSS v4 over a hand-built theme layer
  • TypeScript end to end
  • Cloudflare Workers static asset hosting on an apex custom domain, with sitemap, canonical URLs, and Open Graph and structured data generated at build

Like What You See?

Let's talk about how we can help with your project.