Sprung zum Inhalt: Seite ohne Maus in einem Schritt bedienbar

Der Tastatur-Test (pruef-tastatur.mjs, neu) zeigte auf allen fünf
geprüften Seiten dasselbe Bild: jedes Bedienelement erreichbar, Fokus
durchgehend sichtbar, keine Tastaturfalle -- aber kein Sprunglink.

Ohne ihn muss sich jemand, der die Tastatur benutzt, auf JEDER Seite
erneut durch das komplette Menü tabben (31 bis 52 Punkte), bevor der
eigentliche Inhalt beginnt. WCAG 2.4.1 verlangt genau diesen Ausweg.

An einer Stelle gelöst statt in 35 Dateien: renderHeader() in main.js
setzt das Sprungziel und stellt den Link davor.

Das tabindex="-1" am <main> ist der Teil, der meistens fehlt: ohne ihn
verschiebt der Sprung in Chrome und Safari nur die Bildlaufleiste, der
Tastaturfokus bleibt in der Navigation -- der nächste Tab landet wieder
im Menü und der Sprung war wirkungslos.

Cache-Buster auf 20260827a (244 Stellen), sonst bekäme niemand die
geänderte main.js und main.css ausgeliefert.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-27 01:21:31 +02:00
co-authored by Claude Opus 5
parent b99e3043b6
commit efe98e32a7
38 changed files with 417 additions and 243 deletions
+36
View File
@@ -37,6 +37,42 @@ ul { list-style: none; margin: 0; padding: 0; }
--shadow: 0 10px 30px rgba(0,0,0,.35);
}
/* ---------------------------------------------------------------------
Sprung zum Inhalt (Tastaturbedienung, WCAG 2.4.1)
Der Link ist das erste Element der Seite und für Mausnutzer unsichtbar.
Er darf aber NICHT mit display:none oder visibility:hidden versteckt
werden -- damit wäre er auch für die Tastatur verschwunden, und genau
die soll ihn ja erreichen. Deshalb wird er aus dem Bild geschoben und
kehrt zurück, sobald er den Fokus bekommt.
:focus statt :focus-visible -- ein Sprunglink wird ausschließlich per
Tastatur angesteuert, und ältere Browser kennen :focus-visible nicht;
die würden ihn dann nie zeigen.
Farbe bewusst der Akzentton auf dunklem Text (nicht umgekehrt): Das
ergibt rund 12:1 Kontrast, ohne dass ein greller Kasten aufblitzt.
--------------------------------------------------------------------- */
.site-skip {
position: absolute;
left: -9999px;
top: 0;
z-index: 3000;
background: var(--accent);
color: var(--accent-contrast);
font-family: var(--font-head);
font-weight: 700;
text-decoration: none;
padding: .85rem 1.4rem;
border-radius: 0 0 var(--radius) 0;
box-shadow: var(--shadow);
}
.site-skip:focus { left: 0; }
/* Das Sprungziel bekommt tabindex="-1" (siehe main.js). Ohne diese Regel
malt der Browser dann einen Fokusrahmen um den halben Bildschirm. */
main[tabindex="-1"]:focus { outline: none; }
body {
background: var(--bg);
color: var(--text);
+26
View File
@@ -75,6 +75,12 @@
}
const I18N_CORE = {
/* Steht als Erstes im Wörterbuch, weil es als Erstes angesprungen wird:
der Sprung über die Navigation direkt zum Inhalt. Sichtbar nur, wenn
er den Tastaturfokus hat -- mit der Maus bekommt ihn niemand zu sehen.
Ohne ihn muss man sich auf JEDER Seite erneut durch das komplette
Menü tabben (über 20 Punkte), bevor der eigentliche Text beginnt. */
nav_zum_inhalt: { de: "Zum Inhalt springen", "de-CH": "Zum Inhalt springe", en: "Skip to content", fr: "Aller au contenu", pt: "Ir para o conteúdo" },
nav_start: { de: "Start", "de-CH": "Start", en: "Home", fr: "Accueil", pt: "Início" },
/* Umbenennungen 20.08.2026 (Nutzer-Wunsch): Kategorie "DogFather" -> "Filipe",
Kategorie "HasiDog" -> "Dogi&Hasi". Die Schlüsselnamen (nav_dogfather...)
@@ -547,7 +553,27 @@
function renderHeader() {
const el = document.getElementById("site-header");
if (!el) return;
/* Sprungziel vorbereiten.
Das tabindex="-1" ist der entscheidende Teil und wird fast überall
vergessen: Ein Sprung auf ein Element, das von sich aus keinen Fokus
annehmen kann, verschiebt in Chrome und Safari nur die Bildlaufleiste
-- der Tastaturfokus bleibt oben in der Navigation stehen. Der nächste
Tab landet dann wieder im Menü, und der Sprung war wirkungslos.
Mit -1 nimmt <main> den Fokus programmatisch an, taucht aber selbst
NICHT in der Tab-Reihenfolge auf.
Beides nur setzen, wenn nichts dasteht: Falls eine Seite schon eine
eigene id oder ein eigenes tabindex hat, wird das nicht überfahren. */
const inhalt = document.querySelector("main");
if (inhalt) {
if (!inhalt.id) inhalt.id = "inhalt";
if (!inhalt.hasAttribute("tabindex")) inhalt.setAttribute("tabindex", "-1");
}
el.innerHTML = `
${inhalt ? `<a class="site-skip" href="#${inhalt.id}" data-i18n="nav_zum_inhalt">${t("nav_zum_inhalt")}</a>` : ""}
<header class="site-header">
<div class="nav-bar">
<a class="brand" href="index.html">