Drei Ringe, klappbare Gruppen -- und die Calls-Seite endlich wirklich umgebaut

DU HATTEST RECHT (Bildschirmfoto 3). Beim letzten Mal habe ich an der
Calls-Seite nur das MATERIAL der Karten getauscht und die Aufstellung
gelassen. Drei Gruppen untereinander, und weil "Steht an" 27 Eintraege
hat, lagen die beiden anderen ausserhalb des Bildes -- man SAH die
Aenderung gar nicht, weil man nie so weit kam. Das war Lackieren, kein
Umbauen.

JETZT EIN BRETT AUS ZWEI SPALTEN. Die Gruppen sind eigenstaendige Tafeln
in einem Raster; "Protokoll fehlt" (kurz, dringend) und "Festgehalten"
(Archiv) liegen NEBEN der langen Liste statt darunter. `align-items:
start` ist dabei der Punkt: Ohne ihn waeren alle Tafeln so hoch wie die
hoechste, und neben der langen Liste stuenden zwei fast leere Kaesten.
Welche Tafel wo landet, entscheidet die Breite und nicht das Skript --
eine festgeschriebene Spalte waere eine Zahl, die beim naechsten Fenster
falsch ist.

Jede Tafel klappt zu, und der Zustand wird gemerkt. Vorgabe: "Festgehalten"
ist zu -- es ist das Archiv; wer die Seite oeffnet, will wissen, was
ansteht.

DIE GRUPPEN AUF DER STARTSEITE genauso. Der Kopf IST der Schalter, nicht
ein Dreieck daneben: Die ganze Zeile ist ein Ziel von 40 Pixeln statt
eines von vierzehn. <button> statt <div>, damit Tastaturbedienung und
Ansage nicht mit tabindex und role nachgebaut werden muessen. Gemerkt
wird je Gruppe UND je angesehener Rolle -- DogFather, der sich einen
Creator ansieht, hat dort eine andere Aufteilung im Kopf.

DIE UHR: drei Ringe statt zwei. Aussen die Stunde in Schwarzsilber, in
der Mitte die Minute in Blau, innen die Sekunde in Rot -- von aussen nach
innen immer schneller, so liest man eine Uhr ohne nachzudenken.

UND SIE IST SCHARF. Kein blur(), kein drop-shadow mehr auf den Boegen --
an der alten lag beides drauf, und der Vorwurf stimmte. Der Glanz kommt
jetzt aus dem VERLAUF: Echtes Metall glaenzt nicht, weil es leuchtet,
sondern weil es das Licht abwechselnd hell und dunkel zurueckwirft. Fuenf
Stopps statt zwei -- ein zweifarbiger Verlauf waere ein Farbverlauf,
erst der Wechsel ist Metall. Dazu `shape-rendering="geometricPrecision"`
(sonst rastert der Browser duenne Boegen grober) und Kappen auf `butt`
statt `round`: Eine runde Kappe steht ueber das Ende hinaus, bei null
Sekunden saehe man trotzdem einen Punkt.

Minute und Stunde laufen WEICH mit: die Minute bekommt die Sekunden
anteilig, die Stunde die Minuten. Ein Minutenring, der einmal pro Minute
springt, sieht aus wie eine haengende Anzeige.

Gruen: css-klassen (15), call-kategorien (17), start-ansicht (136),
handy (59), breiten (23), buehne (38), struktur (32).

NOCH OFFEN: der neue Kachelstil fuer die ganze Website und die Rolle
"Spicy Media".

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 04:39:32 +02:00
co-authored by Claude Opus 5
parent 49bd4a7cec
commit 4bb5afc942
23 changed files with 642 additions and 251 deletions
+99 -19
View File
@@ -139,13 +139,51 @@
if (!meine.length) continue;
const abschnitt = el('section', 'gruppe');
const kopf = el('div', 'gruppe__kopf');
/* Manche Gruppen heißen für einen Creator anders -- siehe GRUPPEN. */
const istCreator = zeigt.rolle === 'creator';
/* ===============================================================
JEDE GRUPPE LAESST SICH ZUKLAPPEN (07.09.2026)
Wunsch Filipe: "diese kategorien soll man auch zu und aufmachen
koennen so wie es einem lieb ist gerade."
DER KOPF IST DER SCHALTER, nicht ein Knopf daneben. Eine
Ueberschrift, neben der ein kleines Dreieck sitzt, laedt zum
Zielen ein; eine Ueberschrift, die SELBST der Schalter ist,
trifft man immer. Deshalb <button> statt <div> -- damit kommt
die Tastaturbedienung und die Ansage fuer Vorleseprogramme
geschenkt, statt sie mit tabindex und role nachzubauen.
DER ZUSTAND WIRD GEMERKT. "So wie es einem lieb ist" heisst:
Er muss den naechsten Aufruf ueberleben. Gespeichert wird je
Gruppe und je ANGESEHENER Rolle -- DogFather, der sich einen
Creator ansieht, hat dort eine andere Aufteilung im Kopf als
auf seiner eigenen Seite.
Faellt localStorage aus (privates Fenster, volle Ablage), ist
das kein Fehler: Dann steht eben alles offen. Ein Merker, der
die Seite mitreisst, waere schlimmer als ein vergessener. */
const kopf = el('button', 'gruppe__kopf');
kopf.type = 'button';
const schluessel = `gruppe:${zeigt.rolle}:${gruppe.name}`;
let zu = false;
try { zu = localStorage.getItem(schluessel) === 'zu'; } catch { zu = false; }
kopf.append(el('h2', 'gruppe__name',
(istCreator && gruppe.nameCreator) || gruppe.name));
kopf.append(el('span', 'gruppe__unter',
(istCreator && gruppe.unterCreator) || gruppe.unter));
/* Die Zahl sagt, was hinter einer zugeklappten Gruppe steckt --
ohne sie klappt man drei Gruppen auf, um zu suchen. */
kopf.append(el('span', 'gruppe__zahl', String(meine.length)));
const pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
pfeil.setAttribute('class', 'gruppe__pfeil');
pfeil.setAttribute('viewBox', '0 0 24 24');
pfeil.setAttribute('aria-hidden', 'true');
pfeil.innerHTML = '<path d="m6 9.5 6 6 6-6" fill="none" stroke="currentColor"'
+ ' stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>';
kopf.append(pfeil);
abschnitt.append(kopf);
const liste = el('ul', 'kacheln');
@@ -216,6 +254,23 @@
liste.append(li);
}
abschnitt.append(liste);
/* Das Aufklappen laeuft ueber ein Merkmal am Abschnitt, nicht ueber
`hidden` an der Liste: So kann das CSS den Uebergang zeichnen
und den Pfeil drehen, ohne dass das Skript etwas davon wissen
muss. */
const stand = (istZu) => {
abschnitt.dataset.zu = istZu ? 'ja' : 'nein';
kopf.setAttribute('aria-expanded', istZu ? 'false' : 'true');
liste.hidden = istZu;
};
stand(zu);
kopf.addEventListener('click', () => {
zu = !zu;
stand(zu);
try { localStorage.setItem(schluessel, zu ? 'zu' : 'auf'); } catch { /* egal */ }
});
ziel.append(abschnitt);
}
/* KEIN eigener Aufruf mehr: kopf.js stellt das Licht einmal fuer das
@@ -294,32 +349,57 @@
Sekunden weiter. Stattdessen wird jedes Mal bis zur NÄCHSTEN vollen
Sekunde gewartet -- die Anzeige bleibt für immer im Takt der
Systemuhr. */
const UHR_UMFANG = 276.46;
/* Drei Umfaenge, ausgerechnet und nicht als Formel im Stil: Aendert
jemand einen Radius im HTML, faellt der Ring sofort auf, statt sich
stillschweigend zu verrechnen. U = 2 * pi * r */
const UMFANG = { stunde: 285.88, minute: 241.90, sekunde: 197.92 }; // r = 45.5 / 38.5 / 31.5
function uhrStarten() {
const zeit = $('uhr-zeit'), sek = $('uhr-sek');
const zeiger = $('uhr-zeiger'), minute = $('uhr-minute');
if (!zeit || !zeiger) return;
const ringe = {
stunde: $('uhr-stunde'), minute: $('uhr-minute'), sekunde: $('uhr-sekunde'),
};
if (!zeit || !ringe.sekunde) return;
const p = (n) => String(n).padStart(2, '0');
const kopf = $('uhr-kopf');
/* Voll = kein Versatz. Die Ringe wachsen im Uhrzeigersinn, weil das
SVG im Stil um 90 Grad zurueckgedreht ist. */
const setze = (welcher, anteil) => {
const el = ringe[welcher];
if (el) el.style.strokeDashoffset = String(UMFANG[welcher] * (1 - anteil));
};
const takt = () => {
const j = new Date();
zeit.textContent = `${p(j.getHours())}:${p(j.getMinutes())}`;
sek.textContent = p(j.getSeconds());
/* Voll = kein Versatz. Der Ring wächst im Uhrzeigersinn, weil das
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
const std = j.getHours(), min = j.getMinutes(), sk = j.getSeconds();
zeit.textContent = `${p(std)}:${p(min)}`;
sek.textContent = p(sk);
/* JEDER RING LAEUFT WEICH IN SEINER EIGENEN EINHEIT.
Die Sekunde springt (sie ist die Anzeige selbst), Minute und
Stunde nicht: Ein Minutenring, der einmal pro Minute einen
Sprung macht, sieht aus wie eine haengende Anzeige. Er bekommt
deshalb die Sekunden anteilig dazu -- und die Stunde die
Minuten. Genau das hat Filipe verlangt ("die minuten sollen
auch den minuten entsprechen mitlaufen").
Die Stunde ueber ZWOELF, nicht ueber 24: Ein Zifferblatt hat
zwoelf Stunden, und der Ring soll sich wie eines lesen. */
setze('sekunde', sk / 60);
setze('minute', (min + sk / 60) / 60);
setze('stunde', ((std % 12) + min / 60) / 12);
/* Der volle Zeitpunkt fuer Vorleseprogramme -- sichtbar steht dort
nur die Uhrzeit, vorgelesen wird der ganze Satz. */
const uhr = $('uhr');
if (uhr) uhr.setAttribute('aria-label', `${p(j.getHours())}:${p(j.getMinutes())} Uhr`);
if (uhr) uhr.setAttribute('aria-label', `${p(std)}:${p(min)} Uhr`);
/* KEIN setInterval(1000). Ein fester Takt laeuft mit der Zeit aus
dem Tritt (jeder Durchlauf kostet Millisekunden) und
ueberspringt dann gelegentlich eine Sekunde. Gewartet wird bis
zur naechsten VOLLEN Sekunde -- die Anzeige bleibt fuer immer im
Takt der Systemuhr. */
setTimeout(takt, 1000 - (Date.now() % 1000) + 8);
};
takt();