Fassung 1.4.3: Update ohne Git, Verwaltung auf dem Telefon, eigene Symbole
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Vier Wuensche und ein Fund beim Nachmessen.

- Farbwaehler im Designbereich. Das Textfeld bleibt massgeblich, damit
  "@akzent" ein Verweis auf die Palette bleibt und nicht beim ersten
  Klick zu einem festen Hexwert erstarrt (ADR 066).
- Die Verwaltung auf dem Telefon: das Menue klappt unter 760 px zu und
  nennt den Bereich, in dem man steht; Ziele 44 px, kein Skript.
- Eigene SVG-Symbole statt Unicode-Zeichen - ein Telefon ersetzt die
  sonst durch Emoji in fremdem Stil. Zwei Eintraege trugen dabei
  denselben Pfad; ein Test findet beides (ADR 067).
- Update aus dem Releasearchiv: der Weg fuer Installationen ohne Git.
  Zwei Schritte, weil ein Prozess, der sich selbst ausgetauscht hat,
  mit dem alten Code weiterarbeitet. var/, .env, hochgeladene
  Schriften und das erzeugte Buendel bleiben unberuehrt; public/ wird
  dazu Datei fuer Datei zusammengefuehrt. Geprueft ohne Netz gegen
  eine Releaseseite im Speicher (ADR 068).

Beim Nachmessen der echten Startseite statt einer Testseite: das
groesste Element ueber der Falz ist dort kein <img>, sondern der
Hintergrund des ersten Abschnitts - und der steht nur im Stylesheet.
Drei Vorabverweise mit denselben Geraetegrenzen wie der Compiler holen
genau die Variante, die die Regel auch nimmt. LCP 1,3 s -> 1,1 s.
Dabei gefunden: dasselbe Bild wurde je Geraeteregel neu abgefragt -
Startseite 19 -> 15 Abfragen (ADR 069).

Geprueft: 428 Tests auf PHP 8.2 und 8.4, Editor-, Browser- und
Linkpruefung, Lighthouse auf drei echten Seiten (alles ausser der
Leistung der Startseite 100).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 23:01:32 +02:00
1 parent ea3dd4ddf2
commit d6d476177a
26 files changed
+1740 -26

No files matched your search

+35 -1
View File
@@ -144,7 +144,7 @@ label { display: block; margin: 18px 0 7px; font-size: 13px; font-weight: 600 }
.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-icon { width: 18px; height: 18px; color: var(--shell-muted); flex-shrink: 0 }
.nav-out::after { content: '↗'; margin-left: auto; color: var(--shell-muted); font-size: 11px }
.shell-foot { margin-top: auto; padding-top: 20px; border-top: 1px solid #2a3d35; display: grid; gap: 6px }
@@ -654,3 +654,37 @@ details.card > summary:hover { background: #fafbf8 }
.favicon-field{display:flex;align-items:center;gap:12px}
.favicon-field select{flex:1}
.favicon-preview{border-radius:6px;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.12)}
.color-field { display: flex; gap: 8px; align-items: center }
.color-field input[type="text"], .color-field > input:not([type="color"]) { flex: 1; min-width: 0 }
.color-field-swatch { width: 46px; min-height: 38px; padding: 2px; flex: 0 0 auto; cursor: pointer }
.color-field-palette { flex: 0 0 auto; max-width: 40% }
/* Die Verwaltung auf dem Telefon.
Bis 1000px stand die Navigation als waagerechte Wischleiste da - zwei Listen untereinander,
also zwei Zeilen, und der letzte Eintrag ausserhalb des Bildes. Auf einem Telefon ist ein
Klappmenue ehrlicher: eine Zeile im Ruhezustand, aufgeklappt die vollstaendige Liste mit
Flaechen, die ein Daumen trifft. Das details-Element macht das ohne Skript. */
.shell-menu > summary { display: none }
@media (max-width: 760px) {
.shell-nav { flex-wrap: wrap; padding: 10px 14px; gap: 10px; overflow: visible }
.shell-menu { flex: 1 1 100%; order: 3 }
.shell-menu > summary {
display: flex; align-items: center; gap: 10px; list-style: none; cursor: pointer;
min-height: 44px; padding: 0 14px; border-radius: 10px;
background: #ffffff14; color: #fff; font-weight: 600; font-size: 15px;
}
.shell-menu > summary::-webkit-details-marker { display: none }
.shell-menu > summary:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px }
.shell-menu-icon { font-size: 17px }
.shell-menu[open] > summary { margin-bottom: 8px }
.shell-menu nav { display: block }
.shell-menu:not([open]) nav { display: none }
.nav-label { display: block; margin: 12px 4px 6px; font-size: 11px; letter-spacing: 1.4px; color: #9db3a8 }
.nav-list { display: grid; gap: 2px }
/* Vierundvierzig Pixel: die Flaeche, die ein Daumen zuverlaessig trifft. */
.nav-item { min-height: 44px; display: flex; align-items: center; gap: 12px; padding: 10px 14px; white-space: normal }
.shell-foot { margin-left: auto; border-left: 0; padding-left: 0; gap: 10px }
.shell-version { display: none }
.shell-user strong { display: none }
}
+74
View File
@@ -0,0 +1,74 @@
/*
* Farbfelder in den Verwaltungsformularen.
*
* Eine Farbe tippt man nicht, man wählt sie. Im Editor gibt es dafür längst ein Bedienelement aus
* Farbwähler, Textfeld und Palettenliste - in den Formularen unter „Design" und „Einstellungen"
* stand bisher nur ein nacktes Textfeld.
*
* Das Textfeld bleibt die Wahrheit: Es nimmt einen Wert wie `#1a2b3c` **oder** einen Schlüssel aus
* der Palette (`@accent`), und das kann ein Farbwähler nicht. Deshalb wird es nicht ersetzt,
* sondern ergänzt. Ohne JavaScript bleibt das Feld genau das, was es vorher war.
*/
(() => {
const felder = [...document.querySelectorAll('input[data-color]')];
if (0 === felder.length) {
return;
}
for (const feld of felder) {
const palette = JSON.parse(feld.dataset.palette || '[]');
const reihe = document.createElement('div');
reihe.className = 'color-field';
feld.parentNode.insertBefore(reihe, feld);
const waehler = document.createElement('input');
waehler.type = 'color';
waehler.className = 'color-field-swatch';
waehler.setAttribute('aria-label', 'Farbwähler für ' + (feld.labels?.[0]?.textContent.trim() || 'diese Farbe'));
// Der Wähler kennt nur Hexwerte. Steht im Feld ein Palettenschlüssel, zeigt er die Farbe,
// für die der Schlüssel steht - sonst bliebe er auf Schwarz stehen und sähe falsch aus.
const alsHex = wert => {
if (/^#[0-9a-f]{6}$/i.test(wert)) {
return wert;
}
const treffer = palette.find(eintrag => '@' + eintrag.key === wert);
return treffer ? treffer.value : '#000000';
};
waehler.value = alsHex(feld.value || feld.placeholder || '#000000');
waehler.addEventListener('input', () => {
feld.value = waehler.value;
feld.dispatchEvent(new Event('input', { bubbles: true }));
});
reihe.append(waehler, feld);
if (palette.length > 0) {
const auswahl = document.createElement('select');
auswahl.className = 'color-field-palette';
auswahl.setAttribute('aria-label', 'Farbe aus der Palette');
auswahl.append(new Option('Aus der Palette …', ''));
for (const eintrag of palette) {
auswahl.append(new Option(`${eintrag.name} (${eintrag.value})`, '@' + eintrag.key));
}
auswahl.value = palette.some(eintrag => '@' + eintrag.key === feld.value) ? feld.value : '';
auswahl.addEventListener('change', () => {
if ('' === auswahl.value) {
return;
}
feld.value = auswahl.value;
waehler.value = alsHex(auswahl.value);
feld.dispatchEvent(new Event('input', { bubbles: true }));
});
reihe.append(auswahl);
}
// Tippt jemand von Hand, folgt der Wähler mit - solange der Wert einer ist, den er kennt.
feld.addEventListener('input', () => {
const hex = alsHex(feld.value);
if (waehler.value !== hex) {
waehler.value = hex;
}
});
}
})();