// 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 (
);
}
// 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 */}
{/* Category chips */}
{[['Alle', true], ['Tontechnik', false], ['Anhänger', false], ['Kabel', false]].map(([label, sel]) =>
{label}
)}
{/* Equipment cards */}
{equipment.map((e) =>
{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 */}
{/* 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 (
);
})}
{/* 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 */}
{/* Nav glass pills */}
{/* Photo counter */}
Aktivlautsprecher
● VERMIETET
JBL EON 712
1.300 W · 12″ · Bluetooth
{/* Quick stats grid */}
{[
['Tagespreis', '45 €'],
['Auslastung', '78%'],
['Einsätze', '42']].
map(([k, v]) =>
)}
{/* 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]) =>
)}
);
}
// ─────────────────────────────────────────────────────────────
// 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 (
{/* Hero number */}
Umsatz dieses Jahr
14.820 €
{/* Bar chart */}
{/* Split */}
Bezahlt
2.060 €
12 Rechnungen
{/* Transactions */}
{[
['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) =>
)}
);
}
// ─────────────────────────────────────────────────────────────
// 5. DOKUMENTE / VERTRÄGE
// ─────────────────────────────────────────────────────────────
function DokumenteLight() {
const t = lightTheme;
return (
23 Dateien · 4 Verträge offen
Dokumente
{/* Search */}
{/* Folder grid */}
{[
['Mietverträge', 12, t.accent],
['Übergabe-Protokolle', 8, t.green],
['Rechnungen', 14, t.orange],
['Versicherungen', 3, '#AF52DE']].
map(([n, c, col]) =>
)}
{/* Pending action */}
Zu unterschreiben
Mietvertrag · JBL EON 712
Hochzeit Müller · 18.–22. Mai · 180 €
{/* 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) =>
)}
);
}
Object.assign(window, {
lightTheme, darkTheme, ThemeContext, useTheme, icons, Icon, ImgIcon, IMG_ICONS, Glass, Phone, TabBar, EqAvatar, EqGlyph,
DashboardLight, KalenderLight, DetailLight, FinanzenLight, DokumenteLight
});