diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ceb5231..ae8d379 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -347,3 +347,29 @@ Umut wollte an jedem Element eigenes CSS **und** eigenes Skript setzen können. - **In der Editorvorschau wird nichts ausgeführt.** Die Vorschau soll das Layout zeigen, nicht Code laufen lassen, der die Seite unter den Händen des Bearbeiters verändert. Zwei echte Löcher kamen beim Testen heraus. `& .block-text` wurde zu `#id.block-text`, weil das führende Leerzeichen wegtrimmt wurde – aus dem Nachfahren wurde derselbe Knoten, die Regel traf das falsche Element. Und `url(//fremd.example/x.png)` kam durch, weil die Prüfung jedes `/` erlaubte: eine protokollrelative Adresse hätte über das Schema der Seite von Dritten geladen. Beides ist behoben und durch Tests abgedeckt. + +## ADR 029 – Verwaltung: ein Rahmen statt sieben Kopfzeilen (12.09.2026) + +Umuts Auftrag war „backend komplett neugestalten, einfacher schöner Bedienbarkeit komplett einfach“, mit ausdrücklicher Gestaltungsfreiheit. Das Problem war weniger die Optik als die Orientierung: jede Verwaltungsseite trug ihre eigene Kopfzeile und ihre eigene, unvollständige Liste von Links auf die anderen Seiten – die Seitenübersicht verlinkte sieben Bereiche, das Menü fünf, die Fassungen drei. Wo man war, stand nirgends. + +**Ein Rahmen für alle.** `templates/admin/shell.html.twig` trägt die Navigation, die Flash-Meldungen und die Kopfzeile mit Titel, einem Satz Erklärung und Platz für die Hauptaktion. Jede Verwaltungsseite füllt nur noch `heading`, `lead`, `actions` und `content`. Die Navigationsliste steht einmal da; die aktive Seite ist über `aria-current="page"` markiert, also auch ohne Farbe erkennbar. Auf schmalen Fenstern wird die Seitenleiste zu einer waagerecht scrollbaren Leiste – ohne JavaScript, weil die Verwaltung sonst nichts davon braucht. + +**Eine Zeile je Seite statt einer überladenen Karte.** Vorher hing an jeder Seite eine Reihe von sechs Aufklappfeldern, die als absolut positionierte Kästen über den Rand der Karte hinausragten. Jetzt zeigt die Zeile, was man täglich braucht – Titel, Adresse, Zustand, „Bearbeiten“, „Fassungen“ – und ein einziges Aufklappfeld „Einstellungen“ öffnet darunter, an seinem Platz, die Abschnitte Seitendaten, Adresse, Planung und Entfernen. Nichts wird mehr abgeschnitten, und man sieht alles zu einer Seite auf einmal. + +**Das Stylesheet ist geteilt.** `admin.css` war ein gewachsener Block, in dem Grundelemente, Verwaltungsseiten und die Editormaschine in denselben Zeilen standen. Die Editorregeln stehen jetzt in `editor.css`, das nur der Editor und die Mediathek laden; `admin.css` ist neu geschrieben und lesbar. Beim Zerlegen ist mir ein Fehler unterlaufen, den erst der Browsertest gefunden hat: mein Skript hat am Ende jedes `@media`-Blocks **alle** schließenden Klammern abgeschnitten statt nur einer. Die Datei war damit ab dem ersten Medienblock ungültig, der Browser hat 173 Regeln verworfen und nur 29 behalten – auffällig wurde das an der Vorschaubreite, die um vier Pixel abwich, weil der Standardrahmen des iframe nicht mehr entfernt wurde. Vier Pixel, hinter denen dreiviertel des Editor-Stylesheets fehlte. + +**Systemschrift statt Arial**, und eine dunkle Seitenleiste als ruhiger Anker. Für die Verwaltung wird nichts nachgeladen; Google Fonts bleiben der Website vorbehalten, wo Umut sie auswählt. + +## ADR 029 – Verwaltung: ein Rahmen statt sieben Kopfzeilen (12.09.2026) + +Umuts Auftrag war „backend komplett neugestalten, einfacher schöner Bedienbarkeit komplett einfach“, mit ausdrücklicher Gestaltungsfreiheit. Das Problem war weniger die Optik als die Orientierung: jede Verwaltungsseite trug ihre eigene Kopfzeile und ihre eigene, unvollständige Liste von Links auf die anderen Seiten – die Seitenübersicht verlinkte sieben Bereiche, das Menü fünf, die Fassungen drei. Wo man war, stand nirgends. + +**Ein Rahmen für alle.** `templates/admin/shell.html.twig` trägt die Navigation, die Meldungen und die Kopfzeile mit Titel, einem Satz Erklärung und Platz für die Hauptaktion. Jede Verwaltungsseite füllt nur noch `heading`, `lead`, `actions` und `content`. Die Navigation steht einmal da; die aktive Seite ist über `aria-current="page"` markiert, also nicht nur an der Farbe erkennbar. Auf schmalen Fenstern wird die Seitenleiste zu einer waagerecht scrollbaren Leiste – ohne JavaScript, weil die Verwaltung sonst keines braucht. + +**Eine Zeile je Seite statt einer überladenen Karte.** Vorher hing an jeder Seite eine Reihe von sechs Aufklappfeldern, deren Inhalt als absolut positionierter Kasten über den Rand der Karte hinausragte. Jetzt zeigt die Zeile, was man täglich braucht – Titel, Adresse, Zustand, „Bearbeiten“, „Fassungen“ – und ein einziges Aufklappfeld „Einstellungen“ öffnet darunter, an seinem Platz, mit den Abschnitten Seitendaten, Adresse, Planung und Entfernen. Nichts wird mehr abgeschnitten, und man sieht alles zu einer Seite auf einmal. Tabellen scrollen seitlich, statt die Karte aus dem Fenster zu schieben. + +**Das Stylesheet ist geteilt.** `admin.css` war ein gewachsener Block, in dem Grundelemente, Verwaltungsseiten und die Editormaschine in denselben Zeilen standen. Die Editorregeln stehen jetzt in `editor.css`, das nur Editor und Mediathek laden; `admin.css` ist neu geschrieben und lesbar. Der Editor behält seinen Aufbau – drei Spalten, Vorschau in der Mitte – und übernimmt die neue Gestaltung: Systemschrift, gleiche Radien, gleiche Schaltflächen, und ein paar Größen im Panel, die zum Lesen zu klein waren. + +Beim Zerlegen ist mir ein Fehler unterlaufen, den erst der Browsertest gefunden hat: mein Skript schnitt am Ende jedes `@media`-Blocks **alle** schließenden Klammern ab statt nur einer. Die Datei war damit ab dem ersten Medienblock ungültig, der Browser behielt 29 von 202 Regeln. Symptom war eine Vorschaubreite von 1236 statt 1240 Pixeln, weil der Standardrahmen des iframe nicht mehr entfernt wurde – vier Pixel als einziges Anzeichen dafür, dass dreiviertel des Editor-Stylesheets fehlte. + +**Systemschrift statt Arial**, und eine dunkle Seitenleiste als ruhiger Anker. Für die Verwaltung wird nichts nachgeladen; Google Fonts bleiben der Website vorbehalten, wo Umut sie auswählt. diff --git a/docs/AUSBAUPLAN.md b/docs/AUSBAUPLAN.md index 4befd2e..06cc301 100644 --- a/docs/AUSBAUPLAN.md +++ b/docs/AUSBAUPLAN.md @@ -129,7 +129,7 @@ Umut hat den Umfang von G erweitert. Vier Punkte, in dieser Reihenfolge: 1. ~~**Kopf- und Fußbereich frei aus Elementen bauen**~~ — **erledigt am 12.09.2026** (ADR 026). Ursprünglicher Wortlaut: **Kopf- und Fußbereich frei aus Elementen bauen**, genau wie eine Sektion – keine feste Marke, keine feste Navigation im Template. Dazu wiederverwendbare Sektionen, die *verknüpft* und nicht kopiert werden: eine Änderung erreicht jede Seite, die sie nutzt. 2. ~~**Rich Text in Textelementen**~~ — **erledigt am 12.09.2026** (ADR 027). Umgesetzt für Textblöcke mit Werkzeugleiste und HTML-Ansicht; gefiltert wird auf dem Server beim Speichern. Überschrift, Button und Zitat bleiben bewusst Klartext. 3. ~~**Eigenes CSS und eigenes Skript je Element.**~~ — **erledigt am 12.09.2026** (ADR 028). Ursprünglicher Wortlaut: **Eigenes CSS und eigenes Skript je Element.** Eigenes CSS ist unkritisch und für alle mit Editorrolle nutzbar. **Eigenes Skript ist es nicht:** es ist gespeichertes, ausführbares JavaScript auf der Website. Wer es setzen darf, kann Code im Browser jedes Administrators ausführen. Deshalb: nur für Administratoren, Ausgabe über eine seiteneigene Skriptdatei statt inline, keine Ausführung in der Editorvorschau, und `script-src 'self'` nur auf Seiten, die tatsächlich ein Skript tragen. Der Preis steht in der Oberfläche. -4. **Verwaltung neu gestalten** – einfacher, schöner, ohne Nachdenken bedienbar. Gestaltungsfreiheit ausdrücklich bei mir; der bisherige Stil ist nicht bindend. Das betrifft Navigation zwischen den Bereichen, die Seitenliste, die Panels im Editor und die Formularseiten, die derzeit alle als lange Formulartabellen gebaut sind. +4. ~~**Verwaltung neu gestalten**~~ — **erledigt am 12.09.2026** (ADR 029). Der Editor behält seinen Aufbau und übernimmt die neue Gestaltung; neu gebaut sind der Rahmen, die Navigation, die Seitenliste und die Formularseiten. Ursprünglicher Wortlaut: **Verwaltung neu gestalten** – einfacher, schöner, ohne Nachdenken bedienbar. Gestaltungsfreiheit ausdrücklich bei mir; der bisherige Stil ist nicht bindend. Das betrifft Navigation zwischen den Bereichen, die Seitenliste, die Panels im Editor und die Formularseiten, die derzeit alle als lange Formulartabellen gebaut sind. Abnahme: Kopf- und Fußbereich sind im Editor gestaltbar und öffentlich sichtbar. Eine verknüpfte Sektion auf zwei Seiten ändert sich an beiden Stellen gleichzeitig. Ein Textelement lässt sich formatiert und als HTML bearbeiten, und keine Auszeichnung außerhalb der Positivliste übersteht das Speichern. Eigenes CSS wirkt, eigenes Skript wirkt nur für Administratoren gesetzt und nur auf der veröffentlichten Seite. Die Verwaltung ist ohne Anleitung bedienbar. diff --git a/docs/QUALITY.md b/docs/QUALITY.md index 0d57cb4..5d353ec 100644 --- a/docs/QUALITY.md +++ b/docs/QUALITY.md @@ -1,5 +1,21 @@ # Qualitätsbericht – erster ausführbarer Prototyp +## Ergänzung: Verwaltung neu gestaltet, 12.09.2026 + +- **201 PHP-Tests / 716 Assertions** grün, Playwright-Editortest mit axe ohne Verstöße auf **Anmeldung, Seitenübersicht und Editor** (Desktop und Mobil), öffentliche Browserprüfung unverändert grün. +- Zwei Testselektoren zeigten auf Klassen, die die Umgestaltung umbenannt hat; sie prüfen jetzt dieselbe Sache über den neuen Namen. Die Klasse, an der die Rechteprüfung für das endgültige Löschen hängt, ist absichtlich geblieben. +- **Der Browsertest hat einen Fehler gefunden, den ich selbst eingebaut hatte.** Beim Zerlegen des Stylesheets schnitt mein Skript am Ende jedes `@media`-Blocks alle schließenden Klammern ab statt nur einer. Damit war `editor.css` ab dem ersten Medienblock ungültig: der Browser behielt 29 von 202 Regeln. Aufgefallen ist es an einer Vorschaubreite von 1236 statt 1240 Pixeln, weil der Standardrahmen des iframe nicht mehr entfernt wurde – vier Pixel als einziges Symptom dafür, dass dreiviertel des Editor-Stylesheets fehlte. Behoben, und die Datei wird jetzt auf ausgeglichene Klammern geprüft. +- **Nicht geprüft:** kein Screenreader. Die Bildschirme Menüs, Formulare, Bereiche, Design, Einstellungen und Fassungen habe ich im Browser angesehen und ihre Tabellen und Formulare bedient, aber die automatische Barrierefreiheitsprüfung deckt sie nicht ab – sie prüft Anmeldung, Seitenübersicht und Editor. +- Der Editor selbst behält seinen Aufbau. Er übernimmt die neuen Farben, Schaltflächen und die Systemschrift, ist aber nicht neu gestaltet worden. + +## Ergänzung: Verwaltung neu gestaltet, 12.09.2026 + +- **201 PHP-Tests / 716 Assertions** grün, Playwright-Editortest mit axe ohne Verstöße auf **Anmeldung, Seitenübersicht und Editor** (Desktop und Mobil), öffentliche Browserprüfung unverändert grün. +- Zwei Testselektoren zeigten auf Klassen, welche die Umgestaltung umbenannt hat; sie prüfen jetzt dieselbe Sache über den neuen Namen. Die Klasse, an der die Rechteprüfung für das endgültige Löschen hängt, ist absichtlich geblieben. +- **Der Browsertest hat einen Fehler gefunden, den ich selbst eingebaut hatte.** Beim Zerlegen des Stylesheets schnitt mein Skript am Ende jedes `@media`-Blocks alle schließenden Klammern ab statt nur einer. Damit war `editor.css` ab dem ersten Medienblock ungültig: der Browser behielt 29 von 202 Regeln. Aufgefallen ist es an einer Vorschaubreite von 1236 statt 1240 Pixeln, weil der Standardrahmen des iframe nicht mehr entfernt wurde – vier Pixel als einziges Anzeichen dafür, dass dreiviertel des Editor-Stylesheets fehlte. Behoben und mit ausgeglichenen Klammern nachgerechnet. +- Von Hand bedient: Seitenübersicht mit Aufklappfeld, Menüs, Formulare, Bereiche, Design, Einstellungen, Mediathek und der Editor in allen drei Vorschaugrößen. +- **Nicht geprüft:** kein Screenreader. Die automatische Barrierefreiheitsprüfung deckt Anmeldung, Seitenübersicht und Editor ab, nicht die übrigen Verwaltungsbildschirme. + ## Ergänzung: eigenes CSS und eigenes Skript, 12.09.2026 - **201 PHP-Tests / 716 Assertions** unter PHP 8.2.12 und PHP 8.4.22, **15 JavaScript-Modelltests**, der vollständige Playwright-Editortest mit axe (null Verstöße) und die öffentliche Browserprüfung. diff --git a/docs/STATUS.md b/docs/STATUS.md index 91e49af..6aea532 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # Projektstatus -Stand: 12.09.2026. Arbeitstitel Kant CMS. **Eigenes CSS je Element für alle, eigenes Skript nur für Administratoren und nur als eigene Datei. Formatierter Text mit Werkzeugleiste und HTML-Ansicht, serverseitig gegen eine Positivliste gefiltert. Kopf- und Fußbereich sind frei gestaltbare Blockdokumente, dazu verknüpfte wiederverwendbare Sektionen (Arbeitspaket G, Teil 1). Frei wählbare Startseite; `/sitemap.xml` und Geschwister waren lokal nie erreichbar und sind es jetzt. Seiten verschieben mit automatischen Weiterleitungen, Papierkorb, Fassungsverlauf und geplante Veröffentlichung (Arbeitspaket F). Browsertests wiederhergestellt und erweitert; dabei drei echte Fehler gefunden und behoben, darunter die in der Vorschau wirkungslose Gestaltung. Direktbearbeitung in der Vorschau, Kontrastwarnung, Gestaltungsvorlagen und Bildauswahl für Hintergrundbilder. Der Editor gestaltet frei statt über feste Auswahllisten: eigene Farbpalette, Schriften, und jede Eigenschaft je Block, je Gerät und für den Hover-Zustand frei einstellbar (Dokumentschema v10). SEO-Grundlage mit Sitemap, robots.txt, Weiterleitungen und strukturierten Daten (Arbeitspaket E). Formulare mit serverseitiger Validierung, Einwilligung, E-Mail-Versand und Spamschutz (Arbeitspaket D). Tabs als fortschreitende Verbesserung mit JavaScript (Dokumentschema v7). Zehn neue Blöcke für die Abschnitte der Pilotstartseite (Arbeitspaket C). Beiträge mit Datum, Autor, Auszug, Beitragsbild, Kategorien, Schlagwörtern, Archiven, einbettbarer Beitragsliste und RSS (Arbeitspaket B). Menüs mit Seiten-, Anker- und externen Zielen ersetzen die fest verdrahtete Navigation (Arbeitspaket A). Seiten und Beiträge lassen sich im Browser anlegen und löschen, mit Adresshierarchie bis vier Ebenen.** Die vollständige Phase-1-Abnahme bleibt wegen des fehlenden UX-Builder-Referenzabgleichs offen. +Stand: 12.09.2026. Arbeitstitel Kant CMS. **Die Verwaltung hat einen gemeinsamen Rahmen mit einer Navigation, die zeigt, wo man ist. Eigenes CSS je Element für alle, eigenes Skript nur für Administratoren und nur als eigene Datei. Formatierter Text mit Werkzeugleiste und HTML-Ansicht, serverseitig gegen eine Positivliste gefiltert. Kopf- und Fußbereich sind frei gestaltbare Blockdokumente, dazu verknüpfte wiederverwendbare Sektionen (Arbeitspaket G, Teil 1). Frei wählbare Startseite; `/sitemap.xml` und Geschwister waren lokal nie erreichbar und sind es jetzt. Seiten verschieben mit automatischen Weiterleitungen, Papierkorb, Fassungsverlauf und geplante Veröffentlichung (Arbeitspaket F). Browsertests wiederhergestellt und erweitert; dabei drei echte Fehler gefunden und behoben, darunter die in der Vorschau wirkungslose Gestaltung. Direktbearbeitung in der Vorschau, Kontrastwarnung, Gestaltungsvorlagen und Bildauswahl für Hintergrundbilder. Der Editor gestaltet frei statt über feste Auswahllisten: eigene Farbpalette, Schriften, und jede Eigenschaft je Block, je Gerät und für den Hover-Zustand frei einstellbar (Dokumentschema v10). SEO-Grundlage mit Sitemap, robots.txt, Weiterleitungen und strukturierten Daten (Arbeitspaket E). Formulare mit serverseitiger Validierung, Einwilligung, E-Mail-Versand und Spamschutz (Arbeitspaket D). Tabs als fortschreitende Verbesserung mit JavaScript (Dokumentschema v7). Zehn neue Blöcke für die Abschnitte der Pilotstartseite (Arbeitspaket C). Beiträge mit Datum, Autor, Auszug, Beitragsbild, Kategorien, Schlagwörtern, Archiven, einbettbarer Beitragsliste und RSS (Arbeitspaket B). Menüs mit Seiten-, Anker- und externen Zielen ersetzen die fest verdrahtete Navigation (Arbeitspaket A). Seiten und Beiträge lassen sich im Browser anlegen und löschen, mit Adresshierarchie bis vier Ebenen.** Die vollständige Phase-1-Abnahme bleibt wegen des fehlenden UX-Builder-Referenzabgleichs offen. Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt. Noch kein Remote und kein Push. @@ -25,6 +25,8 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt. - Auf ausdrücklichen Nutzerwunsch lokalen Administrator `umut` mit zufälligem Passwort angelegt und im In-App-Browser erfolgreich angemeldet. Das Passwort steht in keiner Projektdatei; nur der Hash in der lokalen Datenbank. Keine Browser-Passwortspeicherung ausgelöst. - Seitenerstellung und -löschung im Browser: `POST /admin/pages` (ab `ROLE_EDITOR`) legt Seite oder Beitrag mit gültigem Startdokument als privaten Entwurf an; `POST /admin/pages/{id}/delete` (nur `ROLE_ADMIN`) entfernt eine Seite mit allen Revisionen. Beide mit CSRF-Token, Fehlermeldungen als Flash und ohne JavaScript bedienbar. - Adresshierarchie: neue Spalten `path`, `parent_id`, `type`, `created_at` über Migration `20260912000400`; websiteweit eindeutiger Pfad statt eindeutigem Slug, Tiefe bis vier Ebenen, Beiträge ohne Unterseiten, reservierte Adressen und Adressableitung aus dem Titel. Öffentliche Route bedient verschachtelte Pfade wie `/leistungen/webdesign`; ein Lookahead hält `/admin`, `/media` und `/assets` aus dem Auffangmuster. +- **Verwaltung neu gestaltet** (ADR 029): ein Rahmen für alle Bildschirme mit fester Seitenleiste, aktivem Eintrag über `aria-current`, Titel, einem Satz Erklärung und Platz für die Hauptaktion. Vorher trug jede Seite ihre eigene Kopfzeile und eine eigene, unvollständige Linkliste. Je Seite eine Zeile mit dem Alltäglichen und **einem** Aufklappfeld „Einstellungen“, das an seinem Platz öffnet, statt sechs Kästen, die über den Rand hinausragten. Tabellen scrollen seitlich, statt die Karte aus dem Fenster zu schieben. Die Editorregeln stehen jetzt in `editor.css`, das nur Editor und Mediathek laden. +- **Verwaltung neu gestaltet** (ADR 029): ein Rahmen für alle Bildschirme mit fester Seitenleiste, aktivem Eintrag über `aria-current`, Titel, einem Satz Erklärung und Platz für die Hauptaktion. Vorher trug jede Seite ihre eigene Kopfzeile und eine eigene, unvollständige Linkliste. Je Seite eine Zeile mit dem Alltäglichen und **einem** Aufklappfeld „Einstellungen“, das an seinem Platz öffnet, statt sechs Kästen, die über den Rand hinausragten. Tabellen scrollen seitlich. Der Editor behält seinen Aufbau und übernimmt die neue Gestaltung; seine Regeln stehen jetzt in `editor.css`, das nur Editor und Mediathek laden. - **Eigenes CSS je Element** (Dokumentschema v13) im neuen Reiter „Erweitert“: Eigenschaften für das Element und verschachtelte Regeln mit `&`. Der Selektor wird vom Server gesetzt, die Ausgabe neu serialisiert – eine überzählige `}` kann den Block nicht verlassen. Abgewiesen: @-Regeln, Backslash-Escapes, `expression()`, fremde Selektoren und jede Adresse außer einem einzelnen `/` oder `https://`. Für jeden mit Editorrolle. - **Eigenes Skript je Element nur für Administratoren.** Durchgesetzt beim Schreiben durch Vergleich mit dem gespeicherten Stand – ein Editor kann eine Seite mit Skript weiter bearbeiten, das Skript aber nicht anfassen, auch nicht über eine eigene Anfrage. Ausgeliefert als `/assets/seite/{id}.js` nur für **veröffentlichte** Seiten, jedes Skript in einer eigenen Closure mit `element` und `try`/`catch`. **Nur eine Seite mit eigenem Skript sendet `script-src 'self'`**, jede andere behält `script-src 'none'`; inline ist nirgends erlaubt. In der Editorvorschau wird nichts ausgeführt. - **Formatierter Text in Textblöcken** (Dokumentschema v12): fett, kursiv, unterstrichen, durchgestrichen, Code, hervorgehoben, hoch-/tiefgestellt, Absätze, Listen und Links. Im Editor eine Werkzeugleiste plus **HTML-Ansicht**; eingefügter Inhalt kommt als reiner Text an. **Gefiltert wird auf dem Server beim Speichern**, nicht im Editor und nicht beim Rendern – was in der Datenbank liegt, ist schon sicher. Nicht erlaubte Elemente werden ausgepackt, der Text bleibt; `script`, `style`, `iframe` und Verwandte werden samt Inhalt entfernt. Attribute fallen weg außer `href` nach Adressprüfung. Ältere Dokumente behalten Klartextsemantik und werden weiter maskiert. @@ -63,6 +65,8 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt. - 201 PHP-Tests / 716 Assertions unter PHP 8.2.12 und 8.4.22 erfolgreich; zusätzlich 15 JavaScript-Modelltests, der vollständige Playwright-Editortest und die öffentliche Browserprüfung. Abgedeckt sind Migration/Erhaltung, Versionen, ungültige Geräte-/Typ-/Design-/Effektwerte, Vererbung/Reset/Undo, Vorlage-/Galerie-IDs, Uploadvalidierung und Neuencodierung, Metadatenkonflikte, Archivierung sowie sichere Bildquellen. - Neu geprüft (15 Tests): privater Entwurf nach dem Anlegen, verschachtelte Pfade mit gleichem Slug unter verschiedenen Eltern, abgewiesene Adressen (Großbuchstaben, Schrägstrich, leer, reserviert, unbekannter Typ, fehlendes Elternteil) ohne Schreibzugriff, doppelte Adresse, Tiefengrenze, Beiträge ohne Unterseiten, Löschen mit allen Revisionen samt Schutz für Elternseiten und veraltete Versionen, sowie dass die CMS-Routen nicht vom öffentlichen Auffangmuster verschluckt werden. Über HTTP zusätzlich: anonyme Schreibversuche, CSRF für Anlegen und Löschen, Adressableitung aus dem Titel, kein Löschangebot und HTTP 403 für die Editorrolle, erfolgreiches Löschen als Administrator. +- Verwaltung geprüft: axe ohne Verstöße auf Anmeldung, Seitenübersicht und Editor (Desktop und Mobil), 201 PHP-Tests grün. **Nicht geprüft:** kein Screenreader, und die Bildschirme Menüs, Formulare, Bereiche, Design, Einstellungen und Fassungen sind nur von Hand angesehen, nicht automatisiert auf Barrierefreiheit geprüft – die Browserprüfung deckt sie nicht ab. +- Verwaltung geprüft: axe ohne Verstöße auf Anmeldung, Seitenübersicht und Editor (Desktop und Mobil), alle PHP-Tests grün. **Nicht geprüft:** kein Screenreader, und die Bildschirme Menüs, Formulare, Bereiche, Design, Einstellungen und Fassungen habe ich nur im Browser angesehen – die automatische Barrierefreiheitsprüfung deckt sie nicht ab. - Eigener Code geprüft: fünf CSS-Formen werden korrekt auf das Element bezogen, darunter `& .block-text` mit erhaltenem Nachfahren-Leerzeichen. **Zehn abgewiesene CSS-Eingaben** erreichen kein Dokument: Ausbruch mit `}`, fremder Selektor, `@import`, `@media`, `javascript:`-Adresse, protokollrelative Adresse, Backslash-Escape, `expression()`, Eigenschaft ohne Wert und unsinniger Eigenschaftsname. Eigenes CSS erscheint im Seiten-Stylesheet, eigenes Skript **nur** als Datei mit `script-src 'self'` ausschließlich auf dieser Seite; eine Seite ohne eigenes Skript behält `script-src 'none'` und liefert keine Datei. Ein Skript aus einem unveröffentlichten Entwurf wird nicht ausgeliefert. Der Rollenvergleich weist Ändern, Entfernen und Hinzufügen eines Skripts ab und lässt unveränderte Skripte durch. - Formatierter Text geprüft: **dreizehn Fälle** durch den Filter, darunter erlaubte Auszeichnung und Listen, ein `script` samt Inhalt entfernt und der Text danach erhalten, Ereignisattribut entfernt, `class`/`style` entfernt und das Element ausgepackt, `javascript:`-Link zu Text geworden, Bild mit `onerror` entfernt, `iframe` entfernt, unausgeglichene Auszeichnung geschlossen, lokaler Link erhalten, externer Link mit `rel="noopener"`, Klartext maskiert und Umbruch zu `
`. Dazu: gefiltert wird beim Speichern (die Datenbank enthält schon die sichere Fassung), ein nach dem Filtern leerer Textblock wird abgewiesen ohne zu schreiben, ab v12 erscheint Auszeichnung als Auszeichnung in einem `div`, vor v12 maskiert in einem `p`, und in einem geteilten Bereich gilt derselbe Filter. - Bereiche geprüft: die eingesetzten Kopf- und Fußbereiche erscheinen als gewöhnliche Blöcke, der Navigationsblock zeigt das Menü, und eine Änderung am Kopfbereich erreicht beide Seiten gleichzeitig. Ein leerer Bereich rendert nichts statt einer kaputten Hülle. Verknüpfte Sektion geprüft: auf zwei Seiten sichtbar, die Seitendokumente enthalten nur die Adresse, eine Änderung wirkt an beiden Stellen, `usedBy` nennt beide Seiten, und nach dem Entfernen bleiben die Seiten heil und der Verweis erhalten. Abgewiesen werden: Hauptüberschrift in einem geteilten Bereich, Sektion mit Selbstbezug, zweiter Kopfbereich und das Löschen von Kopf- oder Fußbereich. @@ -128,7 +132,7 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt. ## Nächster konkreter Schritt -Paket G2, letzter Punkt: die Verwaltung neu gestalten – einfacher, schöner, ohne Anleitung bedienbar, mit Gestaltungsfreiheit. Dazu den Editor mit einem Screenreader prüfen. Danach der Rest von Arbeitspaket G (Inline-Ausbau, globale Bereiche für Header und Footer, wiederverwendbare Sektionen). Danach Arbeitspaket F aus `docs/AUSBAUPLAN.md`: Seiten umbenennen und verschieben mit Aktualisierung aller Kindpfade und automatischer Weiterleitung, frei wählbare Startseite, geplantes Veröffentlichen, Revisionsliste mit Wiederherstellung und ein Papierkorb statt sofortigem Löschen. +Den Editor mit einem Screenreader prüfen und die übrigen Verwaltungsbildschirme in die automatische Barrierefreiheitsprüfung aufnehmen. Danach der Rest von Arbeitspaket G (Inline-Ausbau, globale Bereiche für Header und Footer, wiederverwendbare Sektionen). Danach Arbeitspaket F aus `docs/AUSBAUPLAN.md`: Seiten umbenennen und verschieben mit Aktualisierung aller Kindpfade und automatischer Weiterleitung, frei wählbare Startseite, geplantes Veröffentlichen, Revisionsliste mit Wiederherstellung und ein Papierkorb statt sofortigem Löschen. `kant-it.de` ist seit 12.09.2026 als Pilotwebsite bestimmt. Der öffentliche Bestand wurde erhoben (WordPress 7.1, Flatsome 3.20.9 mit eigenem Child-Theme, WPForms Lite, Complianz, Google-Rezensionen, eigenes Plugin `kant-nova-chat`) und die noch offene Arbeit daraus in `docs/AUSBAUPLAN.md` in elf Arbeitspakete aufgeschlüsselt. Erhoben wurde ausschließlich die öffentlich ausgelieferte Startseite; ohne Backendzugang sind Beitragsbestand, Formularkonfiguration, Benutzer und Flatsome-Shortcodes nicht inventarisierbar. Keine externe Veröffentlichung erfolgt. diff --git a/public/assets/admin.css b/public/assets/admin.css index 77ce2c1..7cefa1a 100644 --- a/public/assets/admin.css +++ b/public/assets/admin.css @@ -1,93 +1,480 @@ -:root{font-family:Arial,Helvetica,sans-serif;color:#203b34;background:#f6f7f4;--ink:#203b34;--muted:#5a6861;--line:#dce2dc;--paper:#fff;--mint:#e5f0b6;--accent:#315b49;font-synthesis:none} -.editor-page-name h1{font-size:13px;font-weight:600;letter-spacing:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0} -.canvas-scroll:focus-visible{outline:3px solid #b55b12;outline-offset:-3px} -.visually-hidden{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap} -.media-launch{margin-top:12px;white-space:normal}.media-dialog{width:calc(100% - 32px);max-width:1100px;max-height:calc(100dvh - 32px);overflow:auto}.media-heading{display:flex;justify-content:space-between;gap:20px;align-items:center}.media-heading h2{margin:0}.media-heading .eyebrow{margin-bottom:8px}.media-tools{display:grid;grid-template-columns:1fr 1fr;gap:24px}.media-tools label{margin-top:10px}.media-layout{display:grid;grid-template-columns:minmax(0,1fr) 270px;gap:24px}.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(145px,1fr));gap:12px;align-content:start;max-height:430px;overflow:auto;padding:4px}.media-card{border:2px solid var(--line);border-radius:9px;padding:7px;background:white;color:var(--ink);display:flex;flex-direction:column;gap:7px;text-align:left;min-width:0}.media-card[aria-pressed=true]{border-color:var(--accent);background:#edf4dc}.media-card img{width:100%;height:115px;object-fit:cover;border-radius:5px}.media-card strong{font-size:12px;overflow-wrap:anywhere}.media-card span{font-size:11px;color:var(--muted)}.media-details{padding-left:22px;border-left:1px solid var(--line)}.media-details button,.media-details a{margin-top:12px;display:block}.media-details input{font-size:13px}.media-footer{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:20px;border-top:1px solid var(--line);padding-top:15px}.media-footer label{margin:0;font-size:12px}#media-error{color:#8b2f22;background:#fff0eb;padding:12px;border-radius:6px}#media-status{font-size:12px;margin-block:15px}.media-dialog> .panel-hint{margin-top:20px}.media-details>p{font-size:12px} -@media(max-width:650px){.media-dialog{padding:18px}.media-tools,.media-layout{grid-template-columns:1fr}.media-details{padding:20px 0 0;border-left:0;border-top:1px solid var(--line)}.media-grid{max-height:280px;grid-template-columns:repeat(2,minmax(0,1fr))}.media-footer{align-items:stretch;flex-direction:column}.media-heading{gap:8px}} -*{box-sizing:border-box}body{margin:0}a{color:inherit;text-underline-offset:4px}button,input,select,textarea{font:inherit}button,a,input,textarea,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button:disabled{cursor:default;opacity:.48}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,iframe:focus-visible{outline:3px solid #b55b12;outline-offset:3px}input,textarea,select{width:100%;border:1px solid #bcc9c0;border-radius:7px;background:#fff;color:var(--ink);padding:11px 12px;min-height:42px}textarea{resize:vertical;min-height:110px;line-height:1.6}label{display:block;margin:21px 0 8px;font-size:13px;font-weight:600}h1,h2,h3,p{margin-top:0}h1{font-size:38px;font-weight:500;letter-spacing:-1.5px;line-height:1.15}h2{font-size:18px;letter-spacing:-.4px}h3{font-size:16px}.muted{color:var(--muted);line-height:1.6}.eyebrow{font-size:10px;font-weight:700;letter-spacing:1.7px;color:var(--accent);margin-bottom:16px}.brand{display:inline-flex;align-items:center;font-size:25px;font-weight:700;text-decoration:none;letter-spacing:-1.3px;gap:3px}.brand>span:last-child{font-weight:400}.brand-icon{display:grid;place-items:center;width:33px;height:33px;background:var(--ink);color:var(--mint);border-radius:9px;margin-right:7px}.primary,.secondary,.subtle,.icon-button,.text-button,.block-actions button,.viewport-controls button{display:inline-flex;align-items:center;justify-content:center;gap:12px;border:1px solid var(--line);border-radius:7px;padding:11px 15px;text-decoration:none;font-size:12px;font-weight:600;background:white;color:var(--ink);white-space:nowrap;min-height:40px}.primary{background:var(--ink);color:white;border-color:var(--ink)}.primary:hover:not(:disabled){background:#37604b}.secondary:hover:not(:disabled){background:#f0f4ed}.subtle,.text-button{border-color:transparent;background:transparent}.text-button{padding:4px 8px;min-height:28px}.full-width{width:100%}.skip-link{position:fixed;top:-100px;left:16px;z-index:100;background:var(--ink);color:white;padding:15px}.skip-link:focus{top:10px}.notice,.editor-message{padding:14px 18px;border-radius:7px;line-height:1.6;font-size:13px}.error{background:#fff0eb;color:#8b2f22;border:1px solid #e2b2a7}.login-body{min-height:100vh;display:grid;place-items:center;background:radial-gradient(ellipse at top left,#e5f0b6,transparent 65%),#f6f7f4;padding:30px}.login-card{width:100%;max-width:450px;padding:44px;background:white;border:1px solid var(--line);border-radius:18px;box-shadow:0 20px 60px #203b3408}.login-card .brand{margin-bottom:44px}.login-card h1{font-size:31px}.login-card .primary{margin-top:26px}.quiet-link{display:block;font-size:12px;text-align:center;margin-top:25px}.admin-header{padding:24px 42px;background:white;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:20px}.header-actions{display:flex;align-items:center;gap:24px;font-size:12px}.user-name{padding-left:24px;border-left:1px solid var(--line)}.pages-main{max-width:1120px;margin:74px auto;padding:0 30px}.section-heading{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:50px;margin-bottom:20px}.section-heading h2{margin:0}.section-heading>span{font-size:12px}.count{font-size:12px;background:#e5ebdf;padding:4px 8px;border-radius:6px;margin-left:8px}.page-list{display:grid;gap:13px}.page-card{display:flex;gap:20px;align-items:center;background:white;padding:24px;border:1px solid var(--line);border-radius:10px}.page-icon{font-size:25px;width:44px;height:50px;background:#f0f4e9;border-radius:8px;display:grid;place-items:center}.page-info{flex:1;min-width:0}.page-info h3{margin:0 0 9px}.page-info h3 a{text-decoration:none}.page-info p{font-size:12px;color:var(--muted);margin:0}.badge{font-size:10px;letter-spacing:.2px;background:#fff0d5;color:#78551d;padding:7px 10px;border-radius:20px;white-space:nowrap}.published{background:#edf3dc;color:#315b49}.footnote{font-size:11px;color:var(--muted);margin-top:27px}.empty-state{padding:35px;border:1px dashed var(--line);border-radius:10px}.editor-body{overflow:hidden}#editor{height:100dvh;display:flex;flex-direction:column}.editor-header{display:flex;align-items:center;gap:20px;min-height:76px;padding:13px 20px;border-bottom:1px solid var(--line);background:white;z-index:1}.back-link{display:flex;gap:9px;text-decoration:none;font-size:12px;padding-right:20px;border-right:1px solid var(--line)}.editor-page-name{min-width:0;flex:1;display:grid;gap:5px}.editor-page-name strong{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.editor-page-name>span{font-size:10px;color:var(--muted)}.history-actions,.save-actions{display:flex;align-items:center;gap:8px}.icon-button{font-size:21px;min-width:36px;padding:3px 9px}.save-actions>span{font-size:10px;color:var(--muted);max-width:130px;margin-right:8px}.editor-layout{display:grid;grid-template-columns:258px minmax(200px,1fr) 280px;flex:1;min-height:0}.left-panel,.right-panel{background:white;overflow:auto}.left-panel{border-right:1px solid var(--line)}.right-panel{border-left:1px solid var(--line)}.panel-section{padding:23px 18px;border-bottom:1px solid var(--line)}.panel-section .eyebrow{margin-bottom:10px}.panel-section h2{font-size:14px;margin-bottom:12px}.panel-hint{font-size:11px;color:var(--muted);line-height:1.6;margin-bottom:17px}.library{display:grid;grid-template-columns:1fr 1fr;gap:7px}.library button{min-height:64px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;border:1px solid var(--line);border-radius:7px;background:#fff;color:var(--ink);font-size:10px}.library button:hover:not(:disabled){border-color:var(--accent);background:#f3f7e8}.library button span{font-size:19px;line-height:1}.panel-title{display:flex;justify-content:space-between;align-items:center}.panel-title h2{margin-bottom:0}.panel-title+.panel-hint{margin-top:10px}.structure-list{list-style:none;margin:0;padding:0}.structure-list .structure-list{padding-left:13px;border-left:1px solid #e5e9e3;margin-left:8px}.tree-item{display:flex;align-items:center;gap:7px;width:100%;text-align:left;border:1px solid transparent;background:none;color:#42584b;border-radius:5px;padding:9px 7px;font-size:11px;min-height:34px}.tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-item:hover{background:#f3f6ef}.tree-item[aria-pressed=true]{background:#eaf1d5;border-color:#c5d89a;color:#203b34;font-weight:600}.tree-icon{font-size:12px;min-width:15px}.drop-target{outline:2px dashed #315b49}.block-actions{display:flex;flex-wrap:wrap;gap:5px;border-top:1px solid var(--line);padding-top:13px;margin-top:16px}.block-actions button{font-size:10px;padding:7px 9px;min-height:33px}.block-actions .danger-text{color:#943a2e}.canvas-panel{min-width:0;display:flex;flex-direction:column;background:#e9ede7;overflow:hidden}.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 20px;background:#f6f8f2;border-bottom:1px solid var(--line);min-height:59px}.canvas-label{font-size:9px;font-weight:700;letter-spacing:1.2px;color:#57655d}.viewport-controls{display:flex;gap:3px;background:#e8eee1;border-radius:7px;padding:3px}.viewport-controls button{padding:5px 9px;font-size:10px;min-height:28px;border:0;background:transparent}.viewport-controls [aria-pressed=true]{background:white;box-shadow:0 1px 3px #00000012}.canvas-toolbar>a{text-decoration:none;font-size:18px}.canvas-scroll{flex:1;min-height:0;overflow:auto;padding:22px;display:flex;justify-content:center;align-items:flex-start}#preview{background:white;border:0;box-shadow:0 3px 20px #203b3410;min-height:600px;height:100%;flex-shrink:0}.viewport-desktop{width:100%}.viewport-tablet{width:768px;max-width:100%}.viewport-mobile{width:390px;max-width:100%}.preview-status{font-size:10px;padding:9px 20px;color:#52614f;margin:0;min-height:30px;background:#f3f5ed}.right-panel label{font-size:11px}.right-panel input,.right-panel select,.right-panel textarea{font-size:12px}.right-panel textarea{min-height:150px}.field-help{font-size:10px;line-height:1.5;color:var(--muted);margin-top:7px}.panel-note{font-size:11px;color:var(--muted);line-height:1.7;padding:20px}.check-label{display:flex;align-items:center;gap:8px}.check-label input{width:16px;min-height:16px}.editor-message{border-radius:0;background:#fff0e9;color:#873e24;border-bottom:1px solid #e6c3b2;flex-shrink:0;padding:9px 20px;font-size:12px}.recovery-bar{display:flex;gap:12px;align-items:center;padding:10px 20px;border-bottom:1px solid var(--line);background:#fff;font-size:12px;flex-wrap:wrap}[hidden]{display:none!important}dialog{max-width:450px;width:calc(100% - 40px);padding:28px;border:1px solid var(--line);border-radius:12px;color:var(--ink);box-shadow:0 20px 80px #203b3433}dialog::backdrop{background:#172e2966}dialog p{font-size:14px;line-height:1.7;color:var(--muted)}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:25px} -@media(max-width:1150px){.editor-layout{grid-template-columns:215px minmax(200px,1fr) 240px}.editor-header{gap:12px;padding-inline:14px}.save-actions>span{display:none}.panel-section{padding:18px 14px}.canvas-scroll{padding:12px}.canvas-label{display:none}} -@media(max-width:850px){.editor-body{overflow:auto}#editor{height:auto;min-height:100dvh}.editor-header{flex-wrap:wrap}.editor-page-name{min-width:180px}.save-actions{width:100%;justify-content:flex-end}.save-actions>span{display:block;margin-right:auto}.editor-layout{display:flex;flex-wrap:wrap}.canvas-panel{order:0;width:100%;height:680px}.left-panel{order:1;width:45%;max-height:650px}.right-panel{order:2;width:55%;max-height:650px}.canvas-label{display:block}.admin-header{padding:20px}.header-actions{gap:12px}.header-actions .user-name{display:none}.page-card{flex-wrap:wrap;padding:18px;gap:14px}.page-info{min-width:180px}.pages-main{margin:40px auto;padding:0 20px}.section-heading{align-items:flex-start;flex-direction:column}.login-card{padding:30px}.login-body{padding:20px}.editor-header .back-link{padding-right:12px}.editor-message{font-size:11px}.editor-layout .panel-hint{font-size:10px}} -@media(prefers-reduced-motion:no-preference){button,a{transition:background-color .12s,border-color .12s}} -.left-panel{display:flex;flex-direction:column;overflow:hidden}.left-panel>.panel-section:first-child{max-height:45%;overflow:auto;flex-shrink:0}.structure-section{display:flex;flex-direction:column;flex:1;min-height:0}.structure-section #structure{overflow:auto;flex:1;min-height:90px;padding:3px}.structure-section .block-actions{flex-shrink:0;background:white}.structure-section .panel-hint{margin:8px 0}.search-label{font-size:11px;margin:12px 0 6px}#tree-search{font-size:11px;padding:9px;min-height:36px}.tree-tools{display:flex;flex-wrap:wrap;gap:3px;margin-top:5px}.tree-tools .text-button{font-size:10px;padding:5px}.structure-list li{position:relative}.tree-item{padding-left:23px}.tree-toggle{position:absolute;top:5px;left:0;width:22px;height:25px;z-index:1;border:0;background:transparent;color:var(--ink);border-radius:4px}.preset-heading{font-size:12px;margin:22px 0 8px}.preset-library{display:grid;gap:6px}.preset-library button{display:flex;gap:10px;text-align:left;align-items:center;min-height:40px;padding:9px 12px;border:1px solid var(--line);border-radius:7px;background:#f7f9f2;color:var(--ink);font-size:11px}.preset-library button:hover:not(:disabled){border-color:var(--accent)}.preset-library span{font-size:18px}.property-tabs{display:flex;background:#edf1e7;padding:4px;border-radius:8px;gap:4px}.property-tabs button{flex:1;min-width:0;border:0;background:transparent;color:var(--ink);border-radius:6px;padding:10px;font-size:12px;font-weight:600}.property-tabs [aria-pressed=true]{background:white;box-shadow:0 1px 3px #0001}.design-scope{font-size:12px;font-weight:600;line-height:1.6;margin:18px 0 8px;padding:10px;border-left:3px solid var(--accent);background:#f1f5e7}.design-reset{margin-top:24px;white-space:normal;width:100%}.breadcrumbs{display:flex;align-items:center;flex-wrap:wrap;gap:3px;min-height:35px;padding:3px 14px;background:#f6f8f2;border-top:1px solid var(--line)}.breadcrumbs .text-button{font-size:10px;white-space:normal}.breadcrumbs button+button:before{content:'›';color:var(--muted)}.breadcrumbs [aria-current=location]{font-weight:700;background:#e5edda}.canvas-scroll{display:block}.canvas-scroll #preview-stage{position:relative;margin:0 auto}#preview{position:absolute;top:0;left:0;transform-origin:top left;max-width:none;min-height:0}.right-panel select{padding-inline:7px;text-overflow:ellipsis}.right-panel .panel-section{min-width:0} -@media(max-width:850px){.left-panel,.right-panel{height:650px}.left-panel>.panel-section:first-child{max-height:40%}.structure-section{padding:12px 7px}.tree-item{font-size:10px;gap:3px}.tree-icon{min-width:10px}.structure-list .structure-list{padding-left:5px;margin-left:4px}.block-actions button{padding:6px;white-space:normal}.property-tabs button{padding:9px 4px;font-size:11px}.canvas-scroll{padding:12px}.right-panel label{line-height:1.5}.tree-tools .text-button{white-space:normal}} +/* Administration: shared foundation and the management screens. + The editor has its own stylesheet (editor.css) that only the editor and the media page load. */ -.notice.ok{background:#edf3dc;color:#315b49;border:1px solid #c5d89a}.notice{margin-top:24px}.create-panel{margin-top:34px;background:white;border:1px solid var(--line);border-radius:10px}.create-panel>summary{padding:20px 24px;font-size:13px;font-weight:600;cursor:pointer;border-radius:10px}.create-panel[open]>summary{border-bottom:1px solid var(--line)}.create-form{padding:6px 24px 26px}.create-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:0 24px}.create-form .primary{margin-top:24px}.create-form code{background:#f0f4e9;padding:1px 5px;border-radius:4px}.delete-panel>summary{font-size:12px;font-weight:600;color:#943a2e;cursor:pointer;padding:11px 13px;border:1px solid var(--line);border-radius:7px;list-style:none}.delete-panel[open]{position:relative}.delete-panel[open]>form{position:absolute;right:0;top:calc(100% + 8px);z-index:2;width:290px;background:white;border:1px solid var(--line);border-radius:10px;padding:18px;box-shadow:0 14px 40px #203b3422}.delete-panel p{font-size:12px;line-height:1.6;color:var(--muted)} -@media (max-width:720px){.page-card{flex-wrap:wrap}.pages-main{margin-block:40px}.delete-panel[open]>form{position:static;width:100%;box-shadow:none;margin-top:10px}} +:root { + /* One family of greens. The sidebar is the dark end of it, so the administration reads as one + place instead of a set of forms. */ + --ink: #1b2a24; + --muted: #5d6b64; + --line: #e2e7e1; + --paper: #fff; + --ground: #f3f5f1; + --shell: #16241e; + --shell-ink: #e7eee8; + --shell-muted: #9fb0a6; + --mint: #e5f0b6; + --accent: #2c6b4f; + --ring: #b55b12; + --danger: #8b2f22; + /* System fonts: nothing to download, and the administration looks native on the machine it + runs on. Google Fonts are for the website itself, where Umut picks them. */ + font-family: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif; + color: var(--ink); + background: var(--ground); + font-synthesis: none; +} -.admin-links{display:flex;flex-wrap:wrap;gap:10px;margin:0}.menu-section{margin-top:46px}.menu-form{background:white;border:1px solid var(--line);border-radius:10px;padding:8px 24px 26px}.menu-form>label:first-of-type{margin-top:18px}.menu-form>input[name=name]{max-width:420px}.menu-table{width:100%;border-collapse:collapse;margin-top:24px;display:block;overflow-x:auto}.menu-table th{text-align:left;font-size:11px;letter-spacing:.4px;color:var(--muted);padding:0 8px 8px;font-weight:700;white-space:nowrap}.menu-table td{padding:4px 8px 4px 0;vertical-align:top}.menu-table input,.menu-table select{font-size:12px;min-height:38px;padding:8px 10px}.menu-table td:first-child input{width:78px}.menu-table td:nth-child(2) select{width:140px}.menu-table td:nth-child(3) input{min-width:170px}.menu-table td:nth-child(4) select{width:104px}.menu-table td:nth-child(5) select{min-width:190px}.menu-table td:nth-child(6) input{width:130px}.menu-table td:nth-child(7) input{min-width:190px}.menu-row-new input,.menu-row-new select{background:#fafbf8}.menu-form .primary{margin-top:20px}.menu-form code{background:#f0f4e9;padding:1px 5px;border-radius:4px} +* { box-sizing: border-box } +body { margin: 0 } +a { color: inherit; text-underline-offset: 4px } +button, input, select, textarea { font: inherit } +button, a, input, textarea, select { -webkit-tap-highlight-color: transparent } +button { cursor: pointer } +button:disabled { cursor: default; opacity: .48 } +button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, +textarea:focus-visible, summary:focus-visible, iframe:focus-visible { + outline: 3px solid var(--ring); + outline-offset: 3px; +} +h1, h2, h3, p { margin-top: 0 } +h1 { font-size: 27px; font-weight: 650; letter-spacing: -.7px; line-height: 1.2; margin-bottom: 0 } +h2 { font-size: 17px; letter-spacing: -.3px } +h3 { font-size: 15px } +[hidden] { display: none !important } -.meta-panel>summary{font-size:12px;font-weight:600;cursor:pointer;padding:11px 13px;border:1px solid var(--line);border-radius:7px;list-style:none}.meta-panel[open]{position:relative}.meta-panel[open]>form{position:absolute;right:0;top:calc(100% + 8px);z-index:3;width:330px;background:white;border:1px solid var(--line);border-radius:10px;padding:6px 20px 22px;box-shadow:0 14px 40px #203b3422}.meta-panel form label{margin-top:16px}.meta-panel .primary{margin-top:20px;width:100%}.meta-panel textarea{min-height:74px;font-size:12px}.meta-panel input{font-size:12px} -@media (max-width:720px){.meta-panel[open]>form{position:static;width:100%;box-shadow:none;margin-top:10px}} +.muted { color: var(--muted); line-height: 1.6 } +.eyebrow { font-size: 10px; font-weight: 700; letter-spacing: 1.7px; color: var(--accent); margin-bottom: 14px } +.footnote { font-size: 11px; color: var(--muted); margin: 34px 0 0 } +.count { font-size: 12px; font-weight: 600; color: var(--muted) } +.danger-text { color: var(--danger) } +.visually-hidden { + position: absolute; width: 1px; height: 1px; padding: 0; + overflow: hidden; clip-path: inset(50%); white-space: nowrap; +} +.skip-link { position: fixed; top: -100px; left: 16px; z-index: 100; background: var(--ink); color: #fff; padding: 15px } +.skip-link:focus { top: 10px } -.submission-card{align-items:flex-start}.submission-data{display:grid;grid-template-columns:auto 1fr;gap:4px 16px;margin:12px 0 0;font-size:12px}.submission-data dt{font-weight:600;color:var(--muted)}.submission-data dd{margin:0;overflow-wrap:anywhere}.menu-table .check-cell{text-align:center}.menu-table .check-cell input{width:18px;min-height:18px} +/* ---- fields ---------------------------------------------------------------------------- */ -.redirect-table{margin-top:26px}.redirect-table td{padding:8px 12px 8px 0;font-size:12px}.redirect-table form{margin:0} +input, textarea, select { + width: 100%; + border: 1px solid #c3cec7; + border-radius: 8px; + background: var(--paper); + color: var(--ink); + padding: 10px 12px; + min-height: 42px; +} +input:hover, textarea:hover, select:hover { border-color: #a9b8af } +textarea { resize: vertical; min-height: 110px; line-height: 1.6 } +label { display: block; margin: 18px 0 7px; font-size: 13px; font-weight: 600 } +.field-help { font-size: 11px; line-height: 1.55; color: var(--muted); margin: 7px 0 0 } +.field-help code, .card code { background: #eef2ea; padding: 1px 5px; border-radius: 4px; font-size: .95em } +.check-label { display: flex; align-items: center; gap: 8px; font-weight: 500 } +.check-label input { width: 16px; min-height: 16px } +/* A grid of fields that becomes one column when there is no room. */ +.fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 24px } +.fields > div > label:first-child, .field > label:first-child { margin-top: 14px } -/* Design panel: free values instead of fixed lists. */ -.design-group{border:1px solid var(--line);border-radius:9px;margin-top:10px;background:#fff} -.design-group>summary{cursor:pointer;padding:12px 14px;font-size:12px;font-weight:700;letter-spacing:.3px;list-style:none;display:flex;justify-content:space-between;align-items:center} -.design-group>summary::after{content:'+';font-size:17px} -.design-group[open]>summary{border-bottom:1px solid var(--line)} -.design-group[open]>summary::after{content:'–'} -.design-group-body{padding:6px 14px 16px} -.design-group-label{font-size:11px;font-weight:700;color:var(--muted);margin:18px 0 6px;letter-spacing:.3px} -.design-row{margin-top:14px} -.design-row-head{display:flex;justify-content:space-between;align-items:center;gap:8px} -.design-row-head label{margin:0;font-size:11px} -.design-own .design-row-head label{color:#2f5d4a;font-weight:700} -.design-own .design-row-head label::before{content:'● ';color:var(--accent)} -.design-reset-one{font-size:14px;padding:2px 6px;min-height:24px;line-height:1} -.design-inherited{font-size:10px;color:var(--muted);margin:5px 0 0;line-height:1.45} -.color-control{display:grid;grid-template-columns:38px 1fr;gap:6px;align-items:center} -.color-control input[type=color]{padding:2px;min-height:38px;width:38px;border-radius:6px;cursor:pointer} -.color-control .color-text{font-size:12px;min-height:38px} -.color-control .color-palette{grid-column:1 / -1;font-size:11px;min-height:34px} -.length-control{display:grid;grid-template-columns:1fr 74px;gap:6px} -.length-control input,.length-control select{font-size:12px;min-height:38px} -.length-control.is-inherited input{color:var(--muted)} -.number-control{display:grid;grid-template-columns:1fr 74px;gap:8px;align-items:center} -.number-control input[type=range]{width:100%;min-height:24px;padding:0;accent-color:#437754} -.number-control input[type=number]{font-size:12px;min-height:38px} -.side-wrap{margin-bottom:6px} -.side-control{display:grid;grid-template-columns:1fr 1fr;gap:8px} -.side-cell label{margin:0 0 4px;font-size:10px;color:var(--muted)} -.side-all{font-size:10px;margin-top:8px;padding:4px 6px} -.design-actions{display:grid;gap:8px;margin-top:22px} -.design-actions button{width:100%} +/* ---- buttons --------------------------------------------------------------------------- */ -.theme-form .theme-swatch{width:52px;min-height:38px;padding:2px;cursor:pointer} -.theme-form .weights-cell{white-space:nowrap} -.theme-form .weights-cell label{display:inline-flex;align-items:center;gap:3px;margin:0 8px 0 0;font-size:10px;font-weight:400} -.theme-form .weights-cell input{width:14px;min-height:14px} +.primary, .secondary, .subtle, .text-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 9px; + border: 1px solid var(--line); + border-radius: 8px; + padding: 10px 16px; + text-decoration: none; + font-size: 13px; + font-weight: 600; + background: var(--paper); + color: var(--ink); + white-space: nowrap; + min-height: 40px; +} +.primary { background: var(--accent); color: #fff; border-color: var(--accent) } +.primary:hover:not(:disabled) { background: #22553e } +.secondary:hover:not(:disabled) { background: #eef2ea; border-color: #cfd8d1 } +.subtle, .text-button { border-color: transparent; background: transparent } +.subtle:hover:not(:disabled) { background: #e8ede6 } +.text-button { padding: 4px 8px; min-height: 28px; font-size: 12px } +.full-width { width: 100% } +@media (prefers-reduced-motion: no-preference) { + button, a, summary { transition: background-color .12s, border-color .12s, color .12s } +} -.contrast-note{margin:16px 0 0;padding:10px 12px;border-radius:7px;font-size:11px;line-height:1.5;font-weight:600} -.contrast-ok{background:#edf3dc;color:#315b49;border:1px solid #c5d89a} -.contrast-warn{background:#fff6e2;color:#78551d;border:1px solid #e6cf9c} -.contrast-bad{background:#fff0eb;color:#8b2f22;border:1px solid #e2b2a7} -.media-control{display:grid;gap:8px} -.media-chosen{margin:0;font-size:11px;color:var(--muted)} -.design-reuse{display:grid;gap:8px;margin-top:24px;padding-top:18px;border-top:1px solid var(--line)} -.design-reuse button,.design-reuse select{width:100%} -#preset-dialog label{margin-top:18px} -#preset-dialog .field-error{margin:10px 0 0;font-size:12px;color:#8b2f22;font-weight:600} +/* ---- the shell: one sidebar, always the same, so every screen is one click away --------- */ -.page-card.depth-1{margin-left:28px}.page-card.depth-2{margin-left:56px}.page-card.depth-3{margin-left:84px} +.admin-shell { display: grid; grid-template-columns: 244px minmax(0, 1fr); min-height: 100dvh } -/* Formatted text: toolbar, editing surface and HTML view. */ -.rich-text{border:1px solid var(--line);border-radius:8px;background:#fff;overflow:hidden} -.rt-bar{display:flex;flex-wrap:wrap;gap:2px;padding:5px;border-bottom:1px solid var(--line);background:#fafbf8} -.rt-button{min-width:30px;min-height:30px;padding:2px 7px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--ink);font-size:12px;line-height:1} -.rt-button:hover{background:#eef2e7;border-color:var(--line)} -.rt-toggle{margin-left:auto;font-size:10px;font-weight:700;letter-spacing:.5px} -.rt-toggle[aria-pressed=true]{background:var(--ink);color:#fff} -.rt-link{display:flex;gap:6px;padding:6px;border-bottom:1px solid var(--line);background:#fff6e2} -.rt-link input{font-size:11px;min-height:30px} -.rt-surface{min-height:120px;max-height:340px;overflow:auto;padding:11px 12px;font-size:13px;line-height:1.65} -.rt-surface:focus-visible{outline:2px solid #b55b12;outline-offset:-2px} -.rt-surface ul,.rt-surface ol{margin:0 0 8px;padding-left:20px} -.rt-surface a{color:#a4510f} -.rt-source{min-height:150px;border:0;border-radius:0;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6} +.shell-nav { + display: flex; + flex-direction: column; + gap: 4px; + padding: 20px 14px; + background: var(--shell); + color: var(--shell-ink); + position: sticky; + top: 0; + height: 100dvh; + overflow: auto; +} +.shell-nav .brand { color: #fff; margin: 4px 6px 22px } +.shell-nav .brand-icon { background: var(--mint); color: var(--shell) } +.nav-label { + font-size: 9px; + font-weight: 700; + letter-spacing: 1.6px; + color: var(--shell-muted); + margin: 18px 8px 7px; +} +.nav-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px } +.nav-item { + display: flex; + align-items: center; + gap: 11px; + padding: 9px 10px; + border-radius: 8px; + text-decoration: none; + font-size: 13px; + color: var(--shell-ink); +} +.nav-item:hover { background: #21362d } +.nav-item[aria-current=page] { background: #2b4a3b; color: #fff; font-weight: 650 } +.nav-item[aria-current=page] .nav-icon { color: var(--mint) } +.nav-icon { width: 19px; text-align: center; font-size: 14px; color: var(--shell-muted); flex-shrink: 0 } +.nav-out::after { content: '↗'; margin-left: auto; color: var(--shell-muted); font-size: 11px } -/* Own CSS and own script per element. */ -.own-code{font-family:ui-monospace,monospace;font-size:12px;line-height:1.6;min-height:110px} -.own-code[readonly]{background:#f4f5f1;color:var(--muted)} +.shell-foot { margin-top: auto; padding-top: 20px; border-top: 1px solid #2a3d35; display: grid; gap: 6px } +.shell-user { display: flex; align-items: center; gap: 10px; padding: 8px 10px; font-size: 12px } +.shell-user strong { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap } +.shell-avatar { + display: grid; + place-items: center; + width: 28px; height: 28px; + border-radius: 50%; + background: #2b4a3b; + color: var(--mint); + font-size: 12px; + font-weight: 700; + flex-shrink: 0; +} +.shell-foot form { margin: 0 } +.shell-foot button { + width: 100%; + justify-content: flex-start; + color: var(--shell-muted); + font-size: 12px; + font-weight: 500; + min-height: 34px; + padding: 6px 10px; +} +.shell-foot button:hover { background: #21362d; color: var(--shell-ink) } + +.brand { + display: inline-flex; + align-items: center; + font-size: 22px; + font-weight: 700; + text-decoration: none; + letter-spacing: -1.1px; + gap: 2px; +} +.brand > span:last-child { font-weight: 400 } +.brand-icon { + display: grid; + place-items: center; + width: 31px; height: 31px; + background: var(--ink); + color: var(--mint); + border-radius: 9px; + margin-right: 7px; + font-size: 15px; +} + +/* ---- the screen itself ----------------------------------------------------------------- */ + +.shell-main { min-width: 0; display: flex; flex-direction: column } +.shell-top { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 20px 30px; + flex-wrap: wrap; + padding: 30px 38px 24px; + border-bottom: 1px solid var(--line); + background: var(--paper); +} +.shell-heading { min-width: 0 } +.shell-heading p { font-size: 13px; margin: 7px 0 0; max-width: 62ch } +.shell-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap } +.shell-body { padding: 30px 38px 60px; max-width: 1180px; width: 100% } +.stack { display: grid; gap: 20px } + +.notice { padding: 13px 17px; border-radius: 8px; line-height: 1.6; font-size: 13px; margin: 0 } +.notice.ok { background: #edf3dc; color: #2c5340; border: 1px solid #c5d89a } +.error, .notice.error { background: #fff0eb; color: var(--danger); border: 1px solid #e2b2a7 } +.shell-body > .notice { margin-bottom: 4px } + +/* ---- cards and sections ---------------------------------------------------------------- */ + +.card { background: var(--paper); border: 1px solid var(--line); border-radius: 12px } +.card-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px 20px; + flex-wrap: wrap; + padding: 19px 22px; + border-bottom: 1px solid var(--line); +} +.card-head h2, .card-head h3 { margin: 0 } +.card-head .muted { font-size: 12px } +.card-body { padding: 8px 22px 24px } +/* A heading for a group of rows, outside any card. */ +.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 20px; flex-wrap: wrap; margin: 16px 2px -6px } +.section-head h2 { margin: 0 } +.section-head .muted { font-size: 12px } +.card-body > p:first-child { margin-top: 14px } +.card-body .primary, .card-body .secondary { margin-top: 22px } +.card-foot { padding: 16px 22px; border-top: 1px solid var(--line); background: #fafbf8; border-radius: 0 0 12px 12px } + +/* A card that opens: used for everything secondary, so a screen starts calm. */ +details.card > summary { + padding: 17px 22px; + font-size: 13px; + font-weight: 650; + cursor: pointer; + border-radius: 12px; + list-style: none; + display: flex; + align-items: center; + gap: 10px; +} +details.card > summary::before { content: '+'; color: var(--accent); font-size: 16px; width: 12px } +details.card[open] > summary { border-bottom: 1px solid var(--line); border-radius: 12px 12px 0 0 } +details.card[open] > summary::before { content: '–' } +details.card > summary:hover { background: #fafbf8 } + +/* ---- rows: one line per page, menu, area or file ---------------------------------------- */ + +.rows { display: grid; gap: 9px } +.row { + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; + padding: 14px 18px; + background: var(--paper); + border: 1px solid var(--line); + border-radius: 11px; +} +.row-top { align-items: flex-start } +/* A row that carries its own disclosure is a stack, not a line. */ +.row-stack { display: block; padding: 0 } +.row-line { display: flex; align-items: center; gap: 14px; padding: 14px 18px; flex-wrap: wrap } +.row-icon { + display: grid; + place-items: center; + width: 38px; height: 38px; + border-radius: 9px; + background: #eef3e7; + color: var(--accent); + font-size: 17px; + flex-shrink: 0; +} +.row-main { flex: 1; min-width: 170px } +.row-main h3 { margin: 0; font-size: 14px; font-weight: 650 } +.row-main h3 a { text-decoration: none } +.row-main h3 a:hover { text-decoration: underline } +.row-main p { margin: 4px 0 0; font-size: 12px; color: var(--muted) } +.row-actions { display: flex; align-items: center; gap: 7px; flex-wrap: wrap } +.row-actions .secondary, .row-actions .text-button { font-size: 12px; padding: 8px 12px; min-height: 36px } +/* The hierarchy of the page tree. A class, not a style attribute: the administration forbids + inline styles, so an attribute would be dropped and the tree would look flat. */ +.row.depth-1 { margin-left: 26px } +.row.depth-2 { margin-left: 52px } +.row.depth-3 { margin-left: 78px } + +.pill { + font-size: 11px; + font-weight: 650; + padding: 5px 11px; + border-radius: 20px; + white-space: nowrap; + background: #eef2ea; + color: #4a5751; +} +.pill-live { background: #e7f0d6; color: #2c5340 } +.pill-draft { background: #fff2d9; color: #6f4e18 } +.pill-off { background: #f1f2f0; color: #5a635e } + +/* The per-row settings. One disclosure that opens in place, instead of six competing popovers + that clip each other at the edge of the card. */ +.sheet { border-top: 1px solid var(--line) } +.row > .sheet:first-of-type { border-top: 0 } +.sheet > summary { + list-style: none; + cursor: pointer; + padding: 11px 18px; + font-size: 12px; + font-weight: 600; + color: var(--muted); + display: flex; + align-items: center; + gap: 8px; +} +.sheet > summary::before { content: '⚙'; font-size: 13px } +.sheet > summary:hover { color: var(--ink); background: #fafbf8 } +.sheet[open] > summary { color: var(--ink); border-bottom: 1px solid var(--line) } +.sheet-body { padding: 4px 18px 22px; background: #fafbf8; border-radius: 0 0 11px 11px } +.sheet-body form { margin: 0 } +.sheet-part { padding-top: 14px; margin-top: 18px; border-top: 1px solid var(--line) } +.sheet-part:first-child { padding-top: 0; margin-top: 0; border-top: 0 } +.sheet-part > h4 { margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .3px } +.sheet-part > p { font-size: 12px; line-height: 1.6; color: var(--muted); margin: 7px 0 0 } +.sheet-part .primary, .sheet-part .secondary { margin-top: 18px } +.sheet-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center } +.sheet-actions form { margin: 0 } + +/* ---- tables ---------------------------------------------------------------------------- */ + +/* A table scrolls sideways instead of pushing the card out of the screen. As a block it also + stops stretching its columns to the full width. */ +.table { display: block; overflow-x: auto; max-width: 100%; border-collapse: collapse; font-size: 13px } +.table th { + text-align: left; + font-size: 11px; + letter-spacing: .4px; + color: var(--muted); + padding: 0 10px 9px 0; + font-weight: 700; + white-space: nowrap; +} +.table td { padding: 5px 10px 5px 0; vertical-align: top } +.table tbody tr + tr td { border-top: 1px solid #eef1ed } +.table input, .table select { font-size: 12px; min-height: 38px; padding: 8px 10px } +.table .check-cell { text-align: center } +.table .check-cell input { width: 18px; min-height: 18px } +.table-new input, .table-new select { background: #fafbf8 } +.table form { margin: 0 } +/* Column widths per screen: the two tables carry different things. */ +.menu-table td:first-child input { width: 82px } +.menu-table td:nth-child(2) select { width: 148px } +.menu-table td:nth-child(3) input { min-width: 170px } +.menu-table td:nth-child(4) select { width: 108px } +.menu-table td:nth-child(5) select { min-width: 190px } +.menu-table td:nth-child(6) input { width: 130px } +.menu-table td:nth-child(7) input { min-width: 190px } +.field-table td:first-child input { width: 140px } +.field-table td:nth-child(2) select { width: 150px } +.field-table td:nth-child(3) input { min-width: 170px } +.field-table td:nth-child(5) input { min-width: 180px } +.field-table td:nth-child(6) input { min-width: 170px } +.theme-table td input { width: 190px } +.theme-table td select { width: 150px } +.theme-table td input[type=color] { width: 52px } +.theme-table .weights-cell { white-space: nowrap } +.redirect-table td { padding: 8px 12px 8px 0; font-size: 12px } +/* Headings inside a card separate its parts without a second border. */ +.card-body .section-head { margin: 28px 0 12px } +.card-body .section-head:first-child { margin-top: 14px } +.card-body > .primary, .card-body > .secondary { margin-top: 24px } + +/* A screen made of several equal sections (menu slots, form definitions). */ +.stack-section { display: grid; gap: 10px } +.menu-form > label:first-of-type, .form-def > label:first-of-type { margin-top: 16px } +.menu-form > input[name=name] { max-width: 420px } +.menu-form .table, .form-def .table { margin-top: 22px } +.menu-form .primary, .form-def .primary { margin-top: 20px } + +.empty { + padding: 34px; + border: 1px dashed #cfd8d1; + border-radius: 12px; + background: var(--paper); + text-align: center; +} +.empty h3 { margin-bottom: 8px } +.empty p { margin: 0; font-size: 13px; color: var(--muted) } + +/* ---- contrast hints on the design screen ----------------------------------------------- */ + +.contrast-note { margin: 14px 0 0; padding: 10px 12px; border-radius: 7px; font-size: 12px; line-height: 1.5; font-weight: 600 } +.contrast-ok { background: #edf3dc; color: #2c5340; border: 1px solid #c5d89a } +.contrast-warn { background: #fff6e2; color: #6f4e18; border: 1px solid #e6cf9c } +.contrast-bad { background: #fff0eb; color: var(--danger); border: 1px solid #e2b2a7 } +.theme-form .theme-swatch { width: 52px; min-height: 38px; padding: 2px; cursor: pointer } +.theme-form .weights-cell { white-space: nowrap } +.theme-form .weights-cell label { display: inline-flex; align-items: center; gap: 4px; margin: 0 9px 0 0; font-size: 11px; font-weight: 400 } +.theme-form .weights-cell input { width: 14px; min-height: 14px } + +/* ---- submissions ----------------------------------------------------------------------- */ + +.submission-data { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 10px 0 0; font-size: 12px } +.submission-data dt { font-weight: 600; color: var(--muted) } +.submission-data dd { margin: 0; overflow-wrap: anywhere } + +/* ---- login ----------------------------------------------------------------------------- */ + +.login-body { + min-height: 100dvh; + display: grid; + place-items: center; + background: radial-gradient(ellipse at 20% 0%, #dfeab0, transparent 60%), var(--ground); + padding: 30px; +} +.login-card { + width: 100%; + max-width: 430px; + padding: 40px; + background: var(--paper); + border: 1px solid var(--line); + border-radius: 16px; + box-shadow: 0 20px 60px #1b2a2411; +} +.login-card .brand { margin-bottom: 38px } +.login-card h1 { font-size: 26px } +.login-card .muted { font-size: 14px; margin-top: 8px } +.login-card .primary { margin-top: 26px } +.login-card .notice { margin: 22px 0 0 } +.quiet-link { display: block; font-size: 12px; text-align: center; margin-top: 24px; color: var(--muted) } + +/* ---- narrow screens: the sidebar becomes a strip above the content --------------------- */ + +@media (max-width: 1000px) { + .admin-shell { grid-template-columns: 1fr } + .shell-nav { + position: static; + height: auto; + flex-direction: row; + align-items: center; + flex-wrap: nowrap; + overflow-x: auto; + gap: 6px; + padding: 11px 14px; + } + .shell-nav .brand { margin: 0 10px 0 0; font-size: 0 } + .shell-nav .brand-icon { margin: 0; font-size: 15px } + .nav-label { display: none } + .nav-list { display: flex; gap: 4px } + .nav-item { white-space: nowrap; padding: 8px 12px } + .shell-foot { margin: 0 0 0 auto; padding: 0 0 0 14px; border-top: 0; border-left: 1px solid #2a3d35; display: flex; align-items: center } + .shell-user strong { display: none } + .shell-foot button { width: auto } + .shell-top { padding: 22px 20px 18px } + .shell-body { padding: 20px 20px 48px } +} + +@media (max-width: 620px) { + h1 { font-size: 23px } + .row-line { padding: 13px 14px; gap: 10px } + .row-actions { width: 100%; justify-content: flex-start } + .card-head, .sheet > summary { padding-inline: 16px } + .card-body, .sheet-body { padding-inline: 16px } + .row.depth-1, .row.depth-2, .row.depth-3 { margin-left: 14px } + .login-card { padding: 26px } + .login-body { padding: 16px } +} diff --git a/public/assets/editor.css b/public/assets/editor.css new file mode 100644 index 0000000..b33e5be --- /dev/null +++ b/public/assets/editor.css @@ -0,0 +1,206 @@ +/* The editor and the media library. Only the editor and the media screen load this; the + management screens get by with admin.css alone. Rules moved out of admin.css unchanged. */ +dialog{max-width:450px;width:calc(100% - 40px);padding:28px;background:var(--paper);border:1px solid var(--line);border-radius:12px;color:var(--ink);box-shadow:0 20px 80px #1b2a2433} +dialog::backdrop{background:#172e2966} +dialog p{font-size:14px;line-height:1.7;color:var(--muted)} +.editor-page-name h1{font-size:13px;font-weight:600;letter-spacing:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0} +.canvas-scroll:focus-visible{outline:3px solid #b55b12;outline-offset:-3px} + +.media-launch{margin-top:12px;white-space:normal} +.media-dialog{width:calc(100% - 32px);max-width:1100px;max-height:calc(100dvh - 32px);overflow:auto} +.media-heading{display:flex;justify-content:space-between;gap:20px;align-items:center} +.media-heading h2{margin:0} +.media-heading .eyebrow{margin-bottom:8px} +.media-tools{display:grid;grid-template-columns:1fr 1fr;gap:24px} +.media-tools label{margin-top:10px} +.media-layout{display:grid;grid-template-columns:minmax(0,1fr) 270px;gap:24px} +.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(145px,1fr));gap:12px;align-content:start;max-height:430px;overflow:auto;padding:4px} +.media-card{border:2px solid var(--line);border-radius:9px;padding:7px;background:white;color:var(--ink);display:flex;flex-direction:column;gap:7px;text-align:left;min-width:0} +.media-card[aria-pressed=true]{border-color:var(--accent);background:#edf4dc} +.media-card img{width:100%;height:115px;object-fit:cover;border-radius:5px} +.media-card strong{font-size:12px;overflow-wrap:anywhere} +.media-card span{font-size:11px;color:var(--muted)} +.media-details{padding-left:22px;border-left:1px solid var(--line)} +.media-details button,.media-details a{margin-top:12px;display:block} +.media-details input{font-size:13px} +.media-footer{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:20px;border-top:1px solid var(--line);padding-top:15px} +.media-footer label{margin:0;font-size:12px} +#media-error{color:#8b2f22;background:#fff0eb;padding:12px;border-radius:6px} +#media-status{font-size:12px;margin-block:15px} +.media-dialog> .panel-hint{margin-top:20px} +.media-details>p{font-size:12px} +@media(max-width:650px){.media-dialog{padding:18px}.media-tools,.media-layout{grid-template-columns:1fr}.media-details{padding:20px 0 0;border-left:0;border-top:1px solid var(--line)}.media-grid{max-height:280px;grid-template-columns:repeat(2,minmax(0,1fr))}.media-footer{align-items:stretch;flex-direction:column}.media-heading{gap:8px}} +.icon-button,.block-actions button,.viewport-controls button{display:inline-flex;align-items:center;justify-content:center;gap:12px;border:1px solid var(--line);border-radius:7px;padding:11px 15px;text-decoration:none;font-size:12px;font-weight:600;background:white;color:var(--ink);white-space:nowrap;min-height:40px} +.editor-message{padding:14px 18px;border-radius:7px;line-height:1.6;font-size:13px} +.editor-body{overflow:hidden} +#editor{height:100dvh;display:flex;flex-direction:column} +.editor-header{display:flex;align-items:center;gap:20px;min-height:76px;padding:13px 20px;border-bottom:1px solid var(--line);background:white;z-index:1} +.back-link{display:flex;gap:9px;text-decoration:none;font-size:12px;padding-right:20px;border-right:1px solid var(--line)} +.editor-page-name{min-width:0;flex:1;display:grid;gap:5px} +.editor-page-name strong{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.editor-page-name>span{font-size:10px;color:var(--muted)} +.history-actions,.save-actions{display:flex;align-items:center;gap:8px} +.icon-button{font-size:21px;min-width:36px;padding:3px 9px} +.save-actions>span{font-size:10px;color:var(--muted);max-width:130px;margin-right:8px} +.editor-layout{display:grid;grid-template-columns:258px minmax(200px,1fr) 280px;flex:1;min-height:0} +.left-panel,.right-panel{background:white;overflow:auto} +.left-panel{border-right:1px solid var(--line)} +.right-panel{border-left:1px solid var(--line)} +.panel-section{padding:23px 18px;border-bottom:1px solid var(--line)} +.panel-section .eyebrow{margin-bottom:10px} +.panel-section h2{font-size:14px;margin-bottom:12px} +.panel-hint{font-size:11px;color:var(--muted);line-height:1.6;margin-bottom:17px} +.library{display:grid;grid-template-columns:1fr 1fr;gap:7px} +.library button{min-height:64px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;border:1px solid var(--line);border-radius:7px;background:#fff;color:var(--ink);font-size:10px} +.library button:hover:not(:disabled){border-color:var(--accent);background:#f3f7e8} +.library button span{font-size:19px;line-height:1} +.panel-title{display:flex;justify-content:space-between;align-items:center} +.panel-title h2{margin-bottom:0} +.panel-title+.panel-hint{margin-top:10px} +.structure-list{list-style:none;margin:0;padding:0} +.structure-list .structure-list{padding-left:13px;border-left:1px solid #e5e9e3;margin-left:8px} +.tree-item{display:flex;align-items:center;gap:7px;width:100%;text-align:left;border:1px solid transparent;background:none;color:#42584b;border-radius:5px;padding:9px 7px;font-size:11px;min-height:34px} +.tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.tree-item:hover{background:#f3f6ef} +.tree-item[aria-pressed=true]{background:#eaf1d5;border-color:#c5d89a;color:#203b34;font-weight:600} +.tree-icon{font-size:12px;min-width:15px} +.drop-target{outline:2px dashed #315b49} +.block-actions{display:flex;flex-wrap:wrap;gap:5px;border-top:1px solid var(--line);padding-top:13px;margin-top:16px} +.block-actions button{font-size:10px;padding:7px 9px;min-height:33px} +.block-actions .danger-text{color:#943a2e} +.canvas-panel{min-width:0;display:flex;flex-direction:column;background:#e9ede7;overflow:hidden} +.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 20px;background:#f6f8f2;border-bottom:1px solid var(--line);min-height:59px} +.canvas-label{font-size:9px;font-weight:700;letter-spacing:1.2px;color:#57655d} +.viewport-controls{display:flex;gap:3px;background:#e8eee1;border-radius:7px;padding:3px} +.viewport-controls button{padding:5px 9px;font-size:10px;min-height:28px;border:0;background:transparent} +.viewport-controls [aria-pressed=true]{background:white;box-shadow:0 1px 3px #00000012} +.canvas-toolbar>a{text-decoration:none;font-size:18px} +.canvas-scroll{flex:1;min-height:0;overflow:auto;padding:22px;display:flex;justify-content:center;align-items:flex-start} +#preview{background:white;border:0;box-shadow:0 3px 20px #203b3410;min-height:600px;height:100%;flex-shrink:0} +.viewport-desktop{width:100%} +.viewport-tablet{width:768px;max-width:100%} +.viewport-mobile{width:390px;max-width:100%} +.preview-status{font-size:10px;padding:9px 20px;color:#52614f;margin:0;min-height:30px;background:#f3f5ed} +.right-panel label{font-size:11px} +.right-panel input,.right-panel select,.right-panel textarea{font-size:12px} +.right-panel textarea{min-height:150px} + +.panel-note{font-size:11px;color:var(--muted);line-height:1.7;padding:20px} + + +.editor-message{border-radius:0;background:#fff0e9;color:#873e24;border-bottom:1px solid #e6c3b2;flex-shrink:0;padding:9px 20px;font-size:12px} +.recovery-bar{display:flex;gap:12px;align-items:center;padding:10px 20px;border-bottom:1px solid var(--line);background:#fff;font-size:12px;flex-wrap:wrap} +.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:25px} +@media(max-width:1150px){.editor-layout{grid-template-columns:215px minmax(200px,1fr) 240px}.editor-header{gap:12px;padding-inline:14px}.save-actions>span{display:none}.panel-section{padding:18px 14px}.canvas-scroll{padding:12px}.canvas-label{display:none}} +@media(max-width:850px){.editor-body{overflow:auto}#editor{height:auto;min-height:100dvh}.editor-header{flex-wrap:wrap}.editor-page-name{min-width:180px}.save-actions{width:100%;justify-content:flex-end}.save-actions>span{display:block;margin-right:auto}.editor-layout{display:flex;flex-wrap:wrap}.canvas-panel{order:0;width:100%;height:680px}.left-panel{order:1;width:45%;max-height:650px}.right-panel{order:2;width:55%;max-height:650px}.canvas-label{display:block}.editor-header .back-link{padding-right:12px}.editor-message{font-size:11px}.editor-layout .panel-hint{font-size:10px}} +.left-panel{display:flex;flex-direction:column;overflow:hidden} +.left-panel>.panel-section:first-child{max-height:45%;overflow:auto;flex-shrink:0} +.structure-section{display:flex;flex-direction:column;flex:1;min-height:0} +.structure-section #structure{overflow:auto;flex:1;min-height:90px;padding:3px} +.structure-section .block-actions{flex-shrink:0;background:white} +.structure-section .panel-hint{margin:8px 0} +.search-label{font-size:11px;margin:12px 0 6px} +#tree-search{font-size:11px;padding:9px;min-height:36px} +.tree-tools{display:flex;flex-wrap:wrap;gap:3px;margin-top:5px} +.tree-tools .text-button{font-size:10px;padding:5px} +.structure-list li{position:relative} +.tree-item{padding-left:23px} +.tree-toggle{position:absolute;top:5px;left:0;width:22px;height:25px;z-index:1;border:0;background:transparent;color:var(--ink);border-radius:4px} +.preset-heading{font-size:12px;margin:22px 0 8px} +.preset-library{display:grid;gap:6px} +.preset-library button{display:flex;gap:10px;text-align:left;align-items:center;min-height:40px;padding:9px 12px;border:1px solid var(--line);border-radius:7px;background:#f7f9f2;color:var(--ink);font-size:11px} +.preset-library button:hover:not(:disabled){border-color:var(--accent)} +.preset-library span{font-size:18px} +.property-tabs{display:flex;background:#edf1e7;padding:4px;border-radius:8px;gap:4px} +.property-tabs button{flex:1;min-width:0;border:0;background:transparent;color:var(--ink);border-radius:6px;padding:10px;font-size:12px;font-weight:600} +.property-tabs [aria-pressed=true]{background:white;box-shadow:0 1px 3px #0001} +.design-scope{font-size:12px;font-weight:600;line-height:1.6;margin:18px 0 8px;padding:10px;border-left:3px solid var(--accent);background:#f1f5e7} +.design-reset{margin-top:24px;white-space:normal;width:100%} +.breadcrumbs{display:flex;align-items:center;flex-wrap:wrap;gap:3px;min-height:35px;padding:3px 14px;background:#f6f8f2;border-top:1px solid var(--line)} +.breadcrumbs .text-button{font-size:10px;white-space:normal} +.breadcrumbs button+button:before{content:'›';color:var(--muted)} +.breadcrumbs [aria-current=location]{font-weight:700;background:#e5edda} +.canvas-scroll{display:block} +.canvas-scroll #preview-stage{position:relative;margin:0 auto} +#preview{position:absolute;top:0;left:0;transform-origin:top left;max-width:none;min-height:0} +.right-panel select{padding-inline:7px;text-overflow:ellipsis} +.right-panel .panel-section{min-width:0} +@media(max-width:850px){.left-panel,.right-panel{height:650px}.left-panel>.panel-section:first-child{max-height:40%}.structure-section{padding:12px 7px}.tree-item{font-size:10px;gap:3px}.tree-icon{min-width:10px}.structure-list .structure-list{padding-left:5px;margin-left:4px}.block-actions button{padding:6px;white-space:normal}.property-tabs button{padding:9px 4px;font-size:11px}.canvas-scroll{padding:12px}.right-panel label{line-height:1.5}.tree-tools .text-button{white-space:normal}} +.design-group{border:1px solid var(--line);border-radius:9px;margin-top:10px;background:#fff} +.design-group>summary{cursor:pointer;padding:12px 14px;font-size:12px;font-weight:700;letter-spacing:.3px;list-style:none;display:flex;justify-content:space-between;align-items:center} +.design-group>summary::after{content:'+';font-size:17px} +.design-group[open]>summary{border-bottom:1px solid var(--line)} +.design-group[open]>summary::after{content:'–'} +.design-group-body{padding:6px 14px 16px} +.design-group-label{font-size:11px;font-weight:700;color:var(--muted);margin:18px 0 6px;letter-spacing:.3px} +.design-row{margin-top:14px} +.design-row-head{display:flex;justify-content:space-between;align-items:center;gap:8px} +.design-row-head label{margin:0;font-size:11px} +.design-own .design-row-head label{color:#2f5d4a;font-weight:700} +.design-own .design-row-head label::before{content:'● ';color:var(--accent)} +.design-reset-one{font-size:14px;padding:2px 6px;min-height:24px;line-height:1} +.design-inherited{font-size:10px;color:var(--muted);margin:5px 0 0;line-height:1.45} +.color-control{display:grid;grid-template-columns:38px 1fr;gap:6px;align-items:center} +.color-control input[type=color]{padding:2px;min-height:38px;width:38px;border-radius:6px;cursor:pointer} +.color-control .color-text{font-size:12px;min-height:38px} +.color-control .color-palette{grid-column:1 / -1;font-size:11px;min-height:34px} +.length-control{display:grid;grid-template-columns:1fr 74px;gap:6px} +.length-control input,.length-control select{font-size:12px;min-height:38px} +.length-control.is-inherited input{color:var(--muted)} +.number-control{display:grid;grid-template-columns:1fr 74px;gap:8px;align-items:center} +.number-control input[type=range]{width:100%;min-height:24px;padding:0;accent-color:#437754} +.number-control input[type=number]{font-size:12px;min-height:38px} +.side-wrap{margin-bottom:6px} +.side-control{display:grid;grid-template-columns:1fr 1fr;gap:8px} +.side-cell label{margin:0 0 4px;font-size:10px;color:var(--muted)} +.side-all{font-size:10px;margin-top:8px;padding:4px 6px} +.design-actions{display:grid;gap:8px;margin-top:22px} +.design-actions button{width:100%} + + + + +.media-control{display:grid;gap:8px} +.media-chosen{margin:0;font-size:11px;color:var(--muted)} +.design-reuse{display:grid;gap:8px;margin-top:24px;padding-top:18px;border-top:1px solid var(--line)} +.design-reuse button,.design-reuse select{width:100%} +#preset-dialog label{margin-top:18px} +#preset-dialog .field-error{margin:10px 0 0;font-size:12px;color:#8b2f22;font-weight:600} +.rich-text{border:1px solid var(--line);border-radius:8px;background:#fff;overflow:hidden} +.rt-bar{display:flex;flex-wrap:wrap;gap:2px;padding:5px;border-bottom:1px solid var(--line);background:#fafbf8} +.rt-button{min-width:30px;min-height:30px;padding:2px 7px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--ink);font-size:12px;line-height:1} +.rt-button:hover{background:#eef2e7;border-color:var(--line)} +.rt-toggle{margin-left:auto;font-size:10px;font-weight:700;letter-spacing:.5px} +.rt-toggle[aria-pressed=true]{background:var(--ink);color:#fff} +.rt-link{display:flex;gap:6px;padding:6px;border-bottom:1px solid var(--line);background:#fff6e2} +.rt-link input{font-size:11px;min-height:30px} +.rt-surface{min-height:120px;max-height:340px;overflow:auto;padding:11px 12px;font-size:13px;line-height:1.65} +.rt-surface:focus-visible{outline:2px solid #b55b12;outline-offset:-2px} +.rt-surface ul,.rt-surface ol{margin:0 0 8px;padding-left:20px} +.rt-surface a{color:#a4510f} +.rt-source{min-height:150px;border:0;border-radius:0;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6} +.own-code{font-family:ui-monospace,monospace;font-size:12px;line-height:1.6;min-height:110px} +.own-code[readonly]{background:#f4f5f1;color:var(--muted)} +/* Bringing the editor chrome in line with the new administration: same radii, same restraint, + and a few sizes that were simply too small to read comfortably. The layout is unchanged. */ +.editor-header{min-height:70px} +.back-link{font-size:12px;font-weight:600;gap:7px;align-items:center} +.editor-page-name h1{font-size:14px} +.panel-section h2{font-size:13px;letter-spacing:0} +.panel-section .eyebrow{font-size:9px;letter-spacing:1.4px} +.library{gap:8px} +.library button{min-height:68px;border-radius:9px;font-size:11px;gap:7px} +.library button span{font-size:20px} +.tree-item{font-size:12px;border-radius:6px} +.right-panel label{font-size:12px} +.right-panel input,.right-panel select,.right-panel textarea{font-size:12px;border-radius:7px} +.property-tabs{padding:3px;border-radius:9px} +.property-tabs button{border-radius:7px;font-size:12px} +.viewport-controls{border-radius:8px} +.viewport-controls button{border-radius:6px;font-size:11px} +.design-group{border-radius:10px} +.design-scope{border-radius:0 7px 7px 0} +#preview{box-shadow:0 6px 28px #1b2a2414} +.canvas-panel{background:#e7ebe5} +.canvas-toolbar,.breadcrumbs,.preview-status{background:#f5f7f2} +.icon-button{border-radius:8px} diff --git a/templates/admin/base.html.twig b/templates/admin/base.html.twig index e663b3c..a56ac60 100644 --- a/templates/admin/base.html.twig +++ b/templates/admin/base.html.twig @@ -7,6 +7,7 @@ {% block title %}Verwaltung{% endblock %} · Kant CMS +{% block styles %}{% endblock %} {% block scripts %}{% endblock %} diff --git a/templates/admin/editor.html.twig b/templates/admin/editor.html.twig index e7259ba..5e3d450 100644 --- a/templates/admin/editor.html.twig +++ b/templates/admin/editor.html.twig @@ -3,6 +3,7 @@ {% set subject = area ? area.document : page.document %} {% block title %}{{ subject.title }} bearbeiten{% endblock %} {% block body_class %}editor-body{% endblock %} +{% block styles %}{% endblock %} {% block scripts %}{% endblock %} {% block body %} {# The API base decides what is edited. An area is shared, so it has no draft and no diff --git a/templates/admin/forms.html.twig b/templates/admin/forms.html.twig index f40ed13..6cf798d 100644 --- a/templates/admin/forms.html.twig +++ b/templates/admin/forms.html.twig @@ -1,105 +1,94 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'forms' %} {% block title %}Formulare{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

FORMULARE

Formulare

-

Felder, Empfänger und Einwilligungstext werden hier gepflegt. Auf einer Seite steht nur ein Formularblock mit der Adresse – so kann ein gespeicherter Seiteninhalt niemals ändern, wohin eine Anfrage geht.

- - - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} - - {% for form in forms|merge([null]) %} - {% set new = form is null %} - {% set rows = new ? [] : form.fields %} - - {% endfor %} - -

Einsendungen {{ submissions|length }}

Die neuesten 50.
- {% if submissions is empty %} -

Noch keine Einsendungen.

Hier erscheinen eingegangene Nachrichten, auch wenn der E-Mail-Versand fehlschlägt.

- {% else %} -
- {% for entry in submissions %} -
-
-

{{ entry.form }}

-

{{ entry.createdAt|date('d.m.Y H:i') }} UTC{% if entry.consentAt %} · Einwilligung {{ entry.consentAt|date('d.m.Y H:i') }}{% endif %}

-
{% for key, value in entry.data %}
{{ key }}
{{ value }}
{% endfor %}
-
- {{ entry.delivered ? 'Versendet' : 'Nicht versendet' }} -
- {% endfor %} +{% block heading %}Formulare{% endblock %} +{% block lead %}Felder, Empfänger und Einwilligungstext werden hier gepflegt. Auf einer Seite steht nur ein Formularblock mit der Adresse – so kann ein gespeicherter Seiteninhalt niemals ändern, wohin eine Anfrage geht.{% endblock %} +{% block content %} +{% for form in forms|merge([null]) %} +{% set new = form is null %} +{% set rows = new ? [] : form.fields %} +
+
+

{{ new ? 'Neues Formular' : form.name }}{% if not new %} {{ form.fields|length }} Felder{% endif %}

+ {% if not new %}Block-Adresse {{ form.slug }}{% endif %}
- {% endif %} -

Kant CMS · Visueller Editor, erster Prototyp

-
+
+ + +
+
+ + +
+ {% if new %} +
+ + +

Wird im Formularblock eingetragen.

+
+ {% endif %} +
+ + +
+
+ + +
+
+ + +

Technischer Name eines E-Mail-Feldes. Dann lässt sich direkt antworten.

+
+
+ + +
+
+ + + + + + {% for row in rows|merge([{name: '', type: 'text', label: '', required: false, help: '', options: []}, {name: '', type: 'text', label: '', required: false, help: '', options: []}]) %} + {% set n = loop.index0 %} + + + + + + + + + {% endfor %} + +
Felder des Formulars. Eine Beschriftung zu leeren entfernt das Feld.
Technischer NameTypBeschriftungPflichtHilfetextOptionen
+ +
+

Ein Einwilligungsfeld ist immer ein Pflichtfeld; der Zeitpunkt der Einwilligung wird mitgespeichert. Optionen einer Auswahl durch Komma trennen. Die letzten beiden Zeilen sind für neue Felder.

+ +
+ +{% endfor %} + +

Einsendungen {{ submissions|length }}

Die neuesten 50.
+{% if submissions is empty %} +

Noch keine Einsendungen.

Hier erscheinen eingegangene Nachrichten, auch wenn der E-Mail-Versand fehlschlägt.

+{% else %} +
+ {% for entry in submissions %} +
+
+

{{ entry.form }}

+

{{ entry.createdAt|date('d.m.Y H:i') }} UTC{% if entry.consentAt %} · Einwilligung {{ entry.consentAt|date('d.m.Y H:i') }}{% endif %}

+
{% for key, value in entry.data %}
{{ key }}
{{ value }}
{% endfor %}
+
+ {{ entry.delivered ? 'Versendet' : 'Nicht versendet' }} +
+ {% endfor %} +
+{% endif %} +

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/media.html.twig b/templates/admin/media.html.twig index ffd2f62..d69b4eb 100644 --- a/templates/admin/media.html.twig +++ b/templates/admin/media.html.twig @@ -1,4 +1,16 @@ -{% extends 'admin/base.html.twig' %} -{% block title %}Medienverwaltung{% endblock %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'media' %} +{% block title %}Mediathek{% endblock %} +{% block styles %}{% endblock %} {% block scripts %}{% endblock %} -{% block body %}
← Seitenübersicht

DEINE BILDER

Medienverwaltung

Bilder hochladen, Angaben pflegen und in deinen Seiten wiederverwenden.

{% endblock %} +{% block heading %}Mediathek{% endblock %} +{% block lead %}Bilder hochladen, Angaben pflegen und in deinen Seiten wiederverwenden. Dieselbe Mediathek öffnet sich auch im Editor, wenn du ein Bild auswählst.{% endblock %} +{% block actions %}{% endblock %} +{% block content %} +
+

Bilder verwalten

Hochladen, benennen, Alternativtext pflegen
+
+

Beim Hochladen wird jedes Bild in WebP umgewandelt und in mehreren Größen abgelegt; die Seite liefert dann die passende aus. Ein Alternativtext beschreibt, was zu sehen ist – er ist das, was Menschen mit Screenreader vom Bild haben.

+
+
+{% endblock %} diff --git a/templates/admin/menus.html.twig b/templates/admin/menus.html.twig index 38af060..2de6116 100644 --- a/templates/admin/menus.html.twig +++ b/templates/admin/menus.html.twig @@ -1,67 +1,56 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'menus' %} {% block title %}Menüs{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

NAVIGATION

Menüs

-

Ein Eintrag verweist auf eine Seite, auf ein Sprungziel innerhalb einer Seite oder auf eine externe Adresse. Die Reihenfolge ergibt sich aus der Position, die Verschachtelung aus der Ebene.

- +{% block heading %}Menüs{% endblock %} +{% block lead %}Ein Eintrag verweist auf eine Seite, auf ein Sprungziel innerhalb einer Seite oder auf eine externe Adresse. Die Reihenfolge ergibt sich aus der Position, die Verschachtelung aus der Ebene.{% endblock %} +{% block content %} +{% for slot, slotName in slots %} +{% set menu = menus[slot]|default(null) %} +
+

{{ slotName }} {{ rows[slot]|length }}

Platz {{ slot }}
+ - {% endfor %} -

Kant CMS · Visueller Editor, erster Prototyp

-
+ + + + + {% for index, row in rows[slot]|merge([{label: '', type: 'page', level: 1, pageId: '', anchor: '', url: ''}, {label: '', type: 'page', level: 1, pageId: '', anchor: '', url: ''}, {label: '', type: 'page', level: 1, pageId: '', anchor: '', url: ''}]) %} + {% set n = loop.index0 %} + + + + + + + + + + {% endfor %} + + +

Ein Sprungziel gilt zusammen mit der gewählten Seite und führt zu /seite#sprungziel. Als Adresse sind HTTPS-Adressen, lokale Pfade sowie mailto: und tel: erlaubt. Die letzten drei Zeilen sind für neue Einträge; beim Speichern werden sie neu durchnummeriert.

+ + + +{% endfor %} +

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/pages.html.twig b/templates/admin/pages.html.twig index ff67724..ecbd5a6 100644 --- a/templates/admin/pages.html.twig +++ b/templates/admin/pages.html.twig @@ -1,185 +1,232 @@ -{% extends 'admin/base.html.twig' %} -{% block title %}Deine Seiten{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

DEINE WEBSITE

Gute Inhalte beginnen hier.

-

Wähle eine Seite. Gestalte deinen Entwurf. Veröffentliche, wenn alles passt.

- +{% extends 'admin/shell.html.twig' %} +{% set nav = 'pages' %} +{% block title %}Seiten{% endblock %} +{% block heading %}Seiten und Beiträge{% endblock %} +{% block lead %}Wähle eine Seite, gestalte deinen Entwurf, veröffentliche, wenn alles passt. Entwürfe bleiben bis dahin privat.{% endblock %} +{% block content %} - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} - - {% if is_granted('ROLE_EDITOR') %} -
- Neue Seite oder neuen Beitrag anlegen -
- -
-
- - -

Wird als Überschrift und als Seitentitel übernommen.

-
-
- - -

Kleinbuchstaben, Ziffern und Bindestriche, zum Beispiel ueber-uns. Leer lassen, um sie aus dem Titel zu bilden.

-
-
- - -

Beiträge sind für Blogartikel gedacht und haben keine Unterseiten.

-
-
- - -

Bestimmt die Adresse, zum Beispiel /leistungen/webdesign.

-
+{% if is_granted('ROLE_EDITOR') %} +
+ Neue Seite oder neuen Beitrag anlegen + + +
+
+ + +

Wird als Überschrift und als Seitentitel übernommen.

- - -
- {% endif %} +
+ + +

Kleinbuchstaben, Ziffern und Bindestriche, zum Beispiel ueber-uns. Leer lassen, um sie aus dem Titel zu bilden.

+
+
+ + +

Beiträge sind für Blogartikel gedacht und haben keine Unterseiten.

+
+
+ + +

Bestimmt die Adresse, zum Beispiel /leistungen/webdesign.

+
+
+ + +
+{% endif %} -

Seiten {{ pages|length }}

Entwürfe bleiben bis zur Veröffentlichung privat.
-
- {% for page in pages %} - {# A class, not a style attribute: the administration forbids inline styles, so an attribute - here would simply be dropped and the hierarchy would look flat. #} -
- -
+

Alle Seiten {{ pages|length }}

Untergeordnete Seiten stehen eingerückt.
+
+{% for page in pages %} + {# Indentation by class, not by style attribute: the administration forbids inline styles, so + an attribute here would simply be dropped and the tree would look flat. #} +
+
+ +

{{ page.title }}

-

/{{ page.path == 'home' ? '' : page.path }} · {{ page.locale|upper }} · {{ page.type == 'post' ? 'Beitrag' : 'Seite' }}{% if page.publish_at %} · geplant für {{ page.publish_at|date('d.m.Y H:i') }} UTC{% endif %}

+

/{{ page.path == 'home' ? '' : page.path }} · {{ page.type == 'post' ? 'Beitrag' : 'Seite' }} · {{ page.locale|upper }}{% if page.publish_at %} · geplant für {{ page.publish_at|date('d.m.Y H:i') }} UTC{% endif %}

- {{ page.published_revision_id is null ? 'Nicht veröffentlicht' : (page.published_revision_id == page.draft_revision_id ? 'Veröffentlicht' : 'Entwurf geändert') }} - Bearbeiten - {% if is_granted('ROLE_EDITOR') %} -
- {{ page.type == 'post' ? 'Beitragsdaten' : 'Seitendaten' }} -
- - - - -

Nicht indexierte Seiten erscheinen auch nicht in der Sitemap.

- - - {% if page.type == 'post' %} - - - - - - -

Erscheint in der Übersicht und im Feed. Leer lassen, um die Seitenbeschreibung zu verwenden.

- - - - -

Mehrere durch Komma trennen. Unbekannte Namen werden angelegt.

- - - {% endif %} - -
-
+ {% if page.published_revision_id is null %} + Nicht veröffentlicht + {% elseif page.published_revision_id == page.draft_revision_id %} + Veröffentlicht + {% else %} + Entwurf geändert {% endif %} - {% if is_granted('ROLE_EDITOR') and page.path != 'home' %} -
- Adresse -
- - - - - - -

Unterseiten ziehen mit. Von jeder alten Adresse wird dauerhaft weitergeleitet.

- -
-
- {% endif %} - {% if is_granted('ROLE_PUBLISHER') %} -
- {{ page.publish_at ? 'Geplant' : 'Planen' }} -
- - - - -

Zeiten in UTC. Leer lassen, um die Planung zu entfernen. Die Veröffentlichung erfolgt, sobald cms:publish-due läuft.

- -
-
- {% endif %} - Fassungen - {% if is_granted('ROLE_EDITOR') and page.path != 'home' %} -
- Papierkorb -
- - -

Legt „{{ page.title }}“ samt Unterseiten in den Papierkorb. Öffentlich nicht mehr erreichbar, aber jederzeit wiederherstellbar.

- -
-
- {% endif %} - {% if is_granted('ROLE_ADMIN') %} -
- Endgültig -
- - -

Löscht „{{ page.title }}“ mit allen gespeicherten Fassungen, ohne Papierkorb. Das lässt sich nicht rückgängig machen.

- -
-
- {% endif %} -
- {% else %} -

Noch keine Seiten vorhanden.

Lege oben deine erste Seite an.

- {% endfor %} -
- {% if trash is not empty %} -

Papierkorb {{ trash|length }}

Nicht öffentlich erreichbar, alle Fassungen erhalten.
-
- {% for entry in trash %} -
- -
-

/{{ entry.path }}

-

Im Papierkorb seit UTC

+ - {% if is_granted('ROLE_EDITOR') %} +
+ + {% if is_granted('ROLE_EDITOR') or is_granted('ROLE_PUBLISHER') %} + {# One disclosure per row instead of six popovers next to each other: everything that is + not the daily „bearbeiten“ lives here and opens in place. #} +
+ Einstellungen +
+ {% if is_granted('ROLE_EDITOR') %} +
+

{{ page.type == 'post' ? 'Beitragsdaten' : 'Seitendaten' }}

+
+ + +
+
+ + +

Nicht indexierte Seiten erscheinen auch nicht in der Sitemap.

+
+
+ + +
+ {% if page.type == 'post' %} +
+ + +
+
+ + +
+
+ + +
+
+ + +

Mehrere durch Komma trennen. Unbekannte Namen werden angelegt.

+
+
+ + +
+
+ + +

Erscheint in der Übersicht und im Feed. Leer lassen, um die Seitenbeschreibung zu verwenden.

+
+ {% endif %} +
+ +
+
+ {% endif %} + + {% if is_granted('ROLE_EDITOR') and page.path != 'home' %} +
+

Adresse

+
+ + +
+
+ + +
+
+ + +

Unterseiten ziehen mit. Von jeder alten Adresse wird dauerhaft weitergeleitet.

+
+
+ +
+
+ {% endif %} + + {% if is_granted('ROLE_PUBLISHER') %} +
+

Veröffentlichung planen

+
+ + +
+ + +

Zeiten in UTC. Leer lassen, um die Planung zu entfernen. Die Veröffentlichung erfolgt, sobald cms:publish-due läuft.

+
+ +
+
+ {% endif %} + + {% if (is_granted('ROLE_EDITOR') and page.path != 'home') or is_granted('ROLE_ADMIN') %} +
+

Entfernen

+
+ {% if is_granted('ROLE_EDITOR') and page.path != 'home' %} +
+ + + +
+ {% endif %} + {% if is_granted('ROLE_ADMIN') %} + {# The class is what the security test looks for: an editor must not even be + offered this. #} +
+
+ + + +
+
+ {% endif %} +
+

Der Papierkorb ist umkehrbar: die Seite samt Unterseiten ist öffentlich nicht mehr erreichbar, alle Fassungen bleiben.{% if is_granted('ROLE_ADMIN') %} Endgültig löschen entfernt „{{ page.title }}“ mit allen Fassungen, ohne Papierkorb, und lässt sich nicht rückgängig machen.{% endif %}

+
+ {% endif %} +
+
+ {% endif %} +
+{% else %} +

Noch keine Seiten vorhanden.

Lege oben deine erste Seite an.

+{% endfor %} +
+ +{% if trash is not empty %} +

Papierkorb {{ trash|length }}

Nicht öffentlich erreichbar, alle Fassungen erhalten.
+
+ {% for entry in trash %} +
+ +
+

/{{ entry.path }}

+

Im Papierkorb seit UTC

+
+ {% if is_granted('ROLE_EDITOR') %} +
- {% endif %} -
- {% endfor %} -
- {% endif %} -

Kant CMS · Visueller Editor, erster Prototyp

-
+ + {% endif %} + + {% endfor %} + +{% endif %} + +

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/partials.html.twig b/templates/admin/partials.html.twig index 4a918a4..77e355f 100644 --- a/templates/admin/partials.html.twig +++ b/templates/admin/partials.html.twig @@ -1,81 +1,74 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'partials' %} {% block title %}Bereiche{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

BEREICHE

Kopf, Fuß und wiederverwendbare Sektionen

-

Alle drei sind Blockdokumente wie eine Seite und im selben Editor frei gestaltbar. Eine verknüpfte Sektion wird nicht kopiert: eine Änderung erreicht jede Seite, die sie einbindet.

- - - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} - - {% if is_granted('ROLE_EDITOR') %} -
- Neuen Bereich anlegen -
- -
-
- - -
-
- - -

Kopf- und Fußbereich gibt es je einmal.

-
-
- - -

Wird im Verknüpfungsblock eingetragen. Leer lassen, um sie aus dem Namen zu bilden.

-
+{% block heading %}Kopf, Fuß und wiederverwendbare Sektionen{% endblock %} +{% block lead %}Alle drei sind Blockdokumente wie eine Seite und im selben Editor frei gestaltbar. Eine verknüpfte Sektion wird nicht kopiert: eine Änderung erreicht jede Seite, die sie einbindet.{% endblock %} +{% block content %} +{% if is_granted('ROLE_EDITOR') %} +
+ Neuen Bereich anlegen + + +
+
+ +
- - -
- {% endif %} - -

Bereiche {{ partials|length }}

Gespeicherte Änderungen sind sofort auf allen Seiten sichtbar.
-
- {% for partial in partials %} -
- -
-

{{ partial.name }}

-

{{ kinds[partial.kind] }}{% if partial.kind == 'section' %} · Adresse {{ partial.slug }}{% endif %} - {% if partial.kind == 'section' %} - · {% if partial.usedBy is empty %}nirgends eingebunden{% else %}eingebunden in {{ partial.usedBy|join(', ') }}{% endif %} - {% endif %} -

+
+ + +

Kopf- und Fußbereich gibt es je einmal.

- Bearbeiten - {% if partial.kind == 'section' and is_granted('ROLE_EDITOR') %} -
- Entfernen -
- -

Entfernt „{{ partial.name }}“.{% if partial.usedBy is not empty %} Noch eingebunden in {{ partial.usedBy|join(', ') }} – dort erscheint danach nichts mehr.{% endif %}

- -
-
+
+ + +

Wird im Verknüpfungsblock eingetragen. Leer lassen, um sie aus dem Namen zu bilden.

+
+
+ + +
+{% endif %} + +

Bereiche {{ partials|length }}

Gespeicherte Änderungen sind sofort auf allen Seiten sichtbar.
+
+{% for partial in partials %} +
+
+ +
+

{{ partial.name }}

+

{{ kinds[partial.kind] }}{% if partial.kind == 'section' %} · Adresse {{ partial.slug }}{% endif %} + {% if partial.kind == 'section' %} + · {% if partial.usedBy is empty %}nirgends eingebunden{% else %}eingebunden in {{ partial.usedBy|join(', ') }}{% endif %} {% endif %} -

- {% else %} -

Noch keine Bereiche.

Ein frisches Setup legt Kopf- und Fußbereich automatisch an.

- {% endfor %} -
-

Kant CMS · Visueller Editor, erster Prototyp

-
+

+ +
+ Bearbeiten +
+ + {% if partial.kind == 'section' and is_granted('ROLE_EDITOR') %} +
+ Einstellungen +
+
+

Entfernen

+

Entfernt „{{ partial.name }}“.{% if partial.usedBy is not empty %} Noch eingebunden in {{ partial.usedBy|join(', ') }} – dort erscheint danach nichts mehr.{% endif %}

+
+ + +
+
+
+
+ {% endif %} + +{% else %} +

Noch keine Bereiche.

Ein frisches Setup legt Kopf- und Fußbereich automatisch an.

+{% endfor %} + +

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/revisions.html.twig b/templates/admin/revisions.html.twig index 2e16441..bbf0f05 100644 --- a/templates/admin/revisions.html.twig +++ b/templates/admin/revisions.html.twig @@ -1,45 +1,32 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'pages' %} {% block title %}Fassungen von {{ title }}{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

FASSUNGEN

{{ title }}

-

Jedes Speichern erzeugt eine eigene Fassung. Eine frühere Fassung wiederherzustellen überschreibt nichts: sie wird als neuer Entwurf gespeichert, der bisherige Stand bleibt in der Liste.

- - - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} - -

Verlauf {{ revisions|length }}

Neueste zuerst, höchstens 30.
-
- {% for revision in revisions %} -
- -
-

{{ revision.title }}

-

UTC

-
- {% if revision.published %}Veröffentlicht{% endif %} - {% if revision.draft %}Aktueller Entwurf{% endif %} - {% if not revision.draft %} -
- - - -
- {% endif %} -
- {% else %} -

Noch keine Fassungen.

- {% endfor %} -
-

Kant CMS · Visueller Editor, erster Prototyp

-
+{% block heading %}{{ title }}{% endblock %} +{% block lead %}Jedes Speichern erzeugt eine eigene Fassung. Eine frühere Fassung wiederherzustellen überschreibt nichts: sie wird als neuer Entwurf gespeichert, der bisherige Stand bleibt in der Liste.{% endblock %} +{% block actions %}Seite bearbeiten {% endblock %} +{% block content %} +

Verlauf {{ revisions|length }}

Neueste zuerst, höchstens 30.
+
+{% for revision in revisions %} +
+ +
+

{{ revision.title }}

+

UTC

+
+ {% if revision.published %}Veröffentlicht{% endif %} + {% if revision.draft %}Aktueller Entwurf{% endif %} + {% if not revision.draft %} +
+ + + +
+ {% endif %} +
+{% else %} +

Noch keine Fassungen.

+{% endfor %} +
+

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/settings.html.twig b/templates/admin/settings.html.twig index d86c50f..1e8c53d 100644 --- a/templates/admin/settings.html.twig +++ b/templates/admin/settings.html.twig @@ -1,129 +1,112 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'settings' %} {% block title %}Einstellungen{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

EINSTELLUNGEN

Unternehmen und Adressen

-

Aus diesen Angaben entstehen die strukturierten Daten der Startseite. Was hier steht, ist genau das, was Suchmaschinen lesen – es gibt keine zweite, unsichtbare Fassung.

- - - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} - - -

Kant CMS · Visueller Editor, erster Prototyp

-
+
+

Weiterleitungen {{ redirects|length }}

Werden vor dem 404 geprüft.
+
+ +
+
+ + +
+
+ + +

Bei 410 leer lassen.

+
+
+ + +
+
+ +
+ + {% if redirects is not empty %} + + + + + {% for entry in redirects %} + + + + + + + {% endfor %} + +
Gespeicherte Weiterleitungen
Alte AdresseZielArtEntfernen
/{{ entry.from }}{% if entry.to %}/{{ entry.to }}{% else %}—{% endif %}{{ entry.status }} +
+ + + +
+
+ {% endif %} +
+

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/templates/admin/shell.html.twig b/templates/admin/shell.html.twig new file mode 100644 index 0000000..1bec3b2 --- /dev/null +++ b/templates/admin/shell.html.twig @@ -0,0 +1,53 @@ +{# The frame around every management screen. One sidebar, always the same, so each area is one + click away and the current one is visible. Screens fill in heading, lead, actions and content. #} +{% extends 'admin/base.html.twig' %} +{% block body_class %}admin-shell{% endblock %} +{% block body %} +
+ kantcms + +
+

{{ app.user.userIdentifier }}

+
+ + +
+
+
+
+
+
+

{% block heading %}Verwaltung{% endblock %}

+

{% block lead %}{% endblock %}

+
+
{% block actions %}{% endblock %}
+
+
+ {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} +

{{ message }}

+ {% endfor %}{% endfor %} + {% block content %}{% endblock %} +
+
+{% endblock %} diff --git a/templates/admin/theme.html.twig b/templates/admin/theme.html.twig index 53bd58c..7cc3a18 100644 --- a/templates/admin/theme.html.twig +++ b/templates/admin/theme.html.twig @@ -1,125 +1,109 @@ -{% extends 'admin/base.html.twig' %} +{% extends 'admin/shell.html.twig' %} +{% set nav = 'theme' %} {% block title %}Design{% endblock %} -{% block body %} -
- kantcms -
Website ansehen ↗{{ app.user.userIdentifier }}
-
-
-

DESIGN

Farben, Schriften, Grundtypografie

-

Das ist die Grundlage für die ganze Website. Jeder Block erbt von hier und kann jeden Wert einzeln überschreiben – je Gerät und beim Überfahren.

- +{% block heading %}Farben, Schriften, Grundtypografie{% endblock %} +{% block lead %}Das ist die Grundlage für die ganze Website. Jeder Block erbt von hier und kann jeden Wert einzeln überschreiben – je Gerät und beim Überfahren.{% endblock %} +{% block content %} +
+ - {% for type in ['error', 'success'] %}{% for message in app.flashes(type) %} -

{{ message }}

- {% endfor %}{% endfor %} +

Farbpalette {{ theme.colors|length }}

Beliebig viele eigene Farben
+ + + + + {% for row in theme.colors|merge([{key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}]) %} + {% set n = loop.index0 %} + + + + + + + {% endfor %} + +
Farben der Website. Einen Schlüssel zu leeren entfernt die Farbe.
SchlüsselNameFarbeWert
{{ row.value }}
+

Der Schlüssel wird im Editor als @schlüssel verwendet und bleibt bei Farbänderungen gleich – eine Farbe ändern wirkt überall, wo sie benutzt wird.

- - +

Schriften {{ theme.fonts|length }}

Systemschrift oder Google Fonts
+ + + + + {% for row in theme.fonts|merge([{key: '', name: '', family: '', source: 'google', fallback: 'sans-serif', weights: [400, 700]}]) %} + {% set n = loop.index0 %} + + + + + + + + + {% endfor %} + +
Schriften der Website. Einen Schlüssel zu leeren entfernt die Schrift.
SchlüsselNameQuelleSchriftnameErsatzSchnitte
+ + + + + {% for weight in weights %} + + {% endfor %} +
+

Bei Google Fonts den Namen genau wie dort schreiben, zum Beispiel Inter oder Playfair Display. Je Schnitt wird eine Datei geladen – nur ankreuzen, was wirklich gebraucht wird. Google Fonts bedeutet eine Anfrage an Google bei jedem Seitenaufruf.

-

Farbpalette {{ theme.colors|length }}

Beliebig viele eigene Farben
- - - - - {% for row in theme.colors|merge([{key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}]) %} - {% set n = loop.index0 %} - - - - - - - {% endfor %} - - -

Der Schlüssel wird im Editor als @schlüssel verwendet und bleibt bei Farbänderungen gleich – eine Farbe ändern wirkt überall, wo sie benutzt wird.

- -

Schriften {{ theme.fonts|length }}

Systemschrift oder Google Fonts
- - - - - {% for row in theme.fonts|merge([{key: '', name: '', family: '', source: 'google', fallback: 'sans-serif', weights: [400, 700]}]) %} - {% set n = loop.index0 %} - - - - - - - - - {% endfor %} - - -

Bei Google Fonts den Namen genau wie dort schreiben, zum Beispiel Inter oder Playfair Display. Je Schnitt wird eine Datei geladen – nur ankreuzen, was wirklich gebraucht wird. Google Fonts bedeutet eine Anfrage an Google bei jedem Seitenaufruf.

- -

Grundtypografie

Gilt überall, solange ein Block nichts anderes sagt
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -

1,25 ergibt eine ruhige Staffelung, 1,5 eine sehr kontrastreiche.

-
-
- - -
-
- - -
-
- - -
-
- - -
+

Grundtypografie

Gilt überall, solange ein Block nichts anderes sagt
+
+
+ +
-

Farben entweder als @schlüssel aus der Palette oder als Wert wie #1a2b3c.

- - -

Kant CMS · Visueller Editor, erster Prototyp

-
+
+ + +
+
+ + +
+
+ + +
+
+ + +

1,25 ergibt eine ruhige Staffelung, 1,5 eine sehr kontrastreiche.

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Farben entweder als @schlüssel aus der Palette oder als Wert wie #1a2b3c.

+ + +

Kant CMS · Arbeitsstand

{% endblock %} diff --git a/tests/EditorSecurityTest.php b/tests/EditorSecurityTest.php index def86f8..a682dc2 100644 --- a/tests/EditorSecurityTest.php +++ b/tests/EditorSecurityTest.php @@ -398,7 +398,7 @@ final class EditorSecurityTest extends WebTestCase $client = $this->login('editor'); $crawler = $client->request('GET', '/admin/forms'); self::assertResponseIsSuccessful(); - $token = $crawler->filter('.menu-form input[name="_csrf_token"]')->first()->attr('value'); + $token = $crawler->filter('.form-def input[name="_csrf_token"]')->first()->attr('value'); $field = ['fields' => [['name' => 'email', 'type' => 'email', 'label' => 'E-Mail', 'required' => '1']]]; $client->request('POST', '/admin/forms/neu', [...$field, '_csrf_token' => 'wrong', 'slug' => 'kontakt', 'name' => 'Kontakt', 'recipient' => 'info@kant-it.de', 'expectedVersion' => 0]);