Der Knopf "nach oben" ist ein Anker, kein Skript: er springt an den Seitenanfang, eingeblendet ueber eine Scroll-Zeitachse. Abschaltbar und vollstaendig einstellbar - Seite, Form, Groesse, Farben, Schwelle, Beschriftung. Bilder lassen sich vergroessern, je Bild waehlbar (nichts / vergroessern / Adresse), Galerien mit einem Schalter. Grundlage ist ein gewoehnlicher Verweis auf die grosse Fassung: ohne JavaScript oeffnet er das Bild, mit JavaScript einen dialog-Betrachter mit Blaettern, Escape und Pfeiltasten. Das Skript laedt nur, wo es etwas zu tun hat. Die Rechtszeile wird ein Baustein und steht damit im Fussbereich statt als heller Streifen darunter. Die eingebaute Leiste erscheint nur noch, wenn die Seite den Baustein nicht enthaelt. Dabei gefunden: zwei Stylesheet-Regeln griffen seit drei Veroeffentlichungen nicht mehr. Beim Entfernen alter Regeln waren Selektorreste stehen geblieben, und CSS nimmt dann die naechste Regel mit - betroffen waren der Einwilligungsdialog und die Rechtszeile. Ein Test prueft jetzt jede CSS-Datei auf abgebrochene Selektoren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
8.6 KiB
Twig
110 lines
8.6 KiB
Twig
<!doctype html>
|
||
<html lang="{{ page.locale }}">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
{# Der Titel fuer die Suche, sonst der Titel der Seite. #}
|
||
{% set seoTitle = page.seo_title|default('') ?: page.document.title %}
|
||
<title>{{ seoTitle }}</title>
|
||
<meta name="description" content="{{ page.document.description }}">
|
||
<link rel="canonical" href="{{ canonical }}">
|
||
{% if page.robots|default('index') != 'index' %}<meta name="robots" content="noindex, follow">{% endif %}
|
||
<meta property="og:title" content="{{ seoTitle }}">
|
||
<meta property="og:description" content="{{ page.document.description }}">
|
||
<meta property="og:type" content="{{ page.type|default('page') == 'post' ? 'article' : 'website' }}">
|
||
<meta property="og:url" content="{{ canonical }}">
|
||
{# In der Vorschau des Editors gibt es den Websitenamen nicht - dort fehlt das Feld einfach. #}
|
||
{% if page.website_name|default('') %}<meta property="og:site_name" content="{{ page.website_name }}">{% endif %}
|
||
<meta property="og:locale" content="{{ page.locale == 'de' ? 'de_DE' : page.locale }}">
|
||
{% if socialImage|default(null) %}
|
||
<meta property="og:image" content="{{ socialImage.url }}">
|
||
{% if socialImage.alt %}<meta property="og:image:alt" content="{{ socialImage.alt }}">{% endif %}
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
{% else %}
|
||
{# Ohne Bild ist die große Karte eine leere Fläche; die kleine zeigt Titel und Text. #}
|
||
<meta name="twitter:card" content="summary">
|
||
{% endif %}
|
||
{% include 'favicon.html.twig' %}
|
||
{# Ein Bündel statt vier Dateien: jede einzelne hält das Zeichnen der Seite auf. Fehlt es
|
||
(kein Schreibrecht im Ordner), werden die Quellen einzeln geladen - langsamer, aber heil. #}
|
||
{% if styleBundle|default(null) %}
|
||
<link rel="stylesheet" href="{{ styleBundle }}">
|
||
{% else %}
|
||
<link rel="stylesheet" href="/assets/site.css?v={{ kant_version() }}">
|
||
{% if localFonts|default([]) %}<link rel="stylesheet" href="/assets/fonts/fonts.css?v={{ kant_version() }}">{% endif %}
|
||
{% if page.document.schemaVersion >= 2 %}<link rel="stylesheet" href="/assets/design.css?v={{ kant_version() }}">{% endif %}
|
||
{% if page.document.schemaVersion >= 3 %}<link rel="stylesheet" href="/assets/enhancements.css?v={{ kant_version() }}">{% endif %}
|
||
{% endif %}
|
||
{% if localFonts|default([]) %}
|
||
{# Die wichtigsten Schnitte vorab, damit die erste Zeile in der richtigen Schrift steht. #}
|
||
{% for font in localFonts %}<link rel="preload" href="{{ font }}" as="font" type="font/woff2" crossorigin>{% endfor %}
|
||
{% elseif googleFonts|default(null) %}<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="{{ googleFonts }}">{% endif %}
|
||
{% if hasPosts|default(false) %} <link rel="alternate" type="application/rss+xml" title="Beiträge" href="/feed.xml">
|
||
{% endif %}
|
||
{% if needsTabs|default(false) %}<script src="/assets/tabs.js?v={{ kant_version() }}" defer></script>{% endif %}
|
||
{% if needsSlider|default(false) %}<script src="/assets/slider.js?v={{ kant_version() }}" defer></script>{% endif %}
|
||
{% if needsZoom|default(false) %}<script src="/assets/lightbox.js?v={{ kant_version() }}" defer></script>{% endif %}
|
||
{# Own scripts of this page, as a file. Never rendered in the editor preview: the preview must
|
||
show the layout, not run code that could change the page under the editor's hands. #}
|
||
{% if pageScript|default(null) and not editing|default(false) %}<script src="{{ pageScript }}" defer></script>{% endif %}
|
||
{# A data block, not a script: CSP script-src does not apply to application/ld+json. #}
|
||
{% for block in structuredData|default([]) %}<script type="application/ld+json">{{ block|json_encode(constant('JSON_UNESCAPED_SLASHES') b-or constant('JSON_UNESCAPED_UNICODE'))|raw }}</script>
|
||
{% endfor %}
|
||
{% if pageStyles|default('') %}
|
||
{# In the editor the preview inherits the administration's policy, which forbids inline
|
||
styles. The stylesheet therefore travels as inert template text and the editor applies
|
||
it as a constructed stylesheet – same result, no policy violation and a clean console. #}
|
||
{% if editing|default(false) %}<template id="kant-styles">{{ pageStyles|raw }}</template>
|
||
{% else %}<style>{{ pageStyles|raw }}</style>{% endif %}
|
||
{% endif %}
|
||
</head>
|
||
{% set consentOpen = not editing|default(false) and consentGate.needsBanner(app.request) %}
|
||
<body class="kant{{ consentOpen ? ' consent-open' : '' }}">
|
||
<span id="seitenanfang" tabindex="-1"></span>
|
||
<a class="skip-link" href="#main">Zum Inhalt springen</a>
|
||
{# Header and footer are block documents like any page, so they are built from elements
|
||
instead of being written into this template. #}
|
||
{% if areas.header|default(null) %}
|
||
<header class="site-header{{ siteSettings.load().stickyHeader ? ' is-sticky' : '' }}">
|
||
{% for block in areas.header.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: areas.header.schemaVersion|default(1) >= 12, richHeadings: areas.header.schemaVersion|default(1) >= 17, eagerImage: eagerImage|default(null), inHeader: true} only %}{% endfor %}
|
||
</header>
|
||
{% endif %}
|
||
<main id="main" tabindex="-1">
|
||
{% if postMeta is defined and postMeta is not null and siteSettings.load().postMetaHeader %}
|
||
<p class="post-header shell">
|
||
{% if page.published_at %}<time datetime="{{ page.published_at|date('Y-m-d') }}">{{ page.published_at|date('d.m.Y') }}</time>{% endif %}
|
||
{% if page.author %}<span>von {{ page.author }}</span>{% endif %}
|
||
{% for term in postMeta.category %}<a href="{{ term.href }}">{{ term.name }}</a>{% endfor %}
|
||
{% for term in postMeta.tag %}<a class="post-tag" href="{{ term.href }}">#{{ term.name }}</a>{% endfor %}
|
||
</p>
|
||
{% endif %}
|
||
{% for block in page.document.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: page.document.schemaVersion|default(1) >= 12, richHeadings: page.document.schemaVersion|default(1) >= 17, neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
|
||
</main>
|
||
{% if areas.footer|default(null) %}
|
||
<footer class="site-footer">
|
||
{% for block in areas.footer.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: areas.footer.schemaVersion|default(1) >= 12, richHeadings: areas.footer.schemaVersion|default(1) >= 17, eagerImage: null} only %}{% endfor %}
|
||
</footer>
|
||
{% endif %}
|
||
{# Privacy statement, imprint and the way back to the consent decision. All three have to be
|
||
reachable from every page, so they are part of the template and not of the footer area,
|
||
which someone could delete. One labelled landmark, so they are not loose text either. #}
|
||
{% set legal = consentGate.legalLinks() %}
|
||
{% set revocable = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %}
|
||
{# Nur, wenn die Seite die Zeile nicht selbst enthaelt: sonst stuende sie zweimal da. #}
|
||
{% if (legal is not empty or revocable) and not hasLegalBlock|default(false) %}
|
||
<nav class="legal-bar" aria-label="Rechtliches">
|
||
{% for path, label in legal %}{% if not loop.first %} · {% endif %}<a href="/{{ path }}">{{ label }}</a>{% endfor %}
|
||
{% if revocable %}{% if legal is not empty %} · {% endif %}<a href="/einwilligung">Einwilligung ändern</a>{% endif %}
|
||
</nav>
|
||
{% endif %}
|
||
{% set oben = siteSettings.load() %}
|
||
{% if oben.scrollTop|default(false) and not editing|default(false) %}
|
||
{# Ein Anker, kein Skript. Er springt an den Anfang - das kann ein Browser allein. #}
|
||
<a class="scroll-top{{ oben.scrollTopPosition|default('rechts') == 'links' ? ' scroll-top-links' : '' }}" href="#seitenanfang" aria-label="{{ oben.scrollTopLabel|default('Nach oben') }}"><span aria-hidden="true">↑</span></a>
|
||
{% endif %}
|
||
{% if not editing|default(false) and consentGate.needsBanner(app.request) %}
|
||
{% include 'consent-banner.html.twig' with {consent: consentGate.state(app.request), back: app.request.pathInfo} only %}
|
||
{% endif %}
|
||
</body>
|
||
</html>
|