Eigener Suchtitel, Schriften lokal, Bilder in Anzeigegroesse
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

Getrennter Titel fuer Suchergebnisse je Seite und Beitrag (Migration
20260913000100): auf der Seite steht "Leistungen", im Suchergebnis darf
der lange Titel stehen. Leer heisst weiterhin: der Titel der Seite.

Schriften kommen jetzt vom eigenen Server. Ein Knopf im Designbereich
oder cms:fonts:fetch holt die gewaehlten Google-Schnitte als WOFF2 nach
public/assets/fonts. Das spart zwei fremde Verbindungen nacheinander
(gemessen 820 ms) und die IP-Weitergabe an Google. Ohne Dateien bleibt
es beim bisherigen Verweis - eine Seite ohne Schrift waere schlimmer.

Ein Stylesheet statt vier, mit Pruefsumme im Namen und ohne Kommentare,
dazu eine .htaccess mit Kompression, Cache und Schutz interner Dateien.
Der Entwicklungsserver sendet dieselben Kopfzeilen, sonst misst man
lokal etwas, das es im Betrieb nicht gibt.

Bilder in der Groesse, in der sie erscheinen: ein nur fuer Desktop
gesetztes Hintergrundbild gilt auf dem Telefon in Telefongroesse
(189 KB -> 40 KB), img nennt seine feste Breite oder sizes="auto", und
fetchpriority liegt auf dem ersten Bild des Inhalts statt auf dem Logo.

Neu: npm run test:links prueft jeden internen Verweis auf seine
Antwort. Lighthouse gegen den Nachbau: Barrierefreiheit, Best
Practices und SEO 100 auf allen vier geprueften Adressen, Performance
100 ausser auf der bilderreichen Startseite (98 mobil, 96 Desktop).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 15:11:46 +02:00
1 parent 35d8270afd
commit 074ef0e7e5
34 files changed
+1223 -45

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="robots" content="noindex, nofollow">
<title>{% block title %}Verwaltung{% endblock %} · KantSites</title>
{% include 'favicon.html.twig' %}
<link rel="stylesheet" href="/assets/admin.css">
<link rel="stylesheet" href="/assets/admin.css?v={{ kant_version() }}">
{% block styles %}{% endblock %}
{% block scripts %}{% endblock %}
</head>
+2 -2
View File
@@ -3,8 +3,8 @@
{% set subject = area ? area.document : page.document %}
{% block title %}{{ subject.title }} bearbeiten{% endblock %}
{% block body_class %}editor-body{% endblock %}
{% block styles %}<link rel="stylesheet" href="/assets/editor.css">{% endblock %}
{% block scripts %}<script type="module" src="/assets/editor.js"></script>{% endblock %}
{% block styles %}<link rel="stylesheet" href="/assets/editor.css?v={{ kant_version() }}">{% endblock %}
{% block scripts %}<script type="module" src="/assets/editor.js?v={{ kant_version() }}"></script>{% endblock %}
{% block body %}
{# The API base decides what is edited. An area is shared, so it has no draft and no
publication: saving is live, and the publish button is left out. #}
+1 -1
View File
@@ -1,7 +1,7 @@
{% extends 'admin/shell.html.twig' %}
{% set nav = 'media' %}
{% block title %}Mediathek{% endblock %}
{% block scripts %}<script type="module" src="/assets/media-drop.js"></script>{% endblock %}
{% block scripts %}<script type="module" src="/assets/media-drop.js?v={{ kant_version() }}"></script>{% endblock %}
{% block heading %}Mediathek{% endblock %}
{% block lead %}Alle Bilder, neueste zuerst. Beim Hochladen wird jedes Bild in WebP umgewandelt und in mehreren Breiten abgelegt; die Seite liefert dann die passende aus.{% endblock %}
{% block content %}
+5
View File
@@ -88,6 +88,11 @@
<input type="hidden" name="_csrf_token" value="{{ csrf_token('pages') }}">
<input type="hidden" name="expectedVersion" value="{{ page.version }}">
<div class="fields">
<div>
<label for="seotitle-{{ page.id }}">Titel für Suchergebnisse</label>
<input id="seotitle-{{ page.id }}" name="seoTitle" maxlength="160" value="{{ page.seo_title }}" placeholder="{{ page.document.title|default(page.path) }}">
<p class="field-help">Leer lassen, wenn der Titel der Seite auch in der Suche stehen soll. Google zeigt etwa 60 Zeichen und schneidet danach ab; was dahinter steht, liest niemand.</p>
</div>
<div>
<label for="robots-{{ page.id }}">Suchmaschinen</label>
<select id="robots-{{ page.id }}" name="robots">
+18
View File
@@ -110,5 +110,23 @@
<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 %}