Files
musicmouse/web/src/main.tsx
Martin Bauer a55cc4f67f Clock: a "Delphin Uhr" overlay with the school bus and a parents' timer
Einfg on the kiosk keyboard, or Guide (KEY_EPG) on the IR remote, shows a big
analog watch over whatever page is open, built from the Delphin Uhr mockup
without its swimming dolphin. The overlay stays mounted and is only hidden, so it
appears at once. Beside the watch, only while they run:

- The bus: on school days, a silent countdown to `general.clock.bus_time` (07:25)
  that starts `bus_lead_minutes` (60) before. School days skip weekends and the
  school and public holidays of `general.clock.subdivision` (DE-BY), fetched daily
  from openholidaysapi.org and cached under the library cache. A badge on the
  clock says whether there is school today, and why not.
- The dolphin timer, set in parent mode (5/10/15/30 minutes, or until a time).
  It lives in the backend, so a parent's phone can start it for the kiosk. When it
  runs out the kiosk opens the clock at 00:00 and a soft bell chimes on the
  device; it clears after a minute, or at the next key, tap or remote press.

Parent mode is now a way in rather than a page: `?parentMode=1` adds a 🔧 button
next to the ⌨ one, or an "Eltern" tab in the phone's dock, so the URL can stay
bookmarked. The timer comes first there, the device settings below it.

Unmapped IR buttons are logged at debug level, to find out what irserver calls a
new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:45:45 +02:00

47 lines
1.7 KiB
TypeScript

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { PERF } from "./lib/perfSettings";
import { installFullHeight } from "./lib/fullHeight";
import { applySkin } from "./lib/skins";
import "./styles/app.css";
import "./styles/mobile.css";
import "./styles/room.css";
import "./styles/tippen.css";
import "./styles/clock.css";
// Before the first render, so nothing ever paints in the wrong skin's colours.
applySkin();
installFullHeight();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
// Registered after load so it never competes with the first paint. It is what makes
// iOS offer "Zum Home-Bildschirm" as an app rather than a bookmark; see public/sw.js
// for what it does and deliberately does not cache.
//
// Not under `?pi=1`: that is the kiosk, which *is* the server. There is nothing to
// install, and the worker only adds a fetch handler (and a `cache.put` of every response)
// in front of requests that are already on localhost. Any worker an earlier visit left
// behind is removed so the kiosk stops paying for it.
if ("serviceWorker" in navigator && import.meta.env.PROD) {
window.addEventListener("load", () => {
if (!PERF.serviceWorker) {
void navigator.serviceWorker
.getRegistrations()
.then((registrations) => Promise.all(registrations.map((r) => r.unregister())))
.catch(() => undefined);
return;
}
void navigator.serviceWorker.register("/sw.js").catch(() => {
// A plain http:// origin that is not localhost cannot register one. The app
// works exactly the same, it just cannot be installed.
});
});
}