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>
133 lines
9.7 KiB
Twig
133 lines
9.7 KiB
Twig
{% extends 'admin/shell.html.twig' %}
|
||
{% set nav = 'theme' %}
|
||
{% block title %}Design{% endblock %}
|
||
{% 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 %}
|
||
<form action="{{ path('admin_theme_save') }}" method="post" class="card card-body theme-form">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('theme') }}">
|
||
|
||
<div class="section-head"><h2>Farbpalette <span class="count">{{ theme.colors|length }}</span></h2><span class="muted">Beliebig viele eigene Farben</span></div>
|
||
<table class="table theme-table">
|
||
<caption class="visually-hidden">Farben der Website. Einen Schlüssel zu leeren entfernt die Farbe.</caption>
|
||
<thead><tr><th scope="col">Schlüssel</th><th scope="col">Name</th><th scope="col">Farbe</th><th scope="col">Wert</th></tr></thead>
|
||
<tbody>
|
||
{% for row in theme.colors|merge([{key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}, {key: '', name: '', value: '#000000'}]) %}
|
||
{% set n = loop.index0 %}
|
||
<tr{% if row.key == '' %} class="table-new"{% endif %}>
|
||
<td><input name="colors[{{ n }}][key]" maxlength="32" value="{{ row.key }}" pattern="[a-z][a-z0-9-]*" aria-label="Schlüssel von Farbe {{ n + 1 }}" placeholder="akzent"></td>
|
||
<td><input name="colors[{{ n }}][name]" maxlength="60" value="{{ row.name }}" aria-label="Name von Farbe {{ n + 1 }}" placeholder="Akzent"></td>
|
||
<td><input type="color" name="colors[{{ n }}][value]" value="{{ row.value }}" aria-label="Farbwähler für Farbe {{ n + 1 }}" class="theme-swatch"></td>
|
||
<td><code>{{ row.value }}</code></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
<p class="field-help">Der Schlüssel wird im Editor als <code>@schlüssel</code> verwendet und bleibt bei Farbänderungen gleich – eine Farbe ändern wirkt überall, wo sie benutzt wird.</p>
|
||
|
||
<div class="section-head"><h2>Schriften <span class="count">{{ theme.fonts|length }}</span></h2><span class="muted">Systemschrift oder Google Fonts</span></div>
|
||
<table class="table theme-table">
|
||
<caption class="visually-hidden">Schriften der Website. Einen Schlüssel zu leeren entfernt die Schrift.</caption>
|
||
<thead><tr><th scope="col">Schlüssel</th><th scope="col">Name</th><th scope="col">Quelle</th><th scope="col">Schriftname</th><th scope="col">Ersatz</th><th scope="col">Schnitte</th></tr></thead>
|
||
<tbody>
|
||
{% for row in theme.fonts|merge([{key: '', name: '', family: '', source: 'google', fallback: 'sans-serif', weights: [400, 700]}]) %}
|
||
{% set n = loop.index0 %}
|
||
<tr{% if row.key == '' %} class="table-new"{% endif %}>
|
||
<td><input name="fonts[{{ n }}][key]" maxlength="40" value="{{ row.key }}" pattern="[a-z][a-z0-9-]*" aria-label="Schlüssel von Schrift {{ n + 1 }}" placeholder="heading"></td>
|
||
<td><input name="fonts[{{ n }}][name]" maxlength="60" value="{{ row.name }}" aria-label="Name von Schrift {{ n + 1 }}" placeholder="Überschriften"></td>
|
||
<td>
|
||
<select name="fonts[{{ n }}][source]" aria-label="Quelle von Schrift {{ n + 1 }}">
|
||
<option value="system"{{ row.source == 'system' ? ' selected' : '' }}>Systemschrift</option>
|
||
<option value="google"{{ row.source == 'google' ? ' selected' : '' }}>Google Fonts</option>
|
||
</select>
|
||
</td>
|
||
<td><input name="fonts[{{ n }}][family]" maxlength="48" value="{{ row.family }}" aria-label="Schriftname von Schrift {{ n + 1 }}" placeholder="Inter"></td>
|
||
<td>
|
||
<select name="fonts[{{ n }}][fallback]" aria-label="Ersatzschrift von Schrift {{ n + 1 }}">
|
||
{% for value, label in fallbacks %}<option value="{{ value }}"{{ row.fallback == value ? ' selected' : '' }}>{{ label }}</option>{% endfor %}
|
||
</select>
|
||
</td>
|
||
<td class="weights-cell">
|
||
{% for weight in weights %}
|
||
<label><input type="checkbox" name="fonts[{{ n }}][weights][]" value="{{ weight }}"{{ weight in row.weights ? ' checked' : '' }}> {{ weight }}</label>
|
||
{% endfor %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
<p class="field-help">Bei Google Fonts den Namen genau wie dort schreiben, zum Beispiel <code>Inter</code> oder <code>Playfair Display</code>. Je Schnitt wird eine Datei geladen – nur ankreuzen, was wirklich gebraucht wird. <strong>Google Fonts bedeutet eine Anfrage an Google bei jedem Seitenaufruf.</strong></p>
|
||
|
||
<div class="section-head"><h2>Grundtypografie</h2><span class="muted">Gilt überall, solange ein Block nichts anderes sagt</span></div>
|
||
<div class="fields">
|
||
<div>
|
||
<label for="base-font">Schrift für Fließtext</label>
|
||
<select id="base-font" name="base[fontFamily]">
|
||
{% for font in theme.fonts %}<option value="{{ font.key }}"{{ theme.base.fontFamily == font.key ? ' selected' : '' }}>{{ font.name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="base-heading-font">Schrift für Überschriften</label>
|
||
<select id="base-heading-font" name="base[headingFontFamily]">
|
||
{% for font in theme.fonts %}<option value="{{ font.key }}"{{ theme.base.headingFontFamily == font.key ? ' selected' : '' }}>{{ font.name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="base-size">Grundgröße</label>
|
||
<input id="base-size" name="base[fontSize]" value="{{ theme.base.fontSize }}" placeholder="18px">
|
||
</div>
|
||
<div>
|
||
<label for="base-line">Zeilenhöhe</label>
|
||
<input id="base-line" name="base[lineHeight]" type="number" step="0.05" min="0.8" max="3" value="{{ theme.base.lineHeight }}">
|
||
</div>
|
||
<div>
|
||
<label for="base-scale">Überschriftenskala</label>
|
||
<input id="base-scale" name="base[headingScale]" type="number" step="0.01" min="1" max="2" value="{{ theme.base.headingScale }}">
|
||
<p class="field-help">1,25 ergibt eine ruhige Staffelung, 1,5 eine sehr kontrastreiche.</p>
|
||
</div>
|
||
<div>
|
||
<label class="check-label" for="base-underline"><input id="base-underline" name="base[linkUnderline]" type="checkbox" value="1"{{ (theme.base.linkUnderline ?? true) ? ' checked' : '' }}> Links im Fließtext unterstreichen</label>
|
||
<p class="field-help">Aus sieht ruhiger aus, aber dann erkennt man einen Link im Text nur an der Farbe – für Menschen mit Farbsehschwäche also gar nicht. Beim Überfahren erscheint der Strich in beiden Fällen. Menüs, Karten und Schaltflächen sind davon nicht betroffen.</p>
|
||
</div>
|
||
<div>
|
||
<label for="base-container">Inhaltsbreite</label>
|
||
<input id="base-container" name="base[containerWidth]" value="{{ theme.base.containerWidth }}" placeholder="1240px">
|
||
<p class="field-help">Die Breite des Inhalts auf allen Seiten. Abschnitte mit der Inhaltsbreite „Breit“ oder „Randlos“ halten sich nicht daran – das ist der Zweck dieser beiden.</p>
|
||
</div>
|
||
<div>
|
||
<label for="base-color">Textfarbe</label>
|
||
<input id="base-color" name="base[color]" value="{{ theme.base.color }}" placeholder="@ink" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="base-bg">Hintergrundfarbe</label>
|
||
<input id="base-bg" name="base[backgroundColor]" value="{{ theme.base.backgroundColor }}" placeholder="@paper" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="base-link">Linkfarbe</label>
|
||
<input id="base-link" name="base[linkColor]" value="{{ theme.base.linkColor }}" placeholder="@accent" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||
</div>
|
||
</div>
|
||
<p class="field-help">Farben entweder als <code>@schlüssel</code> aus der Palette oder als Wert wie <code>#1a2b3c</code>.</p>
|
||
<button class="primary" type="submit">Design speichern</button>
|
||
</form>
|
||
|
||
<div class="section-head"><h2>Schriften ausliefern</h2><span class="muted">Von Google oder vom eigenen Server</span></div>
|
||
{% if fontsMissing is empty and not fontsLocal %}
|
||
<p class="field-help">Dieses Design nutzt nur Systemschriften – es lädt ohnehin nichts von Google.</p>
|
||
{% else %}
|
||
<p class="field-help">
|
||
{% if fontsLocal %}
|
||
<strong>Die Schriften liegen auf diesem Server.</strong> Besucher laden nichts von Google, und die Seite baut sich schneller auf, weil eine Kette aus zwei fremden Verbindungen entfällt.
|
||
{% else %}
|
||
<strong>Die Schriften kommen zurzeit von Google.</strong> Damit erfährt Google die IP-Adresse jedes Besuchers – in Deutschland ein abmahnfähiger Mangel (LG München I, 3 O 17493/20) –, und der Seitenaufbau wartet auf zwei fremde Verbindungen nacheinander. Mit dem Knopf werden die Dateien einmalig hierher geholt; die Lizenz der Schriften erlaubt das ausdrücklich.
|
||
{% endif %}
|
||
{% if fontsMissing %}<br>Noch nicht hier: {{ fontsMissing|join(', ') }}.{% endif %}
|
||
</p>
|
||
<form method="post" action="{{ path('admin_theme_fonts') }}">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('theme') }}">
|
||
<button type="submit">{{ fontsLocal ? 'Schriften erneut holen' : 'Schriften auf diesen Server holen' }}</button>
|
||
</form>
|
||
{% endif %}
|
||
<p class="footnote">KantSites · Arbeitsstand</p>
|
||
{% endblock %}
|