diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 2e3368c6..49b05279 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -176,21 +176,40 @@ aufgabenRouter.get("/workspace/api/personen", (req, res) => { sichtbarePersonenIds/sichtbareCreatorIds liefern null fuer DogFather (= keine Einschraenkung) und sonst genau die erlaubten Nummern. */ + /* DAS BILD GEHOERT DAZU (07.09.2026, Wunsch Filipe: "jeder soll auch + immer das profilbild von denen sehen mit denen sie verbunden + sind"). + + Diese Liste lieferte bisher `id, name, rolle` -- und deshalb + konnte KEINE Oberflaeche ein Gesicht zeigen, auch wenn eines + hochgeladen war. Der Fehler lag nicht in der Anzeige, sondern + hier: Was nicht mitkommt, kann niemand zeichnen. + + Ausgeliefert wird die fertige ADRESSE, nicht der Dateiname. Sonst + muesste jede Stelle im Browser denselben Pfad zusammensetzen -- + und beim naechsten Umzug des Ordners waere er an sechs Stellen + falsch. Wer kein Bild hat, bekommt `null`; die Oberflaeche zeigt + dann wie bisher den Anfangsbuchstaben. */ + const mitBild = (z) => ({ + id: z.id, name: z.name, rolle: z.rolle, + bild: z.bild ? `/workspace/api/steckbrief/bild/${z.bild}` : null, + }); const ids = sichtbarePersonenIds(req.person); if (ids === null) { return res.json({ personen: db().prepare( - "SELECT id, name, rolle FROM personen WHERE aktiv = 1 ORDER BY " + ROLLEN_SORTIERUNG + ", name").all(), + "SELECT id, name, rolle, bild FROM personen WHERE aktiv = 1 ORDER BY " + + ROLLEN_SORTIERUNG + ", name").all().map(mitBild), }); } if (!ids.length) { - return res.json({ personen: [{ id: req.person.id, name: req.person.name, rolle: req.person.rolle }] }); + return res.json({ personen: [{ id: req.person.id, name: req.person.name, rolle: req.person.rolle, bild: null }] }); } res.json({ personen: db().prepare( - `SELECT id, name, rolle FROM personen WHERE aktiv = 1 + `SELECT id, name, rolle, bild FROM personen WHERE aktiv = 1 AND id IN (${ids.map(() => "?").join(",")}) - ORDER BY ` + ROLLEN_SORTIERUNG + ", name").all(...ids), + ORDER BY ` + ROLLEN_SORTIERUNG + ", name").all(...ids).map(mitBild), }); } catch { res.status(503).json({ fehler: "nicht_verfuegbar" }); diff --git a/server/workspace.js b/server/workspace.js index ada43c9e..731e0a8e 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -1886,7 +1886,26 @@ export function sichtbarePersonenIds(person) { if (istDogFather(person)) return null; const creator = sichtbareCreatorIds(person) || []; const scouts = person.rolle === "manager" ? scoutsVon(person.id) : []; - return [...new Set([person.id, ...creator, ...scouts])]; + /* DOGFATHER IST FUER ALLE SICHTBAR (07.09.2026, Wunsch Filipe: + "dogfather und agentur sollen die alle sehen"). + + Das ist keine Aufweichung der Regel, sondern ihre Vervollstaendigung: + Jede Rolle arbeitet mit DogFather -- er gibt frei, er entscheidet, + er steht in jedem Protokoll. Ihn aus der Personenliste + herauszuhalten hiess bisher, dass sein Name in Terminen und + Aufgaben auftaucht, aber nirgends ein Gesicht dazugehoert. + + Sichtbar heisst hier NUR: Name, Rolle und Bild -- dieselben drei + Angaben wie bei jedem anderen in dieser Liste. Alles Weitere (seine + Eintraege, seine Chats, seine Zahlen) haengt weiterhin an den + eigenen Sichtbarkeitsregeln der jeweiligen Module und ist davon + nicht beruehrt. */ + const dogi = []; + try { + for (const z of db().prepare( + "SELECT id FROM personen WHERE rolle = 'admin' AND aktiv = 1").all()) dogi.push(z.id); + } catch { /* ohne Datenbank bleibt die Liste, wie sie war */ } + return [...new Set([person.id, ...creator, ...scouts, ...dogi])]; } /** WER BETREUT MICH -- die Blickrichtung nach oben. diff --git a/workspace/assets/css/heim.css b/workspace/assets/css/heim.css new file mode 100644 index 00000000..8c78814e --- /dev/null +++ b/workspace/assets/css/heim.css @@ -0,0 +1,347 @@ +/* ===================================================================== + heim.css — was NUR die Startseite braucht. + + Herausgeloest aus start.css am 07.09.2026: Jene Datei wird von + dreizehn Seiten geladen und lief mit ueber 200 KB in die Grenze von + pruef-struktur. Die Begruessungskachel, die Uhr und die Glockenmulde + darin gibt es aber nur hier. + + Wer etwas an der Startseite aendert, sucht ab jetzt zuerst in dieser + Datei -- und wer eine dieser Klassen auf einer ANDEREN Seite benutzen + will, muss diese Datei dort einbinden. pruef-css-klassen meldet es, + wenn er es vergisst. + ===================================================================== */ + +/* ===================================================================== + DIE BEGRÜSSUNGSKACHEL — mit Uhr und Glocke (07.09.2026) + + Wunsch Filipe: "ich will dass diese kacheln da auch übertrieben geil + gemacht wird, wirklich seeeeeehr hochwertig mit einer runden + digitalen uhr rechts in der kachel ... und der benachrichtigungs + button soll auch von der leiste in diese kachel gesetzt werden." + + --------------------------------------------------------------------- + WARUM DIE UHR HIER RICHTIG IST UND NICHT NUR SCHÖN + + Eine Uhr ist die einzige Angabe auf dieser Seite, die sich ändert, + während man hinsieht. Genau das macht sie wertvoll: Sie sagt ohne ein + Wort, dass die Seite LEBT -- dass die Zahlen daneben von jetzt sind + und nicht von gestern Nacht. Eine Oberfläche, bei der man das nicht + weiß, prüft man ständig nach. + + Und sie steht neben dem Datum, das ohnehin dort steht. Zwei Angaben + zur selben Frage gehören zusammen; das Datum oben und die Uhrzeit in + der Fußzeile wären zwei halbe Antworten. + + --------------------------------------------------------------------- + ZWEI RINGE, NICHT EINER + + Der innere Ring füllt sich in einer Minute, der äußere in einer + Stunde. Das ist der Unterschied zwischen "eine Uhr, die tickt" und + "eine Uhr, an der man den Stand der Stunde sieht" -- man erkennt aus + dem Augenwinkel, ob die Stunde jung ist oder gleich vorbei. + + Der Sekundenring läuft NICHT weich. Ein weicher Zeiger sieht teurer + aus, kostet aber eine Bildberechnung pro Bild statt einer pro + Sekunde, und auf einer Seite, die stundenlang offen steht, ist das + der Unterschied zwischen einem warmen und einem kalten Rechner. + Der Sprung ist außerdem ehrlicher: Die Anzeige ist digital. + ===================================================================== */ + +/* Die Kachel selbst: dasselbe Material wie die Bereichskacheln, damit + nicht zwei Oberflächen nebeneinanderliegen. Sie hat KEINE eigene + Farbschiene -- sie gehört zu keiner Kategorie, sie ist der Anfang. */ +.willkommen { + position: relative; + padding: 22px 24px; + border: 1px solid var(--rand); + border-radius: 4px 20px 20px 4px; + background: + linear-gradient(90deg, + rgba(63, 189, 245, .10) 0%, + rgba(63, 189, 245, .03) 14%, + transparent 40%), + linear-gradient(163deg, + rgba(23, 31, 45, .95) 0%, + rgba(11, 15, 24, .95) 58%, + rgba(8, 11, 18, .95) 100%); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, .10), + inset 0 -1px 0 rgba(0, 0, 0, .50), + inset 2px 0 0 -1px color-mix(in srgb, var(--akzent) 34%, transparent), + 0 2px 5px -2px rgba(0, 0, 0, .55), + 0 16px 34px -20px rgba(0, 0, 0, .88); + overflow: hidden; +} +/* Die Leuchtschiene links -- dieselbe Sprache wie überall, hier im + Hausakzent statt in einer Kategoriefarbe. */ +.willkommen::before { + content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; + border-radius: 4px 0 0 4px; + background: linear-gradient(180deg, + color-mix(in srgb, var(--akzent) 55%, #fff) 0%, + var(--akzent) 26%, + color-mix(in srgb, var(--akzent) 50%, transparent) 100%); + box-shadow: + 0 0 9px 0 color-mix(in srgb, var(--akzent) 55%, transparent), + 3px 0 20px -3px color-mix(in srgb, var(--akzent) 42%, transparent); + opacity: .9; + z-index: 2; +} +/* Körnung und ein fester Glanz. Kein wandernder wie bei den Kacheln: + Diese Fläche wird nicht angeklickt, also gibt es nichts zu quittieren. */ +.willkommen::after { + content: ""; position: absolute; inset: 0; pointer-events: none; + border-radius: inherit; + background: + linear-gradient(104deg, + transparent 26%, + rgba(255, 255, 255, .05) 40%, + rgba(255, 255, 255, .085) 47%, + rgba(255, 255, 255, .03) 55%, + transparent 70%); + /* KEINE Körnung auf DIESER Ebene (07.09.2026, gemessen). + + Sie stand hier als zweite Schicht und hat die Deckkraft der + Spiegelung geerbt -- 50 % eines mittelgrauen Rauschens heben jeden + Bildpunkt um rund 30 Stufen an. Gemessen: rgb(56,44,58) statt der + berechneten rgb(20,26,38), und über einer hellen Stelle des + Motivs rgb(85,83,81). Die Karte sah dadurch durchsichtig aus, + obwohl sie zu 95 % deckt. + + Derselbe Fehler wie an der Anmeldekarte heute Nachmittag, und + genau derselbe Grund: Körnung gehört auf eine EIGENE Ebene mit + `mix-blend-mode: overlay` und 3 bis 4 Prozent -- so wie bei den + Kacheln der Startseite. Auf einer geteilten Ebene ist sie kein + Material, sondern ein grauer Schleier. */ + opacity: .5; +} +.willkommen > * { position: relative; z-index: 1; } + +.willkommen__reihe { + display: flex; align-items: center; gap: 16px; + flex-wrap: wrap; +} +.willkommen__text { min-width: 0; flex: 1 1 260px; } + +/* Rechts: Glocke neben der Uhr, beide rechtsbündig. `margin-left: auto` + statt `justify-content: space-between` am Elternteil -- so bleibt die + Gruppe auch dann rechts, wenn der Text davor kurz ist. */ +.willkommen__rechts { + display: flex; align-items: center; gap: 14px; + margin-left: auto; flex: 0 0 auto; +} +/* DER PLATZ WIRD RESERVIERT, NICHT NACHTRÄGLICH GENOMMEN. + + Hier stand `:empty { display: none }` -- und das ist genau der Fehler, + den die Kachel darunter schon einmal gemacht hat: Die Glocke wird + erst nach der ersten Abfrage eingehängt, und bis dahin war der Platz + 0 px breit. In dem Moment, in dem sie erscheint, wächst die Zeile, + und alles darunter rutscht. + + Ein Sprung ist kein Schönheitsfehler, sondern der Grund, warum man + auf den falschen Knopf drückt: Man zielt auf etwas, und im Moment des + Klicks rutscht es weg. Gemessen von pruef-tempo-workspace. + + Also 46 px, immer -- ob die Glocke kommt oder nicht. Auf Geräten, auf + denen es sie gar nicht gibt (iPhone im Browser), bleibt ein leerer + Streifen neben der Uhr; das ist der Preis, und er ist niedriger als + ein springendes Bild. */ +.glocke-platz { width: 46px; height: 46px; flex: none; } + +/* ---------- Die Uhr -------------------------------------------------- + ROT x BABYBLAU (07.09.2026, zweiter Entwurf). + + Filipe: "die uhr rechts soll auch viel viel viel viel viel viel viel + spezieller noch sein und soll eine mischung von rot und babyblau + haben." + + WAS EINE UHR TEUER AUSSEHEN LAESST, IST NICHT DIE FARBE, SONDERN DAS + GEHAEUSE. Bei einer echten Uhr sieht man drei Dinge übereinander: + einen METALLRAND, der das Licht von oben fängt; eine VERTIEFTE + Scheibe darin; und ein GLAS darüber, das spiegelt. Genau diese drei + Schichten sind hier gebaut -- und sie sind der Grund, warum das Ding + jetzt wie ein Gegenstand aussieht und nicht wie ein Kreis mit Zahlen. + + Die Farben kommen aus dem Haus, nicht aus dem Farbkasten: Rot ist + Spicy Media, Babyblau ist DogFather. Dieselben zwei stehen im Kopf + der Anmeldekarte nebeneinander und bilden im Motiv den Neonrahmen. + + Beide Verlaeufe stehen im SVG (siehe start.html) und laufen + GEGENEINANDER -- der Stundenring rot -> blau, der Sekundenring blau + -> rot. Wo der eine warm ist, ist der andere kuehl; so bleiben sie + auseinanderzuhalten, wo sie sich ueberlagern. + -------------------------------------------------------------------- */ +.uhr { + position: relative; + width: 112px; height: 112px; flex: none; + display: grid; place-items: center; + border-radius: 50%; + /* SCHICHT 1: der Metallrand. Ein Verlauf von hell (oben links, wo das + Licht herkommt) nach dunkel und wieder etwas heller unten rechts -- + so sieht eine gedrehte Metallfassung aus. Als `padding` gebaut, + damit die Scheibe darin sitzt statt daneben. */ + padding: 3px; + background: + linear-gradient(150deg, + rgba(255, 255, 255, .20) 0%, + rgba(255, 120, 132, .16) 26%, + rgba(8, 12, 20, .9) 52%, + rgba(143, 216, 255, .16) 78%, + rgba(255, 255, 255, .10) 100%); + box-shadow: + /* Der Rand wirft nach aussen einen sehr weichen zweifarbigen + Schein -- rot oben links, blau unten rechts, wie der Rahmen im + Motiv. Beides weit unter der Schwelle, an der es blendet. */ + -3px -3px 22px -12px rgba(255, 95, 109, .55), + 3px 3px 22px -12px rgba(143, 216, 255, .55), + 0 12px 26px -14px rgba(0, 0, 0, .95); +} +/* SCHICHT 2: die vertiefte Scheibe. Eigene Ebene, damit der Metallrand + darum stehen bleibt. */ +.uhr::before { + content: ""; position: absolute; inset: 3px; border-radius: 50%; + background: + radial-gradient(circle at 50% 28%, rgba(255, 255, 255, .055), transparent 58%), + radial-gradient(circle at 50% 50%, #0c121e 58%, #05080e 100%); + box-shadow: + inset 0 2px 6px -2px rgba(0, 0, 0, .95), + inset 0 -1px 0 rgba(255, 255, 255, .05); +} +/* Die Skala: sechzig feine Striche, aus EINEM sich wiederholenden + Kegelverlauf. Sechzig Elemente im HTML waeren dieselbe Optik mit + sechzig Elementen mehr im Baum. Jeder fuenfte Strich ist kraeftiger + -- das ist der Unterschied zwischen "Punkte im Kreis" und "Skala". */ +.uhr__skala { + position: absolute; inset: 9px; border-radius: 50%; + background: + repeating-conic-gradient(from 0deg, + rgba(255, 145, 155, .55) 0deg 1deg, + transparent 1deg 30deg), + repeating-conic-gradient(from 0deg, + rgba(150, 186, 220, .26) 0deg 0.5deg, + transparent 0.5deg 6deg); + -webkit-mask: radial-gradient(circle, transparent 0 74%, #000 74% 86%, transparent 86%); + mask: radial-gradient(circle, transparent 0 74%, #000 74% 86%, transparent 86%); + opacity: .65; + pointer-events: none; z-index: 1; +} +/* SCHICHT 3: das Glas. Eine Spiegelung von oben links ueber die Kuppe, + dazu ein hauchfeiner heller Bogen am oberen Rand -- die Kante der + Scheibe. Ohne diesen Bogen wirkt die Uhr flach. */ +.uhr__glas { + position: absolute; inset: 3px; border-radius: 50%; + background: + linear-gradient(146deg, + rgba(255, 255, 255, .13) 0%, + rgba(255, 255, 255, .03) 20%, + transparent 44%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16); + pointer-events: none; z-index: 3; +} +.uhr__ring { + position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); + /* Bei Null soll der Ring OBEN beginnen, nicht rechts. */ + transform: rotate(-90deg); + z-index: 2; + overflow: visible; +} +.uhr__bahn, .uhr__minute, .uhr__zeiger { + fill: none; stroke-linecap: round; + stroke-dasharray: 276.46; +} +.uhr__bahn { stroke: rgba(150, 186, 220, .10); stroke-width: 4; } +/* Der Stundenstand: breit und gedeckt, im Verlauf Rot -> Babyblau. Er + aendert sich einmal pro Minute und darf nicht um Aufmerksamkeit + bitten -- deshalb kein Leuchten. */ +.uhr__minute { + stroke: url(#uhr-lauf); + stroke-width: 4; + stroke-dashoffset: 276.46; + opacity: .72; +} +/* Die Sekunde: schmaler, heller, gegenlaeufig gefaerbt und mit einem + Hauch Leuchten. */ +.uhr__zeiger { + stroke: url(#uhr-sekunde); stroke-width: 2; + stroke-dashoffset: 276.46; + filter: drop-shadow(0 0 3px rgba(168, 228, 255, .75)); +} +/* Der Kopf am Ende des Sekundenbogens -- ein Punkt, der mitlaeuft. + Das ist die eine Stelle, an der sich sichtbar etwas BEWEGT, und + deshalb die, an der das Auge merkt, dass die Seite lebt. */ +.uhr__kopf-schein { + fill: rgba(168, 228, 255, .28); + filter: blur(1.2px); +} +.uhr__kopf-kern { + fill: #eaf7ff; + filter: drop-shadow(0 0 4px rgba(168, 228, 255, .95)); +} +.uhr__mitte { + position: relative; z-index: 4; + display: grid; justify-items: center; gap: 2px; line-height: 1; +} +.uhr__zeit { + font-size: 1.46rem; font-weight: 700; letter-spacing: -.015em; + /* Gleiche Zifferbreite: Ohne sie zappelt die Anzeige bei jedem + Minutenwechsel, weil die 1 schmaler ist als die 8. */ + font-variant-numeric: tabular-nums; + color: #f2f8ff; + /* DIE MISCHUNG IM TEXT, ohne Verlaufsschrift: zwei Schatten, einer + rot von links, einer babyblau von rechts. Ein Verlauf IN der + Schrift (background-clip: text) wuerde im Windows-Kontrastmodus zu + unsichtbarer Schrift -- derselbe Grund, aus dem die Ueberschrift + daneben eine feste Farbe traegt. */ + text-shadow: + -6px 0 16px rgba(255, 95, 109, .38), + 6px 0 16px rgba(143, 216, 255, .42); +} +.uhr__sek { + /* .74rem = 11,84 px. Bei .68rem waren es 10,88 -- unter der Grenze von + 11,5, die pruef-css-klassen zieht, und sie hat recht: Diese Ziffern + stehen gesperrt und in Versalhöhe, dadurch wirken sie noch kleiner + als die Zahl sagt. */ + font-size: .74rem; font-weight: 700; + font-variant-numeric: tabular-nums; + letter-spacing: .18em; text-indent: .18em; + padding: 1px 6px 1px 7px; + border-radius: 999px; + color: #cfe9fb; + background: rgba(143, 216, 255, .10); + box-shadow: inset 0 0 0 1px rgba(143, 216, 255, .16); +} + +/* Die Glocke in der Kachel wird in start.css gestaltet und nicht hier. + + Grund (07.09.2026, gefunden von pruef-css-klassen): `glocke.js` laeuft + auf ALLEN siebzehn Seiten und setzt die Klasse `glocke--kachel`, + sobald es einen Platz dafuer gibt. Heute gibt es den nur hier -- aber + die Regel gehoert dorthin, wo das Skript sie brauchen KANN, nicht + dorthin, wo es sie heute zufaellig braucht. Sonst sieht die Glocke + auf der naechsten Seite, die einen Platz bekommt, falsch aus, und + niemand sucht den Fehler in der Aufteilung der Stildateien. + + Die Pruefung hat genau das gemeldet: "gestaltet, aber nicht auf ihrer + Seite -- fehlt auf 17 Seiten". */ + +@media (max-width: 620px) { + /* Auf dem Handy rückt die Gruppe unter den Gruß und die Uhr wird + kleiner -- 108 px neben einer Überschrift lassen für den Namen + keine Zeile mehr übrig. */ + .willkommen { padding: 18px 16px; } + .willkommen__rechts { margin-left: 0; width: 100%; justify-content: flex-start; } + .uhr { width: 88px; height: 88px; } + .uhr__zeit { font-size: 1.18rem; } +} + +@media (prefers-reduced-motion: reduce) { + /* Die Uhr springt ohnehin -- hier gibt es nichts abzuschalten. Die + Zeile steht trotzdem da, damit der nächste, der einen weichen + Zeiger einbaut, sie findet. */ + .uhr__zeiger, .uhr__minute { transition: none; } +} +@media (forced-colors: active) { + .uhr { background: Canvas; box-shadow: none; border: 1px solid CanvasText; } + .uhr__skala, .uhr__glas { display: none; } +} diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index c23092e8..08f83d7a 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -4205,261 +4205,20 @@ body[data-fremde-sicht="ja"]::after { .kachel__zeichen::after { display: none; } } -/* ===================================================================== - DIE BEGRÜSSUNGSKACHEL — mit Uhr und Glocke (07.09.2026) +/* Die Begrüßungskachel mit Uhr und Glocke steht seit dem 07.09.2026 in + einer EIGENEN Datei: assets/css/heim.css. - Wunsch Filipe: "ich will dass diese kacheln da auch übertrieben geil - gemacht wird, wirklich seeeeeehr hochwertig mit einer runden - digitalen uhr rechts in der kachel ... und der benachrichtigungs - button soll auch von der leiste in diese kachel gesetzt werden." + Grund war eine Grenze, die zu Recht anschlug: start.css lief über + 200 KB (pruef-struktur). Die Datei wird von DREIZEHN Seiten geladen -- + Uhr, Zifferblatt, Glockenmulde und Begrüßungskachel braucht aber + genau EINE. Zwölf Seiten haben also Regeln heruntergeladen, die sie + nie anwenden. - --------------------------------------------------------------------- - WARUM DIE UHR HIER RICHTIG IST UND NICHT NUR SCHÖN + Aufteilen war deshalb die richtige Antwort und nicht Kürzen: Es macht + start.css kleiner UND spart den anderen Seiten Bytes. Gemessen mit + grep, bevor es verschoben wurde: `class="uhr"`, `willkommen` und + `glocke-platz` kommen in keiner anderen HTML-Datei vor. */ - Eine Uhr ist die einzige Angabe auf dieser Seite, die sich ändert, - während man hinsieht. Genau das macht sie wertvoll: Sie sagt ohne ein - Wort, dass die Seite LEBT -- dass die Zahlen daneben von jetzt sind - und nicht von gestern Nacht. Eine Oberfläche, bei der man das nicht - weiß, prüft man ständig nach. - - Und sie steht neben dem Datum, das ohnehin dort steht. Zwei Angaben - zur selben Frage gehören zusammen; das Datum oben und die Uhrzeit in - der Fußzeile wären zwei halbe Antworten. - - --------------------------------------------------------------------- - ZWEI RINGE, NICHT EINER - - Der innere Ring füllt sich in einer Minute, der äußere in einer - Stunde. Das ist der Unterschied zwischen "eine Uhr, die tickt" und - "eine Uhr, an der man den Stand der Stunde sieht" -- man erkennt aus - dem Augenwinkel, ob die Stunde jung ist oder gleich vorbei. - - Der Sekundenring läuft NICHT weich. Ein weicher Zeiger sieht teurer - aus, kostet aber eine Bildberechnung pro Bild statt einer pro - Sekunde, und auf einer Seite, die stundenlang offen steht, ist das - der Unterschied zwischen einem warmen und einem kalten Rechner. - Der Sprung ist außerdem ehrlicher: Die Anzeige ist digital. - ===================================================================== */ - -/* Die Kachel selbst: dasselbe Material wie die Bereichskacheln, damit - nicht zwei Oberflächen nebeneinanderliegen. Sie hat KEINE eigene - Farbschiene -- sie gehört zu keiner Kategorie, sie ist der Anfang. */ -.willkommen { - position: relative; - padding: 22px 24px; - border: 1px solid var(--rand); - border-radius: 4px 20px 20px 4px; - background: - linear-gradient(90deg, - rgba(63, 189, 245, .10) 0%, - rgba(63, 189, 245, .03) 14%, - transparent 40%), - linear-gradient(163deg, - rgba(23, 31, 45, .95) 0%, - rgba(11, 15, 24, .95) 58%, - rgba(8, 11, 18, .95) 100%); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, .10), - inset 0 -1px 0 rgba(0, 0, 0, .50), - inset 2px 0 0 -1px color-mix(in srgb, var(--akzent) 34%, transparent), - 0 2px 5px -2px rgba(0, 0, 0, .55), - 0 16px 34px -20px rgba(0, 0, 0, .88); - overflow: hidden; -} -/* Die Leuchtschiene links -- dieselbe Sprache wie überall, hier im - Hausakzent statt in einer Kategoriefarbe. */ -.willkommen::before { - content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; - border-radius: 4px 0 0 4px; - background: linear-gradient(180deg, - color-mix(in srgb, var(--akzent) 55%, #fff) 0%, - var(--akzent) 26%, - color-mix(in srgb, var(--akzent) 50%, transparent) 100%); - box-shadow: - 0 0 9px 0 color-mix(in srgb, var(--akzent) 55%, transparent), - 3px 0 20px -3px color-mix(in srgb, var(--akzent) 42%, transparent); - opacity: .9; - z-index: 2; -} -/* Körnung und ein fester Glanz. Kein wandernder wie bei den Kacheln: - Diese Fläche wird nicht angeklickt, also gibt es nichts zu quittieren. */ -.willkommen::after { - content: ""; position: absolute; inset: 0; pointer-events: none; - border-radius: inherit; - background: - linear-gradient(104deg, - transparent 26%, - rgba(255, 255, 255, .05) 40%, - rgba(255, 255, 255, .085) 47%, - rgba(255, 255, 255, .03) 55%, - transparent 70%); - /* KEINE Körnung auf DIESER Ebene (07.09.2026, gemessen). - - Sie stand hier als zweite Schicht und hat die Deckkraft der - Spiegelung geerbt -- 50 % eines mittelgrauen Rauschens heben jeden - Bildpunkt um rund 30 Stufen an. Gemessen: rgb(56,44,58) statt der - berechneten rgb(20,26,38), und über einer hellen Stelle des - Motivs rgb(85,83,81). Die Karte sah dadurch durchsichtig aus, - obwohl sie zu 95 % deckt. - - Derselbe Fehler wie an der Anmeldekarte heute Nachmittag, und - genau derselbe Grund: Körnung gehört auf eine EIGENE Ebene mit - `mix-blend-mode: overlay` und 3 bis 4 Prozent -- so wie bei den - Kacheln der Startseite. Auf einer geteilten Ebene ist sie kein - Material, sondern ein grauer Schleier. */ - opacity: .5; -} -.willkommen > * { position: relative; z-index: 1; } - -.willkommen__reihe { - display: flex; align-items: center; gap: 16px; - flex-wrap: wrap; -} -.willkommen__text { min-width: 0; flex: 1 1 260px; } - -/* Rechts: Glocke neben der Uhr, beide rechtsbündig. `margin-left: auto` - statt `justify-content: space-between` am Elternteil -- so bleibt die - Gruppe auch dann rechts, wenn der Text davor kurz ist. */ -.willkommen__rechts { - display: flex; align-items: center; gap: 14px; - margin-left: auto; flex: 0 0 auto; -} -/* DER PLATZ WIRD RESERVIERT, NICHT NACHTRÄGLICH GENOMMEN. - - Hier stand `:empty { display: none }` -- und das ist genau der Fehler, - den die Kachel darunter schon einmal gemacht hat: Die Glocke wird - erst nach der ersten Abfrage eingehängt, und bis dahin war der Platz - 0 px breit. In dem Moment, in dem sie erscheint, wächst die Zeile, - und alles darunter rutscht. - - Ein Sprung ist kein Schönheitsfehler, sondern der Grund, warum man - auf den falschen Knopf drückt: Man zielt auf etwas, und im Moment des - Klicks rutscht es weg. Gemessen von pruef-tempo-workspace. - - Also 46 px, immer -- ob die Glocke kommt oder nicht. Auf Geräten, auf - denen es sie gar nicht gibt (iPhone im Browser), bleibt ein leerer - Streifen neben der Uhr; das ist der Preis, und er ist niedriger als - ein springendes Bild. */ -.glocke-platz { width: 46px; height: 46px; flex: none; } - -/* ---------- Die Uhr -------------------------------------------------- */ -.uhr { - position: relative; - width: 108px; height: 108px; flex: none; - display: grid; place-items: center; - border-radius: 50%; - /* Das Gehäuse: außen ein Lichtsaum, innen eine dunkle Scheibe. */ - background: - radial-gradient(circle at 50% 34%, rgba(255, 255, 255, .05), transparent 62%), - radial-gradient(circle at 50% 50%, #0d131f 62%, #070a11 100%); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, .12), - inset 0 -2px 6px -3px rgba(0, 0, 0, .9), - 0 0 0 1px rgba(150, 186, 220, .10), - 0 10px 24px -14px rgba(0, 0, 0, .9); -} -/* Die Skala: sechzig feine Striche, aus EINEM sich wiederholenden - Kegelverlauf. Sechzig Elemente im HTML wären dieselbe Optik mit - sechzig Elementen mehr im Baum. */ -.uhr__skala { - position: absolute; inset: 7px; border-radius: 50%; - background: repeating-conic-gradient(from 0deg, - rgba(150, 186, 220, .30) 0deg 0.5deg, - transparent 0.5deg 6deg); - /* Nur ein schmaler Ring davon ist zu sehen -- der Rest wird - ausgestanzt. */ - -webkit-mask: radial-gradient(circle, transparent 0 76%, #000 76% 84%, transparent 84%); - mask: radial-gradient(circle, transparent 0 76%, #000 76% 84%, transparent 84%); - opacity: .5; - pointer-events: none; -} -/* Die Glasscheibe: eine Spiegelung von oben links über der Kuppe. */ -.uhr__glas { - position: absolute; inset: 0; border-radius: 50%; - background: linear-gradient(148deg, - rgba(255, 255, 255, .10) 0%, - rgba(255, 255, 255, .025) 22%, - transparent 46%); - pointer-events: none; -} -.uhr__ring { - position: absolute; inset: 0; width: 100%; height: 100%; - /* Bei Null soll der Ring OBEN beginnen, nicht rechts. */ - transform: rotate(-90deg); -} -.uhr__bahn, .uhr__minute, .uhr__zeiger { - fill: none; stroke-linecap: round; - stroke-dasharray: 276.46; -} -.uhr__bahn { stroke: rgba(150, 186, 220, .11); stroke-width: 3.5; } -/* Der Stundenstand: gedeckt, breit, ruhig -- er ändert sich einmal pro - Minute und darf nicht um Aufmerksamkeit bitten. */ -.uhr__minute { - stroke: color-mix(in srgb, var(--violett) 62%, transparent); - stroke-width: 3.5; - stroke-dashoffset: 276.46; -} -/* Die Sekunde: schmaler, heller, mit einem Hauch Leuchten. */ -.uhr__zeiger { - stroke: var(--akzent); stroke-width: 2.2; - stroke-dashoffset: 276.46; - filter: drop-shadow(0 0 3px color-mix(in srgb, var(--akzent) 70%, transparent)); -} -.uhr__mitte { display: grid; justify-items: center; gap: 1px; line-height: 1; } -.uhr__zeit { - font-size: 1.42rem; font-weight: 700; letter-spacing: -.01em; - /* Gleiche Zifferbreite: Ohne sie zappelt die Anzeige bei jedem - Minutenwechsel, weil die 1 schmaler ist als die 8. */ - font-variant-numeric: tabular-nums; - color: #eaf2fb; - text-shadow: 0 0 18px rgba(63, 189, 245, .28); -} -.uhr__sek { - font-size: .72rem; font-weight: 600; - font-variant-numeric: tabular-nums; - letter-spacing: .16em; text-indent: .16em; - color: color-mix(in srgb, var(--akzent) 55%, #ffffff); -} - -/* ---------- Die Glocke, wenn sie in der Kachel sitzt ------------------ */ -/* In der Leiste ist sie ein Knopf unter Knöpfen. Hier steht sie allein - neben der Uhr und wird deshalb rund -- sie nimmt die Form ihrer - Nachbarin auf, statt als Rechteck danebenzustehen. */ -.glocke--kachel { - width: 46px; height: 46px; padding: 0; - border-radius: 50%; - justify-content: center; -} -.glocke--kachel .glocke__text { - /* Der Text bleibt für Vorleseprogramme da, verschwindet aber vom - Bildschirm: Neben einer Uhr ist Platz für ein Zeichen, nicht für - eine Beschriftung. */ - position: absolute; - width: 1px; height: 1px; margin: -1px; padding: 0; - overflow: hidden; clip-path: inset(50%); white-space: nowrap; -} -.glocke--kachel .glocke__zeichen { width: 19px; height: 19px; } - -@media (max-width: 620px) { - /* Auf dem Handy rückt die Gruppe unter den Gruß und die Uhr wird - kleiner -- 108 px neben einer Überschrift lassen für den Namen - keine Zeile mehr übrig. */ - .willkommen { padding: 18px 16px; } - .willkommen__rechts { margin-left: 0; width: 100%; justify-content: flex-start; } - .uhr { width: 88px; height: 88px; } - .uhr__zeit { font-size: 1.18rem; } -} - -@media (prefers-reduced-motion: reduce) { - /* Die Uhr springt ohnehin -- hier gibt es nichts abzuschalten. Die - Zeile steht trotzdem da, damit der nächste, der einen weichen - Zeiger einbaut, sie findet. */ - .uhr__zeiger, .uhr__minute { transition: none; } -} -@media (forced-colors: active) { - .uhr { background: Canvas; box-shadow: none; border: 1px solid CanvasText; } - .uhr__skala, .uhr__glas { display: none; } -} /* ===================================================================== @@ -4714,3 +4473,63 @@ body[data-fremde-sicht="ja"]::after { .kachel__unter { font-size: .72rem; } .kachel__wasserzeichen { width: 96px; height: 96px; } } + +/* ---------- Die Glocke, wenn sie in der Kachel sitzt ------------------ */ +/* In der Leiste ist sie ein Knopf unter Knöpfen. Hier steht sie allein + neben der Uhr und wird deshalb rund -- sie nimmt die Form ihrer + Nachbarin auf, statt als Rechteck danebenzustehen. */ +/* Sie bekommt dieselbe Bauart wie die Uhr daneben: ein Metallrand + aussen, eine vertiefte Scheibe darin. Zwei runde Dinge nebeneinander, + die aus verschiedenem Material sind, sehen zusammengesucht aus -- + dasselbe Material macht daraus ein Paar. Kleiner als die Uhr, weil + sie die Nebensache ist. */ +.glocke--kachel { + position: relative; + width: 48px; height: 48px; padding: 3px; + border-radius: 50%; + justify-content: center; + border: 0; + background: + linear-gradient(150deg, + rgba(255, 255, 255, .16) 0%, + rgba(255, 120, 132, .13) 28%, + rgba(8, 12, 20, .9) 54%, + rgba(143, 216, 255, .13) 80%, + rgba(255, 255, 255, .08) 100%); + box-shadow: + -2px -2px 16px -11px rgba(255, 95, 109, .5), + 2px 2px 16px -11px rgba(143, 216, 255, .5), + 0 8px 20px -12px rgba(0, 0, 0, .9); + transition: transform var(--tempo), box-shadow var(--tempo); +} +.glocke--kachel::before { + content: ""; position: absolute; inset: 3px; border-radius: 50%; + background: + radial-gradient(circle at 50% 28%, rgba(255, 255, 255, .05), transparent 58%), + radial-gradient(circle at 50% 50%, #0c121e 58%, #05080e 100%); + box-shadow: + inset 0 2px 5px -2px rgba(0, 0, 0, .95), + inset 0 1px 0 rgba(255, 255, 255, .10); +} +/* Beim Zeigen hebt sie sich einen Pixel und ihr Schein wird kraeftiger + -- dieselbe Sprache wie ueberall: naeherkommen statt aufleuchten. */ +.glocke--kachel:hover { + transform: translateY(-1px); + box-shadow: + -3px -3px 20px -9px rgba(255, 95, 109, .7), + 3px 3px 20px -9px rgba(143, 216, 255, .7), + 0 10px 24px -12px rgba(0, 0, 0, .95); +} +.glocke--kachel .glocke__zeichen { position: relative; z-index: 1; } +@media (prefers-reduced-motion: reduce) { + .glocke--kachel, .glocke--kachel:hover { transform: none; } +} +.glocke--kachel .glocke__text { + /* Der Text bleibt für Vorleseprogramme da, verschwindet aber vom + Bildschirm: Neben einer Uhr ist Platz für ein Zeichen, nicht für + eine Beschriftung. */ + position: absolute; + width: 1px; height: 1px; margin: -1px; padding: 0; + overflow: hidden; clip-path: inset(50%); white-space: nowrap; +} +.glocke--kachel .glocke__zeichen { width: 20px; height: 20px; } diff --git a/workspace/assets/js/start.js b/workspace/assets/js/start.js index 295f218f..ca752080 100644 --- a/workspace/assets/js/start.js +++ b/workspace/assets/js/start.js @@ -301,6 +301,7 @@ if (!zeit || !zeiger) return; const p = (n) => String(n).padStart(2, '0'); + const kopf = $('uhr-kopf'); const takt = () => { const j = new Date(); zeit.textContent = `${p(j.getHours())}:${p(j.getMinutes())}`; @@ -309,6 +310,12 @@ SVG im Stil um 90 Grad zurückgedreht ist. */ zeiger.style.strokeDashoffset = String(UHR_UMFANG * (1 - j.getSeconds() / 60)); if (minute) minute.style.strokeDashoffset = String(UHR_UMFANG * (1 - j.getMinutes() / 60)); + /* Der leuchtende Kopf sitzt am ENDE des Sekundenbogens. Er wird um + die Mitte gedreht -- 6 Grad je Sekunde. Die Drehung passiert im + SVG-Koordinatensystem (0..100), NICHT im Stil: Dort liegt schon + die -90-Grad-Drehung des ganzen Rings, und zwei Drehungen an + zwei Stellen laufen beim nächsten Umbau auseinander. */ + if (kopf) kopf.setAttribute('transform', `rotate(${j.getSeconds() * 6} 50 50)`); /* Der volle Zeitpunkt für Vorleseprogramme -- sichtbar steht dort nur die Uhrzeit, vorgelesen wird der ganze Satz. */ const uhr = $('uhr'); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 8b47e7cd..8ecccd07 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -9,9 +9,9 @@ - - - + + +
@@ -211,10 +211,10 @@ - - - - - + + + + +