Die Verwaltung war eine Seite mit 1786 Zeilen. Wer das Protokoll sehen wollte, scrollte an Mediatheken, Prüfliste, Benutzern und Speicher vorbei — und jede neue Einstellung hätte sie länger gemacht. Jetzt hat sie eine eigene Leiste mit sieben Bereichen, jeder mit seiner Adresse: `#/verwaltung/protokolle` lässt sich ablegen, und ein Neuladen bleibt dort stehen. Die Leiste sieht aus wie die Hauptleiste daneben, damit sie als Navigation gelesen wird und nicht als Knopfreihe; am Telefon wird aus der Spalte ein Streifen zum Schieben. **Gebaut wird nur, was offen ist.** Das ist nicht nur sparsamer: das Abspielfenster fragt im Fünf-Sekunden-Takt nach, die Mediathekenliste beobachtet laufende Scans. Gemessen: nach dem Wechsel in einen anderen Bereich kommt zwölf Sekunden lang keine Anfrage mehr. `admin.ts` ist in acht Dateien zerlegt — eine je Bereich, dazu `teile.ts` für die gemeinsame Überschrift und `index.ts` für Leiste und Weiche. Zwei Dinge mussten dabei nachziehen: **Die Markierung in der Hauptleiste.** Sie verglich stur zwei Segmente; bei `#/verwaltung/metadaten` traf das auf keinen Eintrag, und „Verwaltung" verlor seinen Balken. Jetzt gilt auch ein Segment. **„5 Titel warten — ansehen".** Der Knopf sprang mit `scrollIntoView` zur Prüfliste. Die steht nicht mehr im selben Dokument — er wechselt jetzt den Bereich. Die Hervorhebung danach entfällt damit, und mit ihr ihr CSS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
6.1 KiB
TypeScript
102 lines
6.1 KiB
TypeScript
/* Icons als SVG, nicht als Zeichen aus einer Schriftart.
|
||
*
|
||
* **Keine Emojis.** Ein ▶ oder ✓ aus Unicode sieht auf jedem System anders
|
||
* aus, lässt sich nicht einfärben, nicht in der Strichstärke anpassen und
|
||
* springt in der Grundlinie. Diese hier sind auf 24×24 gezeichnet, erben die
|
||
* Textfarbe und skalieren mit der Schriftgrösse.
|
||
*
|
||
* Der Strichstil ist durchgehend derselbe: 1.8 Einheiten, runde Enden. */
|
||
|
||
const rahmen = (inhalt: string, gefuellt = false): string =>
|
||
`<svg viewBox="0 0 24 24" aria-hidden="true" fill="${gefuellt ? 'currentColor' : 'none'}"`
|
||
+ ` stroke="${gefuellt ? 'none' : 'currentColor'}" stroke-width="1.8"`
|
||
+ ` stroke-linecap="round" stroke-linejoin="round">${inhalt}</svg>`;
|
||
|
||
export const icons = {
|
||
start: rahmen('<path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/>'),
|
||
/** Ordner — fürs Durchklicken durch das Dateisystem. */
|
||
ordner: rahmen('<path d="M3 7.5A1.5 1.5 0 0 1 4.5 6h4.3a1.5 1.5 0 0 1 1.07.45L11.4 7.6"'
|
||
+ ' /><path d="M3 7.5h16.5A1.5 1.5 0 0 1 21 9v8.5a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 17.5Z"/>'),
|
||
/** Drei Punkte — der sichtbare Griff zum Kontextmenü. */
|
||
punkte: rahmen('<circle cx="12" cy="5" r="1.4"/><circle cx="12" cy="12" r="1.4"/>'
|
||
+ '<circle cx="12" cy="19" r="1.4"/>', true),
|
||
/** Winkel nach oben — eine Ebene zurück im Baum. */
|
||
hoch: rahmen('<path d="m6 14 6-6 6 6"/>'),
|
||
runter: rahmen('<path d="m6 10 6 6 6-6"/>'),
|
||
// Sechs Punkte — das übliche Zeichen für „hier anfassen". Gefüllt,
|
||
// weil Kreise mit Radius 1 als Umriss nur ein Fleck wären.
|
||
griff: rahmen('<circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/>'
|
||
+ '<circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/>'
|
||
+ '<circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/>', true),
|
||
/** Filmstreifen — für eine Filmmediathek. */
|
||
filme: rahmen('<rect x="3" y="4" width="18" height="16" rx="2"/>'
|
||
+ '<path d="M7 4v16M17 4v16M3 9h4M3 15h4M17 9h4M17 15h4"/>'),
|
||
/** Bildschirm — für eine Serienmediathek. */
|
||
serien: rahmen('<rect x="2.5" y="4.5" width="19" height="12" rx="2"/><path d="M8 20h8"/>'),
|
||
verwaltung: rahmen('<circle cx="12" cy="12" r="3.2"/>'
|
||
+ '<path d="M19.4 14.5a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-1.8-.3 1.6 1.6 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.6 1.6 0 0 0-1-1.5 1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0 .3-1.8 1.6 1.6 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.6 1.6 0 0 0 1.5-1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9a1.6 1.6 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.6 1.6 0 0 0 1 1.5 1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8V9a1.6 1.6 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1z"/>'),
|
||
suche: rahmen('<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>'),
|
||
abspielen: rahmen('<path d="M8 5.5v13l11-6.5z"/>', true),
|
||
haken: rahmen('<path d="m5 12.5 4.5 4.5L19 7"/>'),
|
||
hakenKreis: rahmen('<circle cx="12" cy="12" r="9"/><path d="m8 12.3 2.8 2.7L16 9.6"/>'),
|
||
stift: rahmen('<path d="M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z"/>'),
|
||
download: rahmen('<path d="M12 3v12"/><path d="m7.5 11 4.5 4.5L16.5 11"/><path d="M4 20h16"/>'),
|
||
links: rahmen('<path d="M15 5 8 12l7 7"/>'),
|
||
rechts: rahmen('<path d="m9 5 7 7-7 7"/>'),
|
||
mehr: rahmen('<circle cx="12" cy="5.5" r="1.3" fill="currentColor" stroke="none"/>'
|
||
+ '<circle cx="12" cy="12" r="1.3" fill="currentColor" stroke="none"/>'
|
||
+ '<circle cx="12" cy="18.5" r="1.3" fill="currentColor" stroke="none"/>'),
|
||
abmelden: rahmen('<path d="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3"/>'
|
||
+ '<path d="M10 16.5 14.5 12 10 7.5"/><path d="M14.5 12H4"/>'),
|
||
schliessen: rahmen('<path d="M6 6l12 12M18 6 6 18"/>'),
|
||
stern: rahmen('<path d="m12 3.6 2.6 5.3 5.9.9-4.3 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8L3.5 9.8l5.9-.9z"/>'),
|
||
|
||
/* --- Spieler -------------------------------------------------------- */
|
||
pause: rahmen('<rect x="7" y="5" width="3.5" height="14" rx="1"/>'
|
||
+ '<rect x="13.5" y="5" width="3.5" height="14" rx="1"/>', true),
|
||
ton: rahmen('<path d="M4 9.5h3.5L12 5.5v13L7.5 14.5H4z"/>'
|
||
+ '<path d="M16 9a4.5 4.5 0 0 1 0 6"/><path d="M18.5 6.5a8 8 0 0 1 0 11"/>'),
|
||
stummGeschaltet: rahmen('<path d="M4 9.5h3.5L12 5.5v13L7.5 14.5H4z"/>'
|
||
+ '<path d="m16.5 9.5 5 5M21.5 9.5l-5 5"/>'),
|
||
vollbild: rahmen('<path d="M4 9V5h4"/><path d="M20 9V5h-4"/>'
|
||
+ '<path d="M4 15v4h4"/><path d="M20 15v4h-4"/>'),
|
||
untertitel: rahmen('<rect x="3" y="5" width="18" height="14" rx="2"/>'
|
||
+ '<path d="M6.5 14.5h4"/><path d="M13.5 14.5h4"/><path d="M6.5 10h11"/>'),
|
||
|
||
/** Eine Wellenform — Tonspur, und nicht zu verwechseln mit der Lautstärke. */
|
||
tonspur: rahmen('<path d="M4 10.5v3M8 7v10M12 4.5v15M16 8.5v7M20 11v2"/>'),
|
||
|
||
/** Schieberegler. **Vorher stand hier ein Zahnrad mit acht Strahlen** — das
|
||
* sah aus wie eine Sonne, und niemand vermutete die Bildqualität dahinter. */
|
||
regler: rahmen('<path d="M4 7h7M15 7h5M4 17h3M11 17h9"/>'
|
||
+ '<circle cx="13" cy="7" r="2.1"/><circle cx="9" cy="17" r="2.1"/>'),
|
||
|
||
/** Eine Uhr — für alles, was erst später gilt. */
|
||
uhr: rahmen('<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 1.8"/>'),
|
||
|
||
/** Den Spieler auf die Leiste unten schrumpfen. */
|
||
verkleinern: rahmen('<path d="M5 9.5 12 16l7-6.5"/>'),
|
||
|
||
/** Und wieder aufs ganze Bild. */
|
||
vergroessern: rahmen('<path d="m5 14.5 7-6.5 7 6.5"/>'),
|
||
/** Ein Mensch — Konten, Profil. */
|
||
person: rahmen('<circle cx="12" cy="8" r="3.6"/>'
|
||
+ '<path d="M4.8 20c.6-3.6 3.6-5.6 7.2-5.6s6.6 2 7.2 5.6"/>'),
|
||
/** Ein beschriebenes Blatt — die Tagesprotokolle. */
|
||
protokoll: rahmen('<path d="M6 3.5h8L19 8v12.5H6Z"/><path d="M14 3.5V8h5"/>'
|
||
+ '<path d="M9 12.5h7M9 16h7"/>'),
|
||
/** Schieberegler — Einstellungen, die eine Stufe haben. */
|
||
stufen: rahmen('<path d="M5 7h14M5 17h14"/><circle cx="9.5" cy="7" r="2.3"/>'
|
||
+ '<circle cx="15" cy="17" r="2.3"/>'),
|
||
} as const;
|
||
|
||
export type IconName = keyof typeof icons;
|
||
|
||
/** Ein Icon als Element — für alles, was nicht aus einer Zeichenkette gebaut wird. */
|
||
export function icon(name: IconName, klasse = 'icon'): HTMLElement {
|
||
const h = document.createElement('span');
|
||
h.className = klasse;
|
||
h.innerHTML = icons[name];
|
||
return h;
|
||
}
|