Gegliedert nach Alter: sieben Stufen, klappbar

Filipe: "ich will da unterkategorien die man auf und zu klappen kann,
so wie heute gestern, diese woche, diesen monate, über ein monat,
über 6 monate, über ein jahr."

DAS IST DIE BESSERE ANTWORT AUF DIE LANGEN LISTEN als die Zahl von
gestern. Zwoelf ist willkuerlich; eine Gliederung nach Alter
beantwortet die Frage, die man an so eine Liste wirklich stellt --
was ist neu?

DIE ERSTEN VIER STUFEN SIND KALENDERBEZOGEN, die letzten drei
altersbezogen:

  Heute, Gestern          der Kalendertag
  Diese Woche             seit MONTAG -- am Dienstag ist der Freitag
                          davor nicht "diese Woche"
  Dieser Monat            seit dem Ersten
  Ueber einen Monat       der Abstand, weil "Mai" niemandem sagt,
  Ueber sechs Monate      wie lange das her ist
  Ueber ein Jahr

Das klingt uneinheitlich und ist genau richtig.

DIE NEUESTE GRUPPE STEHT OFFEN, die aelteren zu. Wer die Seite
aufmacht, will sehen was neu ist -- und alles Aeltere ist sichtbar
VORHANDEN, ohne den Weg zu verstellen. Die eigene Wahl gewinnt und
wird gemerkt, je Gruppe und je Spalte.

EINE LEERE STUFE ERSCHEINT NICHT. Sieben leere Ueberschriften waeren
schlimmer als eine lange Liste. Und gegliedert wird erst UEBER zwoelf
Eintraegen -- darunter waeren es sieben Ueberschriften fuer fuenf
Karten.

Die Zwoelfergrenze von gestern bleibt, gilt aber jetzt JE GRUPPE: Auch
"Ueber ein Jahr" kann dreihundert Eintraege haben, und dann hilft die
Gliederung allein nicht.

GERECHNET WIRD IN ORTSZEIT (window.heuteLokal), nicht in UTC. Ein
Eintrag von gestern 23:40 waere in UTC schon heute und stuende unter
"Heute", waehrend das Datum daneben gestern sagt.

ZWEI DINGE NACHGESEHEN STATT GERATEN:

  abschnittKlappbar nimmt als vierten Wert ein BOOLEAN (zuVorgabe),
  kein Objekt. Ich hatte "{ offen: ... }" angenommen; beim Nachsehen
  in kopf.js stand etwas anderes da.

  Jede Gruppe braucht einen eigenen Merker. Ohne ihn teilten sich
  "Heute bei DogFather" und "Heute bei HasiDog" denselben Zustand,
  und wer die eine zuklappt, klappt die andere mit.

Gemessen mit Eintraegen ueber alle sieben Stufen: 10 Messungen,
0 Befunde -- Reihenfolge, Zahlen, offen/zu, Klapp-Pfeil, kein
seitlicher Ueberstand, und ein Tipp macht eine zugeklappte Gruppe
wirklich auf. pruef-highlights 31, pruef-galerie und
pruef-css-klassen in Ordnung.

NOCH OFFEN -- screen1: "Screenshots oder Kurzschnitte reinposten"
braucht eine Route, die eine Datei an einen EINTRAG haengt. Die gibt
es heute nicht: workspace-video.js legt Coverbilder beim Einlesen
eines TikTok-Links an, workspace-dateien.js laedt hoch, aber ohne
eintrag_id. Das ist ein eigener Brocken und kein Nebenbei.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-24 00:06:59 +02:00
co-authored by Claude Opus 5
parent 2bea133feb
commit 7f732cc403
41 changed files with 778 additions and 591 deletions
+60
View File
@@ -2096,3 +2096,63 @@ body[data-ton] .kopf-zeile::before {
font-size: .82rem; line-height: 1.5;
color: var(--text-leise, #9fb3c8);
}
/* =====================================================================
GLIEDERT NACH ALTER (24.09.2026)
=====================================================================
Filipe: "ich will da unterkategorien die man auf und zu klappen
kann, so wie heute gestern, diese woche, diesen monate ..."
Der Kopf einer Gruppe ist KLEINER als der Kopf der Spalte darueber
-- er gliedert innerhalb, er teilt nicht neu ein. Gleich grosse
Koepfe auf zwei Ebenen lesen sich als eine, und dann weiss man
nicht mehr, was wozu gehoert.
Die Zahl daneben sagt, wie viel darin liegt. Zugeklappt ist sie die
einzige Auskunft, die die Gruppe noch gibt -- ohne sie waere eine
zugeklappte Zeile eine Ueberschrift ohne Inhalt, und niemand macht
sie je auf. */
.zeitgruppe__kopf {
grid-column: 1 / -1;
display: flex; align-items: center; gap: 9px;
width: 100%; margin: 12px 0 6px;
padding: 7px 10px;
font: inherit; font-size: .78rem; font-weight: 600;
letter-spacing: .04em; text-transform: uppercase;
color: var(--text-leise, #9fb3c8);
text-align: left; cursor: pointer;
background: color-mix(in srgb, var(--tinte) 88%, #ffffff 12%);
border: 1px solid color-mix(in srgb, var(--rand) 70%, transparent);
border-radius: 9px;
}
.zeitgruppe__kopf:hover, .zeitgruppe__kopf:focus-visible {
color: var(--text);
border-color: color-mix(in srgb, var(--akzent) 45%, transparent);
}
/* Die erste Gruppe braucht oben keinen Abstand -- darueber steht
schon der Kopf der Spalte. */
.zeitgruppe__kopf:first-child { margin-top: 2px; }
.zeitgruppe__wort { flex: 1 1 auto; min-width: 0; }
.zeitgruppe__zahl {
flex: none;
padding: 1px 8px;
font-size: .78rem; font-variant-numeric: tabular-nums;
color: var(--text);
background: color-mix(in srgb, var(--tinte) 70%, #ffffff 30%);
border-radius: 999px;
}
/* Der Pfeil sitzt rechts und dreht sich mit -- dieselbe Hilfe wie
ueberall (klappPfeil aus kopf.js). */
.zeitgruppe__kopf .klapp-pfeil { flex: none; }
/* Der Inhalt einer Gruppe ist wieder eine Galerie, genau wie die
Spalte es war -- die Kacheln sollen gleich aussehen, egal ob
gegliedert wird oder nicht. */
.zeitgruppe__inhalt[data-galerie="ja"] {
grid-column: 1 / -1;
display: grid; gap: 10px;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
+131 -4
View File
@@ -2079,7 +2079,134 @@
(e.datum DESC, e.id DESC). "Die ersten zwoelf" sind also die
neuesten zwoelf, nicht die erstbesten. */
const AUF_EINMAL = 12;
function kartenLegen(kasten, fach) {
/* ==== NACH ALTER GEGLIEDERT (24.09.2026) ======================
Filipe: "ich will da unterkategorien die man auf und zu
klappen kann, so wie heute gestern, diese woche, diesen
monate, über ein monat, über 6 monate, über ein jahr."
Das ist die bessere Antwort auf die langen Listen als die
Zahl von gestern. Zwoelf ist willkuerlich; eine Gliederung
nach Alter beantwortet die Frage, die man an so eine Liste
wirklich stellt -- "was ist neu?". Und was alt ist, steht
zugeklappt da: sichtbar, dass es existiert, ohne den Weg zu
verstellen.
DIE ERSTEN VIER STUFEN SIND KALENDERBEZOGEN, die letzten drei
altersbezogen. Das klingt uneinheitlich und ist genau richtig:
"Diese Woche" heisst seit Montag, nicht "in den letzten
sieben Tagen" -- am Dienstag ist der Freitag davor nicht
"diese Woche". Fuer alles aeltere zaehlt dagegen der Abstand,
weil "Mai" niemandem sagt, wie lange das her ist.
EINE STUFE OHNE EINTRAEGE ERSCHEINT NICHT. Sieben leere
Ueberschriften waeren schlimmer als eine lange Liste. */
const ZEITSTUFEN = [
{ schluessel: 'heute', name: 'Heute' },
{ schluessel: 'gestern', name: 'Gestern' },
{ schluessel: 'woche', name: 'Diese Woche' },
{ schluessel: 'monat', name: 'Dieser Monat' },
{ schluessel: 'aelter', name: 'Über einen Monat' },
{ schluessel: 'halb', name: 'Über sechs Monate' },
{ schluessel: 'jahr', name: 'Über ein Jahr' },
];
/** In welche Stufe faellt dieser Eintrag?
*
* GERECHNET WIRD IN ORTSZEIT, nicht in UTC. Ein Eintrag von
* gestern 23:40 waere in UTC schon heute -- und stuende dann
* unter "Heute", waehrend das Datum daneben gestern sagt.
* window.heuteLokal() ist die Stelle im Haus, die das richtig
* macht. */
function stufeVon(e) {
const roh = String(e?.datum || '').slice(0, 10);
if (!roh) return 'jahr';
const heute = window.heuteLokal
? window.heuteLokal() : new Date().toISOString().slice(0, 10);
if (roh === heute) return 'heute';
const tag = new Date(heute + 'T00:00:00');
const dann = new Date(roh + 'T00:00:00');
if (Number.isNaN(dann.getTime())) return 'jahr';
const gestern = new Date(tag); gestern.setDate(tag.getDate() - 1);
if (dann.getTime() === gestern.getTime()) return 'gestern';
/* Montag als Wochenanfang -- getDay() liefert Sonntag = 0, und
in Deutschland steht der am Ende. Dieselbe Rechnung wie in
zeitAb() weiter oben. */
const montag = new Date(tag);
montag.setDate(tag.getDate() - ((tag.getDay() + 6) % 7));
if (dann >= montag) return 'woche';
const ersterDesMonats = new Date(tag.getFullYear(), tag.getMonth(), 1);
if (dann >= ersterDesMonats) return 'monat';
const halbesJahr = new Date(tag); halbesJahr.setMonth(tag.getMonth() - 6);
const einJahr = new Date(tag); einJahr.setFullYear(tag.getFullYear() - 1);
if (dann < einJahr) return 'jahr';
if (dann < halbesJahr) return 'halb';
return 'aelter';
}
/** Die Karten eines Fachs in einen Kasten legen -- gegliedert
* nach Alter, jede Gruppe auf- und zuklappbar.
*
* "merker" unterscheidet die Gruppen zweier Spalten: Ohne ihn
* teilten sich "Heute bei DogFather" und "Heute bei HasiDog"
* denselben Zustand, und wer die eine zuklappt, klappt die
* andere mit. */
function kartenLegen(kasten, fach, merker) {
/* Bei wenigen Eintraegen waere eine Gliederung Aufwand ohne
Nutzen: sieben Ueberschriften fuer fuenf Karten. Erst wenn
es sich lohnt, wird gegliedert. */
if (fach.length <= AUF_EINMAL) { stapelLegen(kasten, fach); return; }
const faecher = new Map(ZEITSTUFEN.map((s) => [s.schluessel, []]));
for (const e of fach) {
const s = stufeVon(e);
(faecher.get(s) || faecher.get('jahr')).push(e);
}
let erste = true;
for (const stufe of ZEITSTUFEN) {
const drin = faecher.get(stufe.schluessel) || [];
if (!drin.length) continue; /* leere Stufe: gar nicht erst */
const kopf = el('button', 'zeitgruppe__kopf');
kopf.type = 'button';
kopf.append(el('span', 'zeitgruppe__wort', stufe.name));
kopf.append(el('span', 'zeitgruppe__zahl', String(drin.length)));
if (window.klappPfeil) kopf.append(window.klappPfeil());
kasten.append(kopf);
const innen = el('div', 'zeitgruppe__inhalt');
innen.dataset.galerie = 'ja';
stapelLegen(innen, drin);
kasten.append(innen);
/* DIE NEUESTE GRUPPE STEHT OFFEN, die uebrigen zu. Wer die
Seite aufmacht, will sehen was neu ist -- und nicht erst
etwas aufklappen muessen. Alles Aeltere ist sichtbar
VORHANDEN, ohne den Weg zu verstellen.
Die eigene Wahl gewinnt: abschnittKlappbar merkt sich je
Gruppe, was zuletzt eingestellt war. */
/* Der vierte Wert ist ZU-Vorgabe, kein Objekt -- nachgesehen
in kopf.js, nicht geraten. Die erste Gruppe steht offen
(zu = false), alle weiteren zu. */
window.abschnittKlappbar?.(kopf, innen,
'zeit:' + merker + ':' + stufe.schluessel, !erste);
erste = false;
}
}
/** Ein Stapel Karten mit der Grenze bei zwoelf -- der Rest auf
* einen Druck. Gilt jetzt JE GRUPPE: Auch "Über ein Jahr" kann
* dreihundert Eintraege haben, und dann hilft die Gliederung
* allein nicht. */
function stapelLegen(kasten, fach) {
for (const e of fach.slice(0, AUF_EINMAL)) {
const k = karte(e);
karten.set(e.id, k);
@@ -2180,7 +2307,7 @@
? `Für ${ZEIT_WORT[zeitFilter].toLowerCase()} steht hier nichts.`
: 'Hier steht noch nichts.'));
}
kartenLegen(kasten, fach);
kartenLegen(kasten, fach, bereich + ':kanal:' + (schluessel || 'ohne'));
sp.append(kasten);
window.abschnittKlappbar?.(kopf, kasten, `highlight:kanal:${schluessel || 'ohne'}`);
spalten.append(sp);
@@ -2293,7 +2420,7 @@
ziel.append(kopf2);
const kasten = el('div', 'brettabschnitt__inhalt');
kartenLegen(kasten, fach);
kartenLegen(kasten, fach, bereich + ':art:' + a);
ziel.append(kasten);
/* Der Merker haengt am BRETT und an der Art -- sonst waere
"Regel zugeklappt" auf allen Brettern gleichzeitig zu. */
@@ -2335,7 +2462,7 @@
if (window.klappPfeil) kopf.append(window.klappPfeil());
ziel.append(kopf);
const kasten = el('div', 'zeitstrahl__inhalt');
kartenLegen(kasten, fach);
kartenLegen(kasten, fach, bereich + ':zeit:' + r);
ziel.append(kasten);
window.abschnittKlappbar?.(kopf, kasten, `ansteht:${r}`, r === 'vorbei');
}