Die Kategorien lassen sich auf- und zuklappen

Filipe: "mach das viel geiler alles bitte, ich will auch dass man die
kategorien von den fragen, also die liste auf und zu druecken kann mit
einem button."

ACHTUNDZWANZIG PUNKTE OFFEN SIND RUND ZWEI METER SEITE. Man scrollt,
verliert die Stelle und macht es beim naechsten Mal gar nicht mehr.

  - Jede Kategorie hat einen Knopf. Ein Klick wechselt nur DIESEN
    Abschnitt; die Karte wird nicht neu geholt, sonst springt die Seite
    bei 28 Punkten unter den Fingern weg.
  - Der Zustand liegt im Skript, nicht an den Elementen: Nach dem
    ersten Klick auf einen Punkt zeichnet die Karte sich neu (erst
    danach darf der Stand der anderen ausgeliefert werden). Am Element
    klappte dabei alles wieder zu -- mitten in der Arbeit.
  - Welche zuerst offen steht, ist die eigentliche Entscheidung: die
    erste, in der noch etwas fehlt. Alle zu hiesse jedes Mal suchen,
    alle auf waere der Zustand von vorher.
  - Der Kopf sagt ZUGEKLAPPT, was drin ist: "2 / 8" und, wenn etwas
    hakt, "1 x hakt". Eine zugeklappte Kategorie, die nichts sagt,
    klappt man einmal auf und danach nie wieder zu.
  - Daneben "Alle aufklappen" / "Alle zuklappen" fuer den Durchgang.

WAS DAS BILDSCHIRMFOTO AUSSERDEM GEZEIGT HAT, und was ich im Code nicht
gesehen haette: Die Karte hatte gar keine eigene Flaeche. "Frida · Modi
· 2 von 28 angesehen" lag quer ueber dem Spicy-Media-Schriftzug, der
Knopf darunter ueber einer Chilischote. Genau der Fall, fuer den die
Kontrastmessung ihren dritten Ausgang hat ("konnte nicht nachsehen") --
ueber einem Foto laesst sich Lesbarkeit nicht ausrechnen. Die Antwort
darauf ist keine hellere Schrift, sondern eine deckende Flaeche.

Und der Hinweis "(bei 'Da hakt es' noetig)" stand bisher 28 Mal da,
auch an Punkten, an denen nichts hakt. Ein Satz, der ueberall steht,
wird nirgends gelesen. Jetzt meldet sich das Feld erst, wenn es
gebraucht wird -- mit Rahmen in derselben Farbe wie die Antwort, und
der Mauszeiger springt hinein.

Beim Nachsehen im Bild gefunden: "2 von 28 angesehen" stand danach
zweimal auf demselben Bildschirm. Zweimal dieselbe Zahl laesst einen
ueberlegen, ob es zwei verschiedene sind.

  server/pruef-schritt.mjs   57 Pruefungen (vorher 46), 0 Fehler
  Der Knopf wird wirklich gedrueckt -- ein Knopf, den niemand betaetigt
  hat, ist kein geprueter Knopf.

  pruef-css-klassen, pruef-entwicklung 43, pruef-neue-seiten 93,
  pruef-nachwuchs 123 -- alle 0 Fehler

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-15 14:40:11 +02:00
co-authored by Claude Opus 5
parent 418b6211e2
commit 45f5a35afe
33 changed files with 669 additions and 368 deletions
+119 -4
View File
@@ -90,10 +90,26 @@
/* ---------------------------------------------------------------------
Die Karte
------------------------------------------------------------------- */
.e-karte { margin: 26px 0 0; }
/* DIE KARTE BEKOMMT EINE EIGENE FLÄCHE (15.09.2026).
Bis heute hatte sie keine: Name, Rolle und der Weg zum Notizbrett
standen direkt auf der Bühne — also auf einem Foto. Im Bildschirmfoto
lag „Frida · Modi · 2 von 28 angesehen" quer über dem Spicy-Media-
Schriftzug, und der Knopf darunter über einer Chilischote.
Das ist genau der Fall, für den die Kontrastmessung ihren dritten
Ausgang hat („konnte nicht nachsehen"): Über einem Foto lässt sich
Lesbarkeit nicht ausrechnen, weil das Dahinter von Bild zu Bild
wechselt. Die Antwort darauf ist keine hellere Schrift, sondern eine
deckende Fläche. */
.e-karte {
margin: 26px 0 0; padding: 18px 20px;
background: color-mix(in srgb, var(--tinte) 92%, #ffffff 8%);
border: 1px solid var(--rand);
}
.e-karte__kopf {
display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
padding-bottom: 10px; margin-bottom: 16px;
padding-bottom: 12px; margin-bottom: 16px;
border-bottom: 1px solid var(--rand-hell);
}
.e-karte__name { margin: 0; font-size: 1.22rem; font-weight: 660; color: var(--text); }
@@ -119,7 +135,87 @@
/* ---------------------------------------------------------------------
Ein Block und seine Punkte
------------------------------------------------------------------- */
.e-block { margin: 0 0 26px; }
/* =====================================================================
DIE KATEGORIEN LASSEN SICH AUF- UND ZUKLAPPEN (15.09.2026)
Filipe: „ich will auch dass man die kategorien von den fragen, also
die liste auf und zu drücken kann mit einem button."
Achtundzwanzig Punkte offen sind rund zwei Meter Seite. Man scrollt,
verliert die Stelle und macht es beim nächsten Mal gar nicht mehr.
DER KOPF MUSS ZUGEKLAPPT ETWAS SAGEN, sonst klappt man einmal auf und
danach nie wieder zu. Er trägt deshalb drei Dinge: den Namen, wie
weit die Kategorie ist (3 / 7) und ob darin etwas hakt.
===================================================================== */
.e-bloecke__leiste {
display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
margin: 0 0 14px;
}
.e-bloecke__alle {
font: inherit; font-size: .81rem; font-weight: 600;
padding: 7px 14px; border-radius: 999px; cursor: pointer;
color: var(--text-leise);
background: transparent;
border: 1px solid var(--rand-hell);
transition: background .16s ease, color .16s ease;
}
.e-bloecke__alle:hover { color: var(--text); background: rgba(255, 255, 255, .05); }
.e-bloecke__alle:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.e-block {
margin: 0 0 10px;
border: 1px solid var(--rand);
background: color-mix(in srgb, var(--tinte) 96%, #ffffff 4%);
}
/* Offen bekommt der Kasten etwas mehr Gewicht -- man soll auf einen
Blick sehen, welcher gerade aufgeschlagen ist. */
.e-block[data-offen="ja"] { border-color: var(--rand-hell); }
.e-block__zeile { margin: 0; }
.e-block__schalter {
display: flex; align-items: center; gap: 11px;
width: 100%; text-align: left;
font: inherit;
padding: 13px 15px; cursor: pointer;
background: transparent; border: 0;
color: var(--text);
}
.e-block__schalter:hover { background: rgba(255, 255, 255, .04); }
.e-block__schalter:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
.e-block__pfeil {
flex: 0 0 auto; width: 18px; height: 18px;
fill: none; stroke: var(--text-still); stroke-width: 2;
stroke-linecap: round; stroke-linejoin: round;
transition: transform .18s ease;
}
.e-block[data-offen="ja"] .e-block__pfeil { transform: rotate(90deg); stroke: var(--text-leise); }
.e-block__name {
flex: 1 1 auto; font-size: 1rem; font-weight: 650; color: var(--text);
}
/* Der Stand. Zahlen in Tabellenbreite, damit 3 / 7 und 10 / 12
untereinander nicht wackeln. */
.e-block__stand {
flex: 0 0 auto; font-size: .82rem; color: var(--text-still);
font-variant-numeric: tabular-nums;
padding: 2px 9px; border-radius: 999px; border: 1px solid var(--rand);
}
.e-block__stand[data-fertig="ja"] {
color: #7fb08a; border-color: color-mix(in srgb, #668e6e 50%, transparent);
}
/* „2 × hakt" -- die einzige Stelle im Kopf, die eine Farbe bekommt.
Sie ist auch der einzige Grund, eine zugeklappte Kategorie zu
oeffnen, ohne dass man sie durchgehen will. */
.e-block__hakt {
flex: 0 0 auto; display: inline-flex; align-items: baseline; gap: 4px;
font-size: .78rem; color: #d8a76a;
font-variant-numeric: tabular-nums;
}
.e-block__hakt-wort { font-size: .78rem; }
.e-block__koerper { padding: 0 15px 13px; }
.e-block__titel { margin: 0 0 4px; font-size: 1.02rem; font-weight: 650; color: var(--text); }
.e-block__satz {
margin: 0 0 12px; max-width: 64ch;
@@ -204,8 +300,22 @@
color: var(--text);
background: rgba(0, 0, 0, .28);
border: 1px solid var(--rand);
transition: border-color .16s ease, background .16s ease;
}
.e-punkt__anlass::placeholder { color: var(--text-still); }
/* ES BLEIBT SICHTBAR, AUCH WENN ES NICHT GEBRAUCHT WIRD -- ein Feld,
das erst aufklappt, kostet einen zweiten Blick, und genau an der
Stelle hört man auf zu schreiben. Aber es tritt zurück. */
.e-punkt__anlass[data-noetig="nein"] {
background: rgba(0, 0, 0, .18);
border-color: color-mix(in srgb, var(--rand) 70%, transparent);
}
/* Und es meldet sich, sobald es gebraucht wird. */
.e-punkt__anlass[data-noetig="ja"] {
border-color: color-mix(in srgb, #cc9451 55%, transparent);
background: rgba(0, 0, 0, .3);
}
.e-punkt__anlass[data-noetig="ja"]::placeholder { color: #c99a62; }
.e-punkt__andere, .e-punkt__verdeckt, .e-punkt__wann {
margin: 8px 0 0; font-size: .78rem; line-height: 1.5; color: var(--text-still);
@@ -355,7 +465,12 @@
.t-fuss a { color: var(--text-leise); }
@media (prefers-reduced-motion: reduce) {
.e-person, .e-wahl__knopf, .t-merkmal { transition: none; }
.e-person, .e-wahl__knopf, .t-merkmal,
.e-bloecke__alle, .e-punkt__anlass { transition: none; }
/* Der Pfeil DREHT SICH weiterhin -- er ist keine Verzierung,
sondern die Auskunft "offen oder zu". Nur der Weg dorthin
faellt weg. */
.e-block__pfeil { transition: none; }
}
@media (max-width: 620px) {
+132 -5
View File
@@ -40,6 +40,33 @@
}
let stufen = [];
/* WELCHE KATEGORIEN OFFEN STEHEN (15.09.2026).
Filipe: „ich will auch dass man die kategorien von den fragen, also
die liste auf und zu drücken kann mit einem button."
Der Zustand liegt hier und nicht an den Elementen: Die Karte wird
nach dem ersten Klick auf einen Punkt neu gezeichnet (erst danach
darf der Stand der anderen ueberhaupt ausgeliefert werden). Waere
er an den Elementen, klappte dabei alles wieder zu -- mitten in der
Arbeit, bei jedem ersten Klick. */
let offeneBloecke = null;
let karteFuer = null;
/* Der Pfeil des Aufklappers. Als Zeichnung und nicht als Textzeichen:
„▸" sieht auf jedem Geraet anders aus und faellt bei manchen
Schriften ganz aus. */
const pfeil = () => {
const s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
s.setAttribute('class', 'e-block__pfeil');
s.setAttribute('viewBox', '0 0 24 24');
s.setAttribute('aria-hidden', 'true');
const p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p.setAttribute('d', 'M9 6l6 6-6 6');
s.append(p);
return s;
};
let offen = null; // die gerade geöffnete Person
/* ---- Die vier Knöpfe ------------------------------------------------
@@ -160,12 +187,99 @@
ziel.append(box);
}
/* ---- Die Blöcke ---------------------------------------------------- */
/* ---- Die Blöcke, auf- und zuklappbar --------------------------------
ACHTUNDZWANZIG PUNKTE SIND ZU VIEL FÜR EINEN BILDSCHIRM. Offen
sind es rund zwei Meter Seite; man scrollt, verliert die Stelle
und macht es beim nächsten Mal gar nicht mehr.
WELCHER ZUERST OFFEN STEHT, IST DIE EIGENTLICHE ENTSCHEIDUNG.
Alle zu hieße: jedes Mal erst suchen. Alle auf wäre der Zustand
von vorher. Offen steht deshalb die erste Kategorie, in der noch
etwas fehlt — man landet dort, wo die Arbeit ist. Ist alles
gesetzt, steht die erste offen.
================================================================ */
if (karteFuer !== id) { karteFuer = id; offeneBloecke = null; }
if (offeneBloecke === null) {
offeneBloecke = new Set();
const ersteOffene = r.bloecke.find((b) => b.punkte.some((pt) => !pt.meins))
|| r.bloecke[0];
if (ersteOffene) offeneBloecke.add(ersteOffene.schluessel);
}
const leiste = el('div', 'e-bloecke__leiste');
const alleZu = () => r.bloecke.every((b) => offeneBloecke.has(b.schluessel));
const alleKnopf = el('button', 'e-bloecke__alle');
alleKnopf.type = 'button';
const alleBeschriften = () => {
alleKnopf.textContent = alleZu() ? 'Alle zuklappen' : 'Alle aufklappen';
};
alleBeschriften();
alleKnopf.addEventListener('click', () => {
if (alleZu()) offeneBloecke.clear();
else for (const b of r.bloecke) offeneBloecke.add(b.schluessel);
karte(id);
});
leiste.append(alleKnopf);
/* HIER STAND KURZ „2 von 28 angesehen" -- dieselbe Zahl, die zwei
Zeilen hoeher schon im Kopf der Karte steht. Zweimal dasselbe auf
einem Bildschirm laesst einen ueberlegen, ob es zwei verschiedene
Zahlen sind. Gesehen habe ich das im Bildschirmfoto, nicht im
Code. */
ziel.append(leiste);
for (const b of r.bloecke) {
const gesetzt = b.punkte.filter((pt) => pt.meins).length;
const offen = offeneBloecke.has(b.schluessel);
const haktHier = b.punkte.filter((pt) => pt.meins?.stufe === 'hakt').length;
const abschnitt = el('section', 'e-block');
abschnitt.append(el('h3', 'e-block__titel', b.name));
abschnitt.append(el('p', 'e-block__satz', b.text));
for (const pt of b.punkte) abschnitt.append(punktZeile(id, pt, r.erwartung));
abschnitt.dataset.offen = offen ? 'ja' : 'nein';
if (gesetzt === b.punkte.length) abschnitt.dataset.fertig = 'ja';
const koerper = el('div', 'e-block__koerper');
koerper.id = `block-${b.schluessel}`;
koerper.hidden = !offen;
const titel = el('h3', 'e-block__zeile');
const schalter = el('button', 'e-block__schalter');
schalter.type = 'button';
schalter.setAttribute('aria-expanded', String(offen));
schalter.setAttribute('aria-controls', koerper.id);
schalter.append(pfeil());
schalter.append(el('span', 'e-block__name', b.name));
/* DER STAND STEHT IM KOPF, damit man ihn ZUGEKLAPPT sieht. Eine
zugeklappte Kategorie, die nicht sagt, wie weit sie ist, klappt
man einmal auf und danach nie wieder zu. */
const stand = el('span', 'e-block__stand', `${gesetzt} / ${b.punkte.length}`);
if (gesetzt === b.punkte.length) stand.dataset.fertig = 'ja';
schalter.append(stand);
if (haktHier) {
const h = el('span', 'e-block__hakt', `${haktHier} ×`);
h.title = haktHier === 1 ? 'Hier hakt ein Punkt.' : `Hier haken ${haktHier} Punkte.`;
h.append(el('span', 'e-block__hakt-wort', 'hakt'));
schalter.append(h);
}
schalter.addEventListener('click', () => {
if (offeneBloecke.has(b.schluessel)) offeneBloecke.delete(b.schluessel);
else offeneBloecke.add(b.schluessel);
/* Nur DIESEN Abschnitt umschalten statt die Karte neu zu holen:
Ein Neuaufbau bei jedem Klick ließe die Seite unter den
Fingern springen -- und bei 28 Punkten sieht man das. */
const jetztOffen = offeneBloecke.has(b.schluessel);
abschnitt.dataset.offen = jetztOffen ? 'ja' : 'nein';
koerper.hidden = !jetztOffen;
schalter.setAttribute('aria-expanded', String(jetztOffen));
alleBeschriften();
});
titel.append(schalter);
abschnitt.append(titel);
/* Der erklärende Satz gehört in den Körper, nicht in den Kopf:
Zugeklappt will man die Überschrift lesen, nicht einen Absatz. */
koerper.append(el('p', 'e-block__satz', b.text));
for (const pt of b.punkte) koerper.append(punktZeile(id, pt, r.erwartung));
abschnitt.append(koerper);
ziel.append(abschnitt);
}
}
@@ -193,7 +307,18 @@
const feld = el('textarea', 'e-punkt__anlass');
feld.rows = 2;
feld.value = pt.meins?.anlass || '';
feld.placeholder = 'Woran hast du das gemerkt? (bei „Da hakt es" nötig)';
/* DER HINWEIS STAND BISHER ACHTUNDZWANZIGMAL DA -- auch an Punkten,
an denen nichts hakt. Ein Satz, der überall steht, wird nirgends
gelesen. Jetzt sagt das Feld erst dann, dass es gebraucht wird,
wenn es gebraucht wird. */
const feldSetzen = (stufe) => {
const noetig = stufe === 'hakt';
feld.dataset.noetig = noetig ? 'ja' : 'nein';
feld.placeholder = noetig
? 'Woran hast du das gemerkt? Ein Satz genügt – ohne ihn wird daraus kein Schritt.'
: 'Woran hast du das gemerkt? (kann leer bleiben)';
};
feldSetzen(pt.meins?.stufe || null);
z.append(knopfsatz(pt.meins?.stufe || null, async (stufe, knopf) => {
const reihe = knopf.parentElement;
@@ -201,6 +326,8 @@
b.setAttribute('aria-pressed', String(b === knopf));
}
z.dataset.stufe = stufe;
feldSetzen(stufe);
if (stufe === 'hakt' && !feld.value.trim()) feld.focus();
const a = await hole(`/workspace/api/entwicklung/person/${personId}/${pt.schluessel}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },