feat: clip-cutter modal themed + global radio button styling

Two interrelated changes shipped together.

Clip-cutter modal cleanup. The "VOD zuschneiden" modal was the last
big surface still painted in the apps PRE-purple colour palette:
hardcoded #2b2b2b modal bg, #E5A00D orange title, #1a1a1a slider
tracks (already overridden by the global rule but inline-styles still
sat there), #333 input bgs, #444 borders, plain "white" text, #888
labels, #aaa radio labels. All of it inline. The result: opening the
clip dialog was visually jumping back two themes.

Extracted everything to class-based styles using var() colours:
- .clip-modal* family of classes for layout
- Title now uses var(--text), no orange
- Inputs use var(--bg-elevated) + var(--border-soft) and pick up
  the global focus ring automatically
- The duration display ("Dauer: 00:01:00") now sits in a small
  green-tinted card to make it visually distinct from the input
  rows around it
- Radio labels go through a unified .clip-radio-row with a hover
  background tint, and the :has(input:checked) selector swaps the
  label text colour + weight when a radio is selected

Global radio button styling. The clip modal had four radio buttons
that were the only non-OS-themed control left in the app. Custom
.appearance:none + ::after-driven dot styling matching the new
checkbox visual: 16px circle, 1.5px border, hover purple tint,
checked fills the inner circle with the accent colour + a soft
purple shadow, focus-visible has the same 3px purple ring as every
other form control. Cascades globally so any future radio gets the
treatment for free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 01:56:07 +02:00
parent 32decb4c01
commit 0df8bf357d
2 changed files with 221 additions and 63 deletions

View File

@ -47,90 +47,67 @@
<!-- Clip Dialog Modal -->
<div class="modal-overlay" id="clipModal" role="dialog" aria-modal="true" aria-labelledby="clipDialogTitle">
<div class="modal" style="background: #2b2b2b; max-width: 500px;">
<div class="modal clip-modal">
<button class="modal-close" aria-label="Close" onclick="closeClipDialog()">x</button>
<h2 style="color: #E5A00D; text-align: center; margin-bottom: 20px;" id="clipDialogTitle">VOD zuschneiden</h2>
<h2 class="clip-modal-title" id="clipDialogTitle">VOD zuschneiden</h2>
<!-- Start Zeit mit Slider -->
<div style="margin-bottom: 15px;">
<label id="clipDialogStartLabel" style="display: block; margin-bottom: 5px;">Start:</label>
<input type="range" id="clipStartSlider" min="0" max="100" value="0"
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
oninput="updateFromSlider('start')">
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
<label id="clipDialogStartTimeLabel" style="color: #888;">Startzeit (HH:MM:SS):</label>
<input type="text" id="clipStartTime" value="00:00:00"
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
onchange="updateFromInput('start')">
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogStartLabel">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">Startzeit (HH:MM:SS):</label>
<input type="text" id="clipStartTime" value="00:00:00" class="clip-modal-time-input" onchange="updateFromInput('start')">
</div>
</div>
<!-- End Zeit mit Slider -->
<div style="margin-bottom: 15px;">
<label id="clipDialogEndLabel" style="display: block; margin-bottom: 5px;">Ende:</label>
<input type="range" id="clipEndSlider" min="0" max="100" value="60"
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
oninput="updateFromSlider('end')">
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
<label id="clipDialogEndTimeLabel" style="color: #888;">Endzeit (HH:MM:SS):</label>
<input type="text" id="clipEndTime" value="00:01:00"
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
onchange="updateFromInput('end')">
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogEndLabel">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">Endzeit (HH:MM:SS):</label>
<input type="text" id="clipEndTime" value="00:01:00" class="clip-modal-time-input" onchange="updateFromInput('end')">
</div>
</div>
<!-- Dauer Anzeige -->
<div style="text-align: center; margin-bottom: 20px;">
<span id="clipDialogDurationLabel" style="color: #888;">Dauer: </span>
<span id="clipDurationDisplay" style="color: #00c853;">00:01:00</span>
<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>
<!-- Teil Nummer -->
<div style="margin-bottom: 15px;">
<label id="clipDialogPartLabel" style="display: block; margin-bottom: 8px;">Start Part-Nummer (optional, fur Fortsetzung):</label>
<input type="text" id="clipStartPart" placeholder="z.B. 42"
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 8px 12px; color: white;"
oninput="updateFilenameExamples()">
<div id="clipDialogPartHint" style="color: #888; font-size: 12px; margin-top: 5px;">Leer lassen = Teil 1</div>
<div class="clip-modal-field">
<label class="clip-modal-label" id="clipDialogPartLabel">Start Part-Nummer (optional, fur 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>
<!-- Dateinamen Format -->
<div style="margin-bottom: 20px;">
<label id="clipDialogFormatLabel" style="display: block; margin-bottom: 10px;">Dateinamen-Format:</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
<input type="radio" name="filenameFormat" value="simple" checked onchange="updateFilenameExamples()"
style="width: 18px; height: 18px; accent-color: #9146FF;">
<span id="formatSimple" style="color: #aaa;">01.02.2026_1.mp4 (Standard)</span>
<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 style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
<input type="radio" name="filenameFormat" value="timestamp" onchange="updateFilenameExamples()"
style="width: 18px; height: 18px; accent-color: #9146FF;">
<span id="formatTimestamp" style="color: #aaa;">01.02.2026_CLIP_00-00-00_1.mp4 (mit Zeitstempel)</span>
<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 style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
<input type="radio" name="filenameFormat" value="parts" onchange="updateFilenameExamples()"
style="width: 18px; height: 18px; accent-color: #9146FF;">
<span id="formatParts" style="color: #aaa;">01.02.2026_Part01.mp4 (Parts-Format)</span>
<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 style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 10px;">
<input type="radio" name="filenameFormat" value="template" onchange="updateFilenameExamples()"
style="width: 18px; height: 18px; accent-color: #9146FF;">
<span id="formatTemplate" style="color: #aaa;">{date}_{part}.mp4 (benutzerdefiniert)</span>
<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" style="display:none; margin-top: 10px;">
<input type="text" id="clipFilenameTemplate" value="{date}_{part}.mp4"
placeholder="{date}_{part}.mp4"
style="width: 100%; background: #333; border: 1px solid #444; border-radius: 4px; padding: 8px 12px; color: white; font-family: monospace;"
oninput="updateFilenameExamples()">
<div id="clipTemplateHelp" style="color: #888; font-size: 12px; margin-top: 6px;">Platzhalter: {title} {id} {channel} {date} {part} {trim_start} {trim_end} {trim_length} {date_custom="yyyy-MM-dd"}</div>
<div id="clipTemplateLint" style="color: #8bc34a; font-size: 12px; margin-top: 4px;">Template-Check: OK</div>
<div id="clipFilenameTemplateWrap" class="clip-template-wrap" style="display:none;">
<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="clip-template-lint">Template-Check: OK</div>
<button class="btn-secondary" id="clipTemplateGuideBtn" style="margin-top: 8px;" onclick="openTemplateGuide('clip')">Template Guide</button>
</div>
</div>
<!-- Button -->
<div style="text-align: center;">
<div class="clip-modal-actions">
<button class="btn-pill success" id="clipDialogConfirmBtn" style="padding: 12px 30px;" onclick="confirmClipDialog()">Zur Queue hinzufugen</button>
</div>
</div>

View File

@ -188,6 +188,142 @@ body {
flex: 1;
}
/* ============================================
CLIP-CUTTER MODAL themed to match the rest of the app
============================================
This modal had a stack of hard-coded #2b2b2b / #E5A00D / #888 /
#333 colors from before the Twitch-purple theme was a thing.
Extracted to classes and re-themed using CSS vars + accent. */
.clip-modal {
max-width: 500px;
background: var(--bg-card);
}
.clip-modal-title {
text-align: center;
margin-bottom: 20px;
color: var(--text);
}
.clip-modal-field {
margin-bottom: 15px;
}
.clip-modal-label {
display: block;
margin-bottom: 6px;
color: var(--text);
font-size: 13px;
font-weight: 500;
}
.clip-modal-meta {
color: var(--text-secondary);
font-size: 12px;
}
.clip-modal-time-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.clip-modal-time-input {
width: 100px;
background: var(--bg-elevated);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 6px 10px;
color: var(--text);
font-family: 'Consolas', 'Segoe UI Mono', monospace;
text-align: center;
}
.clip-modal-part-input {
width: 100px;
background: var(--bg-elevated);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 8px 12px;
color: var(--text);
}
.clip-modal-template-input {
width: 100%;
background: var(--bg-elevated);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 8px 12px;
color: var(--text);
font-family: 'Consolas', 'Segoe UI Mono', monospace;
}
.clip-modal-duration {
text-align: center;
margin-bottom: 20px;
padding: 10px;
background: rgba(0, 200, 83, 0.06);
border: 1px solid rgba(0, 200, 83, 0.18);
border-radius: 6px;
}
.clip-modal-duration-value {
color: #00c853;
font-weight: 600;
font-family: 'Consolas', 'Segoe UI Mono', monospace;
font-size: 14px;
}
.clip-modal-hint {
color: var(--text-secondary);
font-size: 12px;
margin-top: 6px;
line-height: 1.4;
}
.clip-template-lint {
color: #8bc34a;
font-size: 12px;
margin-top: 4px;
}
.clip-template-wrap {
margin-top: 10px;
}
.clip-radio-row {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
margin-bottom: 8px;
padding: 6px 8px;
border-radius: 6px;
transition: background 0.15s;
}
.clip-radio-row:hover {
background: rgba(145, 70, 255, 0.06);
}
.clip-radio-label {
color: var(--text-secondary);
font-size: 13px;
flex: 1;
transition: color 0.15s;
}
.clip-radio-row:has(input[type="radio"]:checked) .clip-radio-label {
color: var(--text);
font-weight: 500;
}
.clip-modal-actions {
text-align: center;
margin-top: 10px;
}
.streamer-item .remove {
margin-left: auto;
opacity: 0;
@ -427,6 +563,51 @@ input[type="checkbox"]:disabled {
cursor: not-allowed;
}
/* ============================================
CUSTOM RADIO matches the checkbox visual
============================================ */
input[type="radio"] {
appearance: none;
-webkit-appearance: none;
width: 16px;
height: 16px;
flex-shrink: 0;
border: 1.5px solid var(--border-soft);
border-radius: 50%;
background: var(--bg-card);
cursor: pointer;
position: relative;
transition: background 0.18s, border-color 0.18s, box-shadow 0.18s, transform 0.12s;
vertical-align: middle;
}
input[type="radio"]:hover:not(:disabled) {
border-color: rgba(145, 70, 255, 0.6);
}
input[type="radio"]:checked {
border-color: var(--accent);
background: var(--bg-card);
}
input[type="radio"]:checked::after {
content: '';
position: absolute;
inset: 3px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 6px rgba(145, 70, 255, 0.45);
}
input[type="radio"]:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(145, 70, 255, 0.25);
}
input[type="radio"]:active:not(:disabled) {
transform: scale(0.92);
}
/* ============================================
CUSTOM SELECT chevron via inline SVG background
============================================ */