Skip to content
Back to projects
Live2026

Zeitenrechner

A working-hours calculator with no framework and no build step. Time entries never leave the browser, and the backdrop shows the real weather.

Stack
HTML · CSS · JS
Source files
5
Weather states
7

About the project

Anyone tracking their own working hours does the same arithmetic every evening: clock-in, clock-out, subtract the break, compare against the daily target. Nobody needs an account, a subscription or an app for a task that size. So the Zeitenrechner is a single page with two modes: “balance” turns clock-in and clock-out into the day’s balance, “home time” inverts it and says when you can leave for an even day.

Output is decimal — +1.50h rather than 1:30 — because that is the form the number goes straight into a spreadsheet in. Target hours and break length are configurable and live in local storage, so they are already right on the next visit. The times themselves are never stored and never sent to a server; there simply is none. If you stay on the keyboard, four keys cover it: J for the current time, B to calculate, 1 and 2 for the mode, R to reset.

The calculation itself is short, but it has one case that is easy to miss: shifts crossing midnight. Someone who arrives at 22:00 and leaves at 06:00 did not work minus sixteen hours. A negative gross value means home time is on the next day, so 1,440 minutes get added back.

All of it is deliberately plain HTML, CSS and JavaScript — roughly 1,900 lines across five files. No framework, no build step, no bundle, and the only dependency is a local development server. For a tool that performs a subtraction and should then run untouched for years, that is the more honest construction: there is nothing that breaks in two years because a toolchain moved on.

The most visible part is the backdrop. The page pulls the current weather from the open Open-Meteo API, translates the WMO code into one of seven conditions with an intensity, and builds the scene from it: sky gradient and clouds in CSS, rain, snow and lightning as particles on a canvas. The number of drops follows from area and intensity rather than a fixed count, the loop pauses on a hidden tab, it never starts under reduced motion, and the last response is cached for fifteen minutes so the scene is there immediately.

That is where the actual problem came from, and it was not an effect but legibility. A number sitting on a sky that changes by the hour no longer has a reliable contrast ratio. The first attempt was a switch: dark sky, light text. It was not enough — the moment a bright cloud drifted behind the light text, the text was gone. The fix is halos: every piece of text sitting directly on the sky gets a shadow in the opposite tone of its glyphs. Light text carries a dark halo, dark text a light one, and both stay readable regardless of what passes behind them.

It runs as a static Vercel project serving the folder directly; a push to main deploys. It moved from a Cloudflare Pages address to its own subdomain, and the notice about that move lives in the same codebase: the script checks the hostname and deliberately does nothing on the new domain. On the old one it shows a banner that can normally be dismissed for 24 hours, and only for one in the final week before shutdown.

Examples

The calculation, night shifts included

javascript
let brutto = dep - arrif (brutto < 0) brutto += 1440   // clocking out is on the next dayreturn (brutto - st.breakMinutes) - st.workHours * 60

A negative gross value does not mean “negative working time”, it means “home time is on the next day”. A day has 1,440 minutes, and that settles it.

Weather code to scene

javascript
if (c === 3)  return { cond: 'clouds',  intensity: 2 }if (c === 63) return { cond: 'rain',    intensity: 2 }if (c === 71) return { cond: 'snow',    intensity: 2 }if (c === 95) return { cond: 'thunder', intensity: 2 }

Open-Meteo returns a WMO code. It maps onto seven conditions plus an intensity, and both then drive the same things: the colours of the CSS layers and the particle count on the canvas. Code 0 is the only one that splits — a clear sky becomes its own condition at night.

Contrast on a sky that keeps changing

css
/* dark sky: light glyphs, dark halo */#zr-app[data-sky="dark"] .zr-result {  text-shadow: 0 2px 26px rgba(12,16,30,.5), 0 0 3px rgba(12,16,30,.4);}/* light sky: dark glyphs, light halo */#zr-app:not([data-sky="dark"]) .zr-result {  text-shadow: 0 1px 3px rgba(255,255,255,.9);}

Light text on a dark sky is not enough the moment a bright cloud drifts behind it. So the halo always takes the opposite tone of the glyphs — and the number stays readable whatever happens behind it.

No build, no bundle

json
{  "framework": null,  "buildCommand": null,  "outputDirectory": "src",  "cleanUrls": true}

Vercel serves the folder exactly as it sits in the repository. A push to main is the deployment. What is never built cannot fail to build because a toolchain moved on.