/* VORÜBERGEHENDE MESSUNG (2026-10-29), wird nach der Klärung wieder entfernt: Am iPhone (App vom Home-Bildschirm) steht die Leiste zu hoch. Schickt Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area, vh-Einheiten, Lage der Leiste) an /api/diag/hoehe; der Server schreibt sie ins Log (server/routes/diagHoehe.js). Keine Inhalte, keine Eingaben. Höchstens MAX Meldungen je Seite. */ const MAX = 80 const ZIEL = '/api/diag/hoehe' const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null) export function starteDiagHoehe({ win = window, doc = document }) { const t0 = Date.now() let n = 0 const probe = (stil) => { const el = doc.createElement('div') el.setAttribute('aria-hidden', 'true') el.style.cssText = `position:fixed;left:0;top:0;width:0;visibility:hidden;pointer-events:none;${stil}` doc.body.append(el) return el } const sa = probe('height:0;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)') const vh = probe('height:100vh'); const dvh = probe('height:100dvh'); const svh = probe('height:100svh'); const lvh = probe('height:100lvh') const unten = probe('top:auto;bottom:0;height:1px') // wo „bottom: 0“ eines festen Elements wirklich landet function messe(art, extra) { const vv = win.visualViewport const cs = win.getComputedStyle(sa) const leiste = doc.getElementById('kn-leiste')?.getBoundingClientRect() const huelle = doc.querySelector('.kn-huelle')?.getBoundingClientRect() const aktiv = doc.activeElement return { art, ms: Date.now() - t0, hash: win.location.hash, iw: win.innerWidth, ih: win.innerHeight, ow: win.outerWidth, oh: win.outerHeight, cw: doc.documentElement.clientWidth, ch: doc.documentElement.clientHeight, dh: doc.documentElement.scrollHeight, sw: win.screen?.width, sh: win.screen?.height, sah: win.screen?.availHeight, dpr: win.devicePixelRatio, vv: vv ? [rund(vv.width), rund(vv.height), rund(vv.offsetTop), rund(vv.pageTop), rund(vv.scale)] : null, sy: rund(win.scrollY), sa: [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map((p) => rund(parseFloat(p))), vh: [vh.offsetHeight, dvh.offsetHeight, svh.offsetHeight, lvh.offsetHeight], unten: rund(unten.getBoundingClientRect().bottom), leiste: leiste ? [rund(leiste.top), rund(leiste.bottom), rund(leiste.height)] : null, huelle: huelle ? [rund(huelle.top), rund(huelle.height)] : null, app: Boolean(win.matchMedia?.('(display-mode: standalone)').matches), ns: win.navigator?.standalone ?? null, ori: win.screen?.orientation?.type ?? null, fokus: aktiv && aktiv !== doc.body ? aktiv.tagName : null, tast: doc.body.classList.contains('kn-tastatur'), ...(n === 0 ? { ua: String(win.navigator?.userAgent ?? '').slice(0, 200) } : {}), ...(extra ?? {}), } } function sende(art, extra) { if (n >= MAX) return let daten try { daten = messe(art, extra) } catch (fehler) { daten = { art, fehler: String(fehler?.message ?? fehler).slice(0, 200) } } n += 1 try { win.fetch(ZIEL, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(daten), keepalive: true }).catch(() => {}) } catch { /* Messung darf nie stören */ } } const spaeter = (fn, ms) => win.setTimeout(fn, ms) const entprellt = (art) => { let id = null; return () => { if (id) win.clearTimeout(id); id = spaeter(() => { id = null; sende(art) }, 250) } } sende('start') spaeter(() => sende('start+1s'), 1000) spaeter(() => sende('start+4s'), 4000) win.addEventListener('resize', entprellt('resize')) win.visualViewport?.addEventListener('resize', entprellt('vv-resize')) win.visualViewport?.addEventListener('scroll', entprellt('vv-scroll')) doc.addEventListener('focusin', (ev) => { if (ev.target?.matches?.('textarea, input')) sende('fokus') }) doc.addEventListener('focusout', (ev) => { if (ev.target?.matches?.('textarea, input')) { spaeter(() => sende('unfokus+300ms'), 300); spaeter(() => sende('unfokus+1s'), 1000) } }) doc.addEventListener('visibilitychange', () => { if (doc.visibilityState === 'visible') { sende('sichtbar'); spaeter(() => sende('sichtbar+1s'), 1000) } }) win.addEventListener('hashchange', () => spaeter(() => sende('route'), 600)) win.addEventListener('pageshow', (ev) => { if (ev.persisted) sende('pageshow') }) return { sende } }