Files
Twitch-VOD-Manager/src/index.html
T
Sucukdeluxe 49033f9ff2
Windows CI / verify (push) Failing after 8s
release: publish 1.0.7 Windows shell fixes
Stabilize Start menu and desktop shortcut migration with persistent versioned icon resources, stale-registration recovery, and explicit Windows shell notifications. Keep development, packaged, and shortcut identities aligned, and add regression coverage for orphaned per-user installs, reduced-motion changelog transitions, and update-popover pointer transit.
2026-08-12 12:55:46 +02:00

1140 lines
98 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' file: blob:;">
<title>Twitch VOD Manager</title>
<link rel="stylesheet" href="./styles.css">
<link rel="stylesheet" href="./workspace.css">
</head>
<body class="theme-twitch">
<div class="modal-overlay" id="updateModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="updateModalTitle" onclick="handleUpdateModalOverlayClick(event)">
<div class="modal update-modal">
<button type="button" class="modal-close modal-close-localizable" aria-label="Close" onclick="dismissUpdateModal()">x</button>
<div class="update-modal-eyebrow" id="updateModalEyebrow">Updates</div>
<h2 id="updateModalTitle">Update verfügbar</h2>
<p class="update-modal-message" id="updateModalMessage">Version 0.0.0 ist verfügbar. Jetzt herunterladen?</p>
<div class="update-modal-meta is-hidden" id="updateModalMeta"></div>
<div class="update-changelog-card is-hidden" id="updateChangelogCard">
<div class="update-changelog-header">
<span class="update-changelog-label" id="updateChangelogLabel">Changelog</span>
<button type="button" class="update-changelog-toggle" id="updateChangelogToggle" onclick="toggleUpdateChangelog()" aria-controls="updateChangelogPanel" aria-expanded="false">Changelog anzeigen</button>
</div>
<div class="update-changelog-panel" id="updateChangelogPanel" aria-hidden="true">
<div class="update-changelog-content" id="updateChangelogContent"></div>
<p class="update-changelog-empty" id="updateChangelogEmpty" hidden>Kein Changelog verfügbar.</p>
</div>
</div>
<div class="modal-actions update-modal-actions">
<button class="btn-secondary" id="updateModalDismissBtn" type="button" onclick="dismissUpdateModal()">Nein</button>
<button class="btn-secondary" id="updateModalSkipBtn" type="button" onclick="skipUpdateVersion()">Diese Version überspringen</button>
<button class="btn-primary" id="updateModalConfirmBtn" type="button" onclick="confirmUpdateModal()">Ja, herunterladen</button>
</div>
</div>
</div>
<div class="modal-overlay" id="cutterDiscardModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="cutterDiscardTitle" onclick="handleCutterDiscardOverlayClick(event)" onkeydown="trapCutterDiscardFocus(event)">
<div class="modal cutter-discard-modal">
<h2 id="cutterDiscardTitle">Aktuellen Schnitt verwerfen?</h2>
<p id="cutterDiscardMessage">Beim Öffnen eines anderen Videos gehen die aktuellen Schnittänderungen verloren.</p>
<div class="modal-actions">
<button class="btn-secondary" id="cutterDiscardCancelBtn" type="button" onclick="resolveCutterDiscard(false)">Aktuelles Video behalten</button>
<button class="btn-danger" id="cutterDiscardConfirmBtn" type="button" onclick="resolveCutterDiscard(true)">Verwerfen und öffnen</button>
</div>
</div>
</div>
<!-- Clip Dialog Modal -->
<div class="modal-overlay" id="clipModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="clipDialogTitle">
<div class="modal clip-modal">
<button type="button" class="modal-close modal-close-localizable" aria-label="Close" onclick="closeClipDialog()">x</button>
<h2 class="clip-modal-title" id="clipDialogTitle">VOD zuschneiden</h2>
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogStartLabel" for="clipStartSlider">Start:</label>
<input type="range" id="clipStartSlider" min="0" max="100" value="0" oninput="updateFromSlider('start')">
<div class="clip-modal-time-row">
<label class="clip-modal-meta" id="clipDialogStartTimeLabel" for="clipStartTime">Startzeit (HH:MM:SS):</label>
<input type="text" id="clipStartTime" value="00:00:00" class="clip-modal-time-input" onchange="updateFromInput('start')">
</div>
</div>
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogEndLabel" for="clipEndSlider">Ende:</label>
<input type="range" id="clipEndSlider" min="0" max="100" value="60" oninput="updateFromSlider('end')">
<div class="clip-modal-time-row">
<label class="clip-modal-meta" id="clipDialogEndTimeLabel" for="clipEndTime">Endzeit (HH:MM:SS):</label>
<input type="text" id="clipEndTime" value="00:01:00" class="clip-modal-time-input" onchange="updateFromInput('end')">
</div>
</div>
<div class="clip-modal-duration">
<span id="clipDialogDurationLabel" class="clip-modal-meta">Dauer: </span>
<span id="clipDurationDisplay" class="clip-modal-duration-value">00:01:00</span>
</div>
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogPartLabel" for="clipStartPart">Start Part-Nummer (optional, für Fortsetzung):</label>
<input type="text" id="clipStartPart" placeholder="z.B. 42" class="clip-modal-part-input" oninput="updateFilenameExamples()">
<div id="clipDialogPartHint" class="clip-modal-hint">Leer lassen = Teil 1</div>
</div>
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogFormatLabel">Dateinamen-Format:</label>
<label class="clip-radio-row">
<input type="radio" name="filenameFormat" value="simple" checked onchange="updateFilenameExamples()">
<span id="formatSimple" class="clip-radio-label">01.02.2026_1.mp4 (Standard)</span>
</label>
<label class="clip-radio-row">
<input type="radio" name="filenameFormat" value="timestamp" onchange="updateFilenameExamples()">
<span id="formatTimestamp" class="clip-radio-label">01.02.2026_CLIP_00-00-00_1.mp4 (mit Zeitstempel)</span>
</label>
<label class="clip-radio-row">
<input type="radio" name="filenameFormat" value="parts" onchange="updateFilenameExamples()">
<span id="formatParts" class="clip-radio-label">01.02.2026_Part01.mp4 (Parts-Format)</span>
</label>
<label class="clip-radio-row">
<input type="radio" name="filenameFormat" value="template" onchange="updateFilenameExamples()">
<span id="formatTemplate" class="clip-radio-label">{date}_{part}.mp4 (benutzerdefiniert)</span>
</label>
<div id="clipFilenameTemplateWrap" class="clip-template-wrap">
<input type="text" id="clipFilenameTemplate" value="{date}_{part}.mp4" placeholder="{date}_{part}.mp4" class="clip-modal-template-input" oninput="updateFilenameExamples()">
<div id="clipTemplateHelp" class="clip-modal-hint">Platzhalter: {title} {id} {channel} {date} {part} {trim_start} {trim_end} {trim_length} {date_custom="yyyy-MM-dd"}</div>
<div id="clipTemplateLint" class="template-lint ok">Template-Check: OK</div>
<button type="button" class="btn-secondary" id="clipTemplateGuideBtn" onclick="openTemplateGuide('clip')">Template Guide</button>
</div>
</div>
<div class="clip-modal-actions">
<button type="button" class="btn-pill success" id="clipDialogConfirmBtn" style="padding: 12px 30px;" onclick="confirmClipDialog()">Zur Queue hinzufügen</button>
</div>
</div>
</div>
<!-- Events Viewer Modal -->
<div class="modal-overlay" id="eventsViewerModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="eventsViewerTitle">
<div class="modal viewer-modal viewer-modal-events">
<button type="button" class="modal-close modal-close-localizable" aria-label="Close" onclick="closeEventsViewer()">x</button>
<h2 id="eventsViewerTitle" class="viewer-modal-title"></h2>
<div id="eventsViewerStatus" class="viewer-modal-status" role="status" aria-live="polite"></div>
<div id="eventsViewerList" class="viewer-modal-list"></div>
</div>
</div>
<!-- Chat Replay Viewer Modal -->
<div class="modal-overlay" id="chatViewerModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="chatViewerTitle">
<div class="modal viewer-modal viewer-modal-chat">
<button type="button" class="modal-close modal-close-localizable" aria-label="Close" onclick="closeChatViewer()">x</button>
<h2 id="chatViewerTitle" class="viewer-modal-title"></h2>
<div class="viewer-modal-filter-row">
<input type="text" id="chatViewerFilter" class="viewer-modal-filter-input" placeholder="Filter..." oninput="onChatViewerFilterChange()">
<span id="chatViewerStatus" class="viewer-modal-status viewer-modal-status-inline" role="status" aria-live="polite"></span>
</div>
<div id="chatViewerList" class="viewer-modal-list viewer-modal-list-chat"></div>
</div>
</div>
<!-- Template Guide Modal -->
<div class="modal-overlay" id="templateGuideModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="templateGuideTitle">
<div class="modal template-guide-modal">
<button type="button" class="modal-close modal-close-localizable" aria-label="Close" onclick="closeTemplateGuide()">x</button>
<h2 id="templateGuideTitle">Template Guide</h2>
<p id="templateGuideIntro" class="template-guide-intro">Nutze Variablen für Dateinamen und prüfe das Ergebnis als Live-Vorschau.</p>
<div class="template-guide-actions">
<button type="button" class="btn-secondary" id="templateGuideUseVod" onclick="setTemplateGuidePreset('vod')">VOD Template</button>
<button type="button" class="btn-secondary" id="templateGuideUseParts" onclick="setTemplateGuidePreset('parts')">VOD Part Template</button>
<button type="button" class="btn-secondary" id="templateGuideUseClip" onclick="setTemplateGuidePreset('clip')">Clip Template</button>
</div>
<label id="templateGuideTemplateLabel" for="templateGuideInput" class="template-guide-label">Template</label>
<input type="text" id="templateGuideInput" class="template-guide-input" oninput="updateTemplateGuidePreview()" placeholder="{title}.mp4">
<div class="template-guide-preview-box">
<div class="template-guide-preview-label" id="templateGuideOutputLabel">Live Vorschau</div>
<div id="templateGuideOutput" class="template-guide-output">-</div>
<div id="templateGuideContext" class="template-guide-context"></div>
</div>
<h3 id="templateGuideVarsTitle" class="template-guide-vars-title">Verfügbare Variablen</h3>
<div class="template-guide-table-wrap">
<table class="template-guide-table" aria-labelledby="templateGuideVarsTitle">
<thead>
<tr>
<th id="templateGuideVarCol" scope="col">Variable</th>
<th id="templateGuideDescCol" scope="col">Beschreibung</th>
<th id="templateGuideExampleCol" scope="col">Beispiel</th>
</tr>
</thead>
<tbody id="templateGuideBody"></tbody>
</table>
</div>
<div class="template-guide-footer">
<button type="button" class="btn-secondary" id="templateGuideCloseBtn" onclick="closeTemplateGuide()">Schließen</button>
</div>
</div>
</div>
<header class="app-topbar">
<div class="topbar-navigation-cluster">
<div class="topbar-brand">
<img class="topbar-brand-mark" src="../build/icon.png" alt="" aria-hidden="true">
<span id="logoText">Twitch VOD Manager</span>
</div>
<nav class="nav top-nav" aria-label="Main navigation">
<button class="nav-item top-nav-item active" type="button" aria-current="page" data-tab="vods" onclick="showTab('vods')" title="Twitch VODs">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="m10 9 5 3-5 3z"></path></svg>
<span class="top-nav-label" id="navVodsText">Twitch VODs</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="clips" onclick="showTab('clips')" title="Twitch Clips">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h11a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4z"></path><path d="m17 10 4-2v8l-4-2"></path></svg>
<span class="top-nav-label" id="navClipsText">Twitch Clips</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="cutter" onclick="showTab('cutter')" title="Video schneiden">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="7" r="3"></circle><circle cx="6" cy="17" r="3"></circle><path d="m8.6 8.5 11 7M8.6 15.5 20 8"></path></svg>
<span class="top-nav-label" id="navCutterText">Video schneiden</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="merge" onclick="showTab('merge')" title="Videos zusammenfügen">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5h4a3 3 0 0 1 3 3v8a3 3 0 0 0 3 3h4"></path><path d="m16 16 3 3-3 3M5 19h4a3 3 0 0 0 3-3V8a3 3 0 0 1 3-3h4"></path><path d="m16 2 3 3-3 3"></path></svg>
<span class="top-nav-label" id="navMergeText">Videos zusammenfügen</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="stats" onclick="showTab('stats')" title="Statistik">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V9M10 19V5M16 19v-7M22 19V3"></path></svg>
<span class="top-nav-label" id="navStatsText">Statistik</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="archive" onclick="showTab('archive')" title="Archiv">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16v13H4zM3 3h18v4H3z"></path><path d="M9 11h6"></path></svg>
<span class="top-nav-label" id="navArchiveText">Archiv</span>
</button>
<button class="nav-item top-nav-item" type="button" data-tab="settings" onclick="showTab('settings')" title="Einstellungen">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.82 2.82-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.1V21H9.6v-.1a1.7 1.7 0 0 0-1.08-1.55 1.7 1.7 0 0 0-1.88.35l-.06.06-2.82-2.82.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.1-.4H3V9.6h.1a1.7 1.7 0 0 0 1.55-1.08 1.7 1.7 0 0 0-.35-1.88l-.06-.06 2.82-2.82.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-.6 1.7 1.7 0 0 0 .4-1.1V3h4v.1A1.7 1.7 0 0 0 15.48 4.65a1.7 1.7 0 0 0 1.88-.35l.06-.06 2.82 2.82-.06.06A1.7 1.7 0 0 0 19.4 9c.16.38.36.72.6 1 .3.3.68.5 1.1.6h.1v4h-.1a1.7 1.7 0 0 0-1.7.4z"></path></svg>
<span class="top-nav-label" id="navSettingsText">Einstellungen</span>
</button>
</nav>
</div>
<div class="topbar-actions">
<div class="update-banner workspace-update" id="updateBanner" data-update-state="idle" hidden>
<button type="button" class="workspace-update-button" id="workspaceUpdateButton" onclick="handleWorkspaceUpdateAction()" aria-describedby="updateText" aria-controls="workspaceUpdatePopover" aria-expanded="false">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"></path><path d="m8 11 4 4 4-4"></path><path d="M4 19h16"></path></svg>
<span id="workspaceUpdateLabel">Update</span>
</button>
<div class="workspace-update-popover" id="workspaceUpdatePopover">
<div class="workspace-update-popover-header">
<span id="updateText" role="status" aria-live="polite">Nach Updates suchen.</span>
<button type="button" id="workspaceUpdateDismiss" onclick="dismissWorkspaceUpdatePopover()" aria-label="Update notification dismiss">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18"></path></svg>
<span class="sr-only" id="workspaceUpdateDismissText">Close</span>
</button>
</div>
<div id="updateProgress" class="update-banner-progress-wrap is-hidden">
<div class="update-banner-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Update download" id="updateProgressGauge">
<div id="updateProgressBar" class="update-banner-progress-bar"></div>
</div>
</div>
<div class="workspace-update-popover-actions">
<button type="button" id="updateButton" onclick="downloadUpdate()">Jetzt herunterladen</button>
<button type="button" id="workspaceUpdateLater" onclick="postponeWorkspaceUpdatePopover()">Spater</button>
</div>
</div>
</div>
</div>
</header>
<div class="app workspace-shell">
<aside class="sidebar context-sidebar" aria-label="Workspace context">
<section class="context-panel" data-context-for="vods" data-vods-workspace="streamers" data-vods-layout="split">
<div class="context-switcher" role="group" aria-label="VOD workspace">
<button type="button" id="streamerWorkspaceSwitch" class="active" aria-pressed="true" onclick="setVodsWorkspace('streamers', this)">Streamer</button>
<button type="button" id="queueWorkspaceSwitch" aria-pressed="false" onclick="setVodsWorkspace('queue', this)">Warteschlange</button>
</div>
<div class="streamer-section">
<div class="section-title" id="streamerSectionTitle">
<span class="section-title-label">
<span id="streamerSectionTitleText">Streamer</span>
</span>
<span id="streamerSectionCounter" class="streamer-section-counter"></span>
<button id="btnStreamerBulkRemove" class="btn-close is-hidden" type="button" onclick="bulkRemoveStreamers()" title="Bulk remove">x</button>
</div>
<input type="text" id="streamerListFilter" class="filter-input compact is-hidden" placeholder="Filter..." oninput="onStreamerListFilterChange()">
<div class="streamers" id="streamerList"></div>
</div>
<div class="queue-section">
<div class="queue-header">
<span class="queue-title" id="queueTitleText">Warteschlange</span>
<span class="queue-count" id="queueCount">0</span>
</div>
<div class="queue-list" id="queueList"></div>
<div class="queue-actions">
<button type="button" class="btn btn-start" id="btnStart" onclick="toggleDownload()" disabled>Start</button>
<button type="button" class="btn btn-merge-group is-hidden" id="btnMergeGroup" onclick="createMergeGroupFromSelection()">Merge &amp; Split</button>
<button type="button" class="btn btn-retry" id="btnRetryFailed" onclick="retryFailedDownloads()" title="Nur fehlgeschlagene Downloads erneut starten">Wiederholen</button>
<button type="button" class="btn btn-clear" id="btnClear" onclick="clearCompleted()" disabled>Leeren</button>
</div>
</div>
<div class="stats-bar" id="statsBar"></div>
</section>
<section class="context-panel" data-context-for="clips" hidden>
<div class="context-panel-heading" data-context-heading>Clips</div>
<nav class="context-list" aria-label="Clip sections">
<button type="button" class="context-link active" onclick="focusWorkspaceTarget('clipUrl', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 6h11a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4zM17 10l4-2v8l-4-2"></path></svg><span data-label-source="clipsHeading">Clip-Download</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('clipsInfoTitle', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 11v6M12 7h.01"></path></svg><span data-label-source="clipsInfoTitle">Info</span></button>
</nav>
</section>
<section class="context-panel" data-context-for="cutter" hidden>
<div class="context-panel-heading" data-context-heading>Video schneiden</div>
<nav class="context-list" aria-label="Cutter sections">
<button type="button" class="context-link active" onclick="focusWorkspaceTarget('cutterBrowseBtn', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M3 6h7l2 2h9v11H3z"></path></svg><span data-label-source="cutterSelectTitle">Video auswählen</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('timelineContainer', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 12h16M7 8v8M17 8v8"></path></svg><span data-label-source="cutterInfoSelectionLabel">Auswahl</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('btnCut', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="6" cy="7" r="3"></circle><circle cx="6" cy="17" r="3"></circle><path d="m8.5 8.5 11 7M8.5 15.5 20 8"></path></svg><span data-label-source="btnCut">Schneiden</span></button>
</nav>
</section>
<section class="context-panel" data-context-for="merge" hidden>
<div class="context-panel-heading" data-context-heading>Zusammenfügen</div>
<nav class="context-list" aria-label="Merge sections">
<button type="button" class="context-link active" onclick="focusWorkspaceTarget('mergeAddBtn', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 6h16v12H4zM12 9v6M9 12h6"></path></svg><span data-label-source="mergeAddBtn">Videos hinzufügen</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('mergeFileList', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5 6h14M5 12h14M5 18h14"></path></svg><span data-label-source="mergeTitle">Videos zusammenfügen</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('btnMerge', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5 5h4a3 3 0 0 1 3 3v8a3 3 0 0 0 3 3h4M16 16l3 3-3 3"></path></svg><span data-label-source="btnMerge">Zusammenfügen</span></button>
</nav>
</section>
<section class="context-panel" data-context-for="stats" hidden>
<div class="context-panel-heading" data-context-heading>Statistik</div>
<nav class="context-list" aria-label="Statistics sections">
<button type="button" class="context-link active" onclick="focusWorkspaceTarget('statsSummaryTitle', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 19V9M10 19V5M16 19v-7M22 19V3"></path></svg><span data-label-source="statsSummaryTitle">Übersicht</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('statsTopStreamersTitle', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 7h16M4 12h12M4 17h8"></path></svg><span data-label-source="statsTopStreamersTitle">Top Streamer</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('statsActivityTitle', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="m3 17 5-5 4 3 7-8 2 2"></path></svg><span data-label-source="statsActivityTitle">Aktivität</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('statsSizeBucketsTitle', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 3v9h9"></path></svg><span data-label-source="statsSizeBucketsTitle">Größen-Verteilung</span></button>
</nav>
</section>
<section class="context-panel" data-context-for="archive" hidden>
<div class="context-panel-heading" data-context-heading>Archiv</div>
<nav class="context-list" aria-label="Archive sections">
<button type="button" class="context-link active" onclick="focusWorkspaceTarget('archiveSearchQuery', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="10" cy="10" r="6"></circle><path d="m15 15 5 5"></path></svg><span data-label-source="archiveTitle">Archiv durchsuchen</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('archiveSearchType', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 5h16M7 12h10M10 19h4"></path></svg><span data-label-source="archiveSearchType">Filter</span></button>
<button type="button" class="context-link" onclick="focusWorkspaceTarget('archiveSearchResults', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 4h16v16H4zM8 8h8M8 12h8M8 16h5"></path></svg><span id="archiveResultsNavText">Ergebnisse</span></button>
</nav>
</section>
<section class="context-panel" data-context-for="settings" hidden>
<div class="context-panel-heading" data-context-heading>Einstellungen</div>
<nav class="context-list" aria-label="Settings sections">
<button type="button" class="context-link active" data-settings-pane="design" onclick="setSettingsPane('design', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"></circle><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"></path></svg><span data-label-source="designTitle">Design</span></button>
<button type="button" class="context-link" data-settings-pane="api" onclick="setSettingsPane('api', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M8 12h8M12 8v8"></path><circle cx="12" cy="12" r="9"></circle></svg><span data-label-source="apiTitle">Twitch API</span></button>
<button type="button" class="context-link" data-settings-pane="downloads" onclick="setSettingsPane('downloads', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3v12M8 11l4 4 4-4M4 20h16"></path></svg><span data-label-source="downloadSettingsTitle">Downloads</span></button>
<button type="button" class="context-link" data-settings-pane="automation" onclick="setSettingsPane('automation', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 12a8 8 0 1 0 2.3-5.7M4 4v6h6"></path></svg><span data-label-source="autoVodCardTitle">Automatisierung</span></button>
<button type="button" class="context-link" data-settings-pane="notifications" onclick="setSettingsPane('notifications', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10 21h4"></path></svg><span data-label-source="discordCardTitle">Benachrichtigungen</span></button>
<button type="button" class="context-link" data-settings-pane="storage" onclick="setSettingsPane('storage', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"></ellipse><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"></path></svg><span data-label-source="storageCardTitle">Storage</span></button>
<button type="button" class="context-link" data-settings-pane="maintenance" onclick="setSettingsPane('maintenance', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 7h16v13H4zM8 7V4h8v3M9 12h6"></path></svg><span data-label-source="backupCardTitle">Sicherung &amp; Wartung</span></button>
<button type="button" class="context-link" data-settings-pane="updates" onclick="setSettingsPane('updates', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3v12M8 11l4 4 4-4M4 19h16"></path></svg><span data-label-source="updateTitle">Updates</span></button>
<button type="button" class="context-link" data-settings-pane="system" onclick="setSettingsPane('system', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3 4 7v5c0 5 3.4 8 8 9 4.6-1 8-4 8-9V7z"></path><path d="m9 12 2 2 4-4"></path></svg><span data-label-source="preflightTitle">System-Check</span></button>
<button type="button" class="context-link" data-settings-pane="debug" onclick="setSettingsPane('debug', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5 5h14v14H5zM8 9l2 2-2 2M12 15h4"></path></svg><span data-label-source="debugLogTitle">Debug-Log</span></button>
<button type="button" class="context-link" data-settings-pane="metrics" onclick="setSettingsPane('metrics', this)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 19V9M10 19V5M16 19v-7M22 19V3"></path></svg><span data-label-source="runtimeMetricsTitle">Runtime Metrics</span></button>
</nav>
</section>
</aside>
<main class="main workspace-main">
<header class="header workspace-toolbar">
<div class="toolbar-context" data-toolbar-for="vods">
<button type="button" class="toolbar-icon-button" id="toolbarRefreshVodsBtn" onclick="refreshVODs()" aria-label="Refresh VODs" title="Refresh VODs"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M20 6v5h-5M4 18v-5h5M18.5 9A7 7 0 0 0 6 7M5.5 15A7 7 0 0 0 18 17"></path></svg></button>
<button type="button" class="toolbar-primary" onclick="focusWorkspaceTarget('newStreamer')"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg><span>Streamer</span></button>
</div>
<div class="toolbar-context" data-toolbar-for="clips" hidden>
<button type="button" class="toolbar-primary" onclick="focusWorkspaceTarget('clipUrl')"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg><span data-label-source="clipsHeading">Clip</span></button>
<button type="button" class="toolbar-icon-button" id="toolbarClipDownloadBtn" onclick="downloadClip()" aria-label="Download clip" title="Download clip"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3v12M8 11l4 4 4-4M4 20h16"></path></svg></button>
</div>
<div class="toolbar-context" data-toolbar-for="cutter" hidden>
<button type="button" class="toolbar-primary" onclick="selectCutterVideo()"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M3 6h7l2 2h9v11H3z"></path></svg><span data-label-source="cutterBrowseBtn">Durchsuchen</span></button>
<button type="button" class="toolbar-icon-button" id="toolbarCutBtn" onclick="startCutting()" aria-label="Cut video" title="Cut video"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="6" cy="7" r="3"></circle><circle cx="6" cy="17" r="3"></circle><path d="m8.5 8.5 11 7M8.5 15.5 20 8"></path></svg></button>
</div>
<div class="toolbar-context" data-toolbar-for="merge" hidden>
<button type="button" class="toolbar-primary" onclick="addMergeFiles()"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg><span data-label-source="mergeAddBtn">Videos hinzufügen</span></button>
<button type="button" class="toolbar-icon-button" id="toolbarMergeBtn" onclick="startMerging()" aria-label="Merge videos" title="Merge videos"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5 5h4a3 3 0 0 1 3 3v8a3 3 0 0 0 3 3h4M16 16l3 3-3 3"></path></svg></button>
</div>
<div class="toolbar-context" data-toolbar-for="stats" hidden>
<button type="button" class="toolbar-primary" id="toolbarStatsRefreshBtn" onclick="refreshArchiveStats()"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M20 6v5h-5M4 18v-5h5M18.5 9A7 7 0 0 0 6 7M5.5 15A7 7 0 0 0 18 17"></path></svg><span data-label-source="btnStatsRefresh">Aktualisieren</span></button>
</div>
<div class="toolbar-context" data-toolbar-for="archive" hidden>
<button type="button" class="toolbar-primary" onclick="performArchiveSearch()"><svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="10" cy="10" r="6"></circle><path d="m15 15 5 5"></path></svg><span data-label-source="btnArchiveSearch">Suchen</span></button>
</div>
<div class="toolbar-context" data-toolbar-for="settings" hidden>
<label class="workspace-settings-search" for="settingsSearchInput">
<svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="10" cy="10" r="6"></circle><path d="m15 15 5 5"></path></svg>
<input type="search" id="settingsSearchInput" placeholder="Search settings…" oninput="filterSettings(this.value)" autocomplete="off">
</label>
<button type="button" class="toolbar-icon-button" id="toolbarCheckUpdateBtn" onclick="checkUpdate()" aria-label="Check for updates" title="Check for updates"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M20 6v5h-5M4 18v-5h5M18.5 9A7 7 0 0 0 6 7M5.5 15A7 7 0 0 0 18 17"></path></svg></button>
</div>
<h1 class="workspace-title" id="pageTitle">VODs</h1>
<div class="header-actions workspace-toolbar-actions" data-toolbar-search-for="vods">
<div class="header-search workspace-search">
<input type="text" id="newStreamer" placeholder="Streamer hinzufügen…" onkeypress="if(event.key==='Enter')addStreamer()">
<button id="btnAddStreamer" type="button" onclick="addStreamer()" aria-label="Add streamer" title="Add streamer">+</button>
</div>
<span id="refreshText" class="sr-only">Aktualisieren</span>
</div>
</header>
<div class="content">
<!-- VODs Tab -->
<div class="tab-content active" id="vodsTab">
<div id="streamerProfileHeader" class="streamer-profile-header is-hidden"></div>
<div class="vod-filter-row">
<input type="text" id="vodFilterInput" class="filter-input" placeholder="Filter VODs..." oninput="onVodFilterInput()">
<button type="button" id="vodFilterClearBtn" class="btn-close is-hidden" onclick="clearVodFilter()" title="Clear filter">x</button>
<span id="vodFilterCount" class="form-sublabel vod-filter-count"></span>
<label id="vodHideDownloadedLabel" class="inline-toggle vod-hide-downloaded" title="">
<input type="checkbox" id="vodHideDownloadedToggle" onchange="onVodHideDownloadedChange()">
<span id="vodHideDownloadedText">Hide downloaded</span>
</label>
<label id="vodSortLabel" for="vodSortSelect" class="form-sublabel vod-sort-label">Sort:</label>
<select id="vodSortSelect" class="select-compact" onchange="onVodSortChange()">
<option value="date_desc">Newest first</option>
<option value="date_asc">Oldest first</option>
<option value="views_desc">Most viewed</option>
<option value="duration_desc">Longest first</option>
<option value="duration_asc">Shortest first</option>
</select>
</div>
<div id="vodBulkBar" class="vod-bulk-bar">
<span id="vodBulkCount" class="vod-bulk-count">0 selected</span>
<span class="vod-bulk-spacer"></span>
<button id="vodBulkAddBtn" class="btn-pill primary" type="button" onclick="bulkAddSelectedVodsToQueue()">+ Queue</button>
<button id="vodBulkMarkBtn" class="btn-pill" type="button" onclick="bulkMarkSelectedDownloaded(true)">Mark as downloaded</button>
<button id="vodBulkUnmarkBtn" class="btn-pill" type="button" onclick="bulkMarkSelectedDownloaded(false)">Unmark</button>
<button id="vodBulkClearBtn" class="btn-pill" type="button" onclick="clearVodSelection()">Clear</button>
</div>
<div class="vod-grid" id="vodGrid">
<div class="empty-state">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2h8l4 4v16H6z"></path><path d="M14 2v5h5M10 11l5 3-5 3z"></path></svg>
<h3 id="vodGridEmptyTitle">Keine VODs</h3>
<p id="vodGridEmptyText">Wähle einen Streamer aus der Liste oder füge einen neuen hinzu.</p>
</div>
</div>
</div>
<!-- Clips Tab -->
<div class="tab-content" id="clipsTab">
<div class="clip-input">
<h2 id="clipsHeading">Twitch Clip-Download</h2>
<input type="text" id="clipUrl" placeholder="https://clips.twitch.tv/... oder https://www.twitch.tv/.../clip/...">
<button type="button" class="btn-primary" onclick="downloadClip()" id="btnClip">Clip herunterladen</button>
<div class="clip-status" id="clipStatus" role="status" aria-live="polite"></div>
</div>
<div class="settings-card centered">
<h3 id="clipsInfoTitle">Info</h3>
<p id="clipsInfoText" class="info-text">
Unterstutzte Formate:
- https://clips.twitch.tv/ClipName
- https://www.twitch.tv/streamer/clip/ClipName
Clips werden im Download-Ordner unter "Clips/StreamerName/" gespeichert.
</p>
</div>
</div>
<!-- Video Cutter Tab -->
<div class="tab-content" id="cutterTab">
<div class="cutter-container">
<div class="cutter-source-bar">
<div class="cutter-source-copy">
<span class="cutter-source-title" id="cutterSelectTitle">Video auswählen</span>
<input type="text" id="cutterFilePath" readonly aria-labelledby="cutterSelectTitle" placeholder="Keine Datei ausgewählt…">
</div>
<button type="button" class="btn-secondary" id="cutterBrowseBtn" onclick="selectCutterVideo()">Durchsuchen</button>
<button type="button" class="btn-secondary" id="cutterOpenProjectBtn" onclick="openCutterProject()" disabled>Projekt öffnen</button>
<button type="button" class="btn-secondary" id="cutterSaveProjectBtn" onclick="saveCutterProject()" disabled>Projekt speichern</button>
</div>
<div class="cutter-recovery-panel" id="cutterRecoveryPanel" role="status" hidden>
<span id="cutterRecoveryText">Gespeicherte Bearbeitung gefunden</span>
<button type="button" class="btn-secondary" onclick="recoverCutterProject()">Wiederherstellen</button>
<button type="button" class="btn-secondary" onclick="discardCutterProject()">Verwerfen</button>
</div>
<div class="cutter-workspace" id="cutterWorkspace">
<aside class="cutter-sidebar">
<div class="cutter-sidebar-heading">
<div>
<span class="cutter-eyebrow">EDITOR</span>
<h3 id="cutterEditHeading">Trimmen & schneiden</h3>
</div>
<button type="button" class="cutter-icon-button" id="cutterNewCutBtn" onclick="addCutterCut()" disabled aria-label="Neuer Schnitt" title="Neuer Schnitt">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg>
</button>
</div>
<label class="cutter-preview-toggle">
<span>
<strong id="cutterPreviewModeLabel">Schnittvorschau</strong>
<small id="cutterPreviewModeHint">Entfernte Bereiche überspringen</small>
</span>
<input type="checkbox" id="cutterPreviewMode" checked onchange="setCutterPreviewMode(this.checked)">
<span class="cutter-toggle-track" aria-hidden="true"></span>
</label>
<div class="cutter-export-options">
<label for="cutterExportProfile">Exportprofil</label>
<select id="cutterExportProfile" onchange="setCutterExportProfile(this.value)" disabled>
<option value="quality">Quality</option>
<option value="balanced" selected>Balanced</option>
<option value="fast">Fast</option>
<option value="archive">Archive</option>
</select>
<label for="cutterExportEncoder">Encoder</label>
<select id="cutterExportEncoder" onchange="setCutterExportEncoder(this.value)" disabled>
<option value="software">Software</option>
</select>
<label for="cutterAudioStream">Audiospur</label>
<select id="cutterAudioStream" onchange="setCutterAudioStream(this.value)" disabled>
<option value="0">Keine Audiospur</option>
</select>
</div>
<div class="cutter-trim-card">
<div class="cutter-card-title" id="cutterGlobalTrimLabel">Gesamtauswahl</div>
<div class="cutter-time-field-row">
<label for="startTime" id="cutterStartLabel">Start</label>
<input type="text" id="startTime" value="00:00:00" spellcheck="false" onchange="updateTimeFromInput()">
</div>
<div class="cutter-time-field-row">
<label for="endTime" id="cutterEndLabel">Ende</label>
<input type="text" id="endTime" value="00:00:00" spellcheck="false" onchange="updateTimeFromInput()">
</div>
</div>
<div class="cutter-cut-section">
<div class="cutter-card-title-row">
<span class="cutter-card-title" id="cutterCutsLabel">Entfernte Bereiche</span>
<span class="cutter-cut-count" id="cutterCutCount">0</span>
</div>
<div class="cutter-cut-list" id="cutterCutList">
<div class="cutter-cut-empty" id="cutterCutEmpty">Noch keine Schnitte</div>
</div>
</div>
</aside>
<section class="cutter-preview-panel">
<div class="video-preview" id="cutterPreview">
<video id="cutterVideo" preload="metadata"></video>
<div class="placeholder" id="cutterPreviewEmpty">
<svg aria-hidden="true" width="64" height="64" viewBox="0 0 24 24" fill="currentColor"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM9 8l7 4-7 4V8z"/></svg>
<p id="cutterPreviewPlaceholder">Video auswählen, um eine Vorschau zu sehen</p>
</div>
<div class="cutter-player-loading" id="cutterPlayerLoading" hidden>
<span class="cutter-spinner"></span>
<span id="cutterLoadingLabel">Video wird vorbereitet…</span>
</div>
<div class="cutter-player-controls" id="cutterPlayerControls">
<button type="button" class="cutter-player-button" id="cutterPlayBtn" onclick="toggleCutterPlayback()" disabled aria-label="Abspielen">
<svg class="cutter-play-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg>
<svg class="cutter-pause-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M7 5h4v14H7zM13 5h4v14h-4z"></path></svg>
</button>
<button type="button" class="cutter-player-button" id="cutterStopBtn" onclick="stopCutterPlayback()" disabled data-cutter-media-control aria-label="Stopp">
<svg class="cutter-filled-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M7 7h10v10H7z"></path></svg>
</button>
<button type="button" class="cutter-player-button cutter-skip-button" id="cutterRewindBtn" onclick="skipCutterPlayback(-10)" disabled data-cutter-media-control aria-label="10 Sekunden zurück">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5.2 8.2A8 8 0 1 1 4 14.5"></path><path d="M5 4v5h5"></path></svg><span>10</span>
</button>
<button type="button" class="cutter-player-button cutter-skip-button" id="cutterForwardBtn" onclick="skipCutterPlayback(10)" disabled data-cutter-media-control aria-label="10 Sekunden vor">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M18.8 8.2A8 8 0 1 0 20 14.5"></path><path d="M19 4v5h-5"></path></svg><span>10</span>
</button>
<div class="cutter-volume-control">
<button type="button" class="cutter-player-button" id="cutterMuteBtn" onclick="toggleCutterMute()" disabled aria-label="Ton umschalten">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9H4zm12.5 3a4 4 0 0 0-2-3.46v6.92A4 4 0 0 0 16.5 12z"></path></svg>
</button>
<input type="range" class="cutter-volume" id="cutterVolume" min="0" max="1" step="0.05" value="1" disabled aria-label="Lautstärke">
</div>
<span class="cutter-player-time"><span id="cutterCurrentTime">00:00:00</span><span>/</span><span id="cutterTotalTime">00:00:00</span></span>
<select id="cutterPlaybackRate" hidden disabled aria-label="Wiedergabegeschwindigkeit">
<option value="0.5">0,5×</option>
<option value="0.75">0,75×</option>
<option value="1" selected>1×</option>
<option value="1.25">1,25×</option>
<option value="1.5">1,5×</option>
<option value="2">2×</option>
</select>
<div class="cutter-player-settings">
<button type="button" class="cutter-player-button" id="cutterSettingsBtn" onclick="toggleCutterSettingsMenu()" disabled aria-label="Einstellungen" aria-expanded="false">
<svg class="cutter-filled-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M19.4 13a7.8 7.8 0 0 0 .1-1 7.8 7.8 0 0 0-.1-1l2.1-1.6-2-3.4-2.5 1a8 8 0 0 0-1.7-1L15 3.3h-4L10.6 6a8 8 0 0 0-1.7 1L6.4 6l-2 3.4L6.5 11a7.8 7.8 0 0 0-.1 1 7.8 7.8 0 0 0 .1 1l-2.1 1.6 2 3.4 2.5-1a8 8 0 0 0 1.7 1l.4 2.7h4l.4-2.7a8 8 0 0 0 1.7-1l2.5 1 2-3.4L19.4 13zM13 15.5A3.5 3.5 0 1 1 13 8a3.5 3.5 0 0 1 0 7.5z"></path></svg>
</button>
<div class="cutter-settings-menu" id="cutterSettingsMenu" hidden>
<span id="cutterSpeedLabel">Geschwindigkeit</span>
<div class="cutter-speed-options">
<button type="button" data-rate="0.5" onclick="setCutterPlaybackRate(0.5)">0,5×</button>
<button type="button" data-rate="0.75" onclick="setCutterPlaybackRate(0.75)">0,75×</button>
<button type="button" class="active" data-rate="1" onclick="setCutterPlaybackRate(1)">Normal</button>
<button type="button" data-rate="1.25" onclick="setCutterPlaybackRate(1.25)">1,25×</button>
<button type="button" data-rate="1.5" onclick="setCutterPlaybackRate(1.5)">1,5×</button>
<button type="button" data-rate="2" onclick="setCutterPlaybackRate(2)">2×</button>
</div>
</div>
</div>
<button type="button" class="cutter-player-button" id="cutterFullscreenBtn" onclick="toggleCutterFullscreen()" disabled aria-label="Vollbild">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"></path></svg>
</button>
</div>
</div>
<div class="cutter-info" id="cutterInfo">
<div class="cutter-info-item"><span class="cutter-info-label" id="cutterInfoDurationLabel">Dauer</span><span class="cutter-info-value" id="infoDuration">--:--:--</span></div>
<div class="cutter-info-item"><span class="cutter-info-label" id="cutterInfoResolutionLabel">Auflösung</span><span class="cutter-info-value" id="infoResolution">----×----</span></div>
<div class="cutter-info-item"><span class="cutter-info-label" id="cutterInfoFpsLabel">FPS</span><span class="cutter-info-value" id="infoFps">--</span></div>
<div class="cutter-info-item"><span class="cutter-info-label" id="cutterInfoSelectionLabel">Ausgabe</span><span class="cutter-info-value" id="infoSelection">--:--:--</span></div>
</div>
</section>
</div>
<div class="timeline-container" id="timelineContainer">
<div class="cutter-timeline-toolbar">
<div class="cutter-timeline-timecode" id="cutterTimelineTimecode">00:00:00</div>
<div class="cutter-history-controls">
<button type="button" class="cutter-icon-button" id="cutterUndoBtn" onclick="undoCutterEdit()" disabled aria-label="Rückgängig" title="Rückgängig (Strg+Z)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M9 7 4 12l5 5v-3h5a5 5 0 0 1 5 5v1h2v-1a7 7 0 0 0-7-7H9V7z"></path></svg></button>
<button type="button" class="cutter-icon-button" id="cutterRedoBtn" onclick="redoCutterEdit()" disabled aria-label="Wiederholen" title="Wiederholen (Strg+Y)"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="m15 7 5 5-5 5v-3h-5a5 5 0 0 0-5 5v1H3v-1a7 7 0 0 1 7-7h5V7z"></path></svg></button>
</div>
<div class="cutter-zoom-controls">
<button type="button" class="cutter-icon-button" id="cutterZoomOutBtn" onclick="changeCutterZoom(-0.25)" disabled aria-label="Verkleinern"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M5 12h14"></path></svg></button>
<input type="range" id="cutterZoom" min="1" max="16" step="0.05" value="1" disabled aria-label="Timeline-Zoom">
<button type="button" class="cutter-icon-button" id="cutterZoomInBtn" onclick="changeCutterZoom(0.25)" disabled aria-label="Vergrößern"><svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg></button>
</div>
<div class="cutter-actions">
<button type="button" class="btn-secondary" id="cutterCancelExportBtn" onclick="cancelCutterExport()" hidden>Abbrechen</button>
<button type="button" class="btn-primary" id="btnCut" onclick="startCutting()" disabled>Video exportieren</button>
</div>
</div>
<div class="cutter-timeline-scroll" id="cutterTimelineScroll">
<div class="timeline" id="timeline">
<div class="cutter-ruler" id="cutterRuler"></div>
<div class="cutter-track cutter-video-track" id="cutterVideoTrack">
<span class="cutter-track-label" id="cutterVideoTrackLabel">VIDEO</span>
<div class="cutter-thumbnail-strip" id="cutterThumbnailStrip"></div>
</div>
<div class="cutter-track cutter-audio-track" id="cutterAudioTrack">
<span class="cutter-track-label" id="cutterAudioTrackLabel">AUDIO</span>
<img id="cutterWaveform" alt="" draggable="false">
<div class="cutter-audio-empty" id="cutterAudioEmpty">Keine Audiospur</div>
</div>
<div class="cutter-outside-shade cutter-outside-left" id="cutterOutsideLeft"></div>
<div class="cutter-outside-shade cutter-outside-right" id="cutterOutsideRight"></div>
<div class="timeline-selection" id="timelineSelection">
<button type="button" class="timeline-handle start" id="cutterTrimStartHandle" aria-label="Start verschieben"></button>
<button type="button" class="timeline-handle end" id="cutterTrimEndHandle" aria-label="Ende verschieben"></button>
</div>
<div class="cutter-cut-overlays" id="cutterCutOverlays"></div>
<div class="timeline-current" id="timelineCurrent"><span></span></div>
</div>
</div>
</div>
<div class="progress-container" id="cutProgress">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Cut progress" id="cutProgressGauge">
<div class="progress-bar-fill" id="cutProgressBar"></div>
</div>
<div class="progress-text" id="cutProgressText">0%</div>
</div>
</div>
</div>
<!-- Merge Tab -->
<div class="tab-content" id="mergeTab">
<div class="merge-container">
<div class="settings-card">
<h3 id="mergeTitle">Videos zusammenfügen</h3>
<p id="mergeDesc" class="card-intro">
Wähle mehrere Videos aus um sie zu einem Video zusammenzufügen.
Die Reihenfolge kann per Drag & Drop geändert werden.
</p>
<button type="button" class="btn-secondary" id="mergeAddBtn" onclick="addMergeFiles()">+ Videos hinzufügen</button>
</div>
<div class="file-list" id="mergeFileList">
<div class="empty-state merge-empty-state">
<svg aria-hidden="true" width="48" height="48" viewBox="0 0 24 24" fill="currentColor"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<p id="mergeEmptyText">Keine Videos ausgewahlt</p>
</div>
</div>
<div class="progress-container" id="mergeProgress">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Merge progress" id="mergeProgressGauge">
<div class="progress-bar-fill" id="mergeProgressBar"></div>
</div>
<div class="progress-text" id="mergeProgressText">0%</div>
</div>
<div class="merge-actions">
<button type="button" class="btn-primary" id="btnMerge" onclick="startMerging()" disabled>Zusammenfügen</button>
</div>
</div>
</div>
<!-- Statistics Tab -->
<div class="tab-content" id="statsTab">
<div class="settings-card">
<div class="form-row section-header">
<h3 id="statsTitle">Archiv-Statistik</h3>
<div class="section-header-actions">
<span id="statsLastScannedLabel" class="form-sublabel" role="status" aria-live="polite"></span>
<button type="button" class="btn-secondary" id="btnStatsRefresh" onclick="refreshArchiveStats()">Aktualisieren</button>
</div>
</div>
<p id="statsIntro" class="card-intro flush">Aggregiert über den Download-Ordner. Live-Aufnahmen liegen unter <code>{streamer}/live/</code>, VOD-Downloads direkt unter <code>{streamer}/</code>. Lade-Zeit skaliert mit der Anzahl Dateien.</p>
</div>
<div class="settings-card">
<h3 id="statsSummaryTitle">Übersicht</h3>
<div id="statsSummaryGrid" class="stats-summary-grid"></div>
</div>
<div class="settings-card">
<h3 id="statsTopStreamersTitle">Top Streamer (nach Größe)</h3>
<div id="statsTopStreamers"></div>
</div>
<div class="settings-card">
<h3 id="statsActivityTitle">Aktivität (letzte 30 Tage)</h3>
<div id="statsActivity"></div>
</div>
<div class="settings-card">
<h3 id="statsSizeBucketsTitle">Aufnahme-Größen-Verteilung</h3>
<div id="statsSizeBuckets"></div>
</div>
</div>
<!-- Archive Search Tab -->
<div class="tab-content" id="archiveTab">
<div class="settings-card">
<h3 id="archiveTitle">Archiv durchsuchen</h3>
<p id="archiveIntro" class="card-intro">Suche nach Dateinamen, Streamern oder Datumsangaben. Treffer zeigen Aufnahmen (Live + VOD); zugehörige Chat- und Event-Dateien werden als zusätzliche Schaltflächen angeboten.</p>
<div class="form-row search-bar">
<input type="text" id="archiveSearchQuery" class="filter-input flex-1-1-240" placeholder="Suche...">
<select id="archiveSearchType" class="select-compact">
<option value="all">Alle Typen</option>
<option value="live">Live-Aufnahmen</option>
<option value="vod">VOD-Downloads</option>
</select>
<select id="archiveSearchStreamer" class="select-compact size-md">
<option value="">Alle Streamer</option>
</select>
<select id="archiveSearchSort" class="select-compact">
<option value="date_desc">Neueste zuerst</option>
<option value="date_asc">Älteste zuerst</option>
<option value="size_desc">Größte zuerst</option>
<option value="size_asc">Kleinste zuerst</option>
<option value="name_asc">Name (A-Z)</option>
</select>
<button type="button" class="btn-secondary" id="btnArchiveSearch" onclick="performArchiveSearch()">Suchen</button>
</div>
<div id="archiveSearchSummary" class="form-sublabel" role="status" aria-live="polite"></div>
</div>
<div class="settings-card">
<div id="archiveSearchResults"></div>
</div>
</div>
<!-- Settings Tab -->
<div class="tab-content" id="settingsTab" data-settings-pane="design">
<div class="settings-card" data-settings-pane="design">
<h3 id="designTitle">Design</h3>
<div class="form-group">
<label id="themeLabel" for="themeSelect">Theme</label>
<div class="workspace-theme-picker" id="workspaceThemePicker" role="group" aria-labelledby="themeLabel">
<button type="button" class="workspace-theme-choice" data-theme="light" aria-pressed="false" onclick="selectWorkspaceTheme('light')">
<span class="workspace-theme-preview theme-preview-light" aria-hidden="true"><span></span><span></span><span></span></span>
<span data-label-source="themeLightOption">Light</span>
</button>
<button type="button" class="workspace-theme-choice active" data-theme="twitch" aria-pressed="true" onclick="selectWorkspaceTheme('twitch')">
<span class="workspace-theme-preview theme-preview-dark" aria-hidden="true"><span></span><span></span><span></span></span>
<span id="themeDarkOption">Dark</span>
</button>
<button type="button" class="workspace-theme-choice" data-theme="system" aria-pressed="false" onclick="selectWorkspaceTheme('system')">
<span class="workspace-theme-preview theme-preview-system" aria-hidden="true"><span></span><span></span><span></span></span>
<span id="themeSystemOption">System</span>
</button>
</div>
<select id="themeSelect" class="theme-select-fallback" onchange="changeTheme(this.value)">
<option value="twitch">Twitch</option>
<option value="discord">Discord</option>
<option value="youtube">YouTube</option>
<option value="apple">Apple</option>
<option value="light" id="themeLightOption">Light</option>
<option value="system">System</option>
</select>
</div>
<div class="form-group">
<label id="languageLabel">Sprache</label>
<div class="language-picker" id="languagePicker" role="group" aria-labelledby="languageLabel">
<button type="button" class="lang-option" id="langOptionEn" onclick="selectLanguageOption('en')" aria-pressed="false">
<svg class="flag-icon flag-en" viewBox="0 0 18 12" aria-hidden="true" focusable="false" shape-rendering="crispEdges">
<rect width="18" height="12" fill="#b31942"></rect>
<path d="M0 2h18v2H0zm0 4h18v2H0zm0 4h18v2H0z" fill="#fff"></path>
<path d="M0 0h8v6H0z" fill="#0a3161"></path>
<path d="M1 1h1v1H1zm3 0h1v1H4zm3 0h1v1H7zM2 3h1v1H2zm3 0h1v1H5zM1 5h1v1H1zm3 0h1v1H4zm3 0h1v1H7z" fill="#fff"></path>
</svg>
<span id="languageEnText">English</span>
</button>
<button type="button" class="lang-option" id="langOptionDe" onclick="selectLanguageOption('de')" aria-pressed="false">
<span class="flag-icon flag-de" aria-hidden="true"></span>
<span id="languageDeText">Deutsch</span>
</button>
</div>
<select id="languageSelect" onchange="changeLanguage(this.value)" style="display:none">
<option value="de">de</option>
<option value="en">en</option>
</select>
</div>
<div class="form-group sidebar-layout-setting">
<label class="toggle-row">
<input type="checkbox" id="sidebarSplitViewToggle" checked onchange="applySidebarLayoutPreference(this.checked)">
<span id="sidebarSplitViewLabel">Streamer und Queue gleichzeitig anzeigen</span>
</label>
<p id="sidebarSplitViewHint" class="form-note">Teilt die linke Seitenleiste gleichmäßig auf. Deaktiviert erscheinen wieder die Umschaltflächen.</p>
</div>
</div>
<div class="settings-card" data-settings-pane="api" hidden>
<h3 id="apiTitle">Twitch API</h3>
<p id="apiHelpText" class="card-intro">
<span id="apiHelpIntro">Du brauchst eine Client-ID und ein Client-Secret von Twitch.</span>
<a href="#" id="apiHelpLink" onclick="event.preventDefault(); openTwitchDevConsole()">dev.twitch.tv/console/apps</a>
</p>
<div class="form-group">
<label id="clientIdLabel" for="clientId">Client ID</label>
<input type="text" id="clientId" placeholder="Twitch Client ID">
</div>
<div class="form-group">
<label id="clientSecretLabel" for="clientSecret">Client Secret</label>
<input type="password" id="clientSecret" placeholder="Twitch Client Secret">
</div>
<button type="button" class="btn-primary" id="saveSettingsBtn" onclick="saveSettings()">Speichern & Verbinden</button>
</div>
<div class="settings-card" data-settings-pane="downloads" hidden>
<h3 id="downloadSettingsTitle">Download-Einstellungen</h3>
<div class="form-group download-settings-storage">
<label id="storageLabel" for="downloadPath">Speicherort</label>
<div class="form-row">
<input type="text" id="downloadPath" readonly>
<button type="button" class="btn-secondary" id="selectFolderBtn" onclick="selectFolder()">Ordner</button>
<button type="button" class="btn-secondary" id="openFolderBtn" onclick="openFolder()">Offnen</button>
</div>
</div>
<div class="download-settings-layout">
<div class="download-settings-column">
<section class="download-settings-section">
<h4 id="downloadBasicsTitle">Download und Qualität</h4>
<div class="form-group">
<label id="modeLabel" for="downloadMode">Download-Modus</label>
<select id="downloadMode">
<option value="full" id="modeFullText">Ganzes VOD</option>
<option value="parts" id="modePartsText">In Teile splitten</option>
</select>
</div>
<div class="form-group">
<label id="partMinutesLabel" for="partMinutes">Teil-Lange (Minuten)</label>
<input type="number" id="partMinutes" value="120" min="10" max="480">
</div>
<div class="form-group">
<label id="parallelDownloadsLabel" for="parallelDownloads">Parallele Downloads</label>
<select id="parallelDownloads">
<option value="1" id="parallelDownloads1">1 (Standard)</option>
<option value="2" id="parallelDownloads2">2 (Parallel)</option>
</select>
</div>
<div class="form-group">
<label id="streamlinkQualityLabel" for="streamlinkQuality">Stream-Qualität</label>
<select id="streamlinkQuality">
<option value="best" id="streamlinkQualityBest">Best (Standard)</option>
<option value="source" id="streamlinkQualitySource">Source (Original)</option>
<option value="1080p60" id="streamlinkQuality1080p60">1080p60</option>
<option value="720p60" id="streamlinkQuality720p60">720p60</option>
<option value="720p" id="streamlinkQuality720p">720p</option>
<option value="480p" id="streamlinkQuality480p">480p</option>
<option value="audio_only" id="streamlinkQualityAudio">Audio only</option>
</select>
</div>
<div class="form-group">
<label id="performanceModeLabel" for="performanceMode">Performance-Profil</label>
<select id="performanceMode">
<option value="stability" id="performanceModeStability">Max Stabilitat</option>
<option value="balanced" id="performanceModeBalanced">Ausgewogen</option>
<option value="speed" id="performanceModeSpeed">Max Geschwindigkeit</option>
</select>
</div>
</section>
<section class="download-settings-section">
<div class="download-settings-section-heading">
<h4 id="filenameTemplatesTitle">Dateinamen-Templates</h4>
<button class="btn-secondary" id="settingsTemplateGuideBtn" type="button" onclick="openTemplateGuide('vod')">Template Guide</button>
</div>
<div class="form-row template-presets">
<button class="btn-secondary" id="templatePresetDefault" type="button" onclick="applyTemplatePreset('default')">Preset: Default</button>
<button class="btn-secondary" id="templatePresetArchive" type="button" onclick="applyTemplatePreset('archive')">Preset: Archive</button>
<button class="btn-secondary" id="templatePresetClipper" type="button" onclick="applyTemplatePreset('clipper')">Preset: Clipper</button>
</div>
<div class="filename-template-grid">
<label id="vodTemplateLabel" for="vodFilenameTemplate">VOD Template</label>
<input type="text" id="vodFilenameTemplate" class="input-monospace" placeholder="{title}.mp4" oninput="validateFilenameTemplates()">
<label id="partsTemplateLabel" for="partsFilenameTemplate">VOD Part Template</label>
<input type="text" id="partsFilenameTemplate" class="input-monospace" placeholder="{date}_Part{part_padded}.mp4" oninput="validateFilenameTemplates()">
<label id="defaultClipTemplateLabel" for="defaultClipFilenameTemplate">Clip Template</label>
<input type="text" id="defaultClipFilenameTemplate" class="input-monospace" placeholder="{date}_{part}.mp4" oninput="validateFilenameTemplates()">
</div>
<div id="filenameTemplateHint" class="form-note">Platzhalter: {title} {id} {channel} {date} {part} {part_padded} {trim_start} {trim_end} {trim_length} {date_custom="yyyy-MM-dd"}</div>
<div id="filenameTemplateLint" class="template-lint ok">Template-Check: OK</div>
</section>
</div>
<div class="download-settings-column">
<section class="download-settings-section">
<h4 id="queueAutomationTitle">Queue und Automatisierung</h4>
<div class="download-settings-toggles">
<label class="toggle-row"><input type="checkbox" id="smartSchedulerToggle" checked><span id="smartSchedulerLabel">Smart Queue Scheduler aktivieren</span></label>
<label class="toggle-row"><input type="checkbox" id="duplicatePreventionToggle" checked><span id="duplicatePreventionLabel">Duplikate in Queue verhindern</span></label>
<label class="toggle-row"><input type="checkbox" id="persistQueueToggle" checked><span id="persistQueueLabel">Queue zwischen App-Starts speichern</span></label>
<label class="toggle-row"><input type="checkbox" id="autoResumeQueueToggle"><span id="autoResumeQueueLabel">Queue beim Start automatisch fortsetzen</span></label>
<label class="toggle-row"><input type="checkbox" id="notifyEachCompletionToggle"><span id="notifyEachCompletionLabel">Benachrichtigung bei jedem fertigen Download</span></label>
</div>
</section>
<section class="download-settings-section download-policy-settings">
<h4 id="downloadPolicyTitle">Drosselung und Zeitfenster</h4>
<div class="form-group">
<label id="downloadThrottleLabel" for="downloadThrottleMiBps">Maximale Downloadrate (MiB/s)</label>
<input type="text" id="downloadThrottleMiBps" inputmode="decimal" autocomplete="off" aria-describedby="downloadThrottleHint downloadPolicyValidation">
<div id="downloadThrottleHint" class="form-note">Leer lassen für keine Drosselung. Dezimalwerte wie 1,5 sind möglich.</div>
</div>
<div class="form-group">
<label id="downloadWindowsLabel" for="downloadWindows">Lokale Download-Zeitfenster</label>
<textarea id="downloadWindows" rows="3" aria-describedby="downloadWindowsHint downloadPolicyValidation" placeholder="22:00-06:00"></textarea>
<div id="downloadWindowsHint" class="form-note">Ein Zeitfenster pro Zeile oder durch Semikolon getrennt. Über Mitternacht ist erlaubt.</div>
</div>
<div id="downloadPolicyValidation" class="form-note" role="alert"></div>
<div id="downloadPolicyStatus" class="form-note" role="status" aria-live="polite"></div>
<button type="button" class="btn-secondary" id="downloadPolicyOverrideBtn" onclick="startDownloadPolicyOverride()">Jetzt starten</button>
</section>
<section class="download-settings-section">
<h4 id="recordingMetadataTitle">Aufnahmen und Metadaten</h4>
<div class="download-settings-toggles">
<label class="toggle-row"><input type="checkbox" id="streamlinkDisableAdsToggle" checked><span id="streamlinkDisableAdsLabel">Twitch-Ads beim Download überspringen</span></label>
<label class="toggle-row"><input type="checkbox" id="downloadChatReplayToggle"><span id="downloadChatReplayLabel">Chat-Replay parallel zum VOD speichern (.chat.json)</span></label>
<label class="toggle-row"><input type="checkbox" id="captureLiveChatToggle"><span id="captureLiveChatLabel">Live-Chat während der Aufnahme mitschneiden (.chat.jsonl)</span></label>
<label class="toggle-row"><input type="checkbox" id="logStreamEventsToggle" checked><span id="logStreamEventsLabel">Stream-Events bei Live-Aufnahmen mitloggen (.events.jsonl)</span></label>
<label class="toggle-row"><input type="checkbox" id="autoResumeLiveRecordingToggle" checked><span id="autoResumeLiveRecordingLabel">Live-Aufnahme automatisch fortsetzen wenn Streamlink abbricht (max. 5 Versuche)</span></label>
<label class="toggle-row"><input type="checkbox" id="autoMergeResumedPartsToggle"><span id="autoMergeResumedPartsLabel">Fortgesetzte Aufnahme-Parts automatisch zu einer Datei zusammenfügen (ffmpeg concat)</span></label>
<label class="toggle-row indented"><input type="checkbox" id="deletePartsAfterMergeToggle"><span id="deletePartsAfterMergeLabel">Einzelne Parts nach erfolgreichem Merge löschen</span></label>
</div>
<div class="form-group metadata-cache-setting">
<label id="metadataCacheMinutesLabel" for="metadataCacheMinutes">Metadata-Cache (Minuten)</label>
<input type="number" id="metadataCacheMinutes" value="10" min="1" max="120">
</div>
</section>
</div>
</div>
</div>
<div class="settings-card" data-settings-pane="updates" hidden>
<h3 id="updateTitle">Updates</h3>
<p id="versionInfo" class="card-intro">Version: v1.0.7</p>
<button type="button" class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
</div>
<div class="settings-card" data-settings-pane="system" hidden>
<div class="form-row section-header">
<h3 id="preflightTitle">System-Check</h3>
<span class="health-badge unknown" id="healthBadge">System: Unbekannt</span>
</div>
<div class="form-row" style="margin-bottom: 10px;">
<button type="button" class="btn-secondary" id="btnPreflightRun" onclick="runPreflight(false)">Check ausfuhren</button>
<button type="button" class="btn-secondary" id="btnPreflightFix" onclick="runPreflight(true)">Auto-Fix Tools</button>
</div>
<pre id="preflightResult" class="log-panel">Noch kein Check ausgefuhrt.</pre>
<h4 id="managedToolsTitle">Verwaltete Tools</h4>
<div class="form-row" style="margin-bottom: 10px;">
<button type="button" class="btn-secondary" id="btnRefreshManagedTools" onclick="refreshManagedToolStatus()">Tool-Status aktualisieren</button>
<button type="button" class="btn-secondary" id="btnRepairManagedTools" onclick="repairManagedTools()">Tools reparieren</button>
<button type="button" class="btn-secondary" id="btnResetManagedTools" onclick="resetManagedTools()">Tools zurücksetzen</button>
</div>
<pre id="managedToolStatus" class="log-panel">Noch kein Tool-Status geladen.</pre>
</div>
<div class="settings-card" data-settings-pane="debug" hidden>
<h3 id="debugLogTitle">Live Debug-Log</h3>
<div class="form-row aligned">
<button type="button" class="btn-secondary" id="btnRefreshLog" onclick="refreshDebugLog()">Aktualisieren</button>
<button type="button" class="btn-secondary" id="btnOpenDebugLogFile" onclick="openDebugLogFile()">Log-Datei öffnen</button>
<label class="inline-toggle">
<input type="checkbox" id="debugAutoRefresh" onchange="toggleDebugAutoRefresh(this.checked)">
<span id="autoRefreshText">Auto-Refresh</span>
</label>
</div>
<pre id="debugLogOutput" class="log-panel">Lade...</pre>
</div>
<div class="settings-card" data-settings-pane="storage" hidden>
<div class="form-row section-header">
<h3 id="storageCardTitle">Storage</h3>
<button type="button" class="btn-secondary" id="btnRefreshStorage" onclick="refreshStorageStats()">Aktualisieren</button>
</div>
<p id="storageCardIntro" class="card-intro">Disk-Verbrauch pro Streamer im aktuellen Download-Ordner. Live-Aufnahmen werden separat ausgewiesen.</p>
<div id="storageSummary" class="form-sublabel" style="margin-bottom:8px;" role="status" aria-live="polite"></div>
<div id="storageList"></div>
<hr>
<h4 id="cleanupTitle">Auto-Cleanup</h4>
<p id="cleanupIntro" class="card-intro">Aufnahmen älter als X Tage automatisch archivieren oder löschen. Schiebt Sidecar-Chat-Dateien (.chat.json/.chat.jsonl) mit der Aufnahme.</p>
<label class="toggle-row" style="margin-bottom: 8px;">
<input type="checkbox" id="autoCleanupEnabledToggle">
<span id="autoCleanupEnabledLabel">Auto-Cleanup aktivieren</span>
</label>
<div class="form-row" style="gap:12px; flex-wrap:wrap; margin-bottom: 8px;">
<label class="form-stack size-sm">
<span id="autoCleanupDaysLabel" class="form-sublabel">Tage-Schwelle</span>
<input type="number" id="autoCleanupDays" min="1" max="3650" value="30">
</label>
<label class="form-stack size-md">
<span id="autoCleanupTargetLabel" class="form-sublabel">Bereich</span>
<select id="autoCleanupTarget">
<option value="live_only" id="autoCleanupTargetLive">Nur Live-Aufnahmen</option>
<option value="all" id="autoCleanupTargetAll">Alle Aufnahmen</option>
</select>
</label>
<label class="form-stack size-md">
<span id="autoCleanupActionLabel" class="form-sublabel">Aktion</span>
<select id="autoCleanupAction">
<option value="archive" id="autoCleanupActionArchive">In Archiv verschieben</option>
<option value="delete" id="autoCleanupActionDelete">Löschen</option>
</select>
</label>
</div>
<div class="form-row" style="margin-bottom: 8px; gap: 8px;">
<button type="button" class="btn-secondary" id="btnCleanupDryRun" onclick="runCleanupDryRun()">Vorschau</button>
<button type="button" class="btn-secondary" id="btnCleanupRunNow" onclick="runCleanupNow()">Jetzt ausführen</button>
</div>
<div id="cleanupReport" class="form-note" role="status" aria-live="polite"></div>
</div>
<div class="settings-card" data-settings-pane="notifications" hidden>
<h3 id="discordCardTitle">Discord-Webhook</h3>
<p id="discordCardIntro" class="card-intro">Sende Benachrichtigungen an einen Discord-Channel via Webhook — nützlich für Multi-Device-Setups oder eine dedizierte Archiv-Maschine.</p>
<div class="form-group">
<label id="discordWebhookUrlLabel" for="discordWebhookUrl">Webhook-URL</label>
<input type="text" id="discordWebhookUrl" placeholder="https://discord.com/api/webhooks/...">
</div>
<div class="form-group">
<label class="toggle-row">
<input type="checkbox" id="discordNotifyLiveStartToggle">
<span id="discordNotifyLiveStartLabel">Bei Live-Aufnahme-Start benachrichtigen</span>
</label>
<label class="toggle-row">
<input type="checkbox" id="discordNotifyLiveEndToggle">
<span id="discordNotifyLiveEndLabel">Bei Live-Aufnahme-Ende benachrichtigen</span>
</label>
<label class="toggle-row">
<input type="checkbox" id="discordNotifyVodCompleteToggle">
<span id="discordNotifyVodCompleteLabel">Bei abgeschlossenem VOD-Download benachrichtigen</span>
</label>
<label class="toggle-row">
<input type="checkbox" id="discordNotifyVodAutoQueuedToggle">
<span id="discordNotifyVodAutoQueuedLabel">Bei automatisch eingereihten VODs benachrichtigen</span>
</label>
</div>
</div>
<div class="settings-card" data-settings-pane="automation" hidden>
<h3 id="autoVodCardTitle">Auto-VOD-Download</h3>
<p id="autoVodCardIntro" class="card-intro">Streamer mit aktiviertem VOD-Toggle werden in dem hier festgelegten Intervall auf neue Twitch-VODs geprüft. Neue VODs innerhalb des Alters-Fensters werden automatisch zur Download-Queue hinzugefügt.</p>
<div class="form-row aligned">
<label id="autoVodPollMinutesLabel" class="form-sublabel" for="autoVodPollMinutes">Poll-Intervall (Minuten)</label>
<input type="number" id="autoVodPollMinutes" min="5" max="360" value="15" class="input-narrow">
<label id="autoVodMaxAgeHoursLabel" class="form-sublabel" for="autoVodMaxAgeHours" style="margin-left:12px;">Max. Alter (Stunden)</label>
<input type="number" id="autoVodMaxAgeHours" min="1" max="720" value="24" class="input-narrow">
</div>
<div class="form-row" style="align-items: center; gap: 12px; flex-wrap: wrap;">
<button type="button" class="btn-secondary" id="btnAutoVodScanNow" onclick="triggerManualAutoVodScan()">Jetzt scannen</button>
<button type="button" class="btn-secondary" id="btnAutoRecordScanNow" onclick="triggerManualAutoRecordScan()">Live-Status prüfen</button>
<span id="autoVodStatusLine" class="form-sublabel"></span>
</div>
</div>
<div class="settings-card" data-settings-pane="maintenance" hidden>
<h3 id="backupCardTitle">Sicherung &amp; Wartung</h3>
<p id="backupCardIntro" class="card-intro">Konfiguration sichern, auf einem anderen Gerät wiederherstellen, oder die Liste der bereits heruntergeladenen VODs zurücksetzen.</p>
<div class="form-row" style="margin-bottom: 10px; flex-wrap: wrap;">
<button type="button" class="btn-secondary" id="btnExportConfig" onclick="exportConfigToFile()">Konfiguration exportieren</button>
<button type="button" class="btn-secondary" id="btnImportConfig" onclick="importConfigFromFile()">Konfiguration importieren</button>
<button type="button" class="btn-secondary" id="btnResetDownloadedIds" onclick="resetDownloadedIds()">Downloaded-VODs zurücksetzen</button>
</div>
</div>
<div class="settings-card" data-settings-pane="metrics" hidden>
<h3 id="runtimeMetricsTitle">Runtime Metrics</h3>
<div class="form-row aligned">
<button type="button" class="btn-secondary" id="btnRefreshMetrics" onclick="refreshRuntimeMetrics()">Aktualisieren</button>
<button type="button" class="btn-secondary" id="btnExportMetrics" onclick="exportRuntimeMetrics()">Export JSON</button>
<label class="inline-toggle">
<input type="checkbox" id="runtimeMetricsAutoRefresh" onchange="toggleRuntimeMetricsAutoRefresh(this.checked)">
<span id="runtimeMetricsAutoRefreshText">Auto-Refresh</span>
</label>
</div>
<pre id="runtimeMetricsOutput" class="log-panel">Lade...</pre>
</div>
</div>
</div>
<div class="status-bar">
<div class="status-indicator">
<div class="status-dot" id="statusDot" aria-hidden="true"></div>
<span id="statusText">Nicht verbunden</span>
</div>
<span id="statusBarQueueSummary" class="status-bar-queue-summary"></span>
<span id="versionText" class="status-bar-version"></span>
</div>
</main>
</div>
<div class="modal-overlay" id="commandPaletteModal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="commandPaletteTitle">
<div class="modal command-palette">
<h2 id="commandPaletteTitle" class="cp-title">Befehlspalette</h2>
<input
type="text"
id="commandPaletteInput"
class="cp-input"
placeholder="Suche Befehl..."
autocomplete="off"
spellcheck="false"
aria-label="Befehlspalette"
role="combobox"
aria-autocomplete="list"
aria-controls="commandPaletteList"
aria-expanded="false"
/>
<ul id="commandPaletteList" class="cp-list" role="listbox" aria-label="Befehlsergebnisse"></ul>
<p class="cp-hint" id="commandPaletteHint">Up/Down zum Navigieren, Enter zum Ausführen, Esc zum Schließen</p>
</div>
</div>
<script src="../dist/renderer-locale-de.js"></script>
<script src="../dist/renderer-locale-en.js"></script>
<script src="../dist/renderer-texts.js"></script>
<script src="../dist/renderer-shared.js"></script>
<script src="../dist/renderer-accessibility.js"></script>
<script src="../dist/renderer-settings.js"></script>
<script src="../dist/renderer-streamers.js"></script>
<script src="../dist/renderer-queue.js"></script>
<script src="../dist/renderer-updates.js"></script>
<script src="../dist/renderer-stats.js"></script>
<script src="../dist/renderer-archive.js"></script>
<script src="../dist/renderer-profile.js"></script>
<script src="../dist/renderer-vod-hover.js"></script>
<script src="../dist/renderer-command-palette.js"></script>
<script src="../dist/renderer-cutter.js"></script>
<script src="../dist/renderer.js"></script>
</body>
</html>