Web: gzip, 44-px-Tippflächen mobil, Fokusring, Landmarks, größeres Inhaltsverzeichnis

GZipMiddleware (Buchdaten 1–2 MB → ~400 kB), Werkzeugleiste und
Inhaltsverzeichnis mobil mindestens 44 px, :focus-visible, header/main,
Sprunglink, prefers-reduced-motion, Inhaltsverzeichnis 14 px.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
team3
2026-10-05 16:37:56 +02:00
parent 09908b69c6
commit bbae194554
3 changed files with 19 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse, PlainTextResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.gzip import GZipMiddleware
from pydantic import BaseModel
from . import config, montage, pipeline, state
@@ -21,6 +22,7 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="buchwerk", lifespan=lifespan)
app.add_middleware(GZipMiddleware, minimum_size=1000) # Buchdaten sind 1–2 MB JSON
class ThemaNeu(BaseModel):

View File

@@ -102,6 +102,7 @@ function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? M
<template>
<div class="app" :class="{ 'tab-buch': tab === 'buch' }">
<a class="skip" href="#inhalt">Zum Inhalt springen</a>
<div v-if="navAuf" class="backdrop" @click="navAuf = false"></div>
<aside class="sidebar" :class="{ offen: navAuf }">
<button class="schliessen sekundaer klein" @click="navAuf = false">✕ Schließen</button>
@@ -122,7 +123,7 @@ function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? M
</aside>
<div class="haupt">
<div class="topbar">
<header class="topbar">
<button class="burger sekundaer klein" title="Themen" @click="navAuf = !navAuf">☰</button>
<span class="titel">{{ aktuell?.titel || '' }}</span>
<div class="tabs">
@@ -138,16 +139,16 @@ function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? M
<option value="hell">hell</option>
<option value="auto">auto</option>
</select>
</div>
</header>
<div v-if="fehler" class="fehler" @click="fehler = ''">{{ fehler }} — klicken zum Schließen</div>
<div class="inhalt">
<main class="inhalt" id="inhalt" tabindex="-1">
<Lauf v-if="tab === 'lauf' && state" :state="state"
@start="aktion(() => api.start(topic))"
@pause="aktion(() => api.pause(topic))"
@reset="aktion(() => api.reset(topic))" />
<Guide v-else-if="tab === 'buch' && topic" :topic="topic" :laeuft="laeuft" :fortschritt="fortschritt" />
<div v-else style="padding: 24px; color: var(--dim)">Thema links anlegen oder wählen.</div>
</div>
</main>
</div>
</div>
</template>

View File

@@ -121,7 +121,7 @@ a.knopf { display: inline-block; text-decoration: none; line-height: normal; }
/* Buch-Ansicht */
.guide { flex: 1; display: flex; min-height: 0; }
.guide-nav { width: 270px; min-width: 240px; overflow-y: auto; padding: 14px 10px; border-right: 1px solid var(--rand); background: var(--panel); font-size: 13px; }
.guide-nav { width: 270px; min-width: 240px; overflow-y: auto; padding: 14px 10px; border-right: 1px solid var(--rand); background: var(--panel); font-size: 14px; }
.guide-nav .werkzeuge { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.guide-nav .meta { color: var(--dim); font-size: 12px; margin-bottom: 10px; }
.guide-nav a.kap { display: flex; align-items: baseline; gap: 6px; padding: 6px 8px; border-radius: 6px; cursor: pointer; color: var(--text); text-decoration: none; font-weight: 600; margin-top: 6px; }
@@ -129,7 +129,7 @@ a.knopf { display: inline-block; text-decoration: none; line-height: normal; }
.guide-nav a.kap .t { flex: 1; }
.guide-nav a.abs { display: flex; justify-content: space-between; gap: 6px; padding: 3px 8px 3px 24px; border-radius: 6px; cursor: pointer; color: var(--dim); text-decoration: none; }
.guide-nav a.aktiv { background: var(--karte); color: var(--text); }
.guide-nav .zeit { color: var(--dim); font-size: 11px; white-space: nowrap; }
.guide-nav .zeit { color: var(--dim); font-size: 12px; white-space: nowrap; }
.guide-nav .fortschritt { height: 4px; background: var(--karte); border-radius: 2px; margin: 8px 0 4px; overflow: hidden; }
.guide-nav .fortschritt i { display: block; height: 100%; background: var(--gruen); }
.guide-nav .leer { color: var(--dim); padding: 8px; }
@@ -201,6 +201,12 @@ a.knopf { display: inline-block; text-decoration: none; line-height: normal; }
.lm-fuss button.weiter { background: var(--akzent); color: #fff; border: 0; }
kbd { font: 11px system-ui, sans-serif; border: 1px solid var(--rand); border-radius: 4px; padding: 0 5px; margin-left: 6px; color: var(--dim); }
/* Barrierefreiheit: sichtbarer Fokus für Tastatur, Sprunglink, keine Animationen auf Wunsch */
:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.skip { position: absolute; left: -999px; top: 8px; z-index: 100; background: var(--akzent); color: #fff; padding: 8px 12px; border-radius: 6px; }
.skip:focus { left: 8px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; } }
@media (max-width: 800px) {
.topbar { gap: 8px; padding: 8px 10px; flex-wrap: nowrap; }
.topbar .badge { display: none; }
@@ -212,8 +218,10 @@ kbd { font: 11px system-ui, sans-serif; border: 1px solid var(--rand); border-ra
.burger { display: inline-block; }
.guide-nav { display: none; }
.guide-nav.offen { display: block; position: fixed; inset: 0 auto 0 0; z-index: 25; width: 82vw; font-size: 14.5px; }
.guide-nav.offen a.kap { padding: 10px 8px; }
.guide-nav.offen a.abs { padding: 8px 8px 8px 24px; }
.guide-nav.offen a.kap { padding: 12px 8px; min-height: 44px; box-sizing: border-box; }
.guide-nav.offen a.abs { padding: 10px 8px 10px 24px; min-height: 44px; box-sizing: border-box; align-items: center; }
.mobil-leiste button, .mobil-leiste a.knopf, .burger, .topbar .tabs button, .lm-kopf button, .guide-nav .schliessen { min-height: 44px; min-width: 44px; padding: 10px 14px; font-size: 14px; }
.mobil-leiste { flex-wrap: wrap; }
.buch { padding: 22px 16px 100px; font-size: 1.02rem; }
.lm-screen { padding: 24px 16px 60px; font-size: 1.05rem; }
.lm-kopf, .lm-fuss { padding: 8px 12px; }