Kundenansicht neu: Wasser-Leiste breiter mit Wörtern, Unterseiten im Vollbild

Wunsch vom 29.10. nach dem ersten Blick am iPhone:
- Die Leiste nutzt die Breite (12 px Rand, höchstens 480 px), Symbol über
  dem Wort, und steht ganz über dem Wisch-Balken des iPhones
  (Safe-Area plus 6 px statt minus 10 px).
- Unterseiten öffnen sich ganz und werden mit Unschärfe eingeblendet statt
  hochzufahren. Oben links die Überschrift, oben rechts „Zurück“ als
  Glasknopf, der beim Scrollen stehen bleibt (neuer Platz
  #kn-blatt-zurueck).
- Reiter einer Funktion stehen unten in derselben Wasser-Leiste (Art
  'reiter', Kurzwörter, kleinere Schrift wenn es eng wird), ohne Zurück.
  Ohne Reiter bleibt unten nichts. Die alte Leiste gibt beim Neuaufbau
  ihren WebGL-Kontext frei.

Glas und Unschärfe stehen weiter allein in wasser-leiste.css; huelle.css
bleibt ohne Glas. Desktop unverändert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-29 18:09:42 +00:00
1 parent c997703251
commit 1f350ca432
10 files changed
+147 -95

No files matched your search

+23 -12
View File
@@ -3,7 +3,8 @@
Bereich, reißt bei langen Sprüngen in Tröpfchen und fließt mit einer Welle wieder zusammen; jeder Tipp schickt eine Welle durch die Pille, jedes
Symbol spielt eine kleine Szene. Das Wasser zeichnet ein kleiner WebGL-Shader (nur in der Pille). Er ruht, solange die Pille nicht zu sehen ist,
und kommt einige Sekunden nach der letzten Bedienung zur Ruhe (Akku). Ohne WebGL zeigt die Pille einen CSS-Tropfen (wasser-leiste.css).
Rollen wie die Fahrstuhl-Zeile davor: tablist mit tab, aria-selected und aria-current; Zähler als .kn-zahl am Symbol, Beschriftung per aria-label.
Zwei Arten: 'bereiche' (Hauptleiste: Symbol über dem Wort, Zähler als .kn-zahl am Symbol) und 'reiter' (Reiter einer Unterseite: nur Wörter,
Kurzwort `kurz` falls vorhanden). Rollen wie die Fahrstuhl-Zeile davor: tablist mit tab, aria-selected und aria-current, Beschriftung per aria-label.
Die Rechenfunktionen (Feder, Tröpfchen, Wellen) sind rein und werden mit node --test geprüft; ohne Browser baut das Modul nur die Knöpfe. */
import { h } from './dom.js'
@@ -291,15 +292,17 @@ const beschriftung = (e) => {
}
/**
* Wasser-Leiste bauen. Eintrag { key, label, symbol?, fx?, einheit?, ausrufText? }; symbol und fx folgen ohne Angabe dem key (SYMBOLE, SZENEN).
* bei(key) wird bei jedem Tipp gerufen, auch auf den aktiven Bereich (die Hülle scrollt dann nach oben).
* Liefert das Element mit setzeAktiv(key), setzeAnzeige(key, { zahl, ausruf }), messen() und beende().
* Wasser-Leiste bauen. Eintrag { key, label, kurz?, symbol?, fx?, einheit?, ausrufText? }. Art 'bereiche': symbol und fx folgen ohne Angabe dem key
* (SYMBOLE, SZENEN). Art 'reiter': kein Symbol, keine Szene, sichtbar ist `kurz` (sonst label), vorgelesen label.
* bei(key) wird bei jedem Tipp gerufen, auch auf den aktiven Eintrag (die Hülle scrollt dann nach oben).
* Liefert das Element mit setzeAktiv(key), setzeAnzeige(key, { zahl, ausruf }), messen() und beende() (gibt auch den WebGL-Kontext frei).
*/
export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche', bei = () => {}, warte = (fn, ms) => setTimeout(fn, ms) }) {
export function baueWasserLeiste({ eintraege, aktiv = '', art = 'bereiche', ariaLabel = 'Bereiche', bei = () => {}, warte = (fn, ms) => setTimeout(fn, ms) }) {
const win = globalThis.window
const doc = globalThis.document
const s = neuerZustand()
const liste = eintraege.map((e) => ({ ...e, fx: e.fx ?? SZENEN[e.key] ?? 'pop', zahl: 0, ausruf: false }))
const bereiche = art === 'bereiche'
const liste = eintraege.map((e) => ({ ...e, fx: e.fx ?? (bereiche ? SZENEN[e.key] ?? 'pop' : 'none'), zahl: 0, ausruf: false }))
let aktuell = aktiv
let glc = null
let glVersucht = false // WebGL erst, wenn die Pille zu sehen ist (am Desktop nie)
@@ -308,15 +311,15 @@ export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche'
const jetzt = () => (globalThis.performance?.now ? globalThis.performance.now() : Date.now())
for (const e of liste) {
e.ico = h('span', { class: 'kn-wl-ico' }, baueSymbol(e.symbol ?? e.key, doc))
e.ico = bereiche ? h('span', { class: 'kn-wl-ico' }, baueSymbol(e.symbol ?? e.key, doc)) : null
e.tab = h('button', { class: 'kn-wl-item', role: 'tab', type: 'button', 'data-key': e.key, 'aria-selected': 'false', 'aria-label': e.label, tabindex: '-1', on: { click: () => waehle(e.key) } },
e.ico, h('span', { class: 'kn-wl-label' }, e.label))
e.ico, h('span', { class: 'kn-wl-label' }, e.kurz ?? e.label))
}
const tabs = liste.map((e) => e.tab)
const canvas = h('canvas', { class: 'kn-wl-wasser', 'aria-hidden': 'true' })
const tropfen = h('span', { class: 'kn-wl-tropfen', 'aria-hidden': 'true' })
const wurzel = h('div', {
class: 'kn-wl', role: 'tablist', 'aria-label': ariaLabel, 'data-gl': 'on', 'data-brechung': 'aus',
class: 'kn-wl', role: 'tablist', 'aria-label': ariaLabel, 'data-art': bereiche ? 'bereiche' : 'reiter', 'data-gl': 'on', 'data-brechung': 'aus',
on: { keydown: beiTaste, pointermove: beiMaus, pointerleave: () => { s.maus.on = false; schleife() }, pointerdown: beiDruck },
}, baueFilter(doc), h('span', { class: 'kn-wl-glas', 'aria-hidden': 'true' }), canvas, tropfen, tabs)
@@ -370,6 +373,10 @@ export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche'
function messen() {
if (!(wurzel.offsetWidth > 0)) return // nicht zu sehen (Desktop, Tastatur offen) oder ohne Browser
if (!glVersucht) starteGL()
if (!bereiche) { // passen die Wörter nicht, wird die Schrift kleiner (wie schriftStufe in reiter.js)
wurzel.removeAttribute('data-eng')
if (wurzel.scrollWidth > wurzel.clientWidth + 1) wurzel.setAttribute('data-eng', '')
}
s.w = wurzel.offsetWidth
s.h = wurzel.offsetHeight
const el = liste.find((e) => e.key === aktuell)?.tab
@@ -398,13 +405,14 @@ export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche'
wecke()
}
function symbolMitte(i) {
const b = liste[i]?.ico?.getBoundingClientRect?.()
const b = (liste[i]?.ico ?? liste[i]?.tab)?.getBoundingClientRect?.()
return b ? lokal(b.left + b.width / 2, b.top + b.height / 2) : null
}
/** Szene des Symbols (neu) starten; die anderen Symbole kehren in die Ruhe zurück. */
function spiele(i) {
liste.forEach((e, j) => { if (j !== i) e.ico.removeAttribute('data-fx') })
liste.forEach((e, j) => { if (j !== i) e.ico?.removeAttribute('data-fx') })
const e = liste[i]
if (!e.ico) return
e.ico.removeAttribute('data-fx')
if (e.fx === 'none') return
void e.ico.offsetWidth // Stil auflösen, damit die Animation von vorn beginnt
@@ -467,7 +475,7 @@ export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche'
function setzeAnzeige(key, { zahl = 0, ausruf = false } = {}) {
const e = liste.find((x) => x.key === key)
if (!e) return
if (!e?.ico) return
e.zahl = zahl
e.ausruf = ausruf
const text = anzeigeText({ zahl, ausruf })
@@ -522,6 +530,9 @@ export function baueWasserLeiste({ eintraege, aktiv = '', ariaLabel = 'Bereiche'
for (const fn of aufraeumen.splice(0)) fn()
if (raf) win?.cancelAnimationFrame?.(raf)
raf = 0
glc?.gl.getExtension('WEBGL_lose_context')?.loseContext() // Browser halten nur wenige Kontexte: jede Unterseite baut ihre Reiter neu
glc = null
glVersucht = true
}
wurzel.setzeAktiv = setzeAktiv