Files
kantarciandClaude Opus 5 23fdca4edb Move image corners into the design area
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>
2026-09-16 09:18:27 +02:00

256 lines
20 KiB
Twig
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}