diff --git a/pruef-kalender-computer.png b/pruef-kalender-computer.png index 7a81aa95..500b7b34 100644 Binary files a/pruef-kalender-computer.png and b/pruef-kalender-computer.png differ diff --git a/pruef-kalender-handy.png b/pruef-kalender-handy.png index 7363f5a5..cd8b6edc 100644 Binary files a/pruef-kalender-handy.png and b/pruef-kalender-handy.png differ diff --git a/server/pruef-workspace-seiten.mjs b/server/pruef-workspace-seiten.mjs index 219e379d..47800b14 100644 --- a/server/pruef-workspace-seiten.mjs +++ b/server/pruef-workspace-seiten.mjs @@ -98,12 +98,25 @@ for (const [breite, hoehe, wie] of [[1440, 1000, "Computer"], [390, 844, "Handy" Verweis auf die Startseite? */ rueckweg: document.querySelector(".kopfleiste .marke__haupt")?.tagName === "A" ? document.querySelector(".kopfleiste .marke__haupt").getAttribute("href") : null, + /* Jede Unterseite trägt Zeichen und Farbton ihrer Kachel. Beides + kommt aus bereiche.js, derselben Liste wie die Kachel -- fehlt + eins davon, ist die Zuordnung gerissen und die Seite sieht + wieder aus wie jede andere. */ + ton: document.body.dataset.ton || null, + kopfZeichen: document.querySelectorAll(".kopf-zeichen__svg path").length, + kopfZeichenFarbe: document.querySelector(".kopf-zeichen__svg") + ? getComputedStyle(document.querySelector(".kopf-zeichen__svg")).stroke : null, + wasserzeichen: document.querySelectorAll(".kopf-wasserzeichen__svg path").length, })); const istStart = s === "start.html"; const markeGut = r.markeHaupt && r.markeLeise && r.markeRaute === 1 && (istStart || r.rueckweg === "start.html"); + /* Auf der Startseite gibt es keinen Seitenkopf -- dort sind die + Kacheln selbst der Kopf. Überall sonst ist er Pflicht. */ + const kopfGut = istStart + || (r.ton !== null && r.kopfZeichen > 0 && r.wasserzeichen > 0); const gut = r.ueber <= 0 && konsole.length === 0 && r.abmelden !== false - && markeGut + && markeGut && kopfGut && (r.breit === null || r.breit === "1240px") && (r.kopfzeile === null || r.kopfzeile === "flex"); if (!gut) fehler++; @@ -111,7 +124,8 @@ for (const [breite, hoehe, wie] of [[1440, 1000, "Computer"], [390, 844, "Handy" + `über=${String(r.ueber).padStart(3)}px abmelden=${r.abmelden}` + ` marke=${r.markeHaupt ? `"${r.markeHaupt}"◆"${r.markeLeise}"` : "FEHLT"}` + `${istStart ? "" : ` zurück=${r.rueckweg || "FEHLT"}`}` - + ` breit=${r.breit} kopfzeile=${r.kopfzeile}` + + `${istStart ? "" : ` ton=${r.ton || "FEHLT"} zeichen=${r.kopfZeichen}/${r.wasserzeichen}`}` + + ` breit=${r.breit}` + (konsole.length ? " KONSOLE: " + konsole.join(" | ") : "") + (r.schuld.length ? " -> " + r.schuld.join(" | ") : "")); } diff --git a/workspace/assets/css/kalender.css b/workspace/assets/css/kalender.css index fa9d7826..c687cb5c 100644 --- a/workspace/assets/css/kalender.css +++ b/workspace/assets/css/kalender.css @@ -17,10 +17,21 @@ Man liest zuerst, WO man ist, und bewegt sich dann. Deshalb steht der Monat groß und die Navigation daneben, nicht umgekehrt. */ .k-kopf { + position: relative; overflow: hidden; display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 16px; - margin-bottom: 22px; + margin-bottom: 22px; padding-bottom: 18px; + /* Dieselbe auslaufende Trennlinie wie auf allen anderen Seiten -- der + Kalender hat zwar einen eigenen Kopf, gehört aber zur selben + Familie. Die Farbe kommt aus data-ton am body (kopf.js). */ + border-bottom: 1px solid transparent; + border-image: linear-gradient(90deg, + color-mix(in srgb, var(--ton, var(--akzent)) 40%, var(--rand-hell)), + transparent 62%) 1; } +/* Zeichen und Zeitraum nebeneinander (die Plakette setzt kopf.js). */ +.k-kopf .kopf-zeile__marke { align-items: center; } +.k-kopf .k-kopf__titel { min-width: 0; } .k-zeitraum { margin: 0; font-size: clamp(1.7rem, 3.2vw, 2.4rem); diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index 7c07e025..37b38894 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -230,11 +230,111 @@ body.start > main { position: relative; z-index: 1; } .zurueck { color: inherit; text-decoration: none; border-bottom: 1px solid transparent; } .zurueck:hover { border-bottom-color: currentColor; } +/* ---------- Der Kopf jeder Unterseite ------------------------------------ + + Bis zum 01.09.2026 war das eine Zeile Text mit einem Knopf daneben -- + auf allen dreizehn Seiten dieselbe, in derselben Farbe, ohne Zeichen. + Wer eine Kachel anklickte, verlor beim Übergang jede Spur davon, + woher er kam. + + Jetzt trägt der Kopf Zeichen UND Farbton seiner Kachel (gesetzt von + kopf.js aus derselben Liste, aus der auch die Kachel kommt) und + bekommt dieselbe Behandlung wie sie: Plakette mit dem Zeichen, ein + leuchtender Strich über der Bereichsangabe, ein sehr schwaches + Wasserzeichen dahinter und eine auslaufende Trennlinie darunter. + + Nichts davon ist Schmuck um seiner selbst willen: Es ist dieselbe + Bildsprache wie auf der Startseite, und dadurch weiß man ohne Lesen, + wo man ist. */ .kopf-zeile { + position: relative; + overflow: hidden; display: flex; align-items: flex-end; justify-content: space-between; - gap: 20px; flex-wrap: wrap; margin-bottom: 24px; + gap: 20px; flex-wrap: wrap; + margin-bottom: 28px; padding: 0 0 20px; + border-bottom: 1px solid transparent; + border-image: linear-gradient(90deg, + color-mix(in srgb, var(--ton, var(--akzent)) 40%, var(--rand-hell)), + transparent 62%) 1; +} +.kopf-zeile .titel { + margin: 0; + font-size: clamp(1.5rem, 2.6vw, 2.1rem); + letter-spacing: -.02em; + color: #f2f7fd; + text-shadow: 0 2px 22px color-mix(in srgb, var(--ton, var(--akzent)) 22%, transparent); +} +.kopf-zeile .unterzeile { margin-bottom: 0; } + +/* Zeichen und Text nebeneinander. Der Block darf schrumpfen (min-width), + sonst drückt er den Knopf rechts aus dem Bild -- derselbe Fehler wie + im August in der Kopfleiste. */ +.kopf-zeile__marke { + display: flex; align-items: center; gap: 16px; + min-width: 0; flex: 1 1 auto; + position: relative; z-index: 1; +} +.kopf-zeichen { + flex: none; + display: grid; place-items: center; + width: 52px; height: 52px; border-radius: 15px; + border: 1px solid color-mix(in srgb, var(--ton, var(--akzent)) 34%, transparent); + background: + radial-gradient(120% 120% at 30% 18%, + color-mix(in srgb, var(--ton, var(--akzent)) 26%, transparent), transparent 72%), + rgba(255, 255, 255, .04); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, .09), + 0 12px 28px -14px color-mix(in srgb, var(--ton, var(--akzent)) 70%, transparent); +} +.kopf-zeichen__svg { + width: 26px; height: 26px; + fill: none; stroke: var(--ton, var(--akzent)); stroke-width: 1.6; + stroke-linecap: round; stroke-linejoin: round; +} + +/* Der Bereichsname über dem Titel bekommt denselben leuchtenden Strich + wie die Abschnitte auf der Startseite -- so gehört sichtbar zusammen, + was zusammengehört. */ +.kopf-zeile .marke { + display: flex; align-items: center; gap: 9px; + margin-bottom: 7px; + color: color-mix(in srgb, var(--ton, var(--akzent)) 62%, #cfe0f0); +} +.kopf-zeile .marke::before { + content: ""; + width: 22px; height: 2px; border-radius: 2px; + background: linear-gradient(90deg, var(--ton, var(--akzent)), transparent); +} + +/* Dasselbe Zeichen noch einmal, riesig und fast unsichtbar. Es liegt im + Kopf (overflow: hidden schneidet es sauber ab) und macht jede Seite + auf den ersten Blick unverwechselbar, ohne dass man es bewusst + wahrnimmt. */ +.kopf-wasserzeichen { + position: absolute; right: -18px; top: 50%; + transform: translateY(-50%) rotate(-8deg); + pointer-events: none; + opacity: .06; + z-index: 0; +} +.kopf-wasserzeichen__svg { + width: 190px; height: 190px; + fill: none; stroke: var(--ton, var(--akzent)); stroke-width: 1; + stroke-linecap: round; stroke-linejoin: round; +} +/* Auf schmalen Bildschirmen läge das Wasserzeichen unter dem Text und + würde ihn unruhig machen -- dort bleibt es weg. */ +@media (max-width: 720px) { + .kopf-wasserzeichen { display: none; } + .kopf-zeichen { width: 42px; height: 42px; border-radius: 12px; } + .kopf-zeichen__svg { width: 21px; height: 21px; } + .kopf-zeile__marke { gap: 12px; } +} +@media (forced-colors: active) { + .kopf-wasserzeichen { display: none; } + .kopf-zeichen__svg { stroke: CanvasText; } } -.kopf-zeile .titel { margin: 0; font-size: clamp(1.4rem, 2.2vw, 1.85rem); } .leise { color: var(--text-still); font-weight: 400; } diff --git a/workspace/assets/js/bereiche.js b/workspace/assets/js/bereiche.js new file mode 100644 index 00000000..2e8383d0 --- /dev/null +++ b/workspace/assets/js/bereiche.js @@ -0,0 +1,175 @@ +/* =================================================================== + Die Bereiche -- EINE Quelle für die ganze Anwendung. + + Zeichen, Farbton, Rolle und Ziel jedes Bereichs standen bis zum + 01.09.2026 nur in start.js. Seit jede Unterseite Zeichen und Farbe + ihrer Kachel trägt, brauchen sie auch die Kopfleisten -- und eine + zweite, abgeschriebene Liste wäre der sichere Weg, dass "Aufgaben" + irgendwann auf der Startseite gelb und auf der Aufgabenseite grün ist. + + Deshalb liegt beides jetzt hier und wird von start.js UND kopf.js + benutzt. Wer eine Kachel ändert, ändert damit automatisch auch den + Kopf der zugehörigen Seite. + =================================================================== */ +(() => { + 'use strict'; + + /* ---------- Zeichen ------------------------------------------------------ + + Alle im selben Raster (24×24), alle nur aus Linien -- gefüllte Zeichen + wirken neben Text wie Flecken. Eigene statt einer Fremdbibliothek: + siebzehn Pfade sind kleiner als jede Schriftart und kosten keine + zusätzliche Lieferkette. */ + + const ZEICHEN = { + dashboard: ['M4.5 4.5h6v6h-6z', 'M13.5 4.5h6v4h-6z', 'M13.5 11.5h6v8h-6z', 'M4.5 13.5h6v6h-6z'], + aufgaben: ['M4.5 5h4v14h-4z', 'M10 5h4v9h-4z', 'M15.5 5h4v11.5h-4z'], + kalender: ['M4 6.5h16v13H4z', 'M4 10.5h16', 'M8.5 3.5v4', 'M15.5 3.5v4'], + calls: ['M3.5 7h11v10h-11z', 'M14.5 11l6-3.5v9l-6-3.5z'], + dateien: ['M3.5 6.5h5.5l2 2h9.5v11h-17z'], + profil: ['M12 7a3.2 3.2 0 1 0 0 6.4A3.2 3.2 0 0 0 12 7z', + 'M5.5 20c.7-3.3 3.3-5.3 6.5-5.3s5.8 2 6.5 5.3'], + startcheck: ['M6 4.5h12v15H6z', 'M9.2 9.6l1.7 1.7 3.6-3.7', 'M9.2 15.2h5.6'], + live: ['M12 10.2a1.8 1.8 0 1 0 0 3.6 1.8 1.8 0 0 0 0-3.6z', + 'M8.2 8.2a5.4 5.4 0 0 0 0 7.6', 'M15.8 15.8a5.4 5.4 0 0 0 0-7.6', + 'M5.4 5.4a9.4 9.4 0 0 0 0 13.2', 'M18.6 18.6a9.4 9.4 0 0 0 0-13.2'], + content: ['M3.5 6h17v13h-17z', 'M3.5 10h17', 'M8 6l-1.4 4', 'M13 6l-1.4 4', 'M18 6l-1.4 4'], + technik: ['M4 7.5h16', 'M4 12h16', 'M4 16.5h16', + 'M9 7.5a1.4 1.4 0 1 0 0-.02z', 'M15 12a1.4 1.4 0 1 0 0-.02z', + 'M7.5 16.5a1.4 1.4 0 1 0 0-.02z'], + community: ['M9 8.6a2.7 2.7 0 1 0 0 5.4 2.7 2.7 0 0 0 0-5.4z', + 'M3.5 19.5c.6-2.8 2.8-4.5 5.5-4.5s4.9 1.7 5.5 4.5', + 'M16 8.8a2.4 2.4 0 1 1 1.6 4.3', 'M17 15.2c2 .5 3.3 2 3.7 4.3'], + schutz: ['M12 3.6 5.6 6.3v5.1c0 3.8 2.6 7.2 6.4 8.4 3.8-1.2 6.4-4.6 6.4-8.4V6.3z', + 'M9.4 11.8l1.9 1.9 3.4-3.6'], + reports: ['M4 19.5h16', 'M7.5 16.5V9.5', 'M12 16.5V5.5', 'M16.5 16.5v-5'], + personen: ['M14.4 6.2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z', 'M11.6 12.2 4 19.8', + 'M6.4 17.4l2 2', 'M8.6 15.2l2 2'], + scouting: ['M3.5 5.5h17l-6.6 7.6v6.4l-3.8-2.1v-4.3z'], + wissen: ['M4 5h6.4c.9 0 1.6.7 1.6 1.6V19c0-.9-.7-1.6-1.6-1.6H4z', + 'M20 5h-6.4c-.9 0-1.6.7-1.6 1.6V19c0-.9.7-1.6 1.6-1.6H20z'], + automation: ['M13.2 3.2 6 13.4h5.2l-.8 7.4 7.6-10.6h-5.2z'], + }; + + /* ---------- Die Bereiche ------------------------------------------------- + + `rollen` steuert, wem ein Bereich überhaupt erscheint -- serverseitig + wird es ohnehin noch einmal geprüft, das hier ist nur Höflichkeit. + + `ton` ist der Farbplatz 1..17 -- JEDE Kachel hat ihre eigene Farbe. + + Ein erster Versuch mit frei gewählten Farben ergab Paare mit einem + Abstand von 1,4: praktisch dieselbe Farbe, das sieht nach Zufall aus + statt nach Absicht. Deshalb sind Farben UND Zuordnung gerechnet, von + tools/kachel-farben.mjs. Die Überlegung dahinter: Das Auge vergleicht + nur, was nebeneinander liegt. Siebzehn Farben müssen sich also nicht + alle voneinander unterscheiden, sondern nur von ihren Nachbarn im + Raster -- und da sind es jetzt mindestens 105,9 Grad. + + Wer hier Kacheln umsortiert oder eine hinzufügt, ändert damit die + Nachbarschaften. Dann gehört tools/kachel-farben.mjs neu gelaufen + (dort die Liste NACHBARN anpassen), sonst stimmt der Abstand nicht + mehr und niemand merkt es. */ + + const GRUPPEN = [ + { + name: 'Täglich', + unter: 'Was du sowieso jeden Tag aufmachst', + bereiche: [ + { name: 'Dashboard', unter: 'Alle Creator auf einen Blick', zeichen: 'dashboard', ton: 3, + gross: true, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'uebersicht.html' }, + { name: 'Aufgaben', unter: 'Offen, in Arbeit, Review', zeichen: 'aufgaben', ton: 9, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'aufgaben.html' }, + { name: 'Kalender', unter: 'Termine und Calls', zeichen: 'kalender', ton: 15, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'kalender.html' }, + { name: 'Calls & Protokolle', unter: 'Gespräche mit To-dos', zeichen: 'calls', ton: 8, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'calls.html' }, + { name: 'Dateien', unter: 'Ablage und Freigaben', zeichen: 'dateien', ton: 16, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'dateien.html' }, + ], + }, + { + name: 'Rund um den Creator', + unter: 'Die Betreuungsakte – Stand, Zahlen, Verlauf', + bereiche: [ + { name: 'Creator-Profile', unter: 'Ziele und 90-Tage-Plan', zeichen: 'profil', ton: 4, + rollen: ['admin', 'manager', 'scout'], ziel: 'profil.html' }, + { name: 'Mein Profil', unter: 'Deine Ziele und Pläne', zeichen: 'profil', ton: 4, + rollen: ['creator'], ziel: 'profil.html' }, + { name: 'Start-Check', unter: 'Erstanalyse, 16 Punkte', zeichen: 'startcheck', ton: 14, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'startcheck.html' }, + { name: 'LIVE-Analyse', unter: 'Vorher, während, danach', zeichen: 'live', ton: 6, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=live' }, + { name: 'Content-Planung', unter: 'Idee bis Upload', zeichen: 'content', ton: 10, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'content.html' }, + { name: 'Community', unter: 'Moderation und Konflikte', zeichen: 'community', ton: 5, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=community' }, + { name: 'Technik', unter: 'Setup, Fehler, Lösungen', zeichen: 'technik', ton: 11, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=technik' }, + { name: 'Schutz & Regeln', unter: 'Richtlinien und Vorfälle', zeichen: 'schutz', ton: 1, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=schutz' }, + { name: 'Reports & Review', unter: 'Woche und Entscheidung', zeichen: 'reports', ton: 13, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'report.html' }, + ], + }, + { + name: 'Team & System', + unter: 'Was den Laden am Laufen hält', + /* Für einen Creator bleibt von dieser Gruppe genau eine Kachel übrig: + die Wissens-Bibliothek. "Team & System" wäre dann eine Überschrift, + die etwas verspricht, was gar nicht da ist -- und die klingt, als + gäbe es dahinter noch einen Bereich, den er nicht sehen darf. Er + liest stattdessen "Wissen", und das beschreibt genau, was dort + steht. Der Untertitel wechselt mit. */ + nameCreator: 'Wissen', + unterCreator: 'Nachschlagen, wenn du etwas brauchst', + bereiche: [ + { name: 'Personen & Zugänge', unter: 'Rollen und Codes', zeichen: 'personen', ton: 7, + rollen: ['admin', 'manager'], ziel: 'personen.html' }, + { name: 'Scout-Pipeline', unter: 'Entdeckt bis übergeben', zeichen: 'scouting', ton: 12, + rollen: ['admin', 'manager', 'scout'], ziel: 'scouting.html' }, + { name: 'Wissens-Bibliothek', unter: 'Anleitungen und PDFs', zeichen: 'wissen', ton: 2, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'wissen.html' }, + { name: 'Automationen', unter: 'Was von allein passiert', zeichen: 'automation', ton: 17, + rollen: ['admin', 'manager'], ziel: 'automation.html' }, + ], + }, + ]; + + /* Ein Bereich anhand des Dateinamens finden. Die Bereichsseite + (bereich.html?b=live) trägt ihren Bereich in der Adresse -- deshalb + wird auch die Abfrage verglichen, nicht nur der Dateiname. */ + function zuSeite(pfad, abfrage) { + const datei = String(pfad).split('/').pop() || 'start.html'; + const gesucht = datei + (abfrage || ''); + let treffer = null; + for (const g of GRUPPEN) { + for (const b of g.bereiche) { + if (b.ziel === gesucht) return b; + /* Ohne Abfrage: der erste Bereich mit passendem Dateinamen. + "Mein Profil" und "Creator-Profile" zeigen auf dieselbe Datei + und teilen sich Zeichen und Ton -- da ist beides richtig. */ + if (!treffer && b.ziel.split('?')[0] === datei) treffer = b; + } + } + return treffer; + } + + /* Ein SVG mit dem Zeichen eines Bereichs. Klasse frei wählbar, damit + Kachel, Hinweiszeile und Seitenkopf dieselbe Form in + unterschiedlicher Größe benutzen können. */ + function zeichenBauen(name, klasse = 'kachel__svg') { + 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); + for (const d of (ZEICHEN[name] || [])) { + const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + p.setAttribute('d', d); + svg.append(p); + } + return svg; + } + + window.Bereiche = { ZEICHEN, GRUPPEN, zuSeite, zeichenBauen }; +})(); diff --git a/workspace/assets/js/kopf.js b/workspace/assets/js/kopf.js index b3ac0939..e2e4f70a 100644 --- a/workspace/assets/js/kopf.js +++ b/workspace/assets/js/kopf.js @@ -116,6 +116,70 @@ return true; } + /* ---------- Jede Seite trägt Zeichen und Farbe ihrer Kachel ------------- + + Auf der Startseite hat jeder Bereich sein eigenes Zeichen und seinen + eigenen Farbton. Bisher endete das an der Kachel: Wer sie anklickte, + landete auf einer Seite, der man nicht mehr ansah, woher sie kam -- + dreizehn Seiten, alle in demselben Blau, alle ohne Zeichen. + + Jetzt wird die Kachel weitergereicht. Der Kopf der Seite bekommt + dasselbe Zeichen groß daneben, denselben Farbton für Strich, Zeichen + und Titel, und dasselbe Zeichen noch einmal riesig und fast + unsichtbar als Wasserzeichen -- genau die Behandlung, die auf der + Startseite aus siebzehn Kästen siebzehn unterscheidbare Bereiche + gemacht hat. + + Die Zuordnung kommt aus assets/js/bereiche.js, also aus derselben + Liste wie die Kachel selbst. Wer dort eine Farbe ändert, ändert + Kachel UND Seite -- sie können gar nicht auseinanderlaufen. */ + (function seiteEinfaerben() { + const B = window.Bereiche; + /* Der Kalender hat einen eigenen Kopf (.k-kopf), weil dort der + Zeitraum die Überschrift ist. Er soll sein Zeichen trotzdem + bekommen -- sonst wäre ausgerechnet die aufwendigste Seite die + einzige ohne. */ + const kopf = document.querySelector('.kopf-zeile, .k-kopf'); + if (!B || !kopf) return; + + const bereich = B.zuSeite(location.pathname, location.search); + if (!bereich) return; + + /* Der Ton hängt am body: Damit erreicht er auch Bauteile, die weit + unten auf der Seite stehen (Karten, Knöpfe, Zähler) -- nicht nur + den Kopf. Die 17 Töne selbst stehen in start.css und hängen nur am + Merkmal data-ton, nicht an einer Klasse. */ + document.body.dataset.ton = String(bereich.ton); + + /* Der Kopf ist bisher ein