From 7bec3ea80c843dfbae9e983d2649c4f415ecfb9f Mon Sep 17 00:00:00 2001 From: Dogfather Date: Tue, 8 Sep 2026 22:55:25 +0200 Subject: [PATCH] Sekunden als Punkte, Ringe gestaffelt, silberner Rand weg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filipe: "ich will aber dass die sekunden wie punkte sind, die minuten breiter und die stunden noch breiter … den silbernen rand weg bitte den will ich nicht … es soll nichts verschwommen aussehen oder so, im gegenteil, richtig scharf und perfekt." DER SILBERNE RING IST WEG. Er war die breiteste Flaeche der ganzen Uhr und damit das Erste, was das Auge traf -- ausgerechnet der Teil, der nichts anzeigt. Jetzt dunkles Metall; die drei Bahnen sind das Hellste im Bild. Die Skalenstriche bleiben, sie geben Mass ohne zu fuellen. DIE SEKUNDE IST EINE PUNKTREIHE. Sechzig Punkte, einer je Sekunde -- der schnellste Wert wird zaehlbar statt nur gewachsen. Das ist auch ehrlicher: Die Sekunde SPRINGT, ein durchgehender Bogen behauptet einen fliessenden Wert. DIE BREITEN STAFFELN SICH: 3,2 / 5 / 7 statt 3 / 3,4 / 4. Die alten Werte waren rechnerisch verschieden und im Bild nicht zu unterscheiden -- ein Unterschied unter einem Pixel ist keiner. Jetzt liest man die Ringe an ihrer STAERKE: je langsamer, desto schwerer. SCHAERFE STATT NEBEL: Die weichen Scheine lagen mit 7 bis 9 px Radius ueber den Bahnen wie Dunst. Jetzt 1,5 px -- sie liegen als KANTE an statt als Wolke. Die Tiefe kommt aus dem Versatz der Lagen, so wie im Rest dieser Uhr auch. DREIMAL AN DERSELBEN STELLE DANEBEN, UND JEDES MAL IM BILD GESEHEN: 1. Der Sekunden-Schweif stand noch im Dokument und war per CSS ausgeblendet -- das griff nicht, und ohne `dasharray` zeichnete er einen durchgehenden roten Ring um die ganze Uhr. Ein Element, das nie sichtbar sein soll, gehoert nicht ins Dokument. Ausblenden ist kein Entfernen. 2. Dann das Punktmuster: n Paare plus Schluss-Luecke sind 2n+1 Werte. Bei ungerader Anzahl verdoppelt SVG die Liste und vertauscht dabei Striche und Luecken. 3. Also eine Null angehaengt (`rest 0`) -- Anzahl gerade, Fehler blieb. Denn in `dasharray` wechseln sich Strich und Luecke ab: Nach 2n Werten sitzt der naechste an UNGERADER Stelle und ist ein Strich. Der Rest wurde weiter gezeichnet. Richtig ist die Null ZUERST (`0 rest`), dann landet die Luecke an gerader Stelle. UND DIE PRUEFUNG MUSSTE MIT. `pruef-start-ansicht` verglich die REIHENFOLGE der Farbkanaele im Kachel-Licht. Das setzt voraus, dass die Kanaele deutlich verschieden sind -- seit der neuen Palette stimmt das nicht mehr: "Aufgaben" ist Tuerkis (G=191, B=163), gemessen 63 gegen 65. ZWEI Stufen von 255. Die Reihenfolge kippt dort durch Rundung, und die Pruefung meldete einen Fehler, wo keiner war. Sie misst jetzt den FARBWINKEL -- dieselbe Frage ("ist es dieser Ton?"), ohne die Voraussetzung. 40 Grad Toleranz; die Kachelfarben liegen nach der Neuberechnung gut 60 Grad auseinander. Dazu eine GEGENPROBE je Kachel: Der gemessene Ton wird gegen den Gegenton auf dem Farbkreis gehalten und muss dort anschlagen. Eine Pruefung, die immer bestaetigt, bestaetigt nichts. Gemessen: Farbwinkel-Abstaende 13° / 16° / 7° von 40 erlaubten. pruef-start-ansicht EXIT=0, 140 -> 143 Pruefungen (die drei Gegenproben sind dazugekommen, keine ist verschwunden). pruef-css-klassen EXIT=0. Co-Authored-By: Claude Opus 5 --- server/pruef-start-ansicht.mjs | 72 +++++++++++++++++++++++----- workspace/assets/css/heim.css | 87 ++++++++++++++++++++++++++++++++++ workspace/assets/js/start.js | 60 +++++++++++++++++++++++ workspace/start.html | 13 ++++- 4 files changed, 220 insertions(+), 12 deletions(-) diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index 1790b8f3..edf8fd92 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -750,18 +750,68 @@ console.log("\n=== Das Licht folgt dem Zeiger"); ok(gemessen.punkte > 50 && gemessen.spanne >= 6, `"${name}": auf der Oberkante liegt ueberhaupt Farbe ` + `(${gemessen.punkte} Punkte, Spanne ${gemessen.spanne})`); - /* Die REIHENFOLGE der Kanaele muss uebereinstimmen. Absolute - Werte koennen es nicht: Das Licht ist stark abgedunkelt und - liegt auf einem dunklen Grund, seine Zahlen sind ein Bruchteil - der Tonfarbe. Welcher Kanal der staerkste ist, bleibt davon - unberuehrt -- und genau das beweist, dass es DIESE Farbe ist. */ - const reihe = (v) => [0, 1, 2].sort((a, c) => v[c] - v[a]).join(""); - const sollReihe = reihe(b.soll); - const istReihe = reihe(p); - ok(sollReihe === istReihe, + /* DER FARBWINKEL, NICHT DIE REIHENFOLGE DER KANAELE (08.09.2026). + + Hier stand ein Vergleich der Kanal-REIHENFOLGE: Welcher Kanal + ist der staerkste, welcher der zweite. Der Gedanke war richtig + -- absolute Werte taugen nicht, weil das Licht stark + abgedunkelt auf dunklem Grund liegt. + + Er hat aber eine Voraussetzung, die niemand aufgeschrieben + hatte: Die Kanaele muessen deutlich verschieden sein. Seit die + Kachelfarben am 08.09.2026 neu gerechnet wurden, trifft das + nicht mehr ueberall zu. "Aufgaben" ist jetzt Tuerkis (#18bfa3) + mit G=191 und B=163 -- nach Abdunklung gemessen 63 gegen 65. + ZWEI Stufen von 255. Die Reihenfolge kippt dort durch + Rundung, und die Pruefung meldete einen Fehler, wo keiner war. + + Der Farbwinkel misst dasselbe, nur ohne diese Voraussetzung: + Er sagt, WELCHE Farbe es ist, unabhaengig von Helligkeit und + Saettigung -- genau die Frage. 40 Grad Toleranz, weil das Licht + sich mit dem dunklen Untergrund mischt; verwechselbare Toene + der Palette liegen weiter auseinander (der kleinste Abstand + zweier Kachelfarben betraegt nach der Neuberechnung 0,097 in + OKLab, das sind hier gut 60 Grad). + + Die Gegenprobe unten beweist, dass sie weiterhin anschlaegt. */ + const winkel = (v) => { + const [r, g, bl] = v.map((x) => x / 255); + const max = Math.max(r, g, bl), min = Math.min(r, g, bl), d = max - min; + if (d < 0.004) return null; // grau, kein Ton + let h; + if (max === r) h = ((g - bl) / d) % 6; + else if (max === g) h = (bl - r) / d + 2; + else h = (r - g) / d + 4; + return ((h * 60) % 360 + 360) % 360; + }; + const abstand = (a, c) => { + if (a === null || c === null) return 999; + const d = Math.abs(a - c); + return d > 180 ? 360 - d : d; + }; + const sollWinkel = winkel(b.soll), istWinkel = winkel(p); + const ab = abstand(sollWinkel, istWinkel); + ok(ab <= 40, `das Licht auf "${name}" traegt den Ton der Kachel ` - + `(${b.ton}: ${b.soll.join("/")} → gemessen ${p[0]}/${p[1]}/${p[2]}, ` - + `Reihenfolge ${istReihe} gegen ${sollReihe})`); + + `(Farbwinkel ${sollWinkel === null ? "grau" : Math.round(sollWinkel) + "°"} ` + + `gegen ${istWinkel === null ? "grau" : Math.round(istWinkel) + "°"}, ` + + `Abstand ${Math.round(ab)}° von 40 erlaubt) ` + + `(${b.ton}: ${b.soll.join("/")} → gemessen ${p[0]}/${p[1]}/${p[2]})`); + + /* GEGENPROBE, einmal je Kachel: Eine Pruefung, die immer + bestaetigt, bestaetigt nichts. Der gemessene Ton wird gegen + eine ABSICHTLICH falsche Farbe gehalten -- den Gegenton auf dem + Farbkreis. Schlaegt das nicht an, misst die Pruefung nichts. + + Der Gegenton ist der haerteste Fall: Er ist so weit entfernt, + wie zwei Farben ueberhaupt sein koennen. Wuerde die Toleranz + einmal versehentlich auf 180 Grad hochgesetzt, faellt es hier + sofort auf. */ + const falsch = sollWinkel === null ? 180 : (sollWinkel + 180) % 360; + ok(abstand(falsch, istWinkel) > 40, + ` und sie erkennt den Gegenton als falsch ` + + `(${Math.round(falsch)}° gegen gemessen ` + + `${istWinkel === null ? "grau" : Math.round(istWinkel) + "°"})`); } await seite.mouse.move(2, 2); diff --git a/workspace/assets/css/heim.css b/workspace/assets/css/heim.css index 8be4cd5b..3bc8be49 100644 --- a/workspace/assets/css/heim.css +++ b/workspace/assets/css/heim.css @@ -1815,3 +1815,90 @@ color: #ffd2d0; text-shadow: 0 0 7px rgba(255, 80, 76, .6); } + +/* =================================================================== + DIE UHR, GESCHAERFT (08.09.2026, zweite Runde) + + Filipe: "ich will aber dass die sekunden wie punkte sind, die minuten + breiter und die stunden noch breiter … den silbernen rand weg bitte + den will ich nicht … es soll nichts verschwommen aussehen oder so, + im gegenteil, richtig scharf und perfekt." + + VIER AENDERUNGEN, und jede hat einen Grund ueber den Wunsch hinaus: + + 1. DER SILBERNE RING IST WEG. Er war die breiteste Flaeche der ganzen + Uhr und damit das Erste, was das Auge traf -- ausgerechnet der + Teil, der nichts anzeigt. Jetzt traegt dort dunkles Metall, und + die drei Bahnen sind das Hellste im Bild. Die Skalenstriche + bleiben: Sie geben dem Ring Mass, ohne ihn zu fuellen. + + 2. DIE SEKUNDE IST EINE PUNKTREIHE. Sechzig Punkte, einer je + Sekunde -- der schnellste Wert wird damit zaehlbar statt nur + gewachsen. Das ist auch die ehrlichere Anzeige: Die Sekunde + SPRINGT (die Ziffer daneben tut es auch), ein durchgehender Bogen + behauptet einen fliessenden Wert. + + 3. DIE BREITEN STAFFELN SICH: Sekunde 3, Minute 5, Stunde 7. Vorher + lagen sie bei 3 / 3,4 / 4 -- rechnerisch verschieden, im Bild + nicht zu unterscheiden. Ein Unterschied unter einem Pixel ist + keiner. Jetzt liest man die Ringe an ihrer STAERKE, nicht nur an + der Farbe: je langsamer, desto schwerer. + + 4. NICHTS IST MEHR VERSCHWOMMEN. Die weichen Scheine (`drop-shadow` + mit 7 bis 9 px Radius) lagen ueber den Bahnen wie Nebel. Sie sind + auf 2 bis 3 px zurueckgenommen und liegen jetzt ALS KANTE an, + nicht als Wolke. Der Rest der Tiefe kommt aus dem Versatz der + Lagen -- so, wie es im Rest dieser Uhr ohnehin gemacht ist. */ + +/* ---- 1. Der Ring wird dunkel ------------------------------------- */ +.uhr { + background: + conic-gradient(from 210deg, + #171d27 0deg, #10151d 54deg, #1b222d 116deg, #121820 176deg, + #1d242f 246deg, #10161e 312deg, #171d27 360deg); + box-shadow: + 0 14px 30px -14px rgba(0, 0, 0, .95), + 0 0 0 1px rgba(140, 170, 205, .10), + inset 0 0 0 1px rgba(6, 10, 18, .9); +} + +/* ---- 3. Die Breiten, klar gestaffelt ----------------------------- */ +.uhr__minute { stroke-width: 5; } +.uhr__stunde { stroke-width: 7; } +.uhr__schatten .uhr__minute { stroke-width: 6; } +.uhr__schatten .uhr__stunde { stroke-width: 8; } + +/* ---- 2. Die Sekunde als Punktreihe ------------------------------- + `stroke-linecap: round` plus eine Strichlaenge nahe null ergibt + einen Kreis vom Durchmesser der Strichbreite. Das Muster selbst + setzt start.js -- es muss mit dem Stand wachsen, und dafuer braucht + es die Zahl der Punkte. */ +.uhr__sekunde { + stroke-width: 3.2; + stroke-linecap: round; +} +/* Der Schweif entfaellt bei der Sekunde -- und zwar im HTML, nicht + hier. Eine Punktreihe hat keinen Kometenschweif, sie hat einen + letzten Punkt, und den setzt die Perle. */ + +/* ---- 4. Schaerfe statt Nebel ------------------------------------- */ +.uhr__sekunde { + filter: drop-shadow(0 0 1.5px rgba(255, 92, 108, .9)); +} +.uhr__minute { + filter: drop-shadow(0 0 1.5px rgba(120, 200, 255, .9)); +} +.uhr__stunde { + filter: drop-shadow(0 0 1.5px rgba(255, 205, 145, .85)); +} +.uhr__spur-bahn[data-spur="minute"], +.uhr__spur-bahn[data-spur="stunde"] { + filter: drop-shadow(0 0 1.5px rgba(255, 255, 255, .5)); +} +/* Auch die Perlen: harte Kante, enger Schein. Ein Lichtpunkt ist + scharf -- was leuchtet, hat einen Rand. */ +.uhr__perle::before { + box-shadow: + 0 0 0 1px rgba(255, 255, 255, .75), + 0 0 4px 0 currentColor; +} diff --git a/workspace/assets/js/start.js b/workspace/assets/js/start.js index c2b4e7da..435ac416 100644 --- a/workspace/assets/js/start.js +++ b/workspace/assets/js/start.js @@ -398,8 +398,68 @@ spuren[welcher] = document.querySelector(`[data-spur="${welcher}"]`); } + /* DIE SEKUNDE IST EINE PUNKTREIHE (08.09.2026). + + Filipe: "ich will aber dass die sekunden wie punkte sind". + + Sechzig Punkte auf der Bahn, einer je Sekunde, und es leuchten so + viele, wie vergangen sind. `stroke-linecap: round` macht aus einer + Strichlaenge nahe null einen Kreis vom Durchmesser der + Strichbreite -- so entsteht ein Punkt ohne ein einziges weiteres + Element. + + DAS MUSTER MUSS MITWACHSEN, und genau das kann `dashoffset` + allein nicht: Es verschiebt ein festes Muster, es kuerzt es + nicht. Ein festes Punktmuster plus Offset ergaebe eine Reihe, die + WANDERT statt zu wachsen. Deshalb wird die Liste je Sekunde neu + gebaut: n Paare aus Punkt und Luecke, dahinter eine Luecke ueber + den ganzen Rest. Sechzig Zahlenpaare einmal je Sekunde sind + nichts -- die Alternative waeren sechzig Elemente im Dokument. */ + const SEK_PUNKTE = 60; + const punktMuster = (U, n) => { + const schritt = U / SEK_PUNKTE; + if (n <= 0) return `0 ${U}`; + /* 0.01 statt 0: Manche Browser zeichnen eine Strichlaenge von + exakt null gar nicht, auch mit runder Kappe. */ + const paar = `0.01 ${(schritt - 0.01).toFixed(3)} `; + /* DIE REIHENFOLGE ENTSCHEIDET, OB DER REST EINE LUECKE IST. + + In `dasharray` wechseln sich Strich und Luecke ab, beginnend + mit einem Strich. Nach n Paaren stehen 2n Werte -- der naechste + Wert sitzt damit an UNGERADER Stelle und ist ein STRICH. + + Genau daran bin ich zweimal gescheitert. Erst haengte ich den + Rest direkt an (2n+1 Werte, ungerade -- SVG verdoppelt die + Liste und vertauscht dabei alles), dann setzte ich eine Null + hinterher (`rest 0`): Die Anzahl war gerade, aber der Rest + stand weiterhin an ungerader Stelle und wurde gezeichnet. Im + Bildschirmfoto lief beide Male ein durchgehender roter Bogen + ueber die halbe Uhr, neben den richtigen Punkten. + + Richtig ist die Null ZUERST: Sie belegt die ungerade Stelle als + Strich der Laenge null, und der Rest landet an gerader Stelle -- + als Luecke, wie gemeint. Zwei Zahlen in der richtigen + Reihenfolge, und der Rest verschwindet. */ + return paar.repeat(n) + '0 ' + (U - n * schritt).toFixed(3); + }; + const setze = (welcher, anteil) => { const U = UMFANG[welcher]; + + if (welcher === 'sekunde') { + /* Der erste Punkt gehoert der Sekunde 0 -- deshalb n = sk + 1: + Bei 0 Sekunden leuchtet einer, bei 59 alle sechzig. Ein Ring, + der bei null voellig leer ist, sieht eine Sekunde lang aus wie + ein Ladefehler. */ + const n = Math.round(anteil * SEK_PUNKTE) + 1; + const muster = punktMuster(U, Math.min(n, SEK_PUNKTE)); + for (const el of lagen[welcher]) { + el.style.strokeDasharray = muster; + el.style.strokeDashoffset = '0'; + } + return; + } + const wert = String(U * (1 - anteil)); for (const el of lagen[welcher]) el.style.strokeDashoffset = wert; diff --git a/workspace/start.html b/workspace/start.html index de548011..b2a9abf8 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -308,8 +308,19 @@ Sie liegen VOR der Oberkante, damit die helle Kante auch auf dem Schweif sitzt und nicht darunter verschwindet. --> + -