a11y: bring UI to WCAG 2.2 AAA compliance
Semantic HTML: lang attr, landmarks (header/main/region/complementary), heading hierarchy (h1-h3), dl/dt/dd for info panel. ARIA: labels on all icon buttons, aria-hidden on decorative icons, progressbar role with dynamic aria-valuenow, aria-haspopup/expanded on all menu triggers, role=listbox/option on playlist, aria-selected, computed aria-labels on playlist rows. Contrast: raised --textMuted/--textDim/--icon to AAA 7:1 ratios. Focus: global :focus-visible outline, slider thumb glow, menu item highlight, switch focus-within, row focus styles. Target sizes: 44x44 hit areas on zoom/window/remove buttons via ::before pseudo-elements. Keyboard: playlist arrow nav + Enter/Space activate + Alt+Arrow reorder with live region announcements + move buttons. Speed menu, subtitles menu, and recent menu all keyboard-navigable with Arrow/Enter/Space/Escape. Dividers resizable via Arrow keys. Dynamic document.title updates on video/folder load.
This commit is contained in:
174
src/index.html
174
src/index.html
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
@@ -8,12 +8,12 @@
|
||||
<body>
|
||||
<div id="zoomRoot">
|
||||
<div class="app">
|
||||
<div class="topbar">
|
||||
<div class="brand">
|
||||
<div class="appIcon" aria-hidden="true"><div class="appIconGlow"></div><i class="fa-solid fa-graduation-cap"></i></div>
|
||||
<div class="brandText">
|
||||
<div class="appName">TutorialVault</div>
|
||||
<div class="tagline">Watch local tutorials, resume instantly, and actually finish them.</div>
|
||||
<header class="topbar" data-tauri-drag-region role="banner">
|
||||
<div class="brand" data-tauri-drag-region>
|
||||
<div class="appIcon" data-tauri-drag-region aria-hidden="true"><div class="appIconGlow"></div><i class="fa-solid fa-graduation-cap" data-tauri-drag-region></i></div>
|
||||
<div class="brandText" data-tauri-drag-region>
|
||||
<h1 class="appName" data-tauri-drag-region>TutorialVault</h1>
|
||||
<div class="tagline" data-tauri-drag-region>Watch local tutorials, resume instantly, and actually finish them.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
|
||||
<div class="actionGroup">
|
||||
<div class="zoomControl" data-tooltip="UI Zoom" data-tooltip-desc="Adjust the interface zoom level">
|
||||
<button class="zoomBtn" id="zoomOutBtn"><i class="fa-solid fa-minus"></i></button>
|
||||
<span class="zoomValue" id="zoomResetBtn">100%</span>
|
||||
<button class="zoomBtn" id="zoomInBtn"><i class="fa-solid fa-plus"></i></button>
|
||||
<button class="zoomBtn" id="zoomOutBtn" aria-label="Zoom out"><i class="fa-solid fa-minus" aria-hidden="true"></i></button>
|
||||
<button class="zoomValue" id="zoomResetBtn" aria-label="Reset zoom">100%</button>
|
||||
<button class="zoomBtn" id="zoomInBtn" aria-label="Zoom in"><i class="fa-solid fa-plus" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,39 +46,47 @@
|
||||
|
||||
<div class="actionGroup">
|
||||
<div class="splitBtn primary">
|
||||
<button class="btn primary" id="chooseBtn" data-tooltip="Open Folder" data-tooltip-desc="Browse and select a folder containing videos"><i class="fa-solid fa-folder-open"></i> Open folder</button>
|
||||
<button class="btn drop" id="chooseDropBtn" data-tooltip="Recent Folders" data-tooltip-desc="Open a recently used folder"><i class="fa-solid fa-chevron-down"></i></button>
|
||||
<button class="btn primary" id="chooseBtn" data-tooltip="Open Folder" data-tooltip-desc="Browse and select a folder containing videos"><i class="fa-solid fa-folder-open" aria-hidden="true"></i> Open folder</button>
|
||||
<button class="btn drop" id="chooseDropBtn" aria-label="Recent folders" data-tooltip="Recent Folders" data-tooltip-desc="Open a recently used folder"><i class="fa-solid fa-chevron-down" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionDivider"></div>
|
||||
|
||||
<div class="actionGroup">
|
||||
<button class="toolbarBtn" id="resetProgBtn" data-tooltip="Reset Progress" data-tooltip-desc="Reset DONE / NOW progress for this folder (keeps notes, volume, etc.)"><i class="fa-solid fa-clock-rotate-left"></i></button>
|
||||
<button class="toolbarBtn" id="refreshBtn" data-tooltip="Reload" data-tooltip-desc="Reload the current folder"><i class="fa-solid fa-arrows-rotate"></i></button>
|
||||
</div>
|
||||
<button class="toolbarBtn" id="resetProgBtn" aria-label="Reset progress" data-tooltip="Reset Progress" data-tooltip-desc="Reset DONE / NOW progress for this folder (keeps notes, volume, etc.)"><i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i></button>
|
||||
<button class="toolbarBtn" id="refreshBtn" aria-label="Reload folder" data-tooltip="Reload" data-tooltip-desc="Reload the current folder"><i class="fa-solid fa-arrows-rotate" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="actionDivider"></div>
|
||||
|
||||
<div class="actionGroup windowControls">
|
||||
<button class="toolbarBtn winBtn" id="winMinBtn" aria-label="Minimize" data-tooltip="Minimize" data-tooltip-desc="Minimize window"><i class="fa-solid fa-minus" aria-hidden="true"></i></button>
|
||||
<button class="toolbarBtn winBtn" id="winMaxBtn" aria-label="Maximize" data-tooltip="Maximize" data-tooltip-desc="Maximize or restore window"><i class="fa-solid fa-square" aria-hidden="true"></i></button>
|
||||
<button class="toolbarBtn winBtn winClose" id="winCloseBtn" aria-label="Close" data-tooltip="Close" data-tooltip-desc="Close window"><i class="fa-solid fa-xmark" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="dropdownPortal" id="recentMenu"></div>
|
||||
|
||||
<div class="content" id="contentGrid">
|
||||
<div class="panel">
|
||||
<main class="content" id="contentGrid" role="main">
|
||||
<div class="panel" role="region" aria-label="Video player">
|
||||
<div class="panelHeader">
|
||||
<div style="min-width:0;">
|
||||
<div class="nowTitle" id="nowTitle">No video loaded</div>
|
||||
<h2 class="nowTitle" id="nowTitle">No video loaded</h2>
|
||||
<div class="nowSub" id="nowSub">-</div>
|
||||
</div>
|
||||
|
||||
<div class="progressPill" data-tooltip="Overall Progress" data-tooltip-desc="Folder completion (time-based, using cached durations when available)">
|
||||
<div class="progressLabel">Overall</div>
|
||||
<div class="progressBar"><div id="overallBar"></div></div>
|
||||
<div class="progressBar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="Overall folder progress"><div id="overallBar"></div></div>
|
||||
<div class="progressPct" id="overallPct">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="videoWrap">
|
||||
<video id="player" preload="metadata" crossorigin="anonymous"></video>
|
||||
<video id="player" preload="metadata" crossorigin="anonymous" aria-label="Video player"></video>
|
||||
<div class="videoOverlay" id="videoOverlay">
|
||||
<div class="overlayIcon" id="overlayIcon">
|
||||
<i class="fa-solid fa-play" id="overlayIconI"></i>
|
||||
@@ -87,39 +95,52 @@
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="controlsRow">
|
||||
<div class="group">
|
||||
<button class="iconBtn" id="prevBtn" data-tooltip="Previous" data-tooltip-desc="Go to previous video"><i class="fa-solid fa-backward-step"></i></button>
|
||||
<div class="seekWrap">
|
||||
<div class="seekTrack">
|
||||
<div class="seekFill" id="seekFill"></div>
|
||||
</div>
|
||||
<input type="range" id="seek" class="seek" min="0" max="1000" step="1" value="0" aria-label="Seek">
|
||||
</div>
|
||||
|
||||
<button class="iconBtn primary" id="playPauseBtn" data-tooltip="Play/Pause" data-tooltip-desc="Toggle video playback">
|
||||
<i class="fa-solid fa-play" id="ppIcon"></i>
|
||||
<div class="controlsStrip">
|
||||
<div class="group">
|
||||
<button class="iconBtn" id="prevBtn" aria-label="Previous video" data-tooltip="Previous" data-tooltip-desc="Go to previous video"><i class="fa-solid fa-backward-step" aria-hidden="true"></i></button>
|
||||
|
||||
<button class="iconBtn primary" id="playPauseBtn" aria-label="Play" data-tooltip="Play/Pause" data-tooltip-desc="Toggle video playback">
|
||||
<i class="fa-solid fa-play" id="ppIcon" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button class="iconBtn" id="nextBtn" data-tooltip="Next" data-tooltip-desc="Go to next video"><i class="fa-solid fa-forward-step"></i></button>
|
||||
<button class="iconBtn" id="nextBtn" aria-label="Next video" data-tooltip="Next" data-tooltip-desc="Go to next video"><i class="fa-solid fa-forward-step" aria-hidden="true"></i></button>
|
||||
|
||||
<div class="stripDivider"></div>
|
||||
|
||||
<div class="timeChip" data-tooltip="Time" data-tooltip-desc="Current position / Total duration">
|
||||
<div class="timeDot"></div>
|
||||
<div><span id="timeNow">00:00</span> <span style="color:rgba(165,172,196,.65)">/</span> <span id="timeDur">00:00</span></div>
|
||||
<div><span id="timeNow">00:00</span> <span class="timeSep">/</span> <span id="timeDur">00:00</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="subsBox">
|
||||
<button class="iconBtn" id="subsBtn" data-tooltip="Subtitles" data-tooltip-desc="Load or select subtitles"><i class="fa-regular fa-closed-captioning"></i></button>
|
||||
<button class="iconBtn" id="subsBtn" aria-label="Subtitles" data-tooltip="Subtitles" data-tooltip-desc="Load or select subtitles"><i class="fa-regular fa-closed-captioning" aria-hidden="true"></i></button>
|
||||
<div class="subsMenu" id="subsMenu" role="menu"></div>
|
||||
</div>
|
||||
|
||||
<div class="stripDivider"></div>
|
||||
|
||||
<div class="miniCtl" data-tooltip="Volume" data-tooltip-desc="Adjust volume (saved per folder)">
|
||||
<i class="fa-solid fa-volume-high"></i>
|
||||
<i class="fa-solid fa-volume-high" aria-hidden="true"></i>
|
||||
<div class="volWrap">
|
||||
<div class="volTrack">
|
||||
<div class="volFill" id="volFill"></div>
|
||||
</div>
|
||||
<input type="range" id="volSlider" class="vol" min="0" max="1" step="0.01" value="1">
|
||||
<input type="range" id="volSlider" class="vol" min="0" max="1" step="0.01" value="1" aria-label="Volume">
|
||||
</div>
|
||||
<div class="volTooltip" id="volTooltip">100%</div>
|
||||
</div>
|
||||
|
||||
<div class="stripDivider"></div>
|
||||
|
||||
<div class="miniCtl" data-tooltip="Speed" data-tooltip-desc="Playback speed (saved per folder)">
|
||||
<svg id="speedIcon" width="14" height="14" viewBox="0 0 24 24" style="overflow:visible; color:var(--iconStrong);">
|
||||
<path d="M12 22C6.5 22 2 17.5 2 12S6.5 2 12 2s10 4.5 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity=".5"/>
|
||||
@@ -138,75 +159,70 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="iconBtn" id="fsBtn" data-tooltip="Fullscreen" data-tooltip-desc="Toggle fullscreen mode"><i class="fa-solid fa-expand"></i></button>
|
||||
<div class="stripDivider"></div>
|
||||
|
||||
<button class="iconBtn" id="fsBtn" aria-label="Toggle fullscreen" data-tooltip="Fullscreen" data-tooltip-desc="Toggle fullscreen mode"><i class="fa-solid fa-expand" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="seekWrap">
|
||||
<div class="seekTrack">
|
||||
<div class="seekFill" id="seekFill"></div>
|
||||
</div>
|
||||
<input type="range" id="seek" class="seek" min="0" max="1000" step="1" value="0" aria-label="Seek">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dock" id="dockGrid">
|
||||
<div class="dock" id="dockGrid" role="complementary" aria-label="Details">
|
||||
<div class="dockPane">
|
||||
<div class="dockInner">
|
||||
<div class="dockHeader" id="notesHeader" data-tooltip="Notes" data-tooltip-desc="Your notes are automatically saved for each video file. Write timestamps, TODOs, or reminders.">
|
||||
<div class="dockTitle"><i class="fa-solid fa-note-sticky"></i> Notes</div>
|
||||
<h3 class="dockTitle"><i class="fa-solid fa-note-sticky" aria-hidden="true"></i> Notes</h3>
|
||||
</div>
|
||||
<div class="notesArea">
|
||||
<textarea class="notes" id="notesBox" placeholder="Write timestamps, TODOs, reminders…"></textarea>
|
||||
<div class="notesSaved" id="notesSaved"><i class="fa-solid fa-check"></i> Saved</div>
|
||||
<textarea class="notes" id="notesBox" aria-label="Notes for current video" placeholder="Write timestamps, TODOs, reminders…"></textarea>
|
||||
<div class="notesSaved" id="notesSaved"><i class="fa-solid fa-check" aria-hidden="true"></i> Saved</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dockDividerWrap">
|
||||
<div class="dockDivider" id="dockDivider" data-tooltip="Resize" data-tooltip-desc="Drag to resize panels"></div>
|
||||
<div class="dockDivider" id="dockDivider"></div>
|
||||
</div>
|
||||
|
||||
<div class="dockPane">
|
||||
<div class="dockInner">
|
||||
<div class="dockHeader" id="infoHeader" data-tooltip="Info" data-tooltip-desc="Metadata and progress info for the current folder and video. Updates automatically.">
|
||||
<div class="dockTitle"><i class="fa-solid fa-circle-info"></i> Info</div>
|
||||
<h3 class="dockTitle"><i class="fa-solid fa-circle-info" aria-hidden="true"></i> Info</h3>
|
||||
</div>
|
||||
<div class="infoGrid" id="infoGrid">
|
||||
<div class="kv">
|
||||
<div class="k">Folder</div><div class="v" id="infoFolder">-</div>
|
||||
<div class="k">Next up</div><div class="v" id="infoNext">-</div>
|
||||
<div class="k">Structure</div><div class="v mono" id="infoStruct">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">Folder</dt><dd class="v" id="infoFolder">-</dd>
|
||||
<dt class="k">Next up</dt><dd class="v" id="infoNext">-</dd>
|
||||
<dt class="k">Structure</dt><dd class="v mono" id="infoStruct">-</dd>
|
||||
</dl>
|
||||
|
||||
<div class="kv">
|
||||
<div class="k">Title</div><div class="v" id="infoTitle">-</div>
|
||||
<div class="k">Relpath</div><div class="v mono" id="infoRel">-</div>
|
||||
<div class="k">Position</div><div class="v mono" id="infoPos">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">Title</dt><dd class="v" id="infoTitle">-</dd>
|
||||
<dt class="k">Relpath</dt><dd class="v mono" id="infoRel">-</dd>
|
||||
<dt class="k">Position</dt><dd class="v mono" id="infoPos">-</dd>
|
||||
</dl>
|
||||
|
||||
<div class="kv">
|
||||
<div class="k">File</div><div class="v mono" id="infoFileBits">-</div>
|
||||
<div class="k">Video</div><div class="v mono" id="infoVidBits">-</div>
|
||||
<div class="k">Audio</div><div class="v mono" id="infoAudBits">-</div>
|
||||
<div class="k">Subtitles</div><div class="v mono" id="infoSubsBits">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">File</dt><dd class="v mono" id="infoFileBits">-</dd>
|
||||
<dt class="k">Video</dt><dd class="v mono" id="infoVidBits">-</dd>
|
||||
<dt class="k">Audio</dt><dd class="v mono" id="infoAudBits">-</dd>
|
||||
<dt class="k">Subtitles</dt><dd class="v mono" id="infoSubsBits">-</dd>
|
||||
</dl>
|
||||
|
||||
<div class="kv">
|
||||
<div class="k">Finished</div><div class="v mono" id="infoFinished">-</div>
|
||||
<div class="k">Remaining</div><div class="v mono" id="infoRemaining">-</div>
|
||||
<div class="k">ETA</div><div class="v mono" id="infoEta">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">Finished</dt><dd class="v mono" id="infoFinished">-</dd>
|
||||
<dt class="k">Remaining</dt><dd class="v mono" id="infoRemaining">-</dd>
|
||||
<dt class="k"><abbr title="Estimated time to finish">ETA</abbr></dt><dd class="v mono" id="infoEta">-</dd>
|
||||
</dl>
|
||||
|
||||
<div class="kv">
|
||||
<div class="k">Volume</div><div class="v mono" id="infoVolume">-</div>
|
||||
<div class="k">Speed</div><div class="v mono" id="infoSpeed">-</div>
|
||||
<div class="k">Durations</div><div class="v mono" id="infoKnown">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">Volume</dt><dd class="v mono" id="infoVolume">-</dd>
|
||||
<dt class="k">Speed</dt><dd class="v mono" id="infoSpeed">-</dd>
|
||||
<dt class="k">Durations</dt><dd class="v mono" id="infoKnown">-</dd>
|
||||
</dl>
|
||||
|
||||
<div class="kv">
|
||||
<div class="k">Top folders</div><div class="v mono" id="infoTop">-</div>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt class="k">Top folders</dt><dd class="v mono" id="infoTop">-</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,12 +231,12 @@
|
||||
</div>
|
||||
|
||||
<div class="dividerWrap">
|
||||
<div class="divider" id="divider" data-tooltip="Resize" data-tooltip-desc="Drag to resize panels"></div>
|
||||
<div class="divider" id="divider"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel" role="region" aria-label="Playlist">
|
||||
<div class="panelHeader" style="align-items:center;">
|
||||
<div class="playlistHeader" id="plistHeader" data-tooltip="Playlist" data-tooltip-desc="Drag items to reorder. The blue line shows where it will drop."><i class="fa-solid fa-list"></i> Playlist</div>
|
||||
<h2 class="playlistHeader" id="plistHeader" data-tooltip="Playlist" data-tooltip-desc="Drag items to reorder. The blue line shows where it will drop."><i class="fa-solid fa-list" aria-hidden="true"></i> Playlist</h2>
|
||||
<div style="flex:1 1 auto;"></div>
|
||||
</div>
|
||||
<div class="listWrap">
|
||||
@@ -231,13 +247,13 @@
|
||||
No videos found (searched recursively).<br/>Native playback is happiest with MP4 (H.264/AAC) or WebM.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast" aria-live="polite">
|
||||
<div class="toastInner">
|
||||
<div class="toastIcon"><i class="fa-solid fa-circle-info"></i></div>
|
||||
<div class="toastIcon"><i class="fa-solid fa-circle-info" aria-hidden="true"></i></div>
|
||||
<div class="toastMsg" id="toastMsg">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
28
src/main.ts
28
src/main.ts
@@ -3,18 +3,15 @@
|
||||
* Orchestrates all modules and holds cross-module callbacks.
|
||||
*/
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
import '@fontsource/sora/500.css';
|
||||
import '@fontsource/sora/600.css';
|
||||
import '@fontsource/sora/700.css';
|
||||
import '@fontsource/sora/800.css';
|
||||
import '@fontsource/manrope/400.css';
|
||||
import '@fontsource/manrope/500.css';
|
||||
import '@fontsource/manrope/600.css';
|
||||
import '@fontsource/manrope/700.css';
|
||||
import '@fontsource/manrope/800.css';
|
||||
import '@fontsource/ibm-plex-mono/400.css';
|
||||
import '@fontsource/ibm-plex-mono/500.css';
|
||||
import '@fontsource/ibm-plex-mono/600.css';
|
||||
import '@fontsource/fraunces/600.css';
|
||||
import '@fontsource/fraunces/700.css';
|
||||
import '@fontsource/fraunces/800.css';
|
||||
import '@fontsource/inter/400.css';
|
||||
import '@fontsource/inter/500.css';
|
||||
import '@fontsource/inter/600.css';
|
||||
import '@fontsource/inter/700.css';
|
||||
import '@fontsource/space-mono/400.css';
|
||||
import '@fontsource/space-mono/700.css';
|
||||
import './styles/main.css';
|
||||
import './styles/player.css';
|
||||
import './styles/playlist.css';
|
||||
@@ -37,7 +34,7 @@ import {
|
||||
getVideoDuration, getPlayer, isVolDragging,
|
||||
} from './player';
|
||||
|
||||
import { initPlaylist, renderList } from './playlist';
|
||||
import { initPlaylist, renderList, isDragging } from './playlist';
|
||||
import { initSubtitles, refreshSubtitles, clearSubtitles } from './subtitles';
|
||||
import {
|
||||
initUI, applyZoom, applySplit, applyDockSplit,
|
||||
@@ -97,6 +94,7 @@ async function boot(): Promise<void> {
|
||||
updateInfoPanel();
|
||||
buildSpeedMenu(1.0);
|
||||
notify('Open a folder to begin.');
|
||||
document.title = 'TutorialVault - Open a folder';
|
||||
}
|
||||
|
||||
// ---- onLibraryLoaded ----
|
||||
@@ -123,6 +121,7 @@ async function onLibraryLoaded(info: LibraryInfo, startScan: boolean): Promise<v
|
||||
updateOverall();
|
||||
renderList();
|
||||
updateInfoPanel();
|
||||
document.title = info?.folder ? `${info.folder} - TutorialVault` : 'TutorialVault';
|
||||
|
||||
if (startScan) {
|
||||
try { await api.startDurationScan(); } catch (_) {}
|
||||
@@ -144,6 +143,7 @@ async function loadIndex(
|
||||
|
||||
const it = library.items[currentIndex] || null;
|
||||
updateNowHeader(it);
|
||||
document.title = it ? `${it.title || it.name} - TutorialVault` : 'TutorialVault';
|
||||
|
||||
await api.setCurrent(currentIndex, Number(timecode || 0.0));
|
||||
renderList();
|
||||
@@ -197,7 +197,7 @@ async function tick(): Promise<void> {
|
||||
updateInfoPanel();
|
||||
updateNowHeader(currentItem());
|
||||
|
||||
if (oldIndex !== currentIndex || oldCount !== (library?.items?.length || 0)) {
|
||||
if (!isDragging() && (oldIndex !== currentIndex || oldCount !== (library?.items?.length || 0))) {
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ export function initPlayer(): void {
|
||||
timeNow = document.getElementById('timeNow')!;
|
||||
timeDur = document.getElementById('timeDur')!;
|
||||
speedBtn = document.getElementById('speedBtn')!;
|
||||
speedBtn.setAttribute('aria-haspopup', 'true');
|
||||
speedBtn.setAttribute('aria-expanded', 'false');
|
||||
speedBtnText = document.getElementById('speedBtnText')!;
|
||||
speedIcon = document.getElementById('speedIcon') as unknown as SVGElement;
|
||||
speedMenu = document.getElementById('speedMenu')!;
|
||||
@@ -173,7 +175,14 @@ export function initPlayer(): void {
|
||||
speedBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
if (speedMenu.classList.contains('show')) closeSpeedMenu();
|
||||
else openSpeedMenu();
|
||||
else {
|
||||
openSpeedMenu();
|
||||
const first = speedMenu.querySelector('[role="menuitem"]') as HTMLElement | null;
|
||||
if (first) first.focus();
|
||||
}
|
||||
});
|
||||
speedBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') { closeSpeedMenu(); speedBtn.focus(); }
|
||||
});
|
||||
window.addEventListener('click', () => { closeSpeedMenu(); });
|
||||
speedMenu.addEventListener('click', (e) => e.stopPropagation());
|
||||
@@ -227,6 +236,7 @@ export function nextPrev(delta: number): void {
|
||||
export function updatePlayPauseIcon(): void {
|
||||
if (!ppIcon) return;
|
||||
ppIcon.className = (player.paused || player.ended) ? 'fa-solid fa-play' : 'fa-solid fa-pause';
|
||||
playPauseBtn.setAttribute('aria-label', (player.paused || player.ended) ? 'Play' : 'Pause');
|
||||
}
|
||||
|
||||
export function updateTimeReadout(): void {
|
||||
@@ -328,8 +338,14 @@ export async function loadVideoSrc(
|
||||
|
||||
const SPEEDS = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0];
|
||||
|
||||
export function closeSpeedMenu(): void { speedMenu?.classList.remove('show'); }
|
||||
export function openSpeedMenu(): void { speedMenu?.classList.add('show'); }
|
||||
export function closeSpeedMenu(): void {
|
||||
speedMenu?.classList.remove('show');
|
||||
speedBtn?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
export function openSpeedMenu(): void {
|
||||
speedMenu?.classList.add('show');
|
||||
speedBtn?.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
|
||||
export function buildSpeedMenu(active: number): void {
|
||||
if (!speedMenu) return;
|
||||
@@ -338,6 +354,25 @@ export function buildSpeedMenu(active: number): void {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'speedItem' + (Math.abs(s - active) < 0.0001 ? ' active' : '');
|
||||
row.setAttribute('role', 'menuitem');
|
||||
row.tabIndex = -1;
|
||||
row.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const next = row.nextElementSibling as HTMLElement | null;
|
||||
if (next) next.focus();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const prev = row.previousElementSibling as HTMLElement | null;
|
||||
if (prev) prev.focus();
|
||||
} else if (e.key === 'Escape' || e.key === 'Tab') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
closeSpeedMenu();
|
||||
speedBtn.focus();
|
||||
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
row.click();
|
||||
}
|
||||
});
|
||||
|
||||
const left = document.createElement('div');
|
||||
left.style.display = 'flex'; left.style.alignItems = 'center'; left.style.gap = '10px';
|
||||
@@ -371,21 +406,21 @@ export function updateSpeedIcon(rate: number): void {
|
||||
let needleColor = 'rgba(255,255,255,.85)';
|
||||
|
||||
if (rate <= 0.5) {
|
||||
needleAngle = -60;
|
||||
needleAngle = -150;
|
||||
needleColor = 'rgba(100,180,255,.9)';
|
||||
} else if (rate < 1.0) {
|
||||
const t = (rate - 0.5) / 0.5;
|
||||
needleAngle = -60 + t * 60;
|
||||
needleAngle = -150 + t * 150;
|
||||
needleColor = `rgba(${Math.round(100 + t * 155)},${Math.round(180 + t * 75)},${Math.round(255)},0.9)`;
|
||||
} else if (rate <= 1.0) {
|
||||
needleAngle = 0;
|
||||
needleColor = 'rgba(255,255,255,.85)';
|
||||
} else if (rate < 2.0) {
|
||||
const t = (rate - 1.0) / 1.0;
|
||||
needleAngle = t * 75;
|
||||
needleAngle = t * 150;
|
||||
needleColor = `rgba(255,${Math.round(255 - t * 115)},${Math.round(255 - t * 155)},0.9)`;
|
||||
} else {
|
||||
needleAngle = 75;
|
||||
needleAngle = 150;
|
||||
needleColor = 'rgba(255,140,100,.9)';
|
||||
}
|
||||
|
||||
|
||||
111
src/playlist.ts
111
src/playlist.ts
@@ -21,6 +21,9 @@ let dragFromIndex: number | null = null;
|
||||
let dropTargetIndex: number | null = null;
|
||||
let dropAfter = false;
|
||||
|
||||
/** True while a drag-reorder is in progress. Used to guard against renderList during drag. */
|
||||
export function isDragging(): boolean { return dragFromIndex !== null; }
|
||||
|
||||
// ---- Scrollbar state ----
|
||||
let scrollbarHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let scrollbarDragging = false;
|
||||
@@ -32,6 +35,8 @@ let updateListFades: () => void = () => {};
|
||||
|
||||
export function initPlaylist(): void {
|
||||
listEl = document.getElementById('list')!;
|
||||
listEl.setAttribute('role', 'listbox');
|
||||
listEl.setAttribute('aria-label', 'Playlist');
|
||||
emptyHint = document.getElementById('emptyHint')!;
|
||||
listScrollbar = document.getElementById('listScrollbar')!;
|
||||
listScrollbarThumb = document.getElementById('listScrollbarThumb')!;
|
||||
@@ -81,6 +86,13 @@ export function initPlaylist(): void {
|
||||
window.addEventListener('touchend', endDrag);
|
||||
}
|
||||
|
||||
// Allow internal DnD on the list container
|
||||
if (listEl) {
|
||||
listEl.addEventListener('dragenter', (e) => { e.preventDefault(); });
|
||||
listEl.addEventListener('dragover', (e) => { e.preventDefault(); });
|
||||
listEl.addEventListener('drop', (e) => { e.preventDefault(); });
|
||||
}
|
||||
|
||||
if (listEl) {
|
||||
updateListFades = () => {
|
||||
const atTop = listEl.scrollTop < 5;
|
||||
@@ -98,6 +110,14 @@ export function initPlaylist(): void {
|
||||
setTimeout(updateListFades, 100);
|
||||
setTimeout(updateListFades, 500);
|
||||
}
|
||||
|
||||
// Live region for reorder announcements
|
||||
const liveRegion = document.createElement('div');
|
||||
liveRegion.id = 'playlistLive';
|
||||
liveRegion.setAttribute('aria-live', 'polite');
|
||||
liveRegion.setAttribute('aria-atomic', 'true');
|
||||
liveRegion.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);';
|
||||
document.body.appendChild(liveRegion);
|
||||
}
|
||||
|
||||
export function updateScrollbar(): void {
|
||||
@@ -151,7 +171,7 @@ async function reorderPlaylistByGap(fromIdx: number, targetIdx: number, after: b
|
||||
await cb.refreshCurrentVideoMeta?.();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (err) { console.error('reorderPlaylistByGap failed:', err); }
|
||||
}
|
||||
|
||||
export function renderTreeSvg(it: VideoItem): SVGSVGElement {
|
||||
@@ -246,12 +266,56 @@ export function renderList(): void {
|
||||
row.className = 'row' + (it.index === currentIndex ? ' active' : '');
|
||||
row.draggable = true;
|
||||
row.dataset.index = String(it.index);
|
||||
row.setAttribute('role', 'option');
|
||||
row.setAttribute('aria-selected', it.index === currentIndex ? 'true' : 'false');
|
||||
row.tabIndex = 0;
|
||||
|
||||
// Computed aria-label
|
||||
const durStr = it.duration ? `${fmtTime(it.watched || 0)} / ${fmtTime(it.duration)}` : `${fmtTime(it.watched || 0)} watched`;
|
||||
const statusStr = it.index === currentIndex ? 'Now playing' : it.finished ? 'Done' : '';
|
||||
row.setAttribute('aria-label', `${String(displayIndex + 1).padStart(padN, '0')}. ${it.title || it.name} - ${durStr}${statusStr ? ' - ' + statusStr : ''}`);
|
||||
|
||||
row.onclick = () => {
|
||||
if (dragFromIndex !== null) return;
|
||||
cb.loadIndex?.(it.index, computeResumeTime(it), true);
|
||||
};
|
||||
|
||||
row.addEventListener('keydown', (e) => {
|
||||
const key = e.key;
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
cb.loadIndex?.(it.index, computeResumeTime(it), true);
|
||||
} else if (key === 'ArrowDown' && !e.altKey) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const next = row.nextElementSibling as HTMLElement | null;
|
||||
if (next && next.classList.contains('row')) next.focus();
|
||||
} else if (key === 'ArrowUp' && !e.altKey) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const prev = row.previousElementSibling as HTMLElement | null;
|
||||
if (prev && prev.classList.contains('row')) prev.focus();
|
||||
} else if (e.altKey && key === 'ArrowDown' && displayIndex < library!.items!.length - 1) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
reorderPlaylistByGap(it.index, library!.items![displayIndex + 1].index, true).then(() => {
|
||||
setTimeout(() => {
|
||||
const moved = listEl.querySelector(`[data-index="${it.index}"]`) as HTMLElement | null;
|
||||
if (moved) moved.focus();
|
||||
const lr = document.getElementById('playlistLive');
|
||||
if (lr) lr.textContent = `Moved to position ${displayIndex + 2}`;
|
||||
}, 100);
|
||||
});
|
||||
} else if (e.altKey && key === 'ArrowUp' && displayIndex > 0) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
reorderPlaylistByGap(it.index, library!.items![displayIndex - 1].index, false).then(() => {
|
||||
setTimeout(() => {
|
||||
const moved = listEl.querySelector(`[data-index="${it.index}"]`) as HTMLElement | null;
|
||||
if (moved) moved.focus();
|
||||
const lr = document.getElementById('playlistLive');
|
||||
if (lr) lr.textContent = `Moved to position ${displayIndex}`;
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
row.addEventListener('dragstart', (e) => {
|
||||
dragFromIndex = Number(row.dataset.index);
|
||||
row.classList.add('dragging');
|
||||
@@ -268,8 +332,14 @@ export function renderList(): void {
|
||||
clearDropIndicators();
|
||||
});
|
||||
|
||||
row.addEventListener('dragenter', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
row.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer!.dropEffect = 'move';
|
||||
const rect = row.getBoundingClientRect();
|
||||
const y = e.clientY - rect.top;
|
||||
@@ -280,7 +350,7 @@ export function renderList(): void {
|
||||
row.classList.add(after ? 'drop-after' : 'drop-before');
|
||||
});
|
||||
|
||||
row.addEventListener('drop', (e) => e.preventDefault());
|
||||
row.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); });
|
||||
|
||||
const left = document.createElement('div');
|
||||
left.className = 'left';
|
||||
@@ -316,7 +386,44 @@ export function renderList(): void {
|
||||
else if (it.finished) { tag.classList.add('done'); tag.textContent = 'Done'; }
|
||||
else { tag.classList.add('hidden'); tag.textContent = ''; }
|
||||
|
||||
// Move buttons for keyboard reorder alternative
|
||||
const moveWrap = document.createElement('div');
|
||||
moveWrap.className = 'moveWrap';
|
||||
|
||||
if (displayIndex > 0) {
|
||||
const moveUp = document.createElement('button');
|
||||
moveUp.className = 'moveBtn';
|
||||
moveUp.setAttribute('aria-label', 'Move up');
|
||||
moveUp.innerHTML = '<i class="fa-solid fa-chevron-up" aria-hidden="true"></i>';
|
||||
moveUp.tabIndex = -1;
|
||||
moveUp.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
reorderPlaylistByGap(it.index, library!.items![displayIndex - 1].index, false).then(() => {
|
||||
const lr = document.getElementById('playlistLive');
|
||||
if (lr) lr.textContent = `Moved to position ${displayIndex}`;
|
||||
});
|
||||
});
|
||||
moveWrap.appendChild(moveUp);
|
||||
}
|
||||
|
||||
if (displayIndex < library!.items!.length - 1) {
|
||||
const moveDown = document.createElement('button');
|
||||
moveDown.className = 'moveBtn';
|
||||
moveDown.setAttribute('aria-label', 'Move down');
|
||||
moveDown.innerHTML = '<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>';
|
||||
moveDown.tabIndex = -1;
|
||||
moveDown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
reorderPlaylistByGap(it.index, library!.items![displayIndex + 1].index, true).then(() => {
|
||||
const lr = document.getElementById('playlistLive');
|
||||
if (lr) lr.textContent = `Moved to position ${displayIndex + 2}`;
|
||||
});
|
||||
});
|
||||
moveWrap.appendChild(moveDown);
|
||||
}
|
||||
|
||||
row.appendChild(left);
|
||||
row.appendChild(moveWrap);
|
||||
row.appendChild(tag);
|
||||
listEl.appendChild(row);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
transform-origin:bottom left;
|
||||
pointer-events:none;
|
||||
opacity:0;
|
||||
transition:opacity .25s ease, transform .25s cubic-bezier(.4,0,.2,1);
|
||||
transition:opacity .25s ease, transform .35s var(--ease-bounce);
|
||||
}
|
||||
#toast.show{
|
||||
opacity:1;
|
||||
@@ -17,127 +17,84 @@
|
||||
.toastInner{
|
||||
pointer-events:none;
|
||||
display:flex; align-items:center; gap:12px;
|
||||
padding:12px 14px;
|
||||
border-radius:7px;
|
||||
border:1px solid rgba(255,255,255,.14);
|
||||
background:rgba(18,20,26,.92);
|
||||
box-shadow:0 26px 70px rgba(0,0,0,.70);
|
||||
backdrop-filter:blur(16px);
|
||||
padding:10px 14px;
|
||||
border-radius:var(--r);
|
||||
border:1px solid rgba(140,160,210,.10);
|
||||
background:rgba(18,21,30,.95);
|
||||
box-shadow:var(--shadow2);
|
||||
}
|
||||
.toastIcon{width:18px; height:18px; display:flex; align-items:center; justify-content:center;}
|
||||
.toastIcon .fa{font-size:14px; color:rgba(230,236,252,.82)!important; opacity:.95;}
|
||||
.toastIcon .fa{font-size:14px; color:rgba(200,212,238,.78)!important; opacity:.95; transition:transform .3s var(--ease-bounce);}
|
||||
#toast.show .toastIcon .fa{animation:toastIconIn .4s var(--ease-bounce);}
|
||||
@keyframes toastIconIn{
|
||||
0%{transform:scale(0) rotate(-90deg);}
|
||||
60%{transform:scale(1.2) rotate(5deg);}
|
||||
100%{transform:scale(1) rotate(0);}
|
||||
}
|
||||
.toastMsg{
|
||||
font-size:12.8px;
|
||||
font-weight:760;
|
||||
letter-spacing:.12px;
|
||||
color:rgba(246,248,255,.92);
|
||||
font-size:13px;
|
||||
font-weight:600;
|
||||
letter-spacing:0;
|
||||
color:var(--text);
|
||||
}
|
||||
|
||||
/* Toolbar icon buttons */
|
||||
/* Toolbar icon buttons — borderless */
|
||||
.toolbarIcon{
|
||||
width:36px; height:36px;
|
||||
border-radius:var(--r2);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.025));
|
||||
border:1px solid rgba(255,255,255,.1);
|
||||
color:rgba(246,248,255,.88);
|
||||
background:var(--surface-2);
|
||||
border:none;
|
||||
color:rgba(218,225,240,.85);
|
||||
font-size:14px;
|
||||
transition:all .2s cubic-bezier(.4,0,.2,1);
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0,0,0,.12),
|
||||
0 4px 12px rgba(0,0,0,.15),
|
||||
inset 0 1px 0 rgba(255,255,255,.08);
|
||||
}
|
||||
.toolbarIcon::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.1), transparent 50%);
|
||||
opacity:0;
|
||||
transition:opacity .2s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
}
|
||||
.toolbarIcon:hover{
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.05));
|
||||
border-color:rgba(255,255,255,.18);
|
||||
color:rgba(255,255,255,.98);
|
||||
transform:translateY(-2px);
|
||||
box-shadow:
|
||||
0 4px 8px rgba(0,0,0,.15),
|
||||
0 8px 20px rgba(0,0,0,.2),
|
||||
inset 0 1px 0 rgba(255,255,255,.12);
|
||||
background:var(--surface-3);
|
||||
color:rgba(235,240,252,.95);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
.toolbarIcon:hover::before{opacity:1;}
|
||||
.toolbarIcon:active{
|
||||
transform:translateY(0);
|
||||
box-shadow:0 2px 6px rgba(0,0,0,.18);
|
||||
transform:scale(.9) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
|
||||
/* Fancy tooltip */
|
||||
/* Tooltip */
|
||||
.tooltip{
|
||||
position:fixed;
|
||||
pointer-events:none;
|
||||
z-index:99999;
|
||||
border-radius:var(--r);
|
||||
padding:16px 20px;
|
||||
padding:14px 16px;
|
||||
opacity:0;
|
||||
transform:translateY(8px) scale(.97);
|
||||
transition:opacity .25s ease, transform .25s cubic-bezier(.4,0,.2,1), left .12s ease, top .12s ease;
|
||||
transform:translateY(6px) scale(.96);
|
||||
transition:opacity .2s ease, transform .25s var(--ease-bounce), left .12s ease, top .12s ease;
|
||||
max-width:320px;
|
||||
font-family:var(--sans);
|
||||
overflow:hidden;
|
||||
background:rgba(20,24,32,.5);
|
||||
backdrop-filter:blur(8px) saturate(1.3);
|
||||
-webkit-backdrop-filter:blur(8px) saturate(1.3);
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0,0,0,.3),
|
||||
0 4px 8px rgba(0,0,0,.15),
|
||||
0 12px 24px rgba(0,0,0,.25),
|
||||
0 24px 48px rgba(0,0,0,.2);
|
||||
background:rgba(18,21,30,.95);
|
||||
border:1px solid rgba(140,160,210,.10);
|
||||
box-shadow:var(--shadow2);
|
||||
}
|
||||
.tooltip.visible{
|
||||
opacity:1;
|
||||
transform:translateY(0) scale(1);
|
||||
}
|
||||
.tooltip::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
top:0; left:0; right:0;
|
||||
height:1px;
|
||||
background:linear-gradient(90deg, transparent 5%, rgba(95,175,255,.5) 30%, rgba(75,200,130,.4) 70%, transparent 95%);
|
||||
border-radius:var(--r) var(--r) 0 0;
|
||||
}
|
||||
.tooltip::after{
|
||||
content:"";
|
||||
position:absolute;
|
||||
top:1px; left:1px; right:1px;
|
||||
height:40%;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.05), transparent);
|
||||
border-radius:var(--r) var(--r) 0 0;
|
||||
pointer-events:none;
|
||||
}
|
||||
.tooltip::before{display:none;}
|
||||
.tooltip::after{display:none;}
|
||||
.tooltip-title{
|
||||
font-family:var(--brand);
|
||||
font-weight:800;
|
||||
font-weight:700;
|
||||
font-size:14px;
|
||||
margin-bottom:8px;
|
||||
margin-bottom:6px;
|
||||
letter-spacing:-.01em;
|
||||
background:linear-gradient(135deg, #fff 0%, rgba(180,210,255,1) 50%, rgba(150,230,200,1) 100%);
|
||||
-webkit-background-clip:text;
|
||||
background-clip:text;
|
||||
color:transparent;
|
||||
text-shadow:none;
|
||||
position:relative;
|
||||
z-index:1;
|
||||
color:rgba(235,240,252,.95);
|
||||
}
|
||||
.tooltip-desc{
|
||||
font-family:var(--sans);
|
||||
font-size:12px;
|
||||
font-weight:500;
|
||||
color:rgba(190,200,225,.88);
|
||||
color:rgba(170,182,210,.82);
|
||||
line-height:1.55;
|
||||
letter-spacing:.01em;
|
||||
letter-spacing:0;
|
||||
position:relative;
|
||||
z-index:1;
|
||||
}
|
||||
@@ -147,36 +104,46 @@
|
||||
.subsBox{position:relative;}
|
||||
.subsMenu{
|
||||
position:absolute; left:50%; bottom:calc(100% + 10px);
|
||||
transform:translateX(-50%);
|
||||
transform:translateX(-50%) scale(.95);
|
||||
min-width:220px; padding:8px;
|
||||
border-radius:7px; border:1px solid rgba(255,255,255,.12);
|
||||
background:rgba(18,20,26,.94);
|
||||
box-shadow:0 26px 70px rgba(0,0,0,.70);
|
||||
backdrop-filter:blur(16px);
|
||||
border-radius:var(--r); border:1px solid rgba(140,160,210,.10);
|
||||
background:rgba(18,21,30,.95);
|
||||
box-shadow:var(--shadow);
|
||||
display:none; z-index:30;
|
||||
opacity:0;
|
||||
transition:opacity .15s ease, transform .2s var(--ease-bounce);
|
||||
}
|
||||
.subsMenu.show{display:block;}
|
||||
.subsMenuHeader{padding:6px 12px 4px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.08em; color:rgba(150,160,190,.6);}
|
||||
.subsMenuItem{padding:10px 12px; border-radius:5px; cursor:pointer; user-select:none; font-size:12px; font-weight:600; color:rgba(246,248,255,.92); letter-spacing:.08px; display:flex; align-items:center; gap:10px; transition:background .12s ease;}
|
||||
.subsMenuItem:hover{background:rgba(255,255,255,.06);}
|
||||
.subsMenuItem .fa{font-size:13px; color:var(--iconStrong)!important; opacity:.85; width:18px; text-align:center;}
|
||||
.subsMenuItem.embedded{color:rgba(180,220,255,.95);}
|
||||
.subsDivider{height:1px; background:rgba(255,255,255,.08); margin:6px 4px;}
|
||||
.subsEmpty{padding:10px 12px; color:rgba(165,172,196,.7); font-size:11.5px; text-align:center;}
|
||||
.subsMenu.show{display:block; opacity:1; transform:translateX(-50%) scale(1);}
|
||||
.subsMenuHeader{padding:6px 12px 4px; font-size:10px; font-weight:600; text-transform:uppercase; letter-spacing:.08em; color:var(--textDim); transition:color .2s ease;}
|
||||
.subsMenuItem{padding:10px 12px; border-radius:var(--r3); cursor:pointer; user-select:none; font-size:12px; font-weight:600; color:var(--text); letter-spacing:0; display:flex; align-items:center; gap:10px; transition:all .2s var(--ease-bounce);}
|
||||
.subsMenuItem:hover{background:var(--surfaceHover); padding-left:16px;}
|
||||
.subsMenuItem:active{transform:scale(.97); transition-duration:.08s;}
|
||||
.subsMenuItem .fa{font-size:13px; color:var(--iconStrong)!important; opacity:.85; width:18px; text-align:center; transition:transform .2s var(--ease-bounce), opacity .15s ease;}
|
||||
.subsMenuItem:hover .fa{transform:scale(1.15) rotate(-5deg); opacity:1;}
|
||||
.subsMenuItem.embedded{color:rgba(150,185,230,.92);}
|
||||
.subsDivider{height:1px; background:rgba(140,160,210,.06); margin:6px 4px;}
|
||||
.subsEmpty{padding:10px 12px; color:var(--textDim); font-size:11px; text-align:center;}
|
||||
|
||||
.speedMenu{
|
||||
position:absolute; right:0; bottom:calc(100% + 10px);
|
||||
min-width:180px; padding:8px;
|
||||
border-radius:7px; border:1px solid rgba(255,255,255,.12);
|
||||
background:rgba(18,20,26,.94);
|
||||
box-shadow:0 26px 70px rgba(0,0,0,.70);
|
||||
backdrop-filter:blur(16px);
|
||||
border-radius:var(--r); border:1px solid rgba(140,160,210,.10);
|
||||
background:rgba(18,21,30,.95);
|
||||
box-shadow:var(--shadow);
|
||||
display:none; z-index:30;
|
||||
transform:translateY(8px) scale(.96); opacity:0;
|
||||
transition:transform .18s cubic-bezier(.175,.885,.32,1.275), opacity .15s ease;
|
||||
opacity:0;
|
||||
transform:scale(.95) translateY(4px);
|
||||
transition:opacity .15s ease, transform .2s var(--ease-bounce);
|
||||
}
|
||||
.speedMenu.show{display:block; transform:translateY(0) scale(1); opacity:1;}
|
||||
.speedItem{padding:10px 10px; border-radius:6px; cursor:pointer; user-select:none; font-family:var(--mono); font-size:14px; color:rgba(246,248,255,.92); letter-spacing:.10px; display:flex; align-items:center; justify-content:space-between; gap:10px; transition:all .12s ease;}
|
||||
.speedItem:hover{background:rgba(255,255,255,.06); transform:translateX(3px);}
|
||||
.speedItem .dot{width:8px; height:8px; border-radius:999px; background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.12); flex:0 0 auto; transition:all .15s ease;}
|
||||
.speedItem.active .dot{background:radial-gradient(circle at 30% 30%, rgba(255,255,255,.92), rgba(100,180,255,.55)); box-shadow:0 0 0 3px rgba(100,180,255,.10); border-color:rgba(100,180,255,.24);}
|
||||
.speedMenu.show{display:block; opacity:1; transform:scale(1) translateY(0);}
|
||||
.speedItem{padding:10px 10px; border-radius:var(--r3); cursor:pointer; user-select:none; font-family:var(--mono); font-size:14px; color:var(--text); letter-spacing:.02em; display:flex; align-items:center; justify-content:space-between; gap:10px; transition:all .2s var(--ease-bounce);}
|
||||
.speedItem:hover{background:var(--surfaceHover); padding-left:14px;}
|
||||
.speedItem:active{transform:scale(.97); transition-duration:.08s;}
|
||||
.speedItem .dot{width:8px; height:8px; border-radius:999px; background:rgba(140,165,220,.06); border:none; flex:0 0 auto; transition:all .2s var(--ease-bounce);}
|
||||
.speedItem:hover .dot{transform:scale(1.3);}
|
||||
.speedItem.active .dot{background:rgba(136,164,196,.65);}
|
||||
.speedItem.active:hover .dot{background:rgba(136,164,196,.80); box-shadow:0 0 6px rgba(136,164,196,.25);}
|
||||
|
||||
.subsMenuItem:focus-visible{background:var(--surfaceHover); padding-left:16px; outline:none;}
|
||||
.speedItem:focus-visible{background:var(--surfaceHover); padding-left:14px; outline:none;}
|
||||
.dropItem:focus-visible{background:var(--surfaceHover); padding-left:16px; outline:none;}
|
||||
|
||||
@@ -1,54 +1,74 @@
|
||||
:root{
|
||||
--zoom:1;
|
||||
/* Base backgrounds */
|
||||
--bg0:#060709; --bg1:#0a0c10;
|
||||
/* Strokes - consistent opacity scale */
|
||||
--stroke:rgba(255,255,255,.07);
|
||||
--strokeLight:rgba(255,255,255,.04);
|
||||
--strokeMed:rgba(255,255,255,.10);
|
||||
/* Text - consistent hierarchy */
|
||||
--text:rgba(240,244,255,.91);
|
||||
--textMuted:rgba(155,165,190,.68);
|
||||
--textDim:rgba(120,132,165,.50);
|
||||
/* Surfaces */
|
||||
--surface:rgba(255,255,255,.025);
|
||||
--surfaceHover:rgba(255,255,255,.045);
|
||||
--surfaceActive:rgba(255,255,255,.06);
|
||||
/* Shadows */
|
||||
--shadow:0 16px 48px rgba(0,0,0,.50);
|
||||
--shadow2:0 8px 24px rgba(0,0,0,.32);
|
||||
/* Radii */
|
||||
--r:6px; --r2:5px;
|
||||
/* Base backgrounds — cool dark slate, lighter */
|
||||
--bg0:#0f1117; --bg1:#151821;
|
||||
/* Strokes — cool, very subtle */
|
||||
--stroke:rgba(140,160,210,.07);
|
||||
--strokeLight:rgba(140,160,210,.04);
|
||||
--strokeMed:rgba(140,160,210,.09);
|
||||
--strokeStrong:rgba(140,160,210,.14);
|
||||
/* Text — cool white hierarchy */
|
||||
--text:rgba(218,225,240,.90);
|
||||
--textMuted:rgba(160,174,204,.72);
|
||||
--textDim:rgba(158,174,208,.68);
|
||||
/* Surfaces — cool-tinted, subtle fills */
|
||||
--surface-0:rgba(140,165,220,.02);
|
||||
--surface:rgba(140,165,220,.03);
|
||||
--surface-2:rgba(140,165,220,.045);
|
||||
--surface-3:rgba(140,165,220,.065);
|
||||
--surface-4:rgba(140,165,220,.085);
|
||||
--surfaceHover:rgba(140,165,220,.055);
|
||||
--surfaceActive:rgba(140,165,220,.07);
|
||||
/* Shadows — minimal */
|
||||
--shadow:0 8px 24px rgba(0,0,0,.25);
|
||||
--shadow2:0 4px 12px rgba(0,0,0,.15);
|
||||
--shadow3:none;
|
||||
--shadowFloat:0 6px 20px rgba(0,0,0,.20);
|
||||
--shadowInset:inset 0 1px 3px rgba(0,0,0,.12);
|
||||
/* Radii — architectural */
|
||||
--r:6px; --r2:4px; --r3:3px;
|
||||
/* Easing — refined, springy */
|
||||
--ease-spring:cubic-bezier(.25,.46,.45,.94);
|
||||
--ease-bounce:cubic-bezier(.34,1.56,.64,1);
|
||||
--ease-out-back:cubic-bezier(.34,1.3,.64,1);
|
||||
/* Fonts */
|
||||
--mono:"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--sans:"Manrope", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
--brand:"Sora","Manrope", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
/* Icons */
|
||||
--icon:rgba(175,185,210,.65);
|
||||
--iconStrong:rgba(220,228,248,.85);
|
||||
/* Accent - consistent blue */
|
||||
--accent:rgb(95,175,255);
|
||||
--accentGlow:rgba(95,175,255,.12);
|
||||
--accentBorder:rgba(95,175,255,.22);
|
||||
--accentBg:rgba(95,175,255,.07);
|
||||
/* Success - consistent green */
|
||||
--success:rgb(75,200,130);
|
||||
--successBg:rgba(75,200,130,.07);
|
||||
--successBorder:rgba(75,200,130,.20);
|
||||
/* Tree */
|
||||
--tree:rgba(195,205,230,.10);
|
||||
--treeNode:rgba(200,212,238,.52);
|
||||
--mono:"Space Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--sans:"Inter", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
--brand:"Fraunces", Georgia, "Times New Roman", serif;
|
||||
/* Icons — cool */
|
||||
--icon:rgba(160,175,210,.62);
|
||||
--iconStrong:rgba(200,212,238,.75);
|
||||
/* Accent — steel blue */
|
||||
--accent:#88A4C4;
|
||||
--accentGlow:rgba(136,164,196,.08);
|
||||
--accentBorder:rgba(136,164,196,.18);
|
||||
--accentBg:rgba(136,164,196,.06);
|
||||
/* Success — muted sage */
|
||||
--success:rgb(130,170,130);
|
||||
--successBg:rgba(130,170,130,.06);
|
||||
--successBorder:rgba(130,170,130,.15);
|
||||
/* Tree — cool */
|
||||
--tree:rgba(140,165,220,.07);
|
||||
--treeNode:rgba(200,212,238,.40);
|
||||
}
|
||||
*{box-sizing:border-box;}
|
||||
html,body{height:100%;}
|
||||
body{
|
||||
margin:0; padding:0; font-family:var(--sans); color:var(--text); overflow:hidden;
|
||||
width:100vw; height:100vh;
|
||||
font-weight:400;
|
||||
line-height:1.4;
|
||||
background:
|
||||
radial-gradient(800px 500px at 10% 5%, rgba(95,175,255,.08), transparent 60%),
|
||||
radial-gradient(700px 500px at 90% 8%, rgba(75,200,130,.05), transparent 65%),
|
||||
radial-gradient(900px 600px at 8% 3%, rgba(100,140,210,.04), transparent 55%),
|
||||
radial-gradient(600px 400px at 92% 97%, rgba(90,120,180,.03), transparent 60%),
|
||||
linear-gradient(180deg, var(--bg1), var(--bg0));
|
||||
letter-spacing:.04px;
|
||||
letter-spacing:0;
|
||||
}
|
||||
*:focus{outline:none;}
|
||||
*:focus-visible{
|
||||
outline:2px solid rgba(136,164,196,.65);
|
||||
outline-offset:2px;
|
||||
border-radius:inherit;
|
||||
}
|
||||
#zoomRoot{
|
||||
transform:scale(var(--zoom));
|
||||
@@ -62,14 +82,12 @@ body{
|
||||
.fa, i.fa-solid, i.fa-regular, i.fa-light, i.fa-thin{color:var(--icon)!important;}
|
||||
|
||||
.topbar{
|
||||
display:flex; align-items:center; gap:14px;
|
||||
padding:12px 14px;
|
||||
height:72px;
|
||||
flex:0 0 72px;
|
||||
display:flex; align-items:center; gap:12px;
|
||||
padding:10px 12px;
|
||||
height:62px;
|
||||
flex:0 0 62px;
|
||||
border-bottom:1px solid var(--stroke);
|
||||
background:
|
||||
linear-gradient(135deg, transparent 0%, transparent 48%, rgba(255,255,255,.015) 50%, transparent 52%, transparent 100%) 0 0 / 8px 8px,
|
||||
linear-gradient(180deg, rgba(22,26,38,1), rgba(18,22,32,1));
|
||||
background:#181d2a;
|
||||
min-width:0; z-index:5; position:relative;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
@@ -78,29 +96,22 @@ body{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:
|
||||
radial-gradient(circle, rgba(180,210,255,.096) 2px, transparent 2.5px) 0 0 / 12px 12px,
|
||||
radial-gradient(circle, rgba(220,230,255,.08) 2px, transparent 2.5px) 6px 6px / 12px 12px;
|
||||
radial-gradient(circle, rgba(140,170,220,.08) 2px, transparent 2.5px) 0 0 / 12px 12px,
|
||||
radial-gradient(circle, rgba(160,185,230,.05) 2px, transparent 2.5px) 6px 6px / 12px 12px;
|
||||
pointer-events:none;
|
||||
z-index:1;
|
||||
-webkit-mask-image: linear-gradient(90deg, black 0%, rgba(0,0,0,.5) 20%, transparent 40%);
|
||||
mask-image: linear-gradient(90deg, black 0%, rgba(0,0,0,.5) 20%, transparent 40%);
|
||||
}
|
||||
.topbar::after{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.03), transparent);
|
||||
pointer-events:none;
|
||||
z-index:0;
|
||||
}
|
||||
.topbar::after{display:none;}
|
||||
|
||||
.brand{display:flex; align-items:center; gap:12px; min-width:0; flex:1 1 auto; position:relative; z-index:1;}
|
||||
.brand{display:flex; align-items:center; gap:12px; min-width:0; flex:1 1 auto; position:relative; z-index:1; user-select:none; cursor:default;}
|
||||
.appIcon{
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
flex:0 0 auto;
|
||||
filter: drop-shadow(0 8px 16px rgba(0,0,0,.35));
|
||||
overflow:visible;
|
||||
transition: transform .4s cubic-bezier(.34,1.56,.64,1), filter .3s ease;
|
||||
transition: transform .4s var(--ease-spring), filter .3s ease;
|
||||
cursor:pointer;
|
||||
position:relative;
|
||||
}
|
||||
@@ -117,7 +128,7 @@ body{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
border-radius:50%;
|
||||
background:radial-gradient(circle, rgba(100,180,255,.25), rgba(130,230,180,.15), transparent 70%);
|
||||
background:radial-gradient(circle, rgba(100,160,240,.25), rgba(130,210,200,.15), transparent 70%);
|
||||
transform:scale(.5);
|
||||
transition:transform .4s ease;
|
||||
}
|
||||
@@ -126,7 +137,7 @@ body{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
border-radius:50%;
|
||||
background:conic-gradient(from 0deg, rgba(100,180,255,.5), rgba(130,230,180,.5), rgba(210,160,255,.5), rgba(100,180,255,.5));
|
||||
background:conic-gradient(from 0deg, rgba(100,160,240,.5), rgba(130,210,200,.5), rgba(170,150,230,.5), rgba(100,160,240,.5));
|
||||
mask:radial-gradient(circle, transparent 45%, black 47%, black 53%, transparent 55%);
|
||||
-webkit-mask:radial-gradient(circle, transparent 45%, black 47%, black 53%, transparent 55%);
|
||||
animation:none;
|
||||
@@ -149,7 +160,7 @@ body{
|
||||
}
|
||||
.appIcon:hover{
|
||||
animation:logoWiggle .8s ease-out;
|
||||
filter: drop-shadow(0 0 20px rgba(100,180,255,.5)) drop-shadow(0 0 40px rgba(130,230,180,.3)) drop-shadow(0 12px 24px rgba(0,0,0,.4));
|
||||
filter: drop-shadow(0 0 20px rgba(100,160,240,.5)) drop-shadow(0 0 40px rgba(130,210,200,.3)) drop-shadow(0 12px 24px rgba(0,0,0,.4));
|
||||
}
|
||||
.appIcon:hover .appIconGlow{
|
||||
opacity:1;
|
||||
@@ -160,10 +171,14 @@ body{
|
||||
.appIcon:hover .appIconGlow::after{
|
||||
animation:logoSpin 3s linear infinite;
|
||||
}
|
||||
.appIcon:active{
|
||||
transform:scale(.92);
|
||||
transition-duration:.1s;
|
||||
}
|
||||
.appIcon i{
|
||||
font-size:36px;
|
||||
line-height:1;
|
||||
background:linear-gradient(135deg, rgba(100,180,255,.98), rgba(130,230,180,.92), rgba(210,160,255,.82));
|
||||
background:linear-gradient(135deg, rgba(120,170,240,.95), rgba(140,210,200,.88), rgba(170,150,230,.82));
|
||||
-webkit-background-clip:text;
|
||||
background-clip:text;
|
||||
color:transparent!important;
|
||||
@@ -174,35 +189,35 @@ body{
|
||||
z-index:2;
|
||||
}
|
||||
.appIcon:hover i{
|
||||
background:linear-gradient(135deg, rgba(130,210,255,1), rgba(160,250,200,1), rgba(230,180,255,1));
|
||||
background:linear-gradient(135deg, rgba(140,190,255,1), rgba(160,230,215,1), rgba(190,170,245,1));
|
||||
-webkit-background-clip:text;
|
||||
background-clip:text;
|
||||
}
|
||||
.brandText{min-width:0; position:relative; z-index:1;}
|
||||
.appName{
|
||||
font-family:var(--brand);
|
||||
font-weight:900;
|
||||
font-weight:800;
|
||||
font-size:18px;
|
||||
line-height:1.02;
|
||||
letter-spacing:.35px;
|
||||
letter-spacing:-.02em;
|
||||
margin:0; padding:0;
|
||||
transition:text-shadow .3s ease;
|
||||
transition:text-shadow .3s var(--ease-spring), color .2s ease;
|
||||
}
|
||||
.brand:hover .appName{
|
||||
text-shadow:0 0 20px rgba(100,180,255,.4), 0 0 40px rgba(130,230,180,.2);
|
||||
text-shadow:0 0 20px rgba(100,160,240,.4), 0 0 40px rgba(130,210,200,.2);
|
||||
}
|
||||
.tagline{
|
||||
margin-top:5px;
|
||||
font-size:11.5px;
|
||||
font-size:11px;
|
||||
line-height:1.2;
|
||||
color:rgba(180,188,210,.76);
|
||||
letter-spacing:.18px;
|
||||
color:var(--textMuted);
|
||||
letter-spacing:.02em;
|
||||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||||
max-width:52vw;
|
||||
transition:color .3s ease;
|
||||
transition:color .3s var(--ease-spring);
|
||||
}
|
||||
.brand:hover .tagline{
|
||||
color:rgba(200,210,230,.9);
|
||||
color:rgba(180,192,218,.82);
|
||||
}
|
||||
|
||||
.actions{
|
||||
@@ -215,270 +230,253 @@ body{
|
||||
}
|
||||
.actionDivider{
|
||||
width:1px; height:28px;
|
||||
background:linear-gradient(180deg, transparent, rgba(255,255,255,.12) 20%, rgba(255,255,255,.12) 80%, transparent);
|
||||
background:rgba(140,160,210,.08);
|
||||
margin:0 4px;
|
||||
transition:background .3s ease;
|
||||
}
|
||||
|
||||
/* Zoom control */
|
||||
/* Zoom control — borderless */
|
||||
.zoomControl{
|
||||
display:flex; align-items:center; gap:0;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.015));
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
border-radius:8px;
|
||||
background:var(--surface-2);
|
||||
border:none;
|
||||
border-radius:var(--r2);
|
||||
padding:2px;
|
||||
box-shadow:0 2px 8px rgba(0,0,0,.15), inset 0 1px 0 rgba(255,255,255,.05);
|
||||
transition:background .2s ease;
|
||||
}
|
||||
.zoomControl:hover{
|
||||
background:var(--surface-3);
|
||||
}
|
||||
.zoomBtn{
|
||||
width:28px; height:28px;
|
||||
border:none; background:transparent;
|
||||
border-radius:6px;
|
||||
border:none; background:transparent; position:relative;
|
||||
border-radius:var(--r3);
|
||||
color:var(--text);
|
||||
cursor:pointer;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
transition:all .15s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
}
|
||||
.zoomBtn:hover{
|
||||
background:rgba(255,255,255,.08);
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
.zoomBtn:active{
|
||||
background:rgba(255,255,255,.12);
|
||||
transform:scale(.95);
|
||||
background:var(--surface-4);
|
||||
transform:scale(.9) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.zoomBtn .fa{font-size:10px; opacity:.8;}
|
||||
.zoomBtn::before{content:""; position:absolute; inset:-8px; border-radius:var(--r2);}
|
||||
.zoomBtn .fa{font-size:10px; opacity:.8; transition:opacity .15s ease, transform .15s ease;}
|
||||
.zoomBtn:hover .fa{opacity:1; transform:scale(1.1);}
|
||||
.zoomValue{
|
||||
min-width:48px;
|
||||
text-align:center;
|
||||
font-family:var(--mono);
|
||||
font-size:11px;
|
||||
font-weight:600;
|
||||
font-weight:400;
|
||||
color:var(--text);
|
||||
opacity:.9;
|
||||
cursor:pointer;
|
||||
padding:4px 6px;
|
||||
border-radius:4px;
|
||||
transition:background .15s ease;
|
||||
border-radius:var(--r3);
|
||||
transition:background .15s ease, opacity .15s ease, transform .15s ease;
|
||||
}
|
||||
.zoomValue:hover{
|
||||
background:rgba(255,255,255,.06);
|
||||
background:var(--surface-3);
|
||||
opacity:1;
|
||||
}
|
||||
.zoomValue:active{
|
||||
transform:scale(.95);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
|
||||
/* Toolbar buttons */
|
||||
/* Toolbar buttons — borderless */
|
||||
.toolbarBtn{
|
||||
width:34px; height:34px;
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
border-radius:8px;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
|
||||
border:none;
|
||||
border-radius:var(--r2);
|
||||
background:var(--surface-2);
|
||||
color:var(--text);
|
||||
cursor:pointer;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
transition:all .2s cubic-bezier(.4,0,.2,1);
|
||||
transition:all .2s var(--ease-bounce);
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
box-shadow:0 2px 6px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.06);
|
||||
}
|
||||
.toolbarBtn::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:radial-gradient(circle at 50% 0%, rgba(255,255,255,.15), transparent 70%);
|
||||
opacity:0;
|
||||
transition:opacity .2s ease;
|
||||
}
|
||||
.toolbarBtn:hover{
|
||||
border-color:rgba(255,255,255,.15);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.03));
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.2), inset 0 1px 0 rgba(255,255,255,.1);
|
||||
}
|
||||
.toolbarBtn:hover::before{opacity:1;}
|
||||
.toolbarBtn:active{
|
||||
transform:translateY(0);
|
||||
box-shadow:0 1px 4px rgba(0,0,0,.15);
|
||||
transform:scale(.92) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.toolbarBtn .fa{font-size:13px; opacity:.85; transition:transform .2s ease;}
|
||||
.toolbarBtn:hover .fa{transform:scale(1.1);}
|
||||
.toolbarBtn .fa{font-size:13px; opacity:.85; transition:opacity .15s ease, transform .2s var(--ease-bounce);}
|
||||
.toolbarBtn:hover .fa{opacity:1; transform:scale(1.1);}
|
||||
|
||||
/* Primary split button styling */
|
||||
/* Window control buttons */
|
||||
.winBtn{
|
||||
width:30px; height:30px;
|
||||
border-radius:var(--r2);
|
||||
flex:0 0 auto;
|
||||
position:relative;
|
||||
}
|
||||
.winBtn::before{content:""; position:absolute; inset:-7px; border-radius:var(--r2);}
|
||||
.winBtn .fa{font-size:11px!important; transition:transform .15s var(--ease-bounce)!important;}
|
||||
.winBtn:hover .fa{transform:scale(1.15)!important;}
|
||||
.winClose:hover{
|
||||
background:rgba(255,70,70,.14)!important;
|
||||
}
|
||||
.winClose:hover .fa{color:rgba(255,120,120,.95)!important;}
|
||||
.winClose:active{
|
||||
background:rgba(255,70,70,.22)!important;
|
||||
}
|
||||
.windowControls{gap:4px; margin-left:2px;}
|
||||
|
||||
/* Primary split button — borderless */
|
||||
.splitBtn.primary{
|
||||
border-color:rgba(95,175,255,.25);
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.12), rgba(95,175,255,.04));
|
||||
box-shadow:0 2px 8px rgba(0,0,0,.15), 0 4px 20px rgba(95,175,255,.1), inset 0 1px 0 rgba(255,255,255,.1);
|
||||
background:rgba(136,164,196,.10);
|
||||
}
|
||||
.splitBtn.primary:hover{
|
||||
border-color:rgba(95,175,255,.4);
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.2), 0 8px 32px rgba(95,175,255,.15), inset 0 1px 0 rgba(255,255,255,.15);
|
||||
background:rgba(136,164,196,.15);
|
||||
}
|
||||
.splitBtn.primary .drop{
|
||||
border-left-color:rgba(95,175,255,.2);
|
||||
border-left-color:rgba(140,160,210,.08);
|
||||
}
|
||||
.btn{
|
||||
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
||||
padding:9px 14px;
|
||||
border-radius:var(--r2);
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
|
||||
border:none;
|
||||
background:var(--surface-2);
|
||||
color:var(--text);
|
||||
cursor:pointer; user-select:none;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0,0,0,.2),
|
||||
0 8px 24px rgba(0,0,0,.25),
|
||||
inset 0 1px 0 rgba(255,255,255,.08),
|
||||
inset 0 -1px 0 rgba(0,0,0,.1);
|
||||
transition:all .2s cubic-bezier(.4,0,.2,1);
|
||||
font-size:12.5px; font-weight:700; letter-spacing:.02em;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
.btn::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.1), transparent 50%);
|
||||
opacity:0;
|
||||
transition:opacity .2s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
font-size:13px; font-weight:600; letter-spacing:0;
|
||||
}
|
||||
.btn:hover{
|
||||
border-color:rgba(255,255,255,.15);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.09), rgba(255,255,255,.04));
|
||||
transform:translateY(-2px);
|
||||
box-shadow:
|
||||
0 4px 8px rgba(0,0,0,.2),
|
||||
0 12px 32px rgba(0,0,0,.3),
|
||||
inset 0 1px 0 rgba(255,255,255,.12),
|
||||
inset 0 -1px 0 rgba(0,0,0,.1);
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
.btn:hover::before{opacity:1;}
|
||||
.btn:active{
|
||||
transform:translateY(0);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0,0,0,.2),
|
||||
0 4px 12px rgba(0,0,0,.2),
|
||||
inset 0 1px 0 rgba(255,255,255,.06);
|
||||
transform:scale(.96) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.btn.primary{
|
||||
border-color:rgba(95,175,255,.3);
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.2), rgba(95,175,255,.08));
|
||||
background:rgba(136,164,196,.12);
|
||||
color:#fff;
|
||||
text-shadow:0 1px 2px rgba(0,0,0,.3);
|
||||
}
|
||||
.btn.primary::before{
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.15), transparent 60%);
|
||||
}
|
||||
.btn.primary:hover{
|
||||
border-color:rgba(95,175,255,.45);
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.28), rgba(95,175,255,.12));
|
||||
box-shadow:
|
||||
0 4px 8px rgba(0,0,0,.2),
|
||||
0 12px 32px rgba(95,175,255,.2),
|
||||
0 0 0 1px rgba(95,175,255,.1),
|
||||
inset 0 1px 0 rgba(255,255,255,.15);
|
||||
background:rgba(136,164,196,.18);
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 4px 12px rgba(136,164,196,.12);
|
||||
}
|
||||
.btn .fa{font-size:14px; opacity:.95; color:var(--iconStrong)!important; transition:transform .2s ease; position:relative; z-index:1;}
|
||||
.btn .fa{font-size:14px; opacity:.95; color:var(--iconStrong)!important; transition:transform .2s var(--ease-bounce);}
|
||||
.btn:hover .fa{transform:scale(1.08);}
|
||||
.btn.primary .fa{color:#fff!important;}
|
||||
.btn:hover .fa{transform:scale(1.1);}
|
||||
|
||||
.splitBtn{
|
||||
display:inline-flex;
|
||||
border-radius:var(--r2);
|
||||
overflow:hidden;
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0,0,0,.2),
|
||||
0 8px 24px rgba(0,0,0,.25),
|
||||
inset 0 1px 0 rgba(255,255,255,.06);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
|
||||
border:none;
|
||||
background:var(--surface-2);
|
||||
position:relative; z-index:8;
|
||||
transition:all .2s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
}
|
||||
.splitBtn:hover{
|
||||
border-color:rgba(255,255,255,.14);
|
||||
box-shadow:
|
||||
0 4px 8px rgba(0,0,0,.2),
|
||||
0 12px 32px rgba(0,0,0,.3),
|
||||
inset 0 1px 0 rgba(255,255,255,.08);
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
.splitBtn:active{
|
||||
transform:scale(.98) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.splitBtn .btn{border:none; box-shadow:none; border-radius:0; background:transparent; padding:9px 12px; transform:none;}
|
||||
.splitBtn .btn::before{display:none;}
|
||||
.splitBtn .btn:hover{background:rgba(255,255,255,.06); transform:none; box-shadow:none;}
|
||||
.splitBtn .btn:hover{background:var(--surface-3); transform:none; box-shadow:none;}
|
||||
.splitBtn .drop{
|
||||
width:40px; padding:8px 0;
|
||||
border-left:1px solid rgba(255,255,255,.08);
|
||||
border-left:1px solid rgba(140,160,210,.06);
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
transition:background .15s ease;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.02), transparent);
|
||||
background:transparent;
|
||||
}
|
||||
.splitBtn .drop:hover{background:rgba(255,255,255,.06);}
|
||||
.splitBtn .drop .fa{font-size:16px; opacity:.88; color:var(--iconStrong)!important; transition:transform .2s ease;}
|
||||
.splitBtn .drop:hover .fa{transform:translateY(2px);}
|
||||
.splitBtn .drop:hover{background:var(--surface-3);}
|
||||
.splitBtn .drop .fa{font-size:16px; opacity:.88; color:var(--iconStrong)!important; transition:transform .25s var(--ease-bounce);}
|
||||
.splitBtn .drop:hover .fa{transform:translateY(3px) scale(1.1);}
|
||||
|
||||
.dropdownPortal{
|
||||
position:fixed; z-index:99999;
|
||||
min-width:320px; max-width:560px; max-height:360px;
|
||||
overflow:auto;
|
||||
border-radius:7px;
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
background:rgba(18,20,26,.94);
|
||||
box-shadow:0 26px 70px rgba(0,0,0,.70);
|
||||
backdrop-filter:blur(16px);
|
||||
border-radius:var(--r);
|
||||
border:1px solid rgba(140,160,210,.10);
|
||||
background:rgba(18,21,30,.95);
|
||||
box-shadow:var(--shadow);
|
||||
padding:6px;
|
||||
display:none;
|
||||
transform:scale(var(--zoom));
|
||||
transform-origin:top left;
|
||||
scrollbar-width:thin;
|
||||
scrollbar-color:rgba(255,255,255,.14) rgba(255,255,255,.02);
|
||||
scrollbar-color:rgba(140,160,210,.12) rgba(140,160,210,.02);
|
||||
}
|
||||
.dropdownPortal::-webkit-scrollbar{width:4px; height:4px;}
|
||||
.dropdownPortal::-webkit-scrollbar-track{background:rgba(255,255,255,.015);}
|
||||
.dropdownPortal::-webkit-scrollbar-thumb{background:rgba(255,255,255,.11); border-radius:999px;}
|
||||
.dropdownPortal::-webkit-scrollbar-track{background:rgba(140,160,210,.02);}
|
||||
.dropdownPortal::-webkit-scrollbar-thumb{background:rgba(140,160,210,.10); border-radius:999px;}
|
||||
.dropdownPortal::-webkit-scrollbar-button{width:0; height:0; display:none;}
|
||||
|
||||
.dropItem{display:flex; align-items:center; gap:10px; padding:10px 10px; border-radius:6px; cursor:pointer; user-select:none; color:rgba(246,248,255,.92); font-weight:760; font-size:12.7px; letter-spacing:.12px; line-height:1.25; transition:all .15s ease; position:relative;}
|
||||
.dropItem:hover{background:rgba(255,255,255,.06); padding-right:36px;}
|
||||
.dropItem:active{transform:none;}
|
||||
.dropIcon{width:18px; height:18px; display:flex; align-items:center; justify-content:center; flex:0 0 auto; opacity:.9; transition:transform .2s ease;}
|
||||
.dropItem:hover .dropIcon{transform:scale(1.1);}
|
||||
.dropItem{display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:var(--r3); cursor:pointer; user-select:none; color:var(--text); font-weight:600; font-size:13px; letter-spacing:0; line-height:1.25; transition:all .2s var(--ease-bounce); position:relative;}
|
||||
.dropItem:hover{background:var(--surfaceHover); padding-left:16px; padding-right:36px;}
|
||||
.dropItem:active{transform:scale(.98); transition-duration:.08s;}
|
||||
.dropIcon{width:18px; height:18px; display:flex; align-items:center; justify-content:center; flex:0 0 auto; opacity:.9; transition:transform .25s var(--ease-bounce), opacity .15s ease;}
|
||||
.dropItem:hover .dropIcon{transform:scale(1.15) rotate(-8deg); opacity:1;}
|
||||
.dropIcon .fa{font-size:14px; color:var(--iconStrong)!important;}
|
||||
.dropName{white-space:nowrap; flex:1 1 auto; min-width:0; transition:mask-image .15s ease, -webkit-mask-image .15s ease;}
|
||||
.dropItem:hover .dropName{overflow:hidden; mask-image:linear-gradient(90deg, #000 80%, transparent 100%); -webkit-mask-image:linear-gradient(90deg, #000 80%, transparent 100%);}
|
||||
.dropRemove{position:absolute; right:8px; top:50%; transform:translateY(-50%); width:24px; height:24px; border-radius:8px; background:rgba(255,100,100,.15); border:1px solid rgba(255,100,100,.25); color:rgba(255,180,180,.9); display:none; align-items:center; justify-content:center; font-size:12px; cursor:pointer; transition:all .15s ease;}
|
||||
.dropItem:hover .dropRemove{display:flex;}
|
||||
.dropRemove:hover{background:rgba(255,100,100,.25); border-color:rgba(255,100,100,.4); color:rgba(255,220,220,1);}
|
||||
.dropEmpty{padding:10px 10px; color:rgba(165,172,196,.78); font-size:12.5px;}
|
||||
.dropName{white-space:nowrap; flex:1 1 auto; min-width:0; transition:mask-image .15s ease, -webkit-mask-image .15s ease, color .15s ease;}
|
||||
.dropItem:hover .dropName{overflow:hidden; mask-image:linear-gradient(90deg, #000 80%, transparent 100%); -webkit-mask-image:linear-gradient(90deg, #000 80%, transparent 100%); color:rgba(235,240,252,.96);}
|
||||
.dropRemove{position:absolute; right:8px; top:50%; transform:translateY(-50%) scale(.85); width:24px; height:24px; border-radius:var(--r3); background:rgba(255,100,100,.12); border:1px solid rgba(255,100,100,.20); color:rgba(255,180,180,.9); display:none; align-items:center; justify-content:center; font-size:12px; cursor:pointer; transition:all .2s var(--ease-bounce);}
|
||||
.dropItem:hover .dropRemove{display:flex; transform:translateY(-50%) scale(1);}
|
||||
.dropRemove:hover{background:rgba(255,100,100,.22); border-color:rgba(255,100,100,.35); color:rgba(255,220,220,1); transform:translateY(-50%) scale(1.1);}
|
||||
.dropRemove:active{transform:translateY(-50%) scale(.9); transition-duration:.08s;}
|
||||
.dropRemove::before{content:""; position:absolute; inset:-10px; border-radius:var(--r3);}
|
||||
.dropEmpty{padding:10px 10px; color:var(--textMuted); font-size:13px;}
|
||||
|
||||
.seg{display:inline-flex; border:1px solid rgba(255,255,255,.09); border-radius:var(--r2); overflow:hidden; background:rgba(255,255,255,.02); box-shadow:var(--shadow2); transition:border-color .15s ease;}
|
||||
.seg:hover{border-color:rgba(255,255,255,.14);}
|
||||
.seg .btn{border:none; box-shadow:none; border-radius:0; padding:8px 9px; background:transparent; font-weight:820; transform:none;}
|
||||
.seg .btn:hover{background:rgba(255,255,255,.06); transform:none; box-shadow:none;}
|
||||
.seg .btn:active{background:rgba(255,255,255,.08);}
|
||||
.seg .mid{border-left:1px solid rgba(255,255,255,.10); border-right:1px solid rgba(255,255,255,.10); min-width:62px; font-variant-numeric:tabular-nums;}
|
||||
.seg{display:inline-flex; border:none; border-radius:var(--r2); overflow:hidden; background:var(--surface-2); transition:background .15s ease;}
|
||||
.seg:hover{background:var(--surface-3);}
|
||||
.seg .btn{border:none; box-shadow:none; border-radius:0; padding:8px 9px; background:transparent; font-weight:800; transform:none;}
|
||||
.seg .btn:hover{background:var(--surface-3); transform:none; box-shadow:none;}
|
||||
.seg .btn:active{background:var(--surface-4); transform:scale(.95);}
|
||||
.seg .mid{border-left:1px solid rgba(140,160,210,.06); border-right:1px solid rgba(140,160,210,.06); min-width:62px; font-variant-numeric:tabular-nums;}
|
||||
|
||||
.switch{
|
||||
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
||||
padding:6px 10px;
|
||||
border-radius:8px;
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.015));
|
||||
box-shadow:0 2px 6px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.05);
|
||||
border-radius:var(--r2);
|
||||
border:none;
|
||||
background:var(--surface-2);
|
||||
cursor:pointer; user-select:none;
|
||||
font-size:11.5px; font-weight:650; letter-spacing:.01em;
|
||||
font-size:11px; font-weight:600; letter-spacing:0;
|
||||
color:var(--text);
|
||||
line-height:1;
|
||||
transition:all .2s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
}
|
||||
.switch:hover{
|
||||
border-color:rgba(255,255,255,.14);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.025));
|
||||
box-shadow:0 4px 10px rgba(0,0,0,.18), inset 0 1px 0 rgba(255,255,255,.08);
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
.switch:active{
|
||||
transform:scale(.97) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.switch:focus-within{outline:2px solid rgba(136,164,196,.65); outline-offset:2px;}
|
||||
.switch input{display:none;}
|
||||
.track{
|
||||
width:32px; height:18px;
|
||||
border-radius:999px;
|
||||
background:rgba(255,255,255,.06);
|
||||
border:1px solid rgba(255,255,255,.10);
|
||||
background:rgba(140,165,220,.06);
|
||||
border:1px solid rgba(140,165,220,.10);
|
||||
position:relative;
|
||||
transition:all .25s cubic-bezier(.4,0,.2,1);
|
||||
transition:all .3s var(--ease-bounce);
|
||||
flex:0 0 auto;
|
||||
display:flex; align-items:center;
|
||||
}
|
||||
@@ -486,25 +484,32 @@ body{
|
||||
margin-left:2px;
|
||||
width:14px; height:14px;
|
||||
border-radius:999px;
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.35), rgba(255,255,255,.18));
|
||||
box-shadow:0 2px 6px rgba(0,0,0,.25);
|
||||
transition:transform .2s cubic-bezier(.4,0,.2,1), background .2s ease, box-shadow .2s ease;
|
||||
background:rgba(200,210,230,.25);
|
||||
box-shadow:0 1px 3px rgba(0,0,0,.20);
|
||||
transition:transform .3s var(--ease-out-back), background .2s ease, box-shadow .2s ease, width .15s ease;
|
||||
}
|
||||
.switch:hover .knob{
|
||||
width:16px;
|
||||
}
|
||||
.switch input:checked + .track{
|
||||
background:linear-gradient(90deg, rgba(95,175,255,.25), rgba(130,200,255,.2));
|
||||
border-color:rgba(95,175,255,.3);
|
||||
box-shadow:0 0 12px rgba(95,175,255,.15);
|
||||
background:rgba(136,164,196,.20);
|
||||
border-color:rgba(136,164,196,.28);
|
||||
}
|
||||
.switch input:checked + .track .knob{
|
||||
transform:translateX(14px);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.9), rgba(200,230,255,.8));
|
||||
box-shadow:0 2px 8px rgba(95,175,255,.3), 0 0 0 2px rgba(95,175,255,.15);
|
||||
background:rgba(220,228,240,.85);
|
||||
box-shadow:0 1px 4px rgba(136,164,196,.25);
|
||||
width:14px;
|
||||
}
|
||||
.switch:hover input:checked + .track .knob{
|
||||
width:16px;
|
||||
transform:translateX(12px);
|
||||
}
|
||||
|
||||
.content{flex:1 1 auto; min-height:0; padding:12px; display:grid; grid-template-columns:calc(62% - 7px) 14px calc(38% - 7px); gap:0; overflow:hidden;}
|
||||
.content{flex:1 1 auto; min-height:0; padding:10px; display:grid; grid-template-columns:calc(62% - 5px) 10px calc(38% - 5px); gap:0; overflow:hidden;}
|
||||
|
||||
@media (max-width:1100px){
|
||||
.content{grid-template-columns:1fr; gap:12px; padding:12px;}
|
||||
.content{grid-template-columns:1fr; gap:10px; padding:10px;}
|
||||
.dividerWrap{display:none;}
|
||||
.actions{flex-wrap:wrap;}
|
||||
.seg{width:100%;}
|
||||
|
||||
@@ -1,60 +1,77 @@
|
||||
.panel{border:1px solid var(--stroke); border-radius:var(--r); background:linear-gradient(180deg, var(--surface), rgba(255,255,255,.015)); box-shadow:0 8px 32px rgba(0,0,0,.35); overflow:hidden; min-height:0; display:flex; flex-direction:column; backdrop-filter:blur(12px);}
|
||||
.panelHeader{padding:12px 12px 10px; border-bottom:1px solid var(--stroke); display:flex; align-items:flex-start; justify-content:space-between; gap:12px; flex:0 0 auto; min-width:0;}
|
||||
.nowTitle{font-weight:860; font-size:13.4px; letter-spacing:.14px; max-width:60ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
|
||||
.nowSub{margin-top:4px; color:var(--textMuted); font-size:11.6px; font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:80ch;}
|
||||
.panel{border:1px solid var(--stroke); border-radius:var(--r); background:var(--surface); box-shadow:var(--shadow3); overflow:hidden; min-height:0; display:flex; flex-direction:column; transition:border-color .3s ease;}
|
||||
.panel:hover{border-color:rgba(140,160,210,.10);}
|
||||
.panel::after{display:none;}
|
||||
.panelHeader{padding:10px 12px 8px; border-bottom:1px solid rgba(140,160,210,.04); display:flex; align-items:flex-start; justify-content:space-between; gap:12px; flex:0 0 auto; min-width:0; background:rgba(140,165,220,.015); transition:background .3s ease;}
|
||||
.panelHeader:hover{background:rgba(140,165,220,.025);}
|
||||
.panelHeader::after{display:none;}
|
||||
.nowTitle{font-family:var(--brand); font-weight:700; font-size:14px; letter-spacing:-.01em; max-width:60ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .2s ease;}
|
||||
.nowTitle:hover{color:rgba(235,240,252,.98);}
|
||||
.nowSub{margin-top:4px; color:var(--textMuted); font-size:11px; font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:80ch; transition:color .2s ease;}
|
||||
|
||||
.dividerWrap{display:flex; align-items:stretch; justify-content:center;}
|
||||
.divider{width:14px; cursor:col-resize; position:relative; background:transparent; border:none;}
|
||||
.divider{width:10px; cursor:col-resize; position:relative; background:transparent; border:none;}
|
||||
.divider::after{
|
||||
content:""; position:absolute; top:50%; left:50%;
|
||||
width:4px; height:54px; transform:translate(-50%,-50%);
|
||||
border-radius:999px;
|
||||
background:
|
||||
radial-gradient(circle, rgba(255,255,255,.10) 35%, transparent 40%) 0 0/4px 12px,
|
||||
radial-gradient(circle, rgba(255,255,255,.10) 35%, transparent 40%) 0 6px/4px 12px;
|
||||
opacity:.20; pointer-events:none; transition:opacity .15s ease;
|
||||
radial-gradient(circle, rgba(140,165,220,.10) 35%, transparent 40%) 0 0/4px 12px,
|
||||
radial-gradient(circle, rgba(140,165,220,.10) 35%, transparent 40%) 0 6px/4px 12px;
|
||||
opacity:.20; pointer-events:none; transition:opacity .3s var(--ease-spring), height .3s var(--ease-spring);
|
||||
}
|
||||
.divider:hover::after{opacity:.52;}
|
||||
.divider:hover::after{opacity:.55; height:72px;}
|
||||
.divider:active::after{opacity:.70;}
|
||||
|
||||
.dock{flex:1 1 auto; min-height:0; border-top:1px solid rgba(255,255,255,.09); display:grid; grid-template-columns:62% 14px 38%; background:radial-gradient(900px 240px at 20% 0%, rgba(100,180,255,.06), transparent 60%), rgba(0,0,0,.10); overflow:hidden;}
|
||||
.dock{flex:1 1 auto; min-height:0; border-top:1px solid rgba(140,160,210,.04); display:grid; grid-template-columns:62% 10px 38%; background:rgba(0,0,0,.06); overflow:hidden;}
|
||||
.dockPane{min-height:0; display:flex; flex-direction:column; overflow:hidden;}
|
||||
.dockInner{padding:12px; min-height:0; display:flex; flex-direction:column; gap:10px; height:100%;}
|
||||
.dockHeader{padding:12px 14px 11px; border:1px solid rgba(255,255,255,.08); border-radius:7px; display:flex; align-items:center; justify-content:space-between; gap:10px; background:linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,.012)); flex:0 0 auto; box-shadow:0 14px 36px rgba(0,0,0,.25);}
|
||||
.dockInner{padding:10px; min-height:0; display:flex; flex-direction:column; gap:8px; height:100%;}
|
||||
.dockHeader{padding:10px 12px 9px; border:none; border-radius:var(--r2); display:flex; align-items:center; justify-content:space-between; gap:8px; background:var(--surface-2); flex:0 0 auto; transition:background .2s ease;}
|
||||
.dockHeader:hover{background:var(--surface-3);}
|
||||
#notesHeader{border-bottom-right-radius:7px;}
|
||||
#infoHeader{border-bottom-left-radius:7px; margin-right:12px;}
|
||||
.dockTitle{font-family:var(--brand); font-weight:800; letter-spacing:.02px; font-size:13.5px; color:rgba(246,248,255,.95); display:flex; align-items:center; gap:10px;}
|
||||
.dockTitle .fa{color:var(--iconStrong)!important; opacity:.88; font-size:14px;}
|
||||
.dockTitle{font-family:var(--brand); font-weight:700; letter-spacing:0; font-size:13px; color:rgba(218,225,240,.92); display:flex; align-items:center; gap:10px; transition:color .2s ease;}
|
||||
.dockHeader:hover .dockTitle{color:rgba(235,240,252,.98);}
|
||||
.dockTitle .fa{color:var(--iconStrong)!important; opacity:.88; font-size:14px; transition:transform .3s var(--ease-bounce), opacity .2s ease;}
|
||||
.dockHeader:hover .dockTitle .fa{transform:scale(1.12) rotate(-5deg); opacity:1;}
|
||||
|
||||
.notesArea{flex:1 1 auto; min-height:0; overflow:hidden; position:relative;}
|
||||
.notesSaved{
|
||||
position:absolute;
|
||||
bottom:12px; right:12px;
|
||||
padding:6px 10px;
|
||||
border-radius:6px;
|
||||
background:linear-gradient(135deg, rgba(100,200,130,.2), rgba(80,180,120,.15));
|
||||
border:1px solid rgba(100,200,130,.25);
|
||||
color:rgba(150,230,170,.95);
|
||||
border-radius:var(--r2);
|
||||
background:rgba(130,170,130,.12);
|
||||
border:none;
|
||||
color:rgba(160,195,160,.88);
|
||||
font-size:11px;
|
||||
font-weight:600;
|
||||
font-weight:700;
|
||||
letter-spacing:.02em;
|
||||
display:flex; align-items:center; gap:6px;
|
||||
opacity:0;
|
||||
transform:translateY(4px);
|
||||
transition:opacity .4s ease, transform .4s ease;
|
||||
transform:translateY(4px) scale(.95);
|
||||
transition:opacity .4s ease, transform .4s var(--ease-bounce);
|
||||
pointer-events:none;
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.2);
|
||||
}
|
||||
.notesSaved.show{
|
||||
opacity:1;
|
||||
transform:translateY(0);
|
||||
transform:translateY(0) scale(1);
|
||||
}
|
||||
.notesSaved .fa{font-size:10px; color:rgba(130,220,160,.95)!important;}
|
||||
.notes{width:100%; height:100%; resize:none; border-radius:6px; border:1px solid rgba(255,255,255,.10); background:radial-gradient(900px 280px at 18% 0%, rgba(100,180,255,.09), transparent 62%), rgba(255,255,255,.02); color:rgba(246,248,255,.92); padding:12px 12px; outline:none; font-family:var(--sans); font-size:12.9px; line-height:1.45; letter-spacing:.08px; box-shadow:0 18px 45px rgba(0,0,0,.40); overflow:auto; scrollbar-width:thin; scrollbar-color:rgba(255,255,255,.14) transparent;}
|
||||
.notesSaved .fa{font-size:10px; color:rgba(150,190,150,.88)!important; transition:transform .3s var(--ease-bounce);}
|
||||
.notesSaved.show .fa{animation:checkBounce .5s var(--ease-bounce);}
|
||||
@keyframes checkBounce{
|
||||
0%{transform:scale(0) rotate(-45deg);}
|
||||
60%{transform:scale(1.3) rotate(5deg);}
|
||||
100%{transform:scale(1) rotate(0);}
|
||||
}
|
||||
.notes{width:100%; height:100%; resize:none; border-radius:var(--r3); border:1px solid transparent; background:var(--surface-0); color:rgba(218,225,240,.90); padding:10px 12px; outline:none; font-family:var(--sans); font-size:13px; line-height:1.45; letter-spacing:0; box-shadow:var(--shadowInset); overflow:auto; scrollbar-width:thin; scrollbar-color:rgba(140,160,210,.10) transparent; transition:border-color .25s ease, box-shadow .25s ease, background .2s ease;}
|
||||
.notes:hover{background:rgba(140,165,220,.03);}
|
||||
.notes:focus{border-color:rgba(136,164,196,.25); box-shadow:var(--shadowInset), 0 0 0 2px rgba(136,164,196,.12);}
|
||||
.notes::-webkit-scrollbar{width:2px; height:2px;}
|
||||
.notes::-webkit-scrollbar-track{background:transparent;}
|
||||
.notes::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16); border-radius:0;}
|
||||
.notes::-webkit-scrollbar-thumb{background:rgba(140,160,210,.10); border-radius:0;}
|
||||
.notes::-webkit-scrollbar-button{width:0; height:0; display:none;}
|
||||
.notes::placeholder{color:rgba(165,172,196,.55);}
|
||||
.notes::placeholder{color:rgba(155,170,200,.65); transition:color .2s ease;}
|
||||
.notes:focus::placeholder{color:rgba(148,162,192,.25);}
|
||||
|
||||
.infoGrid{
|
||||
flex:1 1 auto;
|
||||
@@ -84,61 +101,71 @@
|
||||
.infoGrid.at-bottom{--fade-bottom:0px;}
|
||||
.infoGrid::-webkit-scrollbar{width:0; height:0;}
|
||||
|
||||
dl.kv{margin:0;}
|
||||
dl.kv dt,dl.kv dd{margin:0; padding:0;}
|
||||
.kv{
|
||||
display:grid;
|
||||
grid-template-columns:78px 1fr;
|
||||
gap:4px 14px;
|
||||
gap:3px 12px;
|
||||
align-items:baseline;
|
||||
padding:12px 14px;
|
||||
border-radius:var(--r2);
|
||||
border:1px solid var(--strokeLight);
|
||||
background:linear-gradient(170deg, rgba(20,25,35,.65), rgba(14,18,26,.75));
|
||||
box-shadow:var(--shadow2), inset 0 1px 0 rgba(255,255,255,.02);
|
||||
margin-bottom:8px;
|
||||
padding:10px 12px;
|
||||
border-radius:var(--r3);
|
||||
border:none;
|
||||
background:rgba(10,13,20,.45);
|
||||
margin-bottom:6px;
|
||||
transition:background .2s ease;
|
||||
}
|
||||
.kv:hover{background:rgba(10,13,20,.55);}
|
||||
.k{
|
||||
font-family:var(--sans);
|
||||
font-size:9px;
|
||||
font-weight:800;
|
||||
font-size:10px;
|
||||
font-weight:700;
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.12em;
|
||||
letter-spacing:.08em;
|
||||
color:var(--textDim);
|
||||
padding-top:3px;
|
||||
white-space:nowrap;
|
||||
transition:color .2s ease;
|
||||
}
|
||||
.kv:hover .k{color:rgba(148,162,192,.50);}
|
||||
.v{
|
||||
font-family:var(--brand);
|
||||
font-size:12.5px;
|
||||
font-weight:650;
|
||||
font-size:13px;
|
||||
font-weight:600;
|
||||
color:var(--text);
|
||||
letter-spacing:-.01em;
|
||||
word-break:break-word;
|
||||
overflow-wrap:anywhere;
|
||||
line-height:1.35;
|
||||
padding-left:6px;
|
||||
transition:color .2s ease;
|
||||
}
|
||||
.kv:hover .v{color:rgba(230,235,248,.95);}
|
||||
.v.mono{
|
||||
font-family:var(--mono);
|
||||
font-size:11px;
|
||||
font-weight:500;
|
||||
font-weight:400;
|
||||
color:var(--textMuted);
|
||||
letter-spacing:.01em;
|
||||
letter-spacing:.02em;
|
||||
font-variant-numeric:tabular-nums;
|
||||
background:linear-gradient(90deg, var(--accentBg), transparent 80%);
|
||||
background:var(--accentBg);
|
||||
padding:2px 6px;
|
||||
border-radius:3px;
|
||||
margin:-2px 0;
|
||||
transition:background .2s ease, color .2s ease;
|
||||
}
|
||||
.kv:hover .v.mono{background:rgba(136,164,196,.09); color:rgba(170,185,215,.70);}
|
||||
|
||||
.dockDividerWrap{display:flex; align-items:stretch; justify-content:center;}
|
||||
.dockDivider{width:14px; cursor:col-resize; position:relative; background:transparent; border:none;}
|
||||
.dockDivider{width:10px; cursor:col-resize; position:relative; background:transparent; border:none;}
|
||||
.dockDivider::after{
|
||||
content:""; position:absolute; top:50%; left:50%;
|
||||
width:4px; height:44px; transform:translate(-50%,-50%);
|
||||
border-radius:999px;
|
||||
background:
|
||||
radial-gradient(circle, rgba(255,255,255,.10) 35%, transparent 40%) 0 0/4px 12px,
|
||||
radial-gradient(circle, rgba(255,255,255,.10) 35%, transparent 40%) 0 6px/4px 12px;
|
||||
opacity:.18; pointer-events:none; transition:opacity .15s ease;
|
||||
radial-gradient(circle, rgba(140,165,220,.10) 35%, transparent 40%) 0 0/4px 12px,
|
||||
radial-gradient(circle, rgba(140,165,220,.10) 35%, transparent 40%) 0 6px/4px 12px;
|
||||
opacity:.18; pointer-events:none; transition:opacity .3s var(--ease-spring), height .3s var(--ease-spring);
|
||||
}
|
||||
.dockDivider:hover::after{opacity:.44;}
|
||||
.dockDivider:hover::after{opacity:.50; height:60px;}
|
||||
.dockDivider:active::after{opacity:.65;}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.videoWrap{position:relative; background:#000; flex:0 0 auto;}
|
||||
.videoWrap{position:relative; background:#000; flex:0 0 auto; overflow:hidden;}
|
||||
video{width:100%; height:auto; display:block; background:#000; aspect-ratio:16/9; outline:none; cursor:pointer;}
|
||||
video::cue{
|
||||
background:transparent;
|
||||
@@ -34,43 +34,26 @@ video::cue{
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
opacity:0;
|
||||
transition:opacity 0.8s ease;
|
||||
transition:opacity 0.8s var(--ease-spring), transform .3s var(--ease-bounce);
|
||||
border-radius:50%;
|
||||
background:rgba(20,25,35,.5);
|
||||
backdrop-filter:blur(8px) saturate(1.3);
|
||||
-webkit-backdrop-filter:blur(8px) saturate(1.3);
|
||||
border:1.5px solid rgba(255,255,255,.15);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0,0,0,.5),
|
||||
0 0 0 1px rgba(0,0,0,.3);
|
||||
background:rgba(15,17,23,.55);
|
||||
border:none;
|
||||
}
|
||||
.overlayIcon.show{
|
||||
opacity:1;
|
||||
}
|
||||
.overlayIcon.pulse{
|
||||
animation:overlayPulse 0.4s ease-out;
|
||||
animation:overlayPulse 0.4s var(--ease-bounce);
|
||||
}
|
||||
@keyframes overlayPulse{
|
||||
0%{transform:scale(1);}
|
||||
50%{transform:scale(1.15);}
|
||||
50%{transform:scale(1.18);}
|
||||
100%{transform:scale(1);}
|
||||
}
|
||||
.overlayIcon::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
border-radius:50%;
|
||||
background:
|
||||
radial-gradient(circle at 30% 30%, rgba(255,255,255,.1), transparent 50%),
|
||||
radial-gradient(circle at 70% 70%, rgba(95,175,255,.08), transparent 50%);
|
||||
pointer-events:none;
|
||||
}
|
||||
.overlayIcon::before{display:none;}
|
||||
.overlayIcon.show:hover{
|
||||
border-color:rgba(255,255,255,.22);
|
||||
box-shadow:
|
||||
0 12px 40px rgba(0,0,0,.6),
|
||||
0 0 0 1px rgba(0,0,0,.4),
|
||||
0 0 40px rgba(95,175,255,.1);
|
||||
background:rgba(15,17,23,.65);
|
||||
transform:scale(1.08);
|
||||
}
|
||||
.overlayIcon i{
|
||||
font-size:36px;
|
||||
@@ -78,81 +61,67 @@ video::cue{
|
||||
filter:drop-shadow(0 2px 10px rgba(0,0,0,.6));
|
||||
position:relative;
|
||||
z-index:2;
|
||||
transition:transform 0.3s ease, color 0.3s ease;
|
||||
margin-left:4px; /* center play icon visually */
|
||||
transition:transform 0.3s var(--ease-bounce), color 0.3s ease;
|
||||
margin-left:4px;
|
||||
}
|
||||
.overlayIcon.pause i{
|
||||
margin-left:0;
|
||||
}
|
||||
.overlayIcon.show:hover i{
|
||||
transform:scale(1.1);
|
||||
transform:scale(1.15);
|
||||
color:rgba(255,255,255,1)!important;
|
||||
}
|
||||
|
||||
.controls{display:flex; flex-direction:column; gap:10px; padding:12px; border-top:1px solid var(--stroke); flex:0 0 auto; background:linear-gradient(180deg, rgba(0,0,0,.06), rgba(0,0,0,.0));}
|
||||
.controlsRow{display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;}
|
||||
.group{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
|
||||
.controls{display:flex; flex-direction:column; gap:10px; padding:12px; border-top:1px solid rgba(140,160,210,.04); flex:0 0 auto;}
|
||||
.controlsStrip{
|
||||
display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;
|
||||
padding:5px 6px;
|
||||
border-radius:var(--r);
|
||||
border:none;
|
||||
background:var(--surface);
|
||||
transition:background .2s ease;
|
||||
}
|
||||
.controlsStrip:hover{background:rgba(140,165,220,.04);}
|
||||
.controlsStrip::after{display:none;}
|
||||
.stripDivider{width:1px; height:22px; background:rgba(140,160,210,.06); flex:0 0 auto; transition:background .2s ease, height .2s ease;}
|
||||
.controlsStrip:hover .stripDivider{background:rgba(140,160,210,.09); height:24px;}
|
||||
.group{display:flex; align-items:center; gap:8px; flex-wrap:wrap;}
|
||||
.controlsStrip .iconBtn{box-shadow:none;}
|
||||
.controlsStrip .miniCtl{box-shadow:none;}
|
||||
.controlsStrip .timeChip{box-shadow:none;}
|
||||
|
||||
.iconBtn{
|
||||
width:40px; height:36px;
|
||||
border-radius:8px;
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
|
||||
box-shadow:
|
||||
0 2px 6px rgba(0,0,0,.12),
|
||||
inset 0 1px 0 rgba(255,255,255,.06);
|
||||
border-radius:var(--r2);
|
||||
border:none;
|
||||
background:var(--surface-2);
|
||||
box-shadow:var(--shadow3);
|
||||
display:inline-flex; align-items:center; justify-content:center;
|
||||
cursor:pointer; user-select:none;
|
||||
transition:all .2s cubic-bezier(.4,0,.2,1);
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
.iconBtn::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:radial-gradient(circle at 50% 0%, rgba(255,255,255,.15), transparent 70%);
|
||||
opacity:0;
|
||||
transition:opacity .2s ease;
|
||||
transition:all .2s var(--ease-bounce);
|
||||
}
|
||||
.iconBtn:hover{
|
||||
border-color:rgba(255,255,255,.15);
|
||||
background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.03));
|
||||
transform:translateY(-1px);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0,0,0,.2),
|
||||
inset 0 1px 0 rgba(255,255,255,.1);
|
||||
background:var(--surface-3);
|
||||
transform:translateY(-2px);
|
||||
}
|
||||
.iconBtn:hover::before{opacity:1;}
|
||||
.iconBtn:active{
|
||||
transform:translateY(0);
|
||||
box-shadow:0 1px 4px rgba(0,0,0,.15);
|
||||
transform:scale(.9) translateY(0);
|
||||
transition-duration:.08s;
|
||||
}
|
||||
.iconBtn.primary{
|
||||
border-color:rgba(95,175,255,.25);
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.15), rgba(95,175,255,.05));
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0,0,0,.15),
|
||||
0 4px 16px rgba(95,175,255,.08),
|
||||
inset 0 1px 0 rgba(255,255,255,.1);
|
||||
}
|
||||
.iconBtn.primary::before{
|
||||
background:radial-gradient(circle at 50% 0%, rgba(255,255,255,.2), transparent 70%);
|
||||
background:rgba(136,164,196,.10);
|
||||
}
|
||||
.iconBtn.primary:hover{
|
||||
border-color:rgba(95,175,255,.4);
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.22), rgba(95,175,255,.08));
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0,0,0,.2),
|
||||
0 8px 24px rgba(95,175,255,.12),
|
||||
inset 0 1px 0 rgba(255,255,255,.15);
|
||||
background:rgba(136,164,196,.16);
|
||||
box-shadow:0 0 12px rgba(136,164,196,.10);
|
||||
}
|
||||
.iconBtn .fa{font-size:15px; color:var(--iconStrong)!important; opacity:.9; transition:transform .2s ease; position:relative; z-index:1;}
|
||||
.iconBtn:hover .fa{transform:scale(1.1);}
|
||||
.iconBtn .fa{font-size:15px; color:var(--iconStrong)!important; opacity:.9; transition:transform .2s var(--ease-bounce), opacity .15s ease;}
|
||||
.iconBtn:hover .fa{transform:scale(1.15); opacity:1;}
|
||||
|
||||
.timeChip{display:inline-flex; align-items:center; gap:10px; padding:8px 10px; border-radius:999px; border:1px solid var(--strokeMed); background:var(--surface); box-shadow:var(--shadow2); font-family:var(--mono); font-size:12px; color:var(--text); letter-spacing:.15px; font-variant-numeric:tabular-nums; transition:border-color .15s ease;}
|
||||
.timeDot{width:8px; height:8px; border-radius:999px; background:radial-gradient(circle at 35% 35%, rgba(255,255,255,.90), rgba(130,230,180,.55)); box-shadow:0 0 0 3px rgba(130,230,180,.10); opacity:.95; transition:transform .3s ease; animation:pulse 2s ease-in-out infinite;}
|
||||
@keyframes pulse{0%,100%{transform:scale(1);opacity:.95;} 50%{transform:scale(1.15);opacity:1;}}
|
||||
.timeChip{display:inline-flex; align-items:center; gap:8px; padding:6px 9px; border-radius:var(--r2); border:none; background:var(--surface-2); box-shadow:var(--shadow3); font-family:var(--mono); font-size:12px; color:var(--text); letter-spacing:.02em; font-variant-numeric:tabular-nums; transition:background .2s ease;}
|
||||
.timeChip:hover{background:var(--surface-3);}
|
||||
.timeDot{width:8px; height:8px; border-radius:999px; background:rgba(136,164,196,.60); opacity:.95; transition:transform .3s ease, background .3s ease; animation:pulse 2s ease-in-out infinite;}
|
||||
@keyframes pulse{0%,100%{transform:scale(1);opacity:.95;} 50%{transform:scale(1.2);opacity:1;}}
|
||||
|
||||
.seekWrap{display:flex; align-items:center; gap:10px; width:100%; position:relative;}
|
||||
.seekTrack{
|
||||
@@ -161,31 +130,35 @@ video::cue{
|
||||
height:10px;
|
||||
transform:translateY(-50%);
|
||||
border-radius:999px;
|
||||
background:rgba(255,255,255,.06);
|
||||
border:1px solid rgba(255,255,255,.10);
|
||||
box-shadow:0 8px 18px rgba(0,0,0,.28);
|
||||
background:rgba(140,165,220,.04);
|
||||
border:1px solid rgba(140,165,220,.06);
|
||||
box-shadow:var(--shadowInset);
|
||||
overflow:hidden;
|
||||
pointer-events:none;
|
||||
transition:height .2s var(--ease-bounce), border-color .2s ease;
|
||||
}
|
||||
.seekWrap:hover .seekTrack{height:12px; border-color:rgba(140,165,220,.09);}
|
||||
.seekFill{
|
||||
height:100%;
|
||||
width:0%;
|
||||
background:linear-gradient(90deg, rgba(95,175,255,.7), rgba(130,200,255,.5) 60%, rgba(180,230,200,.4));
|
||||
background:rgba(136,164,196,.50);
|
||||
border-radius:999px 0 0 999px;
|
||||
box-shadow:0 0 12px rgba(95,175,255,.3);
|
||||
transition:width .1s linear;
|
||||
transition:width .1s linear, background .2s ease;
|
||||
}
|
||||
.seekWrap:hover .seekFill{background:rgba(136,164,196,.62);}
|
||||
.seek{-webkit-appearance:none; appearance:none; width:100%; height:18px; border-radius:999px; background:transparent; border:none; box-shadow:none; outline:none; position:relative; z-index:2; cursor:pointer; margin:0;}
|
||||
.seek::-webkit-slider-runnable-track{background:transparent; height:18px;}
|
||||
.seek::-webkit-slider-thumb{-webkit-appearance:none; appearance:none; width:18px; height:18px; border-radius:999px; border:2px solid rgba(255,255,255,.25); background:linear-gradient(180deg, rgba(255,255,255,.95), rgba(200,220,255,.8)); box-shadow:0 4px 12px rgba(0,0,0,.4), 0 0 0 4px rgba(95,175,255,.15); cursor:pointer; transition:transform .15s ease, box-shadow .15s ease; margin-top:0;}
|
||||
.seek:hover::-webkit-slider-thumb{transform:scale(1.15); box-shadow:0 6px 16px rgba(0,0,0,.5), 0 0 0 6px rgba(95,175,255,.2);}
|
||||
.seek:active::-webkit-slider-thumb{transform:scale(1.05); box-shadow:0 2px 8px rgba(0,0,0,.4), 0 0 0 8px rgba(95,175,255,.25);}
|
||||
.seek::-webkit-slider-thumb{-webkit-appearance:none; appearance:none; width:16px; height:16px; border-radius:999px; border:2px solid rgba(200,210,230,.28); background:rgba(220,228,240,.88); box-shadow:0 2px 6px rgba(0,0,0,.30); cursor:pointer; transition:transform .2s var(--ease-bounce), box-shadow .2s ease, border-color .2s ease; margin-top:0;}
|
||||
.seek:hover::-webkit-slider-thumb{transform:scale(1.2); box-shadow:0 2px 8px rgba(0,0,0,.35), 0 0 10px rgba(136,164,196,.15); border-color:rgba(200,210,230,.40);}
|
||||
.seek:active::-webkit-slider-thumb{transform:scale(1.05); transition-duration:.08s;}
|
||||
.seek::-moz-range-track{background:transparent; height:18px;}
|
||||
.seek::-moz-range-thumb{width:18px; height:18px; border-radius:999px; border:2px solid rgba(255,255,255,.25); background:linear-gradient(180deg, rgba(255,255,255,.95), rgba(200,220,255,.8)); box-shadow:0 4px 12px rgba(0,0,0,.4); cursor:pointer;}
|
||||
.seek::-moz-range-thumb{width:16px; height:16px; border-radius:999px; border:2px solid rgba(200,210,230,.28); background:rgba(220,228,240,.88); box-shadow:0 2px 6px rgba(0,0,0,.30); cursor:pointer;}
|
||||
|
||||
.miniCtl{display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:999px; border:1px solid var(--strokeMed); background:var(--surface); box-shadow:var(--shadow2); position:relative; transition:border-color .15s ease;}
|
||||
.miniCtl:hover{border-color:rgba(255,255,255,.16);}
|
||||
.miniCtl .fa{font-size:14px; color:var(--iconStrong)!important; opacity:.95; flex:0 0 auto;}
|
||||
.miniCtl{display:flex; align-items:center; gap:8px; padding:6px 9px; border-radius:var(--r2); border:none; background:var(--surface-2); box-shadow:var(--shadow3); position:relative; transition:all .2s var(--ease-bounce);}
|
||||
.miniCtl:hover{background:var(--surface-3); transform:translateY(-1px);}
|
||||
.miniCtl:active{transform:scale(.97) translateY(0); transition-duration:.08s;}
|
||||
.miniCtl .fa{font-size:14px; color:var(--iconStrong)!important; opacity:.95; flex:0 0 auto; transition:transform .2s var(--ease-bounce), opacity .15s ease;}
|
||||
.miniCtl:hover .fa{transform:scale(1.1); opacity:1;}
|
||||
|
||||
.volWrap{position:relative; width:120px; height:14px; display:flex; align-items:center;}
|
||||
.volTrack{
|
||||
@@ -194,25 +167,27 @@ video::cue{
|
||||
height:6px;
|
||||
transform:translateY(-50%);
|
||||
border-radius:999px;
|
||||
background:rgba(255,255,255,.06);
|
||||
border:1px solid rgba(255,255,255,.10);
|
||||
background:rgba(140,165,220,.04);
|
||||
border:1px solid rgba(140,165,220,.06);
|
||||
overflow:hidden;
|
||||
pointer-events:none;
|
||||
transition:height .2s var(--ease-bounce), border-color .2s ease;
|
||||
}
|
||||
.volWrap:hover .volTrack{height:8px; border-color:rgba(140,165,220,.09);}
|
||||
.volFill{
|
||||
height:100%;
|
||||
width:100%;
|
||||
background:linear-gradient(90deg, rgba(95,175,255,.6), rgba(130,200,255,.4));
|
||||
background:rgba(136,164,196,.40);
|
||||
border-radius:999px 0 0 999px;
|
||||
box-shadow:0 0 8px rgba(95,175,255,.2);
|
||||
transition:width .05s linear;
|
||||
transition:width .05s linear, background .2s ease;
|
||||
}
|
||||
.volWrap:hover .volFill{background:rgba(136,164,196,.55);}
|
||||
.vol{-webkit-appearance:none; appearance:none; width:100%; height:14px; border-radius:999px; background:transparent; border:none; outline:none; position:relative; z-index:2; cursor:pointer; margin:0;}
|
||||
.vol::-webkit-slider-runnable-track{background:transparent; height:14px;}
|
||||
.vol::-webkit-slider-thumb{-webkit-appearance:none; appearance:none; width:14px; height:14px; border-radius:999px; border:2px solid rgba(255,255,255,.25); background:linear-gradient(180deg, rgba(255,255,255,.95), rgba(200,220,255,.8)); box-shadow:0 2px 8px rgba(0,0,0,.35), 0 0 0 3px rgba(95,175,255,.12); cursor:pointer; transition:transform .15s ease, box-shadow .15s ease;}
|
||||
.vol:hover::-webkit-slider-thumb{transform:scale(1.15); box-shadow:0 3px 10px rgba(0,0,0,.4), 0 0 0 4px rgba(95,175,255,.18);}
|
||||
.vol::-webkit-slider-thumb{-webkit-appearance:none; appearance:none; width:14px; height:14px; border-radius:999px; border:2px solid rgba(200,210,230,.28); background:rgba(220,228,240,.88); box-shadow:0 1px 4px rgba(0,0,0,.25); cursor:pointer; transition:transform .2s var(--ease-bounce), box-shadow .2s ease;}
|
||||
.vol:hover::-webkit-slider-thumb{transform:scale(1.25); box-shadow:0 1px 6px rgba(0,0,0,.30), 0 0 8px rgba(136,164,196,.12);}
|
||||
.vol::-moz-range-track{background:transparent; height:14px;}
|
||||
.vol::-moz-range-thumb{width:14px; height:14px; border-radius:999px; border:2px solid rgba(255,255,255,.25); background:linear-gradient(180deg, rgba(255,255,255,.95), rgba(200,220,255,.8)); box-shadow:0 2px 8px rgba(0,0,0,.35); cursor:pointer;}
|
||||
.vol::-moz-range-thumb{width:14px; height:14px; border-radius:999px; border:2px solid rgba(200,210,230,.28); background:rgba(220,228,240,.88); box-shadow:0 1px 4px rgba(0,0,0,.25); cursor:pointer;}
|
||||
|
||||
.volTooltip{
|
||||
position:absolute;
|
||||
@@ -220,36 +195,22 @@ video::cue{
|
||||
left:0;
|
||||
padding:8px 12px;
|
||||
border-radius:var(--r2);
|
||||
background:
|
||||
radial-gradient(ellipse 120% 100% at 20% 0%, rgba(95,175,255,.12), transparent 50%),
|
||||
linear-gradient(175deg, rgba(28,34,48,.97), rgba(16,20,30,.98));
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
background:rgba(18,21,30,.95);
|
||||
border:1px solid rgba(140,160,210,.10);
|
||||
color:#fff;
|
||||
font-family:var(--mono);
|
||||
font-size:13px;
|
||||
font-weight:600;
|
||||
letter-spacing:.03em;
|
||||
font-weight:400;
|
||||
letter-spacing:.02em;
|
||||
white-space:nowrap;
|
||||
pointer-events:none;
|
||||
opacity:0;
|
||||
transform:translateX(-50%) translateY(4px);
|
||||
transition:opacity .15s ease, transform .15s ease, left .05s linear;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0,0,0,.3),
|
||||
0 4px 8px rgba(0,0,0,.2),
|
||||
0 12px 24px rgba(0,0,0,.25),
|
||||
inset 0 1px 0 rgba(255,255,255,.08);
|
||||
backdrop-filter:blur(16px);
|
||||
transform:translateX(-50%) translateY(4px) scale(.95);
|
||||
transition:opacity .2s ease, transform .25s var(--ease-bounce), left .05s linear;
|
||||
box-shadow:var(--shadow2);
|
||||
z-index:100;
|
||||
}
|
||||
.volTooltip::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
top:0; left:0; right:0;
|
||||
height:1px;
|
||||
background:linear-gradient(90deg, transparent, rgba(95,175,255,.4) 50%, transparent);
|
||||
border-radius:var(--r2) var(--r2) 0 0;
|
||||
}
|
||||
.volTooltip::before{display:none;}
|
||||
.volTooltip::after{
|
||||
content:"";
|
||||
position:absolute;
|
||||
@@ -257,22 +218,34 @@ video::cue{
|
||||
left:50%;
|
||||
transform:translateX(-50%);
|
||||
border:6px solid transparent;
|
||||
border-top-color:rgba(20,26,36,.95);
|
||||
border-top-color:rgba(18,21,30,.95);
|
||||
}
|
||||
.volTooltip.show{
|
||||
opacity:1;
|
||||
transform:translateX(-50%) translateY(0);
|
||||
transform:translateX(-50%) translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.speedBox{display:flex; align-items:center; gap:10px; position:relative;}
|
||||
.speedBtn{border:none; outline:none; background:transparent; color:rgba(246,248,255,.92); font-family:var(--mono); font-size:12px; letter-spacing:.10px; padding:0 2px; cursor:pointer; line-height:1; display:inline-flex; align-items:center; gap:8px; transition:color .15s ease;}
|
||||
.speedBtn span:first-child{min-width:3.5ch; text-align:right;}
|
||||
.speedBtn:hover{color:rgba(255,255,255,1);}
|
||||
.speedCaret .fa{font-size:12px; opacity:.85; color:var(--icon)!important; transition:transform .2s ease;}
|
||||
.speedBtn:hover .speedCaret .fa{transform:translateY(2px);}
|
||||
.speedBtn{border:none; outline:none; background:transparent; color:var(--text); font-family:var(--mono); font-size:12px; letter-spacing:0; padding:0 2px; cursor:pointer; line-height:1; display:inline-flex; align-items:center; gap:8px; transition:color .15s ease, transform .15s ease;}
|
||||
.speedBtn span:first-child{min-width:3.5ch; text-align:right; transition:transform .15s var(--ease-bounce);}
|
||||
.speedBtn:hover{color:rgba(235,240,250,1);}
|
||||
.speedBtn:hover span:first-child{transform:scale(1.05);}
|
||||
.speedBtn:active{transform:scale(.95); transition-duration:.08s;}
|
||||
.speedCaret .fa{font-size:12px; opacity:.85; color:var(--icon)!important; transition:transform .25s var(--ease-bounce);}
|
||||
.speedBtn:hover .speedCaret .fa{transform:translateY(3px) rotate(180deg);}
|
||||
|
||||
.progressPill{flex:0 0 auto; display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:999px; border:1px solid var(--strokeMed); background:radial-gradient(400px 100px at 20% 0%, var(--accentGlow), transparent 60%), var(--surface); box-shadow:var(--shadow2); min-width:220px;}
|
||||
.progressLabel{font-size:11.2px; font-weight:820; letter-spacing:.12px; text-transform:uppercase; color:rgba(190,198,220,.78); margin-right:2px;}
|
||||
.progressBar{width:120px; height:8px; border-radius:999px; border:1px solid rgba(255,255,255,.09); background:rgba(255,255,255,.05); overflow:hidden;}
|
||||
.progressBar>div{height:100%; width:0%; background:linear-gradient(90deg, rgba(100,180,255,.95), rgba(130,230,180,.88));}
|
||||
.progressPct{font-family:var(--mono); font-size:11.6px; color:rgba(230,235,255,.92); font-variant-numeric:tabular-nums; letter-spacing:.10px; min-width:58px; text-align:right;}
|
||||
.progressPill{flex:0 0 auto; display:flex; align-items:center; gap:8px; padding:6px 10px; border-radius:var(--r2); border:none; background:var(--surface-2); box-shadow:var(--shadow3); min-width:220px; transition:background .2s ease;}
|
||||
.progressPill:hover{background:var(--surface-3);}
|
||||
.progressLabel{font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--textMuted); margin-right:2px; transition:color .2s ease;}
|
||||
.progressPill:hover .progressLabel{color:rgba(170,185,215,.70);}
|
||||
.progressBar{width:120px; height:8px; border-radius:999px; border:none; background:rgba(140,165,220,.04); overflow:hidden; transition:height .2s var(--ease-bounce);}
|
||||
.progressPill:hover .progressBar{height:10px;}
|
||||
.progressBar>div{height:100%; width:0%; background:rgba(136,164,196,.75); transition:width .4s var(--ease-spring);}
|
||||
.progressPct{font-family:var(--mono); font-size:12px; color:var(--text); font-variant-numeric:tabular-nums; letter-spacing:.02em; min-width:58px; text-align:right; transition:color .2s ease;}
|
||||
.progressPill:hover .progressPct{color:rgba(235,240,252,.98);}
|
||||
|
||||
.timeSep{color:rgba(175,185,210,.78);}
|
||||
.seek:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px rgba(136,164,196,.45), 0 2px 6px rgba(0,0,0,.30);}
|
||||
.vol:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px rgba(136,164,196,.45), 0 1px 4px rgba(0,0,0,.25);}
|
||||
.seek:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px rgba(136,164,196,.45), 0 2px 6px rgba(0,0,0,.30);}
|
||||
.vol:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px rgba(136,164,196,.45), 0 1px 4px rgba(0,0,0,.25);}
|
||||
|
||||
@@ -31,70 +31,89 @@
|
||||
border-radius:2px;
|
||||
pointer-events:none;
|
||||
opacity:0;
|
||||
transition:opacity .4s ease;
|
||||
transition:opacity .4s ease, width .2s var(--ease-bounce);
|
||||
z-index:10;
|
||||
}
|
||||
.listWrap:hover .listScrollbar, .listScrollbar.active{opacity:1;}
|
||||
.listWrap:hover .listScrollbar{width:5px;}
|
||||
.listScrollbarThumb{
|
||||
position:absolute;
|
||||
top:0; left:0; right:0;
|
||||
min-height:24px;
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.3), rgba(95,175,255,.15));
|
||||
border:1px solid rgba(95,175,255,.2);
|
||||
background:rgba(136,164,196,.15);
|
||||
border:none;
|
||||
border-radius:2px;
|
||||
box-shadow:0 2px 8px rgba(0,0,0,.2);
|
||||
transition:background .2s ease, border-color .2s ease, box-shadow .2s ease;
|
||||
transition:background .2s ease;
|
||||
cursor:grab;
|
||||
}
|
||||
.listScrollbarThumb:hover{
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.45), rgba(95,175,255,.25));
|
||||
border-color:rgba(95,175,255,.35);
|
||||
background:rgba(136,164,196,.30);
|
||||
}
|
||||
.listScrollbarThumb:active{
|
||||
cursor:grabbing;
|
||||
background:rgba(136,164,196,.35);
|
||||
}
|
||||
.listScrollbar.active .listScrollbarThumb{
|
||||
background:linear-gradient(180deg, rgba(95,175,255,.5), rgba(95,175,255,.3));
|
||||
border-color:rgba(95,175,255,.4);
|
||||
box-shadow:0 2px 12px rgba(95,175,255,.15);
|
||||
background:rgba(136,164,196,.30);
|
||||
}
|
||||
|
||||
.row{position:relative; display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:11px 12px; border-bottom:1px solid var(--strokeLight); cursor:pointer; user-select:none; transition:background .2s ease, box-shadow .2s ease; box-shadow:inset 3px 0 0 transparent;}
|
||||
.row:hover{background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(95,175,255,.45);}
|
||||
.row:active{transform:none;}
|
||||
.row.active{background:linear-gradient(90deg, var(--accentBg), transparent); box-shadow:inset 3px 0 0 rgba(95,175,255,.7), 0 0 0 1px var(--accentGlow) inset;}
|
||||
/* Row — no transform/padding changes (preserves drag-and-drop) */
|
||||
.row{position:relative; display:flex; align-items:flex-start; justify-content:space-between; gap:10px; padding:9px 12px; border-bottom:1px solid var(--strokeLight); cursor:pointer; user-select:none; transition:background .2s ease, box-shadow .2s ease; box-shadow:inset 3px 0 0 transparent;}
|
||||
.row:hover{background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(136,164,196,.40);}
|
||||
.row:active{background:var(--surfaceActive);}
|
||||
.row.active{background:var(--accentBg); box-shadow:inset 3px 0 0 rgba(136,164,196,.65);}
|
||||
.row:focus-visible{outline-offset:-2px; background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(136,164,196,.40);}
|
||||
.row.dragging{opacity:.55;}
|
||||
|
||||
.left{min-width:0; display:flex; align-items:flex-start; gap:10px; flex:1 1 auto;}
|
||||
.numBadge{flex:0 0 auto; min-width:38px; height:22px; padding:0 8px; border-radius:999px; border:1px solid var(--strokeMed); background:radial-gradient(200px 60px at 20% 0%, var(--accentGlow), transparent 55%), var(--surface); box-shadow:var(--shadow2); display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:11.8px; letter-spacing:.08px; color:var(--text); font-variant-numeric:tabular-nums; margin-top:1px; transition:all .2s ease; transform:translateX(0);}
|
||||
.row:hover .numBadge{border-color:var(--accentBorder); background:radial-gradient(200px 60px at 20% 0%, var(--accentGlow), transparent 50%), var(--surfaceHover); transform:translateX(4px);}
|
||||
.left{min-width:0; display:flex; align-items:flex-start; gap:8px; flex:1 1 auto;}
|
||||
.numBadge{flex:0 0 auto; min-width:38px; height:22px; padding:0 8px; border-radius:var(--r2); border:none; background:var(--surface-2); box-shadow:var(--shadow3); display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:12px; letter-spacing:.02em; color:var(--text); font-variant-numeric:tabular-nums; margin-top:1px; transition:all .2s var(--ease-bounce);}
|
||||
.row:hover .numBadge{background:var(--surface-3); transform:scale(1.06);}
|
||||
|
||||
/* Tree connectors — fully static, opaque, dark lines */
|
||||
.treeSvg{flex:0 0 auto; margin-top:1px; overflow:visible;}
|
||||
.treeSvg line{stroke:rgb(65,75,95); stroke-width:1.5;}
|
||||
.treeSvg circle{fill:rgba(230,240,255,.70); stroke:rgba(100,180,255,.22); stroke-width:1; transition:all .15s ease;}
|
||||
.row:hover .treeSvg circle{fill:rgba(240,250,255,.85); stroke:rgba(100,180,255,.35);}
|
||||
.treeSvg line{stroke:rgb(40,46,60); stroke-width:1.5;}
|
||||
.treeSvg circle{fill:rgba(200,212,238,.60); stroke:rgba(136,164,196,.18); stroke-width:1;}
|
||||
|
||||
.textWrap{min-width:0; flex:1 1 auto; transition:transform .2s ease; transform:translateX(0);}
|
||||
.row:hover .textWrap{transform:translateX(4px);}
|
||||
.name{font-size:12.9px; font-weight:820; letter-spacing:.12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .15s ease;}
|
||||
.row:hover .name{color:rgba(255,255,255,.98);}
|
||||
.small{margin-top:4px; font-size:11.4px; color:var(--textMuted); font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .15s ease;}
|
||||
.row:hover .small{color:rgba(175,185,210,.85);}
|
||||
.textWrap{min-width:0; flex:1 1 auto;}
|
||||
.name{font-size:13px; font-weight:700; letter-spacing:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .2s var(--ease-spring), text-shadow .2s ease, transform .2s var(--ease-bounce); transform:translateX(0);}
|
||||
.row:hover .name{color:rgba(235,240,252,.96); text-shadow:0 0 20px rgba(136,164,196,.08); transform:translateX(4px);}
|
||||
.small{margin-top:4px; font-size:11px; color:var(--textMuted); font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .2s ease;}
|
||||
.row:hover .small{color:rgba(170,182,210,.75);}
|
||||
|
||||
.tag{flex:0 0 auto; display:inline-flex; align-items:center; padding:5px 9px; border-radius:999px; border:1px solid var(--stroke); background:var(--surface); color:var(--textMuted); font-size:11px; font-weight:820; letter-spacing:.12px; text-transform:uppercase; margin-top:2px; transition:all .15s ease;}
|
||||
.tag.now{border-color:var(--accentBorder); color:rgba(215,240,255,.90); background:var(--accentBg);}
|
||||
.tag.done{border-color:var(--successBorder); color:rgba(210,255,230,.88); background:var(--successBg);}
|
||||
.tag{flex:0 0 auto; display:inline-flex; align-items:center; padding:5px 9px; border-radius:var(--r2); border:none; background:var(--surface-2); color:var(--textMuted); font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; margin-top:2px; transition:all .2s var(--ease-bounce);}
|
||||
.row:hover .tag{transform:scale(1.05);}
|
||||
.tag.now{color:rgba(170,195,230,.88); background:var(--accentBg); animation:tagGlow 3s ease-in-out infinite;}
|
||||
@keyframes tagGlow{
|
||||
0%,100%{box-shadow:0 0 0 0 rgba(136,164,196,0);}
|
||||
50%{box-shadow:0 0 8px 0 rgba(136,164,196,.12);}
|
||||
}
|
||||
.tag.done{color:rgba(160,195,160,.78); background:var(--successBg);}
|
||||
.row:hover .tag.done{color:rgba(175,210,175,.88);}
|
||||
.tag.hidden{display:none;}
|
||||
.row:hover .tag{transform:scale(1.02);}
|
||||
|
||||
.row.drop-before::before,.row.drop-after::after{
|
||||
content:""; position:absolute; left:10px; right:10px; border-top:2px solid var(--accent);
|
||||
pointer-events:none; filter:drop-shadow(0 0 10px var(--accentGlow));
|
||||
pointer-events:none;
|
||||
animation:dropLineFlash .6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes dropLineFlash{
|
||||
0%,100%{opacity:.6;}
|
||||
50%{opacity:1;}
|
||||
}
|
||||
.row.drop-before::before{top:-1px;}
|
||||
.row.drop-after::after{bottom:-1px;}
|
||||
|
||||
.empty{padding:14px 12px; color:var(--textMuted); font-size:12.5px; line-height:1.4;}
|
||||
.empty{padding:10px 12px; color:var(--textMuted); font-size:13px; line-height:1.4;}
|
||||
|
||||
.playlistHeader{font-weight:900; letter-spacing:.16px; font-size:13.8px; cursor:help; display:flex; align-items:center; gap:10px;}
|
||||
.playlistHeader .fa{color:var(--iconStrong)!important; opacity:.92;}
|
||||
.playlistHeader{font-family:var(--brand); font-weight:800; letter-spacing:-.01em; font-size:14px; cursor:help; display:flex; align-items:center; gap:10px; transition:color .2s ease;}
|
||||
.playlistHeader:hover{color:rgba(235,240,252,.98);}
|
||||
.playlistHeader .fa{color:var(--iconStrong)!important; opacity:.92; transition:transform .3s var(--ease-bounce), opacity .2s ease;}
|
||||
.playlistHeader:hover .fa{transform:rotate(-8deg) scale(1.12); opacity:1;}
|
||||
|
||||
.moveWrap{display:flex; flex-direction:column; gap:2px; flex:0 0 auto; opacity:0; transition:opacity .2s ease;}
|
||||
.row:hover .moveWrap, .row:focus-within .moveWrap{opacity:1;}
|
||||
.moveBtn{width:22px; height:18px; border:none; background:var(--surface-2); border-radius:var(--r3); cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all .15s var(--ease-bounce); padding:0;}
|
||||
.moveBtn:hover{background:var(--surface-3); transform:scale(1.1);}
|
||||
.moveBtn:active{transform:scale(.9); transition-duration:.08s;}
|
||||
.moveBtn .fa{font-size:9px; color:var(--iconStrong)!important; opacity:.7;}
|
||||
.moveBtn:hover .fa{opacity:1;}
|
||||
|
||||
@@ -17,6 +17,9 @@ export function initSubtitles(): void {
|
||||
subsBtn = document.getElementById('subsBtn')!;
|
||||
subsMenu = document.getElementById('subsMenu')!;
|
||||
|
||||
subsBtn.setAttribute('aria-haspopup', 'true');
|
||||
subsBtn.setAttribute('aria-expanded', 'false');
|
||||
|
||||
subsBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
if (!library) return;
|
||||
@@ -24,6 +27,13 @@ export function initSubtitles(): void {
|
||||
else await openSubsMenu();
|
||||
});
|
||||
|
||||
subsBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && subsMenuOpen) {
|
||||
closeSubsMenu();
|
||||
subsBtn.focus();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('click', () => { if (subsMenuOpen) closeSubsMenu(); });
|
||||
subsMenu.addEventListener('click', (e) => e.stopPropagation());
|
||||
}
|
||||
@@ -92,6 +102,29 @@ export function clearSubtitles(): void {
|
||||
export function closeSubsMenu(): void {
|
||||
subsMenuOpen = false;
|
||||
subsMenu?.classList.remove('show');
|
||||
subsBtn?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
function menuItemKeyHandler(e: KeyboardEvent): void {
|
||||
const item = e.currentTarget as HTMLElement;
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
let next = item.nextElementSibling as HTMLElement | null;
|
||||
while (next && !next.classList.contains('subsMenuItem')) next = next.nextElementSibling as HTMLElement | null;
|
||||
if (next) next.focus();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
let prev = item.previousElementSibling as HTMLElement | null;
|
||||
while (prev && !prev.classList.contains('subsMenuItem')) prev = prev.previousElementSibling as HTMLElement | null;
|
||||
if (prev) prev.focus();
|
||||
} else if (e.key === 'Escape' || e.key === 'Tab') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
closeSubsMenu();
|
||||
subsBtn.focus();
|
||||
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
item.click();
|
||||
}
|
||||
}
|
||||
|
||||
export async function openSubsMenu(): Promise<void> {
|
||||
@@ -111,7 +144,10 @@ export async function openSubsMenu(): Promise<void> {
|
||||
for (const sub of available.sidecar) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'subsMenuItem';
|
||||
item.innerHTML = `<i class="fa-solid fa-file-lines"></i> ${sub.label} <span style="opacity:.5;font-size:10px;margin-left:auto;">${sub.format}</span>`;
|
||||
item.setAttribute('role', 'menuitem');
|
||||
item.tabIndex = -1;
|
||||
item.addEventListener('keydown', menuItemKeyHandler);
|
||||
item.innerHTML = `<i class="fa-solid fa-file-lines" aria-hidden="true"></i> ${sub.label} <span style="opacity:.5;font-size:10px;margin-left:auto;">${sub.format}</span>`;
|
||||
item.onclick = async () => {
|
||||
closeSubsMenu();
|
||||
const res = await api.loadSidecarSubtitle(sub.path);
|
||||
@@ -142,7 +178,10 @@ export async function openSubsMenu(): Promise<void> {
|
||||
for (const track of available.embedded) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'subsMenuItem embedded';
|
||||
item.innerHTML = `<i class="fa-solid fa-film"></i> ${track.label} <span style="opacity:.5;font-size:10px;margin-left:auto;">${track.codec}</span>`;
|
||||
item.setAttribute('role', 'menuitem');
|
||||
item.tabIndex = -1;
|
||||
item.addEventListener('keydown', menuItemKeyHandler);
|
||||
item.innerHTML = `<i class="fa-solid fa-film" aria-hidden="true"></i> ${track.label} <span style="opacity:.5;font-size:10px;margin-left:auto;">${track.codec}</span>`;
|
||||
item.onclick = async () => {
|
||||
closeSubsMenu();
|
||||
const res = await api.extractEmbeddedSubtitle(track.index);
|
||||
@@ -169,7 +208,10 @@ export async function openSubsMenu(): Promise<void> {
|
||||
// "Load from file" option
|
||||
const loadItem = document.createElement('div');
|
||||
loadItem.className = 'subsMenuItem';
|
||||
loadItem.innerHTML = '<i class="fa-solid fa-file-import"></i> Load from file...';
|
||||
loadItem.setAttribute('role', 'menuitem');
|
||||
loadItem.tabIndex = -1;
|
||||
loadItem.addEventListener('keydown', menuItemKeyHandler);
|
||||
loadItem.innerHTML = '<i class="fa-solid fa-file-import" aria-hidden="true"></i> Load from file...';
|
||||
loadItem.onclick = async () => {
|
||||
closeSubsMenu();
|
||||
try {
|
||||
@@ -187,7 +229,10 @@ export async function openSubsMenu(): Promise<void> {
|
||||
// "Disable" option
|
||||
const disableItem = document.createElement('div');
|
||||
disableItem.className = 'subsMenuItem';
|
||||
disableItem.innerHTML = '<i class="fa-solid fa-xmark"></i> Disable subtitles';
|
||||
disableItem.setAttribute('role', 'menuitem');
|
||||
disableItem.tabIndex = -1;
|
||||
disableItem.addEventListener('keydown', menuItemKeyHandler);
|
||||
disableItem.innerHTML = '<i class="fa-solid fa-xmark" aria-hidden="true"></i> Disable subtitles';
|
||||
disableItem.onclick = () => {
|
||||
closeSubsMenu();
|
||||
try {
|
||||
@@ -203,4 +248,7 @@ export async function openSubsMenu(): Promise<void> {
|
||||
|
||||
subsMenu.classList.add('show');
|
||||
subsMenuOpen = true;
|
||||
subsBtn.setAttribute('aria-expanded', 'true');
|
||||
const first = subsMenu.querySelector('.subsMenuItem') as HTMLElement | null;
|
||||
if (first) first.focus();
|
||||
}
|
||||
|
||||
105
src/ui.ts
105
src/ui.ts
@@ -9,6 +9,7 @@ import {
|
||||
clamp, fmtTime, fmtBytes, fmtDate, fmtBitrate,
|
||||
currentItem, cb,
|
||||
} from './store';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
|
||||
// ---- DOM refs ----
|
||||
let contentGrid: HTMLElement;
|
||||
@@ -34,6 +35,9 @@ let overallPct: HTMLElement;
|
||||
let toast: HTMLElement;
|
||||
let toastMsg: HTMLElement;
|
||||
let infoGridEl: HTMLElement;
|
||||
let winMinBtn: HTMLElement;
|
||||
let winMaxBtn: HTMLElement;
|
||||
let winCloseBtn: HTMLElement;
|
||||
|
||||
// Info panel elements
|
||||
let infoFolder: HTMLElement;
|
||||
@@ -80,6 +84,8 @@ export function initUI(): void {
|
||||
autoplayChk = document.getElementById('autoplayChk') as HTMLInputElement;
|
||||
chooseBtn = document.getElementById('chooseBtn')!;
|
||||
chooseDropBtn = document.getElementById('chooseDropBtn')!;
|
||||
chooseDropBtn.setAttribute('aria-haspopup', 'true');
|
||||
chooseDropBtn.setAttribute('aria-expanded', 'false');
|
||||
recentMenu = document.getElementById('recentMenu')!;
|
||||
refreshBtn = document.getElementById('refreshBtn')!;
|
||||
resetProgBtn = document.getElementById('resetProgBtn')!;
|
||||
@@ -94,6 +100,29 @@ export function initUI(): void {
|
||||
infoGridEl = document.getElementById('infoGrid')!;
|
||||
player = document.getElementById('player') as HTMLVideoElement;
|
||||
|
||||
// --- Window controls ---
|
||||
winMinBtn = document.getElementById('winMinBtn')!;
|
||||
winMaxBtn = document.getElementById('winMaxBtn')!;
|
||||
winCloseBtn = document.getElementById('winCloseBtn')!;
|
||||
|
||||
const appWindow = getCurrentWebviewWindow();
|
||||
|
||||
winMinBtn.onclick = () => appWindow.minimize();
|
||||
winMaxBtn.onclick = () => appWindow.toggleMaximize();
|
||||
winCloseBtn.onclick = () => appWindow.close();
|
||||
|
||||
// Update maximize icon based on window state
|
||||
const updateMaxIcon = async () => {
|
||||
try {
|
||||
const maximized = await appWindow.isMaximized();
|
||||
const icon = winMaxBtn.querySelector('i')!;
|
||||
icon.className = maximized ? 'fa-solid fa-clone' : 'fa-solid fa-square';
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
appWindow.onResized(() => { updateMaxIcon(); });
|
||||
updateMaxIcon();
|
||||
|
||||
infoFolder = document.getElementById('infoFolder')!;
|
||||
infoNext = document.getElementById('infoNext')!;
|
||||
infoStruct = document.getElementById('infoStruct')!;
|
||||
@@ -126,16 +155,42 @@ export function initUI(): void {
|
||||
}
|
||||
|
||||
// --- Divider drag ---
|
||||
divider.tabIndex = 0;
|
||||
divider.setAttribute('role', 'separator');
|
||||
divider.setAttribute('aria-orientation', 'vertical');
|
||||
divider.setAttribute('aria-label', 'Resize panels');
|
||||
divider.addEventListener('mousedown', (e) => {
|
||||
draggingDivider = true;
|
||||
document.body.style.userSelect = 'none';
|
||||
e.preventDefault();
|
||||
});
|
||||
divider.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const delta = e.key === 'ArrowLeft' ? -0.02 : 0.02;
|
||||
const current = prefs?.split_ratio || 0.62;
|
||||
prefs!.split_ratio = applySplit(current + delta);
|
||||
savePrefsPatch({ split_ratio: prefs!.split_ratio });
|
||||
}
|
||||
});
|
||||
dockDivider.tabIndex = 0;
|
||||
dockDivider.setAttribute('role', 'separator');
|
||||
dockDivider.setAttribute('aria-orientation', 'vertical');
|
||||
dockDivider.setAttribute('aria-label', 'Resize dock panes');
|
||||
dockDivider.addEventListener('mousedown', (e) => {
|
||||
draggingDockDivider = true;
|
||||
document.body.style.userSelect = 'none';
|
||||
e.preventDefault();
|
||||
});
|
||||
dockDivider.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const delta = e.key === 'ArrowLeft' ? -0.02 : 0.02;
|
||||
const current = prefs?.dock_ratio || 0.62;
|
||||
prefs!.dock_ratio = applyDockSplit(current + delta);
|
||||
savePrefsPatch({ dock_ratio: prefs!.dock_ratio });
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', async () => {
|
||||
if (draggingDivider) {
|
||||
@@ -255,6 +310,13 @@ export function initUI(): void {
|
||||
else await openRecentMenu();
|
||||
};
|
||||
|
||||
chooseDropBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && recentOpen) {
|
||||
closeRecentMenu();
|
||||
chooseDropBtn.focus();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => { if (recentOpen) positionRecentMenu(); });
|
||||
window.addEventListener('scroll', () => { if (recentOpen) positionRecentMenu(); }, true);
|
||||
window.addEventListener('click', () => { if (recentOpen) closeRecentMenu(); });
|
||||
@@ -324,13 +386,23 @@ export function updateNowHeader(it: VideoItem | null): void {
|
||||
}
|
||||
|
||||
export function updateOverall(): void {
|
||||
if (!library) { overallBar.style.width = '0%'; overallPct.textContent = '-'; return; }
|
||||
if (!library) {
|
||||
overallBar.style.width = '0%'; overallPct.textContent = '-';
|
||||
const pb = overallBar.parentElement;
|
||||
if (pb) pb.setAttribute('aria-valuenow', '0');
|
||||
return;
|
||||
}
|
||||
if (library.overall_progress === null || library.overall_progress === undefined) {
|
||||
overallBar.style.width = '0%'; overallPct.textContent = '-'; return;
|
||||
overallBar.style.width = '0%'; overallPct.textContent = '-';
|
||||
const pb = overallBar.parentElement;
|
||||
if (pb) pb.setAttribute('aria-valuenow', '0');
|
||||
return;
|
||||
}
|
||||
const p = clamp(library.overall_progress, 0, 100);
|
||||
overallBar.style.width = `${p.toFixed(1)}%`;
|
||||
overallPct.textContent = `${p.toFixed(1)}%`;
|
||||
const progressBarEl = overallBar.parentElement;
|
||||
if (progressBarEl) progressBarEl.setAttribute('aria-valuenow', String(Math.round(p)));
|
||||
}
|
||||
|
||||
export function updateInfoPanel(): void {
|
||||
@@ -470,6 +542,7 @@ function positionRecentMenu(): void {
|
||||
export function closeRecentMenu(): void {
|
||||
recentOpen = false;
|
||||
recentMenu.style.display = 'none';
|
||||
chooseDropBtn?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
export async function openRecentMenu(): Promise<void> {
|
||||
@@ -486,6 +559,8 @@ export async function openRecentMenu(): Promise<void> {
|
||||
for (const it of res.items) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'dropItem';
|
||||
row.setAttribute('role', 'menuitem');
|
||||
row.tabIndex = -1;
|
||||
row.dataset.tooltip = it.name;
|
||||
row.dataset.tooltipDesc = it.path;
|
||||
|
||||
@@ -497,9 +572,10 @@ export async function openRecentMenu(): Promise<void> {
|
||||
name.className = 'dropName';
|
||||
name.textContent = it.name;
|
||||
|
||||
const removeBtn = document.createElement('div');
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'dropRemove';
|
||||
removeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
|
||||
removeBtn.setAttribute('aria-label', `Remove ${it.name}`);
|
||||
removeBtn.innerHTML = '<i class="fa-solid fa-xmark" aria-hidden="true"></i>';
|
||||
removeBtn.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
@@ -522,12 +598,33 @@ export async function openRecentMenu(): Promise<void> {
|
||||
if (!info || !info.ok) { notify('Folder not available.'); return; }
|
||||
await cb.onLibraryLoaded?.(info, true);
|
||||
};
|
||||
row.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const next = row.nextElementSibling as HTMLElement | null;
|
||||
if (next && next.classList.contains('dropItem')) next.focus();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const prev = row.previousElementSibling as HTMLElement | null;
|
||||
if (prev && prev.classList.contains('dropItem')) prev.focus();
|
||||
} else if (e.key === 'Escape' || e.key === 'Tab') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
closeRecentMenu();
|
||||
chooseDropBtn.focus();
|
||||
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
row.click();
|
||||
}
|
||||
});
|
||||
recentMenu.appendChild(row);
|
||||
}
|
||||
}
|
||||
positionRecentMenu();
|
||||
recentMenu.style.display = 'block';
|
||||
recentOpen = true;
|
||||
chooseDropBtn.setAttribute('aria-expanded', 'true');
|
||||
const first = recentMenu.querySelector('.dropItem') as HTMLElement | null;
|
||||
if (first) first.focus();
|
||||
} catch (_) { closeRecentMenu(); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user