Wissen: Kategoriewahl und Filter ohne Systemmenue

Filipe: "wieso sieht das so scheisse aus?" -- zum aufgeklappten
Auswahlmenue der Kategorie. Er hat recht, und die Ursache ist dieselbe
wie bei der Rollenwahl: Ein AUFGEKLAPPTES <select> laesst sich nicht
gestalten. Es kommt vom Betriebssystem, in Windows-Weiss, mitten in
einer dunklen Oberflaeche. Bei zwanzig Eintraegen kam dazu, dass man in
so einer Liste nichts findet.

KATEGORIEWAHL: jetzt dieselben sechs Welten, dieselben Farben und
dieselben Symbole wie die Kacheln auf der Seite. Man waehlt aus
demselben Regal, in dem man sonst stoebert. Unter der Wahl steht die
Beschreibung der gewaehlten Kategorie -- die Zeile, die beim
Einsortieren die eigentliche Frage beantwortet.

Nichts ist vorausgewaehlt (ausser man laedt aus einer Kategorie heraus
hoch). Die stille Vorbelegung war der Grund, warum eine PDF ueber
TikTok LIVE Studio unter "TikTok - Grundlagen" landete.

FILTER Stufe und Geraet: ebenfalls Schalter statt Menue. Zwei bis vier
Werte, die sich nie aendern -- dafuer ein Menue zu oeffnen ist ein Klick
zu viel.

Der Tag-Filter bleibt bewusst ein Auswahlfeld: Tags wachsen mit dem
Bestand, eine Schalterreihe waere irgendwann zwei Zeilen lang.

Geprueft: 20 Kategorien in 6 Welten, nichts vorausgewaehlt, Filter
setzen die Adresse (?stufe=einsteiger), Handy ohne Ueberlauf, 0
Konsolenfehler. Testeintrag landete unter der gewaehlten Kategorie.
This commit is contained in:
2026-08-31 12:14:18 +02:00
parent 389d8c1213
commit e14d9d5042
15 changed files with 244 additions and 112 deletions
+85
View File
@@ -324,3 +324,88 @@
border: 1px solid rgba(95, 201, 154, .3);
border-radius: var(--radius-klein);
}
/* ===================================================================
Kategoriewahl und Filterschalter (31.08.2026).
Beides ersetzt ein <select>. Der Grund ist derselbe wie bei der
Rollenwahl: Ein AUFGEKLAPPTES Systemmenue laesst sich nicht
gestalten -- es kam als weisses Windows-Menue mitten in der dunklen
Oberflaeche. Bei zwanzig Kategorien kam dazu, dass man in so einer
Liste nichts findet.
Hier stehen dieselben sechs Welten und dieselben Farben wie auf den
Kacheln der Seite: Man waehlt aus demselben Regal, in dem man sonst
stoebert.
=================================================================== */
.katwahl { display: grid; gap: 14px; margin-top: 4px; }
.katwahl__welt { --w: var(--akzent); }
.katwahl__welt[data-welt="tiktok"] { --w: #3fbdf5; }
.katwahl__welt[data-welt="aufbau"] { --w: #8a76ff; }
.katwahl__welt[data-welt="bildton"] { --w: #5fc99a; }
.katwahl__welt[data-welt="technik"] { --w: #e8b877; }
.katwahl__welt[data-welt="inhalt"] { --w: #e88fb4; }
.katwahl__welt[data-welt="hilfe"] { --w: #8e9cb0; }
.katwahl__weltname {
margin: 0 0 6px; font-size: .7rem; font-weight: 700;
letter-spacing: .08em; text-transform: uppercase;
color: color-mix(in srgb, var(--w) 55%, #ffffff);
}
.katwahl__reihe { display: flex; gap: 6px; flex-wrap: wrap; }
.katwahl__knopf {
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 13px; cursor: pointer; text-align: left;
background: linear-gradient(#1a2230, #151c28);
border: 1px solid var(--rand); border-radius: 10px;
color: var(--text-leise); font: inherit; font-size: .83rem;
transition: border-color .16s ease, background .16s ease, color .16s ease;
}
.katwahl__knopf:hover { color: var(--text); border-color: color-mix(in srgb, var(--w) 45%, transparent); }
.katwahl__knopf:focus-visible {
outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--w) 32%, transparent);
}
.katwahl__knopf[data-an="ja"] {
color: #fff; font-weight: 600;
border-color: color-mix(in srgb, var(--w) 65%, transparent);
background: linear-gradient(color-mix(in srgb, var(--w) 20%, #1a2230),
color-mix(in srgb, var(--w) 11%, #151c28));
box-shadow: 0 4px 14px -9px var(--w);
}
.katwahl__symbol {
width: 17px; height: 17px; flex: none;
fill: none; stroke: var(--w); stroke-width: 1.7;
stroke-linecap: round; stroke-linejoin: round;
}
.katwahl__name { white-space: nowrap; }
/* ---------- Filterschalter ------------------------------------------------ */
.filterreihe { display: inline-flex; gap: 4px; }
.filterreihe__knopf {
padding: 7px 12px; cursor: pointer;
background: rgba(255, 255, 255, .04);
border: 1px solid var(--rand); border-radius: 8px;
color: var(--text-still); font: inherit; font-size: .82rem;
transition: color .16s ease, border-color .16s ease, background .16s ease;
}
.filterreihe__knopf:hover { color: var(--text); border-color: rgba(255, 255, 255, .22); }
.filterreihe__knopf[data-an="ja"] {
color: #fff; font-weight: 600;
border-color: rgba(63, 189, 245, .55);
background: rgba(63, 189, 245, .16);
}
.filterreihe__knopf:focus-visible {
outline: none; box-shadow: 0 0 0 3px rgba(63, 189, 245, .3);
}
@media (max-width: 620px) {
.katwahl__knopf { flex: 1 1 100%; }
.filterreihe { flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) {
.katwahl__knopf, .filterreihe__knopf { transition: none; }
}
+74 -32
View File
@@ -284,28 +284,44 @@
let filterGefuellt = false;
function fuelleFilter() {
const z = zustand();
const setz = (id, eintraege, wert) => {
/* Stufe und Geraet als Schalterreihe: zwei bis vier Werte, die sich
nie aendern -- dafuer ein Auswahlmenue zu oeffnen ist ein Klick zu
viel, und aufgeklappt liesse es sich ohnehin nicht gestalten. */
const reihe = (id, eintraege, wert, feldname) => {
const f = $(id);
f.textContent = '';
const alle = document.createElement('option');
alle.value = ''; alle.textContent = 'Alle';
f.append(alle);
for (const [w, name] of eintraege) {
const o = document.createElement('option');
o.value = w; o.textContent = name;
f.append(o);
for (const [w, name] of [['', 'Alle'], ...eintraege]) {
const b = el('button', 'filterreihe__knopf', name);
b.type = 'button';
b.setAttribute('role', 'radio');
const an = (wert || '') === w;
b.setAttribute('aria-checked', an ? 'true' : 'false');
if (an) b.dataset.an = 'ja';
if (w) b.dataset.wert = w;
b.addEventListener('click', () => setzeZustand({ [feldname]: w }));
f.append(b);
}
f.value = wert || '';
};
setz('f-stufe', Object.entries(aufbau.stufen), z.stufe);
setz('f-geraet', [['pc', 'PC'], ['handy', 'Handy']], z.geraet);
setz('f-tag', aufbau.tags.map((t) => [t, '#' + t]), z.tag);
reihe('f-stufe', Object.entries(aufbau.stufen), z.stufe, 'stufe');
reihe('f-geraet', [['pc', 'PC'], ['handy', 'Handy']], z.geraet, 'geraet');
/* Die Tags wachsen mit dem Bestand -- dafuer bleibt ein Auswahlfeld
richtig, eine Schalterreihe waere irgendwann zwei Zeilen lang. */
const f = $('f-tag');
f.textContent = '';
const alle = document.createElement('option');
alle.value = ''; alle.textContent = 'Alle';
f.append(alle);
for (const tg of aufbau.tags) {
const o = document.createElement('option');
o.value = tg; o.textContent = '#' + tg;
f.append(o);
}
f.value = z.tag || '';
$('q').value = z.q;
if (filterGefuellt) return;
filterGefuellt = true;
$('f-stufe').addEventListener('change', () => setzeZustand({ stufe: $('f-stufe').value }));
$('f-geraet').addEventListener('change', () => setzeZustand({ geraet: $('f-geraet').value }));
$('f-tag').addEventListener('change', () => setzeZustand({ tag: $('f-tag').value }));
$('f-weg').addEventListener('click', () => setzeZustand({ k: '', q: '', stufe: '', geraet: '', tag: '' }));
let warte = null;
@@ -317,6 +333,24 @@
window.addEventListener('popstate', laden_);
/* ---------- Kategoriewahl ------------------------------------------------ */
function setzeKategorie(schluessel) {
for (const b of document.querySelectorAll('.katwahl__knopf')) {
const an = b.dataset.kategorie === schluessel;
if (an) b.dataset.an = 'ja'; else delete b.dataset.an;
b.setAttribute('aria-checked', an ? 'true' : 'false');
}
const hinweis = $('n-kat-hinweis');
if (hinweis) {
const k = aufbau?.kategorien.find((x) => x.schluessel === schluessel);
hinweis.textContent = k ? k.beschreibung : 'Noch keine Kategorie gewählt.';
}
}
const gewaehlteKategorie = () =>
document.querySelector('.katwahl__knopf[data-an="ja"]')?.dataset.kategorie || '';
/* ---------- Formular ------------------------------------------------------ */
function formularOeffnen(eintrag) {
@@ -335,7 +369,7 @@
nicht bewusst aendert, merkt es nicht. Vorbelegt wird nur, wenn
man aus EINER Kategorie heraus hochlaedt -- dann ist es eine
informierte Annahme, keine geratene. */
$('n-kategorie').value = eintrag ? eintrag.kategorie : (zustand().k || '');
setzeKategorie(eintrag ? eintrag.kategorie : (zustand().k || ''));
$('n-stufe').value = eintrag ? eintrag.stufe : 'einsteiger';
$('n-geraet').value = eintrag ? eintrag.geraet : 'egal';
$('n-tags').value = eintrag ? eintrag.tags.join(', ') : '';
@@ -366,25 +400,32 @@
f.append(o);
}
};
/* Nach Welten gruppiert -- dieselbe Gliederung wie auf der Seite.
Zwanzig Eintraege in einer flachen Liste findet man nicht. */
/* Nach Welten gruppiert -- dieselbe Gliederung und dieselben Farben
wie die Kacheln auf der Seite. Man waehlt aus demselben Regal, in
dem man sonst stoebert. */
const kf = $('n-kategorie');
kf.textContent = '';
const platzhalter = document.createElement('option');
platzhalter.value = '';
platzhalter.textContent = '— Kategorie wählen —';
platzhalter.disabled = true;
kf.append(platzhalter);
for (const welt of aufbau.welten) {
const gruppe = document.createElement('optgroup');
gruppe.label = welt.name;
for (const k of aufbau.kategorien.filter((x) => x.welt === welt.schluessel)) {
const o = document.createElement('option');
o.value = k.schluessel;
o.textContent = k.name;
gruppe.append(o);
const kats = aufbau.kategorien.filter((x) => x.welt === welt.schluessel);
if (!kats.length) continue;
const gruppe = el('div', 'katwahl__welt');
gruppe.dataset.welt = welt.schluessel;
gruppe.append(el('p', 'katwahl__weltname', welt.name));
const reihe = el('div', 'katwahl__reihe');
for (const k of kats) {
const b = el('button', 'katwahl__knopf');
b.type = 'button';
b.dataset.kategorie = k.schluessel;
b.setAttribute('role', 'radio');
b.setAttribute('aria-checked', 'false');
b.title = k.beschreibung;
b.append(symbol(k.symbol, 'katwahl__symbol'));
b.append(el('span', 'katwahl__name', k.name));
b.addEventListener('click', () => setzeKategorie(k.schluessel));
reihe.append(b);
}
if (gruppe.children.length) kf.append(gruppe);
gruppe.append(reihe);
kf.append(gruppe);
}
setz('n-stufe', Object.entries(aufbau.stufen));
setz('n-geraet', Object.entries(aufbau.geraete));
@@ -466,7 +507,7 @@
const angaben = {
titel: $('n-titel').value.trim(),
beschreibung: $('n-beschreibung').value.trim(),
kategorie: $('n-kategorie').value,
kategorie: gewaehlteKategorie(),
stufe: $('n-stufe').value,
geraet: $('n-geraet').value,
tags: $('n-tags').value,
@@ -476,7 +517,8 @@
if (angaben.titel.length < 3) { $('n-fehler').textContent = 'Der Titel fehlt.'; return; }
if (!angaben.kategorie) {
$('n-fehler').textContent = 'Wähle die Kategorie aus, in der die Anleitung stehen soll.';
$('n-kategorie').focus();
$('n-kategorie').scrollIntoView({ block: 'center', behavior: 'smooth' });
$('n-kategorie').querySelector('.katwahl__knopf')?.focus();
return;
}