Die Uhr bekommt Kometenschweife -- und die Koepfe gleiten, statt zu springen

Filipe: "nehm diese uhr bitte und passe sie so gut wie es geht an, auch
die lichter von sekunden, minuten und stunden perfekt anpassen ...
überrasch mich."

ZWEI DINGE, DIE EINE UHR LEBENDIG MACHEN.

1. DER KOMETENSCHWEIF. Ein Bogen in gleichmaessiger Farbe zeigt einen
   STAND. Ein Bogen, der zur Spitze hin heller wird, zeigt eine
   RICHTUNG -- man sieht ohne Nachdenken, wo "jetzt" ist und wohin es
   laeuft. Bei drei Ringen uebereinander ist das der Unterschied
   zwischen Ablesen und Erkennen.

   Gebaut als zweites, kuerzeres Segment ueber dem Bogen: die letzten
   rund 40 Grad, heller, mit runder Kappe. Ein Verlauf ENTLANG der Bahn
   geht in SVG nicht -- `linearGradient` folgt einer Geraden, keiner
   Kurve. Zwei Lagen sind der ehrliche Weg dorthin.

   Die Lage wird gerechnet, nicht geschaetzt: Bei Umfang U, Fortschritt
   a und Schweiflaenge s soll das Segment von (aU - s) bis aU liegen.
   Mit `dasharray: s, U-s` beginnt das sichtbare Stueck bei (U - offset),
   also ist offset = U*(1-a) + s. Die Laenge wird bei kurzen Boegen
   mitgekuerzt -- sonst haenge der Schweif am Anfang einer Minute am
   ENDE des Kreises, sichtbar als heller Strich bei zwoelf Uhr.

2. DIE KOEPFE GLEITEN. Bisher sprangen sie einmal je Sekunde. Jetzt
   uebernimmt der Browser die Bewegung dazwischen (Ueberblendung von
   0,92 s) -- das Skript rechnet weiterhin nur EINMAL je Sekunde. Sechzig
   Bildberechnungen je Sekunde wuerden auf einer stundenlang offenen
   Seite den Rechner warm halten; diese Loesung kostet nichts.

ZWEI FEHLER DABEI, BEIDE NUR IM BILD ZU SEHEN:

  Der erste Versuch rechnete die Winkel aus der vollen Unixzeit, damit
  sie immer weiterwachsen (sonst liefe die Ueberblendung einmal je
  Minute rueckwaerts durchs Zifferblatt). Ergebnis:
  `rotate(1.07333e+10deg)` -- Exponentialschreibweise, Nachkommastellen
  weg. Die Koepfe standen sichtbar neben ihren Boegen, der rote auf der
  anderen Seite der Uhr. Jetzt zaehlt eine eigene Uhr ab dem ersten
  Takt: klein genug zum Rechnen, wachsend genug fuer die Ueberblendung.

  Und ich hatte den Schweifen `rotate(-90deg)` gegeben, damit sie bei
  zwoelf beginnen -- das SVG ist aber bereits gedreht. Sie standen
  dadurch exakt eine Vierteldrehung daneben: oben links, waehrend die
  Boegen oben rechts endeten.

NACHGEMESSEN, in Grad statt nach Augenmass:
  Kopf gegen Bogenende      Abweichung 0,00° / 0,00° / 0,03°
  Schweifende gegen Bogen   Abweichung 0,0° / 0,0° / 0,0°

pruef-start-ansicht EXIT=0 (140), pruef-css-klassen EXIT=0 (28).
Bei `prefers-reduced-motion` gleiten die Koepfe nicht -- sie springen
dann wie vorher.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-08 18:12:26 +02:00
co-authored by Claude Opus 5
parent 4c0ff5caa9
commit e2bd6d4dc4
3 changed files with 179 additions and 4 deletions
+74
View File
@@ -1659,3 +1659,77 @@
weniger Reiz. Die Bahnen bleiben in voller Farbe stehen. */
.uhr__sekunde, .uhr__minute, .uhr__stunde { filter: none; }
}
/* ===================================================================
DER KOMETENSCHWEIF (08.09.2026)
Filipe: "auch die lichter von sekunden, minuten und stunden perfekt
anpassen, wie die läuft und aussieht soll wirklich mega perfekt sein
... überrasch mich."
WAS ER LEISTET: Ein Bogen in gleichmaessiger Farbe zeigt einen
STAND. Ein Bogen, der zur Spitze hin heller wird, zeigt eine
RICHTUNG -- man sieht ohne Nachdenken, wo "jetzt" ist und wohin es
laeuft. Bei drei Ringen uebereinander ist das der Unterschied
zwischen Ablesen und Erkennen.
WARUM ZWEI LAGEN UND KEIN VERLAUF: Ein Farbverlauf ENTLANG einer
Kreisbahn geht in SVG nicht -- `linearGradient` folgt einer Geraden,
`radialGradient` einem Radius, keiner einer Bahn. Der ehrliche Weg
ist ein zweites, kuerzeres Segment ueber dem ersten. Es liegt vor
der Oberkante, damit die helle Kante auch auf dem Schweif sitzt.
`stroke-linecap: round` nur hier, nicht am Hauptbogen: Die runde
Kappe markiert die Spitze. Am Hauptbogen wuerde sie auch den ANFANG
runden -- und der liegt bei zwoelf Uhr, wo eine runde Kappe wie ein
Fehler aussieht.
AUGENSCHONEND: Der Schweif ist heller als der Bogen, aber nicht
greller -- er bleibt in derselben Farbfamilie und wird nicht
animiert. Was hier leuchtet, leuchtet ruhig. */
/* KEINE EIGENE DREHUNG (Korrektur nach dem Bildschirmfoto).
Ich hatte hier `transform: rotate(-90deg)` gesetzt, damit der
Schweif wie die Bahnen bei zwoelf Uhr beginnt. Das war doppelt: Das
ganze SVG `.uhr__ring` ist bereits um -90 Grad gedreht (siehe dort,
"Bei Null soll der Ring OBEN beginnen"). Die Schweife standen
dadurch exakt eine Vierteldrehung neben ihren Boegen -- im Bild oben
links, waehrend die Boegen oben rechts endeten. Die Rechnung war
richtig, die Drehung eine zu viel. */
.uhr__spur-bahn {
fill: none;
stroke-linecap: round;
opacity: .95;
}
.uhr__spur-bahn[data-spur="sekunde"] {
stroke: #ffd2d6; stroke-width: 3;
filter: drop-shadow(0 0 3px rgba(255, 120, 134, .85));
}
.uhr__spur-bahn[data-spur="minute"] {
stroke: #d8efff; stroke-width: 3.4;
filter: drop-shadow(0 0 3px rgba(130, 205, 255, .8));
}
.uhr__spur-bahn[data-spur="stunde"] {
stroke: #ffe9c9; stroke-width: 4;
filter: drop-shadow(0 0 3px rgba(233, 190, 130, .75));
}
/* DIE KOEPFE GLEITEN, STATT ZU SPRINGEN.
Die Anzeige selbst rechnet weiterhin nur EINMAL je Sekunde -- das
war eine bewusste Entscheidung (sechzig Bildberechnungen je Sekunde
halten auf einer stundenlang offenen Seite den Rechner warm). Die
Bewegung dazwischen uebernimmt der Browser: eine Ueberblendung von
knapp einer Sekunde, die genau bis zum naechsten Takt laeuft.
Damit sieht die Perle aus wie ein gleitender Zeiger und kostet
trotzdem nichts. Voraussetzung ist, dass der Winkel WEITERZAEHLT
statt bei 360 Grad zurueckzuspringen -- sonst liefe die Ueberblendung
einmal je Minute rueckwaerts durch das ganze Zifferblatt. Genau das
macht start.js jetzt (fortlaufende Sekunden statt sk % 60). */
.uhr__perle {
transition: transform .92s cubic-bezier(.33, 0, .2, 1);
}
@media (prefers-reduced-motion: reduce) {
.uhr__perle { transition: none; }
}
+77 -4
View File
@@ -379,11 +379,42 @@
for (const welcher of ['sekunde', 'minute', 'stunde']) {
lagen[welcher] = [...document.querySelectorAll(`[data-ring="${welcher}"]`)];
}
/* DER SCHWEIF (08.09.2026): die letzten Grad vor der Spitze, heller.
Gerechnet, nicht geschaetzt. Ein Kreis mit Umfang U, Fortschritt
a (0..1) und Schweiflaenge s soll ein Segment von (a*U - s) bis
a*U zeigen. Mit `dasharray: s, U-s` beginnt das sichtbare Stueck
bei (U - offset), also gilt
U - offset = a*U - s -> offset = U*(1-a) + s
Die Laenge wird bei kurzen Boegen mitgekuerzt: Am Anfang einer
Minute ist der Bogen selbst noch kuerzer als der Schweif, und ein
Schweif ohne Bogen darunter haenge sonst am Ende des Kreises --
sichtbar als heller Strich bei zwoelf Uhr, wo nichts sein darf. */
const SCHWEIF = { sekunde: 30, minute: 26, stunde: 22 }; // Bogenlaenge
const spuren = {};
for (const welcher of ['sekunde', 'minute', 'stunde']) {
spuren[welcher] = document.querySelector(`[data-spur="${welcher}"]`);
}
const setze = (welcher, anteil) => {
const wert = String(UMFANG[welcher] * (1 - anteil));
const U = UMFANG[welcher];
const wert = String(U * (1 - anteil));
for (const el of lagen[welcher]) el.style.strokeDashoffset = wert;
const spur = spuren[welcher];
if (!spur) return;
const s = Math.min(SCHWEIF[welcher], U * anteil);
if (s <= 0.5) { spur.style.opacity = '0'; return; } /* nichts zu zeigen */
spur.style.opacity = '';
spur.style.strokeDasharray = `${s} ${U - s}`;
spur.style.strokeDashoffset = String(U * (1 - anteil) + s);
};
/* Der Nullpunkt der eigenen Uhr -- siehe unten bei den Koepfen. */
let nullpunkt = null, startWinkel = 0, minStart = 0, stdStart = 0, sekStart = 0;
const takt = () => {
const j = new Date();
const std = j.getHours(), min = j.getMinutes(), sk = j.getSeconds();
@@ -409,16 +440,58 @@
sechzig. Auf einer Seite, die stundenlang offen steht, ist das
der Unterschied zwischen einem warmen und einem kalten
Rechner. */
/* DIE KOEPFE ZAEHLEN WEITER, STATT ZURUECKZUSPRINGEN.
Sie gleiten jetzt (Ueberblendung in heim.css) statt zu
springen. Damit das funktioniert, darf der Winkel bei 59 -> 0
Sekunden NICHT von 354 auf 0 fallen -- die Ueberblendung liefe
sonst einmal je Minute rueckwaerts durch das ganze
Zifferblatt. Gerechnet wird deshalb mit der fortlaufenden Zeit:
Der Winkel waechst immer weiter, und 366 Grad sieht genauso aus
wie 6, nur dass der Weg dorthin vorwaerts fuehrt.
Die Anzeige selbst rechnet weiterhin nur einmal je Sekunde --
die Bewegung dazwischen macht der Browser, nicht das Skript. */
/* SEIT DEM ERSTEN TAKT, NICHT SEIT 1970.
Der erste Versuch rechnete mit der vollen Unixzeit. Ergebnis im
Bildschirmfoto: `rotate(1.07333e+10deg)` -- eine Zahl in
Exponentialschreibweise, bei der die Nachkommastellen laengst
weg sind. Die Perlen standen sichtbar NEBEN dem Ende ihrer
Boegen, die rote sogar auf der anderen Seite des Zifferblatts.
Gerechnet war es richtig, darstellbar nicht.
Jetzt zaehlt eine eigene Uhr ab dem ersten Takt: Startwinkel ist
der echte Stand beim Laden, danach kommen 6 Grad je Sekunde
dazu. Die Zahl bleibt klein (ein Tag sind 518 400 Grad), waechst
aber weiter -- und nur das braucht die Ueberblendung, damit sie
nie rueckwaerts laeuft. */
if (nullpunkt === null) {
nullpunkt = Math.floor(j.getTime() / 1000);
startWinkel = sk * 6;
minStart = min; stdStart = std; sekStart = sk;
}
const seit = Math.floor(j.getTime() / 1000) - nullpunkt;
const winkelSek = startWinkel + seit * 6;
const perle = $('uhr-perle');
if (perle) perle.style.transform = `rotate(${sk * 6}deg)`;
if (perle) perle.style.transform = `rotate(${winkelSek.toFixed(2)}deg)`;
/* Die beiden anderen Koepfe laufen mit IHREN Ringen -- die Minute
nimmt die Sekunden anteilig mit, die Stunde die Minuten. Sonst
staende der Kopf neben dem Ende seines Bogens, und genau das
sieht man sofort. */
/* Minute und Stunde ebenso: Startstand plus das, was seither
vergangen ist. Sie tragen die kleineren Einheiten anteilig mit,
damit ihr Kopf nie neben dem Ende ihres Bogens steht. */
const perleMin = $('uhr-perle-minute');
if (perleMin) perleMin.style.transform = `rotate(${(min + sk / 60) * 6}deg)`;
if (perleMin) {
const start = (minStart + sekStart / 60) * 6;
perleMin.style.transform = `rotate(${(start + seit * 0.1).toFixed(3)}deg)`;
}
const perleStd = $('uhr-perle-stunde');
if (perleStd) perleStd.style.transform = `rotate(${((std % 12) + min / 60) * 30}deg)`;
if (perleStd) {
const start = ((stdStart % 12) + minStart / 60) * 30;
perleStd.style.transform = `rotate(${(start + seit / 120).toFixed(4)}deg)`;
}
setze('minute', (min + sk / 60) / 60);
setze('stunde', ((std % 12) + min / 60) / 12);
+28
View File
@@ -286,6 +286,34 @@
<circle class="uhr__minute" data-ring="minute" cx="50" cy="50" r="38.5" id="uhr-minute" />
<circle class="uhr__stunde" data-ring="stunde" cx="50" cy="50" r="31.5" id="uhr-stunde" />
<!-- ==== DER KOMETENSCHWEIF (08.09.2026) ==================
Filipe: "auch die lichter von sekunden, minuten und
stunden perfekt anpassen, wie die läuft und aussieht
soll wirklich mega perfekt sein."
Ein Bogen in gleichmaessiger Farbe zeigt einen STAND.
Ein Bogen, der zur Spitze hin heller wird, zeigt eine
RICHTUNG -- man sieht ohne Nachdenken, wo "jetzt" ist
und wohin es laeuft. Das ist der Unterschied zwischen
einer Anzeige und einem Instrument.
Gebaut als eigenes Segment ueber dem Bogen: die letzten
rund 40 Grad vor der Spitze, heller und mit runder
Kappe. Ein Verlauf ENTLANG des Kreises geht in SVG
nicht -- linearGradient folgt einer Geraden, nicht
einer Bahn. Zwei Lagen uebereinander sind der ehrliche
Weg dorthin.
Sie liegen VOR der Oberkante, damit die helle Kante
auch auf dem Schweif sitzt und nicht darunter
verschwindet. -->
<g class="uhr__spur">
<circle class="uhr__spur-bahn" data-spur="sekunde" cx="50" cy="50" r="45.5" />
<circle class="uhr__spur-bahn" data-spur="minute" cx="50" cy="50" r="38.5" />
<circle class="uhr__spur-bahn" data-spur="stunde" cx="50" cy="50" r="31.5" />
</g>
<g class="uhr__kante" transform="translate(0 -0.4)">
<circle class="uhr__sekunde" data-ring="sekunde" cx="50" cy="50" r="45.5" />
<circle class="uhr__minute" data-ring="minute" cx="50" cy="50" r="38.5" />