From fa8e86ad812f7635948e07be3a640876296c4075 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Thu, 17 Sep 2026 02:41:41 +0200 Subject: [PATCH] Die Personenkacheln der Entwicklung im Hausstil Filipe, mit zwei Bildschirmfotos nebeneinander: "wieso ist diese seite noch nicht im stil und perfektioniert wie die auf screen2 ... aber so dass ich die leute in einer kachel aussuchen kann alle einzel." Der Unterschied lag NICHT im Aufbau -- der stimmte -- und auch nicht am Inhalt: Die 28 Punkte sind laengst ganz auf Modi-Arbeit gebaut (Im Chat 8, Wenn es eng wird 7, Im Team 7, Wie viel von selbst 6, dazu sechs Fragen an einen selbst). Es waren zwei andere Dinge: 1. DIE ZAHLEN WAREN GRAUE TEXTZEILEN Drueben stehen sie als abgesetzte Kaesten mit grosser Ziffer und eigenem Ton. `bilanz-zahl` liegt in start.css und gehoert dem ganzen Haus -- es wird jetzt benutzt statt nachgebaut. Ein Nachbau laeuft auseinander, sobald jemand eins von beiden anfasst. Aus "0 von 28 angesehen" werden drei Kaesten wie auf der Checkliste: angesehen / noch offen / verschieden gesehen. Die ersten beiden teilen dasselbe Ganze, die dritte ist das Ergebnis dieser Seite -- wo zwei Leute dasselbe sehen, gibt es nichts zu besprechen. 2. EINE ANGEKLICKTE PERSON SAH AUS WIE EINE NICHT ANGEKLICKTE Die Kachel war ein Knopf ohne Zustand. Man klickte jemanden an, die Karte ging auf -- und oben blieb alles gleich. Wer scrollte, wusste nicht mehr, wen er offen hat. Jetzt `aria-pressed`, damit es auch ein Vorleseprogramm sagen kann, und eine gedaempfte Umrandung. Die Reihe heisst "Person waehlen" wie auf dem Aufgabenbrett. Nebenbei: Der Ton des ersten Kastens springt auf gruen, sobald nichts mehr offen ist -- sonst saehe eine fertig angesehene Person aus wie eine halbe. NEU: pruef-entwicklung-kacheln (15 Pruefungen) Nicht "sieht gleich aus" -- das laesst sich nicht messen -- sondern was das Aussehen traegt: Benutzt die Kachel den Hausbaustein oder einen Nachbau (und steht nichts Selbstgebautes daneben)? Ergibt angesehen + noch offen die Gesamtzahl, die die Auskunft selbst nennt? Und die Gegenprobe zur Auswahl: VOR dem ersten Klick darf keine Kachel gewaehlt sein, danach genau eine, und beim naechsten Klick wandert sie, statt sich zu sammeln. pruef-entwicklung 43 und pruef-css-klassen unveraendert gruen. Co-Authored-By: Claude Opus 5 --- server/pruef-entwicklung-kacheln.mjs | 250 +++++++++++++++++++++++++++ workspace/app.webmanifest | 8 +- workspace/assets/css/entwicklung.css | 44 ++++- workspace/assets/js/entwicklung.js | 80 +++++++-- workspace/aufgaben.html | 24 +-- workspace/automation.html | 26 +-- workspace/befinden.html | 24 +-- workspace/bereich.html | 34 ++-- workspace/calls.html | 28 +-- workspace/chat.html | 26 +-- workspace/content.html | 30 ++-- workspace/crew-index.html | 10 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 26 +-- workspace/entwicklung.html | 26 +-- workspace/index.html | 8 +- workspace/kalender.html | 28 +-- workspace/leistung.html | 26 +-- workspace/personen.html | 26 +-- workspace/profil.html | 28 +-- workspace/rechte.html | 24 +-- workspace/report.html | 28 +-- workspace/scouting.html | 26 +-- workspace/start.html | 24 +-- workspace/startcheck.html | 28 +-- workspace/steckbrief.html | 28 +-- workspace/talente.html | 24 +-- workspace/team.html | 26 +-- workspace/teamlage.html | 24 +-- workspace/treff-moderation.html | 24 +-- workspace/treff-regeln.html | 26 +-- workspace/uebersicht.html | 26 +-- workspace/wissen.html | 26 +-- 33 files changed, 714 insertions(+), 380 deletions(-) create mode 100644 server/pruef-entwicklung-kacheln.mjs diff --git a/server/pruef-entwicklung-kacheln.mjs b/server/pruef-entwicklung-kacheln.mjs new file mode 100644 index 00000000..b7c0c25f --- /dev/null +++ b/server/pruef-entwicklung-kacheln.mjs @@ -0,0 +1,250 @@ +/* ===================================================================== + DIE PERSONENKACHELN AUF DER ENTWICKLUNGSSEITE (17.09.2026) + + Filipe, mit zwei Bildschirmfotos nebeneinander: "wieso ist diese + seite noch nicht im stil und perfektioniert wie die auf screen2 ... + aber so dass ich die leute in einer kachel aussuchen kann alle + einzel." + + Der Unterschied zur Checkliste lag nicht im Aufbau -- der stimmte -- + sondern an zwei Stellen: + + 1. Die Zahlen standen als graue Textzeilen da, drueben als + abgesetzte Kaesten mit grosser Ziffer (`bilanz-zahl`). + 2. Eine angeklickte Person sah aus wie eine nicht angeklickte. Wer + scrollte, wusste nicht mehr, wen er offen hat. + + --------------------------------------------------------------------- + WAS HIER GEPRUEFT WIRD -- UND WARUM NICHT DAS AUSSEHEN + + Ob etwas "gleich aussieht", laesst sich nicht messen. Die drei Fragen + darunter schon, und sie sind das, was das Aussehen traegt: + + - Benutzt die Kachel den HAUSBAUSTEIN (`bilanz-zahl`) oder einen + Nachbau? Ein Nachbau laeuft auseinander, sobald jemand eins von + beiden anfasst. + - Stimmen die Zahlen? "angesehen" plus "noch offen" MUSS die + Gesamtzahl ergeben. Zwei Zahlen, die nebeneinander stehen und + nicht zusammenpassen, sind schlimmer als eine. + - Sieht man, wer gewaehlt ist? Und zwar so, dass es auch ein + Vorleseprogramm sagen kann. + + MIT GEGENPROBE: Vor dem ersten Klick darf KEINE Kachel gewaehlt sein. + Sonst hiesse "eine ist gewaehlt" nur, dass immer eine gewaehlt ist. + + Aufrufen mit: node server/pruef-entwicklung-kacheln.mjs + Bild: SCHIRM=1 node server/pruef-entwicklung-kacheln.mjs + ===================================================================== */ + +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { portMussFreiSein } from "./helfer-port.mjs"; + +const PORT = await portMussFreiSein(4475, "pruef-entwicklung-kacheln"); +const ordner = mkdtempSync(join(tmpdir(), "ws-ekach-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(180_000, "pruef-entwicklung-kacheln"); +await new Promise((r) => setTimeout(r, 700)); +const BASIS = `http://127.0.0.1:${PORT}`; + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const schluessel = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", schluessel, jetzt); + +function anlegen(name, rolle, code) { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 64, + { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen" + + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, + createHmac("sha256", schluessel).update(code).digest("hex"), jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} +anlegen("Filipe", "admin", "CODE-DOGI-0001"); +anlegen("VanVan", "hand", "CODE-HAND-0001"); +anlegen("Diene", "modi", "CODE-MODI-0001"); +d.close(); + +const an = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }), +}); +const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); + +const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await chromium.launch(); +try { + const kontext = await browser.newContext({ viewport: { width: 1280, height: 900 } }); + await kontext.addCookies(keks.split("; ").map((k) => { + const [name, ...rest] = k.split("="); + return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" }; + })); + const seite = await kontext.newPage(); + const konsole = []; + seite.on("console", (e) => { if (e.type() === "error") konsole.push(e.text()); }); + await seite.goto(`${BASIS}/workspace/entwicklung.html`, { waitUntil: "networkidle" }); + await seite.waitForSelector("#personen .e-person", { timeout: 15_000 }); + await seite.waitForTimeout(400); + + /* ===================================================================== + 1. DIE REIHE UND IHRE KACHELN + ===================================================================== */ + melde(""); + melde("=== 1. Die Reihe ==="); + { + const b = await seite.evaluate(() => { + const reihe = document.getElementById("personen"); + const kacheln = [...reihe.querySelectorAll(".e-person")]; + return { + schild: reihe.getAttribute("aria-label") || "", + anzahl: kacheln.length, + namen: kacheln.map((k) => k.querySelector(".e-person__name")?.textContent || ""), + /* Der Hausbaustein, nicht ein Nachbau. */ + bilanzJe: kacheln.map((k) => k.querySelectorAll(".bilanz-zahl").length), + eigeneKlassen: kacheln.map((k) => k.querySelectorAll( + "[class*='e-person__bilanz'] > :not(.bilanz-zahl)").length), + }; + }); + ok(b.schild === "Person wählen", `die Reihe sagt, was sie ist ("${b.schild}")`); + ok(b.anzahl === 2, `zwei Kacheln (${b.namen.join(", ")})`); + /* DREI KAESTEN JE KACHEL -- dieselben drei wie auf der Checkliste. */ + ok(b.bilanzJe.length > 0 && b.bilanzJe.every((n) => n === 3), + `jede Kachel traegt drei Bilanz-Kaesten (${b.bilanzJe.join(", ")})`); + /* UND NICHTS DANEBEN: Ein eigener Nachbau neben dem Hausbaustein + waere genau die zweite Wahrheit, die hier vermieden wird. */ + ok(b.eigeneKlassen.every((n) => n === 0), + "und nichts Selbstgebautes daneben"); + } + + /* ===================================================================== + 2. STIMMEN DIE ZAHLEN? + ===================================================================== */ + melde(""); + melde("=== 2. Die Zahlen ==="); + { + const z = await seite.evaluate(() => [...document.querySelectorAll("#personen .e-person")] + .map((k) => { + const werte = [...k.querySelectorAll(".bilanz-zahl")].map((b) => ({ + n: Number(b.querySelector(".bilanz-zahl__n")?.textContent || "x"), + text: b.querySelector(".bilanz-zahl__text")?.textContent || "", + stufe: b.dataset.stufe || "", + })); + return { name: k.querySelector(".e-person__name")?.textContent || "", werte }; + })); + + ok(z.every((k) => k.werte.every((w) => Number.isFinite(w.n))), + "jede Ziffer ist eine Zahl"); + ok(z.every((k) => k.werte[0].text === "angesehen" + && k.werte[1].text === "noch offen" + && k.werte[2].text === "verschieden gesehen"), + `die drei heissen ueberall gleich (${z[0]?.werte.map((w) => w.text).join(" · ")})`); + + /* DIE ENTSCHEIDENDE RECHNUNG: Die ersten beiden teilen dasselbe + Ganze. Gehen sie nicht auf, stehen zwei Zahlen nebeneinander, die + einander widersprechen -- genau das, was auf der Startseite + repariert wurde. Die Gesamtzahl kommt aus der Auskunft selbst, + nicht aus einer festen Zahl hier. */ + const gesamt = await seite.evaluate(async () => { + const a = await fetch("/workspace/api/entwicklung/lage", { credentials: "same-origin" }); + return a.ok ? (await a.json()).gesamt_fremd : -1; + }); + ok(gesamt > 0, `die Seite kennt ${gesamt} Punkte insgesamt`); + const summenOk = z.every((k) => k.werte[0].n + k.werte[1].n === gesamt); + ok(summenOk, + `angesehen + noch offen = ${gesamt} bei jeder Kachel ` + + `(${z.map((k) => `${k.name}: ${k.werte[0].n}+${k.werte[1].n}`).join(", ")})`); + + /* Der Ton gehoert zur Aussage: fertig ist gruen, halb ist es nicht. */ + ok(z.every((k) => k.werte[0].stufe === (k.werte[1].n === 0 ? "gut" : "offen")), + "fertig Angesehenes ist gruen, Halbes nicht"); + } + + /* ===================================================================== + 3. EINZELN AUSWAEHLBAR -- UND MAN SIEHT ES + ===================================================================== */ + melde(""); + melde("=== 3. Auswaehlen ==="); + { + /* GEGENPROBE ZUERST: Vorher darf keine gewaehlt sein. */ + const vorher = await seite.evaluate(() => [...document.querySelectorAll("#personen .e-person")] + .map((k) => k.getAttribute("aria-pressed"))); + ok(vorher.length > 0 && vorher.every((w) => w === "false"), + `vor dem Klick ist keine gewaehlt (${vorher.join(", ")})`); + + await seite.click("#personen .e-person:nth-of-type(2)"); + await seite.waitForTimeout(900); + + const nachher = await seite.evaluate(() => ({ + gedrueckt: [...document.querySelectorAll("#personen .e-person")] + .map((k) => k.getAttribute("aria-pressed")), + wer: document.querySelector('#personen .e-person[aria-pressed="true"] .e-person__name') + ?.textContent || "", + karteOffen: !document.getElementById("karte")?.hidden, + })); + ok(nachher.gedrueckt.filter((w) => w === "true").length === 1, + `genau eine ist gewaehlt (${nachher.gedrueckt.join(", ")})`); + ok(nachher.wer === "Diene", `und zwar die angeklickte (${nachher.wer})`); + ok(nachher.karteOffen, "ihre Karte steht offen"); + + /* UND DIE AUSWAHL WANDERT, statt sich zu sammeln. */ + await seite.click("#personen .e-person:nth-of-type(1)"); + await seite.waitForTimeout(900); + const um = await seite.evaluate(() => + document.querySelector('#personen .e-person[aria-pressed="true"] .e-person__name') + ?.textContent || ""); + const wieViele = await seite.evaluate(() => + document.querySelectorAll('#personen .e-person[aria-pressed="true"]').length); + ok(um === "VanVan" && wieViele === 1, + `ein Klick weiter ist nur noch sie gewaehlt (${um}, ${wieViele} gewaehlt)`); + } + + /* ===================================================================== + 4. NICHTS KAPUTT + ===================================================================== */ + melde(""); + melde("=== 4. Die Konsole ==="); + ok(konsole.length === 0, + konsole.length ? `Fehler in der Konsole: ${konsole[0]}` : "keine Fehler in der Konsole"); + + if (process.env.SCHIRM) { + const pfad = fileURLToPath(new URL("pruef-entwicklung-kacheln.png", import.meta.url)); + await seite.screenshot({ path: pfad, fullPage: false }); + melde(" -- Bildschirmfoto: server/pruef-entwicklung-kacheln.png"); + } + await kontext.close(); +} finally { + await browser.close(); +} + +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } +process.exit(fehler ? 1 : 0); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 65ae51fb..2038374c 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609170226", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609170241", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609170226", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609170241", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609170226", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609170241", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609170226", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609170241", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/entwicklung.css b/workspace/assets/css/entwicklung.css index ed293405..89d7dc8d 100644 --- a/workspace/assets/css/entwicklung.css +++ b/workspace/assets/css/entwicklung.css @@ -68,7 +68,10 @@ .e-person { display: flex; flex-direction: column; gap: 3px; - min-width: 210px; flex: 1 1 210px; + /* Seit die drei Bilanz-Kaesten darin stehen, braucht die Kachel + mehr Grundbreite -- bei 210px wurde jeder Kasten einzeilig + gequetscht und die Ziffer rutschte unter ihr Wort. */ + min-width: 260px; flex: 1 1 300px; padding: 13px 15px; text-align: left; cursor: pointer; font: inherit; background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%); @@ -85,7 +88,44 @@ /* Die Abweichung ist das Ergebnis dieser Seite — sie darf als Einzige auffallen. */ .e-person__zahl--wichtig { color: var(--text); font-weight: 620; } -.e-person__zahl--alt { color: var(--text-still); } +.e-person__zahl--alt { color: var(--text-still); margin-top: 8px; } + +/* --------------------------------------------------------------------- + DIE DREI ZAHLEN AUF DER KACHEL (17.09.2026) + + Filipe, mit zwei Bildschirmfotos nebeneinander: „wieso ist diese seite + noch nicht im stil und perfektioniert wie die auf screen2". Der + Unterschied lag an den Zahlen: hier graue Textzeilen, drüben die + abgesetzten Kästen mit großer Ziffer. + + `.bilanz-zahl` kommt aus start.css und wird hier nur gesetzt — nicht + nachgebaut. Ein zweites Aussehen für dieselbe Sache läuft auseinander, + sobald jemand eins davon anfasst. + + AUTO-FIT STATT DREI SPALTEN: Auf dem Handy ist eine Kachel schmal, + am Rechner breit. `minmax` bricht genau dann um, wenn der Platz nicht + reicht — eine feste Spaltenzahl wäre wieder eine Rechnung von gestern. + ------------------------------------------------------------------- */ +.e-person__bilanz { + display: grid; gap: 8px; margin-top: 10px; + grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); +} +/* Die Kästen sitzen in einem Knopf und dürfen ihn nicht abfangen — + sonst trifft ein Tippen auf die Zahl die Kachel nicht. */ +.e-person__bilanz .bilanz-zahl { pointer-events: none; padding: 9px 11px; } +.e-person__bilanz .bilanz-zahl__n { font-size: 1.35rem; } +.e-person__bilanz .bilanz-zahl__text { font-size: .74rem; } + +/* WEN HABE ICH GERADE OFFEN? Vorher sah die Kachel nach dem Klick aus + wie vorher; wer scrollte, verlor den Faden. Gedämpft hell statt + grell — die Karte darunter ist die Hauptsache, die Kachel sagt nur, + wozu sie gehört. */ +.e-person[aria-pressed="true"] { + border-color: color-mix(in srgb, var(--akzent) 58%, transparent); + background: + linear-gradient(180deg, color-mix(in srgb, var(--akzent) 11%, transparent), transparent 62%), + color-mix(in srgb, var(--tinte) 90%, #ffffff 10%); +} /* --------------------------------------------------------------------- Die Karte diff --git a/workspace/assets/js/entwicklung.js b/workspace/assets/js/entwicklung.js index 37f76d03..9f360cc0 100644 --- a/workspace/assets/js/entwicklung.js +++ b/workspace/assets/js/entwicklung.js @@ -99,7 +99,14 @@ const kopf = document.querySelector('#karte .e-karte__stand'); if (kopf) kopf.textContent = satz; const kachel = personZahlen.get(letzteKarte.id); - if (kachel) kachel.textContent = satz; + if (kachel) { + /* Zwei Ziffern statt eines Satzes -- und der Ton wechselt auf + gruen, sobald nichts mehr offen ist. Ohne das bliebe eine + fertig angesehene Person blaugrau wie eine halbe. */ + kachel.angesehen.textContent = String(gesetzt); + kachel.offen.textContent = String(Math.max(0, r.gesamt - gesetzt)); + kachel.kasten.dataset.stufe = gesetzt >= r.gesamt ? 'gut' : 'offen'; + } /* Und die Köpfe der Kategorien -- sonst steht zugeklappt „0 / 7“ über sieben beantworteten Fragen. */ @@ -164,35 +171,65 @@ } /* ---- Die Personen --------------------------------------------------- */ + /* EIN BILANZ-KASTEN, wie ihn die Checkliste benutzt (17.09.2026). + + Filipe, mit zwei Bildschirmfotos nebeneinander: "wieso ist diese + seite noch nicht im stil und perfektioniert wie die auf screen2". + + Der Unterschied war nicht der Aufbau -- der stimmte -- sondern die + Zahlen: Hier standen sie als graue Textzeilen, drueben als + abgesetzte Kaesten mit grosser Ziffer und eigenem Ton. Zwei + Bretter, die dasselbe tun und verschieden aussehen, wirken wie + zwei verschiedene Sachen. + + `bilanz-zahl` steht in start.css und gehoert damit dem ganzen Haus, + nicht der Checkliste. Nachbauen waere eine zweite Wahrheit ueber + dasselbe Aussehen gewesen. */ + function bilanzZahl(n, text, stufe, hinweis) { + const kasten = el('div', 'bilanz-zahl'); + kasten.dataset.stufe = stufe; + if (hinweis) kasten.title = hinweis; + const ziffer = el('span', 'bilanz-zahl__n', String(n)); + kasten.append(ziffer); + kasten.append(el('span', 'bilanz-zahl__text', text)); + return { kasten, ziffer }; + } + function personKarte(p, lage) { const k = el('button', 'e-person'); k.type = 'button'; + /* JEDE PERSON IST EINZELN WAEHLBAR -- UND MAN SIEHT ES. + Vorher war die Kachel ein Knopf ohne Zustand: Man klickte jemanden + an, die Karte ging auf, und die Kachel sah aus wie vorher. Wer + dann scrollte, wusste nicht mehr, wen er offen hat. */ + k.setAttribute('aria-pressed', 'false'); + k.dataset.person = String(p.id); k.append(el('span', 'e-person__name', p.name)); k.append(el('span', 'e-person__rolle', p.rolle_name)); - const zahlen = el('span', 'e-person__zahlen'); - const wieViele = el('span', 'e-person__zahl', - `${p.gesetzt} von ${lage.gesamt_fremd} angesehen`); - /* Die Kachel merkt sich ihr Zahlenfeld -- daran zieht - zaehlerNachziehen() sie nach, ohne die Liste neu zu holen. */ - personZahlen.set(p.id, wieViele); - zahlen.append(wieViele); - /* DIE ABWEICHUNG STEHT VORNE UND NICHT IM DETAIL. Sie ist das - Ergebnis dieser Seite: Wo zwei Leute dasselbe sehen, gibt es + const gesamt = Number(lage.gesamt_fremd) || 0; + const gesetzt = Number(p.gesetzt) || 0; + const zahlen = el('div', 'e-person__bilanz'); + /* DIESELBEN DREI WIE DRUEBEN, und die ersten beiden teilen dasselbe + Ganze: angesehen + noch offen = alle Punkte. Die dritte ist das + Ergebnis dieser Seite -- wo zwei Leute dasselbe sehen, gibt es nichts zu besprechen. */ - if (p.abweichungen) { - const z = el('span', 'e-person__zahl e-person__zahl--wichtig', - `${p.abweichungen} × verschieden gesehen`); - z.title = 'Hier lohnt das Gespräch – nicht dort, wo ihr einig seid.'; - zahlen.append(z); - } + const a = bilanzZahl(gesetzt, 'angesehen', + gesamt && gesetzt >= gesamt ? 'gut' : 'offen'); + const o = bilanzZahl(Math.max(0, gesamt - gesetzt), 'noch offen', 'offen'); + const v = bilanzZahl(Number(p.abweichungen) || 0, 'verschieden gesehen', + 'verbessern', 'Hier lohnt das Gespräch – nicht dort, wo ihr einig seid.'); + zahlen.append(a.kasten, o.kasten, v.kasten); + /* Die Kachel merkt sich ihre Ziffern -- daran zieht + zaehlerNachziehen() sie nach, ohne die Liste neu zu holen. */ + personZahlen.set(p.id, { angesehen: a.ziffer, offen: o.ziffer, kasten: a.kasten }); + k.append(zahlen); if (p.veraltet) { const z = el('span', 'e-person__zahl e-person__zahl--alt', `${p.veraltet} älter als ${lage.verblasst_nach} Tage`); z.title = 'Eine alte Einschätzung ist keine Einschätzung mehr.'; - zahlen.append(z); + k.append(z); } - k.append(zahlen); /* NUR HIER WIRD GESPRUNGEN: Wer eine Person anklickt, will zu ihrer Karte. Überall sonst bleibt die Seite stehen, wo sie steht. */ k.addEventListener('click', () => karte(p.id, true)); @@ -220,6 +257,13 @@ alles darunter weg. */ async function karte(id, springen = false) { offen = id; + /* WER IST GERADE OFFEN? Die Antwort gehoert auf die Kacheln, nicht + nur in eine Variable. `aria-pressed` sagt es zugleich einem + Vorleseprogramm -- eine Umrandung allein kann das nicht. */ + for (const kachel of document.querySelectorAll('#personen .e-person')) { + kachel.setAttribute('aria-pressed', + kachel.dataset.person === String(id) ? 'true' : 'false'); + } const ziel = $('karte'); const vorherY = window.scrollY; ziel.hidden = false; diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index f59e20f0..c2803481 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - + @@ -282,10 +282,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 606cc01f..a184ec09 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -168,10 +168,10 @@ - - - - - + + + + + diff --git a/workspace/befinden.html b/workspace/befinden.html index f6c004fe..201259fe 100644 --- a/workspace/befinden.html +++ b/workspace/befinden.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -105,9 +105,9 @@ - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 8c109af7..85acfaa4 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -181,14 +181,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index 4361a17b..ca61d471 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -89,11 +89,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index 0d4989eb..32b51899 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -12,26 +12,26 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -341,10 +341,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index 07e766f2..65589591 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -179,12 +179,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/crew-index.html b/workspace/crew-index.html index 8cf884d4..26e18ad7 100644 --- a/workspace/crew-index.html +++ b/workspace/crew-index.html @@ -36,15 +36,15 @@ - - - + + + - + @@ -339,6 +339,6 @@ - + diff --git a/workspace/crew.webmanifest b/workspace/crew.webmanifest index 7c6b5bc3..1eb99c70 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609170226", + "src": "/assets/img/app-symbole/crew-192.png?v=202609170241", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609170226", + "src": "/assets/img/app-symbole/crew-512.png?v=202609170241", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609170226", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609170241", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609170226", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609170241", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index c462efc7..6665e836 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -108,10 +108,10 @@ - - - - - + + + + + diff --git a/workspace/entwicklung.html b/workspace/entwicklung.html index 4d8da6bc..3628505d 100644 --- a/workspace/entwicklung.html +++ b/workspace/entwicklung.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -67,7 +67,7 @@ @@ -102,9 +102,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 89d6d6a6..7af99ca2 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -30,9 +30,9 @@ - - - + + + @@ -401,6 +401,6 @@ - + diff --git a/workspace/kalender.html b/workspace/kalender.html index 90ae37b5..7ba09d55 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -377,11 +377,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index a12a807f..d74e6ba8 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -444,10 +444,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index 29704a35..02158f66 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -423,10 +423,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index 42982555..f3f8434d 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -214,11 +214,11 @@ - - - - - - + + + + + + diff --git a/workspace/rechte.html b/workspace/rechte.html index 95d6b605..4ccfdea7 100644 --- a/workspace/rechte.html +++ b/workspace/rechte.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -134,9 +134,9 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - + + + + diff --git a/workspace/report.html b/workspace/report.html index 4ad30399..7ab9bbe3 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -106,11 +106,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index cae27e82..1017869d 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -173,10 +173,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 383463e5..35635c71 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -12,21 +12,21 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - + + - + @@ -674,10 +674,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index 215a4089..4ca54d31 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -85,11 +85,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 3dd94b09..312efc55 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -146,11 +146,11 @@ - - - - - - + + + + + + diff --git a/workspace/talente.html b/workspace/talente.html index b8cf18d5..89b3cb6f 100644 --- a/workspace/talente.html +++ b/workspace/talente.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -94,9 +94,9 @@ - - - - + + + + diff --git a/workspace/team.html b/workspace/team.html index f01ec918..aee10061 100644 --- a/workspace/team.html +++ b/workspace/team.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + @@ -110,10 +110,10 @@ - - - - - + + + + + diff --git a/workspace/teamlage.html b/workspace/teamlage.html index 6260e97f..7b5c8bfe 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/treff-moderation.html b/workspace/treff-moderation.html index 2f10b351..31b1af30 100644 --- a/workspace/treff-moderation.html +++ b/workspace/treff-moderation.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -110,9 +110,9 @@ - - - - + + + + diff --git a/workspace/treff-regeln.html b/workspace/treff-regeln.html index 047088a5..755809cb 100644 --- a/workspace/treff-regeln.html +++ b/workspace/treff-regeln.html @@ -7,18 +7,18 @@ - - - - - - - + + + + + + + - + @@ -211,10 +211,10 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 3745424c..974cbde2 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -12,22 +12,22 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -149,11 +149,11 @@ - + - - - - + + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index ca703a17..0f49becc 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -190,10 +190,10 @@ - - - - - + + + + +