Checklisten-Gruppen klappen zu, Markierungen tragen ein Zeichen

Zwei Wuensche vom 01.09.2026:
  "neben den Titel soll immer ein Knopf sein, wo man die Liste aufmacht
   oder wieder zumacht -- die sollen auch zu, damit die Seiten nicht so
   lang sind."
  "und wenn die Ansprechpartner was markieren, sollen die da so ein
   Zeichen haben, so dass die sehen, da ist was -- und es soll auch oben
   in der Kachel angezeigt werden."

ZUKLAPPEN. Der Gruppenkopf ist jetzt ein Knopf ueber die volle Breite
(49 px hoch, mit dem Daumen treffbar), nicht ein Pfeilchen daneben. Zu
ist der Standard. Gemessen: Die LIVE-Analyse ist damit 1100 statt 2081
Pixel lang -- 47 Prozent gespart. Welche Gruppen offen waren, merkt sich
der Browser; sonst waere jede Bewertung ein Ruecksprung an den Anfang.

DAS ZEICHEN. Eine Raute mit Ausrufestrich, in Bernstein. Drei
Entscheidungen, keine davon Geschmack:
  * FORM -- alles andere auf dem Bildschirm ist rund oder eckig. Eine
    Spitze nach oben gibt es sonst nirgends, und deshalb findet das Auge
    sie zwischen zwanzig Kacheln ohne Suchen.
  * FARBE -- immer dieselbe, nie die der Kachel. Ein Zeichen, das die
    Farbe wechselt, muss gelesen werden; eines, das immer gleich
    aussieht, wird erkannt. Rot waere falsch: "verbessern" ist ein
    Auftrag, kein Fehler.
  * BEWEGUNG -- ein Atmen ueber 3,2 s, kein Blinken; bei "weniger
    Bewegung" bleibt der Schein stehen statt zu verschwinden.

Es steht an drei Stellen, immer aus derselben Quelle (bereiche.js): am
Gruppenkopf, am Punkt selbst und oben auf der Kachel der Startseite.

WARUM DAS ZEICHEN AM GRUPPENKOPF PFLICHT IST. Ohne es waere Zuklappen
ein Rueckschritt gewesen: Der Betreuer markiert etwas, die Gruppe ist zu,
und der Creator erfaehrt es nie. Die Zahl "zu verbessern" in der Bilanz
klappt die betroffenen Gruppen jetzt auf und springt hin.

Auf der Kachel nur fuer Creator. Fuer einen Betreuer waere es die Liste
dessen, was er selbst angehakt hat -- sie waechst mit seiner Arbeit, und
nur der Creator kann sie abbauen. Ein Zaehler, den man nicht auf null
bringen kann, wird ignoriert, und dann sind auch die daneben nichts wert.

ZWEI FEHLER, DIE DIE PRUEFUNG GEFUNDEN HAT:
  1. Das Zeichen stiess auf der Kachel mit der Zahl zusammen (zwei
     Pixel). Behoben an der Ursache: Besteht die Zahl NUR aus
     Markierungen, ist sie dieselbe Auskunft ein zweites Mal und
     entfaellt; sonst ruecken Zahl und Pfeil nach unten.
  2. Danach war die Pruefung wertlos -- sie verglich mit einem Element,
     das es nun nicht mehr gab, und war ohne einen einzigen Vergleich
     gruen. Sie zaehlt jetzt die geprueften Nachbarn mit; null Nachbarn
     ist ein Fehler, kein Erfolg.

Geprueft wird SICHTBARKEIT, nicht Vorhandensein: Ein zugeklappter Punkt
steht weiterhin im Dokument, alle bisherigen Pruefungen waeren gruen
geblieben, auch wenn der Creator seine Markierung nie zu Gesicht
bekaeme. Dazu Gegenproben: keine Markierung ohne Grund, und wer nichts
markiert bekommen hat, sieht auch kein Zeichen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-01 19:42:34 +02:00
co-authored by Claude Opus 5
parent f9bf7d5674
commit d4cd8952b7
35 changed files with 740 additions and 151 deletions
+60 -1
View File
@@ -196,5 +196,64 @@
return svg;
}
window.Bereiche = { ZEICHEN, GRUPPEN, zuSeite, zeichenBauen };
/* ---------- Das Merkzeichen ----------------------------------------------
Wunsch vom 01.09.2026: "wenn die Ansprechpartner was markieren, sollen
die da so ein Zeichen/Symbol haben, so dass die sehen, da ist was --
und es soll auch oben in der Kachel angezeigt werden."
Es ist bewusst das EINZIGE Zeichen der ganzen Anwendung, das eine
eigene Form hat statt der 24×24-Linienzeichen oben: eine Raute mit
Ausrufestrich. Der Grund ist nicht Geschmack, sondern Erkennbarkeit --
alle anderen Zeichen sind Rechtecke und Kreise; eine Raute ist die
einzige Form auf dem Bildschirm, die spitz nach oben zeigt, und
genau deshalb findet das Auge sie zwischen zwanzig Kacheln sofort.
Und es ist immer DIESELBE Farbe (Bernstein), egal auf welcher Kachel
es sitzt. Ein Zeichen, das seine Farbe wechselt, muss gelesen werden;
eines, das immer gleich aussieht, wird erkannt. Rot wäre falsch --
"verbessern" ist kein Fehler, sondern ein Auftrag.
anzahl > 1 schreibt die Zahl daneben, sonst steht nur das Zeichen.
Der Satz für Vorleseprogramme wird immer gesetzt: Ein Symbol ohne
Text ist für sie nicht vorhanden. */
function merkzeichenBauen(anzahl = 0, klasse = 'merk') {
const s = document.createElement('span');
s.className = klasse;
s.setAttribute('role', 'img');
s.setAttribute('aria-label', anzahl > 0
? `${anzahl} ${anzahl === 1 ? 'Punkt ist' : 'Punkte sind'} zum Verbessern markiert`
: 'Zum Verbessern markiert');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('class', klasse + '__svg');
/* Drei Pfade: die gefüllte Raute, der Strich, der Punkt. Getrennt,
damit die Fläche eine andere Deckkraft bekommen kann als die
Linie -- eine durchgehend volle Raute wäre auf dunklem Grund ein
Klecks. */
const teile = [
['M12 1.9 22.1 12 12 22.1 1.9 12z', 'flaeche'],
['M12 7.5v5.6', 'strich'],
['M12 16.9h.01', 'punkt'],
];
for (const [d, art] of teile) {
const p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p.setAttribute('d', d);
p.setAttribute('class', `${klasse}__${art}`);
svg.append(p);
}
s.append(svg);
if (anzahl > 1) {
const n = document.createElement('span');
n.className = klasse + '__n';
n.textContent = String(anzahl);
n.setAttribute('aria-hidden', 'true');
s.append(n);
}
return s;
}
window.Bereiche = { ZEICHEN, GRUPPEN, zuSeite, zeichenBauen, merkzeichenBauen };
})();
+115 -8
View File
@@ -97,32 +97,132 @@
if (daten.creator_id || !daten.darf_bewerten) {
const bilanz = el('div', 'liste-fest__bilanz');
bilanz.append(zahl(gut, 'passt so', 'gut'));
bilanz.append(zahl(zuTun, zuTun === 1 ? 'zu verbessern' : 'zu verbessern', 'verbessern'));
bilanz.append(zahl(zuTun, 'zu verbessern', 'verbessern'));
bilanz.append(zahl(alle.length - gut - zuTun, 'noch offen', 'offen'));
block.append(bilanz);
}
/* ---- Die Gruppen ---- */
const aufgeklappt = standLesen(bereich);
const markierte = []; /* Gruppen mit etwas zum Verbessern */
for (const g of daten.gruppen) {
const abschnitt = el('section', 'fest-gruppe');
const gk = el('div', 'fest-gruppe__kopf');
const offenZahl = g.punkte
.filter((p) => daten.stand[p.schluessel]?.stufe === 'verbessern').length;
if (offenZahl) markierte.push(g.schluessel);
/* Der Kopf ist ein KNOPF, nicht eine Überschrift mit einem Knopf
daneben. Ein Ziel von 40 Pixeln Höhe über die ganze Breite trifft
man auch mit dem Daumen -- ein 16-Pixel-Pfeil daneben nicht. */
const gk = el('button', 'fest-gruppe__kopf');
gk.type = 'button';
const koerperId = `fg-${bereich}-${g.schluessel}`;
gk.setAttribute('aria-controls', koerperId);
const pfeil = el('span', 'fest-gruppe__pfeil');
pfeil.setAttribute('aria-hidden', 'true');
gk.append(pfeil);
gk.append(el('h3', 'fest-gruppe__name', g.name));
gk.append(el('span', 'fest-gruppe__zahl', String(g.punkte.length)));
/* Das Merkzeichen am Gruppenkopf ist der Grund, warum zugeklappte
Gruppen überhaupt vertretbar sind: Ohne es wäre eine Markierung
hinter einer geschlossenen Gruppe unsichtbar -- der Betreuer
markiert etwas, und der Creator erfährt es nie. */
if (offenZahl) {
gk.append(window.Bereiche.merkzeichenBauen(offenZahl, 'merk'));
abschnitt.dataset.markiert = 'ja';
}
abschnitt.append(gk);
if (g.text) abschnitt.append(el('p', 'fest-gruppe__text', g.text));
const koerper = el('div', 'fest-gruppe__koerper');
koerper.id = koerperId;
if (g.text) koerper.append(el('p', 'fest-gruppe__text', g.text));
const liste = el('div', 'fest-punkte');
for (const p of g.punkte) {
liste.append(punktBauen(p, daten, bereich, behaelter));
}
abschnitt.append(liste);
koerper.append(liste);
abschnitt.append(koerper);
/* Zu, sofern nicht ausdrücklich anders gemerkt. Das ist der ganze
Zweck: Vier Bereiche mit je einundzwanzig Punkten sind offen eine
Seite, die niemand zu Ende scrollt. */
klappZustand(abschnitt, gk, koerper, aufgeklappt.has(g.schluessel));
gk.addEventListener('click', () => {
const jetztAuf = abschnitt.dataset.auf !== 'ja';
klappZustand(abschnitt, gk, koerper, jetztAuf);
standSchreiben(bereich, g.schluessel, jetztAuf);
});
block.append(abschnitt);
}
/* Die Zahl "zu verbessern" wird zum Weg dorthin. Eine Zahl, die einen
auf etwas hinweist, das man dann selbst suchen muss, ist eine
halbe Auskunft. */
const bilanzKnopf = block.querySelector('.bilanz-zahl[data-stufe="verbessern"]');
if (bilanzKnopf && zuTun > 0 && markierte.length) {
bilanzKnopf.dataset.klickbar = 'ja';
bilanzKnopf.setAttribute('role', 'button');
bilanzKnopf.tabIndex = 0;
bilanzKnopf.title = 'Die markierten Punkte aufklappen';
const hin = () => {
let erste = null;
for (const a of block.querySelectorAll('.fest-gruppe[data-markiert="ja"]')) {
const k = a.querySelector('.fest-gruppe__kopf');
const b = a.querySelector('.fest-gruppe__koerper');
klappZustand(a, k, b, true);
standSchreiben(bereich, b.id.split('-').slice(2).join('-'), true);
if (!erste) erste = a;
}
erste?.scrollIntoView({ block: 'start', behavior: ruhig() ? 'auto' : 'smooth' });
};
bilanzKnopf.addEventListener('click', hin);
bilanzKnopf.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); hin(); }
});
}
behaelter.append(block);
behaelter.hidden = false;
};
const ruhig = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function klappZustand(abschnitt, knopf, koerper, auf) {
abschnitt.dataset.auf = auf ? 'ja' : '';
knopf.setAttribute('aria-expanded', auf ? 'true' : 'false');
/* hidden statt display:none im Stil: So findet auch die Suche im
Browser (Strg+F) nichts in einer zugeklappten Gruppe -- sonst
springt sie an eine Stelle, an der nichts zu sehen ist. */
koerper.hidden = !auf;
}
/* Welche Gruppen offen waren, überlebt das Neuladen. Ohne das wäre
jede Bewertung ein Rückschlag: Die Liste baut sich neu auf, und der
Betreuer sucht die Gruppe, in der er gerade war, von vorn.
localStorage und nicht der Server: Es ist eine Ansichtssache dieses
Geräts, keine Eigenschaft des Creators. */
const SCHLUESSEL = (bereich) => `dfw-checkliste-auf-${bereich}`;
function standLesen(bereich) {
try {
const roh = localStorage.getItem(SCHLUESSEL(bereich));
return new Set(roh ? JSON.parse(roh) : []);
} catch { return new Set(); }
}
function standSchreiben(bereich, gruppe, auf) {
try {
const menge = standLesen(bereich);
if (auf) menge.add(gruppe); else menge.delete(gruppe);
localStorage.setItem(SCHLUESSEL(bereich), JSON.stringify([...menge]));
} catch { /* privater Modus -- dann eben nur für diesen Aufbau */ }
}
function zahl(n, text, stufe) {
const k = el('div', 'bilanz-zahl');
k.dataset.stufe = stufe;
@@ -155,11 +255,18 @@
kopf.append(links);
/* Die Marke rechts -- immer sichtbar, auch für den Creator, auch
wenn sie "noch offen" lautet. Er soll sehen, wo er steht. */
const marke = el('span', 'fest-punkt__marke-stufe',
wenn sie "noch offen" lautet. Er soll sehen, wo er steht.
Bei "verbessern" steht das Merkzeichen davor: dieselbe Raute wie
am Gruppenkopf und auf der Kachel. Wer sie auf der Startseite
gesehen hat, erkennt hier wieder, was gemeint war -- ohne den Text
lesen zu müssen. */
const rechts = el('div', 'fest-punkt__stufe');
if (stufe === 'verbessern') rechts.append(window.Bereiche.merkzeichenBauen(0, 'merk'));
rechts.append(el('span', 'fest-punkt__marke-stufe',
stufe === 'gut' ? 'Passt so'
: stufe === 'verbessern' ? 'Verbessern' : 'Offen');
kopf.append(marke);
: stufe === 'verbessern' ? 'Verbessern' : 'Offen'));
kopf.append(rechts);
k.append(kopf);
/* Der Grund steht in voller Breite -- "verbessern" ohne Grund ist
+44 -2
View File
@@ -60,8 +60,20 @@
review_bald: 'Reports & Review',
ohne_betreuung: 'Personen & Zugänge',
ohne_profil: 'Creator-Profile',
markiert_live: 'LIVE-Analyse',
markiert_content: 'Content-Ideen',
markiert_community: 'Community',
markiert_technik: 'Technik',
};
/* Diese Hinweise bekommen zusätzlich das Merkzeichen auf die Kachel.
Sie sind etwas anderes als "3 Aufgaben offen": Da hat jemand
persönlich etwas markiert und wartet auf eine Reaktion. Eine Zahl
allein macht diesen Unterschied nicht sichtbar. */
const MIT_MERKZEICHEN = new Set([
'markiert_live', 'markiert_content', 'markiert_community', 'markiert_technik',
]);
/* Findet einen Bereich über seinen Namen -- damit Hinweis und Kachel
dasselbe Zeichen und denselben Farbton benutzen, ohne dass die
Zuordnung ein zweites Mal irgendwo stünde. */
@@ -98,9 +110,10 @@
for (const h of hinweise) {
const kachel = HINWEIS_ZU_KACHEL[h.art];
if (!kachel) continue;
const bisher = zahlen.get(kachel) || { n: 0, warn: false };
const bisher = zahlen.get(kachel) || { n: 0, warn: false, merk: 0 };
bisher.n += h.anzahl;
if (h.stufe === 'warnung') bisher.warn = true;
if (MIT_MERKZEICHEN.has(h.art)) bisher.merk += h.anzahl;
zahlen.set(kachel, bisher);
}
@@ -152,7 +165,27 @@
a.append(text);
const zahl = zahlen.get(b.name);
if (zahl && zahl.n > 0) {
/* Das Merkzeichen oben in der Kachel. Es sitzt im Link und nicht
im Text, damit es sich absolut an der Kachelecke ausrichten
kann statt im Fluss mitzuschwimmen. */
if (zahl && zahl.merk > 0) {
const m = window.Bereiche.merkzeichenBauen(zahl.merk, 'merk');
m.classList.add('kachel__merk');
a.append(m);
/* Die Kachel merkt sich das: Zahl und Pfeil ruecken dann nach
unten, damit sie dem Zeichen nicht ins Gehege kommen. Die
Browserpruefung hat genau diese Ueberdeckung gemeldet -- mit
blossem Auge waren es zwei Pixel. */
li.dataset.merk = 'ja';
}
/* Besteht die Zahl NUR aus Markierungen, waere sie dieselbe
Auskunft ein zweites Mal: Das Zeichen sagt bereits "hier ist
etwas", und bei mehreren steht die Anzahl darin. Dann lieber
der Pfeil -- zwei Marken uebereinander auf einer Kachel von
achtzig Pixeln sind Unruhe ohne Gewinn. */
if (zahl && zahl.n > 0 && zahl.n > zahl.merk) {
const marke = el('span', 'kachel__zahl', String(zahl.n));
if (zahl.warn) marke.dataset.warn = 'ja';
/* Vorlesende Programme bekommen einen Satz statt einer nackten
@@ -334,6 +367,15 @@
ohne_profil: (n) => `${n} Creator-Profil${n === 1 ? ' ist' : 'e sind'} noch leer`,
ohne_startcheck: (n) => `${n} Creator ${n === 1 ? 'hat' : 'haben'} noch keinen Start-Check`,
startcheck_handlung: (n) => `${n} ${n === 1 ? 'Punkt im Start-Check braucht' : 'Punkte im Start-Check brauchen'} Handlung`,
/* Diese vier beginnen mit der Zahl, wie alle anderen auch: Die Zeile
schneidet die führende Zahl ab und zeigt sie getrennt davor. Ein
Satz, der anders anfängt ("Deine Betreuung hat 3 …"), stünde
hinter der Marke ein zweites Mal mit der 3 -- kein Fehler, den ein
Programm meldet, aber einer, den jeder sieht. */
markiert_live: (n) => `${n} markierte${n === 1 ? 'r Punkt' : ' Punkte'} in der LIVE-Analyse`,
markiert_content: (n) => `${n} markierte ${n === 1 ? 'Content-Idee' : 'Content-Ideen'}`,
markiert_community: (n) => `${n} markierte${n === 1 ? 'r Punkt' : ' Punkte'} bei Community`,
markiert_technik: (n) => `${n} markierte${n === 1 ? 'r Punkt' : ' Punkte'} bei Technik`,
};
async function hinweiseLaden() {