Verwaltung: eine Suche ueber alles, mit Tastatur bedienbar

Bisher gab es genau ein Suchfeld, und es durchsuchte nur die
Anfragenliste. Wer den Namen eines Kunden im Kopf hatte, musste raten,
in welchem Reiter er nachsehen muss: War das eine Anfrage, ein laufendes
Projekt, eine offene Rechnung? Bei drei Vorgaengen merkt man sich das,
bei dreissig nicht mehr.

Jetzt: Strg+K von ueberall, auf dem Handy der Lupenknopf oben. Ein
Aufruf durchsucht Anfragen, Kunden, Projekte und Zahlungen; jeder
Treffer traegt seinen Zusammenhang (Nummer, Kunde, Betrag, Liefertermin)
und fuehrt per Enter in den passenden Reiter, bei einer Anfrage direkt in
die Detailansicht.

Gebaut nach dem ARIA-Muster 'Combobox mit Listbox-Popup' aus den W3C
Authoring Practices -- nachgeschlagen, nicht aus dem Gedaechtnis:
role=combobox am Eingabefeld, aria-expanded, aria-controls,
aria-activedescendant, role=listbox, role=option mit aria-selected. Der
Fokus bleibt dabei im Eingabefeld, damit man weitertippen kann; die
Auswahl wandert ueber aria-activedescendant. Ohne diese Auszeichnung
waere ein Feld, das Vorschlaege einblendet, fuer einen Screenreader
stumm -- das sieht man beim Testen mit den Augen nie.

Vier Fallen ausdruecklich behandelt:
- Nicht bei jedem Tastendruck suchen (180 ms Wartezeit): 'Musterbau'
  haette sonst neun Abfragen ausgeloest, acht davon veraltet.
- Das Wettrennen der Antworten: Jede Abfrage bekommt eine laufende
  Nummer, nur die neueste darf zeichnen. Sonst ueberschreibt eine spaet
  eintreffende alte Antwort die neue.
- Leer, laedt und Fehler sind eigene Zustaende. Ein Kasten, der bei
  einem Serverfehler leer bleibt, sieht aus wie 'nichts gefunden'.
- LIKE-Sonderzeichen: '%' waere ein Platzhalter, '_' ein beliebiges
  Zeichen -- und Unterstriche stehen regelmaessig in E-Mail-Adressen.
  Der Fehler ist tueckisch, weil die Suche trotzdem Treffer liefert, nur
  die falschen.

Ausserdem zum dritten Mal dieselbe Spezifitaetsfalle gefunden: '.wd p'
schlug die eigene Regel, der Tastaturhinweis erschien in 17,9px statt
11,5px -- so gross wie der Inhalt, den er erklaert. Jetzt festgenagelt
durch eine Pruefung, die Groessenverhaeltnisse vergleicht.

Geprueft: 28 gegen eine echte Datenbank (darunter alle
LIKE-Sonderzeichen), 64 im Browser auf Computer und Handy inkl. der
ARIA-Vorgaben, des Wettrennens und des Fehlerzustands.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-23 23:13:23 +02:00
co-authored by Claude Opus 5
parent 659a1ee9ce
commit 2ae77fc54f
6 changed files with 1035 additions and 1 deletions
+1 -1
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v24";
const CACHE_NAME = "dogfather-webdesign-v25";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
+426
View File
@@ -247,6 +247,149 @@
.vw-ub-kachel:hover:not(:disabled), .vw-ub-projekt:hover { transform: none; }
}
/* ================================================================
SUCHE ÜBER ALLES
Liegt über der Seite, nicht daneben: Sie ist ein Werkzeug, das man
kurz benutzt und wieder wegtut. Ein fest eingebautes Suchfeld
nähme dauerhaft Platz für etwas, das man zehnmal am Tag für drei
Sekunden braucht.
================================================================ */
.vw-palette { position: fixed; inset: 0; z-index: 200; }
.vw-palette[hidden] { display: none; }
.vw-palette-hg {
position: absolute; inset: 0;
background: rgb(0 0 0 / .55);
backdrop-filter: blur(3px);
}
.vw-palette-kasten {
position: relative;
/* Oben statt mittig: Die Liste wächst nach unten, und ein mittig
zentrierter Kasten würde bei jedem Tastendruck springen. */
margin: max(8vh, calc(1rem + var(--wd-sicher-oben, 0px))) auto 0;
width: min(680px, calc(100vw - 1.6rem));
max-height: min(70vh, 620px);
display: flex; flex-direction: column;
background: var(--wd-flaeche-2, #16191d);
border: 1px solid rgb(var(--wd-blau-rgb) / .28);
border-radius: var(--wd-radius-l, 20px);
box-shadow: 0 24px 70px -20px rgb(0 0 0 / .85);
overflow: hidden;
}
.vw-palette-kopf {
display: flex; align-items: center; gap: .6rem;
padding: .85rem 1rem;
border-bottom: 1px solid rgb(255 255 255 / .07);
}
.vw-palette-lupe { color: var(--wd-text-soft); flex-shrink: 0; }
.vw-palette-feld {
flex: 1; min-width: 0;
background: none; border: 0; outline: none;
color: var(--wd-text);
/* 16px, damit iOS beim Antippen nicht in die Seite hineinzoomt. */
font-size: 16px; font-family: var(--font-body);
}
.vw-palette-feld::placeholder { color: var(--wd-text-soft); }
.vw-palette-weg {
flex-shrink: 0; min-height: 44px; min-width: 44px;
background: rgb(255 255 255 / .06); border: 1px solid rgb(255 255 255 / .1);
border-radius: 8px; color: var(--wd-text-soft);
font-size: .74rem; font-weight: 700; cursor: pointer;
}
.vw-palette-weg:focus-visible { outline: 2px solid rgb(var(--wd-blau-rgb) / .95); outline-offset: 2px; }
/* Zwei Klassen, weil `.wd p` (Klasse + Element) sonst gewinnt.
Dieselbe Falle wie schon bei `.wd a`, `.wd .wd-btn--haupt` und den
Zwischenueberschriften der Uebersicht: Die Fusszeile und der Zaehler
erschienen in 17,9px statt 11,5px -- ein Tastaturhinweis, der so
gross ist wie der Inhalt, den er erklaert. */
.wd .vw-palette-zahl {
margin: 0; padding: .35rem 1rem 0;
font-size: .76rem; color: var(--wd-text-soft);
}
.wd .vw-palette-zahl:empty { display: none; }
.vw-palette-liste {
list-style: none; margin: 0; padding: .35rem;
overflow-y: auto; flex: 1;
}
.vw-pal-treffer {
display: flex; align-items: center; gap: .7rem;
padding: .6rem .65rem; border-radius: 10px;
cursor: pointer;
}
/* Die Auswahl ist eine FLÄCHE, kein Rahmen: Ein Rahmen verschiebt bei
manchen Schriftgrössen die Zeile um einen Pixel, und beim Durchgehen
mit den Pfeiltasten zappelt dann die ganze Liste. */
.vw-pal-treffer.vw-pal-an { background: rgb(var(--wd-blau-rgb) / .16); }
.vw-pal-art {
flex-shrink: 0; width: 62px; text-align: center;
padding: .16rem 0; border-radius: 999px;
font-size: .68rem; font-weight: 800; letter-spacing: .03em;
text-transform: uppercase;
background: rgb(255 255 255 / .07); color: var(--wd-text-soft);
}
.vw-pal-art--anfrage { background: rgb(var(--wd-gold-rgb) / .16); color: var(--wd-gold); }
.vw-pal-art--kunde { background: rgb(var(--wd-blau-rgb) / .16); color: var(--wd-blau); }
.vw-pal-art--projekt { background: rgb(var(--wd-lila-rgb) / .18); color: var(--wd-lila); }
.vw-pal-art--zahlung { background: rgb(var(--wd-ok-rgb) / .16); color: var(--wd-ok); }
.vw-pal-mitte { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.vw-pal-titel {
font-size: .93rem; font-weight: 650; color: var(--wd-text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vw-pal-unten {
font-size: .78rem; color: var(--wd-text-soft);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vw-pal-marke {
flex-shrink: 0; font-size: .72rem; color: var(--wd-text-soft);
padding: .12rem .5rem; border-radius: 999px;
background: rgb(255 255 255 / .06);
}
.wd .vw-pal-hinweis {
padding: 1.4rem 1rem; text-align: center;
color: var(--wd-text-soft); font-size: .9rem;
}
.vw-pal-hinweis--fehler { color: #e78278; }
.wd .vw-palette-fuss {
margin: 0; padding: .55rem 1rem;
border-top: 1px solid rgb(255 255 255 / .07);
font-size: .72rem; color: var(--wd-text-soft);
}
.wd .vw-palette-fuss kbd {
display: inline-block; padding: .05rem .35rem; margin: 0 .1rem;
border: 1px solid rgb(255 255 255 / .14); border-radius: 5px;
background: rgb(255 255 255 / .05); font-size: .9em;
font-family: var(--font-body);
}
@media (max-width: 620px) {
/* Auf dem Handy gibt es keine Tastatur mit Strg -- der Hinweis wäre
dort eine Anleitung für etwas Unmögliches. */
.wd .vw-palette-fuss { display: none; }
.vw-palette-kasten { max-height: min(80vh, 620px); }
}
/* Der Lupenknopf in der Kopfzeile. */
.vw-suche-auf {
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
min-height: 44px; padding: 0 .8rem;
background: rgb(255 255 255 / .05);
border: 1px solid var(--wd-rand); border-radius: 10px;
color: var(--wd-text-soft); cursor: pointer; font: inherit; font-size: .84rem;
}
.vw-suche-auf:hover { border-color: rgb(var(--wd-blau-rgb) / .4); color: var(--wd-text); }
.vw-suche-auf:focus-visible { outline: 2px solid rgb(var(--wd-blau-rgb) / .95); outline-offset: 2px; }
.vw-suche-auf kbd {
padding: .05rem .3rem; border: 1px solid rgb(255 255 255 / .14);
border-radius: 4px; font-size: .72rem; font-family: var(--font-body);
}
@media (max-width: 620px) { .vw-suche-auf kbd { display: none; } }
/* ---------- Kennzahlen, gehobene Fassung ----------
Die erste Fassung war eine Reihe stiller Zahlen. Zahlen, die man
nur anschauen kann, sind Dekoration. Diese hier sind Werkzeuge:
@@ -891,6 +1034,17 @@
class="vw-abzeichen" id="vw-pf-zaehler" hidden>0</span></button>
</div>
<div class="vw-wer">
<!-- Der Lupenknopf steht neben dem Namen, nicht in der
Reiterleiste: Suchen ist keine Ansicht, sondern ein
Werkzeug -- es gehoert nicht zwischen die Kategorien. -->
<button type="button" class="vw-suche-auf" id="vw-suche-auf"
aria-label="Suche über alles öffnen">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" />
</svg>
Suchen <kbd>Strg K</kbd>
</button>
<span id="vw-name">—</span>
<button type="button" class="wd-btn wd-btn--still wd-btn--klein" id="vw-abmelden">Abmelden</button>
</div>
@@ -909,6 +1063,52 @@
2. Was wartet auf den KUNDEN? (da bin ich raus)
3. Wie steht es ums Geld? (Umsatz, Gebühren, Offenes)
Erst danach Projekte und Verlauf. -->
<!-- ============ SUCHE ÜBER ALLES ============
Aufbau nach dem ARIA-Muster "Combobox mit Listbox-Popup"
(W3C Authoring Practices). Das ist kein Selbstzweck: Ein
Suchfeld, das Vorschläge einblendet, ist für einen
Screenreader ohne diese Auszeichnung stumm — man tippt, es
erscheinen Treffer, und angesagt wird nichts.
Die Rollen sind bewusst getrennt: Das EINGABEFELD behält den
Tastaturfokus die ganze Zeit (deshalb aria-activedescendant
statt echtem Fokuswechsel), die Liste daneben ist nur
Anzeige. Würde der Fokus in die Liste springen, könnte man
nicht weitertippen, um die Suche zu verfeinern. -->
<div class="vw-palette" id="vw-palette" hidden>
<div class="vw-palette-hg" data-palette-zu></div>
<div class="vw-palette-kasten" role="dialog" aria-modal="true"
aria-labelledby="vw-pal-titel">
<h2 id="vw-pal-titel" class="wd-visuell-versteckt">Suche über alles</h2>
<div class="vw-palette-kopf">
<svg class="vw-palette-lupe" width="18" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" />
</svg>
<input type="text" id="vw-pal-feld" class="vw-palette-feld"
role="combobox" aria-expanded="false" aria-controls="vw-pal-liste"
aria-autocomplete="list" aria-label="Nach Anfragen, Kunden, Projekten oder Zahlungen suchen"
placeholder="Name, Nummer, E-Mail …" autocomplete="off" spellcheck="false" />
<button type="button" class="vw-palette-weg" data-palette-zu
aria-label="Suche schließen">Esc</button>
</div>
<!-- Der Zähler wird angesagt, ohne den Fokus zu stören.
aria-live="polite": Wer gerade tippt, wird nicht
unterbrochen, hört die Zahl aber nach dem Tippen. -->
<p class="vw-palette-zahl" id="vw-pal-zahl" role="status" aria-live="polite"></p>
<ul class="vw-palette-liste" id="vw-pal-liste" role="listbox"
aria-label="Suchergebnisse"></ul>
<p class="vw-palette-fuss">
<kbd>↑</kbd><kbd>↓</kbd> auswählen · <kbd>Enter</kbd> öffnen ·
<kbd>Esc</kbd> schließen · <kbd>Strg</kbd>+<kbd>K</kbd> von überall
</p>
</div>
</div>
<div id="vw-uebersicht-bereich">
<div class="vw-laedt" id="vw-ub-laedt">Wird geladen…</div>
<div id="vw-ub-inhalt" hidden>
@@ -2245,6 +2445,232 @@
postfach: { titel: "Postfach", kasten: "vw-postfach-bereich" },
};
/* =====================================================================
SUCHE ÜBER ALLES ("Befehlspalette")
Bisher gab es ein Suchfeld, und es durchsuchte nur die Anfragen. Wer
einen Namen im Kopf hatte, musste raten, in welchem Reiter er
nachsehen muss. Jetzt: Strg+K von überall, auf dem Handy der
Lupenknopf oben.
VIER DINGE, DIE MAN LEICHT FALSCH MACHT
1. BEI JEDEM TASTENDRUCK SUCHEN. Wer "Musterbau" tippt, löst neun
Abfragen aus, von denen acht schon veraltet sind, bevor sie
ankommen. Deshalb eine kurze Wartezeit -- lang genug, um Tippen
zu erkennen, kurz genug, um nicht zu bremsen.
2. DAS WETTRENNEN DER ANTWORTEN. Auch mit Wartezeit können zwei
Abfragen unterwegs sein. Kommt die ältere später an, überschreibt
sie die neuere -- man tippt weiter und sieht plötzlich wieder die
Treffer von vorhin. Deshalb bekommt jede Abfrage eine laufende
Nummer, und nur die jeweils neueste darf zeichnen.
3. DEN FOKUS IN DIE LISTE VERSCHIEBEN. Naheliegend, aber falsch: Dann
kann man nicht mehr weitertippen. Der Fokus bleibt im Eingabefeld,
die Auswahl wandert über aria-activedescendant -- so schreibt es
das ARIA-Muster vor, und so verhält es sich auch richtig.
4. NUR DEN GUTEN FALL BAUEN. Leer, lädt und Fehler sind eigene
Zustände. Ein Kasten, der bei einem Serverfehler einfach leer
bleibt, sieht aus wie "nichts gefunden" -- und man sucht dann an
der falschen Stelle nach dem Problem.
===================================================================== */
var palette = document.getElementById("vw-palette");
var palFeld = document.getElementById("vw-pal-feld");
var palListe = document.getElementById("vw-pal-liste");
var palZahl = document.getElementById("vw-pal-zahl");
var palTreffer = [];
var palAuswahl = -1;
var palLauf = 0; // laufende Nummer gegen das Wettrennen
var palZeitgeber = null;
var palVorherFokus = null; // wohin der Fokus zurückkehrt
var ART_TEXT = {
anfrage: "Anfrage", kunde: "Kunde", projekt: "Projekt", zahlung: "Zahlung",
};
function paletteAuf() {
if (!palette.hidden) return;
/* Merken, wo der Fokus war. Beim Schliessen muss er dorthin zurueck --
sonst steht man nach Esc am Seitenanfang und muss sich neu
orientieren. */
palVorherFokus = document.activeElement;
palette.hidden = false;
palFeld.value = "";
palListeLeeren();
palZahl.textContent = "";
palFeld.focus();
document.body.style.overflow = "hidden"; // Hintergrund nicht mitscrollen
}
function paletteZu() {
if (palette.hidden) return;
palette.hidden = true;
document.body.style.overflow = "";
palFeld.setAttribute("aria-expanded", "false");
palFeld.removeAttribute("aria-activedescendant");
if (palVorherFokus && palVorherFokus.focus) {
try { palVorherFokus.focus(); } catch (e) { /* Element ist weg, egal */ }
}
}
function palListeLeeren() {
palListe.innerHTML = "";
palTreffer = [];
palAuswahl = -1;
palFeld.setAttribute("aria-expanded", "false");
palFeld.removeAttribute("aria-activedescendant");
}
function palHinweis(text, art) {
palListe.innerHTML = '<li class="vw-pal-hinweis' + (art ? " vw-pal-hinweis--" + art : "") +
'" role="presentation">' + s(text) + "</li>";
palTreffer = [];
palAuswahl = -1;
palFeld.setAttribute("aria-expanded", "false");
palFeld.removeAttribute("aria-activedescendant");
}
function palZeichnen(d) {
palTreffer = d.treffer || [];
palAuswahl = -1;
if (d.zuKurz) {
palHinweis("Mindestens zwei Zeichen eingeben.");
palZahl.textContent = "";
return;
}
if (!palTreffer.length) {
palHinweis("Nichts gefunden zu „" + d.begriff + "“.");
palZahl.textContent = "Keine Treffer";
return;
}
palListe.innerHTML = palTreffer.map(function (t, i) {
return '<li class="vw-pal-treffer" role="option" id="vw-pal-o' + i + '"' +
' aria-selected="false" data-nr="' + i + '">' +
'<span class="vw-pal-art vw-pal-art--' + s(t.art) + '">' +
s(ART_TEXT[t.art] || t.art) + "</span>" +
'<span class="vw-pal-mitte">' +
'<span class="vw-pal-titel">' + s(t.titel || "—") + "</span>" +
'<span class="vw-pal-unten">' + s(t.unten || "") + "</span>" +
"</span>" +
(t.marke ? '<span class="vw-pal-marke">' + s(t.marke) + "</span>" : "") +
"</li>";
}).join("");
palFeld.setAttribute("aria-expanded", "true");
palZahl.textContent = palTreffer.length + (palTreffer.length === 1 ? " Treffer" : " Treffer")
+ (d.begrenzt ? " (je Bereich höchstens " + d.grenzeJeBereich + ")" : "");
palWaehlen(0);
}
function palWaehlen(nr) {
var knoten = palListe.querySelectorAll('[role="option"]');
if (!knoten.length) return;
if (nr < 0) nr = knoten.length - 1;
if (nr >= knoten.length) nr = 0;
palAuswahl = nr;
knoten.forEach(function (k, i) {
k.setAttribute("aria-selected", i === nr ? "true" : "false");
k.classList.toggle("vw-pal-an", i === nr);
});
/* aria-activedescendant statt echtem Fokus: Der Fokus bleibt im
Eingabefeld, damit man weitertippen kann -- angesagt wird
trotzdem die ausgewaehlte Zeile. */
palFeld.setAttribute("aria-activedescendant", knoten[nr].id);
knoten[nr].scrollIntoView({ block: "nearest" });
}
function palOeffnen(nr) {
var t = palTreffer[nr];
if (!t) return;
paletteZu();
var knopf = document.querySelector('[data-tab="' + t.ziel + '"]');
if (knopf) knopf.click();
/* Bei einer Anfrage direkt die Detailansicht oeffnen. Nur in den
Reiter zu springen waere die halbe Strecke -- man muesste den
Treffer in der Liste noch einmal suchen, den man gerade
ausgewaehlt hat. */
if (t.art === "anfrage" && typeof zeigeDetail === "function") {
setTimeout(function () { zeigeDetail(t.id); }, 260);
}
}
async function palSuchen(begriff) {
var meine = ++palLauf;
if (!begriff || begriff.trim().length < 2) {
palHinweis("Mindestens zwei Zeichen eingeben.");
palZahl.textContent = "";
return;
}
palHinweis("Wird gesucht…", "laedt");
try {
var d = await api("/webdesign/admin/suche?q=" + encodeURIComponent(begriff));
if (meine !== palLauf) return; // eine neuere Abfrage ist schon unterwegs
palZeichnen(d);
} catch (fehler) {
if (meine !== palLauf) return;
/* Ein Fehler darf NICHT wie "nichts gefunden" aussehen. */
palHinweis("Die Suche ist fehlgeschlagen: " + (fehler.message || "unbekannt"), "fehler");
palZahl.textContent = "";
}
}
if (palette) {
palFeld.addEventListener("input", function () {
clearTimeout(palZeitgeber);
var wert = palFeld.value;
/* 180 ms: kurz genug, dass es sofort wirkt, lang genug, dass beim
Durchtippen eines Namens nicht jede Taste eine Abfrage ausloest. */
palZeitgeber = setTimeout(function () { palSuchen(wert); }, 180);
});
palFeld.addEventListener("keydown", function (e) {
if (e.key === "ArrowDown") { e.preventDefault(); palWaehlen(palAuswahl + 1); }
else if (e.key === "ArrowUp") { e.preventDefault(); palWaehlen(palAuswahl - 1); }
else if (e.key === "Enter") { e.preventDefault(); palOeffnen(palAuswahl); }
else if (e.key === "Escape") { e.preventDefault(); paletteZu(); }
else if (e.key === "Home" && palTreffer.length) { e.preventDefault(); palWaehlen(0); }
else if (e.key === "End" && palTreffer.length) { e.preventDefault(); palWaehlen(palTreffer.length - 1); }
});
palListe.addEventListener("click", function (e) {
var zeile = e.target.closest ? e.target.closest("[data-nr]") : null;
if (zeile) palOeffnen(Number(zeile.dataset.nr));
});
/* Mit der Maus darueberfahren waehlt mit aus -- sonst zeigt die
Tastaturauswahl auf eine andere Zeile als die, die man anklickt. */
palListe.addEventListener("mousemove", function (e) {
var zeile = e.target.closest ? e.target.closest("[data-nr]") : null;
if (zeile && Number(zeile.dataset.nr) !== palAuswahl) palWaehlen(Number(zeile.dataset.nr));
});
document.querySelectorAll("[data-palette-zu]").forEach(function (k) {
k.addEventListener("click", paletteZu);
});
/* Strg+K bzw. Cmd+K von ueberall.
Nicht abfangen, waehrend in einem anderen Feld getippt wird? Doch --
gerade dann ist es nuetzlich. Die Tastenkombination ist in Browsern
mit der Adressleiste belegt, deshalb preventDefault; das ist bei
dieser Kombination die uebliche Erwartung in Verwaltungsoberflaechen. */
document.addEventListener("keydown", function (e) {
if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) {
e.preventDefault();
palette.hidden ? paletteAuf() : paletteZu();
}
/* Esc schliesst auch, wenn der Fokus schon woanders liegt. */
if (e.key === "Escape" && !palette.hidden) paletteZu();
});
var lupe = document.getElementById("vw-suche-auf");
if (lupe) lupe.addEventListener("click", paletteAuf);
}
var aktiverTab = "uebersicht";
document.getElementById("vw-titel").textContent = ANSICHTEN[aktiverTab].titel;