All case studies

Case study — Local-first astronomy in the browser

AWQAT

أوقات — “times.” A retro-pixel prayer clock that computes the day’s five prayers from solar geometry, then draws the sky it just calculated.

Most prayer apps are thin clients for somebody else’s API — they phone home for a timetable, wrap it in ads, and go blank on airplane mode. But prayer times aren’t a database. They’re astronomy — a closed-form calculation that’s been solvable with pencil and paper for centuries.

AWQAT does the whole thing on-device, in vanilla JavaScript with zero runtime dependencies, fully offline. And then it goes one step further: the clock face is a living pixel scene, driven by the same solar engine that set the times.

AWQAT Today view — pixel mosque under an afternoon sky, countdown to Maghrib, and five prayer cards with times and astronomical definitions
The Today view, live.The sun sits where it actually is for 5:12 PM; the timeline at the mosque’s feet marks each prayer; every card shows the astronomical definition behind its time. Asr is current — Maghrib is 3:18:59 away.

The real problem

Prayer times are astronomy. Astronomy runs fine on a phone.

Fajr begins when the sun is a fixed angle below the horizon. Dhuhr is solar noon. Asr is a shadow-length ratio. Maghrib is sunset; Isha, deeper twilight. Given a date, a latitude, and a longitude, all five fall out of the solar declination and the equation of time — the same two quantities printed on the back of every almanac.

The existing options ignore that. The API apps need connectivity, show ads, and ship your location upstream to display numbers they can’t explain. The printed masjid timetable is beautifully reliable — for one city, one calculation method, one year. Travel, or follow a different school on Asr, and you’re back to searching.

When the computation is centuries old and fits in your pocket, the respectful move is to do it on the device — offline, private, and explained.

The principle

One engine: sun position → angles → your clock.

The core is a few dozen lines of celestial mechanics. From the Julian day it derives the sun’s declination and the equation of time; each prayer is then a root-finding question — at what hour does the sun reach this angle?— answered per method, per madhhab, entirely in UTC, and localized only at the moment it’s drawn.

The living sky · same numbersThe scene isn't an animation loop. The sun's position, the palette blend, the window light at dusk, and the moon's phase are all derived from the same computed times — the sky is the calculation, drawn.

The decisions that mattered

Four calls shaped everything else.

Zero runtime dependencies

The dependency list is the attack surface, so the list is empty.

The production bundle is one sectioned module of vanilla ES — 18 KB gzipped — driving a static HTML shell. Vite and jsdom exist only at build and test time. There is nothing to phone home, nothing to expire, nothing to audit but the source itself. For an app people build a daily practice around, boring longevity is a feature.

UTC until the very end

Every time is solved in UTC; localization is a display concern.

The solar math never touches local time. Each prayer is a root-finding question — at what hour does the sun reach this angle? — answered in UTC, per method, per madhhab, and rendered through Intl time zones only at the last moment. Seven calculation methods (ISNA, MWL, Egyptian, Umm al-Qura, Karachi, Tehran, Jafari) are just different pairs of twilight angles; the Hanafi Asr is a one-line change to a shadow factor.

The clock face is the sky

If the app computes the sun, the app should show the sun.

The same engine that sets the times drives a pixel scene above them. The sun's screen position comes from the actual fraction of daylight elapsed — not an animation loop. The palette blends continuously through dawn, day, dusk, and night; the mosque windows light at Maghrib; and the moon renders at its real synodic phase, waxing on the right, waning on the left, earthshine on the dark side.

Honesty lives in the interface

A prayer app carries real responsibility, so the disclaimers ship on every view — not in the fine print.

Every card carries its astronomical definition — "when an object's shadow equals its own length" — and the footer names the method, madhhab, coordinates, and time zone that produced the numbers. Congregational times are explicitly deferred to the local masjid. Tabular Hijri dates are labeled estimates that can differ from moon sighting. The app can always answer why.

Seeing it run

Four frames, one screen, hours apart.

These are real captures of the same view across a single day — not mockups, not alternate themes. The palette blends continuously between phases; the countdown and period label track the actual prayer windows; the timeline segment for the current prayer stays lit.

Fajr — deep orange dawn gradient behind the pixel mosque, waning moon still up, 4:25 AM
Fajr · 4:25 AM. The dawn band builds while last night’s moon hangs on; sunrise is 1:14:58 out.
Dhuhr — full daylight, pixel sun near the top of its computed arc, 1:25 PM
Dhuhr · 1:25 PM. Just past solar noon — the sun tops out exactly where the equation of time says it should.
Moments before Maghrib — orange-to-violet sunset, sun touching the horizon, countdown at 2 minutes 59 seconds
Maghrib − 00:02:59. The sun’s disc meets the horizon as the countdown runs out.
Night — starfield over the darkened mosque, gibbous moon overhead at its real phase, windows lit, 11:30 PM
Isha · 11:30 PM. Stars out, windows lit — and the moon drawn at its real phase for the date.

Tonight's moon

computing…

Not a screenshot. This is the app’s actual moon renderer running on this page — it just drew the moon as it is right now, the moment you scrolled here. The same code paints AWQAT’s night sky.

Learn mode

Tap any prayer card, and the clock becomes a teacher.

Every prayer opens into a step-by-step walkthrough — Maghrib unfolds into 29 steps across its three rak’ah. A pixel figure demonstrates each posture while the recitation appears one line at a time: Arabic, transliteration, meaning. Narration plays per line — real committed Qur’an recordings first, generated dhikr clips second, the device’s Arabic voice as a last resort — highlighting the active card and turning pages by itself. A single incrementing token guards every audio callback, so pause, skip, and close cancel cleanly with no orphaned sound.

Learn mode — pixel figure in sujud on a prayer rug, step 7 of 29, tasbih line shown in Arabic, transliteration, and English, marked recite ×3
Sujud, step 7 of 29. Posture on the left, one line per card on the right, RECITE ×3 tracking repetitions. The dusk in the pixel window matches the real sky — it’s Maghrib.
Learn mode — Surah Al-Fatiha step showing the Bismillah as a single card with Arabic, transliteration, and translation, page 1 of 7
One ayah per page. Al-Fatiha splits into seven cards paged in step with the audio; a manual flip never interrupts playback.

The calendar

Two calendars, one table — and a year you can see.

The Hijri calendar is arithmetic too: the tabular reckoning cycles 11 leap years through every 30, so Gregorian ↔ Hijri conversion is pure integer math — the same zero-dependency rule as the solar engine. AWQAT runs both side by side and charts the whole year’s daylight, so you can watch Fajr and Isha breathe with the seasons.

Month view — July 2026 with Hijri dates for Muharram and Safar 1448 and all five prayer times for every day
Month. The printed masjid timetable, generated on the spot for your coordinates and method — today in gold, Fridays marked.
Year view — dawn-to-dusk chart for 2026 showing seasonal drift of night, twilight, and daylight bands, plus important Islamic dates
Year. Night, twilight, and daylight bands drifting across 2026 — with Ramadan, both Eids, and Ashura each carrying a “tabular estimate” honesty label.

The evidence

Small enough to read. Tested like it matters.

The whole app is one sectioned module driving a static HTML shell. The seven test suites don’t mock it — they load the real module into jsdom and drive it end to end: full Learn-mode playback runs, pagination contracts, moon-phase math pinned to known new and full moons, sun-arc continuity across day boundaries. Any change to the DOM contract or the audio queue has to update the tests in the same commit.

0
runtime dependencies
18 KB
gzipped JavaScript
1
module drives it all
7
headless test suites
7
calculation methods
100%
offline after load

The boundaries

What it deliberately defers to people.

Congregational times. Jama’ah and Jumu’ah are set by the local masjid, and the app says so on every view — it computes when prayer becomes due, not when the community gathers. The tabular Hijri calendar can differ a day or two from moon sighting, so every date wears an estimate label and a ±2-day adjustment lives in settings.

And the narration is a pronunciation aid, not a substitute for instruction — the Learn overlay itself tells you that proper recitation (tajwid) is learned from a teacher. The software knows the edge of its competence and points past it.

The point

Local-first isn’t a constraint. It’s a design position.

AWQAT went from a single-file prototype to a tested, shipping app in four feature commits — small enough that one person can hold the whole thing in their head, and boring enough, in the best sense, to still work untouched in ten years. The pattern generalizes: wherever the underlying data is deterministic — tides, seasons, sky events, timetables of any celestial kind — the computation belongs on the device, and the interface should be able to answer why.