Umut on the DEEG hero image: "It should be rounded, but it was done from somewhere, and the corners look odd. And the editor has no image corner rounding at all, does it?" It did have one - per block, under Design > Bild > Bildecken. What it did not have was the 12px that enhancements.css set on .block-image for every installation: rounded on screen, present in no field, and only removable by overriding it on each block. On a template that is square throughout it shows, because the elements above the image - the gradient, the sign, the blue call to action - are square and stick out of the rounded corner. The radius is now theme.base.imageRadius, validated like the container width, emitted as --k-image-radius and used by the stylesheet. The default stays 12px so existing sites look the same; 0px makes every image square. The per-block setting still wins. DEEG is set to 0px, in the seed script and in the installation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
256 lines
20 KiB
Twig
256 lines
20 KiB
Twig
{% extends 'admin/shell.html.twig' %}
|
||
{% set nav = 'theme' %}
|
||
{# Die lokalen Schriftdateien, sofern es sie gibt: dann zeigt die Vorschau jede Google-Schrift so,
|
||
wie die Website sie ausliefert. Von Google selbst wird hier nichts geladen. #}
|
||
{% block styles %}<link rel="stylesheet" href="/assets/buttons.css?v={{ kant_version() }}"><link rel="stylesheet" href="/assets/button-controls.css?v={{ kant_version() }}"><link rel="stylesheet" href="/assets/color-control.css?v={{ kant_version() }}">{% if fontsStylesheet %}<link rel="stylesheet" href="/assets/fonts/fonts.css?v={{ kant_version() }}">{% endif %}{% endblock %}
|
||
{% block scripts %}<script type="module" src="/assets/theme-panel.js?v={{ kant_version() }}"></script><script type="module" src="/assets/theme-buttons.js?v={{ kant_version() }}"></script>{% endblock %}
|
||
{% 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 %}
|
||
|
||
{# Eine Farbe der Palette als Karte: der Farbwähler ist die Fläche selbst. #}
|
||
{% macro color(n, row) %}
|
||
<li class="swatch-card{{ row.key == '' ? ' swatch-card-new' : '' }}" data-color-card>
|
||
<label class="swatch-well" for="color-{{ n }}-value">
|
||
<input type="color" id="color-{{ n }}-value" name="colors[{{ n }}][value]" value="{{ row.value }}" class="swatch-input" aria-label="Farbwert von Farbe {{ n }}" data-color-value>
|
||
<code class="swatch-hex" data-color-hex>{{ row.value }}</code>
|
||
</label>
|
||
<div class="swatch-fields">
|
||
<div>
|
||
<label for="color-{{ n }}-name">Name</label>
|
||
<input id="color-{{ n }}-name" name="colors[{{ n }}][name]" maxlength="60" value="{{ row.name }}" placeholder="Akzent" data-color-name>
|
||
</div>
|
||
<div>
|
||
<label for="color-{{ n }}-key">Schlüssel</label>
|
||
<input id="color-{{ n }}-key" name="colors[{{ n }}][key]" maxlength="32" value="{{ row.key }}" pattern="[a-z][a-z0-9\-]*" placeholder="akzent" data-color-key>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="field-remove" data-remove-color hidden aria-label="Farbe entfernen" title="Farbe entfernen">{% include 'admin/icon.html.twig' with {name: 'remove'} only %}</button>
|
||
</li>
|
||
{% endmacro %}
|
||
|
||
{# Eine Schrift als Karte: oben die Probe in der Schrift selbst, darunter die Einstellungen. #}
|
||
{% macro font(n, row, fallbacks, weights) %}
|
||
<li class="font-card{{ row.key == '' ? ' font-card-new' : '' }}" data-font-card>
|
||
<div class="font-sample" data-font-sample>
|
||
<span class="font-sample-big" aria-hidden="true">Aa</span>
|
||
<span class="font-sample-line">
|
||
<span class="font-sample-text">Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.</span>
|
||
<span class="font-sample-weights" data-font-weights>{% for weight in weights %}<span class="font-weight-{{ weight }}"{{ weight in row.weights ? '' : ' hidden' }}>{{ weight }}</span>{% endfor %}</span>
|
||
</span>
|
||
</div>
|
||
<div class="font-fields">
|
||
<div>
|
||
<label for="font-{{ n }}-name">Name</label>
|
||
<input id="font-{{ n }}-name" name="fonts[{{ n }}][name]" maxlength="60" value="{{ row.name }}" placeholder="Überschriften" data-font-name>
|
||
</div>
|
||
<div>
|
||
<label for="font-{{ n }}-key">Schlüssel</label>
|
||
<input id="font-{{ n }}-key" name="fonts[{{ n }}][key]" maxlength="40" value="{{ row.key }}" pattern="[a-z][a-z0-9\-]*" placeholder="heading" data-font-key>
|
||
</div>
|
||
<div>
|
||
<label for="font-{{ n }}-source">Quelle</label>
|
||
<select id="font-{{ n }}-source" name="fonts[{{ n }}][source]" data-font-source>
|
||
<option value="system"{{ row.source == 'system' ? ' selected' : '' }}>Systemschrift</option>
|
||
<option value="google"{{ row.source == 'google' ? ' selected' : '' }}>Google Fonts</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="font-{{ n }}-family">Schriftname</label>
|
||
<input id="font-{{ n }}-family" name="fonts[{{ n }}][family]" maxlength="48" value="{{ row.family }}" placeholder="Inter" data-font-family>
|
||
</div>
|
||
<div>
|
||
<label for="font-{{ n }}-fallback">Ersatzschrift</label>
|
||
<select id="font-{{ n }}-fallback" name="fonts[{{ n }}][fallback]" data-font-fallback>
|
||
{% for value, label in fallbacks %}<option value="{{ value }}"{{ row.fallback == value ? ' selected' : '' }}>{{ label }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<fieldset class="font-weights">
|
||
<legend>Schnitte</legend>
|
||
{% for weight in weights %}
|
||
<label class="chip"><input type="checkbox" name="fonts[{{ n }}][weights][]" value="{{ weight }}"{{ weight in row.weights ? ' checked' : '' }} data-font-weight> {{ weight }}</label>
|
||
{% endfor %}
|
||
</fieldset>
|
||
</div>
|
||
<button type="button" class="field-remove" data-remove-font hidden aria-label="Schrift entfernen" title="Schrift entfernen">{% include 'admin/icon.html.twig' with {name: 'remove'} only %}</button>
|
||
</li>
|
||
{% endmacro %}
|
||
|
||
{% block content %}
|
||
{% import _self as karte %}
|
||
{% set emptyColor = {key: '', name: '', value: '#4a7c59'} %}
|
||
{% set emptyFont = {key: '', name: '', family: '', source: 'google', fallback: 'sans-serif', weights: [400, 700]} %}
|
||
<form action="{{ path('admin_theme_save') }}" method="post" class="theme-form stack" data-theme-editor data-palettes="{{ palettes|json_encode|e('html_attr') }}" data-color-names="{{ colorNames|json_encode|e('html_attr') }}">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('theme') }}">
|
||
|
||
<section class="card">
|
||
<div class="card-head">
|
||
<h2>Farbpalette <span class="count" data-color-count>{{ theme.colors|length }}</span></h2>
|
||
<span class="muted">Jede Farbe hat einen Schlüssel – im Editor als <code>@schlüssel</code>, und eine Änderung hier wirkt überall</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="palette-presets" role="group" aria-label="Fertige Paletten" hidden data-palette-presets>
|
||
<span class="palette-presets-label">Fertige Palette übernehmen</span>
|
||
<div class="palette-preset-row">
|
||
{% for preset in palettes %}
|
||
<button type="button" class="palette-preset" data-palette-preset="{{ loop.index0 }}" aria-label="Palette {{ preset.name }} übernehmen">
|
||
<svg viewBox="0 0 50 14" aria-hidden="true" focusable="false">{% for key, value in preset.colors %}<rect x="{{ loop.index0 * 10 }}" y="0" width="10" height="14" fill="{{ value }}"></rect>{% endfor %}</svg>
|
||
<span>{{ preset.name }}</span>
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
<p class="field-help" data-palette-status role="status">Ersetzt die fünf Grundfarben; eigene Farben bleiben. Gilt erst nach dem Speichern.</p>
|
||
</div>
|
||
<ul class="swatch-list" data-color-list>
|
||
{% for row in theme.colors|merge([emptyColor, emptyColor]) %}{{ karte.color(loop.index0, row) }}{% endfor %}
|
||
</ul>
|
||
<template data-color-template>{{ karte.color('__n__', emptyColor) }}</template>
|
||
<div class="list-actions">
|
||
<button type="button" class="secondary" data-add-color hidden>{% include 'admin/icon.html.twig' with {name: 'plus'} only %}Farbe hinzufügen</button>
|
||
<p class="field-help">Eine Farbe ohne Schlüssel wird nicht gespeichert – so lässt sich eine Farbe auch ohne Skript entfernen. Die beiden leeren Karten sind für neue Farben.</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="card-head">
|
||
<h2>Schriften <span class="count" data-font-count>{{ theme.fonts|length }}</span></h2>
|
||
<span class="muted">Systemschrift oder Google Fonts – ausgeliefert vom eigenen Server</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="font-list" data-font-list>
|
||
{% for row in theme.fonts|merge([emptyFont]) %}{{ karte.font(loop.index0, row, fallbacks, weights) }}{% endfor %}
|
||
</ul>
|
||
<template data-font-template>{{ karte.font('__n__', emptyFont, fallbacks, weights) }}</template>
|
||
<div class="list-actions">
|
||
<button type="button" class="secondary" data-add-font hidden>{% include 'admin/icon.html.twig' with {name: 'plus'} only %}Schrift hinzufügen</button>
|
||
<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. {% if fontsStylesheet %}Die Probe nutzt die Schriftdateien auf diesem Server; eine neue Schrift erscheint darin erst, wenn sie unten geholt wurde.{% else %}Die Probe zeigt eine Google-Schrift erst, wenn sie unten auf diesen Server geholt wurde.{% endif %}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="card-head">
|
||
<h2>Grundtypografie</h2>
|
||
<span class="muted">Gilt überall, solange ein Block nichts anderes sagt</span>
|
||
</div>
|
||
<div class="card-body type-layout">
|
||
<div class="type-fields">
|
||
<div class="fields">
|
||
<div>
|
||
<label for="base-font">Schrift für Fließtext</label>
|
||
<select id="base-font" name="base[fontFamily]" data-font-select>
|
||
{% 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]" data-font-select>
|
||
{% 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 for="base-image-radius">Bildecken</label>
|
||
<input id="base-image-radius" name="base[imageRadius]" value="{{ theme.base.imageRadius }}" placeholder="12px">
|
||
<p class="field-help">Gilt für jedes Bild auf der Website – auch in Galerien, Logoleisten und Beitragslisten. <code>0px</code> macht sie eckig. Ein einzelnes Bild lässt sich im Editor unter <strong>Design → Bild → Bildecken</strong> abweichend einstellen.</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">Abschnitte mit der Inhaltsbreite „Breit“ oder „Randlos“ halten sich nicht daran – das ist der Zweck dieser beiden.</p>
|
||
</div>
|
||
</div>
|
||
<div class="fields type-colors">
|
||
<div>
|
||
<label for="base-color">Textfarbe</label>
|
||
<input id="base-color" name="base[color]" value="{{ theme.base.color }}" placeholder="@ink" data-base-color>
|
||
</div>
|
||
<div>
|
||
<label for="base-bg">Seitenfarbe</label>
|
||
<input id="base-bg" name="base[backgroundColor]" value="{{ theme.base.backgroundColor }}" placeholder="@paper" data-base-color>
|
||
</div>
|
||
<div>
|
||
<label for="base-link">Linkfarbe</label>
|
||
<input id="base-link" name="base[linkColor]" value="{{ theme.base.linkColor }}" placeholder="@accent" data-base-color>
|
||
</div>
|
||
</div>
|
||
<p class="field-help">Aus der Palette wählen oder eine eigene Farbe eintragen. Eine Palettenfarbe folgt späteren Änderungen an der Palette, eine eigene bleibt, wie sie ist.</p>
|
||
<div class="field-wide">
|
||
<label class="check-label" for="base-underline"><input id="base-underline" name="base[linkUnderline]" type="checkbox" value="1"{{ (theme.base.linkUnderline ?? true) ? ' checked' : '' }} data-base-underline> 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>
|
||
{# Die Probe: so liest sich die Website mit diesen Werten. Ohne Skript in der Schrift
|
||
der Verwaltung, mit Skript in Schrift, Größe, Zeilenhöhe und Farben von hier. #}
|
||
<aside class="type-preview-column" aria-label="Vorschau der Grundtypografie">
|
||
<h3 class="form-part">Vorschau</h3>
|
||
<div class="type-preview" data-type-preview>
|
||
<p class="type-preview-h1" data-type-h1>Überschrift der Seite</p>
|
||
<p class="type-preview-h2" data-type-h2>Ein Abschnitt darunter</p>
|
||
<p class="type-preview-p" data-type-p>Fließtext in der Grundgröße, mit <span class="type-preview-link" data-type-link>einem Link im Text</span> und genug Zeilen, um die Zeilenhöhe zu sehen. Gute Typografie fällt nicht auf – sie liest sich einfach leicht.</p>
|
||
</div>
|
||
<p class="field-help">Überschriften folgen der Skala: die erste Ebene ist vier Stufen über der Grundgröße, die zweite drei.</p>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
|
||
<fieldset class="button-defaults" id="button-defaults" data-schema="{{ {colors: theme.colors, fonts: theme.fonts, buttonPresets: buttonPresets}|json_encode|e('html_attr') }}">
|
||
<legend>Standardbutton</legend>
|
||
<p class="field-help">Die Grundlage für alle Button-Elemente. Eigene Varianten und Gestaltung im Editor haben Vorrang.</p>
|
||
<div class="button-defaults-layout">
|
||
<div class="fields">
|
||
{% for key, options in buttonOptions %}<div><label for="buttons-{{ key }}">{{ {shape: 'Form', appearance: 'Darstellung', size: 'Button-Größe'}[key] }}</label><select id="buttons-{{ key }}" name="buttons[{{ key }}]">{% for value, label in options %}<option value="{{ value }}"{{ theme.buttons[key] == value ? ' selected' : '' }}>{{ label }}</option>{% endfor %}</select></div>{% endfor %}
|
||
<div><label for="buttons-fontFamily">Button-Schrift</label><select id="buttons-fontFamily" name="buttons[fontFamily]" data-font-select><option value="inherit">Schrift der Umgebung übernehmen</option>{% for font in theme.fonts %}<option value="{{ font.key }}"{{ theme.buttons.fontFamily == font.key ? ' selected' : '' }}>{{ font.name }}</option>{% endfor %}</select></div>
|
||
<div><label for="buttons-fontSize">Button-Schriftgröße (px)</label><input id="buttons-fontSize" type="number" min="10" max="72" step="0.5" name="buttons[fontSize]" value="{{ theme.buttons.fontSize }}"></div>
|
||
<div><label for="buttons-fontWeight">Button-Schriftstärke</label><select id="buttons-fontWeight" name="buttons[fontWeight]">{% for weight in weights %}<option value="{{ weight }}"{{ theme.buttons.fontWeight == weight ? ' selected' : '' }}>{{ weight }}</option>{% endfor %}</select></div>
|
||
<div><label for="buttons-letterSpacing">Buchstabenabstand (px)</label><input id="buttons-letterSpacing" type="number" min="-1" max="8" step="0.1" name="buttons[letterSpacing]" value="{{ theme.buttons.letterSpacing }}"></div>
|
||
<div><label for="buttons-textTransform">Schreibweise</label><select id="buttons-textTransform" name="buttons[textTransform]"><option value="none"{{ theme.buttons.textTransform == 'none' ? ' selected' : '' }}>Wie eingegeben</option><option value="uppercase"{{ theme.buttons.textTransform == 'uppercase' ? ' selected' : '' }}>GROSSBUCHSTABEN</option></select></div>
|
||
{% for key, label in {backgroundColor: 'Button-Farbe', color: 'Button-Textfarbe', hoverBackgroundColor: 'Button-Farbe beim Überfahren', hoverColor: 'Textfarbe beim Überfahren'} %}<div><label for="buttons-{{ key }}">{{ label }}</label><input id="buttons-{{ key }}" name="buttons[{{ key }}]" value="{{ theme.buttons[key] }}" data-button-color></div>{% endfor %}
|
||
</div>
|
||
<div class="button-preview-column"><h3>Live-Vorschau</h3><div class="button-state-previews" aria-label="Standardbutton-Vorschau"></div><div class="button-preset-grid" role="group" aria-label="Standardbutton-Vorlagen">{% for preset in buttonPresets %}<button type="button" class="button-preset" data-button-preset aria-label="Standardbutton {{ preset.name }}"><span class="button-preset-label">{{ preset.name }}</span></button>{% endfor %}</div></div>
|
||
</div>
|
||
</fieldset>
|
||
<div class="theme-save">
|
||
<button class="primary" type="submit">Design speichern</button>
|
||
<span class="muted">Gilt sofort auf allen Seiten.</span>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="card">
|
||
<div class="card-head">
|
||
<h2>Schriften ausliefern</h2>
|
||
<span class="muted">{{ fontsMissing ? fontsMissing|length ~ ' fehlt noch' : 'immer vom eigenen Server' }}</span>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if fontsMissing is empty and not fontsLocal %}
|
||
<p class="muted">Dieses Design nutzt nur Systemschriften – es lädt ohnehin nichts von Google.</p>
|
||
{% else %}
|
||
{% if fontsLocal %}
|
||
<p class="muted"><strong>Die Schriften liegen auf diesem Server.</strong> Besucher laden nichts von Google, und die Seite baut sich schneller auf.</p>
|
||
{% else %}
|
||
<p class="muted"><strong>Die Schriftdateien fehlen auf diesem Server.</strong> Bis sie hier liegen, zeigt die Website die eingestellte Ersatzschrift – von Google wird nichts geladen, weder hier noch beim Besucher.</p>
|
||
{% endif %}
|
||
{% if fontsMissing %}<p class="field-help">Noch nicht hier: {{ fontsMissing|join(', ') }}.</p>{% endif %}
|
||
<form method="post" action="{{ path('admin_theme_fonts') }}">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('theme') }}">
|
||
<button class="{{ fontsLocal ? 'secondary' : 'primary' }}" type="submit">{{ fontsLocal ? 'Schriften erneut holen' : 'Schriften auf diesen Server holen' }}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<p class="footnote">KantSites · Arbeitsstand</p>
|
||
{% endblock %}
|