/* Geraeterahmen fuer die Heatmap-Ansicht (Analyse): MacBook Pro 14", iPad und iPhone 16 Pro. MacBook und iPhone stammen aus den Rahmen der neuen WEBklar-Website (device-mockups, KNSONWS/homepage), das iPad ist im gleichen Stil nachgebaut. Styles: geraete.css. Reine Zeichenketten ohne DOM, damit Node-Tests sie pruefen koennen. */ // Je Geraeteklasse (wie vpClass in analytics/client/core.js: m < 768 <= t < 1024 <= d) die Bildschirmflaeche des Rahmens (canvas) // und darin die Website (site: unter der Statusleiste, in echter Breite). export const HEAT_DEVICES = { d: { label: 'Computer', frame: 'macbook', canvas: [1512, 893], site: { top: 0, width: 1512, height: 893 } }, t: { label: 'Tablet', frame: 'ipad', canvas: [820, 1180], site: { top: 24, width: 820, height: 1156 } }, m: { label: 'Handy', frame: 'iphone', canvas: [402, 874], site: { top: 59, width: 402, height: 815 } }, } const esc = (v) => String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])) /** Uhrzeit wie in Apple-Werbebildern (9:41), Datum von heute: { mac: 'Fr. 2. Okt. 9:41', ipad: '9:41 Fr. 2. Okt.' } */ export function deviceClock(date = new Date()) { // "Fr., 2. Okt." => "Fr. 2. Okt." (wie die macOS-Menueleiste) const day = new Intl.DateTimeFormat('de-DE', { weekday: 'short', day: 'numeric', month: 'short' }).format(date).replace(',', '') return { mac: `${day} 9:41`, ipad: `9:41 ${day}` } } /** Untertitel unter einem Geraet je Zustand (loading | ready | error | timeout). */ export function heatCaption(state, count = 0, format = (n) => String(n)) { if (state === 'ready') { if (!(count > 0)) return 'Noch keine Klicks auf dieser Seite (letzte 30 Tage)' return `${format(count)} ${count === 1 ? 'Klick' : 'Klicks'} · letzte 30 Tage` } if (state === 'error') return 'Die Heatmap konnte nicht geladen werden.' if (state === 'timeout') return 'Deine Website lässt sich hier nicht anzeigen.' return 'Lädt …' } /** Adresszeile im MacBook-Safari: Host + Pfad, ohne https:// und ohne Parameter. */ export function addressOf(url) { try { const u = new URL(url) return u.host + (u.pathname === '/' ? '' : u.pathname) } catch { return '' } } /** Website im Rahmen: iframe in echter Breite unter der Statusleiste. Sandbox ohne Formulare, Pop-ups und Navigation des Portals. */ function siteHtml(vp, src, title) { const s = HEAT_DEVICES[vp].site return `
` } function canvasHtml(vp, inner) { const [w, h] = HEAT_DEVICES[vp].canvas return `