// Screens for Variant 1: Light Liquid Glass (Apple iOS 26 default) const lightTheme = { dark: false, bg: '#F2F2F7', bgGradient: 'linear-gradient(180deg, #EEF2F8 0%, #F5F2EC 60%, #F2F2F7 100%)', pageBg: '#F2F2F7', card: '#FFFFFF', cardAlt: '#F7F7FA', text: '#000000', textSec: 'rgba(60,60,67,0.6)', textTer: 'rgba(60,60,67,0.3)', accent: '#007AFF', accentSoft: 'rgba(0,122,255,0.12)', onAccent: '#FFFFFF', green: '#34C759', greenSoft: 'rgba(52,199,89,0.14)', orange: '#FF9500', orangeSoft: 'rgba(255,149,0,0.14)', red: '#FF3B30', redSoft: 'rgba(255,59,48,0.10)', purple: '#AF52DE', sep: 'rgba(60,60,67,0.12)', // Interaction surfaces chip: 'rgba(120,120,128,0.12)', // light gray pill / icon tile fill chipStrong: 'rgba(120,120,128,0.16)', // segmented track solidBg: '#1c1c1e', // primary dark buttons + selected chips solidFg: '#ffffff', // text on solidBg sheetBg: '#FFFFFF', inputBg: '#FFFFFF', inputBorder: 'rgba(60,60,67,0.18)', scrim: 'rgba(0,0,0,0.4)', grabber: 'rgba(60,60,67,0.2)', shadowCard: '0 1px 2px rgba(0,0,0,0.04), 0 6px 20px rgba(0,0,0,0.06)' }; const darkTheme = { dark: true, bg: '#000000', bgGradient: 'linear-gradient(180deg, #0a0a0c 0%, #000000 60%, #000000 100%)', pageBg: '#000000', card: '#1C1C1E', cardAlt: '#2C2C2E', text: '#FFFFFF', textSec: 'rgba(235,235,245,0.62)', textTer: 'rgba(235,235,245,0.32)', accent: '#0A84FF', accentSoft: 'rgba(10,132,255,0.22)', onAccent: '#FFFFFF', green: '#30D158', greenSoft: 'rgba(48,209,88,0.20)', orange: '#FF9F0A', orangeSoft: 'rgba(255,159,10,0.20)', red: '#FF453A', redSoft: 'rgba(255,69,58,0.20)', purple: '#BF5AF2', sep: 'rgba(84,84,88,0.55)', chip: 'rgba(120,120,128,0.28)', chipStrong: 'rgba(120,120,128,0.36)', solidBg: '#48484A', solidFg: '#ffffff', sheetBg: '#1C1C1E', inputBg: '#2C2C2E', inputBorder: 'rgba(84,84,88,0.6)', scrim: 'rgba(0,0,0,0.6)', grabber: 'rgba(235,235,245,0.3)', shadowCard: '0 1px 2px rgba(0,0,0,0.5), 0 8px 24px rgba(0,0,0,0.4)' }; // Theme context — every screen reads its palette via useTheme() const ThemeContext = React.createContext(lightTheme); const useTheme = () => React.useContext(ThemeContext); // Tiny SF-style icon factory const Icon = ({ d, size = 17, color = 'currentColor', sw = 1.8, fill = false }) => ; const icons = { trailer: 'M3 16h2m14 0h2M5 16a2 2 0 104 0M15 16a2 2 0 104 0M3 16V8a1 1 0 011-1h14a2 2 0 012 2v7M3 16h12', speaker: 'M8 4h8a1 1 0 011 1v14a1 1 0 01-1 1H8a1 1 0 01-1-1V5a1 1 0 011-1zM12 9.5a2 2 0 100 0.001M12 15.5a3 3 0 100 0.001', mic: 'M12 2a3 3 0 00-3 3v6a3 3 0 006 0V5a3 3 0 00-3-3zM5 11a7 7 0 0014 0M12 18v3M9 21h6', cable: 'M4 12a4 4 0 014-4h2v8H8a4 4 0 01-4-4zM20 12a4 4 0 01-4 4h-2V8h2a4 4 0 014 4zM10 12h4', cal: 'M4 6a2 2 0 012-2h12a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM4 10h16M8 2v4M16 2v4', euro: 'M18 7a6 6 0 00-9 0M18 17a6 6 0 01-9 0M5 10h10M5 14h10', doc: 'M6 2h8l6 6v12a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2zM14 2v6h6M9 14h6M9 18h4', bell: 'M12 2a6 6 0 016 6v4l2 3H4l2-3V8a6 6 0 016-6zM10 19a2 2 0 004 0', plus: 'M12 5v14M5 12h14', search: 'M11 4a7 7 0 100 14 7 7 0 000-14zM21 21l-5.2-5.2', filter: 'M4 6h16M7 12h10M10 18h4', back: 'M15 6l-6 6 6 6', chev: 'M9 6l6 6-6 6', more: 'M5 12h.01M12 12h.01M19 12h.01', pin: 'M12 2v8M12 10l-4 4M12 10l4 4M9 14h6l-2 7h-2l-2-7z', camera: 'M4 8a2 2 0 012-2h2l2-2h4l2 2h2a2 2 0 012 2v10a2 2 0 01-2 2H6a2 2 0 01-2-2V8zM12 17a4 4 0 100-8 4 4 0 000 8z', check: 'M5 12l5 5L20 7', arrow: 'M5 12h14M13 5l7 7-7 7', sound: 'M3 9v6h4l5 4V5L7 9H3zM16 8a5 5 0 010 8M19 5a9 9 0 010 14', star: 'M12 2l3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1L12 2z', download: 'M12 4v12M6 10l6 6 6-6M4 20h16', edit: 'M4 20h4l11-11-4-4L4 16v4zM14 5l4 4' }; // ───────────────────────────────────────────────────────────── // Equipment glyphs — solid, filled (like the user's reference speaker) // ───────────────────────────────────────────────────────────── function EqGlyph({ kind = 'speaker', size = 26, color = '#1c1c1e' }) { const common = { width: size, height: size, viewBox: '0 0 24 24' }; switch (kind) { case 'speaker': return ( ); case 'mic': return ( ); case 'mixer': return ( ); case 'trailer': return ( ); case 'light': return ( ); case 'cable': return ( ); case 'case': return ( ); case 'light2': case 'par': return ( ); default: return ; } } // ───────────────────────────────────────────────────────────── // Themed PNG icon — swaps between light/dark image based on theme. // Use for richly-colored / detailed icons that don't fit the stroke pattern. // ───────────────────────────────────────────────────────────── function ImgIcon({ light, dark, size = 22, alt = '', style }) { const t = useTheme(); const src = t && t.dark ? dark : light; return ( {alt}); } // Canonical themed icon paths for the new equipment + logistik glyphs. // Per user request: use the colored/filled (dark) version in BOTH light and dark modes. const IMG_ICONS = { equipment: { light: 'assets/icon-equipment-dark.png', dark: 'assets/icon-equipment-dark.png' }, truck: { light: 'assets/icon-truck-dark.png', dark: 'assets/icon-truck-dark.png' }, // Line-art glyphs (black on transparent) — invert in dark mode. warenhaus: { light: 'assets/icon-warenhaus.png', dark: 'assets/icon-warenhaus.png', invertDark: true }, lager: { light: 'assets/icon-lager.png', dark: 'assets/icon-lager.png', invertDark: true }, lieferwagen: { light: 'assets/icon-lieferwagen.png', dark: 'assets/icon-lieferwagen.png', invertDark: true } }; const EQ_GLYPHS = ['speaker', 'mic', 'mixer', 'trailer', 'light', 'cable', 'case', 'par']; const EQ_EMOJIS = ['🔊', '🎤', '🎚️', '🎛️', '🚚', '🚐', '💡', '🔌', '📦', '🎸', '🥁', '⚡']; // Equipment avatar — photo > emoji > glyph, on a soft tile function EqAvatar({ item = {}, size = 54, radius = 12 }) { const t = useTheme(); const tileFill = t.dark ? '#2C2C2E' : '#EFEFF4'; const glyphInk = t.dark ? 'rgba(235,235,245,0.85)' : '#1c1c1e'; const tile = { width: size, height: size, borderRadius: radius, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }; if (item.photo) { return (
); } if (item.emoji) { return
{item.emoji}
; } return
; } // Downscale + compress an uploaded image to a data URL (keeps localStorage small) function compressImage(file, maxSize = 480) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = (e) => { const img = new Image(); img.onerror = reject; img.onload = () => { let { width, height } = img; const scale = Math.min(1, maxSize / Math.max(width, height)); width = Math.max(1, Math.round(width * scale)); height = Math.max(1, Math.round(height * scale)); const canvas = document.createElement('canvas'); canvas.width = width;canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); try {resolve(canvas.toDataURL('image/jpeg', 0.82));} catch (err) {reject(err);} }; img.src = e.target.result; }; reader.readAsDataURL(file); }); } // ───────────────────────────────────────────────────────────── // Liquid Glass primitives // ───────────────────────────────────────────────────────────── function Glass({ children, dark = false, radius = 22, style = {}, padding = 0, tint }) { return (
{/* Glossy specular sheen — the "liquid" highlight running across the top */}
{children}
); } function Phone({ children, dark = false, bg }) { // Document-flow wrapper: grows with its content so the PAGE scrolls // (no fixed height, no inner overflow clip). Stays position:relative as a // positioning context for in-flow decorations; full-screen overlays use // position:fixed so they pin to the viewport regardless of scroll. return (
{children}
); } // ───────────────────────────────────────────────────────────── // Tab bar (liquid glass floating) // ───────────────────────────────────────────────────────────── function TabBar({ active = 'home', dark = false, theme }) { const t = theme || lightTheme; const items = [ { id: 'home', d: 'M3 12L12 3l9 9M5 10v10h14V10' }, { id: 'cal', d: icons.cal }, { id: 'add', d: icons.plus, isAdd: true }, { id: 'eur', d: icons.euro }, { id: 'doc', d: icons.doc }]; return (
{items.map((i) => { if (i.isAdd) { return (
); } const isActive = i.id === active; return (
); })}
); } // ───────────────────────────────────────────────────────────── // 1. DASHBOARD // ───────────────────────────────────────────────────────────── function DashboardLight() { const t = lightTheme; const equipment = [ { name: 'JBL EON 712', cat: 'Aktivlautsprecher', status: 'Vermietet', until: 'bis Sa', color: t.green, ic: icons.speaker, photo: 'linear-gradient(135deg, #1c1c1e 0%, #3a3a3c 100%)' }, { name: 'Anhänger 750kg', cat: 'Kastenanhänger', status: 'Verfügbar', until: '', color: t.textSec, ic: icons.trailer, photo: 'linear-gradient(135deg, #c4d1de 0%, #8a9bb0 100%)' }, { name: 'Shure SM58 (×4)', cat: 'Mikrofon-Set', status: 'Vermietet', until: 'bis Mi', color: t.green, ic: icons.mic, photo: 'linear-gradient(135deg, #2a2a2a 0%, #555 100%)' }, { name: 'Yamaha MG10XU', cat: 'Mischpult', status: 'Wartung', until: '', color: t.orange, ic: icons.cable, photo: 'linear-gradient(135deg, #232c3a 0%, #4a5568 100%)' }]; return (
{/* Greeting + title */}
Mittwoch, 20. Mai
Übersicht
{/* Quick stats */}
Aktive Mieten
7
+2 diese Woche
Umsatz Mai
2.480 €
+18%
{/* Categories */}
Mein Equipment
Alle 23
{/* Category chips */}
{[['Alle', true], ['Tontechnik', false], ['Anhänger', false], ['Kabel', false]].map(([label, sel]) =>
{label}
)}
{/* Equipment cards */}
{equipment.map((e) =>
{e.name}
{e.cat}
{e.status}
{e.until &&
{e.until}
}
)}
); } // ───────────────────────────────────────────────────────────── // 2. KALENDER // ───────────────────────────────────────────────────────────── function KalenderLight() { const t = lightTheme; const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; const items = [ { name: 'JBL EON 712', cat: 'Aktivlautsprecher', start: 1, span: 4, color: '#007AFF', who: 'Hochzeit Müller' }, { name: 'Shure SM58 ×4', cat: 'Mikrofon-Set', start: 2, span: 2, color: '#34C759', who: 'Vereinsfest' }, { name: 'Anhänger 750kg', cat: 'Kastenanhänger', start: 0, span: 1, color: '#FF9500', who: 'P. Schmidt' }, { name: 'Yamaha MG10XU', cat: 'Mischpult', start: 3, span: 3, color: '#AF52DE', who: 'Hochzeit Müller' }, { name: 'DJ Kabelset', cat: 'Zubehör', start: 4, span: 3, color: '#FF3B30', who: 'Geburtstag K.' }]; return (
Kalenderwoche 21
Mai 2026
Woche
{/* Day header */}
{days.map((d, i) =>
{d}
{18 + i}
)}
{/* Bookings */}
{items.map((it, idx) => { const cellW = (328 - 6 * 4) / 7; const left = it.start * (cellW + 4); const width = it.span * cellW + (it.span - 1) * 4; return (
{it.name}
{it.who}
); })}
{/* Today list */}
Heute · Mi 20.5
Abholung · JBL EON 712
14:00 · Hochzeit Müller
Rückgabe · Anhänger 750kg
17:30 · P. Schmidt
); } // ───────────────────────────────────────────────────────────── // 3. OBJEKT DETAIL // ───────────────────────────────────────────────────────────── function DetailLight() { const t = lightTheme; return ( {/* Hero photo area */}
{/* speaker silhouette */}
JBL
{/* Nav glass pills */}
{/* Photo counter */}
1 / 6
Aktivlautsprecher
● VERMIETET
JBL EON 712
1.300 W · 12″ · Bluetooth
{/* Quick stats grid */}
{[ ['Tagespreis', '45 €'], ['Auslastung', '78%'], ['Einsätze', '42']]. map(([k, v]) =>
{v}
{k}
)}
{/* Current rental */}
Aktuell vermietet
Hochzeit Müller
18. – 22. Mai · 4 Tage
180 €
Verlauf
{[['Vereinsfest TSV', 'Apr · 2 Tage', '90 €'], ['Konzert Open Air', 'Mär · 3 Tage', '135 €']].map(([w, d, p]) =>
{w}
{d}
{p}
)}
); } // ───────────────────────────────────────────────────────────── // 4. FINANZEN // ───────────────────────────────────────────────────────────── function FinanzenLight() { const t = lightTheme; const bars = [62, 78, 45, 88, 95, 110, 132, 98]; const months = ['O', 'N', 'D', 'J', 'F', 'M', 'A', 'M']; const max = Math.max(...bars); return (
Mai 2026
Finanzen
{/* Hero number */}
Umsatz dieses Jahr
14.820 €
+24%
ggü. Vorjahr
{/* Bar chart */}
{bars.map((v, i) =>
{months[i]}
)}
{/* Split */}
Offen
420 €
3 Rechnungen
Bezahlt
2.060 €
12 Rechnungen
{/* Transactions */}
Letzte Zahlungen
Alle
{[ ['Hochzeit Müller', 'JBL EON · 4 Tage', '180 €', t.green, 'Bezahlt'], ['Vereinsfest TSV', 'SM58 Set · 2 Tage', '60 €', t.green, 'Bezahlt'], ['Geburtstag K.', 'Anhänger · 1 Tag', '40 €', t.orange, 'Offen'], ['Open Air', 'Komplett-Set', '320 €', t.green, 'Bezahlt']]. map(([w, c, p, col, st], i, arr) =>
{w}
{c}
{p}
{st}
)}
); } // ───────────────────────────────────────────────────────────── // 5. DOKUMENTE / VERTRÄGE // ───────────────────────────────────────────────────────────── function DokumenteLight() { const t = lightTheme; return (
23 Dateien · 4 Verträge offen
Dokumente
{/* Search */}
Suchen
{/* Folder grid */}
{[ ['Mietverträge', 12, t.accent], ['Übergabe-Protokolle', 8, t.green], ['Rechnungen', 14, t.orange], ['Versicherungen', 3, '#AF52DE']]. map(([n, c, col]) =>
{n}
{c} Dateien
)}
{/* Pending action */}
Zu unterschreiben
Wartet auf Signatur
Mietvertrag · JBL EON 712
Hochzeit Müller · 18.–22. Mai · 180 €
Senden
Vorschau
{/* Recent files */}
Zuletzt
{[ ['Übergabeprotokoll_JBL.pdf', 'Heute · 9:42', 'PDF'], ['Rechnung_2026-042.pdf', 'Gestern', 'PDF'], ['Foto_Anhaenger_Schaden.heic', 'Mo, 18.5', 'IMG'], ['Versicherung_Anhaenger.pdf', '15.5.', 'PDF']]. map(([n, d, k], i, arr) =>
{k}
{n}
{d}
)}
); } Object.assign(window, { lightTheme, darkTheme, ThemeContext, useTheme, icons, Icon, ImgIcon, IMG_ICONS, Glass, Phone, TabBar, EqAvatar, EqGlyph, DashboardLight, KalenderLight, DetailLight, FinanzenLight, DokumenteLight });