Files
Viz/public/index.html
T

634 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Viz — Audio Visualizer Studio für DaVinci Resolve</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@700&family=Inter:wght@300;400;500;600;700;800&family=Montserrat:wght@400;600;800&family=Orbitron:wght@600;800;900&family=Outfit:wght@400;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app-layout">
<!-- Header -->
<header class="app-header">
<div class="brand">
<div class="brand-logo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 12a4 4 0 1 0 8 0 4 4 0 1 0-8 0"></path>
<path d="M12 2v2"></path>
<path d="M12 20v2"></path>
<path d="M2 12h2"></path>
<path d="M20 12h2"></path>
</svg>
</div>
<div class="brand-text">
<h1>VIZ<span>Studio</span></h1>
<span class="badge">DaVinci Alpha Edition</span>
</div>
</div>
<div class="header-actions">
<!-- Quick Presets -->
<div class="quick-preset-picker">
<label for="quickPresetSelect">⚡ Quick Preset:</label>
<select id="quickPresetSelect">
<option value="trap_nation">🟣 Trap Nation Classic</option>
<option value="cyberpunk">🌌 Cyberpunk Neon</option>
<option value="minimal_clean">⚪ Minimal Clean</option>
<option value="fire_amber">🔥 Fire & Amber</option>
<option value="matrix_glitch">🟢 Matrix Glitch</option>
<option value="deep_violet">🔮 Deep Violet Dream</option>
</select>
</div>
<button id="headerExportBtn" class="btn btn-primary btn-glow">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
Exportieren
</button>
</div>
</header>
<!-- Main Workspace -->
<main class="workspace">
<!-- Left: Stage & Player -->
<section class="stage-section">
<!-- Aspect & Tools Bar -->
<div class="stage-toolbar">
<div class="aspect-switchers">
<button class="aspect-btn active" data-aspect="16:9" title="16:9 YouTube Standard">
<span class="icon">📺</span> 16:9
</button>
<button class="aspect-btn" data-aspect="9:16" title="9:16 Shorts, TikTok & Reels">
<span class="icon">📱</span> 9:16
</button>
<button class="aspect-btn" data-aspect="1:1" title="1:1 Quadratisch (Instagram Feed)">
<span class="icon">🔲</span> 1:1
</button>
</div>
<div class="stage-toggles">
<label class="toggle-chip" title="Zeigt TikTok & Shorts UI-Überlagerung für exaktes Platzieren">
<input type="checkbox" id="toggleSafeZone">
<span>Safe-Zone Overlay</span>
</label>
<label class="toggle-chip" title="Transparenz-Gitter im Hintergrund anzeigen">
<input type="checkbox" id="toggleAlphaGrid" checked>
<span>Alpha-Gitter</span>
</label>
</div>
</div>
<!-- Canvas Viewport -->
<div class="canvas-viewport" id="canvasViewport">
<div class="canvas-wrapper aspect-16-9" id="canvasWrapper">
<canvas id="vizCanvas" width="1920" height="1080"></canvas>
<!-- Safe Zone Overlay (Visual helper for Shorts UI) -->
<div class="safe-zone-overlay hidden" id="safeZoneOverlay">
<div class="safe-zone-box">
<div class="safe-zone-title">Shorts / TikTok Safe Area</div>
<div class="safe-zone-mock-ui">
<div class="mock-right-buttons">
<div class="mock-btn">❤️</div>
<div class="mock-btn">💬</div>
<div class="mock-btn">↗️</div>
<div class="mock-disc">💿</div>
</div>
<div class="mock-bottom-bar">
<div class="mock-channel">@DeinChannel</div>
<div class="mock-song">🎵 Song Title • Original Audio</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Audio Player Controls -->
<div class="player-bar">
<div class="player-main-controls">
<button id="btnPlayPause" class="player-btn-play" title="Play / Pause (Leertaste)">
<svg id="iconPlay" viewBox="0 0 24 24" width="22" height="22" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
<svg id="iconPause" class="hidden" viewBox="0 0 24 24" width="22" height="22" fill="currentColor"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
</button>
<button id="btnRewind" class="player-btn-sub" title="-5 Sekunden">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg>
</button>
<button id="btnForward" class="player-btn-sub" title="+5 Sekunden">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg>
</button>
<div class="time-display">
<span id="currentTime">00:00</span> / <span id="durationTime">00:00</span>
</div>
</div>
<!-- Waveform / Timeline Scrubber -->
<div class="timeline-container">
<input type="range" id="seekScrubber" min="0" max="100" value="0" step="0.1" class="timeline-slider">
<div class="timeline-progress-bar" id="timelineProgressBar"></div>
</div>
<div class="player-extra-controls">
<button id="btnLoop" class="player-btn-sub active" title="Audio-Schleife an/aus">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>
</button>
<div class="volume-control">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
<input type="range" id="volumeSlider" min="0" max="1" step="0.05" value="0.9" class="volume-slider">
</div>
</div>
</div>
</section>
<!-- Right: Inspector & Parameter Controls -->
<aside class="controls-sidebar">
<!-- Navigation Tabs -->
<nav class="sidebar-tabs">
<button class="tab-btn active" data-tab="tab-media">📁 Media</button>
<button class="tab-btn" data-tab="tab-core">⭕ Kreis</button>
<button class="tab-btn" data-tab="tab-spectrum">📊 Spektrum</button>
<button class="tab-btn" data-tab="tab-fx">✨ FX & Shake</button>
<button class="tab-btn" data-tab="tab-colors">🎨 Farben</button>
<button class="tab-btn" data-tab="tab-text">📝 Text</button>
<button class="tab-btn" data-tab="tab-export">🚀 Export</button>
</nav>
<div class="sidebar-content">
<!-- TAB 1: MEDIA -->
<div class="tab-panel active" id="tab-media">
<div class="panel-section">
<h3 class="section-title">1. Audio-Datei (Song)</h3>
<div class="dropzone" id="audioDropzone">
<input type="file" id="audioFileInput" accept="audio/*,.wav,.mp3,.flac,.m4a,.ogg" class="file-input-hidden">
<div class="dropzone-content">
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
<p class="dropzone-text"><strong>WAV / MP3 / FLAC</strong> hierher ziehen</p>
<span class="dropzone-sub">oder klicken zum Auswählen (bis 200 MB)</span>
<div class="file-badge hidden" id="audioFileBadge">Kein Song geladen</div>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">2. Cover-Art / Logo</h3>
<div class="dropzone" id="imageDropzone">
<input type="file" id="imageFileInput" accept="image/*,.png,.jpg,.jpeg,.webp" class="file-input-hidden">
<div class="dropzone-content">
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
<p class="dropzone-text"><strong>Cover-Bild / Logo</strong> hierher ziehen</p>
<span class="dropzone-sub">PNG / JPG (wird zentriert zugeschnitten)</span>
<div class="file-badge hidden" id="imageFileBadge">Standard-Cover</div>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">3. Track-Metadaten</h3>
<div class="form-group">
<label for="trackTitle">Song-Titel</label>
<input type="text" id="trackTitle" placeholder="z. B. Du bist mein Käse" value="Dein Songtitel">
</div>
<div class="form-group">
<label for="trackArtist">Interpret / Artist</label>
<input type="text" id="trackArtist" placeholder="z. B. Jannik" value="Artist Name">
</div>
</div>
</div>
<!-- TAB 2: CORE & CIRCLE -->
<div class="tab-panel" id="tab-core">
<div class="panel-section">
<h3 class="section-title">Zentraler Kreis (Artwork)</h3>
<div class="control-row">
<label for="circleRadius">Kreis-Radius</label>
<div class="range-group">
<input type="range" id="circleRadius" min="60" max="300" value="150" step="1">
<span class="range-value" id="valCircleRadius">150px</span>
</div>
</div>
<div class="control-row">
<label for="pulseIntensity">Bass-Pulse Stärke</label>
<div class="range-group">
<input type="range" id="pulseIntensity" min="0" max="1" value="0.45" step="0.01">
<span class="range-value" id="valPulseIntensity">45%</span>
</div>
</div>
<div class="control-row">
<label for="pulseDecay">Pulse Glättung (Decay)</label>
<div class="range-group">
<input type="range" id="pulseDecay" min="0.1" max="0.95" value="0.8" step="0.01">
<span class="range-value" id="valPulseDecay">80%</span>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Rand & Progress-Ring</h3>
<div class="control-row">
<label for="borderWidth">Rand-Dicke</label>
<div class="range-group">
<input type="range" id="borderWidth" min="0" max="25" value="6" step="1">
<span class="range-value" id="valBorderWidth">6px</span>
</div>
</div>
<div class="control-row">
<label for="borderColor">Rand-Farbe</label>
<input type="color" id="borderColor" value="#ffffff" class="color-picker-input">
</div>
<div class="toggle-row">
<label for="showProgressRing">Fortschritts-Ring (0-360°)</label>
<input type="checkbox" id="showProgressRing" checked>
</div>
<div class="control-row">
<label for="progressRingColor">Ring-Farbe</label>
<input type="color" id="progressRingColor" value="#00f0ff" class="color-picker-input">
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Rotation & Drehung</h3>
<div class="control-row">
<label for="rotationSpeed">Vinyl-Drehung (Speed)</label>
<div class="range-group">
<input type="range" id="rotationSpeed" min="0" max="20" value="3" step="0.5">
<span class="range-value" id="valRotationSpeed">3 RPM</span>
</div>
</div>
<div class="toggle-row">
<label for="beatSpinImpulse">Beat-Drehimpuls bei Drops</label>
<input type="checkbox" id="beatSpinImpulse" checked>
</div>
</div>
</div>
<!-- TAB 3: SPECTRUM -->
<div class="tab-panel" id="tab-spectrum">
<div class="panel-section">
<h3 class="section-title">Spektrum-Modus</h3>
<div class="radio-pill-group">
<label class="radio-pill">
<input type="radio" name="spectrumMode" value="radial_bars" checked>
<span>📊 Balken</span>
</label>
<label class="radio-pill">
<input type="radio" name="spectrumMode" value="smooth_wave">
<span>〰️ Welle</span>
</label>
<label class="radio-pill">
<input type="radio" name="spectrumMode" value="floating_dots">
<span>✨ Punkte</span>
</label>
<label class="radio-pill">
<input type="radio" name="spectrumMode" value="dual_ring">
<span>⭕ Dual Ring</span>
</label>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Geometrie & Dichte</h3>
<div class="control-row">
<label for="barCount">Anzahl Balken / Punkte</label>
<div class="range-group">
<input type="range" id="barCount" min="32" max="256" value="96" step="8">
<span class="range-value" id="valBarCount">96</span>
</div>
</div>
<div class="control-row">
<label for="spectrumMaxHeight">Maximale Höhe</label>
<div class="range-group">
<input type="range" id="spectrumMaxHeight" min="20" max="300" value="130" step="5">
<span class="range-value" id="valSpectrumMaxHeight">130px</span>
</div>
</div>
<div class="control-row">
<label for="barWidth">Balkenbreite</label>
<div class="range-group">
<input type="range" id="barWidth" min="1" max="15" value="4" step="0.5">
<span class="range-value" id="valBarWidth">4px</span>
</div>
</div>
<div class="control-row">
<label for="barSpacing">Abstand vom Kreis</label>
<div class="range-group">
<input type="range" id="barSpacing" min="0" max="60" value="12" step="1">
<span class="range-value" id="valBarSpacing">12px</span>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Frequenz-Analyse</h3>
<div class="control-row">
<label for="freqSensitivity">Empfindlichkeit</label>
<div class="range-group">
<input type="range" id="freqSensitivity" min="0.5" max="3.0" value="1.3" step="0.1">
<span class="range-value" id="valFreqSensitivity">1.3x</span>
</div>
</div>
<div class="control-row">
<label for="freqFocus">Frequenz-Fokus</label>
<select id="freqFocus" class="select-input">
<option value="balanced">Ausgewogen (Sub bis Höhen)</option>
<option value="bass_heavy">Basslastig (EDM / Trap)</option>
<option value="vocal_mid">Mitten & Gesang (Pop / Rock)</option>
</select>
</div>
<div class="toggle-row">
<label for="mirrorSpectrum">Spektrum spiegeln (Symmetrisch)</label>
<input type="checkbox" id="mirrorSpectrum" checked>
</div>
</div>
</div>
<!-- TAB 4: FX & SHAKE -->
<div class="tab-panel" id="tab-fx">
<div class="panel-section">
<h3 class="section-title">Partikel-System (Staub & Funken)</h3>
<div class="toggle-row">
<label for="enableParticles">Partikel aktivieren</label>
<input type="checkbox" id="enableParticles" checked>
</div>
<div class="control-row">
<label for="particleCount">Partikel-Anzahl</label>
<div class="range-group">
<input type="range" id="particleCount" min="20" max="250" value="90" step="5">
<span class="range-value" id="valParticleCount">90</span>
</div>
</div>
<div class="control-row">
<label for="particleSpeed">Basis-Geschwindigkeit</label>
<div class="range-group">
<input type="range" id="particleSpeed" min="0.2" max="4.0" value="1.0" step="0.1">
<span class="range-value" id="valParticleSpeed">1.0x</span>
</div>
</div>
<div class="control-row">
<label for="particleWarp">Warp-Explosion bei Drops</label>
<div class="range-group">
<input type="range" id="particleWarp" min="0" max="5.0" value="2.5" step="0.1">
<span class="range-value" id="valParticleWarp">2.5x</span>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Kamera & Bass-Shake</h3>
<div class="control-row">
<label for="camShakeIntensity">Bass-Shake Intensität</label>
<div class="range-group">
<input type="range" id="camShakeIntensity" min="0" max="1.0" value="0.35" step="0.05">
<span class="range-value" id="valCamShakeIntensity">35%</span>
</div>
</div>
<div class="control-row">
<label for="rgbSplitIntensity">RGB-Split / Glitch bei Drops</label>
<div class="range-group">
<input type="range" id="rgbSplitIntensity" min="0" max="1.0" value="0.25" step="0.05">
<span class="range-value" id="valRgbSplitIntensity">25%</span>
</div>
</div>
<div class="toggle-row">
<label for="beatFlash">Beat-Flash (Helligkeits-Flash)</label>
<input type="checkbox" id="beatFlash">
</div>
</div>
</div>
<!-- TAB 5: COLORS & GLOW -->
<div class="tab-panel" id="tab-colors">
<div class="panel-section">
<h3 class="section-title">Farbpalette & Gradients</h3>
<div class="control-row">
<label for="colorGrad1">Hauptfarbe 1</label>
<input type="color" id="colorGrad1" value="#00f0ff" class="color-picker-input">
</div>
<div class="control-row">
<label for="colorGrad2">Hauptfarbe 2</label>
<input type="color" id="colorGrad2" value="#ff007f" class="color-picker-input">
</div>
<div class="control-row">
<label for="colorParticle">Partikel-Farbe</label>
<input type="color" id="colorParticle" value="#ffffff" class="color-picker-input">
</div>
<div class="control-row">
<label for="gradientStyle">Farbverlauf-Art</label>
<select id="gradientStyle" class="select-input">
<option value="angular">360° Winkelverlauf (Radial Conic)</option>
<option value="radial">Zentrum zu Rand (Linear Radial)</option>
<option value="linear_h">Horizontal (Links nach Rechts)</option>
<option value="rainbow">Dynamischer Regenbogen-Zyklus</option>
</select>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Neon Bloom & Glow</h3>
<div class="control-row">
<label for="glowIntensity">Glow-Leuchtkraft</label>
<div class="range-group">
<input type="range" id="glowIntensity" min="0" max="1.0" value="0.65" step="0.05">
<span class="range-value" id="valGlowIntensity">65%</span>
</div>
</div>
<div class="control-row">
<label for="glowBlur">Glow-Radius</label>
<div class="range-group">
<input type="range" id="glowBlur" min="0" max="50" value="20" step="2">
<span class="range-value" id="valGlowBlur">20px</span>
</div>
</div>
</div>
<div class="panel-section">
<h3 class="section-title">Hintergrund-Modus</h3>
<select id="backgroundMode" class="select-input">
<option value="transparent">100% Transparent (Alpha-Kanal für DaVinci)</option>
<option value="blurred_cover">Unscharfes Cover-Artwork im Hintergrund</option>
<option value="dark_vignette">Dunkle Obsidian-Vignette</option>
<option value="solid_black">Reines Schwarz (#000000)</option>
</select>
</div>
</div>
<!-- TAB 6: TYPOGRAPHY -->
<div class="tab-panel" id="tab-text">
<div class="panel-section">
<h3 class="section-title">Song-Info Overlay</h3>
<div class="toggle-row">
<label for="showTextOverlay">Text-Overlay anzeigen</label>
<input type="checkbox" id="showTextOverlay" checked>
</div>
<div class="control-row">
<label for="fontFamily">Schriftart</label>
<select id="fontFamily" class="select-input">
<option value="'Outfit', sans-serif">Outfit (Modern Clean)</option>
<option value="'Orbitron', sans-serif">Orbitron (Cyber / EDM)</option>
<option value="'Montserrat', sans-serif">Montserrat (Bold Studio)</option>
<option value="'Inter', sans-serif">Inter (Minimalist)</option>
<option value="'Cinzel', serif">Cinzel (Cinematic Serif)</option>
</select>
</div>
<div class="control-row">
<label for="textPosition">Position</label>
<select id="textPosition" class="select-input">
<option value="below_circle">Direkt unter dem Kreis</option>
<option value="bottom_center">Unten Zentriert</option>
<option value="top_center">Oben Zentriert</option>
</select>
</div>
<div class="control-row">
<label for="titleFontSize">Titel Schriftgröße</label>
<div class="range-group">
<input type="range" id="titleFontSize" min="16" max="72" value="38" step="2">
<span class="range-value" id="valTitleFontSize">38px</span>
</div>
</div>
<div class="control-row">
<label for="artistFontSize">Artist Schriftgröße</label>
<div class="range-group">
<input type="range" id="artistFontSize" min="12" max="48" value="22" step="2">
<span class="range-value" id="valArtistFontSize">22px</span>
</div>
</div>
<div class="toggle-row">
<label for="textPulseWithBeat">Text pulsiert im Beat</label>
<input type="checkbox" id="textPulseWithBeat" checked>
</div>
<div class="toggle-row">
<label for="showTimecode">Live-Timecode einblenden</label>
<input type="checkbox" id="showTimecode" checked>
</div>
</div>
</div>
<!-- TAB 7: EXPORT -->
<div class="tab-panel" id="tab-export">
<div class="panel-section">
<h3 class="section-title">DaVinci Resolve Export-Einstellungen</h3>
<div class="form-group">
<label for="exportFormat">Video-Format & Transparenz</label>
<select id="exportFormat" class="select-input">
<option value="prores">🎬 QuickTime ProRes 4444 (.mov) — Echter Alpha-Kanal für DaVinci (Beste Qualität)</option>
<option value="webm">🌐 WebM VP9 (.webm) — Echter Alpha-Kanal (Kompakte Datei)</option>
<option value="mp4">📹 MP4 Video (.mp4) — H.264 (Mit gewähltem Hintergrund)</option>
</select>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="exportResolution">Auflösung</label>
<select id="exportResolution" class="select-input">
<option value="1080p">1080p Full HD</option>
<option value="4k">4K Ultra HD (2160p)</option>
<option value="720p">720p HD (Schneller Test)</option>
</select>
</div>
<div class="form-group">
<label for="exportFps">Bildrate (FPS)</label>
<select id="exportFps" class="select-input">
<option value="60">60 FPS (Super Flüssig)</option>
<option value="30">30 FPS (Standard)</option>
</select>
</div>
</div>
<div class="export-banner">
<div class="export-banner-icon">💡</div>
<div class="export-banner-text">
<strong>Workflow in DaVinci Resolve:</strong><br>
Ziehe die exportierte <code>.mov</code> Datei einfach auf eine Videospur über deinen Hintergrund. Der Hintergrund ist zu 100% transparent freigestellt!
</div>
</div>
<!-- Export Button -->
<button id="btnStartExport" class="btn btn-primary btn-large btn-glow">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
Video jetzt rendern & downloaden
</button>
<!-- Progress Modal / Bar -->
<div class="export-progress-card hidden" id="exportProgressCard">
<div class="progress-header">
<span id="exportStatusText">Rendert Video...</span>
<span id="exportPercent">0%</span>
</div>
<div class="progress-bar-track">
<div class="progress-bar-fill" id="exportProgressBarFill"></div>
</div>
<div class="progress-meta">
<span id="exportFpsLive">FPS: --</span>
<span id="exportEta">Verbleibend: --</span>
</div>
<button id="btnCancelExport" class="btn btn-secondary btn-sm">Abbrechen</button>
</div>
</div>
<!-- Preset Manager Section -->
<div class="panel-section">
<h3 class="section-title">Presets verwalten</h3>
<div class="preset-manager-controls">
<button id="btnSavePreset" class="btn btn-secondary btn-sm">💾 Aktuelles Preset speichern</button>
<button id="btnExportPresetJson" class="btn btn-secondary btn-sm">📤 Export JSON</button>
<label class="btn btn-secondary btn-sm">
📥 Import JSON
<input type="file" id="importPresetInput" accept=".json" class="file-input-hidden">
</label>
</div>
</div>
</div>
</div>
</aside>
</main>
</div>
<script src="presets.js"></script>
<script src="app.js"></script>
</body>
</html>