Building Sandglass: prayer times without a server

How Sandglass calculates prayer times on the device, keeps its timer honest in background tabs, and works properly in Arabic.

A wooden hourglass on a stone ledge in early morning light

Most focus timers assume your day is one long uninterrupted block. Here it isn't. If you work or study in the UAE, prayer breaks the day into natural sections, and a timer that ignores them is a timer you end up fighting.

So I am building Sandglass, a focus app made for the Gulf. These are the parts that were more interesting to build than I expected.

Calculating prayer times on the device

Prayer times depend on where you are and on the calculation method, so the obvious approach is to call an API. I didn't want to. An API means a network round trip, a failure mode, and a reason the app can't work offline.

Instead Sandglass uses adhan, a small library that does the astronomy in JavaScript. You give it coordinates, a date and a method, and it gives you times.

import { Coordinates, CalculationMethod, PrayerTimes } from 'adhan';

const dubai = new Coordinates(25.2048, 55.2708);
const params = CalculationMethod.Dubai();
const times = new PrayerTimes(dubai, new Date(), params);

times.nextPrayer(); // 'asr'

Each emirate has its own coordinates in the app, so someone in Fujairah gets Fujairah's times, not Dubai's. No location permission needed: you pick your city once.

A timer that survives background tabs

Browsers slow down timers in background tabs. If you count down by subtracting one every second with setInterval, a 25-minute session in a background tab can quietly take 30.

The fix is to never count. Sandglass stores when a session started and how long it should last, and works out the remaining time from the clock every time it draws:

const remaining = Math.max(0, session.endsAt - Date.now());

The interval only decides how often the screen updates, not how much time has passed. Close the laptop, come back, and the timer is right.

Arabic is a layout, not a translation

Translating the strings was the easy part. Arabic reads right to left, so the whole layout has to mirror: the timer controls, the task list, the progress bar, even which side a panel slides in from.

Setting dir="rtl" on the page does most of the work, as long as the CSS uses logical properties like margin-inline-start instead of margin-left. Anything written with left and right had to be rewritten. It is worth doing from the first line of CSS.

No account needed

Sandglass works fully as a guest. Everything is saved in the browser. If you sign in, your data syncs through Supabase, with row-level security on every table so no one can read anyone else's sessions.

Most people trying a focus app will not make an account first. Asking them to is the fastest way to lose them.

Small things that make it feel finished

  • Share cards for a focus streak are drawn on a Canvas at 1080×1920, the size Instagram stories expect, so they look sharp when posted.
  • The ambient sounds are generated with the Web Audio API instead of shipped as audio files. The app stays small and the sound never loops audibly.
  • It installs as a PWA, so it sits on your home screen like any other app.

Sandglass is still in development. If you work or study in the UAE and want to try it early, email me.

  1. 2 min read

    What actually makes a school website fast

    Lessons from rebuilding school websites in Next.js: static pages, almost no JavaScript, lighter images and technical SEO for parents searching locally.

    Performance, SEO, Next.js

  2. 3 min read

    Locking down a CRM with Postgres row-level security

    How Postgres row-level security keeps each school's leads private in a Supabase admissions CRM, and lets an agency import leads without reading any.

    Supabase, PostgreSQL, Security