From c0e0b473ce455163fc6a28ecdd170015d95cde43 Mon Sep 17 00:00:00 2001 From: Sportinger Date: Sun, 10 May 2026 13:25:58 +0200 Subject: [PATCH 1/3] Add clip effect and color paste actions --- docs/Features/Color-Correction.md | 2 +- docs/Features/Effects.md | 2 + docs/Features/Keyframes.md | 10 + src/components/panels/color/ColorEditor.tsx | 51 +++ src/components/panels/color/colorTab.css | 20 ++ src/components/timeline/Timeline.tsx | 19 +- .../timeline/TimelineContextMenu.tsx | 64 ++++ src/stores/timeline/clipboardSlice.ts | 315 +++++++++++++++++- src/stores/timeline/index.ts | 2 + src/stores/timeline/selectors.ts | 4 + src/stores/timeline/types.ts | 20 ++ 11 files changed, 506 insertions(+), 3 deletions(-) diff --git a/docs/Features/Color-Correction.md b/docs/Features/Color-Correction.md index 922271c76..c3ff32880 100644 --- a/docs/Features/Color-Correction.md +++ b/docs/Features/Color-Correction.md @@ -4,7 +4,7 @@ Professional color correction for MasterSelects should become a dedicated clip tab that feels close to DaVinci Resolve's node page, while still offering a dense list/inspector mode for fast slider-based work. The important product decision is that node view and list view are not separate systems. They are two views over the same color-grade state. -This document started as the implementation plan and now also records the first production slices. The current implementation ships the canonical clip color state, the Properties Color tab, the dockable Color workspace, two-mode List/Nodes switching where Nodes opens the right-side workspace, left-button node-canvas panning, a collapsible workspace inspector, keyframable/MIDI-ready primary controls, a dedicated Wheels node with RGB chroma wheels plus luma sliders, project persistence, preview/export layer wiring, and a realtime WebGPU fused primary/wheels pass that can apply serial Primary and Wheels nodes in one shader pass. +This document started as the implementation plan and now also records the first production slices. The current implementation ships the canonical clip color state, the Properties Color tab, the dockable Color workspace, two-mode List/Nodes switching where Nodes opens the right-side workspace, left-button node-canvas panning, a collapsible workspace inspector, keyframable/MIDI-ready primary controls, a dedicated Wheels node with RGB chroma wheels plus luma sliders, a toolbar action to enable all color stopwatches at the playhead, timeline context-menu copy/paste for grades with color keyframes, project persistence, preview/export layer wiring, and a realtime WebGPU fused primary/wheels pass that can apply serial Primary and Wheels nodes in one shader pass. Remaining professional work includes advanced graph branches/mixers, qualifiers/windows, LUT management, still/reference workflows, selected-node scope routing, and higher precision intermediate textures. diff --git a/docs/Features/Effects.md b/docs/Features/Effects.md index 8937b999e..3dc58b927 100644 --- a/docs/Features/Effects.md +++ b/docs/Features/Effects.md @@ -85,6 +85,8 @@ effect.{effectId}.{paramName} `EffectsTab` reads interpolated values from the timeline store and writes animated numbers back through `setPropertyValue`. +The clip context menu supports Copy Effects and Paste Effects. This copies the full effect stack plus matching `effect.*` keyframes and pastes them onto the selected clip set. + ## Current Notes - `color` and `point` parameter types exist in the effect type system, but the current registered effects do not use them. diff --git a/docs/Features/Keyframes.md b/docs/Features/Keyframes.md index da9cf8d3f..65a1842c3 100644 --- a/docs/Features/Keyframes.md +++ b/docs/Features/Keyframes.md @@ -55,6 +55,16 @@ Examples: Audio fades are built from `audio-volume.volume` keyframes, and EQ lanes use the same effect-property naming pattern. +### Color Properties + +Color correction parameters use the color graph namespace: + +```text +color.{versionId}.{nodeId}.{paramName} +``` + +The Color panel can enable every color stopwatch for the active grade version at the current playhead. Timeline Copy Color and Paste Color copy the grade state together with its `color.*` keyframes. + ### Mask Properties Masks use flat keyframe property names scoped by mask id: diff --git a/src/components/panels/color/ColorEditor.tsx b/src/components/panels/color/ColorEditor.tsx index 5cd709d5e..80d2e0296 100644 --- a/src/components/panels/color/ColorEditor.tsx +++ b/src/components/panels/color/ColorEditor.tsx @@ -166,6 +166,17 @@ function NodeViewIcon() { ); } +function SetAllKeyframesIcon() { + return ( + + ); +} + function InspectorToggleIcon({ collapsed }: { collapsed: boolean }) { return (
+
{ + if (contextMenu.clipId) { + copyClipEffects(contextMenu.clipId); + } + setContextMenu(null); + }} + > + Copy Effects +
+
{ + if (contextMenu.clipId) { + copyClipColor(contextMenu.clipId); + } + setContextMenu(null); + }} + > + Copy Color +
+
{ + if (!canPasteEffects) return; + pasteClipEffects(getPasteTargetClipIds()); + setContextMenu(null); + }} + > + Paste Effects +
+
{ + if (!canPasteColor) return; + pasteClipColor(getPasteTargetClipIds()); + setContextMenu(null); + }} + > + Paste Color +
+
): string[] { + const ids = explicitTargets && explicitTargets.length > 0 + ? explicitTargets + : [...selectedClipIds]; + return [...new Set(ids)]; +} + export const createClipboardSlice: SliceCreator = (set, get) => ({ copyClips: () => { const { clips, selectedClipIds, clipKeyframes, tracks } = get(); @@ -703,4 +745,275 @@ export const createClipboardSlice: SliceCreator = (set, get) = invalidateCache(); log.info('Pasted keyframes', { count: clipboardKeyframes.length, targetClipId }); }, + + copyClipEffects: (clipId) => { + const { clips, clipKeyframes } = get(); + const clip = clips.find(c => c.id === clipId); + + if (!clip) { + log.warn('No source clip found for effects copy', { clipId }); + return; + } + + const effectIds = new Set((clip.effects || []).map(effect => effect.id)); + const keyframes = (clipKeyframes.get(clipId) || []) + .filter(keyframe => { + const parsed = parseEffectKeyframeProperty(keyframe.property); + return !!parsed && effectIds.has(parsed.effectId); + }) + .map(cloneKeyframe); + + set({ + clipboardEffects: { + sourceClipId: clipId, + effects: (clip.effects || []).map(cloneEffect), + keyframes, + }, + }); + + log.info('Copied clip effects', { + clipId, + effectCount: clip.effects?.length ?? 0, + keyframeCount: keyframes.length, + }); + }, + + pasteClipEffects: (targetClipIds) => { + const { + clipboardEffects, + selectedClipIds, + clips, + clipKeyframes, + keyframeRecordingEnabled, + selectedKeyframeIds, + invalidateCache, + } = get(); + + if (!clipboardEffects) { + log.debug('No copied effects to paste'); + return; + } + + const targetIds = getTargetClipIds(targetClipIds, selectedClipIds); + const targetIdSet = new Set(targetIds); + const targetClips = clips.filter(clip => targetIdSet.has(clip.id)); + + if (targetClips.length === 0) { + log.warn('No target clips found for effects paste', { targetClipIds: targetIds }); + return; + } + + captureSnapshot(targetClips.length === 1 ? 'Paste effects' : 'Paste effects to clips'); + + const nextKeyframes = new Map(clipKeyframes); + let nextSelectedKeyframeIds = selectedKeyframeIds; + let nextRecordingEnabled = keyframeRecordingEnabled; + + const updatedClips = clips.map(clip => { + if (!targetIdSet.has(clip.id)) return clip; + + const effectIdMap = new Map(); + const pastedEffects = clipboardEffects.effects.map(effect => { + const nextEffectId = generateEffectId(); + effectIdMap.set(effect.id, nextEffectId); + return { + ...cloneEffect(effect), + id: nextEffectId, + }; + }); + + const existingKeyframes = nextKeyframes.get(clip.id) || []; + const removedEffectKeyframeIds = new Set(); + const retainedKeyframes = existingKeyframes.filter(keyframe => { + if (!keyframe.property.startsWith('effect.')) return true; + removedEffectKeyframeIds.add(keyframe.id); + return false; + }); + + const pastedKeyframes: Keyframe[] = clipboardEffects.keyframes + .map(keyframe => { + const parsed = parseEffectKeyframeProperty(keyframe.property); + if (!parsed) return null; + + const mappedEffectId = effectIdMap.get(parsed.effectId); + if (!mappedEffectId) return null; + + return { + ...cloneKeyframe(keyframe), + id: generateKeyframeId(), + clipId: clip.id, + time: clampKeyframeTime(keyframe.time, clip.duration), + property: createEffectProperty(mappedEffectId, parsed.paramName) as AnimatableProperty, + }; + }) + .filter((keyframe): keyframe is Keyframe => keyframe !== null); + + const clipKeyframesAfterPaste = [...retainedKeyframes, ...pastedKeyframes] + .toSorted((a, b) => a.time - b.time); + + if (clipKeyframesAfterPaste.length > 0) { + nextKeyframes.set(clip.id, clipKeyframesAfterPaste); + } else { + nextKeyframes.delete(clip.id); + } + + if (removedEffectKeyframeIds.size > 0) { + nextSelectedKeyframeIds = new Set( + [...nextSelectedKeyframeIds].filter(keyframeId => !removedEffectKeyframeIds.has(keyframeId)) + ); + } + + const recordingPrefix = `${clip.id}:effect.`; + if ([...nextRecordingEnabled].some(recordingKey => recordingKey.startsWith(recordingPrefix))) { + nextRecordingEnabled = new Set( + [...nextRecordingEnabled].filter(recordingKey => !recordingKey.startsWith(recordingPrefix)) + ); + } + + return { + ...clip, + effects: pastedEffects, + }; + }); + + set({ + clips: updatedClips, + clipKeyframes: nextKeyframes, + keyframeRecordingEnabled: nextRecordingEnabled, + selectedKeyframeIds: nextSelectedKeyframeIds, + }); + invalidateCache(); + + log.info('Pasted clip effects', { + targetClipCount: targetClips.length, + effectCount: clipboardEffects.effects.length, + keyframeCount: clipboardEffects.keyframes.length, + }); + }, + + hasClipboardEffects: () => { + return get().clipboardEffects !== null; + }, + + copyClipColor: (clipId) => { + const { clips, clipKeyframes } = get(); + const clip = clips.find(c => c.id === clipId); + + if (!clip) { + log.warn('No source clip found for color copy', { clipId }); + return; + } + + const keyframes = (clipKeyframes.get(clipId) || []) + .filter(keyframe => keyframe.property.startsWith('color.')) + .map(cloneKeyframe); + + set({ + clipboardColor: { + sourceClipId: clipId, + colorCorrection: ensureColorCorrectionState(clip.colorCorrection), + keyframes, + }, + }); + + log.info('Copied clip color', { + clipId, + keyframeCount: keyframes.length, + }); + }, + + pasteClipColor: (targetClipIds) => { + const { + clipboardColor, + selectedClipIds, + clips, + clipKeyframes, + keyframeRecordingEnabled, + selectedKeyframeIds, + invalidateCache, + } = get(); + + if (!clipboardColor) { + log.debug('No copied color to paste'); + return; + } + + const targetIds = getTargetClipIds(targetClipIds, selectedClipIds); + const targetIdSet = new Set(targetIds); + const targetClips = clips.filter(clip => targetIdSet.has(clip.id)); + + if (targetClips.length === 0) { + log.warn('No target clips found for color paste', { targetClipIds: targetIds }); + return; + } + + captureSnapshot(targetClips.length === 1 ? 'Paste color' : 'Paste color to clips'); + + const nextKeyframes = new Map(clipKeyframes); + let nextSelectedKeyframeIds = selectedKeyframeIds; + let nextRecordingEnabled = keyframeRecordingEnabled; + + const updatedClips = clips.map(clip => { + if (!targetIdSet.has(clip.id)) return clip; + + const existingKeyframes = nextKeyframes.get(clip.id) || []; + const removedColorKeyframeIds = new Set(); + const retainedKeyframes = existingKeyframes.filter(keyframe => { + if (!keyframe.property.startsWith('color.')) return true; + removedColorKeyframeIds.add(keyframe.id); + return false; + }); + + const pastedKeyframes = clipboardColor.keyframes.map(keyframe => ({ + ...cloneKeyframe(keyframe), + id: generateKeyframeId(), + clipId: clip.id, + time: clampKeyframeTime(keyframe.time, clip.duration), + })); + + const clipKeyframesAfterPaste = [...retainedKeyframes, ...pastedKeyframes] + .toSorted((a, b) => a.time - b.time); + + if (clipKeyframesAfterPaste.length > 0) { + nextKeyframes.set(clip.id, clipKeyframesAfterPaste); + } else { + nextKeyframes.delete(clip.id); + } + + if (removedColorKeyframeIds.size > 0) { + nextSelectedKeyframeIds = new Set( + [...nextSelectedKeyframeIds].filter(keyframeId => !removedColorKeyframeIds.has(keyframeId)) + ); + } + + const recordingPrefix = `${clip.id}:color.`; + if ([...nextRecordingEnabled].some(recordingKey => recordingKey.startsWith(recordingPrefix))) { + nextRecordingEnabled = new Set( + [...nextRecordingEnabled].filter(recordingKey => !recordingKey.startsWith(recordingPrefix)) + ); + } + + return { + ...clip, + colorCorrection: structuredClone(clipboardColor.colorCorrection), + }; + }); + + set({ + clips: updatedClips, + clipKeyframes: nextKeyframes, + keyframeRecordingEnabled: nextRecordingEnabled, + selectedKeyframeIds: nextSelectedKeyframeIds, + }); + invalidateCache(); + + log.info('Pasted clip color', { + targetClipCount: targetClips.length, + keyframeCount: clipboardColor.keyframes.length, + }); + }, + + hasClipboardColor: () => { + return get().clipboardColor !== null; + }, }); diff --git a/src/stores/timeline/index.ts b/src/stores/timeline/index.ts index 0f0e62e33..7a0828335 100644 --- a/src/stores/timeline/index.ts +++ b/src/stores/timeline/index.ts @@ -222,6 +222,8 @@ export const useTimelineStore = create()( // Clipboard state for copy/paste clipboardData: null as import('./types').ClipboardClipData[] | null, clipboardKeyframes: null as import('./types').ClipboardKeyframeData[] | null, + clipboardEffects: null as import('./types').ClipboardClipEffectsData | null, + clipboardColor: null as import('./types').ClipboardClipColorData | null, // AI action visual feedback (transient, not serialized) aiActionOverlays: [] as import('./types').AIActionOverlay[], diff --git a/src/stores/timeline/selectors.ts b/src/stores/timeline/selectors.ts index 62dc937f5..06d27ab7d 100644 --- a/src/stores/timeline/selectors.ts +++ b/src/stores/timeline/selectors.ts @@ -251,4 +251,8 @@ export const selectMarkerActions = (state: TimelineStore) => ({ export const selectClipboardActions = (state: TimelineStore) => ({ copyClips: state.copyClips, pasteClips: state.pasteClips, + copyClipEffects: state.copyClipEffects, + pasteClipEffects: state.pasteClipEffects, + copyClipColor: state.copyClipColor, + pasteClipColor: state.pasteClipColor, }); diff --git a/src/stores/timeline/types.ts b/src/stores/timeline/types.ts index 123fc45e2..6a0de6f56 100644 --- a/src/stores/timeline/types.ts +++ b/src/stores/timeline/types.ts @@ -539,9 +539,23 @@ export interface ClipboardKeyframeData { handleOut?: BezierHandle; } +export interface ClipboardClipEffectsData { + sourceClipId: string; + effects: Effect[]; + keyframes: Keyframe[]; +} + +export interface ClipboardClipColorData { + sourceClipId: string; + colorCorrection: ColorCorrectionState; + keyframes: Keyframe[]; +} + export interface ClipboardState { clipboardData: ClipboardClipData[] | null; clipboardKeyframes: ClipboardKeyframeData[] | null; + clipboardEffects: ClipboardClipEffectsData | null; + clipboardColor: ClipboardClipColorData | null; } // Clipboard actions interface @@ -551,6 +565,12 @@ export interface ClipboardActions { hasClipboardData: () => boolean; copyKeyframes: () => void; pasteKeyframes: () => void; + copyClipEffects: (clipId: string) => void; + pasteClipEffects: (targetClipIds?: string[]) => void; + hasClipboardEffects: () => boolean; + copyClipColor: (clipId: string) => void; + pasteClipColor: (targetClipIds?: string[]) => void; + hasClipboardColor: () => boolean; } // Mask actions interface From 6edc457089e7b0fc7734a67104aeed90b75f934b Mon Sep 17 00:00:00 2001 From: Sportinger Date: Tue, 12 May 2026 00:07:34 +0200 Subject: [PATCH 2/3] refactor(css): modularize app styles --- .../issue-131-css-modularization-plan.md | 521 + src/App.css | 17094 ---------------- src/App.tsx | 3 +- .../common/IndexedDBErrorDialog.css | 142 + .../common/IndexedDBErrorDialog.tsx | 3 + src/components/common/InfoDialog.css | 177 + src/components/common/InfoDialog.tsx | 2 + src/components/common/LinuxVulkanWarning.css | 61 + src/components/common/LinuxVulkanWarning.tsx | 1 + src/components/common/NativeHelperStatus.css | 422 + src/components/common/NativeHelperStatus.tsx | 2 + src/components/common/RelinkDialog.css | 171 + src/components/common/RelinkDialog.tsx | 2 + src/components/common/SavedToast.css | 46 + src/components/common/SavedToast.tsx | 1 + src/components/common/SplashScreen.css | 156 + src/components/common/SplashScreen.tsx | 3 + src/components/common/Toolbar.css | 614 + src/components/common/Toolbar.tsx | 1 + .../common/TutorialCampaignDialog.css | 164 + .../common/TutorialCampaignDialog.tsx | 2 + src/components/common/TutorialOverlay.css | 352 + src/components/common/TutorialOverlay.tsx | 1 + src/components/common/WelcomeOverlay.css | 645 + src/components/common/WelcomeOverlay.tsx | 1 + src/components/common/WhatsNewDialog.css | 1360 ++ src/components/common/WhatsNewDialog.tsx | 2 + src/components/export/ExportPanel.css | 1151 ++ src/components/export/ExportPanel.tsx | 1 + src/components/panels/ClipPropertiesPanel.tsx | 2 + src/components/panels/MediaPanel.css | 1460 ++ src/components/panels/MediaPanel.tsx | 1 + .../properties/AnalysisTranscriptTabs.css | 239 + .../panels/properties/ClipPropertiesPanel.css | 34 + .../panels/properties/EffectsTab.css | 62 + .../panels/properties/PropertiesPanel.css | 1247 ++ src/components/panels/properties/TextTab.css | 318 + .../properties/VolumeBlendshapeTabs.css | 226 + src/components/panels/properties/index.tsx | 5 + src/components/preview/MaskOverlay.css | 443 + src/components/preview/MaskOverlay.tsx | 1 + src/components/preview/Preview.css | 641 + src/components/preview/Preview.tsx | 2 + src/components/preview/PreviewEditMode.css | 421 + src/components/preview/SourceMonitor.css | 166 + src/components/preview/SourceMonitor.tsx | 1 + src/components/timeline/MarkerContextMenu.tsx | 1 + src/components/timeline/MulticamDialog.css | 312 + src/components/timeline/MulticamDialog.tsx | 1 + src/components/timeline/SlotGrid.css | 267 + src/components/timeline/SlotGrid.tsx | 1 + src/components/timeline/Timeline.css | 183 + src/components/timeline/Timeline.tsx | 2 + src/components/timeline/TimelineClip.css | 1359 ++ src/components/timeline/TimelineClip.tsx | 1 + src/components/timeline/TimelineControls.css | 186 + src/components/timeline/TimelineControls.tsx | 1 + .../timeline/TimelineInteractions.css | 483 + src/components/timeline/TimelineKeyframes.css | 716 + src/components/timeline/TimelineKeyframes.tsx | 1 + src/components/timeline/TimelineMarkers.css | 433 + src/components/timeline/TimelineNavigator.css | 174 + src/components/timeline/TimelineNavigator.tsx | 1 + src/components/timeline/TimelineTracks.css | 244 + src/components/timeline/VerticalScrollbar.tsx | 1 + .../timeline/components/TimelineOverlays.tsx | 1 + src/index.css | 26 - src/main.tsx | 3 +- src/styles/app-shell.css | 896 + src/styles/base.css | 64 + src/styles/shared-controls.css | 653 + src/styles/tokens.css | 439 + 72 files changed, 17697 insertions(+), 17122 deletions(-) create mode 100644 docs/plans/issue-131-css-modularization-plan.md delete mode 100644 src/App.css create mode 100644 src/components/common/IndexedDBErrorDialog.css create mode 100644 src/components/common/InfoDialog.css create mode 100644 src/components/common/LinuxVulkanWarning.css create mode 100644 src/components/common/NativeHelperStatus.css create mode 100644 src/components/common/RelinkDialog.css create mode 100644 src/components/common/SavedToast.css create mode 100644 src/components/common/SplashScreen.css create mode 100644 src/components/common/Toolbar.css create mode 100644 src/components/common/TutorialCampaignDialog.css create mode 100644 src/components/common/TutorialOverlay.css create mode 100644 src/components/common/WelcomeOverlay.css create mode 100644 src/components/common/WhatsNewDialog.css create mode 100644 src/components/export/ExportPanel.css create mode 100644 src/components/panels/MediaPanel.css create mode 100644 src/components/panels/properties/AnalysisTranscriptTabs.css create mode 100644 src/components/panels/properties/ClipPropertiesPanel.css create mode 100644 src/components/panels/properties/EffectsTab.css create mode 100644 src/components/panels/properties/PropertiesPanel.css create mode 100644 src/components/panels/properties/TextTab.css create mode 100644 src/components/panels/properties/VolumeBlendshapeTabs.css create mode 100644 src/components/preview/MaskOverlay.css create mode 100644 src/components/preview/Preview.css create mode 100644 src/components/preview/PreviewEditMode.css create mode 100644 src/components/preview/SourceMonitor.css create mode 100644 src/components/timeline/MulticamDialog.css create mode 100644 src/components/timeline/SlotGrid.css create mode 100644 src/components/timeline/Timeline.css create mode 100644 src/components/timeline/TimelineClip.css create mode 100644 src/components/timeline/TimelineControls.css create mode 100644 src/components/timeline/TimelineInteractions.css create mode 100644 src/components/timeline/TimelineKeyframes.css create mode 100644 src/components/timeline/TimelineMarkers.css create mode 100644 src/components/timeline/TimelineNavigator.css create mode 100644 src/components/timeline/TimelineTracks.css delete mode 100644 src/index.css create mode 100644 src/styles/app-shell.css create mode 100644 src/styles/base.css create mode 100644 src/styles/shared-controls.css create mode 100644 src/styles/tokens.css diff --git a/docs/plans/issue-131-css-modularization-plan.md b/docs/plans/issue-131-css-modularization-plan.md new file mode 100644 index 000000000..49eb3be98 --- /dev/null +++ b/docs/plans/issue-131-css-modularization-plan.md @@ -0,0 +1,521 @@ +# Issue 131 CSS Modularization Plan + +## Goal + +Split the monolithic `src/App.css` into owner-focused CSS files that live with the relevant view, panel, or component area. + +The target result is not a temporary import-only split. The target result is the long-term structure: + +- Global CSS only contains tokens, themes, base document styles, app shell layout, and genuinely shared primitives. +- Feature and view CSS lives next to the owning React code. +- Class names that are part of runtime contracts remain stable. +- CSS cascade order is made explicit with cascade layers. +- The final Vite build still emits merged CSS assets. + +Current baseline: + +- `src/App.css`: about `17,094` lines. +- Many panels already have dedicated CSS files. +- The large remaining areas are timeline, media panel, properties panel, preview, common dialogs, and app shell styles. + +## Non-Goals + +- Do not convert the whole app to CSS Modules. +- Do not rename behavioral selectors during this refactor. +- Do not redesign UI, spacing, colors, or interaction states. +- Do not combine unrelated visual cleanup with the split. +- Do not remove existing test, tutorial, AI, or debug selector contracts. + +## CSS Modules Decision + +CSS Modules are not the best primary solution for this issue. + +Reason: + +- Many class names are used as structural contracts by timeline hit testing, DOM queries, tutorial selectors, debug tools, tests, and AI tooling. +- A full CSS Modules migration would require broad `:global(...)` usage or parallel data attributes, increasing churn without solving the immediate monolith problem cleanly. +- Plain CSS with clear ownership, namespaces, and cascade layers fits the current editor architecture better. + +Allowed use: + +- New isolated leaf components may use CSS Modules later. +- Existing large editor surfaces should remain plain CSS for this refactor. + +## Target Structure + +Expected global files: + +```text +src/styles/tokens.css +src/styles/base.css +src/styles/app-shell.css +src/styles/shared-controls.css +``` + +Expected colocated component and feature files: + +```text +src/components/common/Toolbar.css +src/components/common/WelcomeOverlay.css +src/components/common/WhatsNewDialog.css +src/components/common/SplashScreen.css +src/components/common/InfoDialog.css +src/components/common/RelinkDialog.css +src/components/common/TutorialOverlay.css +src/components/common/TutorialCampaignDialog.css +src/components/common/NativeHelperStatus.css +src/components/common/IndexedDBErrorDialog.css +src/components/common/LinuxVulkanWarning.css + +src/components/export/ExportPanel.css + +src/components/panels/MediaPanel.css +src/components/panels/properties/ClipPropertiesPanel.css +src/components/panels/properties/PropertiesPanel.css +src/components/panels/properties/TextTab.css +src/components/panels/properties/AnalysisTranscriptTabs.css +src/components/panels/properties/VolumeBlendshapeTabs.css +src/components/panels/properties/EffectsTab.css + +src/components/preview/Preview.css +src/components/preview/PreviewEditMode.css +src/components/preview/SourceMonitor.css +src/components/preview/MaskOverlay.css + +src/components/timeline/Timeline.css +src/components/timeline/TimelineControls.css +src/components/timeline/TimelineTracks.css +src/components/timeline/TimelineClip.css +src/components/timeline/TimelineInteractions.css +src/components/timeline/TimelineNavigator.css +src/components/timeline/TimelineMarkers.css +src/components/timeline/TimelineKeyframes.css +src/components/timeline/SlotGrid.css +src/components/timeline/MulticamDialog.css +``` + +`src/App.css` should either be removed or reduced to a tiny compatibility file if a temporary import path cannot be removed safely. The preferred final state is no meaningful CSS content in `src/App.css`. + +## Cascade Layers + +Use cascade layers to make order explicit: + +```css +@layer tokens, base, shell, shared, components, features, overlays; +``` + +Layer ownership: + +- `tokens`: CSS variables and theme tokens. +- `base`: reset, `html`, `body`, `#root`, text selection, focus defaults. +- `shell`: `.app`, main layout, workspace columns, high-level containers. +- `shared`: generic reusable editor controls such as sliders, draggable numbers, keyframe toggles, common context menu primitives. +- `components`: component-owned UI that is not a major editor view. +- `features`: large editor surfaces such as timeline, media panel, properties panel, preview, export. +- `overlays`: dialogs, tutorial overlays, welcome flow, changelog, warning banners. + +Rules: + +- Every moved block should be wrapped in the correct `@layer`. +- Do not rely on incidental import order where layer order is enough. +- When two selectors intentionally override each other in the same layer, preserve their relative order inside the same file or document the dependency. + +## Stable Selector Contracts + +Do not rename or module-scope these selectors without a separate migration: + +- `.timeline-*` +- `.media-*` +- `.clip-*` +- `.keyframe-*` +- `.source-monitor-*` +- `.preview-*` +- `.welcome-overlay*` +- `.changelog-*` +- `.tutorial-*` +- `.slot-grid-*` +- `.timeline-context-menu` +- Selectors referenced by `data-ai-id`, tutorial definitions, drag/drop logic, or `closest(...)` calls. + +If a selector is used by JS behavior, prefer adding a future `data-*` contract in a later task before renaming the class. + +## Estimated File Sizes + +The split should keep most files below 1,000 lines. Larger files are acceptable when they represent one dense feature surface. + +| File | Target LOC | +|---|---:| +| `src/styles/tokens.css` | 600-700 | +| `src/styles/base.css` | 80-120 | +| `src/styles/app-shell.css` | 500-700 | +| `src/styles/shared-controls.css` | 300-500 | +| `src/components/common/Toolbar.css` | 500-700 | +| `src/components/export/ExportPanel.css` | 1,100-1,200 | +| `src/components/panels/MediaPanel.css` | 1,400-1,500 | +| `src/components/panels/properties/ClipPropertiesPanel.css` | 400-500 | +| `src/components/panels/properties/PropertiesPanel.css` | 600-800 | +| `src/components/panels/properties/TextTab.css` | 300-400 | +| `src/components/panels/properties/AnalysisTranscriptTabs.css` | 300-450 | +| `src/components/panels/properties/VolumeBlendshapeTabs.css` | 250-400 | +| `src/components/panels/properties/EffectsTab.css` | 300-450 | +| `src/components/timeline/Timeline.css` | 500-700 | +| `src/components/timeline/TimelineControls.css` | 250-400 | +| `src/components/timeline/TimelineTracks.css` | 400-600 | +| `src/components/timeline/TimelineClip.css` | 800-1,000 | +| `src/components/timeline/TimelineInteractions.css` | 600-800 | +| `src/components/timeline/TimelineNavigator.css` | 300-450 | +| `src/components/timeline/TimelineMarkers.css` | 250-400 | +| `src/components/timeline/TimelineKeyframes.css` | 600-800 | +| `src/components/timeline/SlotGrid.css` | 250-350 | +| `src/components/preview/Preview.css` | 350-500 | +| `src/components/preview/PreviewEditMode.css` | 700-900 | +| `src/components/preview/SourceMonitor.css` | 150-250 | +| `src/components/common/WelcomeOverlay.css` | 400-500 | +| `src/components/common/NativeHelperStatus.css` | 600-750 | +| `src/components/common/WhatsNewDialog.css` | 1,100-1,300 | +| `src/components/common/SplashScreen.css` | 150-220 | +| `src/components/common/InfoDialog.css` | 150-220 | +| `src/components/common/RelinkDialog.css` | 150-220 | +| `src/components/common/TutorialOverlay.css` | 300-400 | +| `src/components/common/TutorialCampaignDialog.css` | 150-220 | +| `src/components/common/IndexedDBErrorDialog.css` | 120-170 | +| `src/components/common/LinuxVulkanWarning.css` | 50-90 | +| `src/components/timeline/MulticamDialog.css` | 300-350 | +| `src/components/preview/MaskOverlay.css` | 400-500 | + +## Branch and Merge Flow + +1. Start from `staging`. +2. Create a feature branch: + +```bash +git checkout staging +git pull --ff-only +git checkout -b refactor/issue-131-css-modularization +``` + +3. Do all refactor work on the feature branch. +4. Run verification. +5. Commit only if build, lint, and tests pass. +6. Merge back to `staging` after review. +7. Delete the feature branch after merge. + +Do not merge to `master` as part of this issue unless explicitly requested. + +## Parallel Agent Strategy + +The key to parallel work is avoiding multiple agents editing the same files at the same time. + +Use one coordinator and several focused workers. + +### Coordinator + +Owns: + +- `src/App.css` +- `src/App.tsx` +- `src/main.tsx` +- `src/styles/*` +- final integration +- final verification + +Responsibilities: + +1. Create the extraction manifest. +2. Add cascade layer order in the global entry. +3. Create global CSS files. +4. Remove extracted blocks from `src/App.css` after worker changes are ready. +5. Resolve import order and build issues. +6. Run final verification. + +The coordinator should be the only agent that removes large blocks from `src/App.css`. + +### Worker A: Timeline Core + +Owns: + +- `src/components/timeline/Timeline.css` +- `src/components/timeline/TimelineControls.css` +- `src/components/timeline/TimelineTracks.css` +- `src/components/timeline/TimelineNavigator.css` +- imports in timeline files only + +Source ranges: + +- Timeline container, controls, ruler, track headers, lanes, navigator, scrollbars. +- Approximate source area: `src/App.css` lines `5889-6521`, `8260-9049`. + +Do not edit: + +- Timeline clip internals. +- Timeline keyframes. +- Slot grid. +- Global shared controls. + +### Worker B: Timeline Clips and Interactions + +Owns: + +- `src/components/timeline/TimelineClip.css` +- `src/components/timeline/TimelineInteractions.css` +- `src/components/timeline/TimelineMarkers.css` +- imports in timeline files only + +Source ranges: + +- Timeline clips, clip states, drag previews, trim handles, fade handles, markers, playhead overlays, AI flash overlays, drop zones. +- Approximate source area: `src/App.css` lines `6522-8260`, `8475-8813`, `8938-9049`. + +Do not edit: + +- Timeline keyframe expansion and curve editor. +- Slot grid. + +### Worker C: Timeline Keyframes and Slot Grid + +Owns: + +- `src/components/timeline/TimelineKeyframes.css` +- `src/components/timeline/SlotGrid.css` +- `src/components/timeline/MulticamDialog.css` +- imports in timeline files only + +Source ranges: + +- Keyframe diamonds, keyframe context menu, expandable clip properties inside timeline, track-level keyframe expansion, curve editor, slot grid, multicam dialog. +- Approximate source area: `src/App.css` lines `10702-11480`, `13075-13384`, `16648-17094`. + +Do not edit: + +- Properties panel tabs. +- Shared draggable number and precision slider primitives unless instructed by coordinator. + +### Worker D: Media Panel + +Owns: + +- `src/components/panels/MediaPanel.css` +- import in `src/components/panels/MediaPanel.tsx` + +Source ranges: + +- Media panel list, table, grid, board, badges, label picker, add dropdown, media context menu. +- Approximate source area: `src/App.css` lines `9059-10523`. + +Do not edit: + +- Shared context menu primitives if used outside media panel. +- Timeline clip badges unless the selector is media-panel specific. + +### Worker E: Properties Panel + +Owns: + +- `src/components/panels/properties/ClipPropertiesPanel.css` +- `src/components/panels/properties/PropertiesPanel.css` +- `src/components/panels/properties/TextTab.css` +- `src/components/panels/properties/AnalysisTranscriptTabs.css` +- `src/components/panels/properties/VolumeBlendshapeTabs.css` +- `src/components/panels/properties/EffectsTab.css` +- imports in `ClipPropertiesPanel.tsx`, `PropertiesPanel.tsx`, and property tab components only + +Source ranges: + +- Clip properties, unified properties panel, embedded transcript and analysis tabs, text tab, volume tab, blendshapes, effects sections, MIDI rows in properties context. +- Approximate source area: `src/App.css` lines `3562-5738`. + +Do not edit: + +- Shared precision slider, draggable number, keyframe toggle, or global control primitives unless assigned by coordinator. + +### Worker F: Preview and Source Monitor + +Owns: + +- `src/components/preview/Preview.css` +- `src/components/preview/PreviewEditMode.css` +- `src/components/preview/SourceMonitor.css` +- `src/components/preview/MaskOverlay.css` +- imports in preview files only + +Source ranges: + +- Preview containers, floating preview, checkerboard canvas, preview edit mode, preview composition selector, bottom controls, source monitor, mask overlay. +- Approximate source area: `src/App.css` lines `1592-1832`, `11480-13075`. + +Do not edit: + +- App shell column layout outside preview. +- Export panel styles. + +### Worker G: Common Dialogs and Overlays + +Owns: + +- `src/components/common/WelcomeOverlay.css` +- `src/components/common/WhatsNewDialog.css` +- `src/components/common/SplashScreen.css` +- `src/components/common/InfoDialog.css` +- `src/components/common/RelinkDialog.css` +- `src/components/common/TutorialOverlay.css` +- `src/components/common/TutorialCampaignDialog.css` +- `src/components/common/NativeHelperStatus.css` +- `src/components/common/IndexedDBErrorDialog.css` +- `src/components/common/LinuxVulkanWarning.css` +- imports in common dialog files only + +Source ranges: + +- Welcome overlay, native helper, browser warnings, info dialog, relink dialog, changelog, splash screen, indexeddb error, linux warning, tutorial overlays. +- Approximate source area: `src/App.css` lines `13384-16648`. + +Do not edit: + +- Auth and billing dialogs already covered by `authBillingDialogs.css`, except where shared overlay classes require coordination. + +### Worker H: Export, Toolbar, Shell, Shared Controls + +Owns: + +- `src/components/export/ExportPanel.css` +- `src/components/common/Toolbar.css` +- `src/styles/app-shell.css` +- `src/styles/shared-controls.css` +- imports in export and toolbar files only + +Source ranges: + +- Toolbar, menu bar, project section, app shell, slots panel, old effects panel styles, export panel, precision slider, draggable number, keyframe toggle, common context menu primitives. +- Approximate source area: `src/App.css` lines `763-3562`, `10523-10791`, `12485-12618`. + +Do not edit: + +- Timeline-specific keyframe rules after `10791`. +- Preview-specific context menus if already assigned to Worker F. + +## Worker Instructions + +Each worker should: + +1. Read the owning component files before moving CSS. +2. Move full selector blocks only. +3. Preserve selector text exactly unless the coordinator approved a rename. +4. Wrap moved CSS in the assigned `@layer`. +5. Add the CSS import to the owning component or folder entrypoint. +6. Avoid editing `src/App.css` directly unless assigned as coordinator. +7. Record any ambiguous selectors in a short handoff note. + +Preferred import pattern: + +```ts +import './Timeline.css'; +``` + +Avoid importing a large feature stylesheet from unrelated parents. + +## Extraction Manifest + +Before parallel work starts, the coordinator should create a manifest like this: + +```text +source: src/App.css +block: lines 5919-6100 +target: src/components/timeline/Timeline.css +layer: features +owner: Worker A +notes: timeline root, empty state, high-level body +``` + +The manifest can live in the PR description or in a temporary local note. If committed, place it at: + +```text +docs/plans/issue-131-css-extraction-manifest.md +``` + +The manifest is the contract that prevents two workers from moving the same selectors. + +## Integration Order + +1. Coordinator creates `src/styles/tokens.css`, `src/styles/base.css`, `src/styles/app-shell.css`, and cascade layer order. +2. Workers add target CSS files and component imports without deleting from `src/App.css`. +3. Coordinator removes migrated blocks from `src/App.css` in one pass. +4. Coordinator checks for duplicate selectors left in both places. +5. Coordinator runs build and lint. +6. Coordinator fixes import or cascade issues. +7. Coordinator removes `src/App.css` import from `src/App.tsx` if no meaningful CSS remains. + +This is still the direct final architecture. The temporary duplicate step is only a merge-conflict control mechanism during parallel work. + +## Duplicate Selector Checks + +Use these checks after integration: + +```bash +rg "^[.#][A-Za-z0-9_-]+" src/**/*.css +rg "App.css" src +``` + +For a deeper duplicate scan, use a small script or Stylelint later. Manual review is acceptable for this refactor if the moved blocks remain exact and the final `App.css` is empty or nearly empty. + +## Verification + +Run targeted checks while developing: + +```bash +npm run build +npm run lint +npm run test +``` + +Before commit, all required repo checks must pass: + +```bash +npm run build +npm run lint +npm run test +``` + +Manual UI smoke areas: + +- App startup and welcome flow. +- Toolbar menus and nested flyouts. +- Timeline controls, clips, trimming, markers, keyframes, navigator. +- Media panel list, grid, board, dropdowns, and context menus. +- Properties panel tabs and controls. +- Preview edit mode and source monitor. +- Export panel. +- Changelog, splash, tutorial, relink, native helper, and warning dialogs. + +If the dev server and browser are already running, use the AI bridge for basic app health: + +```powershell +$token = Get-Content -Path .ai-bridge-token -Raw +$headers = @{ Authorization = "Bearer $token"; 'Content-Type' = 'application/json' } +$body = @{ tool = 'getStats'; args = @{} } | ConvertTo-Json -Depth 4 +Invoke-RestMethod -Uri 'http://localhost:5173/api/ai-tools' -Method Post -Headers $headers -Body $body +``` + +## Commit Plan + +Recommended commits: + +1. `docs: add issue 131 css modularization plan` +2. `refactor(css): add global style layers` +3. `refactor(css): move timeline styles to owned files` +4. `refactor(css): move panel and preview styles` +5. `refactor(css): move dialog and overlay styles` +6. `refactor(css): remove app css monolith` + +If the work is done by multiple agents on one branch, squash or reorganize commits before merge so each commit is buildable. + +## Review Checklist + +- `src/App.css` no longer contains feature-scale CSS. +- Global styles are limited to tokens, base, shell, and shared primitives. +- Feature CSS is imported by owning components or folder entrypoints. +- Cascade layer order is explicit. +- No class names used by JS behavior were renamed. +- No tutorial, debug, AI, or test selectors were broken. +- Build, lint, and tests pass. +- Visual smoke check covers timeline, media, properties, preview, export, and overlays. + diff --git a/src/App.css b/src/App.css deleted file mode 100644 index e293946a4..000000000 --- a/src/App.css +++ /dev/null @@ -1,17094 +0,0 @@ -/* WebVJ Mixer - Dark Theme Styles */ - -:root { - /* ═══════════════════════════════════════════ - PRIMITIVE COLORS (raw palette) - ═══════════════════════════════════════════ */ - - /* Gray scale */ - --gray-950: #0a0a0a; - --gray-900: #0f0f0f; - --gray-850: #141414; - --gray-800: #161616; - --gray-750: #1a1a1a; - --gray-700: #1e1e1e; - --gray-650: #222222; - --gray-600: #282828; - --gray-550: #2a2a2a; - --gray-500: #333333; - --gray-450: #3a3a3a; - --gray-400: #444444; - --gray-350: #4a4a4a; - --gray-300: #555555; - --gray-250: #666666; - --gray-200: #888888; - --gray-150: #999999; - --gray-100: #aaaaaa; - --gray-75: #cccccc; - --gray-50: #d4d4d4; - --gray-25: #e0e0e0; - --gray-0: #ffffff; - - /* Blue (accent) */ - --blue-700: #1a5fb0; - --blue-600: #2080CC; - --blue-500: #2D8CEB; - --blue-400: #4DA3F0; - --blue-300: #60a5fa; - --blue-200: #93c5fd; - - /* Red (danger) */ - --red-700: #c53030; - --red-600: #E74C3C; - --red-500: #ef4444; - --red-400: #f87171; - --red-300: #ff6b6b; - - /* Green (success) */ - --green-700: #15803d; - --green-600: #22c55e; - --green-500: #27AE60; - --green-400: #4ade80; - --green-300: #51cf66; - - /* Yellow/Amber (warning) */ - --amber-700: #b45309; - --amber-600: #f59e0b; - --amber-500: #F39C12; - --amber-400: #fbbf24; - --amber-300: #fcd34d; - - /* Purple */ - --purple-600: #7c3aed; - --purple-500: #8b5cf6; - --purple-400: #a855f7; - --purple-300: #c084fc; - - /* Cyan */ - --cyan-500: #22d3ee; - --cyan-400: #67e8f9; - - /* ═══════════════════════════════════════════ - SEMANTIC TOKENS (meaning-based) - ═══════════════════════════════════════════ */ - - /* Backgrounds */ - --bg-primary: var(--gray-900); - --bg-secondary: var(--gray-800); - --bg-tertiary: var(--gray-700); - --bg-hover: var(--gray-600); - --bg-active: var(--gray-550); - --bg-elevated: var(--gray-650); - --bg-input: var(--gray-750); - --bg-overlay: rgba(0, 0, 0, 0.7); - --bg-overlay-light: rgba(0, 0, 0, 0.4); - --bg-canvas: #000000; - - /* Borders */ - --border-color: var(--gray-950); - --border-subtle: var(--gray-500); - --border-strong: var(--gray-400); - --border-focus: var(--blue-500); - - /* Text */ - --text-primary: var(--gray-50); - --text-secondary: var(--gray-150); - --text-muted: var(--gray-250); - --text-disabled: var(--gray-300); - --text-inverse: #000000; - --text-on-accent: #ffffff; - - /* Accent (primary action color) */ - --accent: var(--blue-500); - --accent-hover: var(--blue-400); - --accent-dim: rgba(45, 140, 235, 0.15); - --accent-subtle: rgba(45, 140, 235, 0.1); - - /* Timeline accent (slightly different blue for timeline elements) */ - --accent-timeline: #2997E5; - --accent-timeline-hover: var(--blue-600); - - /* Status colors */ - --danger: var(--red-600); - --danger-light: var(--red-400); - --danger-dim: rgba(239, 68, 68, 0.15); - --danger-subtle: rgba(239, 68, 68, 0.1); - --danger-border: rgba(239, 68, 68, 0.3); - - --success: var(--green-600); - --success-light: var(--green-400); - --success-dim: rgba(34, 197, 94, 0.15); - --success-subtle: rgba(34, 197, 94, 0.1); - - --warning: var(--amber-500); - --warning-light: var(--amber-400); - --warning-dim: rgba(245, 158, 11, 0.15); - - --info: var(--blue-300); - --info-dim: rgba(96, 165, 250, 0.15); - - /* Purple (expressions, AI, special) */ - --purple: var(--purple-500); - --purple-light: var(--purple-400); - --purple-dim: rgba(139, 92, 246, 0.15); - - /* ═══════════════════════════════════════════ - SPACING SCALE (4px base grid) - ═══════════════════════════════════════════ */ - --sp-0: 0px; - --sp-0-5: 2px; - --sp-1: 4px; - --sp-1-5: 6px; - --sp-2: 8px; - --sp-2-5: 10px; - --sp-3: 12px; - --sp-4: 16px; - --sp-5: 20px; - --sp-6: 24px; - --sp-8: 32px; - --sp-10: 40px; - - /* ═══════════════════════════════════════════ - TYPOGRAPHY - ═══════════════════════════════════════════ */ - --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-mono: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; - - --font-2xs: 9px; - --font-xs: 10px; - --font-sm: 11px; - --font-md: 12px; - --font-lg: 13px; - --font-xl: 14px; - --font-2xl: 16px; - --font-3xl: 18px; - - --font-normal: 400; - --font-medium: 500; - --font-semibold: 600; - --font-bold: 700; - - --leading-tight: 1.2; - --leading-normal: 1.4; - --leading-relaxed: 1.5; - - --tracking-tight: -0.01em; - --tracking-normal: 0; - --tracking-wide: 0.5px; - - /* ═══════════════════════════════════════════ - BORDER RADIUS - ═══════════════════════════════════════════ */ - --radius-xs: 1px; - --radius-sm: 2px; - --radius-md: 4px; - --radius-lg: 6px; - --radius-xl: 8px; - --radius-2xl: 12px; - --radius-full: 9999px; - - /* ═══════════════════════════════════════════ - SHADOWS - ═══════════════════════════════════════════ */ - --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4); - --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.5); - --shadow-dialog: 0 20px 50px rgba(0, 0, 0, 0.5); - - /* ═══════════════════════════════════════════ - TRANSITIONS - ═══════════════════════════════════════════ */ - --transition-fast: 0.1s ease; - --transition-normal: 0.15s ease; - --transition-slow: 0.2s ease; - --transition-slower: 0.3s ease; - - /* ═══════════════════════════════════════════ - Z-INDEX SCALE - ═══════════════════════════════════════════ */ - --z-base: 1; - --z-dropdown: 100; - --z-sticky: 200; - --z-overlay: 1000; - --z-modal: 10000; - --z-tooltip: 10001; - - /* ═══════════════════════════════════════════ - COMPONENT TOKENS - ═══════════════════════════════════════════ */ - - /* Toolbar */ - --toolbar-height: 28px; - --toolbar-bg: var(--bg-tertiary); - --toolbar-font-size: var(--font-sm); - - /* Tab bar */ - --tab-height: 26px; - --tab-font-size: var(--font-sm); - --tab-active-bg: var(--gray-550); - - /* Panel headers */ - --panel-header-bg: var(--bg-tertiary); - --panel-header-font-size: var(--font-xl); - --panel-header-weight: var(--font-semibold); - --panel-header-padding: var(--sp-3) var(--sp-4); - - /* Inputs */ - --input-bg: var(--bg-tertiary); - --input-border: var(--border-color); - --input-border-focus: var(--accent); - --input-radius: var(--radius-md); - --input-padding: var(--sp-1-5) var(--sp-2); - --input-font-size: var(--font-md); - - /* Buttons */ - --btn-radius: var(--radius-md); - --btn-padding: var(--sp-2) var(--sp-4); - --btn-font-size: var(--font-md); - --btn-font-weight: var(--font-medium); - - /* Scrollbar */ - --scrollbar-width: 6px; - --scrollbar-track: var(--bg-secondary); - --scrollbar-thumb: var(--border-strong); - --scrollbar-thumb-hover: var(--text-muted); - - /* Slot/group colors (fixed palette) */ - --group-0: #ff6b6b; - --group-1: #4ecdc4; - --group-2: #ffe66d; - --group-3: #95e1d3; - --group-4: #dda0dd; - --group-5: #87ceeb; - - /* Pick whip / expression colors */ - --expr-color: #e5c07b; - --expr-color-light: #f0d090; - --expr-color-dark: #d4a84b; - --expr-color-stroke: #b8922f; - --expr-connected: #98c379; - - /* Marker type colors */ - --marker-green: var(--success); - --marker-amber: var(--amber-600); - --marker-red: var(--danger); - --marker-purple: var(--purple); - --marker-cyan: var(--cyan-500); - - /* Timeline grid lines (subtle tinted vertical lines) */ - --timeline-grid-video: #282830; - --timeline-grid-audio: #283028; - - /* Preview overlay buttons (over dark canvas) */ - --preview-overlay-bg: rgba(0, 0, 0, 0.6); - --preview-overlay-bg-hover: rgba(0, 0, 0, 0.8); - - /* Chat bubble (user message) */ - --chat-user-bg: var(--gray-500); - --chat-user-border: var(--gray-450); - - /* Backward compat aliases for panels that used different names */ - --accent-primary: var(--accent); - --accent-success: var(--success); - --accent-danger: var(--red-300); - --warning-color: var(--warning); - --accent-color: var(--accent); -} - -/* ═══════════════════════════════════════════ - THEME: LIGHT - Overrides semantic + component tokens only. - Primitives stay fixed, --bg-canvas stays #000. - ═══════════════════════════════════════════ */ - -[data-theme="light"] { - /* Backgrounds */ - --bg-primary: #f5f5f5; - --bg-secondary: #e8e8e8; - --bg-tertiary: #dedede; - --bg-hover: #d0d0d0; - --bg-active: #c8c8c8; - --bg-elevated: #ffffff; - --bg-input: #ffffff; - --bg-overlay: rgba(0, 0, 0, 0.4); - --bg-overlay-light: rgba(0, 0, 0, 0.2); - - /* Borders */ - --border-color: #c0c0c0; - --border-subtle: #d0d0d0; - --border-strong: #a0a0a0; - - /* Text */ - --text-primary: #1a1a1a; - --text-secondary: #4a4a4a; - --text-muted: #777777; - --text-disabled: #aaaaaa; - --text-inverse: #ffffff; - - /* Accent */ - --accent: #1a73e8; - --accent-hover: #1565c0; - --accent-dim: rgba(26, 115, 232, 0.12); - --accent-subtle: rgba(26, 115, 232, 0.08); - --accent-timeline: #1a73e8; - --accent-timeline-hover: #1565c0; - - /* Status colors */ - --danger: #d32f2f; - --danger-light: #ef5350; - --danger-dim: rgba(211, 47, 47, 0.1); - --danger-subtle: rgba(211, 47, 47, 0.06); - --danger-border: rgba(211, 47, 47, 0.25); - --success: #2e7d32; - --success-light: #43a047; - --success-dim: rgba(46, 125, 50, 0.1); - --success-subtle: rgba(46, 125, 50, 0.06); - --warning: #ed6c02; - --warning-light: #ff9800; - --warning-dim: rgba(237, 108, 2, 0.1); - --info: #0288d1; - --info-dim: rgba(2, 136, 209, 0.1); - --purple: #7b1fa2; - --purple-light: #9c27b0; - --purple-dim: rgba(123, 31, 162, 0.1); - - /* Shadows */ - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); - --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); - --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12); - --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.14); - --shadow-dialog: 0 12px 40px rgba(0, 0, 0, 0.18); - - /* Component tokens */ - --tab-active-bg: #d0d0d0; - --scrollbar-track: #e8e8e8; - --scrollbar-thumb: #bbb; - --scrollbar-thumb-hover: #999; - - /* Timeline grid - light tinted lines */ - --timeline-grid-video: #d0d0d8; - --timeline-grid-audio: #c8d8c8; - - /* Preview overlay - frosted glass on dark canvas */ - --preview-overlay-bg: rgba(255, 255, 255, 0.2); - --preview-overlay-bg-hover: rgba(255, 255, 255, 0.35); - - /* Chat bubble */ - --chat-user-bg: #d0d8e8; - --chat-user-border: #b8c4d8; - - /* Compat aliases */ - --accent-primary: var(--accent); - --accent-danger: #ef5350; -} - -/* Light theme: overlay/dialog/changelog overrides */ -[data-theme="light"] .welcome-overlay { - background: linear-gradient(165deg, rgba(255, 255, 255, 0.97) 0%, rgba(240, 240, 240, 0.99) 100%); - border: 1px solid rgba(0, 0, 0, 0.1); - box-shadow: - 0 0 0 1px rgba(0, 0, 0, 0.04), - 0 40px 80px rgba(0, 0, 0, 0.15), - 0 0 120px rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .welcome-enter { - color: rgba(0, 0, 0, 0.4); -} - -[data-theme="light"] .welcome-enter:hover { - color: rgba(0, 0, 0, 0.7); -} - -[data-theme="light"] .welcome-enter kbd { - background: rgba(0, 0, 0, 0.06); - border: 1px solid rgba(0, 0, 0, 0.1); - color: rgba(0, 0, 0, 0.5); -} - -[data-theme="light"] .welcome-enter:hover kbd { - background: rgba(0, 0, 0, 0.1); - border-color: rgba(0, 0, 0, 0.15); - color: rgba(0, 0, 0, 0.7); -} - -[data-theme="light"] .changelog-header { - background: linear-gradient(to bottom, rgba(245, 245, 245, 0.97) 0%, rgba(245, 245, 245, 0.85) 50%, transparent 100%); -} - -[data-theme="light"] .changelog-header::after { - background: linear-gradient(to bottom, rgba(245, 245, 245, 0.97), transparent); -} - -[data-theme="light"] .changelog-version { - color: rgba(0, 0, 0, 0.4); -} - -[data-theme="light"] .changelog-brand-master { - color: rgba(0, 0, 0, 0.9); -} - -[data-theme="light"] .changelog-brand-selects { - color: rgba(0, 0, 0, 0.34); -} - -[data-theme="light"] .changelog-header-title { - color: rgba(0, 0, 0, 0.4); -} - -[data-theme="light"] .changelog-header-button { - background: rgba(0, 0, 0, 0.05); - border-color: rgba(0, 0, 0, 0.1); - color: rgba(0, 0, 0, 0.68); -} - -[data-theme="light"] .changelog-header-button:hover { - background: rgba(0, 0, 0, 0.08); - border-color: rgba(0, 0, 0, 0.14); - color: rgba(0, 0, 0, 0.88); -} - -[data-theme="light"] .changelog-notice-link { - color: rgba(146, 64, 14, 0.95); -} - -[data-theme="light"] .changelog-close { - background: rgba(0, 0, 0, 0.05); -} - -[data-theme="light"] .changelog-close:hover { - background: rgba(0, 0, 0, 0.1); -} - -[data-theme="light"] .changelog-tabs { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .changelog-tab { - background: rgba(0, 0, 0, 0.04); -} - -[data-theme="light"] .changelog-tab:hover { - background: rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .changelog-tab.active { - background: rgba(0, 0, 0, 0.1); -} - -[data-theme="light"] .changelog-calendar { - background: transparent; - border: none; - box-shadow: none; -} - -[data-theme="light"] .changelog-calendar-day { - background: rgba(0, 0, 0, 0.06); - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.03); -} - -[data-theme="light"] .changelog-calendar-day:not(.is-future):not(.is-outside-range):hover { - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18); -} - -[data-theme="light"] .changelog-calendar-tooltip { - background: rgba(255, 255, 255, 0.98); - border: 1px solid rgba(0, 0, 0, 0.08); - color: rgba(0, 0, 0, 0.82); - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); -} - -[data-theme="light"] .changelog-calendar-tooltip::after { - background: rgba(255, 255, 255, 0.98); - border-right: 1px solid rgba(0, 0, 0, 0.08); - border-bottom: 1px solid rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .changelog-calendar-level-1 .changelog-calendar-fill-main { - background: rgba(46, 125, 50, 0.22); -} - -[data-theme="light"] .changelog-calendar-level-2 .changelog-calendar-fill-main { - background: rgba(46, 125, 50, 0.38); -} - -[data-theme="light"] .changelog-calendar-level-3 .changelog-calendar-fill-main { - background: rgba(46, 125, 50, 0.58); -} - -[data-theme="light"] .changelog-calendar-level-4 .changelog-calendar-fill-main { - background: rgba(46, 125, 50, 0.82); -} - -[data-theme="light"] .changelog-calendar-community-level-0 .changelog-calendar-fill-community { - background: rgba(245, 158, 11, 0.16); -} - -[data-theme="light"] .changelog-calendar-community-level-1 .changelog-calendar-fill-community { - background: rgba(245, 158, 11, 0.3); -} - -[data-theme="light"] .changelog-calendar-community-level-2 .changelog-calendar-fill-community { - background: rgba(217, 119, 6, 0.48); -} - -[data-theme="light"] .changelog-calendar-community-level-3 .changelog-calendar-fill-community { - background: rgba(217, 119, 6, 0.68); -} - -[data-theme="light"] .changelog-calendar-community-level-4 .changelog-calendar-fill-community { - background: rgba(180, 83, 9, 0.9); -} - -[data-theme="light"] .changelog-group-line { - background: linear-gradient(90deg, rgba(0, 0, 0, 0.12) 0%, transparent 100%); -} - -[data-theme="light"] .changelog-item { - background: rgba(0, 0, 0, 0.02); -} - -[data-theme="light"] .changelog-item.has-description:hover { - background: rgba(0, 0, 0, 0.05); -} - -[data-theme="light"] .changelog-item-community { - background: - linear-gradient(135deg, rgba(217, 119, 6, 0.08) 0%, rgba(255, 255, 255, 0.03) 45%, rgba(250, 204, 21, 0.08) 100%); - border: 1px solid rgba(217, 119, 6, 0.2); - box-shadow: inset 0 0 0 1px rgba(250, 204, 21, 0.06); -} - -[data-theme="light"] .changelog-badge-community { - background: rgba(217, 119, 6, 0.14); - border-color: rgba(217, 119, 6, 0.3); - color: rgba(146, 64, 14, 0.96); -} - -[data-theme="light"] .changelog-contributor-link, -[data-theme="light"] .changelog-contributor-label { - color: rgba(146, 64, 14, 0.86); -} - -[data-theme="light"] .changelog-commit-count { - background: rgba(0, 0, 0, 0.06); -} - -[data-theme="light"] .changelog-footer { - background: linear-gradient(to top, rgba(245, 245, 245, 0.97) 0%, rgba(245, 245, 245, 0.85) 40%, transparent 100%); -} - -[data-theme="light"] .changelog-footer::before { - background: linear-gradient(to top, rgba(245, 245, 245, 0.97), transparent); -} - -[data-theme="light"] .changelog-content { - background: rgba(245, 245, 245, 0.97); -} - -[data-theme="light"] .changelog-dont-show { - color: rgba(0, 0, 0, 0.4); -} - -[data-theme="light"] .changelog-dont-show:hover { - color: rgba(0, 0, 0, 0.7); -} - -[data-theme="light"] .stats-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.12); -} - -[data-theme="light"] .stats-section { - border-bottom: 1px solid rgba(0, 0, 0, 0.06); -} - -[data-theme="light"] .stats-bar-container { - background: rgba(0, 0, 0, 0.08); -} - -/* ═══════════════════════════════════════════ - THEME: MIDNIGHT (OLED) - True black backgrounds, slightly brighter text. - ═══════════════════════════════════════════ */ - -[data-theme="midnight"] { - /* True black backgrounds */ - --bg-primary: #000000; - --bg-secondary: #080808; - --bg-tertiary: #111111; - --bg-hover: #1a1a1a; - --bg-active: #1e1e1e; - --bg-elevated: #141414; - --bg-input: #0a0a0a; - --bg-overlay: rgba(0, 0, 0, 0.85); - --bg-overlay-light: rgba(0, 0, 0, 0.6); - - /* Borders */ - --border-color: #1a1a1a; - --border-subtle: #2a2a2a; - --border-strong: #3a3a3a; - - /* Text */ - --text-primary: #e0e0e0; - --text-secondary: #a0a0a0; - - /* Accent */ - --accent: #3d9df5; - --accent-hover: #5bb0ff; - --accent-dim: rgba(61, 157, 245, 0.15); - --accent-subtle: rgba(61, 157, 245, 0.1); - - /* Shadows */ - --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.6); - --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.7); - --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.8); - --shadow-dialog: 0 20px 50px rgba(0, 0, 0, 0.8); - - /* Component tokens */ - --tab-active-bg: #1e1e1e; - --scrollbar-track: #080808; - --scrollbar-thumb: #2a2a2a; - --scrollbar-thumb-hover: #3a3a3a; - - /* Timeline grid - deeper black tinted lines */ - --timeline-grid-video: #101018; - --timeline-grid-audio: #081008; -} - -/* ═══════════════════════════════════════════ - THEME TRANSITION - Brief smooth transition when switching themes - ═══════════════════════════════════════════ */ - -html.theme-transitioning, -html.theme-transitioning *, -html.theme-transitioning *::before, -html.theme-transitioning *::after { - transition: background-color 0.2s ease, color 0.15s ease, - border-color 0.2s ease, box-shadow 0.2s ease !important; -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - padding: 0; - background: var(--bg-primary); - color: var(--text-primary); - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - font-size: var(--font-md); - overflow: hidden; - user-select: none; - -webkit-user-select: none; -} - -/* Allow text selection in inputs */ -input, textarea { - user-select: text; - -webkit-user-select: text; -} - -.app { - display: flex; - flex-direction: column; - min-height: 100vh; - min-width: 900px; - height: 100vh; - width: 100vw; - overflow: auto; -} - -.project-load-progress { - position: fixed; - left: 50%; - bottom: 18px; - z-index: var(--z-overlay); - width: min(420px, calc(100vw - 32px)); - transform: translateX(-50%); - padding: 10px 12px; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--bg-elevated) 94%, transparent); - box-shadow: var(--shadow-lg); - pointer-events: none; -} - -.project-load-progress-head, -.project-load-progress-detail { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-3); - min-width: 0; -} - -.project-load-progress-head { - color: var(--text-primary); - font-size: var(--font-md); - font-weight: var(--font-semibold); -} - -.project-load-progress-head span:first-child, -.project-load-progress-detail span:first-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.project-load-progress-bar { - height: 4px; - margin: 8px 0 6px; - overflow: hidden; - border-radius: var(--radius-full); - background: var(--bg-input); -} - -.project-load-progress-bar > div { - height: 100%; - border-radius: inherit; - background: var(--accent); - transition: width var(--transition-normal); -} - -.project-load-progress-detail { - color: var(--text-secondary); - font-size: var(--font-sm); -} - -/* Toolbar */ -.toolbar { - display: flex; - align-items: center; - gap: 0; - padding: 0 var(--sp-2); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - min-height: 28px; - flex-shrink: 0; -} - -.toolbar-section { - display: flex; - align-items: center; - gap: var(--sp-2); - padding: 0 var(--sp-3); -} - -.toolbar-spacer { - flex: 1; -} - -.toolbar-center { - display: flex; - align-items: center; - gap: var(--sp-3); - justify-content: center; -} - -.toolbar-right { - margin-left: 0; -} - -.toolbar-credit-pill { - display: inline-flex; - align-items: center; - gap: 6px; - height: 28px; - padding: 0 10px; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 999px; - background: rgba(255, 255, 255, 0.05); - color: var(--text-primary); - cursor: pointer; - transition: - border-color var(--transition-fast), - background var(--transition-fast); -} - -.toolbar-credit-pill:hover { - border-color: rgba(255, 255, 255, 0.18); - background: rgba(255, 255, 255, 0.09); -} - -.toolbar-credit-pill-label { - color: rgba(255, 255, 255, 0.45); - font-size: var(--font-xs); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.toolbar-credit-pill-value { - color: rgba(255, 255, 255, 0.85); - font-size: var(--font-sm); - font-variant-numeric: tabular-nums; -} - -.toolbar-right .version { - color: var(--text-secondary); - font-size: var(--font-sm); - margin-right: var(--sp-3); - opacity: 0.7; -} - -.toolbar-project { - display: flex; - align-items: center; - height: 28px; - padding: 0 var(--sp-2) 0 var(--sp-1); - border-right: 1px solid var(--border-color); -} - -.toolbar-project .project-name { - font-weight: var(--font-semibold); - font-size: var(--font-sm); - color: var(--text-primary); - cursor: pointer; - padding: 3px var(--sp-1-5); - border-radius: var(--radius-sm); - max-width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.toolbar-project .project-name:hover { - background: var(--bg-hover); -} - -.toolbar-project .project-name-input { - font-size: var(--font-sm); - font-weight: var(--font-semibold); - padding: 3px var(--sp-1-5); - border-radius: var(--radius-sm); - border: 1px solid var(--accent); - background: var(--bg-primary); - color: var(--text-primary); - width: 150px; -} - -.toolbar-project .restore-permission-btn { - font-size: var(--font-md); - font-weight: var(--font-medium); - padding: var(--sp-1) var(--sp-3); - border-radius: var(--radius-md); - border: 1px solid var(--accent); - background: var(--accent); - color: var(--text-on-accent); - cursor: pointer; - transition: all var(--transition-normal) ease; -} - -.toolbar-project .restore-permission-btn:hover { - background: var(--accent-hover); - border-color: var(--accent-hover); -} - -.toolbar-project .restore-permission-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Menu Bar */ -.menu-bar { - display: flex; - align-items: center; - height: 28px; -} - -.menu-item { - position: relative; -} - -.menu-trigger { - background: transparent; - border: none; - color: var(--text-secondary); - font-size: var(--font-sm); - padding: var(--sp-1) var(--sp-2-5); - cursor: pointer; - height: 28px; - transition: background var(--transition-fast), color var(--transition-fast); -} - -.menu-trigger:hover, -.menu-trigger.active { - background: var(--bg-hover); - color: var(--text-primary); -} - -.menu-dropdown { - position: absolute; - top: 100%; - left: 0; - min-width: 200px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - box-shadow: var(--shadow-lg); - z-index: 10000; - padding: var(--sp-1) 0; - overflow: visible; -} - -.menu-dropdown-wide { - min-width: 220px; -} - -.menu-option { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: var(--sp-1-5) var(--sp-3); - background: transparent; - border: none; - color: var(--text-primary); - font-size: var(--font-sm); - cursor: pointer; - text-align: left; - transition: background var(--transition-fast); -} - -.menu-option:hover { - background: var(--bg-hover); -} - -.menu-option:disabled, -.menu-option.disabled { - color: var(--text-muted); - cursor: default; -} - -.menu-option:disabled:hover, -.menu-option.disabled:hover { - background: transparent; -} - -.menu-option-wip { - opacity: 0.5; -} - -.menu-wip-badge { - font-size: var(--font-sm); - margin-left: auto; - filter: saturate(3) brightness(0.9); -} - -.menu-option .shortcut { - color: var(--text-muted); - font-size: var(--font-sm); - margin-left: var(--sp-6); -} - -.menu-option .menu-hint { - color: var(--text-muted); - font-size: var(--font-xs); - margin-left: auto; - padding-left: 12px; -} - -.menu-option.checked { - color: var(--accent); -} - -.menu-option.project-item { - padding-right: 8px; -} - -.menu-option .delete-btn { - background: transparent; - border: none; - color: var(--text-muted); - font-size: var(--font-2xl); - cursor: pointer; - padding: 2px 6px; - border-radius: var(--radius-sm); - opacity: 0; - transition: opacity var(--transition-fast), background var(--transition-fast); -} - -.menu-option:hover .delete-btn { - opacity: 1; -} - -.menu-option .delete-btn:hover { - background: rgba(255, 68, 68, 0.2); - color: var(--danger); -} - -.menu-separator { - height: 1px; - background: var(--border-color); - margin: 4px 0; -} - -.menu-submenu { - padding: 0; -} - -.menu-label { - display: block; - padding: 8px 16px 4px; - font-size: var(--font-sm); - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.menu-empty { - display: block; - padding: var(--sp-2) var(--sp-4); - font-size: var(--font-md); - color: var(--text-muted); - font-style: italic; -} - -.menu-info { - display: block; - padding: 4px 16px 8px; - font-size: var(--font-sm); - color: var(--text-secondary); -} - -.menu-sublabel { - display: block; - padding: 6px 16px 2px; - font-size: var(--font-xs); - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.4px; -} - -.menu-subseparator { - height: 1px; - margin: 4px 12px; - background: var(--border-color); -} - -/* Nested submenu (flyout) styles */ -.menu-item-with-submenu { - position: relative; -} - -.menu-item-with-submenu > .menu-option { - display: flex; - justify-content: space-between; - align-items: center; -} - -.menu-item-with-submenu > .menu-option::after { - content: '▶'; - font-size: 8px; - color: var(--text-muted); - margin-left: var(--sp-3); -} - -.menu-nested-submenu { - display: none; - position: absolute; - left: 100%; - top: -4px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - box-shadow: var(--shadow-xl); - min-width: 140px; - padding: 4px 0; - z-index: 1001; -} - -.menu-item-with-submenu:hover > .menu-nested-submenu { - display: block; -} - -.menu-nested-submenu-layouts { - min-width: 320px; -} - -.menu-nested-submenu-panels { - min-width: 240px; -} - -.menu-nested-submenu-recent { - min-width: 300px; - max-width: 420px; -} - -.menu-option-recent { - align-items: flex-start; - gap: 16px; -} - -.menu-recent-text { - display: flex; - min-width: 0; - flex: 1; - flex-direction: column; - gap: 2px; -} - -.menu-recent-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.menu-recent-meta { - overflow: hidden; - color: var(--text-muted); - font-size: var(--font-xs); - text-overflow: ellipsis; - white-space: nowrap; -} - -.menu-recent-kind { - flex: 0 0 auto; - color: var(--text-muted); - font-size: var(--font-xs); - line-height: 18px; -} - -.menu-layout-row { - display: flex; - align-items: stretch; - gap: 6px; - padding: 0 6px; -} - -.menu-layout-load { - flex: 1; - min-width: 0; - border-radius: var(--radius-sm); -} - -.menu-layout-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.menu-layout-default-btn { - flex: 0 0 auto; - min-width: 64px; - margin: 2px 0; - padding: 0 10px; - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - background: transparent; - color: var(--text-muted); - font-size: var(--font-xs); - cursor: pointer; - transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); -} - -.menu-layout-default-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.menu-layout-default-btn.active { - border-color: var(--accent); - background: rgba(92, 153, 255, 0.12); - color: var(--accent); -} - -.project-name.no-project { - color: var(--text-secondary); - cursor: default; - font-style: italic; -} - -.project-name.no-project:hover { - background: transparent; -} - -.menu-slider { - display: flex; - align-items: center; - padding: var(--sp-2) var(--sp-4); - gap: var(--sp-2); -} - -.menu-slider input[type="range"] { - flex: 1; - height: 4px; - appearance: none; - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - cursor: pointer; -} - -.menu-slider input[type="range"]::-webkit-slider-thumb { - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; -} - -.menu-slider input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; - border: none; -} - -.menu-slider .slider-value { - font-size: var(--font-sm); - color: var(--text-muted); - min-width: 36px; - text-align: right; -} - -/* EQ Container */ -.eq-container { - display: flex; - gap: var(--sp-1); - padding: var(--sp-2) var(--sp-4); - justify-content: center; -} - -.eq-band { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--sp-1); -} - -.eq-slider { - writing-mode: vertical-lr; - direction: rtl; - width: 20px; - height: 80px; - appearance: none; - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - cursor: pointer; -} - -.eq-slider::-webkit-slider-thumb { - appearance: none; - width: 16px; - height: 8px; - border-radius: var(--radius-sm); - background: var(--accent); - cursor: pointer; -} - -.eq-slider::-moz-range-thumb { - width: 16px; - height: 8px; - border-radius: var(--radius-sm); - background: var(--accent); - cursor: pointer; - border: none; -} - -.eq-label { - font-size: var(--font-2xs); - color: var(--text-muted); - text-align: center; -} - -.status { - font-size: var(--font-md); -} - -.status.ready { - color: var(--success); -} - -.status.loading { - color: var(--warning); -} - -/* GPU Selector Dropdown */ -.gpu-selector { - position: relative; - margin-right: var(--sp-2); -} - -.gpu-trigger { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - cursor: pointer; - font-size: var(--font-xl); - transition: all var(--transition-normal) ease; -} - -.gpu-trigger:hover:not(:disabled) { - background: var(--bg-hover); - border-color: var(--text-muted); -} - -.gpu-trigger:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.gpu-trigger.switching { - animation: gpu-spin 0.8s linear infinite; -} - -@keyframes gpu-spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -.gpu-dropdown { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 200px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - box-shadow: var(--shadow-lg); - z-index: 10001; - overflow: hidden; -} - -.gpu-dropdown-header { - padding: var(--sp-2) var(--sp-3); - font-size: var(--font-sm); - font-weight: var(--font-semibold); - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom: 1px solid var(--border-color); - background: var(--bg-tertiary); -} - -.gpu-option { - display: flex; - flex-direction: column; - align-items: flex-start; - width: 100%; - padding: var(--sp-2-5) var(--sp-3); - background: transparent; - border: none; - cursor: pointer; - transition: background var(--transition-fast) ease; -} - -.gpu-option:hover { - background: var(--bg-hover); -} - -.gpu-option.active { - background: var(--accent-dim); -} - -.gpu-option .gpu-icon { - position: absolute; - right: 12px; - font-size: var(--font-2xl); -} - -.gpu-option .gpu-label { - font-size: var(--font-sm); - font-weight: var(--font-medium); - color: var(--text-primary); - display: flex; - align-items: center; - gap: var(--sp-2); -} - -.gpu-option .gpu-desc { - font-size: var(--font-sm); - color: var(--text-muted); - margin-top: 2px; -} - -.gpu-note { - padding: var(--sp-2) var(--sp-3); - font-size: var(--font-xs); - color: var(--text-muted); - background: var(--bg-tertiary); - border-top: 1px solid var(--border-color); - font-style: italic; -} - -/* Project Section */ -.project-section { - position: relative; -} - -.project-name { - font-size: var(--font-sm); - color: var(--text-primary); - cursor: pointer; - padding: 3px 6px; - border-radius: var(--radius-sm); - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.project-name:hover { - background: var(--bg-hover); -} - -.project-name-input { - font-size: var(--font-sm); - padding: 3px 6px; - border-radius: var(--radius-sm); - border: 1px solid var(--accent); - background: var(--bg-primary); - color: var(--text-primary); - width: 150px; -} - -.project-buttons { - display: flex; - gap: var(--sp-1); -} - -.project-menu { - position: absolute; - top: 100%; - left: 0; - margin-top: var(--sp-1); - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - min-width: 280px; - max-height: 300px; - overflow-y: auto; - z-index: 1000; - box-shadow: var(--shadow-md); -} - -.project-menu-header { - padding: var(--sp-2) var(--sp-3); - font-size: var(--font-md); - color: var(--text-secondary); - border-bottom: 1px solid var(--border-color); -} - -.project-menu-empty { - padding: var(--sp-4); - text-align: center; - color: var(--text-secondary); - font-size: var(--font-lg); -} - -.project-menu-item { - display: flex; - align-items: center; - gap: var(--sp-2); - padding: var(--sp-2-5) var(--sp-3); - cursor: pointer; - border-bottom: 1px solid var(--border-color); -} - -.project-menu-item:last-child { - border-bottom: none; -} - -.project-menu-item:hover { - background: var(--bg-hover); -} - -.project-menu-name { - flex: 1; - font-size: var(--font-lg); - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.project-menu-date { - font-size: var(--font-sm); - color: var(--text-secondary); -} - -.project-menu-delete { - width: 20px; - height: 20px; - padding: 0; - border: none; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - font-size: var(--font-2xl); - line-height: 1; - border-radius: var(--radius-md); - opacity: 0; - transition: opacity var(--transition-normal); -} - -.project-menu-item:hover .project-menu-delete { - opacity: 1; -} - -.project-menu-delete:hover { - background: rgba(255, 0, 0, 0.2); - color: var(--danger-light); -} - -/* Main Content */ -.main-content { - display: flex; - flex: 1; - overflow: hidden; -} - -/* Left Column - Toolbar + Slots */ -.left-column { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - overflow: hidden; -} - -/* Slots Area - Main largest element */ -.slots-area { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - min-height: 0; - background: var(--bg-secondary); - overflow: hidden; -} - -/* Resize Handle */ -.resize-handle { - width: 2px; - background: var(--border-color); - cursor: col-resize; - flex-shrink: 0; - transition: background var(--transition-slow); -} - -.resize-handle:hover, -.resizing .resize-handle { - background: var(--accent); -} - -.resizing { - cursor: col-resize; - user-select: none; -} - -/* Right Column - Preview + Effects */ -.right-column { - width: 360px; - flex-shrink: 0; - display: flex; - flex-direction: column; - background: var(--bg-secondary); -} - -.effects-section { - flex: 1; - overflow-y: auto; -} - -/* Floating Draggable Preview */ -.floating-preview { - position: fixed; - z-index: 1000; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - box-shadow: var(--shadow-xl); - overflow: hidden; - min-width: 280px; - max-width: 800px; -} - -.floating-preview-header { - display: flex; - align-items: center; - gap: var(--sp-1-5); - padding: 5px 10px; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - cursor: grab; - user-select: none; - font-size: var(--font-sm); - font-weight: var(--font-medium); -} - -.floating-preview-header:active { - cursor: grabbing; -} - -.drag-handle { - color: var(--text-secondary); - font-size: var(--font-md); - letter-spacing: -2px; -} - -.dragging-preview { - cursor: grabbing; - user-select: none; -} - -.dragging-preview .floating-preview { - opacity: 0.95; - box-shadow: var(--shadow-xl); -} - -/* Preview - maintains video aspect ratio, always shows full video */ -.preview-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: var(--bg-canvas); - height: 100%; - width: 100%; - overflow: hidden; - position: relative; -} - -.preview-stats { - position: absolute; - top: 8px; - right: 8px; - font-size: var(--font-sm); - color: var(--text-secondary); - background: var(--preview-overlay-bg); - padding: var(--sp-1) var(--sp-2); - border-radius: var(--radius-md); - z-index: 10; - cursor: pointer; - font-family: 'SF Mono', 'Consolas', monospace; - user-select: none; - backdrop-filter: blur(4px); - transition: all var(--transition-normal) ease; -} - -.preview-stats:hover { - background: var(--preview-overlay-bg-hover); -} - -.preview-top-right-overlays { - position: absolute; - top: 8px; - right: 8px; - z-index: 22; - display: flex; - align-items: flex-start; - justify-content: flex-end; - gap: 8px; - pointer-events: none; -} - -.preview-top-right-overlays > * { - pointer-events: auto; -} - -.preview-top-right-overlays .preview-stats { - position: static; -} - -.preview-stats-compact { - min-width: 80px; -} - -.preview-stats-expanded { - min-width: 200px; - padding: var(--sp-2) var(--sp-3); -} - -.stats-header { - margin-bottom: var(--sp-2); - padding-bottom: 6px; - border-bottom: 1px solid rgba(255, 255, 255, 0.15); -} - -.stats-section { - margin-bottom: var(--sp-2); - padding-bottom: 6px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); -} - -.stats-section:last-child { - margin-bottom: 0; - padding-bottom: 0; - border-bottom: none; -} - -.stats-label { - font-size: var(--font-2xs); - text-transform: uppercase; - letter-spacing: 0.5px; - opacity: 0.5; - margin-bottom: var(--sp-1); -} - -.stats-row { - display: flex; - justify-content: space-between; - gap: var(--sp-3); - font-size: var(--font-xs); - line-height: 1.6; -} - -.stats-bar-container { - height: 4px; - background: rgba(255, 255, 255, 0.1); - border-radius: var(--radius-sm); - margin: 4px 0; - display: flex; - overflow: hidden; -} - -.stats-bar { - height: 100%; - transition: width var(--transition-slow) ease; -} - -.stats-bar-import { - background: #4af; -} - -.stats-bar-render { - background: #4f4; -} - -.stats-bar-submit { - background: #fa4; -} - -.preview-canvas-wrapper { - position: relative; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg-primary); -} - -/* Checkerboard pattern on canvas itself for transparency visualization */ -.preview-canvas-wrapper.show-transparency-grid .preview-canvas { - background-color: #404040; - background-image: - linear-gradient(45deg, #303030 25%, transparent 25%), - linear-gradient(-45deg, #303030 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #303030 75%), - linear-gradient(-45deg, transparent 75%, #303030 75%); - background-size: 48px 48px; - background-position: 0 0, 0 24px, 24px -24px, -24px 0px; -} - -.preview-canvas { - max-width: 100%; - max-height: 100%; - width: auto; - height: auto; - object-fit: contain; - background: var(--bg-canvas); - display: block; -} - -.loading { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: flex; - flex-direction: column; - align-items: center; - gap: var(--sp-4); - color: var(--text-secondary); -} - -.loading-spinner { - width: 40px; - height: 40px; - border: 3px solid var(--border-color); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Panel Headers */ -.panel-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--sp-3) var(--sp-4); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.panel-header h3, -.panel-header h4 { - margin: 0; - font-size: var(--font-xl); - font-weight: var(--font-semibold); -} - -.panel-empty { - padding: var(--sp-6) var(--sp-4); - text-align: center; - color: var(--text-secondary); -} - -/* Slots Panel */ -.layer-panel { - display: flex; - flex-direction: column; - height: 100%; - min-height: 0; - overflow: hidden; -} - -.slots-panel .panel-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.slot-count { - font-size: var(--font-md); - color: var(--text-secondary); -} - -/* Grid Controls */ -.grid-controls { - display: flex; - gap: var(--sp-1); -} - -.grid-btn { - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-xl); - width: 28px; - height: 28px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all var(--transition-normal); -} - -.grid-btn:hover { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); -} - -/* Unified Grid with Row and Column Triggers */ -.slots-grid-with-triggers { - flex: 1; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - padding: var(--sp-2); - display: grid; - /* grid-template-columns set dynamically via inline style */ - grid-auto-rows: min-content; - gap: var(--sp-1-5); - align-content: start; -} - -.trigger-corner { - /* Empty corner cell */ -} - -.row-trigger-btn, -.column-trigger-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-sm); - font-weight: var(--font-semibold); - cursor: pointer; - transition: all var(--transition-normal); - display: flex; - align-items: center; - justify-content: center; -} - -.column-trigger-btn { - height: 28px; -} - -.row-trigger-btn { - /* Height matches slot via aspect-ratio */ -} - -.row-trigger-btn:hover, -.column-trigger-btn:hover { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); -} - -.row-trigger-btn:active, -.column-trigger-btn:active { - transform: scale(0.95); -} - -.row-trigger-btn.active, -.column-trigger-btn.active { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); - box-shadow: 0 0 10px var(--accent); -} - -/* Slot Item */ -.slot-item { - position: relative; - aspect-ratio: 1; - background: var(--bg-tertiary); - border: 2px dashed var(--border-color); - border-radius: var(--radius-lg); - overflow: hidden; - cursor: pointer; - transition: all var(--transition-slow); - display: flex; - flex-direction: column; -} - -.slot-item.empty { - opacity: 0.4; -} - -.slot-item.has-layer { - border-style: solid; - opacity: 0.6; -} - -.slot-item.has-media { - opacity: 1; - border-color: var(--border-color); -} - -.slot-item:hover { - border-color: var(--gray-300); - opacity: 1; -} - -.slot-item.selected { - border-color: var(--accent); - box-shadow: 0 0 0 1px var(--accent); -} - -/* Drag & Drop states */ -.slot-item.dragging { - opacity: 0.5; - transform: scale(0.95); -} - -.slot-item.drag-over { - border-color: var(--accent); - border-style: solid; - background: rgba(45, 140, 235, 0.1); -} - -.slot-item.column-highlight, -.slot-item.row-highlight { - border-color: var(--accent); - box-shadow: 0 0 12px var(--accent), inset 0 0 20px rgba(45, 140, 235, 0.2); - opacity: 1; -} - -.slot-item[draggable="true"] { - cursor: grab; -} - -.slot-item[draggable="true"]:active { - cursor: grabbing; -} - -/* Slot Header */ -.slot-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--sp-1-5) var(--sp-2); - background: transparent; - font-size: 22px; - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: 1; -} - -.slot-number { - color: var(--text-secondary); - font-weight: var(--font-semibold); - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); -} - -/* Slot Preview */ -.slot-preview { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg-canvas); -} - -.slot-thumbnail { - width: 100%; - height: 100%; - object-fit: cover; -} - -.slot-empty-media { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - color: var(--text-secondary); - font-size: 20px; - transition: all var(--transition-slow); -} - -.slot-empty-media:hover { - background: var(--bg-hover); - color: var(--accent); -} - -/* Slot Placeholder (empty slot) */ -.slot-placeholder { - width: 100%; - height: 100%; - display: flex; - align-items: flex-start; - justify-content: flex-end; - padding: var(--sp-1) var(--sp-1-5); - cursor: pointer; -} - -.slot-placeholder:hover { - background: var(--bg-hover); -} - -.slot-add { - font-size: 32px; - color: var(--text-secondary); - opacity: 0; - cursor: pointer; - transition: opacity var(--transition-normal), color var(--transition-normal), transform var(--transition-normal); -} - -.slot-placeholder:hover .slot-add { - opacity: 0.6; -} - -.slot-add:hover { - opacity: 1 !important; - color: var(--accent); - transform: scale(1.2); -} - -/* Slot Controls */ -.slot-controls { - position: absolute; - bottom: 4px; - right: 4px; - z-index: 1; -} - - -.btn-clear { - background: rgba(0, 0, 0, 0.5); - border: none; - color: var(--danger); - cursor: pointer; - font-size: 20px; - padding: var(--sp-1) var(--sp-2); - line-height: 1; - border-radius: var(--radius-md); - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); -} - -.btn-clear:hover { - color: var(--danger-light); - background: rgba(0, 0, 0, 0.7); -} - -/* Visibility button in slots */ -.visibility-btn { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: 2px; - font-size: 20px; - line-height: 1; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); -} - -.visibility-btn.visible { - color: var(--accent); -} - -/* Layer Settings in Effects Panel */ -.layer-settings { - padding: var(--sp-3); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -/* Control Rows */ -.control-row { - display: flex; - align-items: center; - gap: var(--sp-2); - margin-bottom: var(--sp-2); -} - -.control-row:last-child { - margin-bottom: 0; -} - -.control-row label { - min-width: 70px; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.control-row input[type="range"] { - flex: 1; - height: 4px; - -webkit-appearance: none; - background: var(--bg-primary); - border-radius: var(--radius-sm); - cursor: pointer; -} - -.control-row input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 14px; - height: 14px; - background: var(--accent); - border-radius: 50%; - cursor: pointer; -} - -.control-row input[type="range"]::-moz-range-thumb { - width: 14px; - height: 14px; - background: var(--accent); - border-radius: 50%; - cursor: pointer; - border: none; -} - -.control-row span { - min-width: 40px; - text-align: right; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.control-row select { - flex: 1; - padding: var(--sp-1) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); - cursor: pointer; -} - -.control-row select:focus { - outline: none; - border-color: var(--accent); -} - -/* Effects Panel */ -.effects-panel { - display: flex; - flex-direction: column; - height: 100%; -} - -.effect-add select { - padding: var(--sp-1) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); - cursor: pointer; -} - -.effects-list { - flex: 1; - overflow-y: auto; - padding: var(--sp-2); -} - -.effect-item { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - margin-bottom: var(--sp-2); - overflow: hidden; - transition: opacity var(--transition-normal); -} - -.effect-item.bypassed { - opacity: 0.5; -} - -.effect-item.bypassed .effect-params { - pointer-events: none; -} - -.effect-item.dragging { - opacity: 0.4; -} - -.effect-item.drop-target { - border-top: 2px solid var(--accent-color, #4fc3f7); -} - -.effect-drag-handle { - cursor: grab; - opacity: 0.4; - font-size: 12px; - user-select: none; - line-height: 1; -} - -.effect-drag-handle:hover { - opacity: 0.8; -} - -.effect-header { - display: flex; - align-items: center; - padding: var(--sp-2) var(--sp-3); - background: var(--bg-secondary); - gap: var(--sp-2); -} - -.effect-bypass-btn { - background: none; - border: none; - padding: var(--sp-1); - cursor: pointer; - color: var(--accent-color); - border-radius: var(--radius-md); - display: flex; - align-items: center; - justify-content: center; - transition: all var(--transition-normal); -} - -.effect-bypass-btn:hover { - background: var(--bg-tertiary); -} - -.effect-bypass-btn.bypassed { - color: var(--text-secondary); -} - -.effect-name { - font-weight: var(--font-medium); - text-transform: capitalize; - flex: 1; -} - -.effect-params { - padding: var(--sp-2) var(--sp-3); -} - -/* Effect Quality Section */ -.effect-quality-section { - margin-top: var(--sp-2); - border-top: 1px solid var(--border-color); - padding-top: 8px; -} - -.effect-quality-header { - display: flex; - align-items: center; - gap: var(--sp-1-5); - cursor: pointer; - padding: 4px 0; - user-select: none; -} - -.effect-quality-header:hover { - color: var(--accent-color); -} - -.effect-quality-toggle { - font-size: var(--font-xs); - width: 12px; -} - -.effect-quality-title { - font-size: var(--font-sm); - font-weight: var(--font-medium); - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); - flex: 1; -} - -.effect-quality-reset { - font-size: var(--font-xs); - padding: 2px 6px; - opacity: 0.7; -} - -.effect-quality-reset:hover { - opacity: 1; -} - -.effect-quality-params { - padding: 8px 0 0 18px; -} - -.effect-quality-warning { - font-size: var(--font-xs); - color: var(--warning-color, #f0ad4e); - margin-top: var(--sp-2); - opacity: 0.8; -} - -.btn-xs { - font-size: var(--font-xs); - padding: 2px 6px; - border-radius: var(--radius-sm); -} - -.effect-info { - margin: 0; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.transform-section { - padding: var(--sp-3); - border-top: 1px solid var(--border-color); - background: var(--bg-tertiary); -} - -.transform-section h4 { - margin: 0 0 12px 0; - font-size: var(--font-lg); - color: var(--text-secondary); -} - -/* Export Panel */ -.export-panel { - display: flex; - flex-direction: column; - height: 100%; -} - -.export-panel .panel-header { - padding: var(--sp-3); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.export-panel .panel-header h3 { - margin: 0; - font-size: var(--font-xl); -} - -.export-form { - padding: var(--sp-3); - display: flex; - flex-direction: column; - gap: var(--sp-3); -} - -.export-section { - display: flex; - flex-direction: column; - gap: var(--sp-2); -} - -.export-section-header { - font-size: var(--font-sm); - font-weight: var(--font-semibold); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; - padding-bottom: 4px; - border-bottom: 1px solid var(--border-color); - margin-bottom: var(--sp-1); -} - -.info-icon-wrapper { - position: relative; - margin-left: var(--sp-1); -} - -.info-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - font-size: var(--font-sm); - font-style: italic; - font-family: Georgia, serif; - color: var(--text-muted); - cursor: pointer; - border-radius: 50%; - transition: all var(--transition-slow) ease; -} - -.info-icon:hover { - color: var(--gray-100); - background: rgba(255, 255, 255, 0.08); - transform: scale(1.1); -} - -.info-tooltip { - position: absolute; - left: 50%; - top: calc(100% + 8px); - transform: translateX(-50%); - width: 200px; - padding: var(--sp-2) var(--sp-2-5); - background: var(--gray-300); - color: var(--text-primary); - font-size: var(--font-sm); - font-style: normal; - font-weight: normal; - line-height: 1.4; - border-radius: var(--radius-lg); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - z-index: 100; -} - -.info-tooltip::before { - content: ''; - position: absolute; - left: 50%; - top: -6px; - transform: translateX(-50%); - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid var(--gray-300); -} - -.export-panel .control-row { - display: flex; - align-items: center; - gap: var(--sp-2); -} - -.export-panel .control-row label { - display: flex; - align-items: center; - min-width: 80px; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.export-panel select, -.export-panel input[type="text"] { - flex: 1; - padding: var(--sp-1-5) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); -} - -.export-input-group { - flex: 1; - display: flex; - align-items: center; - min-width: 0; -} - -.export-input-group input { - flex: 1; - min-width: 60px; - border-radius: 4px 0 0 4px; - text-overflow: ellipsis; - overflow: hidden; -} - -.export-extension { - padding: var(--sp-1-5) var(--sp-2); - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-left: none; - border-radius: 0 4px 4px 0; - color: var(--text-secondary); - font-size: var(--font-md); -} - -.export-extension-select { - padding: 6px 20px 6px 8px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-left: none; - border-radius: 0 4px 4px 0; - color: var(--text-secondary); - font-size: var(--font-md); - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M3 4.5L6 8l3-3.5H3z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 4px center; - width: auto; - flex-shrink: 0; -} - -.export-extension-select:hover { - background-color: var(--bg-hover); - color: var(--text-primary); -} - -.export-extension-select:focus { - outline: none; - border-color: var(--accent-color); -} - -.export-summary { - padding: var(--sp-2-5); - background: var(--bg-tertiary); - border-radius: var(--radius-md); - font-size: var(--font-sm); - color: var(--text-secondary); - display: flex; - flex-direction: column; - gap: var(--sp-1); -} - -.export-error { - padding: var(--sp-2-5); - background: rgba(239, 68, 68, 0.1); - border: 1px solid var(--danger); - border-radius: var(--radius-md); - color: var(--danger); - font-size: var(--font-md); -} - -.export-start-btn { - width: 100%; - padding: var(--sp-2-5); - background: var(--accent); - color: var(--bg-primary); - border: none; - border-radius: var(--radius-md); - font-weight: var(--font-semibold); - cursor: pointer; -} - -.export-start-btn:hover { - background: var(--accent-hover); -} - -.export-start-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.export-progress-container { - padding: var(--sp-3); - display: flex; - flex-direction: column; - gap: var(--sp-3); -} - -.export-progress-bar { - height: 8px; - background: var(--bg-tertiary); - border-radius: var(--radius-md); - overflow: hidden; -} - -.export-progress-fill { - height: 100%; - background: var(--accent); - transition: width var(--transition-fast) ease-out; -} - -.export-progress-info { - display: flex; - justify-content: space-between; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.export-eta { - text-align: center; - font-size: var(--font-lg); - color: var(--text-primary); -} - -.export-cancel-btn { - width: 100%; - padding: var(--sp-2); - background: var(--bg-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - cursor: pointer; -} - -.export-cancel-btn:hover { - background: var(--bg-hover); -} - -/* Export Panel refresh */ -.export-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-3); - padding: var(--sp-3) var(--sp-3) 0; -} - -.export-mode-tabs { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding: 4px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 14px; -} - -.export-mode-tab { - padding: 8px 12px; - border: none; - border-radius: 10px; - background: transparent; - color: var(--text-secondary); - font-size: var(--font-md); - font-weight: var(--font-medium); - cursor: pointer; - transition: background var(--transition-fast) ease, color var(--transition-fast) ease; -} - -.export-mode-tab:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.06); - color: var(--text-primary); -} - -.export-mode-tab.is-active { - background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 72%, transparent) 0%, color-mix(in srgb, var(--accent-hover) 88%, transparent) 100%); - color: var(--bg-primary); -} - -.export-mode-tab:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.export-toolbar-cta { - width: auto; - min-width: 152px; -} - -.export-form { - padding: 0 var(--sp-3) var(--sp-3); - display: flex; - flex-direction: column; - gap: var(--sp-3); - overflow-y: auto; -} - -.export-summary-sticky { - position: sticky; - top: 0; - z-index: 8; - backdrop-filter: blur(14px); -} - -.export-summary-badges { - margin: 0 calc(var(--sp-3) * -1) 0; - padding: 10px 12px 12px; - border-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; - box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2); -} - -.export-summary-badges .export-pill-row { - gap: 6px; - flex: 1 1 auto; - min-width: 0; -} - -.export-summary-actions { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: stretch; - gap: 12px; -} - -.export-summary-cta { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: stretch; - min-width: 108px; - white-space: nowrap; - padding: 10px 18px; - border-radius: 16px; - border: 1px solid color-mix(in srgb, var(--success) 42%, transparent); - background: color-mix(in srgb, var(--success) 14%, rgba(255, 255, 255, 0.02)); - color: var(--success-light); - font-weight: var(--font-semibold); - letter-spacing: 0.01em; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); - transition: - transform var(--transition-fast) ease, - border-color var(--transition-fast) ease, - color var(--transition-fast) ease, - background var(--transition-fast) ease; -} - -.export-summary-cta:hover:not(:disabled) { - background: color-mix(in srgb, var(--success) 18%, rgba(255, 255, 255, 0.03)); - border-color: color-mix(in srgb, var(--success) 56%, transparent); - color: color-mix(in srgb, var(--success-light) 88%, #ffffff 12%); - transform: translateY(-1px); -} - -.export-summary-cta:disabled { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.08); - color: var(--text-muted); - box-shadow: none; -} - -.export-hero-card, -.export-section { - padding: 14px; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.025) 0%, rgba(255, 255, 255, 0.01) 100%), - var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 14px; - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); -} - -.export-hero-card { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 10px; -} - -.export-hero-main { - display: flex; - flex-direction: column; - gap: 6px; -} - -.export-hero-kicker { - font-size: 11px; - font-weight: var(--font-semibold); - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--text-muted); -} - -.export-hero-title-row { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 10px; -} - -.export-hero-title-row h3 { - margin: 0; - font-size: 20px; - line-height: 1.1; - color: var(--text-primary); -} - -.export-hero-main p { - margin: 0; - max-width: 54ch; - font-size: var(--font-md); - line-height: 1.5; - color: var(--text-secondary); -} - -.export-badge { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: 999px; - background: color-mix(in srgb, var(--accent) 18%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - color: var(--accent); - font-size: 11px; - font-weight: var(--font-semibold); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.export-hero-metrics { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: flex-start; - gap: 4px; - min-width: 120px; -} - -.export-hero-metric-label, -.export-hero-metric-subtle { - font-size: 11px; - color: var(--text-muted); -} - -.export-hero-metrics strong { - font-size: 24px; - line-height: 1; - color: var(--text-primary); -} - -.export-pill-row { - grid-column: 1 / -1; - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.export-pill { - display: inline-flex; - align-items: center; - padding: 6px 10px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.045); - border: 1px solid rgba(255, 255, 255, 0.08); - color: var(--text-secondary); - font-size: 12px; - font: inherit; - line-height: 1.2; - white-space: nowrap; - cursor: pointer; - appearance: none; - transition: - transform var(--transition-fast) ease, - border-color var(--transition-fast) ease, - color var(--transition-fast) ease, - background var(--transition-fast) ease; -} - -.export-pill:hover { - transform: translateY(-1px); - color: var(--text-primary); - border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); -} - -.export-pill:focus-visible { - outline: none; - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent); -} - -.export-pill-warning { - color: var(--warning-light); - border-color: color-mix(in srgb, var(--warning) 35%, transparent); - background: var(--warning-subtle); -} - -.export-hero-footnote { - grid-column: 1 / -1; - font-size: 12px; - color: var(--text-muted); -} - -.export-workflow-section, -.export-workflow-section .export-legacy-control { - display: none; -} - -.export-command-row { - display: flex; - flex-direction: column; - gap: 10px; -} - -.export-command-bar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - flex-wrap: wrap; -} - -.export-command-actions { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - width: 100%; -} - -.export-preset-picker { - display: inline-flex; - align-items: center; - flex: 1 1 220px; - min-width: 0; - max-width: 100%; -} - -.export-preset-picker select { - width: 100%; - min-width: 0; - max-width: 100%; - padding: 8px 34px 8px 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 999px; - background: var(--bg-primary); - color: var(--text-primary); - font: inherit; -} - -.export-channel-grid { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 12px; - align-items: start; -} - -.export-channel-card { - display: flex; - flex-direction: column; - gap: 12px; - padding: 12px; - background: rgba(255, 255, 255, 0.025); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 14px; - scroll-margin-top: 104px; -} - -.export-channel-card.is-disabled { - opacity: 0.82; -} - -.export-channel-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.export-channel-title { - display: flex; - flex-direction: column; - gap: 4px; -} - -.export-channel-title span { - font-size: 11px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.export-channel-title strong { - font-size: 18px; - line-height: 1.1; - color: var(--text-primary); -} - -.export-method-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; -} - -.export-method-card { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - min-height: 0; - padding: 10px 12px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 12px; - color: var(--text-secondary); - text-align: left; - cursor: pointer; - transition: - border-color var(--transition-fast) ease, - transform var(--transition-fast) ease, - box-shadow var(--transition-fast) ease, - color var(--transition-fast) ease; -} - -.export-method-card strong { - color: var(--text-primary); - font-size: 14px; -} - -.export-method-card span:last-child { - font-size: 12px; - line-height: 1.45; -} - -.export-method-card:hover { - transform: translateY(-1px); - border-color: color-mix(in srgb, var(--accent) 40%, var(--border-color)); - color: var(--text-primary); -} - -.export-method-card.is-active { - border-color: color-mix(in srgb, var(--accent) 70%, transparent); - background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 11%, transparent) 0%, rgba(255, 255, 255, 0.02) 100%), var(--bg-primary); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); -} - -.export-method-chip { - display: inline-flex; - align-items: center; - padding: 4px 8px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.05); - color: var(--text-muted); - font-size: 10px; - font-weight: var(--font-semibold); - letter-spacing: 0.06em; - text-transform: uppercase; -} - -.export-status-row { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 10px; -} - -.export-status-button { - padding: 6px 12px; - font-size: 11px; -} - -.export-status-ok { - color: var(--success); - font-size: 12px; - font-weight: var(--font-semibold); -} - -.export-error-inline { - margin-top: 4px; - font-size: 11px; -} - -.export-basics-section { - padding: 0; - background: transparent; - border: 0; - box-shadow: none; - gap: 14px; -} - -.export-basics-section > .export-section-header { - display: none; -} - -.export-quick-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 10px; -} - -.export-quick-grid-stack { - grid-template-columns: 1fr; -} - -.export-field-card { - display: flex; - flex-direction: column; - gap: 10px; - padding: 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 12px; - scroll-margin-top: 104px; -} - -.export-scroll-highlight { - animation: export-scroll-highlight 1.1s ease; -} - -@keyframes export-scroll-highlight { - 0%, - 100% { - border-color: rgba(255, 255, 255, 0.06); - box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); - } - - 30%, - 70% { - border-color: color-mix(in srgb, var(--accent) 58%, rgba(255, 255, 255, 0.06)); - box-shadow: - 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent), - 0 0 22px color-mix(in srgb, var(--accent) 16%, transparent); - } -} - -.export-subcard { - background: rgba(255, 255, 255, 0.02); -} - -.export-field-head { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 10px; -} - -.export-field-head span { - font-size: 12px; - color: var(--text-muted); -} - -.export-field-head strong { - font-size: 13px; - color: var(--text-primary); -} - -.export-chip-row, -.export-toggle-row { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.export-container-groups { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); - gap: 12px 18px; -} - -.export-container-group { - display: flex; - flex-direction: column; - gap: 8px; - min-width: 0; -} - -.export-container-group-label { - font-size: 10px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.1em; -} - -.export-container-group .export-chip-row { - display: grid; - grid-template-columns: repeat(2, max-content); - justify-content: start; - align-content: start; -} - -.export-chip, -.export-toggle { - padding: 8px 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 999px; - background: var(--bg-primary); - color: var(--text-secondary); - font-size: 12px; - font-weight: var(--font-medium); - cursor: pointer; - transition: - border-color var(--transition-fast) ease, - background var(--transition-fast) ease, - color var(--transition-fast) ease, - transform var(--transition-fast) ease; -} - -.export-chip:hover, -.export-toggle:hover { - transform: translateY(-1px); - color: var(--text-primary); - border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); -} - -.export-chip.is-active, -.export-toggle.is-active { - color: var(--accent); - background: color-mix(in srgb, var(--accent) 12%, transparent); - border-color: color-mix(in srgb, var(--accent) 30%, transparent); -} - -.export-toggle:disabled, -.export-chip:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -.export-chip-static { - cursor: default; -} - -.export-chip-static:hover { - transform: none; - color: var(--text-secondary); -} - -.export-inline-inputs { - display: flex; - align-items: center; - gap: 8px; -} - -.export-inline-inputs-single { - max-width: 140px; -} - -.export-inline-inputs span { - color: var(--text-muted); -} - -.export-slider-control { - display: flex; - flex-direction: column; - gap: 8px; - min-width: 0; -} - -.export-slider-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} - -.export-slider-head label { - font-size: 12px; - color: var(--text-muted); -} - -.export-slider-value { - min-width: 0; - font-size: 12px; - color: var(--text-primary); - text-align: right; -} - -.export-slider-input { - width: 100%; - min-width: 0; -} - -.export-preset-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 8px; -} - -.export-preset-card { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - padding: 12px; - background: var(--bg-primary); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 12px; - color: var(--text-secondary); - text-align: left; - cursor: pointer; - transition: - border-color var(--transition-fast) ease, - background var(--transition-fast) ease, - transform var(--transition-fast) ease; -} - -.export-preset-card strong { - color: var(--text-primary); - font-size: 13px; -} - -.export-preset-card span { - font-size: 11px; - color: var(--text-muted); -} - -.export-preset-card:hover { - transform: translateY(-1px); - border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); -} - -.export-preset-card.is-active { - border-color: color-mix(in srgb, var(--accent) 34%, transparent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} - -.export-audio-grid { - display: grid; - gap: 10px; -} - -.export-inline-note { - padding: 10px 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.035); - border: 1px solid rgba(255, 255, 255, 0.06); - color: var(--text-secondary); - font-size: 12px; - line-height: 1.45; -} - -.export-inline-note-warning { - background: var(--warning-subtle); - border-color: color-mix(in srgb, var(--warning) 35%, transparent); - color: var(--warning-light); -} - -.export-stats-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 10px; -} - -.export-stat-card { - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 12px; -} - -.export-stat-card span { - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} - -.export-stat-card strong { - font-size: 13px; - color: var(--text-primary); -} - -.export-stats-grid-compact { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.export-advanced-section { - display: none; - gap: 10px; -} - -.export-advanced-section .export-section-header { - color: var(--text-primary); - border-bottom-style: dashed; -} - -.export-panel .control-row { - display: flex; - align-items: center; - gap: 12px; -} - -.export-panel .control-row label { - display: flex; - align-items: center; - min-width: 96px; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.export-panel select, -.export-panel input[type="text"], -.export-panel input[type="number"] { - flex: 1; - min-width: 0; - padding: 10px 12px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 10px; - color: var(--text-primary); - font-size: var(--font-md); -} - -.export-input-group { - flex: 1; - display: flex; - align-items: center; - min-width: 0; -} - -.export-range-row { - display: flex; - align-items: center; - gap: 10px; - flex: 1; -} - -.export-range-row input[type="range"] { - flex: 1; -} - -.export-range-row span { - min-width: 78px; - text-align: right; -} - -.export-summary { - padding: 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 12px; -} - -.export-progress-container { - padding: var(--sp-3); - display: flex; - flex-direction: column; - gap: var(--sp-3); -} - -.export-progress-bar { - height: 10px; - background: rgba(255, 255, 255, 0.06); - border-radius: 999px; - overflow: hidden; -} - -.export-progress-fill { - height: 100%; - background: linear-gradient(90deg, var(--accent), var(--accent-hover)); - transition: width var(--transition-fast) ease-out; -} - -.export-progress-info { - display: flex; - justify-content: space-between; - gap: 12px; - font-size: var(--font-md); - color: var(--text-secondary); -} - -.export-eta { - text-align: left; - font-size: var(--font-md); - color: var(--text-primary); -} - -.export-cancel-btn { - width: 100%; - padding: var(--sp-2); - background: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border-color); - border-radius: 12px; - cursor: pointer; -} - -@media (max-width: 900px) { - .export-toolbar, - .export-hero-card { - grid-template-columns: 1fr; - flex-direction: column; - align-items: stretch; - } - - .export-toolbar-cta { - width: 100%; - } - - .export-summary-actions { - grid-template-columns: 1fr; - } - - .export-command-bar { - flex-direction: column; - align-items: stretch; - } - - .export-summary-cta { - width: 100%; - min-height: 44px; - } - - .export-method-grid, - .export-channel-grid, - .export-preset-grid, - .export-quick-grid, - .export-stats-grid { - grid-template-columns: 1fr; - } - - .export-hero-metrics { - align-items: flex-start; - } -} - -/* Clip Properties Panel */ -.clip-properties-panel { - display: flex; - flex-direction: column; - height: 100%; -} - -.clip-properties-panel .panel-header { - padding: var(--sp-3); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.clip-properties-panel .panel-header h3 { - margin: 0; - font-size: var(--font-lg); - font-weight: var(--font-medium); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.properties-content { - flex: 1; - overflow-y: auto; - padding: var(--sp-2); -} - -.properties-section { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - padding: var(--sp-2) var(--sp-2-5); - margin-bottom: var(--sp-1); -} - -.properties-section h4 { - margin: 0 0 6px 0; - font-size: var(--font-xs); - font-weight: var(--font-semibold); - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); -} - -.properties-section .control-row { - display: flex; - align-items: center; - gap: var(--sp-1-5); - margin-bottom: 3px; -} - -.properties-section .control-row:last-child { - margin-bottom: 0; -} - -.properties-section .control-row label { - min-width: 20px; - font-size: var(--font-sm); - color: var(--text-secondary); - white-space: nowrap; -} - -/* Fixed-width property label for uniform alignment */ -.properties-section .control-row .prop-label { - width: 52px; - min-width: 52px; - font-size: var(--font-sm); - color: var(--text-secondary); - white-space: nowrap; -} - -/* Multi-value inline row (X Y Z side by side) */ -.multi-value-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); - flex: 1; - column-gap: 12px; - row-gap: 3px; - min-width: 0; -} - -/* Labeled value cell: [label ... value] left/right aligned */ -.labeled-value { - min-width: 0; - display: grid; - grid-template-columns: 32px max-content; - align-items: center; - justify-content: start; - column-gap: 10px; - background: transparent; - border: 1px solid transparent; - border-radius: var(--radius-sm); - overflow: visible; - transition: background var(--transition-fast), border-color var(--transition-fast); - cursor: default; -} - -.labeled-value:hover { - border-color: transparent; -} - -.labeled-value-label { - font-size: var(--font-2xs); - color: var(--text-secondary); - padding: 3px 0; - width: 32px; - user-select: none; - opacity: 0.75; - text-align: left; -} - -.midi-parameter-label { - cursor: context-menu; -} - -.midi-parameter-label:hover { - color: var(--text-primary); -} - -.midi-parameter-menu { - min-width: 210px; -} - -.labeled-value .draggable-number-anchor { - flex: 0 0 auto; - min-width: 0; - justify-content: flex-end; -} - -/* Override draggable-number style when inside labeled-value */ -.labeled-value .draggable-number { - display: block; - width: max-content; - min-width: 0; - border: 1px solid transparent; - background: transparent; - border-radius: 2px; - padding: 3px 4px; - text-align: right; - color: #5b8abf; -} - -.labeled-value .draggable-number:hover { - background: rgba(45, 140, 235, 0.08); - border-color: rgba(45, 140, 235, 0.28); - color: #7aabe0; -} - -.labeled-value:active .draggable-number, -.labeled-value .draggable-number:active { - color: #9cc4f0; - background: rgba(45, 140, 235, 0.12); -} - -.labeled-value .draggable-number-input { - min-width: 0; - background: var(--bg-primary); - border-color: var(--accent); - color: #d7e7fb; -} - -/* AE-style rotation: "2x +30.0°" */ -.rotation-value-ae { - grid-template-columns: 32px max-content max-content; - column-gap: 6px; -} - -.rotation-value-ae .draggable-number:first-of-type { - opacity: 1; - font-size: 11px; - min-width: 28px; - flex-shrink: 0; -} - -.rotation-value-ae .draggable-number:first-of-type:hover { - opacity: 1; -} - -.rotation-row { - grid-template-columns: repeat(auto-fit, minmax(126px, 1fr)); - flex-wrap: wrap; -} - -.properties-section .control-row input[type="range"] { - flex: 1; -} - -.scene-nav-speed-control { - display: inline-grid; - grid-template-columns: minmax(76px, 112px) 34px; - align-items: center; - gap: 6px; - min-width: 120px; -} - -.scene-nav-speed-control input[type="range"] { - width: 100%; - min-width: 0; -} - -.scene-nav-speed-control span { - color: #7aabe0; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 11px; - font-variant-numeric: tabular-nums; - text-align: right; -} - -.properties-section .control-row .value { - min-width: 45px; - text-align: right; - font-size: var(--font-sm); - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--text-primary); -} - -.properties-section .control-row select { - flex: 1; - padding: var(--sp-1) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); -} - -.transform-tab-compact { - container-type: inline-size; -} - -.transform-tab-compact .properties-section { - border-radius: var(--radius-md); - margin-bottom: 4px; - padding: 6px 8px; -} - -.transform-tab-compact .properties-section .control-row { - min-width: 0; -} - -.transform-tab-compact .transform-option-row { - flex-wrap: wrap; - row-gap: 4px; -} - -.transform-tab-compact .transform-option-row .prop-label { - flex: 0 1 auto; - width: auto; - min-width: 50px; - max-width: 84px; - overflow: hidden; - text-overflow: ellipsis; -} - -.transform-tab-compact .scene-nav-row .btn-xs { - flex: 0 0 auto; -} - -.transform-tab-compact .scene-nav-icon-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - min-width: 24px; - height: 22px; - padding: 2px; -} - -.transform-tab-compact .scene-nav-action-icon { - width: 14px; - height: 14px; - fill: none; - stroke: currentColor; - stroke-width: 1.6; - stroke-linecap: round; - stroke-linejoin: round; -} - -.transform-tab-compact .transform-param-row { - display: grid; - grid-template-columns: 18px minmax(42px, max-content) minmax(0, 1fr); - align-items: center; - column-gap: 6px; - row-gap: 2px; -} - -.transform-tab-compact .transform-param-row > .keyframe-toggle { - grid-column: 1; -} - -.transform-tab-compact .transform-param-row > .prop-label { - grid-column: 2; - width: auto; - min-width: 0; - max-width: 72px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 11px; -} - -.transform-tab-compact .transform-param-row > .multi-value-row, -.transform-tab-compact .transform-param-row > .labeled-value, -.transform-tab-compact .transform-param-row > .draggable-number-anchor { - grid-column: 3; - min-width: 0; -} - -.transform-tab-compact .transform-param-row > .labeled-value, -.transform-tab-compact .transform-param-row > .draggable-number-anchor { - justify-self: start; -} - -.transform-tab-compact .multi-value-row { - grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); - column-gap: 8px; - row-gap: 2px; - align-items: center; -} - -.transform-tab-compact .rotation-row { - grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); -} - -.transform-tab-compact .labeled-value { - grid-template-columns: minmax(12px, max-content) max-content; - column-gap: 6px; -} - -.transform-tab-compact .labeled-value.with-keyframe-toggle { - grid-template-columns: 18px minmax(12px, max-content) max-content; - column-gap: 4px; -} - -.transform-tab-compact .rotation-value-ae { - grid-template-columns: minmax(28px, max-content) max-content max-content; - column-gap: 5px; -} - -.transform-tab-compact .rotation-value-ae.with-keyframe-toggle { - grid-template-columns: 18px minmax(28px, max-content) max-content max-content; -} - -.transform-tab-compact .labeled-value-label { - width: auto; - min-width: 12px; - padding: 2px 0; -} - -.transform-tab-compact .labeled-value .draggable-number { - padding: 2px 3px; -} - -@container (max-width: 240px) { - .transform-tab-compact .properties-section { - padding: 5px 6px; - } - - .transform-tab-compact .transform-option-row .prop-label { - min-width: 0; - } - - .transform-tab-compact .scene-nav-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - } - - .transform-tab-compact .scene-nav-speed-control { - grid-column: 1 / -1; - grid-template-columns: minmax(0, 1fr) 34px; - width: 100%; - } - - .transform-tab-compact .transform-param-row { - grid-template-columns: 18px minmax(0, 1fr); - grid-template-areas: - "key label" - "values values"; - } - - .transform-tab-compact .transform-param-row > .keyframe-toggle { - grid-area: key; - } - - .transform-tab-compact .transform-param-row > .prop-label { - grid-area: label; - max-width: none; - } - - .transform-tab-compact .transform-param-row > .multi-value-row, - .transform-tab-compact .transform-param-row > .labeled-value, - .transform-tab-compact .transform-param-row > .draggable-number-anchor { - grid-area: values; - box-sizing: border-box; - padding-left: 22px; - } - - .transform-tab-compact .multi-value-row { - grid-template-columns: 1fr; - row-gap: 3px; - } - - .transform-tab-compact .rotation-row { - grid-template-columns: 1fr; - } - - .transform-tab-compact .labeled-value { - grid-template-columns: 34px max-content; - column-gap: 5px; - } - - .transform-tab-compact .rotation-value-ae { - grid-template-columns: 34px 22px max-content; - column-gap: 4px; - } - - .transform-tab-compact .labeled-value .draggable-number { - text-align: left; - } -} - -.properties-actions { - padding: var(--sp-2); - text-align: center; -} - -.properties-actions .btn { - width: 100%; -} - -/* Unified Properties Panel with Tabs */ -.properties-panel { - display: flex; - flex-direction: column; - height: 100%; -} - -.properties-panel .panel-header { - padding: var(--sp-2) var(--sp-3); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.properties-panel .panel-header h3 { - margin: 0; - font-size: var(--font-lg); - font-weight: var(--font-medium); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.solid-color-bar { - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.properties-tabs { - display: flex; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - min-height: 32px; - flex-shrink: 0; -} - -.properties-tabs .tab-btn { - display: flex; - align-items: center; - gap: var(--sp-1-5); - padding: 0 16px; - height: 32px; - background: transparent; - border: none; - border-right: 1px solid var(--border-color); - color: var(--text-secondary); - font-size: var(--font-md); - font-weight: var(--font-medium); - cursor: pointer; - white-space: nowrap; - transition: background var(--transition-normal), color var(--transition-normal); -} - -.properties-tabs .tab-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.properties-tabs .tab-btn.active { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 2px solid var(--accent); - margin-bottom: -1px; -} - -.properties-tabs .tab-btn .badge { - background: var(--accent); - color: var(--text-inverse); - font-size: var(--font-xs); - font-weight: var(--font-semibold); - padding: 1px 5px; - border-radius: var(--radius-xl); - min-width: 16px; - text-align: center; -} - -.properties-panel .properties-content { - flex: 1; - overflow-y: auto; - padding: 0; -} - -.properties-tab-content { - padding: var(--sp-2); -} - -.lottie-tab { - display: flex; - flex-direction: column; - gap: 6px; - padding: 8px; -} - -.lottie-tab .properties-section { - margin-bottom: 0; - border-radius: var(--radius-md); - padding: 8px; -} - -.lottie-summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - min-width: 0; -} - -.lottie-summary-text { - min-width: 0; -} - -.lottie-title { - overflow: hidden; - color: var(--text-primary); - font-size: 12px; - font-weight: var(--font-semibold); - text-overflow: ellipsis; - white-space: nowrap; -} - -.lottie-meta { - margin-top: 2px; - color: var(--text-muted); - font-size: 11px; -} - -.lottie-loop-toggle { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 0 0 auto; - color: var(--text-secondary); - font-size: 11px; -} - -.lottie-loop-toggle input { - accent-color: var(--accent); -} - -.lottie-controls-section, -.lottie-state-section { - display: grid; - gap: 7px; -} - -.lottie-field-row { - display: grid; - grid-template-columns: 96px minmax(0, 1fr); - align-items: center; - gap: 8px; - min-width: 0; -} - -.lottie-field-label { - overflow: hidden; - color: var(--text-secondary); - font-size: 11px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.lottie-select, -.lottie-input { - width: 100%; - max-width: 360px; - min-width: 0; - height: 24px; - padding: 3px 8px; - border: 1px solid var(--border-color); - border-radius: 3px; - background: var(--bg-primary); - color: var(--text-primary); - font-size: 12px; - outline: none; -} - -.lottie-select { - cursor: pointer; -} - -.lottie-select:focus, -.lottie-input:focus { - border-color: color-mix(in srgb, var(--accent) 70%, var(--border-color)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 24%, transparent); -} - -.lottie-segmented-control, -.lottie-boolean-control { - display: grid; - grid-auto-columns: minmax(0, 1fr); - grid-auto-flow: column; - width: 100%; - max-width: 360px; - min-width: 0; - overflow: hidden; - border: 1px solid var(--border-color); - border-radius: 3px; - background: var(--bg-primary); -} - -.lottie-playback-mode { - max-width: 440px; - grid-auto-columns: minmax(72px, 1fr); -} - -.lottie-segmented-control button, -.lottie-boolean-control button { - height: 24px; - min-width: 0; - padding: 0 8px; - border: 0; - border-right: 1px solid var(--border-color); - background: transparent; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; -} - -.lottie-segmented-control button:last-child, -.lottie-boolean-control button:last-child { - border-right: 0; -} - -.lottie-segmented-control button:hover, -.lottie-boolean-control button:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.lottie-segmented-control button.active, -.lottie-boolean-control button.active { - background: color-mix(in srgb, var(--accent) 22%, var(--bg-primary)); - color: var(--text-primary); -} - -.lottie-input-list, -.lottie-cue-panel, -.lottie-cue-list { - display: grid; - gap: 6px; -} - -.lottie-subsection-title { - margin-top: 2px; - color: var(--text-secondary); - font-size: 10px; - font-weight: var(--font-semibold); - letter-spacing: 0.06em; - text-transform: uppercase; -} - -.lottie-input-row { - display: grid; - grid-template-columns: minmax(94px, 1fr) 18px minmax(80px, 112px); - align-items: center; - gap: 7px; - min-height: 28px; - min-width: 0; - padding: 4px 6px; - border: 1px solid transparent; - border-radius: 3px; - background: color-mix(in srgb, var(--bg-primary) 70%, transparent); -} - -.lottie-input-row.has-keyframes { - border-color: color-mix(in srgb, var(--warning) 36%, transparent); -} - -.lottie-input-label { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: baseline; - gap: 6px; - min-width: 0; -} - -.lottie-input-label span:first-child { - overflow: hidden; - color: var(--text-primary); - font-size: 12px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.lottie-input-label span:last-child { - color: var(--text-muted); - font-size: 10px; -} - -.lottie-input-row .draggable-number-anchor { - justify-self: end; -} - -.lottie-input-row .draggable-number { - min-width: 54px; - padding: 3px 6px; - border: 1px solid transparent; - border-radius: 3px; - color: #7aabe0; - text-align: right; -} - -.lottie-input-row .draggable-number:hover { - border-color: rgba(45, 140, 235, 0.28); - background: rgba(45, 140, 235, 0.08); - color: #9cc4f0; -} - -.lottie-input-static, -.lottie-input-note { - grid-column: 2 / 4; -} - -.lottie-input-note { - justify-self: start; - color: var(--text-muted); - font-size: 11px; -} - -.lottie-cue-add-btn { - justify-self: start; -} - -.lottie-cue-row { - display: grid; - grid-template-columns: 68px minmax(0, 1fr) 24px; - align-items: center; - gap: 6px; - max-width: 360px; -} - -.lottie-cue-row .lottie-select { - max-width: none; -} - -.lottie-time-input { - text-align: right; -} - -.lottie-cue-remove { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: 1px solid var(--border-color); - border-radius: 3px; - background: var(--bg-primary); - color: var(--text-secondary); - cursor: pointer; -} - -.lottie-cue-remove:hover { - border-color: color-mix(in srgb, var(--danger) 48%, var(--border-color)); - color: var(--danger-light); -} - -.lottie-resolution-row { - display: grid; - grid-template-columns: minmax(64px, 88px) auto minmax(64px, 88px) 38px auto; - align-items: center; - gap: 6px; - width: 100%; - max-width: 360px; - min-width: 0; -} - -.lottie-resolution-row span { - color: var(--text-muted); - font-size: 11px; -} - -.lottie-resolution-input { - text-align: right; -} - -.lottie-link-toggle { - height: 24px; - padding: 0 8px; - border: 1px solid var(--border-color); - border-radius: 3px; - background: var(--bg-primary); - color: var(--text-secondary); - font-size: 10px; - cursor: pointer; -} - -.lottie-link-toggle.active { - border-color: color-mix(in srgb, var(--accent) 42%, var(--border-color)); - background: color-mix(in srgb, var(--accent) 18%, var(--bg-primary)); - color: var(--text-primary); -} - -.lottie-state-control { - display: grid; - grid-template-columns: 18px minmax(0, 1fr); - align-items: center; - gap: 7px; - width: 100%; - max-width: 360px; - min-width: 0; -} - -.lottie-state-control .lottie-select { - max-width: none; -} - -.lottie-color-row { - display: grid; - grid-template-columns: 28px minmax(0, 1fr); - align-items: center; - gap: 7px; - width: 100%; - max-width: 360px; - min-width: 0; -} - -.lottie-color-swatch { - position: relative; - width: 26px; - height: 22px; - overflow: hidden; - border: 1px solid var(--border-color); - border-radius: 3px; - background: - linear-gradient(45deg, #555 25%, transparent 25%), - linear-gradient(-45deg, #555 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #555 75%), - linear-gradient(-45deg, transparent 75%, #555 75%); - background-color: #222; - background-position: 0 0, 0 6px, 6px -6px, -6px 0; - background-size: 12px 12px; -} - -.lottie-color-swatch input { - position: absolute; - inset: -3px; - width: calc(100% + 6px); - height: calc(100% + 6px); - padding: 0; - border: 0; - opacity: 0.85; - cursor: pointer; -} - -.slot-clip-tab { - display: flex; - flex-direction: column; - gap: var(--sp-2); -} - -.slot-clip-summary { - gap: var(--sp-1); -} - -.slot-clip-summary-row { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: var(--sp-2); -} - -.slot-clip-summary-title { - min-width: 0; -} - -.slot-clip-title { - margin-top: 2px; - font-size: var(--font-md); - font-weight: var(--font-semibold); - color: var(--text-primary); - word-break: break-word; -} - -.slot-clip-slot-badge { - padding: 5px 9px; - border-radius: 999px; - background: rgba(240, 196, 90, 0.14); - border: 1px solid rgba(240, 196, 90, 0.32); - color: #f0c45a; - font-size: var(--font-sm); - font-weight: var(--font-semibold); -} - -.slot-clip-summary-meta { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.slot-clip-summary-meta span { - padding: 3px 7px; - border-radius: var(--radius-sm); - background: var(--bg-primary); - border: 1px solid var(--border-color); - color: var(--text-secondary); - font-size: var(--font-xs); -} - -.slot-clip-summary-meta .slot-clip-status-active { - border-color: rgba(58, 206, 128, 0.38); - color: #56d98f; - background: rgba(58, 206, 128, 0.11); -} - -.slot-clip-transport { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: var(--sp-1); -} - -.slot-clip-transport-btn, -.slot-clip-end-btn, -.slot-clip-handle { - border: 1px solid var(--border-color); - background: var(--bg-primary); - color: var(--text-primary); -} - -.slot-clip-transport-btn { - min-height: 34px; - border-radius: var(--radius-md); - cursor: pointer; - font-size: var(--font-sm); - font-weight: var(--font-medium); -} - -.slot-clip-transport-btn.primary { - border-color: rgba(74, 158, 255, 0.5); - background: rgba(74, 158, 255, 0.16); -} - -.slot-clip-transport-btn:hover:not(:disabled), -.slot-clip-end-btn:hover { - background: var(--bg-hover); -} - -.slot-clip-transport-btn:disabled { - opacity: 0.45; - cursor: default; -} - -.slot-clip-section-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-2); -} - -.slot-clip-section-header span { - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; - color: var(--text-secondary); - font-size: var(--font-xs); -} - -.slot-clip-timeline-shell { - display: flex; - flex-direction: column; - border-radius: var(--radius-md); - border: 1px solid var(--border-color); - background: var(--bg-primary); - overflow: hidden; -} - -.slot-clip-ruler { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - align-items: center; - min-height: 22px; - padding: 0 8px; - color: var(--text-tertiary); - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; - font-size: 10px; - background: rgba(255, 255, 255, 0.03); - border-bottom: 1px solid var(--border-color); -} - -.slot-clip-ruler span:nth-child(2) { - text-align: center; -} - -.slot-clip-ruler span:nth-child(3) { - text-align: right; -} - -.slot-clip-track-stack { - position: relative; - min-height: 116px; - background: - repeating-linear-gradient( - 90deg, - rgba(255, 255, 255, 0.035) 0, - rgba(255, 255, 255, 0.035) 1px, - transparent 1px, - transparent 10% - ), - linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(0, 0, 0, 0.12)); - overflow: hidden; -} - -.slot-clip-track-row { - position: relative; - display: grid; - grid-template-columns: 30px minmax(0, 1fr); - min-height: 29px; - border-bottom: 1px solid rgba(255, 255, 255, 0.055); -} - -.slot-clip-track-row:last-of-type { - border-bottom: none; -} - -.slot-clip-track-label { - position: relative; - z-index: 4; - display: flex; - align-items: center; - justify-content: center; - border-right: 1px solid var(--border-color); - background: rgba(0, 0, 0, 0.32); - color: var(--text-secondary); - font-size: 10px; - font-weight: var(--font-semibold); -} - -.slot-clip-track-label-audio { - color: #57cf87; -} - -.slot-clip-track-label-video { - color: #63adff; -} - -.slot-clip-track-lane { - position: relative; - min-width: 0; - overflow: hidden; -} - -.slot-clip-clip { - position: absolute; - top: 5px; - bottom: 5px; - min-width: 3px; - border-radius: var(--radius-sm); - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(98, 157, 255, 0.64); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); - overflow: hidden; -} - -.slot-clip-clip span { - display: block; - padding: 2px 5px; - color: rgba(255, 255, 255, 0.86); - font-size: 10px; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.slot-clip-clip-audio { - background: rgba(56, 190, 108, 0.68); -} - -.slot-clip-clip-image, -.slot-clip-clip-lottie { - background: rgba(153, 122, 255, 0.66); -} - -.slot-clip-clip-text, -.slot-clip-clip-solid { - background: rgba(238, 156, 68, 0.66); -} - -.slot-clip-clip-model, -.slot-clip-clip-gaussian-avatar, -.slot-clip-clip-gaussian-splat, -.slot-clip-clip-splat-effector, -.slot-clip-clip-camera { - background: rgba(90, 202, 200, 0.64); -} - -.slot-clip-empty-timeline { - display: flex; - align-items: center; - justify-content: center; - min-height: 116px; - color: var(--text-tertiary); - font-size: var(--font-sm); -} - -.slot-clip-trim-mask { - position: absolute; - top: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.45); - pointer-events: none; - z-index: 2; -} - -.slot-clip-trim-mask-left { - left: 0; -} - -.slot-clip-trim-mask-right { - right: 0; -} - -.slot-clip-range-window { - position: absolute; - top: 0; - bottom: 0; - background: rgba(74, 158, 255, 0.08); - border-left: 1px solid rgba(240, 196, 90, 0.7); - border-right: 1px solid rgba(240, 196, 90, 0.7); - pointer-events: none; - z-index: 3; -} - -.slot-clip-range-progress { - position: absolute; - top: 0; - bottom: 0; - background: rgba(74, 158, 255, 0.12); - pointer-events: none; - z-index: 3; -} - -.slot-clip-playhead { - position: absolute; - top: 0; - bottom: 0; - width: 2px; - margin-left: -1px; - background: #ff6b57; - box-shadow: 0 0 0 1px rgba(255, 107, 87, 0.18); - pointer-events: none; - z-index: 5; -} - -.slot-clip-handle { - position: absolute; - top: 0; - bottom: 0; - width: 14px; - margin-left: -7px; - background: transparent; - border: none; - cursor: ew-resize; - z-index: 6; -} - -.slot-clip-handle::before { - content: ''; - position: absolute; - top: 6px; - bottom: 6px; - left: 5px; - width: 4px; - border-radius: var(--radius-sm); - background: #f0c45a; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.22); -} - -.slot-clip-handle.dragging { - background: rgba(240, 196, 90, 0.08); -} - -.slot-clip-timecodes { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(86px, 1fr)); - gap: var(--sp-1); -} - -.slot-clip-timecode { - display: flex; - flex-direction: column; - gap: 2px; - padding: 8px 10px; - border-radius: var(--radius-md); - background: var(--bg-primary); - border: 1px solid var(--border-color); -} - -.slot-clip-timecode span { - font-size: var(--font-xs); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.slot-clip-timecode strong { - font-size: var(--font-sm); - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; - color: var(--text-primary); -} - -.slot-clip-end-behavior { - display: flex; - flex-wrap: wrap; - gap: var(--sp-1); -} - -.slot-clip-end-btn { - flex: 1 1 110px; - min-height: 34px; - border-radius: var(--radius-md); - cursor: pointer; - font-size: var(--font-sm); -} - -.slot-clip-end-btn.active { - border-color: rgba(74, 158, 255, 0.55); - background: rgba(74, 158, 255, 0.16); - color: var(--text-primary); -} - -.properties-tab-content.effects-tab .effect-add-row { - display: flex; - gap: var(--sp-1-5); - align-items: center; - margin-bottom: var(--sp-2); -} - -.properties-tab-content.effects-tab .effect-add-row select { - flex: 1; - min-width: 0; - padding: var(--sp-1-5) var(--sp-2-5); - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); -} - -.effect-mode-toggle { - display: flex; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - overflow: hidden; - flex-shrink: 0; -} - -.effect-mode-btn { - display: flex; - align-items: center; - justify-content: center; - padding: var(--sp-1-5) var(--sp-2); - background: var(--bg-tertiary); - border: none; - color: var(--text-secondary); - cursor: pointer; - transition: background 0.15s, color 0.15s; -} - -.effect-mode-btn:not(:last-child) { - border-right: 1px solid var(--border-color); -} - -.effect-mode-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.effect-mode-btn.active { - background: var(--accent-color); - color: #fff; -} - -.effect-mode-label { - flex: 1; - font-size: var(--font-md); - color: var(--text-secondary); - font-weight: 500; -} - -.properties-tab-content.masks-tab .section-header-with-button { - margin-bottom: var(--sp-2); -} - -/* Transcript Tab (embedded in PropertiesPanel) */ -.properties-tab-content.transcript-tab { - display: flex; - flex-direction: column; - gap: var(--sp-2); -} - -.transcript-tab-actions { - display: flex; - gap: var(--sp-1-5); - margin-top: var(--sp-1-5); -} - -.transcript-progress-bar { - height: 4px; - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - overflow: hidden; -} - -.transcript-progress-fill { - height: 100%; - background: var(--accent); - transition: width 0.3s ease; -} - -.transcript-progress-text { - font-size: var(--font-sm); - color: var(--text-secondary); -} - -.transcript-search-input { - width: 100%; - padding: var(--sp-1-5) var(--sp-2-5); - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - color: var(--text-primary); - font-size: var(--font-md); -} - -.transcript-content-embedded { - flex: 1; - min-height: 100px; - max-height: 200px; - overflow-y: auto; - padding: var(--sp-2); - background: var(--bg-tertiary); - border-radius: var(--radius-md); -} - -.transcript-empty-state { - color: var(--text-secondary); - font-size: var(--font-md); - text-align: center; - padding: var(--sp-5); -} - -.transcript-words-flow { - display: flex; - flex-wrap: wrap; - gap: var(--sp-1); - line-height: 1.6; -} - -.transcript-word-inline { - cursor: pointer; - padding: 1px 2px; - border-radius: var(--radius-sm); - font-size: var(--font-md); - transition: background var(--transition-normal); -} - -.transcript-word-inline:hover { - background: var(--bg-hover); -} - -.transcript-word-inline.active { - background: var(--accent); - color: var(--bg-primary); -} - -.transcript-word-inline.highlighted { - background: rgba(255, 200, 0, 0.3); -} - -.transcript-status-bar { - font-size: var(--font-sm); - color: var(--text-secondary); - text-align: right; -} - -/* Text Tab (embedded in PropertiesPanel) */ -/* ========== TEXT TAB (Compact AE-style) ========== */ -.tt { - display: flex; - flex-direction: column; - gap: 2px; -} - -.tt-section { - padding: var(--sp-2) var(--sp-2-5); - border-bottom: 1px solid var(--border-color); -} - -.tt-section:last-child { - border-bottom: none; -} - -.tt-section-header { - font-size: var(--font-sm); - font-weight: var(--font-semibold); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: var(--sp-1-5); -} - -/* Textarea */ -.tt-textarea { - width: 100%; - min-height: 48px; - padding: var(--sp-1-5) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - color: var(--text-primary); - font-size: var(--font-md); - font-family: inherit; - resize: vertical; - line-height: 1.4; -} - -.tt-textarea:focus { - outline: none; - border-color: var(--accent); -} - -/* Full-width select */ -.tt-select-full { - width: 100%; - padding: var(--sp-1) var(--sp-2); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - color: var(--text-primary); - font-size: var(--font-sm); - cursor: pointer; - margin-bottom: var(--sp-1); -} - -.tt-select-full:focus { - outline: none; - border-color: var(--accent); -} - -/* Two-column row */ -.tt-row-2col { - display: flex; - gap: var(--sp-1); - margin-bottom: var(--sp-1); -} - -.tt-row-2col > * { - flex: 1; - min-width: 0; -} - -.tt-row-2col .tt-select-full { - margin-bottom: 0; -} - -/* Compact number input with icon */ -.tt-compact-num { - display: flex; - align-items: center; - gap: 2px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 3px; - padding: 2px 4px; - cursor: ew-resize; - height: 24px; -} - -.tt-compact-num:hover { - border-color: var(--accent); -} - -.tt-num-icon { - display: flex; - align-items: center; - color: var(--text-secondary); - flex-shrink: 0; - pointer-events: none; -} - -.tt-compact-num input[type="number"] { - flex: 1; - width: 100%; - min-width: 0; - background: transparent; - border: none; - color: var(--accent); - font-size: 11px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - padding: 0 2px; - -moz-appearance: textfield; -} - -.tt-compact-num input[type="number"]::-webkit-inner-spin-button, -.tt-compact-num input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.tt-compact-num input[type="number"]:focus { - outline: none; - color: var(--text-primary); -} - -.tt-num-unit { - font-size: 9px; - color: var(--text-secondary); - flex-shrink: 0; - pointer-events: none; -} - -/* Color row */ -.tt-color-row { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 4px; - height: 24px; -} - -.tt-color-row:last-child { - margin-bottom: 0; -} - -.tt-color-swatch { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid var(--border-color); - border-radius: 3px; - cursor: pointer; - flex-shrink: 0; - -webkit-appearance: none; - background: none; -} - -.tt-color-swatch::-webkit-color-swatch-wrapper { - padding: 1px; -} - -.tt-color-swatch::-webkit-color-swatch { - border: none; - border-radius: 2px; -} - -.tt-color-swatch:disabled { - opacity: 0.4; -} - -.tt-color-label { - font-size: 11px; - color: var(--text-secondary); - min-width: 36px; -} - -.tt-color-hex { - flex: 1; - min-width: 0; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 3px; - color: var(--text-primary); - font-size: 10px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - padding: 2px 6px; - height: 22px; -} - -.tt-color-hex:focus { - outline: none; - border-color: var(--accent); -} - -/* Toggle checkbox */ -.tt-toggle { - display: flex; - align-items: center; - cursor: pointer; - flex-shrink: 0; -} - -.tt-toggle input { - display: none; -} - -.tt-toggle-box { - width: 14px; - height: 14px; - border: 1px solid var(--border-color); - border-radius: 2px; - background: var(--bg-primary); - display: flex; - align-items: center; - justify-content: center; - transition: all 0.15s; -} - -.tt-toggle input:checked + .tt-toggle-box { - background: var(--accent); - border-color: var(--accent); -} - -.tt-toggle input:checked + .tt-toggle-box::after { - content: ''; - width: 8px; - height: 5px; - border-left: 1.5px solid var(--text-inverse); - border-bottom: 1.5px solid var(--text-inverse); - transform: rotate(-45deg) translateY(-1px); -} - -/* Toggle in section header */ -.tt-toggle-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - font-size: 11px; - font-weight: 600; - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.tt-toggle-header input { - width: 12px; - height: 12px; - cursor: pointer; - accent-color: var(--accent); -} - -/* Alignment buttons row */ -.tt-align-row { - display: flex; - gap: 1px; - align-items: center; -} - -.tt-align-row button { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 24px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - color: var(--text-secondary); - cursor: pointer; - transition: all 0.1s; - padding: 0; -} - -.tt-align-row button:first-child { - border-radius: 3px 0 0 3px; -} - -.tt-align-row button:nth-child(3) { - border-radius: 0 3px 3px 0; -} - -.tt-align-row button:nth-child(5) { - border-radius: 3px 0 0 3px; -} - -.tt-align-row button:last-child { - border-radius: 0 3px 3px 0; -} - -.tt-align-row button:not(:first-child) { - margin-left: -1px; -} - -.tt-align-row button:hover { - background: var(--bg-tertiary); - color: var(--text-primary); - z-index: 1; -} - -.tt-align-row button.active { - background: var(--accent); - color: var(--text-inverse); - border-color: var(--accent); - z-index: 2; -} - -.tt-align-sep { - width: 8px; - flex-shrink: 0; -} - -/* Draggable number label - click and drag to adjust value */ -.draggable-label { - cursor: ew-resize !important; - user-select: none; - padding: 2px 4px; - border-radius: 3px; - transition: background-color 0.15s ease; -} - -.draggable-label:hover { - background: var(--bg-tertiary); - color: var(--accent-color); -} - -.draggable-label.dragging { - background: var(--accent-color); - color: var(--text-on-accent); -} - -/* Analysis Tab (embedded in PropertiesPanel) */ -.properties-tab-content.analysis-tab { - display: flex; - flex-direction: column; - gap: 8px; -} - -.analysis-tab-actions { - display: flex; - gap: 6px; -} - -/* Coverage bar for analysis/transcript tabs */ -.coverage-bar { - display: flex; - align-items: center; - gap: 6px; -} - -.coverage-bar-bg { - flex: 1; - height: 4px; - background: rgba(255, 255, 255, 0.08); - border-radius: 2px; - overflow: hidden; -} - -.coverage-bar-fill { - height: 100%; - border-radius: 2px; - transition: width 0.3s ease; -} - -.coverage-bar-fill.analysis-fill { - background: #e88a1a; -} - -.coverage-bar-fill.transcript-fill { - background: var(--accent); -} - -.coverage-bar-text { - font-size: 10px; - color: var(--text-muted); - white-space: nowrap; -} - -/* Accent button for Continue actions */ -.btn-accent { - background: var(--accent) !important; - color: #fff !important; - border-color: var(--accent) !important; -} - -.btn-accent:hover { - filter: brightness(1.15); -} - -.analysis-progress-bar { - height: 4px; - background: var(--bg-tertiary); - border-radius: 2px; - overflow: hidden; -} - -.analysis-progress-fill { - height: 100%; - background: var(--accent); - transition: width 0.3s ease; -} - -.analysis-progress-text { - font-size: 11px; - color: var(--text-secondary); -} - -.analysis-realtime-grid { - display: flex; - flex-direction: column; - gap: 6px; -} - -.analysis-metric { - display: flex; - align-items: center; - gap: 8px; -} - -.analysis-metric .metric-label { - width: 60px; - font-size: 11px; - color: var(--text-secondary); -} - -.analysis-metric .metric-bar { - flex: 1; - height: 8px; - background: var(--bg-tertiary); - border-radius: 4px; - overflow: hidden; -} - -.analysis-metric .metric-fill { - height: 100%; - transition: width 0.15s ease; -} - -.analysis-metric .metric-fill.focus { - background: var(--success-light); -} - -.analysis-metric .metric-fill.motion { - background: #60a5fa; -} - -.analysis-metric .metric-value { - width: 40px; - font-size: 11px; - text-align: right; -} - -.analysis-stats-grid { - display: flex; - flex-direction: column; - gap: 4px; -} - -.analysis-stats-grid .stat-row { - display: flex; - justify-content: space-between; - font-size: 11px; -} - -.analysis-stats-grid .stat-row span:first-child { - color: var(--text-secondary); -} - -.analysis-empty-state { - color: var(--text-secondary); - font-size: 12px; - text-align: center; - padding: 20px; -} - -/* Clip type badge in header */ -.properties-panel .panel-header { - display: flex; - align-items: center; - gap: 8px; -} - -.clip-type-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 4px; - background: var(--bg-hover); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Volume Tab (Audio) */ -.volume-tab .section-header-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; -} - -.volume-tab .section-header-row h4 { - margin: 0; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); -} - -/* EQ Section */ -.eq-section { - padding-bottom: 16px; -} - -.eq-bands { - display: flex; - gap: 4px; - margin-bottom: 8px; -} - -.eq-band { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; -} - -.eq-band-kf { - height: 20px; - display: flex; - align-items: center; -} - -.eq-band-value { - font-size: 9px; - font-family: 'SF Mono', monospace; - color: var(--text-secondary); - text-align: center; - min-width: 28px; -} - -.eq-slider { - writing-mode: vertical-lr; - direction: rtl; - height: 100px; - width: 18px; - margin: 0; - cursor: pointer; -} - -.eq-band-label { - font-size: 9px; - color: var(--text-secondary); - text-align: center; -} - -.eq-scale { - display: flex; - justify-content: space-between; - font-size: 9px; - color: var(--text-secondary); - padding: 0 4px; -} - -/* Blendshapes Tab */ -.blendshapes-tab .section-header-row { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.blendshapes-tab .section-header-row h4 { - margin: 0; - font-size: 11px; - font-weight: 600; - color: var(--text-primary); - flex: 1; -} - -.blendshape-presets { - display: flex; - flex-wrap: wrap; - gap: 4px; -} - -.blendshape-presets .btn { - font-size: 10px; - padding: 2px 8px; -} - -.blendshape-group-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 0; -} - -.blendshape-group-header h4 { - flex: 1; -} - -.blendshape-collapse-arrow { - font-size: 8px; - color: var(--text-secondary); - width: 10px; - text-align: center; -} - -.blendshape-sliders { - display: flex; - flex-direction: column; - gap: 1px; -} - -.blendshape-row { - display: flex; - align-items: center; - gap: 6px; - padding: 1px 0; -} - -.blendshape-label { - font-size: 10px; - min-width: 90px !important; - max-width: 90px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.blendshape-slider { - flex: 1; - height: 3px; - -webkit-appearance: none; - appearance: none; - background: var(--bg-tertiary); - border-radius: 2px; - cursor: pointer; -} - -.blendshape-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; -} - -.blendshape-slider::-moz-range-thumb { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; - border: none; -} - -.blendshape-row .draggable-number { - min-width: 36px; - text-align: right; - font-size: 10px; -} - -/* Buttons */ -.btn { - padding: 4px 10px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 2px; - color: var(--text-primary); - font-size: 11px; - cursor: pointer; - transition: all 0.2s; -} - -.btn:hover:not(:disabled) { - background: var(--bg-hover); - border-color: var(--border-strong); -} - -.btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btn-active { - background: var(--accent); - border-color: var(--accent); - color: var(--text-on-accent); -} - -.btn-active:hover:not(:disabled) { - background: var(--accent-hover); -} - -.btn-export { - background: linear-gradient(135deg, var(--accent) 0%, #2070C0 100%); - border-color: var(--accent); - color: var(--text-on-accent); - font-weight: var(--font-semibold); -} - -.btn-export:hover:not(:disabled) { - background: linear-gradient(135deg, var(--accent-hover) 0%, var(--accent) 100%); - border-color: var(--accent-hover); -} - -.btn-sm { - padding: 3px 6px; - font-size: 11px; -} - -.btn-danger { - color: var(--danger); -} - -.btn-danger:hover:not(:disabled) { - background: var(--danger); - border-color: var(--danger); - color: var(--text-on-accent); -} - -.btn-reload-all { - background: var(--warning); - border-color: var(--warning); - color: var(--text-inverse); - font-weight: var(--font-medium); -} - -.btn-reload-all:hover { - background: #cc8800; - border-color: #cc8800; -} - -/* MIDI */ -.midi-unsupported { - font-size: 12px; - color: var(--text-secondary); -} - -/* Checkboxes */ -input[type="checkbox"] { - width: 16px; - height: 16px; - margin-right: 8px; - cursor: pointer; -} - -/* Scrollbar */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); -} - -/* Responsive */ -@media (max-width: 1200px) { - .right-column { - width: 320px; - } -} - -@media (max-width: 900px) { - .main-content { - flex-direction: column; - } - - .slots-area { - flex: 1; - min-height: 300px; - } - - .right-column { - width: 100%; - border-left: none; - border-top: 1px solid var(--border-color); - max-height: 250px; - } - - .floating-preview { - width: 280px !important; - left: 8px !important; - top: 60px !important; - } -} - -/* Slot Groups - Connected Rectangle Visual */ - -/* Group colors - 6 different colors that cycle */ -.slot-item.group-0 { --group-color: var(--group-0); } -.slot-item.group-1 { --group-color: var(--group-1); } -.slot-item.group-2 { --group-color: var(--group-2); } -.slot-item.group-3 { --group-color: var(--group-3); } -.slot-item.group-4 { --group-color: var(--group-4); } -.slot-item.group-5 { --group-color: var(--group-5); } - -/* Base group styling - colored border */ -.slot-item.in-group { - border: 3px solid var(--group-color); - border-radius: 6px; - margin: 0; -} - -/* Left edge of group - has border on all sides except right */ -.slot-item.in-group.group-left { - border-right: none; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - margin-right: -3px; /* Overlap with next slot */ - z-index: 1; -} - -/* Right edge of group - has border on all sides except left */ -.slot-item.in-group.group-right { - border-left: none; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -/* Middle of group (3+ slots) - no left/right borders */ -.slot-item.in-group.group-middle { - border-left: none; - border-right: none; - border-radius: 0; - margin-left: -3px; - margin-right: -3px; -} - -/* Group highlight on hover - glow effect */ -.slot-item.group-highlight { - box-shadow: 0 0 20px var(--group-color), 0 0 40px var(--group-color); - z-index: 2; -} - -.slot-item.group-highlight.group-left { - box-shadow: -5px 0 20px var(--group-color), 0 -5px 20px var(--group-color), 0 5px 20px var(--group-color); -} - -.slot-item.group-highlight.group-right { - box-shadow: 5px 0 20px var(--group-color), 0 -5px 20px var(--group-color), 0 5px 20px var(--group-color); -} - -/* Half-slot drop zone indicators */ -.drop-zone-indicator { - position: absolute; - top: 0; - bottom: 0; - width: 50%; - pointer-events: none; - z-index: 5; - transition: all 0.15s; -} - -.drop-zone-indicator.left { - left: 0; - border-right: 2px dashed rgba(255, 255, 255, 0.3); -} - -.drop-zone-indicator.right { - right: 0; - border-left: 2px dashed rgba(255, 255, 255, 0.3); -} - -.drop-zone-indicator.active { - background: rgba(45, 140, 235, 0.2); -} - -.drop-zone-indicator.left.active { - border-right-color: var(--accent); -} - -.drop-zone-indicator.right.active { - border-left-color: var(--accent); -} - -/* Copy mode indicator (Ctrl+drag) */ -.slot-item.copy-mode { - border-color: var(--success); - background: rgba(68, 255, 68, 0.1); -} - -.slot-item.copy-mode::after { - content: '+'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 32px; - font-weight: bold; - color: var(--success); - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); - z-index: 10; - pointer-events: none; -} - -/* Drop zone left/right visual feedback */ -.slot-item.drop-zone-left { - border-left-color: var(--accent); - border-left-width: 4px; -} - -.slot-item.drop-zone-right { - border-right-color: var(--accent); - border-right-width: 4px; -} - -/* ========== VIEW TABS ========== */ -.view-tabs { - display: flex; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - padding: 0 8px; -} - -.tab-btn { - padding: 10px 20px; - background: transparent; - border: none; - border-bottom: 2px solid transparent; - color: var(--text-secondary); - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; -} - -.tab-btn:hover { - color: var(--text-primary); - background: var(--bg-hover); -} - -.tab-btn.active { - color: var(--accent); - border-bottom-color: var(--accent); -} - -/* ========== TIMELINE ========== */ -.timeline-container { - display: flex; - flex-direction: column; - height: 100%; - background: var(--bg-primary); -} - -.timeline-container.timeline-empty { - justify-content: center; - align-items: center; -} - -.timeline-empty-message { - text-align: center; - color: var(--text-secondary); -} - -.timeline-empty-message p { - margin: 0; -} - -.timeline-empty-message p:first-child { - font-size: 14px; - color: var(--text-primary); - margin-bottom: 8px; -} - -.timeline-empty-message .hint { - font-size: 11px; -} - -.toolbar-slide-wrapper { - flex-shrink: 0; -} - -.slot-grid-toolbar { - display: flex; - align-items: center; - gap: 8px; - padding: 3px 10px; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - min-height: 30px; -} - -.slot-grid-toolbar-back { - display: flex; - align-items: center; - gap: 5px; -} - -.slot-grid-toolbar-title { - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - letter-spacing: 0.5px; - text-transform: uppercase; -} - -.timeline-toolbar { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 10px; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; - min-height: 30px; -} - -.timeline-controls { - display: flex; - gap: 4px; -} - -.timeline-time { - font-family: monospace; - font-size: 12px; - color: var(--text-primary); - background: var(--bg-primary); - padding: 3px 6px; - border-radius: 2px; - min-width: 140px; - text-align: center; -} - -.duration-display { - cursor: pointer; - padding: 1px 4px; - border-radius: 2px; - transition: background 0.15s; -} - -.duration-display:hover { - background: var(--accent-primary); - color: var(--text-on-accent); -} - -.duration-input { - font-family: monospace; - font-size: 12px; - width: 70px; - padding: 1px 4px; - border: 1px solid var(--accent-primary); - border-radius: 2px; - background: var(--bg-secondary); - color: var(--text-primary); - text-align: center; - outline: none; -} - -.timeline-zoom { - display: flex; - align-items: center; - gap: 8px; - font-size: 12px; - color: var(--text-secondary); -} - -.timeline-zoom .btn-icon { - display: flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - padding: 0; - border-radius: 2px; - color: var(--text-secondary); - opacity: 0.6; -} - -.timeline-zoom .btn-icon:hover { - opacity: 1; - background: var(--bg-hover); -} - -.timeline-zoom .btn-icon.btn-active { - color: var(--accent); - opacity: 1; - background: var(--accent-dim); -} - -.timeline-inout-controls { - display: flex; - align-items: center; - gap: 4px; -} - -.timeline-inout-controls .btn-sm { - min-width: 24px; - padding: 4px 8px; - font-weight: bold; -} - -.timeline-inout-controls .btn-active { - background: var(--warning); - color: var(--text-on-accent); -} - -.timeline-tracks-controls { - margin-left: auto; - display: flex; - gap: 8px; -} - -.timeline-body { - display: flex; - flex-direction: row; - flex: 1; - overflow: hidden; - position: relative; -} - -.timeline-body-content { - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - position: relative; -} - -/* Sticky header row - spans full width */ -.timeline-header-row { - display: flex; - position: sticky; - top: 0; - z-index: 20; - background: var(--bg-tertiary); - flex-shrink: 0; -} - -.timeline-header-row .ruler-header { - width: 150px; - flex-shrink: 0; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - color: var(--text-secondary); - background: var(--bg-tertiary); - border-right: 1px solid var(--border-color); - border-bottom: 1px solid var(--border-color); -} - -.time-ruler-wrapper { - flex: 1; - overflow: hidden; - height: 30px; - border-bottom: 1px solid var(--border-color); - transition: none; -} - -.time-ruler-wrapper.comp-switching { - opacity: 1; -} - -.timeline-scroll-wrapper { - flex: 1; - overflow: hidden; - position: relative; -} - -/* Composition switch: track rows stay visible while their labels/heights update. */ -.phase-exiting > .track-header, -.phase-exiting > .track-lane, -.phase-entering > .track-header, -.phase-entering > .track-lane { - animation: none; - transition-duration: var(--composition-switch-layout-duration, 475ms), 160ms, 160ms; -} - -/* Content row - track headers and lanes side by side */ -.timeline-content-row { - display: flex; - flex: 1; - min-height: 100%; - align-items: stretch; -} - -/* Track headers */ -.track-headers { - width: 150px; - flex-shrink: 0; - background: var(--bg-secondary); - border-right: 1px solid var(--border-color); -} - -.ruler-header { - height: 30px; - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - color: var(--text-secondary); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); -} - -.track-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0; - border-bottom: 1px solid var(--border-color); - background: var(--bg-tertiary); - position: relative; - overflow: hidden; - transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; -} - -.track-header.video { - background: linear-gradient(90deg, rgba(61, 90, 128, 0.15), transparent); -} - -.track-header.audio { - background: linear-gradient(90deg, rgba(45, 107, 74, 0.15), transparent); -} - -/* Green separator line between video and audio track sections */ -.track-header.video + .track-header.audio, -.track-lane.video + .track-lane.audio { - border-top: 2px solid var(--success); -} - -/* Preview header shown when dragging media to create new track */ -.track-header-preview { - display: flex; - align-items: center; - justify-content: center; - border: 2px dashed rgba(255, 255, 255, 0.3); - border-radius: 2px; - margin: 4px 4px 0 4px; - background: rgba(255, 255, 255, 0.05); - transition: all 0.15s ease; -} - -.track-header-preview.video { - border-color: rgba(37, 99, 235, 0.4); -} - -.track-header-preview.video.active { - border-color: rgba(37, 99, 235, 0.8); - background: rgba(37, 99, 235, 0.15); -} - -.track-header-preview.audio { - border-color: rgba(22, 163, 74, 0.4); -} - -.track-header-preview.audio.active { - border-color: rgba(22, 163, 74, 0.8); - background: rgba(22, 163, 74, 0.15); -} - -.track-header-preview-label { - font-size: 11px; - color: rgba(255, 255, 255, 0.5); - font-weight: 500; -} - -.track-header-preview.active .track-header-preview-label { - color: rgba(255, 255, 255, 0.8); -} - -.track-header.dimmed, -.track-lane.dimmed { - opacity: 0.4; -} - -.track-name { - font-size: 11px; - font-weight: 500; - color: var(--text-primary); - cursor: text; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 62px; -} - -.track-layer-id { - flex-shrink: 0; - font-size: 10px; - color: var(--text-secondary); - line-height: 1; -} - -.track-name-input { - font-size: 12px; - font-weight: 500; - color: var(--text-primary); - background: var(--bg-primary); - border: 1px solid var(--accent-timeline); - border-radius: 2px; - padding: 0 4px; - width: 80px; - outline: none; -} - -.track-controls { - display: flex; - flex-direction: row; - align-items: center; - gap: 2px; - margin-left: auto; - position: absolute; - right: 4px; - top: 50%; - transform: translateY(-50%); -} - -/* Track pick whip for layer parenting */ -.track-pick-whip { - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - cursor: grab; - opacity: 0.5; - transition: opacity 0.15s, transform 0.15s; - color: var(--expr-color); -} - -.track-pick-whip:hover { - opacity: 1; - transform: scale(1.1); -} - -.track-pick-whip.dragging { - cursor: grabbing; - opacity: 1; - transform: scale(1.2); -} - -.track-pick-whip.has-parent { - opacity: 1; - color: var(--expr-color); -} - -.track-pick-whip.has-children { - opacity: 0.8; - color: var(--expr-connected); -} - -.track-pick-whip.has-parent.has-children { - opacity: 1; - color: var(--expr-color); -} - -.track-pick-whip-icon { - width: 14px; - height: 14px; -} - -.btn-icon { - background: none; - border: none; - font-size: 10px; - cursor: pointer; - opacity: 0.7; - padding: 1px 3px; - line-height: 1; -} - -.btn-icon:hover { - opacity: 1; -} - -.btn-icon.muted, -.btn-icon.hidden { - opacity: 0.3; -} - -.btn-icon.solo-active { - color: var(--warning); - opacity: 1; - font-weight: bold; -} - -/* Timeline tracks area */ -.timeline-tracks { - flex: 1; - position: relative; - overflow-x: hidden; /* Hidden - using TimelineNavigator instead */ - overflow-y: visible; -} - -.track-lanes-scroll { - position: relative; -} - -.time-ruler-container { - position: relative; - height: 30px; - display: flex; - overflow: hidden; -} - -.time-ruler { - height: 30px; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - position: relative; - cursor: pointer; - flex: 1; -} - -.time-ruler:hover { - background: var(--bg-hover); -} - -/* Ruler Marker Button (M) */ -.ruler-marker-btn { - position: absolute; - right: 6px; - top: 50%; - transform: translateY(-50%); - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 2px; - color: var(--text-secondary); - font-size: 10px; - font-weight: bold; - cursor: grab; - transition: all 0.15s ease; - z-index: 10; -} - -.ruler-marker-btn:hover { - background: var(--accent-timeline); - border-color: var(--accent-timeline); - color: var(--text-on-accent); - box-shadow: 0 0 8px var(--accent-timeline); -} - -.ruler-marker-btn.dragging { - background: var(--accent-timeline); - border-color: var(--accent-timeline); - color: var(--text-on-accent); - cursor: grabbing; - transform: translateY(-50%) scale(1.15); - box-shadow: 0 0 12px var(--accent-timeline); -} - -.time-marker { - position: absolute; - top: 0; - height: 100%; - border-left: 1px solid var(--border-color); -} - -.time-marker.main { - border-left-color: var(--border-strong); -} - -.time-marker.sub { - border-left-color: var(--bg-active); - height: 50%; - top: 50%; -} - -.time-label { - position: absolute; - top: 4px; - left: 4px; - font-size: 10px; - color: var(--text-secondary); - white-space: nowrap; -} - -.timeline-ruler-cache-indicator { - position: absolute; - bottom: 1px; - height: 3px; - background: var(--amber-300); - opacity: 0.82; - cursor: pointer; - z-index: 2; -} - -.timeline-ruler-cache-indicator.cache { - box-shadow: 0 0 5px rgba(251, 191, 36, 0.32); -} - -.timeline-ruler-cache-indicator.proxy { - box-shadow: 0 0 5px rgba(251, 191, 36, 0.32); -} - -/* Track lanes */ -.track-lane { - position: relative; - border-bottom: 1px solid var(--border-color); - background: var(--bg-secondary); - min-width: 100%; - overflow: hidden; - transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; -} - -.track-lane.video { - background: repeating-linear-gradient( - 90deg, - var(--bg-secondary), - var(--bg-secondary) calc(var(--grid-size, 50px) - 1px), - var(--timeline-grid-video) calc(var(--grid-size, 50px) - 1px), - var(--timeline-grid-video) var(--grid-size, 50px) - ); -} - -.track-lane.audio { - background: repeating-linear-gradient( - 90deg, - var(--bg-secondary), - var(--bg-secondary) calc(var(--grid-size, 50px) - 1px), - var(--timeline-grid-audio) calc(var(--grid-size, 50px) - 1px), - var(--timeline-grid-audio) var(--grid-size, 50px) - ); -} - -.composition-exit-clips-overlay { - position: absolute; - top: 0; - left: 0; - min-width: inherit; - width: 100%; - pointer-events: none; - z-index: 12; -} - -.composition-exit-track-row { - position: relative; - width: 100%; - overflow: visible; -} - -/* Timeline clips */ -.timeline-clip { - position: absolute; - top: 4px; - bottom: 4px; - border-radius: 2px; - cursor: grab; - overflow: hidden; - transition: box-shadow 0.15s; - /* Fallback background in case type class is missing */ - background: #3a4050; - border: 1px solid #4a5060; -} - -.timeline-clip.video { - background: #3d5a80; - border: 1px solid #4a6a90; -} - -.timeline-clip.audio { - background: #2d6b4a; - border: 1px solid #3a7a58; -} - -.timeline-clip.text { - background: #5c3d7a; - border: 1px solid #6d4e8a; -} - -.timeline-clip.text .clip-content { - background: rgba(92, 61, 122, 0.9); -} - -.timeline-clip.math-scene { - background: #1f6f68; - border: 1px solid #31a89b; -} - -.timeline-clip.math-scene .clip-content { - background: rgba(18, 79, 75, 0.9); -} - -.timeline-clip.camera, -.timeline-clip.gaussian-splat, -.timeline-clip.model { - background: linear-gradient(180deg, #444b53, #31363d); - border: 1px solid #5f6771; -} - -.timeline-clip.camera .clip-content, -.timeline-clip.gaussian-splat .clip-content, -.timeline-clip.model .clip-content { - background: linear-gradient(to bottom, rgba(8, 10, 12, 0.72) 0%, rgba(8, 10, 12, 0.28) 52%, transparent 100%); -} - -.clip-text-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; - font-size: 10px; - font-weight: bold; - margin-right: 4px; - flex-shrink: 0; -} - -.timeline-clip.solid { - border: 1px solid rgba(255, 255, 255, 0.25); -} - -.timeline-clip.solid .clip-content { - background: rgba(0, 0, 0, 0.3); -} - -.timeline-clip.solid .clip-name, -.timeline-clip.solid .clip-duration { - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); -} - -.timeline-clip.loading.solid { - opacity: 0.5; -} - -.timeline-clip.loading.text { - background: rgba(147, 51, 234, 0.3) !important; - border-color: rgba(168, 85, 247, 0.8) !important; -} - -/* Loading state for clips - dashed style like drag preview */ -.timeline-clip.loading { - background: rgba(40, 60, 90, 0.85) !important; - border: 2px dashed var(--accent) !important; - box-shadow: none !important; - display: block; -} - -.timeline-clip.loading:not(.entrance-animate):not(.exit-animate) { - /* Override global .loading class which sets transform for centered spinners. */ - transform: none !important; -} - -.timeline-clip.loading.audio { - display: none; -} - -.timeline-clip.loading .clip-thumbnails, -.timeline-clip.loading .clip-waveform { - display: none; -} - -.timeline-clip.loading .clip-content { - background: transparent !important; - height: 100%; - justify-content: center; - align-items: center; - flex-direction: row; - gap: 6px; -} - -.timeline-clip.loading .clip-name-row { - justify-content: center; -} - -.timeline-clip.loading .clip-meta { - align-items: center; - max-width: none; -} - -.timeline-clip.loading .clip-name { - opacity: 0.8; -} - -.timeline-clip.loading .clip-duration { - display: none; -} - -.timeline-clip.loading::after { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.08), - transparent - ); - animation: clip-loading-shimmer 1.5s infinite; -} - -@keyframes clip-loading-shimmer { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } -} - -/* Clip entrance animation on composition switch - GPU accelerated */ -@keyframes clip-entrance-from-left { - from { - opacity: 1; - transform: translate3d(-72px, 0, 0); - } - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -@keyframes clip-entrance-from-right { - from { - opacity: 1; - transform: translate3d(72px, 0, 0); - } - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -/* Clip exit animation - flies out without fading */ -@keyframes clip-exit-right { - from { - opacity: 1; - transform: translate3d(0, 0, 0); - } - to { - opacity: 1; - transform: translate3d(calc(140vw + 100%), 0, 0); - } -} - -@keyframes clip-exit-left { - from { - opacity: 1; - transform: translate3d(0, 0, 0); - } - to { - opacity: 1; - transform: translate3d(calc(-140vw - 100%), 0, 0); - } -} - -.timeline-clip.entrance-animate-left { - animation: clip-entrance-from-left 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; -} - -.timeline-clip.entrance-animate-right { - animation: clip-entrance-from-right 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; -} - -.timeline-clip.exit-animate-right { - animation: clip-exit-right 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; -} - -.timeline-clip.exit-animate-left { - animation: clip-exit-left 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; -} - -.clip-loading-spinner { - width: 14px; - height: 14px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: var(--text-on-accent); - border-radius: 50%; - animation: clip-spinner 0.8s linear infinite; - margin-right: 6px; - flex-shrink: 0; -} - -/* Pending download state for YouTube clips */ -.timeline-clip.pending-download { - background: linear-gradient(180deg, rgba(255, 0, 0, 0.2), rgba(180, 0, 0, 0.15)) !important; - border: 2px dashed #ff0000 !important; - box-shadow: none !important; -} - -.timeline-clip.pending-download .clip-thumbnails, -.timeline-clip.pending-download .clip-waveform { - display: none; -} - -.timeline-clip.pending-download .clip-content { - background: transparent !important; -} - -.timeline-clip.pending-download .clip-name { - opacity: 0.9; -} - -/* YouTube thumbnail preview in pending clip */ -.clip-youtube-preview { - position: absolute; - inset: 0; - background-size: cover; - background-position: center; - opacity: 0.5; - z-index: 1; -} - -/* Download progress bar at bottom */ -.clip-download-progress { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 4px; - background: rgba(0, 0, 0, 0.5); - z-index: 15; -} - -.clip-download-progress-bar { - height: 100%; - background: linear-gradient(90deg, #ff0000, #ff4444); - transition: width 0.2s ease-out; - box-shadow: 0 0 4px rgba(255, 0, 0, 0.6); -} - -/* Download status text */ -.clip-download-status { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(0, 0, 0, 0.7); - color: var(--text-on-accent); - padding: 4px 8px; - border-radius: 4px; - font-size: 10px; - font-weight: 500; - z-index: 20; - display: flex; - align-items: center; - gap: 6px; -} - -.clip-download-status .download-spinner { - width: 12px; - height: 12px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: #ff0000; - border-radius: 50%; - animation: clip-spinner 0.8s linear infinite; -} - -/* Download error state */ -.timeline-clip.download-error { - background: rgba(239, 68, 68, 0.2) !important; - border: 2px dashed var(--danger) !important; -} - -.clip-download-error-badge { - position: absolute; - top: 2px; - left: 2px; - background: var(--danger); - color: var(--text-on-accent); - font-size: 10px; - font-weight: bold; - padding: 1px 5px; - border-radius: 2px; - z-index: 20; - cursor: help; -} - -/* Proxy progress bar in timeline clips */ -.clip-proxy-progress { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: rgba(0, 0, 0, 0.5); - z-index: 10; -} - -.clip-proxy-progress-bar { - height: 100%; - background: linear-gradient(90deg, var(--success), var(--success-light)); - transition: width 0.2s ease-out; - box-shadow: 0 0 4px rgba(34, 197, 94, 0.6); -} - -.clip-proxy-badge { - position: absolute; - top: 2px; - right: 2px; - background: var(--success); - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 4px; - border-radius: 2px; - z-index: 10; -} - -.clip-proxy-error { - position: absolute; - top: 2px; - right: 2px; - background: var(--danger); - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 4px; - border-radius: 2px; - z-index: 10; -} - -/* Proxy generating indicator with fill animation */ -.clip-proxy-generating { - position: absolute; - top: 2px; - right: 2px; - display: flex; - align-items: center; - gap: 3px; - z-index: 10; -} - -.clip-proxy-generating .proxy-percent { - font-size: 8px; - color: var(--warning-light); - font-weight: bold; -} - -/* Proxy fill badge - shared styles */ -.proxy-fill-badge { - position: relative; - width: 14px; - height: 14px; - border-radius: 2px; - overflow: hidden; - font-size: 9px; - font-weight: bold; - line-height: 14px; - text-align: center; -} - -.proxy-fill-bg { - position: absolute; - inset: 0; - background: rgba(251, 191, 36, 0.2); - color: rgba(251, 191, 36, 0.4); - display: flex; - align-items: center; - justify-content: center; -} - -.proxy-fill-progress { - position: absolute; - bottom: 0; - left: 0; - right: 0; - background: var(--warning-light); - color: var(--text-inverse); - display: flex; - align-items: flex-end; - justify-content: center; - overflow: hidden; - transition: height 0.3s ease-out; -} - -.timeline-clip.has-proxy { - border-color: var(--success) !important; -} - -.timeline-clip.generating-proxy { - border-color: var(--warning-light) !important; -} - -.timeline-clip.proxy-error { - border-color: var(--danger) !important; -} - -/* Loading state takes visual priority over proxy states */ -.timeline-clip.loading.has-proxy, -.timeline-clip.loading.generating-proxy { - border-color: var(--accent) !important; -} - -.timeline-clip.loading .clip-proxy-generating, -.timeline-clip.loading .clip-proxy-badge { - display: none; -} - -/* Reversed clip styles */ -.clip-reversed-badge { - position: absolute; - top: 2px; - right: 20px; - background: #f97316; - color: var(--text-inverse); - font-size: 10px; - font-weight: bold; - padding: 1px 4px; - border-radius: 2px; - z-index: 10; -} - -.timeline-clip.reversed { - border-color: #f97316 !important; -} - -.timeline-clip.reversed .clip-thumbnails { - transform: scaleX(-1); -} - -/* Needs reload indicator - file permission required after refresh */ -.clip-reload-badge { - position: absolute; - top: 2px; - left: 2px; - background: var(--danger); - color: var(--text-on-accent); - font-size: 10px; - font-weight: bold; - padding: 1px 5px; - border-radius: 2px; - z-index: 10; - cursor: help; -} - -.timeline-clip.needs-reload { - opacity: 0.6; - border-color: var(--danger) !important; -} - -.timeline-clip.needs-reload .clip-thumbnails { - filter: grayscale(1); -} - -/* Linked group (multicam) styles */ -.clip-linked-group-badge { - position: absolute; - top: 2px; - right: 38px; - background: var(--purple); - color: var(--text-on-accent); - font-size: 10px; - font-weight: bold; - padding: 1px 4px; - border-radius: 2px; - z-index: 10; -} - -.timeline-clip.linked-group { - border-color: var(--purple) !important; - box-shadow: 0 0 6px rgba(139, 92, 246, 0.4); -} - -/* Transcript badge on timeline clip */ -.clip-transcript-badge { - position: absolute; - bottom: 2px; - right: 20px; - background: var(--accent); - color: var(--text-inverse); - font-size: 8px; - font-weight: bold; - padding: 0px 3px; - border-radius: 2px; - z-index: 10; - pointer-events: none; - line-height: 13px; -} - -/* Analysis badge on timeline clip */ -.clip-analysis-badge { - position: absolute; - bottom: 2px; - right: 2px; - background: #e88a1a; - color: var(--text-inverse); - font-size: 8px; - font-weight: bold; - padding: 0px 3px; - border-radius: 2px; - z-index: 10; - pointer-events: none; - line-height: 13px; -} - -/* Clip badge fill effect (partial coverage) */ -.clip-badge-fill { - background: transparent; - overflow: hidden; - width: 13px; - height: 13px; - padding: 0; -} - -.clip-badge-fill .clip-badge-bg { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.15); - color: rgba(255, 255, 255, 0.35); - font-size: 8px; - font-weight: bold; -} - -.clip-badge-fill .clip-badge-progress { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - align-items: flex-end; - justify-content: center; - overflow: hidden; - color: var(--text-inverse); - font-size: 8px; - font-weight: bold; -} - -.clip-badge-fill .clip-badge-transcript-fill { - background: var(--accent); -} - -.clip-badge-fill .clip-badge-analysis-fill { - background: #e88a1a; -} - -/* Pick Whip (Layer Parenting) styles */ -.pick-whip { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - margin-left: 6px; - cursor: grab; - opacity: 0.8; - transition: opacity 0.15s, transform 0.15s; - flex-shrink: 0; - pointer-events: auto; /* Override parent's pointer-events: none */ - color: var(--expr-color); /* Yellow/gold color */ -} - -.pick-whip:hover { - opacity: 1; - transform: scale(1.15); - color: var(--expr-color-light); -} - -.pick-whip.dragging { - cursor: grabbing; - opacity: 1; -} - -.pick-whip.has-parent { - opacity: 1; - color: var(--expr-connected); /* Green when connected */ -} - -.pick-whip.has-parent:hover { - color: var(--danger); -} - -.pick-whip-icon { - color: inherit; -} - -/* Parent-child link connection lines */ -/* Cable shadow for 3D depth effect */ -.parent-child-link-shadow { - stroke: rgba(0, 0, 0, 0.4); - stroke-width: 5; - stroke-linecap: round; - filter: blur(2px); - transform: translate(1px, 2px); -} - -/* Main cable - thick yellow like audio cables */ -.parent-child-link { - stroke: var(--expr-color); - stroke-width: 3; - stroke-linecap: round; - fill: none; - filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3)); -} - -/* Cable connectors */ -.parent-child-link-endpoint { - fill: var(--expr-color-dark); - stroke: var(--expr-color-stroke); - stroke-width: 1.5; - filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); -} - -.parent-child-link-start { - fill: var(--expr-color-dark); - stroke: var(--expr-color-stroke); - stroke-width: 1.5; - filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); -} - -/* Hover effects */ -.parent-child-link-group:hover .parent-child-link { - stroke: var(--expr-color-light); - stroke-width: 3.5; -} - -.parent-child-link-group:hover .parent-child-link-shadow { - stroke-width: 6; -} - -.parent-child-link-group:hover .parent-child-link-endpoint, -.parent-child-link-group:hover .parent-child-link-start { - fill: var(--expr-color-light); - stroke: var(--expr-color-dark); - r: 6; -} - -/* Clip with parent indicator */ -.timeline-clip.has-parent { - border-color: var(--expr-color) !important; - box-shadow: 0 0 6px rgba(229, 192, 123, 0.3); -} - -.timeline-clip.has-parent::after { - content: ''; - position: absolute; - left: 0; - top: 0; - width: 3px; - height: 100%; - background: var(--expr-color); - opacity: 0.6; -} - -@keyframes clip-spinner { - to { transform: rotate(360deg); } -} - -/* Transcript word markers */ -.clip-transcript-markers { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 4px; - pointer-events: none; - z-index: 5; -} - -.transcript-marker { - position: absolute; - bottom: 0; - height: 4px; - background: var(--warning-light); - border-radius: 1px 1px 0 0; - opacity: 0.9; - pointer-events: auto; - cursor: pointer; - transition: opacity 0.1s, height 0.1s; -} - -.transcript-marker:hover { - opacity: 1; - height: 6px; - background: var(--warning); -} - -/* Transcribing progress indicator */ -.clip-transcribing-indicator { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3px; - background: rgba(0, 0, 0, 0.4); - z-index: 6; -} - -.transcribing-progress { - height: 100%; - background: linear-gradient(90deg, var(--purple-light), #d946ef); - transition: width 0.2s ease-out; - box-shadow: 0 0 4px rgba(168, 85, 247, 0.6); -} - -/* Analysis overlay - full height graph covering entire clip */ -.clip-analysis-overlay { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - pointer-events: none; - overflow: hidden; -} - -.analysis-overlay-canvas { - display: block; - position: absolute; - bottom: 0; - left: 0; -} - -/* Real-time position indicator on analysis overlay */ -.analysis-position-indicator { - position: absolute; - top: 0; - bottom: 0; - width: 1px; - pointer-events: none; - z-index: 10; -} - -.analysis-indicator-line { - position: absolute; - top: 0; - bottom: 0; - width: 1px; - background: rgba(255, 255, 255, 0.8); - box-shadow: 0 0 4px rgba(255, 255, 255, 0.5); -} - -.analysis-indicator-values { - position: absolute; - top: -16px; - left: 4px; - display: flex; - gap: 6px; - font-size: 9px; - font-weight: 600; - white-space: nowrap; -} - -.analysis-indicator-values .focus-value { - color: var(--success-light); - text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); -} - -.analysis-indicator-values .motion-value { - color: #60a5fa; - text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); -} - -/* Analysis legend labels on clip */ -.analysis-legend-labels { - position: absolute; - top: 2px; - right: 4px; - display: flex; - gap: 8px; - font-size: 8px; - font-weight: 500; - pointer-events: none; - z-index: 4; -} - -.analysis-legend-labels .legend-focus { - color: var(--success-light); - text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); -} - -.analysis-legend-labels .legend-motion { - color: #60a5fa; - text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); -} - -.analysis-legend-labels .legend-progress { - color: var(--warning-light); - font-weight: bold; - text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); -} - -/* Analyzing progress indicator */ -.clip-analyzing-indicator { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3px; - background: rgba(0, 0, 0, 0.4); - z-index: 6; -} - -.analyzing-progress { - height: 100%; - background: linear-gradient(90deg, var(--success), var(--success-light)); - transition: width 0.2s ease-out; - box-shadow: 0 0 4px rgba(34, 197, 94, 0.6); -} - -/* Waveform generation progress indicator */ -.clip-waveform-indicator { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 4px; - background: rgba(0, 0, 0, 0.4); - z-index: 6; -} - -.waveform-progress { - height: 100%; - background: linear-gradient(90deg, var(--danger), var(--danger-light), var(--danger)); - background-size: 200% 100%; - animation: waveform-progress-pulse 1s ease-in-out infinite; - box-shadow: 0 0 4px rgba(239, 68, 68, 0.6); -} - -@keyframes waveform-progress-pulse { - 0%, 100% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } -} - -/* Audio clip generating waveform */ -.timeline-clip.audio.generating-waveform { - border-color: var(--danger) !important; -} - -/* Clip with transcript indicator */ -.timeline-clip.has-transcript::before { - content: 'T'; - position: absolute; - top: 2px; - left: 2px; - background: var(--purple-light); - color: var(--text-on-accent); - font-size: 8px; - font-weight: bold; - padding: 1px 3px; - border-radius: 2px; - z-index: 10; -} - -.timeline-clip.image { - background: #5c3d7a; - border: 1px solid #6d4e8a; -} - -.timeline-clip:hover { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); -} - -.timeline-clip.selected { - box-shadow: 0 0 0 2px var(--accent-timeline); -} - -/* Nested composition clips always have orange outline */ -.timeline-clip.composition { - box-shadow: 0 0 0 2px #f97316; -} - -.timeline-clip.composition.selected { - box-shadow: 0 0 0 2px #f97316, 0 0 0 4px var(--accent-timeline); -} - -.timeline-clip.dragging { - opacity: 0.9; - cursor: grabbing; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - z-index: 100; -} - -.timeline-clip.linked-dragging { - opacity: 0.9; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - z-index: 99; -} - -/* Visual feedback when forcing overlap - red glow to indicate clip will be trimmed */ -.timeline-clip.forcing-overlap { - box-shadow: 0 0 0 2px var(--danger), 0 4px 12px rgba(239, 68, 68, 0.5); -} - -/* Cut tool mode */ -.timeline-clip.cut-mode { - cursor: crosshair !important; -} - -.timeline-clip.cut-mode:hover { - box-shadow: 0 0 0 1px #f97316; -} - -/* Cut indicator line */ -.cut-indicator { - position: absolute; - top: 0; - bottom: 0; - width: 2px; - background: #f97316; - pointer-events: none; - z-index: 100; - box-shadow: 0 0 4px rgba(249, 115, 22, 0.8); -} - -.cut-indicator::before, -.cut-indicator::after { - content: ''; - position: absolute; - left: -4px; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; -} - -.cut-indicator::before { - top: 0; - border-top: 6px solid #f97316; -} - -.cut-indicator::after { - bottom: 0; - border-bottom: 6px solid #f97316; -} - -/* ============================================ - AI Action Visual Feedback Animations - ============================================ */ - -/* Split Glow - vertical blue glow line at cut position */ -@keyframes ai-split-glow-fade { - 0% { - opacity: 0; - transform: scaleY(0.8); - box-shadow: 0 0 0 0 transparent; - } - 5% { - opacity: 1; - transform: scaleY(1); - box-shadow: 0 0 16px 6px rgba(59, 130, 246, 0.95), 0 0 32px 12px rgba(59, 130, 246, 0.5); - } - 30% { - opacity: 1; - transform: scaleY(1); - box-shadow: 0 0 20px 8px rgba(59, 130, 246, 0.8), 0 0 40px 16px rgba(59, 130, 246, 0.35); - } - 100% { - opacity: 0; - transform: scaleY(0.95); - box-shadow: 0 0 0 0 transparent; - } -} - -.ai-split-glow { - position: absolute; - width: 3px; - margin-left: -1.5px; - background: #60a5fa; - pointer-events: none; - z-index: 200; - opacity: 0; - animation: ai-split-glow-fade 1s cubic-bezier(0.22, 1, 0.36, 1) both; - box-shadow: 0 0 16px 6px rgba(59, 130, 246, 0.95), 0 0 32px 12px rgba(59, 130, 246, 0.5); -} - -.ai-split-glow::before, -.ai-split-glow::after { - content: ''; - position: absolute; - left: -4px; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; -} - -.ai-split-glow::before { - top: 0; - border-top: 6px solid #3b82f6; -} - -.ai-split-glow::after { - bottom: 0; - border-bottom: 6px solid #3b82f6; -} - -/* Delete Ghost - fading red outline at old clip position */ -@keyframes ai-delete-fade { - 0% { - opacity: 0.8; - transform: scale(1); - } - 100% { - opacity: 0; - transform: scale(0.97) translateY(3px); - } -} - -.ai-delete-ghost { - position: absolute; - border-radius: 4px; - background: rgba(239, 68, 68, 0.25); - border: 2px dashed rgba(239, 68, 68, 0.7); - pointer-events: none; - z-index: 199; - animation: ai-delete-fade 0.6s cubic-bezier(0.55, 0, 1, 0.45) both; - display: flex; - align-items: center; - padding: 0 8px; - box-shadow: 0 0 12px rgba(239, 68, 68, 0.4); -} - -.ai-delete-ghost .ghost-name { - color: rgba(255, 255, 255, 0.5); - font-size: 10px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-decoration: line-through; -} - -/* Trim Highlight - green edge glow at trimmed side */ -@keyframes ai-trim-pulse { - 0% { - opacity: 1; - box-shadow: 0 0 10px 3px rgba(34, 197, 94, 0.8); - } - 100% { - opacity: 0; - box-shadow: 0 0 0 0 transparent; - } -} - -.ai-trim-highlight { - position: absolute; - width: 4px; - margin-left: -2px; - background: #22c55e; - pointer-events: none; - z-index: 200; - animation: ai-trim-pulse 0.8s ease-out both; - box-shadow: 0 0 14px 4px rgba(34, 197, 94, 0.85); - border-radius: 1px; -} - -/* Silent Zone - yellow/amber highlight for detected silent sections */ -@keyframes ai-silent-zone-fade { - 0% { - opacity: 0.7; - } - 50% { - opacity: 0.5; - } - 100% { - opacity: 0; - } -} - -.ai-silent-zone { - position: absolute; - border-radius: 3px; - background: repeating-linear-gradient( - -45deg, - rgba(251, 191, 36, 0.2), - rgba(251, 191, 36, 0.2) 4px, - rgba(251, 191, 36, 0.1) 4px, - rgba(251, 191, 36, 0.1) 8px - ); - border: 1.5px dashed rgba(251, 191, 36, 0.6); - pointer-events: none; - z-index: 198; - animation: ai-silent-zone-fade 2s ease-out both; - box-shadow: inset 0 0 8px rgba(251, 191, 36, 0.3); -} - -/* Low Quality Zone - red/orange highlight for detected low quality sections */ -@keyframes ai-low-quality-zone-fade { - 0% { - opacity: 0.7; - } - 50% { - opacity: 0.5; - } - 100% { - opacity: 0; - } -} - -.ai-low-quality-zone { - position: absolute; - border-radius: 3px; - background: repeating-linear-gradient( - -45deg, - rgba(239, 68, 68, 0.2), - rgba(239, 68, 68, 0.2) 4px, - rgba(239, 68, 68, 0.1) 4px, - rgba(239, 68, 68, 0.1) 8px - ); - border: 1.5px dashed rgba(239, 68, 68, 0.6); - pointer-events: none; - z-index: 198; - animation: ai-low-quality-zone-fade 2s ease-out both; - box-shadow: inset 0 0 8px rgba(239, 68, 68, 0.3); -} - -/* AI Move Animation - blue shadow during FLIP transition */ -.timeline-clip.ai-moving { - z-index: 100; - box-shadow: 0 2px 12px rgba(59, 130, 246, 0.4), 0 0 0 1px rgba(59, 130, 246, 0.3); -} - -/* ============================================ - AI Preview Canvas Flash Overlays - ============================================ */ - -/* Base flash overlay */ -.ai-preview-flash { - position: absolute; - inset: 0; - pointer-events: none; - z-index: 500; - border-radius: inherit; -} - -/* Capture frame shutter effect - white flash */ -@keyframes ai-shutter-flash { - 0% { - opacity: 0.9; - background: white; - } - 40% { - opacity: 0.3; - background: white; - } - 100% { - opacity: 0; - background: transparent; - } -} - -.ai-preview-flash-shutter { - animation: ai-shutter-flash 0.4s ease-out both; -} - -/* Undo flash - brief orange border pulse */ -@keyframes ai-undo-flash { - 0% { - box-shadow: inset 0 0 0 3px rgba(251, 146, 60, 0.8); - background: rgba(251, 146, 60, 0.1); - } - 100% { - box-shadow: inset 0 0 0 0 transparent; - background: transparent; - } -} - -.ai-preview-flash-undo { - animation: ai-undo-flash 0.6s ease-out both; -} - -/* Redo flash - brief green border pulse */ -@keyframes ai-redo-flash { - 0% { - box-shadow: inset 0 0 0 3px rgba(34, 197, 94, 0.8); - background: rgba(34, 197, 94, 0.1); - } - 100% { - box-shadow: inset 0 0 0 0 transparent; - background: transparent; - } -} - -.ai-preview-flash-redo { - animation: ai-redo-flash 0.6s ease-out both; -} - -/* Import flash - brief blue border pulse */ -@keyframes ai-import-flash { - 0% { - box-shadow: inset 0 0 0 3px rgba(59, 130, 246, 0.8); - background: rgba(59, 130, 246, 0.1); - } - 100% { - box-shadow: inset 0 0 0 0 transparent; - background: transparent; - } -} - -.ai-preview-flash-import { - animation: ai-import-flash 0.6s ease-out both; -} - -/* ============================================ - AI Marker Animations - ============================================ */ - -/* Marker pop-in animation */ -@keyframes ai-marker-pop { - 0% { - transform: scaleY(0); - opacity: 0; - } - 60% { - transform: scaleY(1.2); - opacity: 1; - } - 100% { - transform: scaleY(1); - opacity: 1; - } -} - -.timeline-marker.ai-marker-added { - animation: ai-marker-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; - transform-origin: bottom center; -} - -/* Marker fade-out animation */ -@keyframes ai-marker-fade { - 0% { - opacity: 1; - transform: scaleY(1); - } - 100% { - opacity: 0; - transform: scaleY(0); - } -} - -.timeline-marker.ai-marker-removed { - animation: ai-marker-fade 0.3s ease-in both; - transform-origin: bottom center; -} - -/* ============================================ - AI Keyframe Animations - ============================================ */ - -/* Keyframe diamond pop animation */ -@keyframes ai-keyframe-pop { - 0% { - transform: scale(0) rotate(45deg); - opacity: 0; - } - 60% { - transform: scale(1.4) rotate(45deg); - opacity: 1; - } - 100% { - transform: scale(1) rotate(45deg); - opacity: 1; - } -} - -.keyframe-diamond.ai-keyframe-added { - animation: ai-keyframe-pop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both; -} - -/* Premiere-style drag feedback */ -.timeline-tracks.dragging-clip { - cursor: grabbing; - user-select: none; -} - -/* Marquee selection */ -.timeline-tracks.marquee-selecting { - cursor: crosshair; -} - -.marquee-selection { - position: absolute; - background: rgba(59, 130, 246, 0.15); - border: 1px solid rgba(59, 130, 246, 0.6); - border-radius: 2px; - pointer-events: none; - z-index: 1000; -} - -/* Disable all text selection in timeline */ -.timeline-container, -.timeline-container * { - user-select: none !important; - -webkit-user-select: none !important; -} - -.track-lane.drag-target { - background: rgba(59, 130, 246, 0.15); -} - -.track-lane.external-drag-target { - background: rgba(34, 197, 94, 0.15); -} - -/* External file drag preview */ -.timeline-clip-preview { - position: absolute; - top: 4px; - bottom: 4px; - border-radius: 2px; - background: rgba(34, 197, 94, 0.5); - border: 2px dashed var(--success); - pointer-events: none; - display: flex; - align-items: center; - justify-content: center; -} - -.timeline-clip-preview .clip-content { - padding: 4px 8px; -} - -.timeline-clip-preview .clip-name { - color: var(--text-on-accent); - font-size: 11px; - font-weight: 500; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); -} - -.timeline-clip-preview.audio { - background: rgba(59, 130, 246, 0.5); - border-color: rgba(59, 130, 246, 0.8); -} - -.track-lane.new-track-preview { - background: rgba(59, 130, 246, 0.1); - border: 2px dashed rgba(59, 130, 246, 0.5); - border-radius: 2px; - margin-top: 4px; - position: relative; -} - -/* New track drop zones - appear when dragging media */ -.new-track-drop-zone { - height: 36px; - margin-top: 4px; - border: 2px dashed rgba(255, 255, 255, 0.3); - border-radius: 4px; - background: rgba(255, 255, 255, 0.05); - display: flex; - align-items: center; - justify-content: center; - position: relative; - transition: all 0.15s ease; -} - -.new-track-drop-zone .drop-zone-label { - font-size: 11px; - color: rgba(255, 255, 255, 0.5); - font-weight: 500; - pointer-events: none; - z-index: 1; -} - -.new-track-drop-zone.video { - height: 60px; /* Match default video track height */ - border-color: rgba(37, 99, 235, 0.4); -} - -.new-track-drop-zone.audio { - height: 40px; /* Match default audio track height */ - border-color: rgba(22, 163, 74, 0.4); -} - -.new-track-drop-zone.active, -.new-track-drop-zone:hover { - background: rgba(255, 255, 255, 0.1); -} - -.new-track-drop-zone.video.active, -.new-track-drop-zone.video:hover { - border-color: rgba(37, 99, 235, 0.8); - background: rgba(37, 99, 235, 0.15); -} - -.new-track-drop-zone.audio.active, -.new-track-drop-zone.audio:hover { - border-color: rgba(22, 163, 74, 0.8); - background: rgba(22, 163, 74, 0.15); -} - -.new-track-drop-zone.active .drop-zone-label, -.new-track-drop-zone:hover .drop-zone-label { - color: rgba(255, 255, 255, 0.8); -} - -.new-track-drop-zone .timeline-clip-preview { - position: absolute; - top: 4px; - bottom: 4px; - border-radius: 3px; - pointer-events: none; -} - -.clip-content { - position: relative; - z-index: 5; - padding: 2px 6px; - height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.2) 50%, transparent 100%); - pointer-events: none; - overflow: hidden; -} - -.clip-meta { - display: flex; - flex-direction: column; - gap: 1px; - width: max-content; - max-width: calc(100% - 12px); - min-width: 0; - will-change: transform; -} - -.clip-name-row { - display: flex; - flex-direction: row; - align-items: center; - gap: 2px; - min-width: 0; - width: 100%; -} - -.clip-name { - font-size: 10px; - line-height: 1.2; - font-weight: 600; - color: var(--text-on-accent); - min-width: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); -} - -.clip-duration { - font-size: 9px; - color: rgba(255, 255, 255, 0.8); - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); -} - -.clip-type-icon { - width: 14px; - height: 14px; - flex-shrink: 0; - color: rgba(235, 239, 244, 0.9); - filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45)); -} - -.clip-static-artwork { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 2; -} - -.clip-static-artwork-icon { - width: 42px; - height: 42px; - max-width: calc(100% - 18px); - max-height: calc(100% - 18px); - color: rgba(205, 211, 217, 0.5); - filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.28)); -} - -/* Clip thumbnails filmstrip */ -.clip-thumbnails { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - overflow: hidden; - opacity: 0.85; -} - -.clip-thumb { - height: 100%; - width: auto; - object-fit: cover; - flex-shrink: 0; - pointer-events: none; -} - -.clip-thumb-placeholder { - height: 100%; - width: 71px; - flex-shrink: 0; - background: #1a1a1a; -} - -.timeline-clip.video .clip-thumbnails, -.timeline-clip.image .clip-thumbnails { - background: rgba(0, 0, 0, 0.3); -} - -.timeline-clip.audio .clip-thumbnails { - background: linear-gradient(180deg, #16a34a, #15803d); -} - -/* Trim handles */ -.trim-handle { - position: absolute; - top: 0; - bottom: 0; - width: 10px; - cursor: ew-resize; - background: rgba(255, 255, 255, 0.2); - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 10; -} - -.trim-handle:hover { - background: rgba(255, 255, 255, 0.5); -} - -.trim-handle.left { - left: 0; - border-radius: 4px 0 0 4px; -} - -.trim-handle.right { - right: 0; - border-radius: 0 4px 4px 0; -} - -/* Trimming state */ -.timeline-clip.trimming, -.timeline-clip.linked-trimming { - z-index: 100; - box-shadow: 0 0 0 2px var(--warning); -} - -.timeline-clip.trimming .trim-handle, -.timeline-clip.linked-trimming .trim-handle { - opacity: 1; - background: var(--warning); -} - -.timeline-clip:hover .trim-handle { - opacity: 1; -} - -/* Fade handles - corner handles for fade-in/out */ -.fade-handle { - position: absolute; - top: 0; - width: 12px; - height: 12px; - cursor: ew-resize; - background: rgba(251, 191, 36, 0.8); - border-radius: 0 0 4px 0; - opacity: 0; - transition: opacity 0.15s, background 0.15s, transform 0.15s; - z-index: 15; -} - -.fade-handle:hover { - background: rgba(251, 191, 36, 1); - transform: scale(1.1); -} - -.fade-handle.left { - left: 0; - border-radius: 0 0 4px 0; -} - -.fade-handle.right { - right: 0; - left: auto; - border-radius: 0 0 0 4px; -} - -/* When fade is active, position handle at end of fade region */ -.fade-handle.left.active { - border-radius: 2px; -} - -.fade-handle.right.active { - border-radius: 2px; -} - -.timeline-clip:hover .fade-handle { - opacity: 1; -} - -.timeline-clip.fading .fade-handle { - opacity: 1; - background: var(--warning); -} - -/* Fade curve container - SVG bezier curve showing opacity animation */ -/* Keyframe tick marks on clip bar */ -.clip-keyframe-ticks { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 8px; - pointer-events: none; - z-index: 6; -} - -.keyframe-tick { - position: absolute; - bottom: -4px; - width: 16px; - height: 16px; - padding: 0; - border: 0; - background: transparent; - appearance: none; - transform: translateX(-50%); - pointer-events: auto; - cursor: grab; - z-index: 1; -} - -.keyframe-tick::after { - content: ''; - position: absolute; - left: 50%; - bottom: 5px; - width: 4px; - height: 4px; - background: var(--warning); - transform: translateX(-50%) rotate(45deg); - opacity: 0.7; - transition: opacity 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease; -} - -.keyframe-tick.state-change::after { - background: var(--cyan-500); -} - -.keyframe-tick:hover::after, -.keyframe-tick:focus-visible::after, -.keyframe-tick.dragging::after { - transform: translateX(-50%) rotate(45deg) scale(2.15); - opacity: 1; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35); -} - -.keyframe-tick:active, -.keyframe-tick.dragging { - cursor: grabbing; -} - -.timeline-clip.selected .keyframe-tick::after { - opacity: 1; -} - -.fade-curve-container { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - pointer-events: none; - z-index: 5; - overflow: hidden; -} - -.fade-curve-svg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -/* Playhead */ -.playhead { - position: absolute; - top: 0; - width: 2px; - min-height: 100%; - height: max-content; - z-index: 30; - pointer-events: none; - transform: translateX(-1px); - --timeline-switch-base-x: -1px; -} - -.playhead-head { - width: 14px; - height: 14px; - background: var(--accent-timeline); - border-radius: 2px; - transform: translateX(-6px); - cursor: grab; - pointer-events: auto; - position: sticky; - top: 4px; - z-index: 35; -} - -.playhead-head:hover { - background: var(--accent-hover); -} - -.playhead-head:active { - cursor: grabbing; -} - -.playhead-line { - width: 2px; - min-height: calc(100vh - 50px); - height: 100%; - background: var(--accent-timeline); - margin-top: -2px; -} - -/* Timeline Navigator - Horizontal scrollbar with zoom handles */ -.timeline-navigator { - height: 16px; - background: var(--bg-primary); - border-top: 1px solid var(--border-color); - padding: 3px 14px 3px 150px; /* Left: track headers width, Right: vertical scrollbar width */ - flex-shrink: 0; -} - -.timeline-navigator-track { - height: 100%; - background: var(--bg-tertiary); - border-radius: 3px; - position: relative; - cursor: pointer; -} - -.timeline-navigator-thumb { - position: absolute; - top: 0; - height: 100%; - background: var(--scrollbar-thumb); - border: none; - border-radius: 3px; - cursor: grab; - display: flex; - align-items: center; - justify-content: space-between; - transition: background 0.1s; -} - -.timeline-navigator-thumb:hover { - background: var(--scrollbar-thumb-hover); -} - -.timeline-navigator-thumb.dragging { - cursor: grabbing; - background: var(--scrollbar-thumb-hover); -} - -.timeline-navigator-handle { - width: 14px; - height: 100%; - cursor: ew-resize; - display: flex; - align-items: center; - justify-content: center; - transition: opacity 0.15s; -} - -.timeline-navigator-handle:hover::before { - background: var(--text-muted); -} - -.timeline-navigator-handle-left { - border-radius: 3px 0 0 3px; -} - -.timeline-navigator-handle-right { - border-radius: 0 3px 3px 0; -} - -.timeline-navigator-handle::before { - content: ''; - width: 6px; - height: 6px; - background: var(--bg-active); - border-radius: 50%; -} - -.timeline-navigator-grip { - flex: 1; - display: flex; - align-items: center; - justify-content: center; -} - -.timeline-navigator-grip::before, -.timeline-navigator-grip::after { - display: none; -} - -/* Vertical Scrollbar - matches horizontal TimelineNavigator style */ -.vertical-scrollbar { - width: 10px; - background: var(--bg-primary); - border-left: 1px solid var(--border-color); - flex-shrink: 0; - padding: 1px; -} - -.vertical-scrollbar-track { - width: 100%; - height: 100%; - background: var(--bg-tertiary); - border-radius: 3px; - position: relative; - cursor: pointer; -} - -.vertical-scrollbar-thumb { - position: absolute; - left: 0; - width: 100%; - background: var(--scrollbar-thumb); - border-radius: 3px; - cursor: grab; - display: flex; - flex-direction: column; - align-items: center; - transition: background 0.1s; -} - -.vertical-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); -} - -.vertical-scrollbar-thumb.dragging { - cursor: grabbing; - background: var(--scrollbar-thumb-hover); -} - -.vertical-scrollbar-handle { - width: 100%; - height: 10px; - cursor: ns-resize; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.7; - transition: opacity 0.15s; -} - -.vertical-scrollbar-handle:hover { - opacity: 1; - background: rgba(255, 255, 255, 0.1); -} - -.vertical-scrollbar-handle-top { - border-radius: 3px 3px 0 0; -} - -.vertical-scrollbar-handle-bottom { - border-radius: 0 0 3px 3px; -} - -.vertical-scrollbar-handle::before { - content: ''; - width: 6px; - height: 6px; - background: var(--bg-canvas); - border-radius: 50%; -} - -.vertical-scrollbar-grip { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; -} - -.vertical-scrollbar-grip::before, -.vertical-scrollbar-grip::after { - content: ''; - width: 6px; - height: 2px; - background: var(--text-secondary); - border-radius: 1px; - opacity: 0.5; -} - -/* Snap indicator line */ -.snap-line { - position: absolute; - top: 0; - bottom: 0; - width: 2px; - background: var(--success, #4ade80); - z-index: 99; - pointer-events: none; - box-shadow: 0 0 8px var(--success, #4ade80); -} - -.snap-line-guide { - opacity: 0.35; - box-shadow: none; -} - -/* In/Out Work Area Markers */ -.work-area-overlay { - position: absolute; - top: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.3); - pointer-events: none; - z-index: 10; -} - -.work-area-overlay::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - width: 100%; - background: repeating-linear-gradient( - 45deg, - transparent, - transparent 10px, - rgba(0, 0, 0, 0.1) 10px, - rgba(0, 0, 0, 0.1) 20px - ); -} - -.in-out-marker { - position: absolute; - top: 0; - bottom: 0; - z-index: 95; - pointer-events: none; - --timeline-switch-base-x: 0px; -} - -.in-out-marker .marker-flag { - position: absolute; - top: 0; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: bold; - color: var(--text-on-accent); - border-radius: 2px; - cursor: pointer; - pointer-events: auto; -} - -.in-marker .marker-flag { - background: var(--amber-600); - left: 0; - border-radius: 0 2px 2px 0; -} - -.out-marker .marker-flag { - background: var(--amber-600); - right: 0; - border-radius: 2px 0 0 2px; -} - -.in-out-marker .marker-line { - position: absolute; - top: 16px; - bottom: 0; - width: 2px; - background: var(--amber-600); -} - -.in-marker .marker-line { - left: 0; -} - -.out-marker .marker-line { - right: 0; -} - -.in-out-marker .marker-flag:hover { - transform: scale(1.1); - box-shadow: 0 0 6px rgba(245, 158, 11, 0.6); -} - -.in-out-marker.dragging .marker-flag { - transform: scale(1.2); - box-shadow: 0 0 8px rgba(245, 158, 11, 0.8); -} - -.in-out-marker.dragging .marker-line { - width: 3px; - box-shadow: 0 0 6px rgba(245, 158, 11, 0.5); -} - -/* Timeline Markers - like playhead, spans from ruler through all tracks */ -.timeline-marker { - position: absolute; - top: 0; - width: 2px; - min-height: 100%; - height: max-content; - z-index: 29; - pointer-events: none; - transform: translateX(-1px); - --timeline-switch-base-x: -1px; - --marker-color: var(--accent-timeline); -} - -.timeline-marker-head { - width: 14px; - height: 14px; - background: var(--marker-color); - border-radius: 3px; - transform: translateX(-6px); - cursor: grab; - pointer-events: auto; - position: sticky; - top: 4px; - z-index: 35; - display: flex; - align-items: center; - justify-content: center; - font-size: 9px; - font-weight: bold; - color: var(--text-on-accent); - transition: transform 0.1s, filter 0.1s, box-shadow 0.1s; -} - -.timeline-marker-head:hover { - transform: translateX(-6px) scale(1.15); - filter: brightness(1.2); - box-shadow: 0 0 6px var(--marker-color); -} - -.timeline-marker.dragging .timeline-marker-head { - cursor: grabbing; - transform: translateX(-6px) scale(1.2); - filter: brightness(1.3); - box-shadow: 0 0 8px var(--marker-color); -} - -.timeline-marker.is-stop-marker .timeline-marker-head { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--warning-light) 45%, transparent), - 0 0 10px color-mix(in srgb, var(--warning-light) 20%, transparent); -} - -.timeline-marker .timeline-marker-line { - width: 2px; - min-height: calc(100vh - 50px); - height: 100%; - background: var(--marker-color); - margin-top: -2px; - opacity: 0.7; -} - -.timeline-marker.is-stop-marker .timeline-marker-line { - background: - repeating-linear-gradient( - to bottom, - var(--marker-color) 0 10px, - color-mix(in srgb, var(--warning-light) 42%, var(--marker-color)) 10px 16px - ); -} - -.timeline-marker:hover .timeline-marker-line, -.timeline-marker.dragging .timeline-marker-line { - opacity: 1; - box-shadow: 0 0 4px var(--marker-color); -} - -@keyframes timeline-switch-enter-left { - from { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) - 72px), 0, 0); } - to { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } -} - -@keyframes timeline-switch-enter-right { - from { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) + 72px), 0, 0); } - to { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } -} - -@keyframes timeline-switch-exit-right { - from { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } - to { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) + 140vw + 100%), 0, 0); } -} - -@keyframes timeline-switch-exit-left { - from { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } - to { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) - 140vw - 100%), 0, 0); } -} - -.playhead.timeline-switch-enter-left, -.timeline-marker.timeline-switch-enter-left, -.in-out-marker.timeline-switch-enter-left { - animation: timeline-switch-enter-left 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; -} - -.playhead.timeline-switch-enter-right, -.timeline-marker.timeline-switch-enter-right, -.in-out-marker.timeline-switch-enter-right { - animation: timeline-switch-enter-right 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; -} - -.playhead.timeline-switch-exit-right, -.timeline-marker.timeline-switch-exit-right, -.in-out-marker.timeline-switch-exit-right { - animation: timeline-switch-exit-right 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; -} - -.playhead.timeline-switch-exit-left, -.timeline-marker.timeline-switch-exit-left, -.in-out-marker.timeline-switch-exit-left { - animation: timeline-switch-exit-left 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; -} - -/* Ghost marker for drag-to-create */ -.timeline-marker.ghost { - opacity: 0.7; - pointer-events: none; - z-index: 100; -} - -.timeline-marker.ghost .timeline-marker-head { - animation: ghost-pulse 0.5s ease-in-out infinite; -} - -.timeline-marker.ghost .timeline-marker-line { - opacity: 0.5; - background: linear-gradient(180deg, var(--marker-color) 0%, transparent 100%); -} - -@keyframes ghost-pulse { - 0%, 100% { transform: scale(1); filter: brightness(1); } - 50% { transform: scale(1.2); filter: brightness(1.3); } -} - -/* Drop animation */ -.timeline-marker.drop-animation { - opacity: 1; - animation: marker-drop 0.3s ease-out forwards; -} - -.timeline-marker.drop-animation .timeline-marker-head { - animation: marker-drop-icon 0.3s ease-out forwards; -} - -.timeline-marker.drop-animation .timeline-marker-line { - animation: marker-drop-line 0.3s ease-out forwards; -} - -@keyframes marker-drop { - 0% { opacity: 0.7; } - 50% { opacity: 1; } - 100% { opacity: 0; } -} - -@keyframes marker-drop-icon { - 0% { transform: scale(1.2); filter: brightness(1.5); } - 50% { transform: scale(1.4); filter: brightness(2); } - 100% { transform: scale(1); filter: brightness(1); } -} - -@keyframes marker-drop-line { - 0% { - opacity: 0.5; - box-shadow: 0 0 8px var(--marker-color); - } - 50% { - opacity: 1; - box-shadow: 0 0 16px var(--marker-color), 0 0 32px var(--marker-color); - } - 100% { - opacity: 0; - box-shadow: 0 0 0px var(--marker-color); - } -} - -/* Add Marker Button in ruler-header */ -.ruler-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 8px; -} - -.ruler-header > span { - flex: 1; - text-align: center; -} - -.add-marker-btn { - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - padding: 0; - background: transparent; - border: 1px solid transparent; - border-radius: 3px; - color: var(--text-secondary); - cursor: grab; - transition: all 0.15s ease; - font-size: 10px; - font-weight: bold; -} - -.add-marker-btn:hover { - background: var(--bg-hover); - border-color: var(--border-color); - color: var(--accent-timeline); -} - -.add-marker-btn.dragging { - background: var(--accent-timeline); - border-color: var(--accent-timeline); - color: var(--text-on-accent); - cursor: grabbing; - transform: scale(1.1); - box-shadow: 0 0 12px var(--accent-timeline); -} - -/* RAM Preview */ -.timeline-ram-preview { - display: flex; - align-items: center; - gap: 6px; -} - -.timeline-slot-toggle { - display: flex; - align-items: center; - margin-right: 4px; -} - -.ram-preview-progress { - width: 80px; - height: 6px; - background: var(--bg-primary); - border-radius: 3px; - overflow: hidden; -} - -.ram-preview-progress-bar { - height: 100%; - background: linear-gradient(90deg, var(--success), var(--success-light)); - border-radius: 3px; - transition: width 0.1s ease-out; -} - -/* View dropdown */ -.view-dropdown { - position: relative; -} - -.view-dropdown .btn { - display: flex; - align-items: center; -} - -.view-dropdown-menu { - position: absolute; - top: 100%; - left: 0; - margin-top: 4px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 4px 0; - min-width: 160px; - z-index: 1000; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); -} - -.view-dropdown-item { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - cursor: pointer; - font-size: 12px; - color: var(--text-primary); - transition: background 0.1s; -} - -.view-dropdown-item:hover { - background: var(--bg-tertiary); -} - -.view-check { - width: 14px; - font-size: 11px; - text-align: center; - color: transparent; - flex-shrink: 0; -} - -.view-check.checked { - color: var(--accent-color, #4a9eff); -} - -.btn-danger { - background: var(--danger); - color: var(--text-on-accent); -} - -.btn-danger:hover { - background: var(--red-300); -} - -/* RAM Preview cached range indicator (green = fully cached) */ -.ram-preview-indicator { - position: absolute; - top: 0; - height: 4px; - background: linear-gradient(90deg, var(--success), var(--success-light)); - opacity: 0.8; - pointer-events: none; - z-index: 90; -} - -/* RAM Preview progress text (during rendering) */ -.ram-preview-progress-text { - position: absolute; - top: 6px; - font-size: 10px; - font-weight: bold; - color: var(--success); - background: var(--bg-overlay); - padding: 2px 6px; - border-radius: 3px; - pointer-events: none; - z-index: 95; - white-space: nowrap; -} - -/* Playback cache indicator (green line for frames cached during playback) */ -.playback-cache-indicator { - position: absolute; - top: 0; - height: 3px; - background: var(--success); - opacity: 0.6; - pointer-events: none; - z-index: 89; -} - -/* Timeline export progress overlay */ -.timeline-export-overlay { - position: absolute; - top: 0; - height: 500px; - background: rgba(250, 204, 21, 0.4); - border-right: 3px solid rgba(250, 204, 21, 1); - pointer-events: none; - z-index: 200; -} - -/* Timeline export percentage */ -.timeline-export-text { - position: absolute; - top: 50px; - font-size: 24px; - font-weight: bold; - color: rgba(250, 204, 21, 0.9); - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.6); - pointer-events: none; - z-index: 201; - white-space: nowrap; -} - -/* Audio Waveform */ -.clip-waveform { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; -} - -.waveform-container { - position: relative; - width: 100%; - height: 100%; - display: flex; - align-items: center; -} - -.waveform-bar { - position: absolute; - bottom: 50%; - transform: translateY(50%); - background: rgba(34, 197, 94, 0.8); - border-radius: 1px; -} - -.timeline-clip.audio .waveform-bar { - background: rgba(255, 255, 255, 0.6); -} - -.waveform-canvas { - position: absolute; - top: 0; - left: 0; - pointer-events: none; -} - -/* Nested composition mixdown waveform - shown at bottom of clip */ -.clip-mixdown-waveform { - position: absolute; - bottom: 2px; - left: 0; - right: 0; - height: 30px; - overflow: hidden; - opacity: 0.7; - pointer-events: none; - z-index: 5; -} - -.clip-mixdown-waveform .waveform-canvas { - filter: hue-rotate(180deg); /* Different color from regular waveforms */ -} - -/* Nested composition mixdown generating indicator */ -.clip-mixdown-indicator { - position: absolute; - bottom: 4px; - left: 4px; - font-size: 9px; - color: var(--text-secondary); - background: rgba(0, 0, 0, 0.5); - padding: 2px 6px; - border-radius: 3px; - z-index: 6; -} - -/* Nested composition clip boundary markers */ -.nested-clip-boundaries { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - pointer-events: none; - z-index: 8; -} - -.nested-boundary-line { - position: absolute; - top: 0; - bottom: 0; - width: 1px; - background: rgba(255, 60, 60, 0.8); - box-shadow: 1px 0 0 rgba(255, 255, 255, 0.15); -} - -/* Track header resize cursor on shift hover */ -.track-header { - cursor: default; - transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; -} - -.track-header:hover { - background: var(--bg-hover); -} - -/* ========== MEDIA PANEL ========== */ -.media-panel { - display: flex; - flex-direction: column; - height: 100%; - background: var(--bg-secondary); - transition: background 0.15s ease, box-shadow 0.15s ease; - position: relative; -} - -.media-panel.drop-target { - background: rgba(74, 144, 226, 0.1); -} - -/* Drop overlay - visible when dragging files over the panel */ -.media-panel-drop-overlay { - position: absolute; - inset: 0; - background: rgba(74, 144, 226, 0.15); - border: 3px dashed var(--accent-color); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - z-index: 100; - pointer-events: none; - animation: dropPulse 1.5s ease-in-out infinite; -} - -@keyframes dropPulse { - 0%, 100% { background: rgba(74, 144, 226, 0.1); } - 50% { background: rgba(74, 144, 226, 0.25); } -} - -.drop-overlay-content { - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; - color: var(--accent-color); -} - -.drop-overlay-content svg { - animation: dropBounce 0.8s ease-in-out infinite; -} - -@keyframes dropBounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-10px); } -} - -.drop-overlay-content span { - font-size: 16px; - font-weight: 600; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); -} - -.media-panel-header { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; -} - -.media-panel-title { - font-weight: 600; - font-size: 13px; -} - -.media-panel-count { - font-size: 11px; - color: var(--text-secondary); - margin-right: auto; - white-space: nowrap; -} - -.media-panel-search { - position: relative; - display: flex; - align-items: center; - width: min(260px, 28vw); - min-width: 140px; - height: 24px; - color: var(--text-muted); - flex: 0 1 260px; -} - -.media-panel-search svg { - position: absolute; - left: 8px; - pointer-events: none; -} - -.media-panel-search input { - width: 100%; - height: 24px; - padding: 0 24px 0 26px; - border: 1px solid var(--border-color); - border-radius: 4px; - background: var(--bg-primary); - color: var(--text-primary); - font-size: 11px; - outline: none; -} - -.media-panel-search input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 1px rgba(45, 140, 235, 0.28); -} - -.media-panel-search-clear { - position: absolute; - right: 4px; - width: 17px; - height: 17px; - border: 0; - border-radius: 3px; - background: transparent; - color: var(--text-secondary); - font-size: 12px; - line-height: 17px; - cursor: pointer; -} - -.media-panel-search-clear:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.media-panel-actions { - display: flex; - align-items: center; - gap: 4px; -} - -.media-panel-actions .btn { - height: 24px; - display: inline-flex; - align-items: center; - box-sizing: border-box; -} - -.media-panel-content { - flex: 1; - overflow: auto; - display: flex; - flex-direction: column; -} - -.media-panel-view-transition-layer { - position: fixed; - overflow: hidden; - pointer-events: none; - z-index: 10000; - contain: layout paint; -} - -.media-panel-view-transition-clone { - position: absolute !important; - margin: 0 !important; - box-sizing: border-box; - overflow: hidden; - pointer-events: none !important; - user-select: none !important; - transform-origin: 0 0; - will-change: transform, opacity; -} - -.media-panel-view-transition-clone, -.media-panel-view-transition-clone * { - pointer-events: none !important; - transition: none !important; -} - -.media-panel-view-transition-hidden { - visibility: hidden !important; -} - -/* Table wrapper for horizontal scrolling */ -.media-panel-table-wrapper { - min-width: 650px; - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; -} - -/* Column headers */ -.media-column-headers { - display: flex; - align-items: center; - padding: 4px 0; - border-bottom: 1px solid var(--border-color); - background: var(--bg-tertiary); - position: sticky; - top: 0; - z-index: 10; - font-size: 10px; - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Column base styles */ -.media-col { - flex-shrink: 0; - padding: 0 6px; - font-size: 11px; - color: var(--text-secondary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Column widths */ -.media-col-name { - display: flex; - align-items: center; - gap: 4px; - overflow: hidden; - position: relative; - flex-shrink: 0; -} - -/* Resize handle for name column */ -.media-col-resize-handle { - position: absolute; - right: -1px; - top: 2px; - bottom: 2px; - width: 3px; - cursor: col-resize; - background: var(--border-color); - border-radius: 1px; - transition: background 0.15s; - z-index: 5; -} - -.media-col-resize-handle:hover, -.media-col-resize-handle:active { - background: var(--accent-color); -} - -/* Column header drag styles */ -.media-column-headers .media-col { - cursor: grab; - user-select: none; - transition: background 0.1s, opacity 0.1s; - position: relative; -} - -.media-column-headers .media-col:active { - cursor: grabbing; -} - -.media-column-headers .media-col.dragging { - opacity: 0.5; -} - -.media-column-headers .media-col.drag-over { - background: rgba(74, 144, 226, 0.3); -} - -.media-column-headers .media-col.sorted { - color: var(--text-primary); -} - -.media-sort-indicator { - font-size: 7px; - margin-left: 2px; - opacity: 0.7; - display: inline-block; - vertical-align: middle; -} - -.media-column-headers .media-col:hover { - color: var(--text-primary); -} - -/* Label color column */ -.media-col-label { - width: 30px; - min-width: 30px; - max-width: 30px; - display: flex; - align-items: center; - justify-content: center; - padding: 0 2px; - cursor: pointer; -} - -.media-label-dot { - width: 8px; - height: 8px; - border-radius: 2px; - display: block; - flex-shrink: 0; -} - -.media-label-dot:hover { - outline: 1px solid rgba(255, 255, 255, 0.3); -} - -/* Label color picker popup */ -.label-picker-backdrop { - position: fixed; - inset: 0; - z-index: 10001; -} - -.label-picker-popup { - background: var(--bg-secondary, #1e1e1e); - border: 1px solid var(--border-color, #3a3a3a); - border-radius: 4px; - padding: 4px; - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 2px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); -} - -.label-picker-swatch { - width: 16px; - height: 16px; - border-radius: 2px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.1s; -} - -.label-picker-swatch:hover { - transform: scale(1.2); - outline: 1px solid rgba(255, 255, 255, 0.5); -} - -.label-picker-swatch.none { - border: 1px solid var(--border-color, #555); -} - -.label-picker-x { - font-size: 10px; - line-height: 1; - color: var(--text-secondary); -} - -.media-col-duration { - width: 56px; - min-width: 56px; - text-align: right; -} - -.media-col-resolution { - width: 118px; - min-width: 118px; - text-align: right; -} - -.media-col-fps { - width: 40px; - min-width: 40px; - text-align: right; -} - -.media-col-container { - width: 72px; - min-width: 72px; - text-align: center; -} - -.media-col-codec { - width: 68px; - min-width: 68px; - text-align: center; -} - -.media-col-audio { - width: 44px; - min-width: 44px; - text-align: center; -} - -.media-col-bitrate { - width: 70px; - min-width: 70px; - text-align: right; -} - -.media-col-size { - width: 70px; - min-width: 70px; - text-align: right; - padding-right: 12px; -} - -.media-panel-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - color: var(--text-secondary); - text-align: center; - padding: 20px; -} - -.media-panel-empty .drop-icon { - margin-bottom: 12px; - opacity: 0.4; -} - -.media-panel-empty .drop-icon svg { - stroke: currentColor; -} - -.media-panel-empty .hint { - font-size: 12px; - margin-top: 8px; - opacity: 0.7; -} - -.media-item-list { - padding: 4px 0; - user-select: none; - flex: 1; - min-height: 0; - overflow: auto; - contain: layout paint; -} - -.media-classic-virtual-spacer { - flex: 0 0 auto; - pointer-events: none; -} - -/* View mode toggle buttons */ -.media-view-toggle { - padding: 2px 4px !important; - min-width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - opacity: 0.5; - box-sizing: border-box; - border: 1px solid var(--border-color); -} - -.media-view-toggle.active { - opacity: 1; - background: var(--bg-hover); -} - -.media-view-segment { - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border-color); - border-radius: 4px; - background: rgba(0, 0, 0, 0.12); -} - -.media-view-segment .media-view-toggle { - border: 0; - background: transparent; -} - -.media-view-segment .media-view-toggle.active { - background: var(--bg-hover); -} - -/* Grid View */ -.media-grid-wrapper { - flex: 1; - min-height: 0; - overflow: auto; - padding: 8px; - user-select: none; -} - -.media-grid-breadcrumb { - display: flex; - align-items: center; - gap: 2px; - padding: 4px 2px 8px; - font-size: 11px; - color: var(--text-muted); - flex-wrap: wrap; -} - -.media-grid-breadcrumb-sep { - color: var(--text-muted); - opacity: 0.4; - margin: 0 1px; -} - -.media-grid-breadcrumb-btn { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: 2px 4px; - border-radius: 3px; - font-size: 11px; - transition: background 0.1s, color 0.1s; -} - -.media-grid-breadcrumb-btn:hover { - background: rgba(255, 255, 255, 0.08); - color: var(--text-primary); -} - -.media-grid-breadcrumb-btn.active { - color: var(--text-primary); - cursor: default; -} - -.media-grid-breadcrumb-btn.active:hover { - background: none; -} - -.media-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 8px; -} - -.media-grid-item { - display: flex; - flex-direction: column; - border-radius: 4px; - cursor: pointer; - overflow: hidden; - border: 2px solid transparent; - transition: border-color 0.1s, background 0.1s; -} - -.media-grid-item:hover { - background: var(--bg-hover); -} - -.media-grid-item.selected { - border-color: var(--accent); - background: rgba(45, 140, 235, 0.15); -} - -.media-grid-thumb { - position: relative; - aspect-ratio: 16 / 9; - background: var(--bg-secondary); - overflow: hidden; - border-radius: 3px 3px 0 0; -} - -.media-grid-thumb img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.media-grid-thumb-placeholder { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.7; -} - -.media-grid-duration { - position: absolute; - bottom: 3px; - right: 3px; - background: rgba(0, 0, 0, 0.75); - color: var(--text-primary); - font-size: 10px; - padding: 1px 4px; - border-radius: 2px; - font-variant-numeric: tabular-nums; -} - -.media-grid-badge { - position: absolute; - top: 3px; - right: 3px; - background: rgba(255, 255, 255, 0.15); - color: var(--text-secondary); - font-size: 10px; - padding: 1px 5px; - border-radius: 8px; - font-variant-numeric: tabular-nums; - min-width: 16px; - text-align: center; -} - -.media-grid-name { - padding: 3px 6px; - font-size: 11px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-align: center; - color: var(--text-secondary); - max-width: 100%; - align-self: center; - width: fit-content; - border-radius: 3px; -} - -.media-grid-item.selected .media-grid-name { - color: var(--text-primary); - background: rgba(45, 140, 235, 0.25); -} - -.media-marquee { - position: absolute; - border: 1px solid rgba(45, 140, 235, 0.6); - background: rgba(45, 140, 235, 0.15); - pointer-events: none; - z-index: 5; -} - -/* Media Board View */ -.media-board-wrapper { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - overflow: hidden; - background: - linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px), - linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px), - var(--bg-primary); - background-size: 32px 32px; - background-position: - var(--media-board-grid-x, 0px) var(--media-board-grid-y, 0px), - var(--media-board-grid-x, 0px) var(--media-board-grid-y, 0px), - 0 0; -} - -.media-board-wrapper.board-interacting { - background: var(--bg-primary); -} - -.media-board-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - min-height: 34px; - padding: 5px 8px; - border-bottom: 1px solid var(--border-color); - background: var(--bg-tertiary); - flex-shrink: 0; -} - -.media-board-toolbar-title { - display: flex; - align-items: baseline; - gap: 8px; - min-width: 0; -} - -.media-board-toolbar-title span:first-child { - font-size: 12px; - font-weight: 600; - color: var(--text-primary); -} - -.media-board-toolbar-title span:last-child { - font-size: 11px; - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.media-board-toolbar-actions { - display: inline-flex; - align-items: center; - gap: 4px; - flex-shrink: 0; -} - -.media-board-canvas { - position: relative; - flex: 1; - min-height: 0; - overflow: hidden; - cursor: grab; - user-select: none; -} - -.media-board-canvas:active { - cursor: grabbing; -} - -.media-board-wrapper.board-interacting .media-board-canvas-inner { - will-change: transform; -} - -.media-board-wrapper.board-interacting .media-board-node, -.media-board-wrapper.board-interacting .media-board-group, -.media-board-wrapper.board-interacting .media-board-insert-gap { - box-shadow: none; - transition: none; -} - -.media-board-wrapper.board-interacting .media-board-node-body, -.media-board-wrapper.board-interacting .media-board-node-timeline-drag { - transition: none; -} - -.media-board-wrapper.board-interacting .media-board-node-thumb img { - pointer-events: none; -} - -.media-board-canvas-inner { - position: absolute; - left: 0; - top: 0; - width: 1px; - height: 1px; - transform-origin: 0 0; -} - -.media-board-overview-canvas { - position: absolute; - display: block; - pointer-events: none; - z-index: 4; -} - -.media-board-group { - position: absolute; - contain: layout paint style; - border: 1px solid rgba(255, 255, 255, 0.09); - border-radius: 6px; - background: rgba(255, 255, 255, 0.035); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); - pointer-events: auto; - transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease, border-color 0.1s, box-shadow 0.1s, opacity 0.2s ease, filter 0.2s ease; -} - -.media-board-group.folder-group { - cursor: grab; -} - -.media-board-group.root-group { - background: rgba(255, 255, 255, 0.025); - border-color: rgba(255, 255, 255, 0.12); -} - -.media-board-group.folder-group.depth-1 { - background: rgba(255, 255, 255, 0.04); -} - -.media-board-group.folder-group.depth-2, -.media-board-group.folder-group.depth-3 { - background: rgba(255, 255, 255, 0.05); -} - -.media-board-group.folder-group .media-board-group-header { - cursor: grab; -} - -.media-board-group.folder-group.drag-preview { - border-color: rgba(45, 140, 235, 0.45); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.06), - 0 12px 26px rgba(0, 0, 0, 0.24); - z-index: 15; -} - -.media-board-group.folder-group.selected { - border-color: var(--accent); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.06), - 0 0 0 2px rgba(45, 140, 235, 0.24), - 0 10px 22px rgba(0, 0, 0, 0.24); -} - -.media-board-group.folder-group.drag-source-preview { - z-index: 14; -} - -.media-board-group-header { - height: 42px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 0 16px; - border-bottom: 1px solid rgba(255, 255, 255, 0.075); - color: var(--text-secondary); - font-size: 12px; -} - -.media-board-group-header span:first-child { - color: var(--text-primary); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.media-board-group.folder-group .media-board-group-header span:first-child { - cursor: text; -} - -.media-board-group-header span:last-child { - min-width: 22px; - height: 18px; - border-radius: 9px; - display: inline-flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.08); - color: var(--text-secondary); - font-size: 10px; - font-variant-numeric: tabular-nums; -} - -.media-board-group-rename { - flex: 0 1 auto; - min-width: 4ch; - max-width: min(42ch, calc(100% - 34px)); - height: 24px; - padding: 2px 6px; - border: 1px solid var(--accent); - border-radius: 4px; - background: var(--bg-primary); - color: var(--text-primary); - font-size: 12px; - font-weight: 600; - outline: none; -} - -.media-board-insert-gap { - position: absolute; - contain: layout paint style; - border: 1px dashed rgba(45, 140, 235, 0.68); - border-radius: 6px; - background: rgba(45, 140, 235, 0.12); - box-shadow: inset 0 0 0 1px rgba(45, 140, 235, 0.1); - pointer-events: none; - transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease; - z-index: 3; -} - -.media-board-node { - position: absolute; - display: block; - contain: layout paint style; - overflow: hidden; - border: 1px solid var(--border-color); - border-top: 3px solid var(--border-color); - border-radius: 6px; - background: var(--bg-secondary); - box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22); - cursor: grab; - transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease, border-color 0.1s, box-shadow 0.1s, background 0.1s, opacity 0.2s ease, filter 0.2s ease; - pointer-events: auto; - z-index: 5; -} - -.media-board-node:hover { - background: var(--bg-hover); - border-color: rgba(255, 255, 255, 0.18); -} - -.media-board-node.drag-preview { - box-shadow: 0 14px 30px rgba(0, 0, 0, 0.34); - z-index: 20; -} - -.media-board-node.drag-source-preview { - z-index: 18; -} - -.media-board-node.selected { - border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(45, 140, 235, 0.3), 0 10px 22px rgba(0, 0, 0, 0.26); -} - -.media-board-node.no-file { - opacity: 0.55; -} - -.media-board-node.search-dimmed, -.media-board-group.search-dimmed { - opacity: 0.26; - filter: grayscale(1) saturate(0.2); -} - -.media-board-node.lod-compact { - box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18); -} - -.media-board-node.lod-compact .media-board-node-timeline-drag { - display: none; -} - -.media-board-node.lod-thumbnail-paused .media-board-node-placeholder { - opacity: 0.54; -} - -.media-board-node-thumb { - position: relative; - width: 100%; - height: 100%; - background: var(--bg-primary); - overflow: hidden; -} - -.media-board-node-thumb img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.media-board-node-placeholder { - height: 100%; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.74; -} - -.media-board-solid-preview { - width: 100%; - height: 100%; -} - -.media-board-text-preview { - height: 100%; - display: flex; - align-items: center; - padding: 10px; - background: rgba(255, 255, 255, 0.035); - font-size: 17px; - font-weight: 700; - line-height: 1.1; - overflow: hidden; - word-break: break-word; -} - -.media-board-duration, -.media-board-progress { - position: absolute; - right: 6px; - bottom: 5px; - padding: 1px 5px; - border-radius: 3px; - background: rgba(0, 0, 0, 0.72); - color: var(--text-primary); - font-size: 10px; - font-variant-numeric: tabular-nums; -} - -.media-board-progress { - left: 6px; - right: auto; - color: var(--accent); -} - -.media-board-node-timeline-drag { - position: absolute; - top: 5px; - right: 5px; - width: 22px; - height: 22px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: rgba(0, 0, 0, 0.58); - color: var(--text-primary); - cursor: grab; - opacity: 0; - transition: opacity 0.1s, background 0.1s; -} - -.media-board-node:hover .media-board-node-timeline-drag, -.media-board-node.selected .media-board-node-timeline-drag { - opacity: 1; -} - -.media-board-node-timeline-drag:hover { - background: rgba(45, 140, 235, 0.78); -} - -.media-board-node-body { - position: absolute; - left: 0; - right: 0; - bottom: 0; - min-height: 0; - padding: 18px 8px 8px; - background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.82)); - opacity: 0; - pointer-events: none; - transform: translateY(8px); - transition: opacity 0.12s ease, transform 0.12s ease; -} - -.media-board-node:hover .media-board-node-body, -.media-board-node.selected .media-board-node-body, -.media-board-node.drag-preview .media-board-node-body { - opacity: 1; - transform: translateY(0); -} - -.media-board-node-name { - color: var(--text-primary); - font-size: 12px; - font-weight: 500; - line-height: 1.2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.media-board-node-meta { - display: flex; - align-items: center; - gap: 5px; - margin-top: 5px; - color: var(--text-muted); - font-size: 10px; - line-height: 1; - overflow: hidden; -} - -.media-board-node-meta span { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.media-board-marquee { - position: absolute; - border: 1px solid rgba(45, 140, 235, 0.72); - background: rgba(45, 140, 235, 0.16); - pointer-events: none; - z-index: 50; -} - -.media-item { - display: flex; - align-items: center; - padding: 1px 0; - cursor: pointer; - transition: background 0.1s; - border-left: 2px solid transparent; - height: 20px; -} - -.media-item:hover { - background: var(--bg-hover); -} - -.media-item.selected { - background: rgba(45, 140, 235, 0.25); - border-left-color: var(--accent); -} - -.media-item.selected:hover { - background: rgba(45, 140, 235, 0.35); -} - -.media-item.no-file { - opacity: 0.5; - cursor: not-allowed; -} - -.media-item.no-file .media-col-name::after { - content: ' (reload required)'; - font-size: 10px; - color: var(--text-secondary); -} - -.media-item.importing { - opacity: 0.5; -} - -.media-item.importing .media-col-name::after { - content: ' (importing...)'; - font-size: 10px; - color: var(--accent); - animation: importPulse 1.5s ease-in-out infinite; -} - -@keyframes importPulse { - 0%, 100% { opacity: 0.5; } - 50% { opacity: 1; } -} - -.media-folder-arrow { - font-size: 8px; - color: var(--text-secondary); - flex-shrink: 0; - width: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: transform 0.15s; - user-select: none; -} - -.media-folder-arrow.expanded { - transform: rotate(90deg); -} - -.media-folder-arrow:hover { - color: var(--text-primary); -} - -.media-folder-icon { - font-size: 12px; - filter: grayscale(1) brightness(0.7); - line-height: 1; -} - -.media-item-icon { - flex-shrink: 0; - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -.media-item-name { - flex: 1; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding: 2px 4px; - border-radius: 3px; - cursor: default; - color: var(--text-primary); - min-width: 0; -} - -.media-item-name.editable { - cursor: text; -} - -.media-item-name.editable:hover { - background: rgba(255, 255, 255, 0.1); -} - -.media-item-rename { - background: var(--bg-primary); - border: 1px solid var(--accent); - border-radius: 2px; - padding: 2px 6px; - font-size: 12px; - color: var(--text-primary); - outline: none; - width: auto; - min-width: 30px; - max-width: 100%; - font-family: inherit; - field-sizing: content; -} - -/* Legacy - replaced by media-col system */ -.media-item-info { - font-size: 10px; - color: var(--text-secondary); - flex-shrink: 0; - display: none; -} - -.media-item-proxy-badge { - background: var(--success); - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - flex-shrink: 0; -} - -/* Full coverage badges (solid) */ -.media-item-transcript-badge { - background: var(--accent); - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - flex-shrink: 0; - cursor: pointer; -} - -.media-item-proxy-error { - background: var(--danger); - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - flex-shrink: 0; -} - -.media-item-transcript-badge:hover { - filter: brightness(1.2); -} - -.media-item-analysis-badge { - background: #e88a1a; - color: var(--text-inverse); - font-size: 9px; - font-weight: bold; - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - flex-shrink: 0; - cursor: pointer; -} - -.media-item-analysis-badge:hover { - filter: brightness(1.2); -} - -/* Partial coverage fill badges */ -.media-item-transcript-fill, -.media-item-analysis-fill { - margin-left: 4px; - flex-shrink: 0; - cursor: pointer; -} - -.media-item-transcript-fill:hover, -.media-item-analysis-fill:hover { - filter: brightness(1.2); -} - -.coverage-fill-badge { - position: relative; - width: 14px; - height: 14px; - border-radius: 3px; - overflow: hidden; - font-size: 9px; - font-weight: bold; - line-height: 14px; - text-align: center; - display: inline-block; -} - -.coverage-fill-badge .coverage-fill-bg { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; -} - -.coverage-fill-badge .coverage-fill-progress { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - align-items: flex-end; - justify-content: center; - overflow: hidden; - transition: height 0.3s ease-out; -} - -/* Transcript fill colors */ -.coverage-fill-badge.transcript-fill .coverage-fill-bg { - background: rgba(99, 102, 241, 0.2); - color: rgba(99, 102, 241, 0.4); -} - -.coverage-fill-badge.transcript-fill .coverage-fill-progress { - background: var(--accent); - color: var(--text-inverse); -} - -/* Analysis fill colors */ -.coverage-fill-badge.analysis-fill .coverage-fill-bg { - background: rgba(232, 138, 26, 0.2); - color: rgba(232, 138, 26, 0.4); -} - -.coverage-fill-badge.analysis-fill .coverage-fill-progress { - background: #e88a1a; - color: var(--text-inverse); -} - -.media-item-proxy-generating { - display: flex; - align-items: center; - gap: 4px; - margin-left: 4px; - flex-shrink: 0; -} - -.media-item-proxy-generating .proxy-percent { - font-size: 9px; - color: var(--warning-light); - font-weight: bold; -} - -.media-folder-children { - /* Children indentation handled by paddingLeft */ -} - -/* Drag target styling */ -.media-item.drag-target, -.media-grid-item.drag-target { - background: var(--accent) !important; - outline: 2px dashed var(--accent); - outline-offset: -2px; -} - -.media-item.dragging { - opacity: 0.5; -} - -/* Add dropdown */ -.add-dropdown-container { - position: relative; -} - -.add-dropdown-trigger.active { - background: var(--bg-hover); -} - -.add-dropdown-menu { - position: absolute; - top: 100%; - right: 0; - margin-top: 4px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); - min-width: 180px; - z-index: 1000; - overflow: visible; -} - -.add-dropdown-item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - font-size: 12px; - color: var(--text-primary); - cursor: pointer; - transition: background 0.1s; -} - -.add-dropdown-item:hover { - background: var(--bg-hover); -} - -.add-dropdown-icon { - width: 20px; - text-align: center; - font-size: 14px; -} - -.add-dropdown-hint { - margin-left: auto; - font-size: 10px; - color: var(--text-muted); - font-style: italic; -} - -.add-dropdown-separator { - height: 1px; - background: var(--border-color); - margin: 4px 0; -} - -.add-dropdown-item.has-submenu { - position: relative; -} - -.add-dropdown-submenu { - display: none; - position: absolute; - left: 100%; - top: -4px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 4px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); - min-width: 140px; - padding: 4px 0; -} - -.add-dropdown-item.has-submenu:hover > .add-dropdown-submenu { - z-index: 10001; -} - -.add-dropdown-item.has-submenu:hover > .add-dropdown-submenu { - display: block; -} - -/* Context Menu */ -.media-context-menu { - position: fixed; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); - min-width: 160px; - z-index: 10000; - overflow: visible; -} - -.media-context-menu button { - display: block; - width: 100%; - padding: 8px 12px; - text-align: left; - background: none; - border: none; - color: var(--text-primary); - font-size: 12px; - cursor: pointer; - transition: background 0.1s; -} - -.media-context-menu button:hover { - background: var(--bg-hover); -} - -.media-context-menu button.danger { - color: var(--danger); -} - -.media-context-menu button.danger:hover { - background: rgba(255, 68, 68, 0.15); -} - -.context-menu-divider { - height: 1px; - background: var(--border-color); - margin: 4px 0; -} - -/* ========== PRECISION SLIDER ========== */ -.precision-slider { - flex: 1; - height: 20px; - cursor: ew-resize; - display: flex; - align-items: center; -} - -.precision-slider-track { - width: 100%; - height: 4px; - background: var(--bg-primary); - border-radius: 2px; - position: relative; -} - -.precision-slider-fill { - position: absolute; - top: 0; - left: 0; - height: 100%; - background: var(--accent); - border-radius: 2px; - pointer-events: none; -} - -.precision-slider-thumb { - position: absolute; - top: 50%; - width: 12px; - height: 12px; - background: var(--accent); - border-radius: 50%; - transform: translate(-50%, -50%); - pointer-events: none; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -} - -.precision-slider:hover .precision-slider-thumb { - box-shadow: 0 0 0 3px rgba(45, 140, 235, 0.3); -} - -.precision-slider:active { - cursor: grabbing; -} - -.precision-slider:active .precision-slider-thumb { - background: var(--accent-hover); - box-shadow: 0 0 0 4px rgba(45, 140, 235, 0.4); -} - -/* ========== DRAGGABLE NUMBER ========== */ -.draggable-number { - display: inline-block; - padding: 3px 6px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 3px; - color: #5b8abf; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-variant-numeric: tabular-nums; - font-size: 11px; - cursor: ew-resize; - user-select: none; - min-width: 50px; - text-align: right; - transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease; -} - -.draggable-number-anchor { - position: relative; - display: inline-flex; - align-items: center; -} - -/* When used as primary input in control-row (replacing slider + value) */ -.control-row > .draggable-number, -.control-row > .draggable-number-anchor { - flex: 1; -} - -.control-row > .draggable-number-anchor { - justify-content: flex-end; -} - -.control-row > .draggable-number-anchor > .draggable-number, -.control-row > .draggable-number-anchor > .draggable-number-input { - text-align: right; -} - -.draggable-number:hover { - border-color: #2d5a8e; - background: var(--bg-secondary); - color: #7aabe0; -} - -.draggable-number:active { - color: #9cc4f0; - cursor: grabbing; -} - -.draggable-number.editing, -.draggable-number-input { - cursor: text; - user-select: text; -} - -.draggable-number-input { - outline: none; - box-sizing: border-box; - text-align: right; - color: #d7e7fb; - background: var(--bg-secondary); - border-color: #2d5a8e; -} - -.draggable-number-input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 1px rgba(45, 140, 235, 0.35); -} - -.draggable-number-bounds-popover { - position: fixed; - z-index: 40; - min-width: 160px; - padding: 8px; - background: rgba(20, 24, 30, 0.98); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35); -} - -.draggable-number-bounds-title { - margin-bottom: 6px; - color: var(--text-primary); - font-size: 10px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.draggable-number-bounds-row { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 6px; -} - -.draggable-number-bounds-row label { - width: 46px; - color: var(--text-secondary); - font-size: 11px; -} - -.draggable-number-bounds-row input { - flex: 1; - min-width: 0; - padding: 3px 6px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-primary); - font-size: 11px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; -} - -.draggable-number-bounds-row input:focus { - outline: none; - border-color: var(--accent); -} - -.draggable-number-bounds-actions { - display: flex; - justify-content: flex-end; - gap: 6px; - margin-top: 8px; -} - -/* ========== KEYFRAME TOGGLE ========== */ -.keyframe-toggle { - width: 18px; - height: 18px; - padding: 2px; - background: transparent; - border: 1px solid var(--border-color); - border-radius: 3px; - cursor: pointer; - color: var(--text-secondary); - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.15s ease; -} - -.keyframe-toggle:hover { - border-color: var(--accent); - color: var(--accent); -} - -.keyframe-toggle.has-keyframes { - color: var(--amber-600); - border-color: var(--amber-600); -} - -.keyframe-toggle.has-keyframes:hover { - color: var(--warning-light); - border-color: var(--warning-light); -} - -.keyframe-toggle.recording { - background: transparent; - color: var(--amber-600); - border-color: var(--amber-600); - animation: keyframe-recording-activate 700ms ease-out; -} - -.keyframe-toggle.recording:hover { - background: transparent; - color: var(--warning-light); - border-color: var(--warning-light); -} - -.keyframe-toggle.recording-feedback { - animation: keyframe-recording-pulse 900ms ease-in-out infinite; -} - -@keyframes keyframe-recording-activate { - 0%, 45% { - background: var(--amber-600); - color: var(--text-inverse); - border-color: var(--amber-600); - box-shadow: 0 0 6px var(--amber-600); - } - 100% { - background: transparent; - color: var(--amber-600); - border-color: var(--amber-600); - box-shadow: none; - } -} - -@keyframes keyframe-recording-pulse { - 0%, 100% { - background: transparent; - border-color: var(--amber-600); - color: var(--amber-600); - opacity: 1; - filter: none; - box-shadow: none; - } - 50% { - background: rgba(245, 158, 11, 0.22); - border-color: var(--warning-light); - color: var(--warning-light); - opacity: 1; - filter: drop-shadow(0 0 4px rgba(245, 158, 11, 0.85)); - box-shadow: 0 0 8px rgba(245, 158, 11, 0.45); - } -} - -.keyframe-toggle-placeholder { - width: 18px; - height: 18px; - flex-shrink: 0; -} - -/* ========== KEYFRAME DIAMONDS (Timeline) ========== */ -.keyframe-diamond { - position: absolute; - width: 8px; - height: 8px; - background: var(--amber-600); - color: var(--amber-600); - /* Center diamond horizontally on keyframe time position */ - transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%); - top: 50%; - cursor: pointer; - z-index: 5; - /* Only transition visual properties, not position (left) - for smooth clip dragging */ - transition: background 0.1s ease, box-shadow 0.1s ease, filter 700ms ease, transform 0.1s ease; -} - -.keyframe-diamond:hover { - background: var(--warning-light); - color: var(--warning-light); - box-shadow: 0 0 6px var(--amber-600); - transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%) scale(1.2); -} - -.keyframe-diamond.selected { - background: var(--red-500); - color: var(--red-500); - box-shadow: 0 0 8px var(--red-500); -} - -.keyframe-diamond.state-change { - background: var(--cyan-500); - color: var(--cyan-500); - box-shadow: 0 0 5px rgba(6, 182, 212, 0.45); -} - -.keyframe-diamond.state-change:hover, -.keyframe-diamond.state-change.selected { - background: #36b8ff; - box-shadow: 0 0 8px rgba(54, 184, 255, 0.75); -} - -.keyframe-diamond.dragging { - background: var(--cyan-500); - color: var(--cyan-500); - box-shadow: 0 0 10px var(--cyan-500); - transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%) scale(1.3); -} - -.keyframe-diamond.row-highlighted { - filter: drop-shadow(0 0 5px rgba(245, 158, 11, 0.85)) brightness(1.35) saturate(1.15); - transition: background 0.1s ease, box-shadow 0.1s ease, filter 120ms ease, transform 0.1s ease; -} - -/* Keyframe easing type visual styles */ -/* Linear: Diamond shape (default) */ -.keyframe-diamond.easing-linear { - border-radius: 0; -} - -/* Ease-in: Left half rounded (slow start) */ -.keyframe-diamond.easing-ease-in { - border-radius: 50% 0 0 50%; - background: linear-gradient(135deg, var(--purple-light) 0%, var(--amber-600) 100%); -} - -.keyframe-diamond.easing-ease-in:hover { - background: linear-gradient(135deg, var(--purple-300) 0%, var(--warning-light) 100%); - color: var(--warning-light); -} - -.keyframe-diamond.easing-ease-in.selected { - background: linear-gradient(135deg, var(--red-500) 0%, var(--danger-light) 100%); - color: var(--red-500); -} - -/* Ease-out: Right half rounded (slow end) */ -.keyframe-diamond.easing-ease-out { - border-radius: 0 50% 50% 0; - background: linear-gradient(135deg, var(--amber-600) 0%, var(--cyan-500) 100%); -} - -.keyframe-diamond.easing-ease-out:hover { - background: linear-gradient(135deg, var(--warning-light) 0%, var(--cyan-400) 100%); - color: var(--warning-light); -} - -.keyframe-diamond.easing-ease-out.selected { - background: linear-gradient(135deg, var(--danger-light) 0%, var(--red-500) 100%); - color: var(--red-500); -} - -/* Ease-in-out: Fully rounded (circle/pill) */ -.keyframe-diamond.easing-ease-in-out { - border-radius: 50%; - background: linear-gradient(135deg, var(--purple-light) 0%, var(--cyan-500) 100%); -} - -.keyframe-diamond.easing-ease-in-out:hover { - background: linear-gradient(135deg, var(--purple-300) 0%, var(--cyan-400) 100%); - color: var(--cyan-400); -} - -.keyframe-diamond.easing-ease-in-out.selected { - background: linear-gradient(135deg, var(--red-500) 0%, var(--danger-light) 100%); - color: var(--red-500); -} - -.keyframe-diamond.state-change, -.keyframe-diamond.state-change.easing-linear, -.keyframe-diamond.state-change.easing-ease-in, -.keyframe-diamond.state-change.easing-ease-out, -.keyframe-diamond.state-change.easing-ease-in-out { - background: var(--cyan-500); -} - -.keyframe-diamond.state-change:hover, -.keyframe-diamond.state-change.selected { - background: #36b8ff; - color: #36b8ff; -} - -.keyframe-rotation-path-label { - position: absolute; - left: 10px; - top: -5px; - display: block; - transform: rotate(-45deg); - transform-origin: center; - color: currentColor; - font-size: 6px; - font-weight: 700; - line-height: 1; - letter-spacing: 0; - opacity: 0.78; - pointer-events: none; - text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); - transition: opacity 0.1s ease; -} - -.keyframe-diamond:hover .keyframe-rotation-path-label { - opacity: 1; -} - -/* Keyframe context menu */ -.keyframe-context-menu { - background: var(--bg-tertiary); - border: 1px solid var(--gray-450); - border-radius: 6px; - padding: 4px 0; - min-width: 140px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); -} - -.keyframe-context-menu .context-menu-title { - padding: 6px 12px; - font-size: 11px; - color: var(--gray-200); - border-bottom: 1px solid var(--gray-450); - margin-bottom: 4px; -} - -.keyframe-context-menu .context-menu-subtitle { - padding: 7px 12px 4px; - margin-top: 4px; - border-top: 1px solid var(--gray-450); - font-size: 10px; - color: var(--text-secondary); - text-transform: uppercase; -} - -.keyframe-context-menu .context-menu-item { - padding: 6px 12px; - cursor: pointer; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 12px; - color: var(--text-primary); -} - -.keyframe-context-menu .context-menu-item:hover { - background: var(--bg-active); -} - -.keyframe-context-menu .context-menu-item.active { - color: var(--amber-600); -} - -.keyframe-context-menu .context-menu-item .checkmark { - color: var(--success); - font-size: 14px; -} - -/* ========== EXPANDABLE CLIP PROPERTIES (Timeline) ========== */ -.clip-expand-arrow { - cursor: pointer; - transition: transform 0.2s ease; - color: var(--text-secondary); - font-size: 10px; - margin-right: 4px; -} - -.clip-expand-arrow:hover { - color: var(--text-primary); -} - -.clip-expand-arrow.expanded { - transform: rotate(90deg); -} - -.clip-expand-arrow.has-keyframes { - color: var(--amber-600); -} - -.timeline-clip.has-keyframes .clip-content { - border-left: 2px solid var(--amber-600); -} - -.timeline-clip.expanded { - overflow: visible; - z-index: 10; -} - -.clip-property-rows { - position: absolute; - left: 0; - right: 0; - top: 100%; - background: rgba(0, 0, 0, 0.85); - border: 1px solid var(--border-color); - border-top: none; - border-radius: 0 0 4px 4px; - z-index: 3; - overflow: hidden; -} - -.property-group { - border-bottom: 1px solid var(--border-color); -} - -.property-group:last-child { - border-bottom: none; -} - -.property-group-header { - display: flex; - align-items: center; - height: 18px; - padding: 0 8px; - margin: 0; - box-sizing: border-box; - cursor: pointer; - background: var(--bg-tertiary); - font-size: 10px; - color: var(--text-secondary); - user-select: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); -} - -.property-group-header:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.property-group-arrow { - margin-right: 4px; - transition: transform 0.15s ease; -} - -.property-group-arrow.expanded { - transform: rotate(90deg); -} - -.property-row { - display: flex; - align-items: center; - height: 18px; - padding: 0 8px 0 20px; - background: var(--bg-secondary); - border-bottom: 1px solid rgba(255, 255, 255, 0.05); -} - -.property-row:last-child { - border-bottom: none; -} - -.property-label { - width: 50px; - font-size: 9px; - color: var(--text-secondary); - text-transform: uppercase; - flex-shrink: 0; -} - -.keyframe-track { - flex: 1; - height: 100%; - position: relative; -} - -/* In track-level keyframe rows, keyframe-track should fill the full width */ -.keyframe-track-row .keyframe-track { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; -} - -.keyframe-track-line { - position: absolute; - top: 50%; - left: 0; - right: 0; - height: 1px; - background: rgba(255, 255, 255, 0.1); -} - -/* ========== TRACK-BASED KEYFRAME EXPANSION ========== */ -.track-header-top { - display: flex; - align-items: center; - justify-content: flex-start; - padding: 0 8px; - padding-right: 30px; - margin: 0; - box-sizing: border-box; - position: relative; - width: 100%; -} - -.track-header-main { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} - -.track-expand-arrow { - cursor: pointer; - transition: transform 0.2s ease; - color: var(--text-secondary); - font-size: 10px; - flex-shrink: 0; -} - -.track-expand-arrow:hover { - color: var(--text-primary); -} - -.track-expand-arrow.expanded { - transform: rotate(90deg); -} - -.track-expand-arrow.has-keyframes { - color: var(--amber-600); -} - -.track-header.expanded { - flex-direction: column; - align-items: stretch; - padding: 0; -} - -.track-header.expanded .track-header-top { - flex-shrink: 0; -} - -.track-clip-row { - position: relative; - width: 100%; - flex-shrink: 0; - margin: 0; - padding: 0; - box-sizing: border-box; - overflow: hidden; -} - -/* Property labels in track header (left column) */ -.track-property-labels { - background: rgba(0, 0, 0, 0.4); - border-top: 1px solid var(--border-color); - width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; -} - -.property-label-row { - height: 18px; - display: flex; - align-items: center; - padding: 0 8px; - box-sizing: border-box; - font-size: 9px; - color: var(--text-secondary); - text-transform: uppercase; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); -} - -.property-label-row.sub { - padding-left: 20px; -} - -.property-label-row.flat { - padding-left: 8px; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - display: flex; - align-items: center; - gap: 4px; -} - -.property-label-row.flat .property-label { - flex-shrink: 0; - min-width: 45px; -} - -.property-label-row.flat.dragging { - background: rgba(45, 140, 235, 0.1); -} - -.property-label-row.flat.keyframe-row-highlighted { - background: rgba(245, 158, 11, 0.12); - color: var(--text-primary); - transition: background 120ms ease, color 120ms ease; -} - -.property-label-row.flat:not(.keyframe-row-highlighted) { - transition: background 700ms ease, color 700ms ease; -} - -.property-keyframe-controls { - display: flex; - align-items: center; - gap: 1px; - margin-left: auto; -} - -.kf-nav-btn, .kf-add-btn { - background: none; - border: none; - color: var(--text-secondary); - font-size: 8px; - padding: 2px 3px; - cursor: pointer; - opacity: 0.6; - transition: opacity 0.1s; -} - -.kf-nav-btn:hover, .kf-add-btn:hover { - opacity: 1; - color: var(--accent-timeline); -} - -.kf-nav-btn.disabled { - opacity: 0.2; - cursor: default; -} - -.kf-add-btn { - font-size: 10px; - color: var(--text-secondary); -} - -.kf-add-btn.has-keyframe { - color: var(--accent-timeline); - opacity: 1; -} - -.property-value { - font-size: 9px; - color: var(--accent-timeline); - font-family: monospace; - min-width: 40px; - text-align: right; - cursor: ns-resize; - padding: 0 4px; - border-radius: 2px; - user-select: none; -} - -.property-value:hover { - background: rgba(45, 140, 235, 0.1); -} - -.property-label-group { - border-bottom: 1px solid var(--border-color); - box-sizing: border-box; - margin: 0; - padding: 0; -} - -.property-label-group:last-child { - border-bottom: none; -} - -/* Keyframe tracks in timeline area (right column) */ -.track-property-tracks { - background: rgba(0, 0, 0, 0.4); - border-top: 1px solid var(--border-color); - position: relative; - width: 100%; - box-sizing: border-box; - /* Align with left column */ - margin-top: 1px; - padding: 0; -} - -.keyframe-track-row { - height: 18px; - position: relative; - box-sizing: border-box; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - /* Ensure track row starts at left edge */ - margin: 0; - padding: 0; -} - -.keyframe-track-row.group-header { - background: var(--bg-tertiary); -} - -.keyframe-track-row.flat { - border-bottom: 1px solid rgba(255, 255, 255, 0.05); -} - -.keyframe-track-group { - border-bottom: 1px solid var(--border-color); - box-sizing: border-box; - margin: 0; - padding: 0; -} - -.keyframe-track-group:last-child { - border-bottom: none; -} - -.track-lane.expanded { - display: flex; - flex-direction: column; - align-items: stretch; - overflow: visible; -} - -/* ========== CURVE EDITOR ========== */ -.curve-editor-svg { - display: block; - background: rgba(0, 0, 0, 0.3); - border-top: 1px solid var(--border-color); -} - -.curve-editor-grid { - stroke: var(--border-color); - stroke-width: 0.5; - opacity: 0.3; -} - -.curve-editor-grid-major { - stroke: var(--border-color); - stroke-width: 1; - opacity: 0.5; -} - -.curve-editor-curve { - fill: none; - stroke: var(--amber-600); - stroke-width: 2; -} - -.curve-editor-curve.step { - stroke: var(--cyan-500); - stroke-linejoin: miter; -} - -.curve-editor-keyframe { - fill: var(--amber-600); - stroke: var(--bg-primary); - stroke-width: 2; - cursor: pointer; - transition: r 0.1s ease; -} - -.curve-editor-keyframe:hover { - r: 6; -} - -.curve-editor-keyframe.selected { - fill: var(--red-500); - stroke: var(--gray-0); -} - -.curve-editor-keyframe.state-change { - fill: var(--cyan-500); -} - -.curve-editor-keyframe.state-change.selected { - fill: #36b8ff; -} - -.curve-editor-handle { - fill: var(--cyan-500); - stroke: var(--bg-primary); - stroke-width: 1; - cursor: pointer; -} - -.curve-editor-handle:hover { - fill: var(--cyan-400); -} - -.curve-editor-handle-line { - stroke: var(--cyan-500); - stroke-width: 1; - opacity: 0.6; -} - -.curve-editor-value-label { - font-size: 9px; - fill: var(--text-secondary); -} - -/* Curve editor header (left side Y-axis) */ -.curve-editor-header { - position: relative; - display: flex; - flex-direction: column; - justify-content: space-between; - background: rgba(0, 0, 0, 0.3); - border-top: 1px solid var(--border-color); -} - -.curve-editor-close-btn { - position: absolute; - top: 2px; - right: 2px; - width: 16px; - height: 16px; - padding: 0; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 14px; - line-height: 1; - cursor: pointer; - opacity: 0.6; - transition: opacity 0.1s; -} - -.curve-editor-close-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.curve-editor-y-axis { - position: relative; - width: 100%; - height: 100%; -} - -.curve-editor-tick { - position: absolute; - right: 4px; - transform: translateY(-50%); -} - -.curve-editor-tick-label { - font-size: 9px; - color: var(--text-secondary); - font-family: monospace; -} - -/* Property row when curve is expanded */ -.property-label-row.flat.curve-expanded { - background: rgba(245, 158, 11, 0.1); - border-left: 2px solid var(--amber-600); -} - -/* Keyframe track row when curve is expanded */ -.keyframe-track-row.flat.curve-expanded { - height: auto; - flex-direction: column; -} - -.keyframe-track-row.flat.curve-expanded .keyframe-track { - position: relative; - height: 18px; - flex-shrink: 0; -} - -/* ========== PREVIEW EDIT MODE ========== */ -.preview-container { - position: relative; - overflow: hidden; -} - -.preview-controls { - position: absolute; - top: 8px; - left: 8px; - display: flex; - align-items: center; - gap: 8px; - z-index: 20; -} - -.preview-edit-btn { - padding: 4px 10px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - transition: all 0.15s; - backdrop-filter: blur(4px); -} - -.preview-edit-btn:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); - border-color: var(--gray-300); -} - -.preview-edit-btn:disabled { - cursor: not-allowed; - opacity: 0.55; -} - -.preview-edit-btn.active { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); -} - -.preview-scene-toggle-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 26px; - height: 24px; - padding: 0; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - cursor: pointer; - transition: all 0.15s; - backdrop-filter: blur(4px); -} - -.preview-scene-toggle-btn:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); - border-color: var(--gray-300); -} - -.preview-scene-toggle-btn.active { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); -} - -.preview-zoom-label { - font-size: 11px; - color: var(--text-secondary); - background: var(--preview-overlay-bg); - padding: 4px 8px; - border-radius: 4px; - font-family: monospace; - backdrop-filter: blur(4px); -} - -.preview-reset-btn { - padding: 4px 8px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - transition: all 0.15s; - backdrop-filter: blur(4px); -} - -.preview-reset-btn:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); -} - -/* Preview Composition Selector */ -.preview-comp-selector { - position: absolute; - top: 8px; - right: 8px; - display: flex; - align-items: center; - gap: 4px; - z-index: 20; -} - -.preview-comp-dropdown-wrapper { - position: relative; -} - -.preview-comp-dropdown-btn { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - transition: all 0.15s; - backdrop-filter: blur(4px); -} - -.preview-comp-dropdown-btn:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); - border-color: var(--gray-300); -} - -.preview-comp-name { - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.preview-comp-arrow { - font-size: 8px; - opacity: 0.7; -} - -.preview-comp-dropdown { - position: absolute; - top: 100%; - left: 0; - margin-top: 4px; - min-width: 160px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - padding: 4px 0; - z-index: 100; - max-height: min(70vh, 420px); - overflow-y: auto; -} - -.preview-comp-option { - display: block; - width: 100%; - padding: 6px 12px; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 11px; - text-align: left; - cursor: pointer; - transition: all 0.15s; -} - -.preview-comp-option:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.preview-comp-option.active { - color: var(--accent); - background: rgba(45, 140, 235, 0.1); -} - -.preview-comp-option.layer-option { - padding-left: 24px; - font-size: 10px; -} - -.preview-comp-group-label { - padding: 6px 12px 4px; - color: var(--text-muted); - font-size: 10px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.preview-comp-separator { - height: 1px; - background: var(--border-color); - margin: 4px 0; -} - -.preview-add-btn { - padding: 4px 8px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: all 0.15s; - line-height: 1; - backdrop-filter: blur(4px); -} - -.preview-add-btn:hover { - background: var(--accent); - border-color: var(--accent); - color: var(--text-inverse); -} - -.preview-close-btn { - padding: 4px 8px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: all 0.15s; - line-height: 1; - backdrop-filter: blur(4px); -} - -.preview-close-btn:hover { - background: var(--red-700); - border-color: var(--red-700); - color: var(--text-on-accent); -} - -.preview-overlay { - position: absolute; - top: 0; - left: 0; - pointer-events: auto; - z-index: 10; -} - -.preview-edit-hint { - position: absolute; - bottom: 8px; - left: 50%; - transform: translateX(-50%); - font-size: 10px; - color: var(--text-secondary); - background: var(--bg-overlay); - padding: 4px 12px; - border-radius: 4px; - white-space: nowrap; - z-index: 20; - pointer-events: none; -} - -.preview-splat-progress-overlay { - position: absolute; - left: 50%; - bottom: 42px; - width: min(460px, calc(100% - 32px)); - transform: translateX(-50%); - padding: 10px 12px; - color: var(--text-primary); - background: rgba(10, 12, 16, 0.86); - border: 1px solid rgba(255, 255, 255, 0.14); - border-radius: 6px; - box-shadow: 0 10px 32px rgba(0, 0, 0, 0.32); - backdrop-filter: blur(8px); - z-index: 24; - pointer-events: none; -} - -.preview-splat-progress-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - font-size: 12px; - font-weight: 600; - line-height: 1.3; -} - -.preview-splat-progress-name { - margin-top: 3px; - color: var(--text-secondary); - font-size: 11px; - line-height: 1.35; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.preview-splat-progress-track { - height: 5px; - margin-top: 8px; - overflow: hidden; - background: rgba(255, 255, 255, 0.14); - border-radius: 999px; -} - -.preview-splat-progress-fill { - height: 100%; - width: 0; - background: var(--accent); - border-radius: inherit; - transition: width 0.16s ease-out; -} - -.preview-splat-progress-overlay.error .preview-splat-progress-fill { - background: var(--danger); -} - -.preview-scene-object-overlay { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - z-index: 14; - pointer-events: none; -} - -.preview-scene-object-handle { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - padding: 0; - transform: translate(-50%, -50%); - color: #f6f8fb; - background: rgba(12, 15, 20, 0.82); - border: 1px solid rgba(255, 255, 255, 0.38); - border-radius: 50%; - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42); - cursor: pointer; - font-size: 10px; - font-weight: 700; - line-height: 1; - pointer-events: auto; - user-select: none; -} - -.preview-scene-object-handle:hover, -.preview-scene-object-handle.selected { - border-color: #ffffff; - background: #2563eb; -} - -.preview-scene-object-handle.kind-effector { - background: #7c2d12; -} - -.preview-scene-object-handle.kind-splat { - background: #166534; -} - -.preview-scene-object-handle.kind-camera { - background: #0f766e; -} - -.preview-scene-object-handle.kind-model, -.preview-scene-object-handle.kind-plane { - background: #581c87; -} - -.preview-camera-wireframe { - position: absolute; - inset: 0; - overflow: visible; - pointer-events: none; -} - -.preview-camera-wireframe-line { - fill: none; - stroke: rgba(20, 184, 166, 0.78); - stroke-width: 1.5; - stroke-linecap: round; - stroke-linejoin: round; - vector-effect: non-scaling-stroke; - filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.95)); -} - -.preview-camera-wireframe-line.role-body { - stroke: rgba(45, 212, 191, 0.9); - stroke-width: 1.9; -} - -.preview-camera-wireframe-line.role-direction { - stroke: rgba(96, 165, 250, 0.92); - stroke-width: 2.2; -} - -.preview-camera-wireframe-line.selected { - stroke-opacity: 1; - filter: - drop-shadow(0 1px 1px rgba(0, 0, 0, 0.95)) - drop-shadow(0 0 5px rgba(45, 212, 191, 0.58)); -} - -.preview-scene-world-grid { - position: absolute; - inset: 0; - overflow: visible; - pointer-events: none; -} - -.preview-scene-world-grid-line { - fill: none; - stroke: rgba(148, 163, 184, 0.22); - stroke-width: 1; - stroke-linecap: round; - stroke-linejoin: round; - vector-effect: non-scaling-stroke; - filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.7)); -} - -.preview-scene-world-grid-line.major { - stroke: rgba(148, 163, 184, 0.36); -} - -.preview-scene-world-grid-line.axis-x { - stroke: rgba(239, 68, 68, 0.58); - stroke-width: 1.6; -} - -.preview-scene-world-grid-line.axis-y { - stroke: rgba(34, 197, 94, 0.58); - stroke-width: 1.6; -} - -.preview-scene-world-grid-line.axis-z { - stroke: rgba(59, 130, 246, 0.58); - stroke-width: 1.6; -} - -.preview-scene-object-handle.selected { - background: #2563eb; - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.92), 0 8px 20px rgba(0, 0, 0, 0.45); -} - -.preview-scene-object-handle.gizmo-center { - width: 28px; - height: 28px; - z-index: 5; - color: #f8fafc; - background: rgba(8, 11, 16, 0.18); - border: 2px solid rgba(248, 250, 252, 0.88); - box-shadow: - 0 0 0 1px rgba(8, 11, 16, 0.86), - 0 0 14px rgba(59, 130, 246, 0.28), - 0 8px 20px rgba(0, 0, 0, 0.52); -} - -.preview-scene-object-handle.gizmo-center.center-draggable { - cursor: grab; -} - -.preview-scene-object-handle.gizmo-center.center-draggable:active { - cursor: grabbing; -} - -.preview-scene-object-handle.gizmo-center span { - display: block; - width: 10px; - height: 10px; - color: transparent; - background: #2563eb; - border-radius: 999px; - font-size: 0; -} - -.preview-scene-object-handle.gizmo-center.mode-scale span { - border-radius: 2px; -} - -.preview-scene-object-handle.gizmo-center.mode-rotate span { - width: 13px; - height: 13px; - background: transparent; - border: 2px solid #2563eb; -} - -.preview-scene-object-context-menu { - position: absolute; - z-index: 30; - min-width: 154px; - padding: 4px; - background: rgba(12, 15, 20, 0.96); - border: 1px solid rgba(255, 255, 255, 0.16); - border-radius: 6px; - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45); - pointer-events: auto; -} - -.preview-scene-object-context-menu button { - width: 100%; - height: 28px; - padding: 0 10px; - color: #f8fafc; - background: transparent; - border: 0; - border-radius: 4px; - font-size: 12px; - font-weight: 600; - text-align: left; - cursor: pointer; -} - -.preview-scene-object-context-menu button:hover { - background: rgba(59, 130, 246, 0.28); -} - -.preview-scene-gizmo-axis-layer { - position: absolute; - inset: 0; - pointer-events: none; -} - -.preview-scene-gizmo-axis { - position: absolute; - height: 34px; - margin: -17px 0 0 0; - padding: 0; - background: transparent; - border: 0; - cursor: grab; - pointer-events: auto; - transform-origin: 0 50%; -} - -.preview-scene-gizmo-axis:active { - cursor: grabbing; -} - -.preview-scene-gizmo-axis-line { - position: absolute; - left: 0; - right: 0; - top: 50%; - height: 5px; - border-radius: 999px; - background: currentColor; - box-shadow: - 0 0 0 1px rgba(0, 0, 0, 0.78), - 0 0 9px color-mix(in srgb, currentColor 58%, transparent); - transform: translateY(-50%); -} - -.preview-scene-gizmo-axis-line, -.preview-scene-gizmo-end, -.preview-scene-gizmo-label, -.preview-scene-gizmo-rotate-stroke { - opacity: 0; -} - -.preview-scene-gizmo-axis.mode-rotate .preview-scene-gizmo-axis-line { - background: repeating-linear-gradient( - 90deg, - currentColor 0, - currentColor 7px, - transparent 7px, - transparent 12px - ); -} - -.preview-scene-gizmo-axis.mode-scale .preview-scene-gizmo-axis-line { - height: 4px; -} - -.preview-scene-gizmo-end { - position: absolute; - right: -7px; - top: 50%; - width: 14px; - height: 14px; - background: currentColor; - border: 2px solid rgba(8, 11, 16, 0.92); - border-radius: 2px; - box-shadow: - 0 1px 4px rgba(0, 0, 0, 0.55), - 0 0 8px color-mix(in srgb, currentColor 52%, transparent); - transform: translateY(-50%); -} - -.preview-scene-gizmo-axis.mode-move .preview-scene-gizmo-end { - right: -14px; - width: 0; - height: 0; - background: transparent; - border-top: 8px solid transparent; - border-bottom: 8px solid transparent; - border-left: 15px solid currentColor; - border-right: 0; - border-radius: 0; - box-shadow: none; - filter: - drop-shadow(0 0 1px rgba(0, 0, 0, 0.95)) - drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5)); -} - -.preview-scene-gizmo-axis.mode-scale .preview-scene-gizmo-end { - right: -8px; - width: 16px; - height: 16px; -} - -.preview-scene-gizmo-label { - position: absolute; - transform: translate(-50%, -50%); - font-size: 11px; - font-weight: 800; - pointer-events: none; - text-shadow: - 0 1px 2px rgba(0, 0, 0, 0.9), - 0 -1px 2px rgba(0, 0, 0, 0.9); -} - -.preview-scene-gizmo-axis.axis-x, -.preview-scene-gizmo-label.axis-x { - color: #ef4444; -} - -.preview-scene-gizmo-axis.axis-y, -.preview-scene-gizmo-label.axis-y { - color: #22c55e; -} - -.preview-scene-gizmo-axis.axis-z, -.preview-scene-gizmo-label.axis-z { - color: #3b82f6; -} - -.preview-scene-gizmo-rotate { - position: absolute; - width: 128px; - height: 128px; - overflow: visible; - transform: translate(-50%, -50%); - pointer-events: none; -} - -.preview-scene-gizmo-rotate-hit { - fill: none; - stroke: transparent; - stroke-width: 30px; - cursor: grab; - pointer-events: stroke; -} - -.preview-scene-gizmo-rotate-hit:active { - cursor: grabbing; -} - -.preview-scene-gizmo-rotate-stroke { - fill: none; - stroke: currentColor; - stroke-width: 4px; - pointer-events: none; - filter: - drop-shadow(0 0 1px rgba(0, 0, 0, 0.95)) - drop-shadow(0 0 6px color-mix(in srgb, currentColor 55%, transparent)); -} - -.preview-scene-gizmo-rotate-ring.axis-x { - color: #ef4444; -} - -.preview-scene-gizmo-rotate-ring.axis-y { - color: #22c55e; -} - -.preview-scene-gizmo-rotate-ring.axis-z { - color: #3b82f6; -} - -.preview-scene-gizmo-toolbar { - display: flex; - gap: 3px; - padding: 3px; - background: rgba(8, 11, 16, 0.82); - border: 1px solid rgba(255, 255, 255, 0.18); - border-radius: 5px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.36); - pointer-events: auto; -} - -.preview-scene-gizmo-toolbar-slot:empty { - display: none; -} - -.preview-scene-gizmo-toolbar button { - min-width: 44px; - height: 22px; - padding: 0 7px; - color: var(--text-secondary); - background: transparent; - border: 0; - border-radius: 3px; - cursor: pointer; - font-size: 10px; -} - -.preview-scene-gizmo-toolbar button:hover, -.preview-scene-gizmo-toolbar button.active { - color: #ffffff; - background: rgba(255, 255, 255, 0.15); -} - -.preview-canvas-wrapper { - transform-origin: center center; - transition: transform 0.1s ease-out; -} - -/* Preview Bottom Controls (Quality Selector) */ -.preview-controls-bottom { - position: absolute; - bottom: 8px; - left: 8px; - display: flex; - align-items: center; - gap: 8px; - z-index: 20; -} - -.preview-transparency-toggle { - display: flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - padding: 0; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - transition: all 0.15s ease; - backdrop-filter: blur(4px); -} - -.preview-transparency-toggle:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); - border-color: var(--gray-300); -} - -.preview-transparency-toggle.active { - background: rgba(45, 140, 235, 0.2); - border-color: var(--accent); - color: var(--accent); -} - -.preview-quality-dropdown-wrapper { - position: relative; -} - -.preview-quality-dropdown-btn { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - background: var(--preview-overlay-bg); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - transition: all 0.15s; - backdrop-filter: blur(4px); -} - -.preview-quality-dropdown-btn:hover { - background: var(--preview-overlay-bg-hover); - color: var(--text-primary); - border-color: var(--gray-300); -} - -.preview-quality-label { - min-width: 45px; -} - -.preview-quality-dropdown { - position: absolute; - bottom: 100%; - left: 0; - margin-bottom: 4px; - min-width: 120px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - padding: 4px 0; - z-index: 100; -} - -.preview-quality-option { - display: block; - width: 100%; - padding: 6px 12px; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 11px; - text-align: left; - cursor: pointer; - transition: all 0.15s; -} - -.preview-quality-option:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.preview-quality-option.active { - color: var(--accent); - background: rgba(45, 140, 235, 0.1); -} - -.preview-quality-desc { - opacity: 0.6; - margin-left: 4px; -} - -/* ========== SOURCE MONITOR ========== */ -.source-monitor { - position: absolute; - inset: 28px 0 0 0; /* below preview-controls bar */ - display: flex; - flex-direction: column; - background: var(--bg-primary); - z-index: 15; -} - -.source-monitor-media { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-height: 0; - overflow: hidden; - background: var(--bg-canvas); -} - -.source-monitor-video, -.source-monitor-canvas, -.source-monitor-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - display: block; -} - -.source-monitor-video, -.source-monitor-canvas { - cursor: pointer; -} - -/* Toolbar - matches timeline-toolbar style */ -.source-monitor-toolbar { - display: flex; - align-items: center; - gap: 8px; - padding: 3px 8px; - background: var(--bg-tertiary); - border-top: 1px solid var(--border-color); - min-height: 30px; - flex-shrink: 0; -} - -.source-monitor-transport { - display: flex; - gap: 2px; -} - -.source-monitor-backend { - display: flex; - gap: 4px; -} - -.source-monitor-transport .btn { - min-width: 24px; - padding: 3px 4px; - font-size: 10px; - line-height: 1; -} - -.source-monitor-timecode { - display: flex; - align-items: center; - gap: 4px; -} - -.source-monitor-timecode .timeline-time { - min-width: auto; - padding: 2px 5px; - font-size: 11px; -} - -.source-monitor-time-sep { - color: var(--text-secondary); - font-size: 10px; - opacity: 0.5; -} - -/* Scrub bar */ -.source-monitor-scrub { - flex: 1; - height: 24px; - display: flex; - align-items: center; - cursor: pointer; - user-select: none; - padding: 0 2px; -} - -.source-monitor-scrub-track { - flex: 1; - height: 4px; - background: var(--bg-primary); - border-radius: 2px; - position: relative; -} - -.source-monitor-scrub:hover .source-monitor-scrub-track { - height: 6px; -} - -.source-monitor-scrub-fill { - height: 100%; - background: var(--accent); - border-radius: 2px; - position: absolute; - top: 0; - left: 0; - pointer-events: none; -} - -.source-monitor-scrub-handle { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--text-primary); - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - pointer-events: none; - opacity: 0; - transition: opacity 0.15s; -} - -.source-monitor-scrub:hover .source-monitor-scrub-handle { - opacity: 1; -} - -.source-monitor-status { - font-size: 10px; - color: var(--text-secondary); - white-space: nowrap; -} - -/* Header controls */ -.preview-source-label { - font-size: 11px; - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - min-width: 0; - padding: 0 4px; -} - -.preview-close-source-btn { - background: none; - border: none; - color: var(--text-secondary); - font-size: 13px; - cursor: pointer; - padding: 2px 6px; - border-radius: 2px; - line-height: 1; -} - -.preview-close-source-btn:hover { - color: var(--text-primary); - background: var(--bg-hover); -} - -/* ========== CONTEXT MENU ========== */ -.timeline-context-menu, -.media-context-menu { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); - min-width: 180px; - padding: 4px 0; - font-size: 12px; -} - -.context-menu-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - transition: background 0.1s; - color: var(--text-primary); -} - -.context-menu-item:hover { - background: var(--bg-hover); -} - -.context-menu-item.disabled { - color: var(--text-secondary); - cursor: not-allowed; - opacity: 0.5; -} - -.context-menu-item.disabled:hover { - background: transparent; -} - -.context-menu-item.danger { - color: var(--danger); -} - -.context-menu-item.danger:hover { - background: rgba(239, 68, 68, 0.15); -} - -.context-menu-separator { - height: 1px; - background: var(--border-color); - margin: 4px 0; -} - -.context-menu-icon { - width: 20px; - text-align: center; - font-size: 14px; - margin-right: 8px; - flex-shrink: 0; -} - -.context-menu-hint { - margin-left: auto; - font-size: 10px; - color: var(--text-muted); - font-style: italic; -} - -/* Submenu styles */ -.context-menu-item.has-submenu { - position: relative; -} - -.submenu-arrow { - font-size: 8px; - color: var(--text-secondary); -} - -.context-submenu { - display: none; - position: absolute; - left: 100%; - top: -4px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); - min-width: 160px; - padding: 4px 0; -} - -.context-menu-item.has-submenu:hover > .context-submenu { - z-index: 10001; -} - -.context-menu-item.has-submenu:hover .context-submenu { - display: block; -} - -/* Clip color submenu grid */ -.clip-color-submenu { - padding: 6px !important; - min-width: auto !important; -} - -.clip-color-grid { - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 3px; -} - -.clip-color-grid .label-picker-swatch { - width: 18px; - height: 18px; - border-radius: 3px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.1s; -} - -.clip-color-grid .label-picker-swatch:hover { - transform: scale(1.25); - outline: 1px solid rgba(255, 255, 255, 0.5); -} - -.clip-color-grid .label-picker-swatch.active { - outline: 2px solid #fff; - outline-offset: 1px; -} - -.clip-color-grid .label-picker-swatch.none { - border: 1px solid var(--border-color, #555); -} - -/* ========== MASK SECTION STYLES ========== */ -.masks-section .section-header-with-button { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.masks-section .section-header-with-button h4 { - margin: 0; -} - -.mask-add-menu-container { - position: relative; -} - -.btn-add { - background: var(--accent); - color: var(--text-inverse); - border: none; - padding: 4px 10px; - font-size: 11px; - font-weight: 600; - cursor: pointer; - border-radius: 4px; -} - -.btn-add:hover { - background: var(--accent-hover); -} - -.mask-add-menu { - position: absolute; - top: 100%; - right: 0; - margin-top: 4px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 6px; - min-width: 120px; - z-index: 100; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); -} - -.mask-add-menu button { - display: block; - width: 100%; - text-align: left; - padding: 8px 12px; - background: none; - border: none; - color: var(--text-primary); - cursor: pointer; - font-size: 12px; -} - -.mask-add-menu button:hover { - background: var(--bg-hover); -} - -.mask-add-menu button:first-child { - border-radius: 6px 6px 0 0; -} - -.mask-add-menu button:last-child { - border-radius: 0 0 6px 6px; -} - -.mask-empty { - color: var(--text-secondary); - font-size: 12px; - padding: 12px 0; - text-align: center; -} - -.masks-tab { - gap: 10px; -} - -.mask-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 6px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; -} - -.mask-toolbar-group, -.mask-item-actions, -.mask-active-actions { - display: flex; - align-items: center; - gap: 4px; -} - -.mask-icon-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - background: transparent; - border: 1px solid transparent; - border-radius: 5px; - color: var(--text-secondary); - cursor: pointer; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.12s ease; -} - -.mask-icon-btn:hover:not(:disabled) { - background: var(--bg-hover); - border-color: var(--border-color); - color: var(--text-primary); -} - -.mask-icon-btn.active { - background: rgba(45, 140, 235, 0.18); - border-color: rgba(45, 140, 235, 0.7); - color: #72b7ff; -} - -.mask-icon-btn.danger:hover:not(:disabled) { - background: rgba(239, 68, 68, 0.16); - border-color: rgba(239, 68, 68, 0.5); - color: var(--danger); -} - -.mask-icon-btn.cancel { - color: #ff9d5c; -} - -.mask-icon-btn:disabled { - opacity: 0.35; - cursor: default; -} - -.mask-icon-btn.expanded svg { - transform: rotate(0deg); -} - -.mask-list { - display: flex; - flex-direction: column; - gap: 6px; -} - -.mask-item { - position: relative; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 6px; - overflow: visible; -} - -.mask-item.active { - border-color: rgba(45, 140, 235, 0.85); - box-shadow: inset 3px 0 0 rgba(45, 140, 235, 0.9); -} - -.mask-item.color-menu-open { - z-index: 25; -} - -.mask-item.disabled { - opacity: 0.68; -} - -.mask-item-header { - display: flex; - align-items: center; - gap: 6px; - min-height: 38px; - padding: 5px 6px; - cursor: pointer; -} - -.mask-item-header:hover { - background: var(--bg-hover); -} - -.mask-outline-swatch { - width: 14px; - height: 14px; - flex: 0 0 14px; - padding: 0; - border: 1px solid rgba(255, 255, 255, 0.48); - border-radius: 4px; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.38); - cursor: pointer; -} - -.mask-outline-swatch:hover, -.mask-outline-swatch[aria-expanded="true"] { - border-color: rgba(255, 255, 255, 0.82); -} - -.mask-outline-color-menu { - position: absolute; - top: 34px; - left: 40px; - z-index: 30; - display: grid; - grid-template-columns: repeat(6, 18px) 26px; - align-items: center; - gap: 6px; - padding: 7px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: var(--shadow-lg); -} - -.mask-outline-color-option { - position: relative; - width: 18px; - height: 18px; - padding: 0; - border: 1px solid rgba(255, 255, 255, 0.42); - border-radius: 4px; - cursor: pointer; -} - -.mask-outline-color-option:hover, -.mask-outline-color-option.active { - border-color: #fff; - box-shadow: 0 0 0 2px rgba(45, 140, 235, 0.45); -} - -.mask-outline-color-input { - width: 26px; - height: 22px; - padding: 0; - border: 1px solid var(--border-color); - border-radius: 4px; - background: transparent; - cursor: pointer; -} - -.mask-name { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 1px; - padding: 0; - background: transparent; - border: 0; - color: var(--text-primary); - cursor: pointer; - text-align: left; -} - -.mask-name span { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; -} - -.mask-name small { - color: var(--text-secondary); - font-size: 10px; -} - -.mask-name-input { - flex: 1; - min-width: 0; - font-size: 12px; - padding: 4px 6px; - border: 1px solid var(--accent); - border-radius: 4px; - background: var(--bg-primary); - color: var(--text-primary); - outline: none; -} - -.mask-active-card { - padding: 10px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 6px; -} - -.mask-active-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} - -.mask-active-header strong { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: var(--text-primary); - margin-bottom: 2px; -} - -.mask-active-header span { - color: var(--text-secondary); - font-size: 10px; -} - -.mask-path-row { - display: flex; - align-items: center; - gap: var(--sp-2); -} - -.mask-path-row label { - min-width: 70px; -} - -.mask-path-row span { - margin-left: auto; - min-width: auto; - color: var(--text-secondary); - font-size: 10px; - white-space: nowrap; -} - -.mask-vertex-tools { - display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; - align-items: center; - gap: 6px; - padding: 7px 0 9px; - border-top: 1px solid var(--border-color); -} - -.mask-vertex-tools > span { - color: var(--text-secondary); - font-size: 10px; - white-space: nowrap; -} - -.mask-mode-segmented { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 4px; - margin-bottom: 10px; -} - -.mask-mode-segmented.compact { - margin-bottom: 0; -} - -.mask-mode-segmented button { - min-width: 0; - height: 26px; - border: 1px solid var(--border-color); - border-radius: 4px; - background: var(--bg-tertiary); - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; -} - -.mask-mode-segmented button.active { - border-color: rgba(45, 140, 235, 0.8); - background: rgba(45, 140, 235, 0.15); - color: #9ccdff; -} - -.mask-property-groups { - display: flex; - flex-direction: column; - gap: 10px; -} - -.mask-property-group { - padding-top: 8px; - border-top: 1px solid var(--border-color); -} - -.mask-property-group h5 { - margin: 0 0 6px; - color: var(--text-secondary); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} - -.mask-property-group .control-row { - margin-bottom: 6px; -} - -.mask-property-group .control-row:last-child { - margin-bottom: 0; -} - -.mask-empty { - color: var(--text-secondary); - font-size: 12px; - padding: 14px 0; - text-align: center; -} - -/* ========== MASK OVERLAY STYLES ========== */ -.mask-overlay-svg { - pointer-events: auto; - z-index: 50; - overflow: visible; -} - -.mask-overlay-svg text { - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); -} - -.mask-edge-highlight { - filter: drop-shadow(0 0 4px rgba(255, 153, 0, 0.65)); -} - -.mask-edge-insert-preview { - animation: mask-active-pulse 0.9s ease-in-out infinite; - filter: drop-shadow(0 0 5px rgba(255, 153, 0, 0.75)); -} - -.mask-vertex-point { - transition: fill 0.1s ease, stroke 0.1s ease; -} - -.mask-handle-point { - transition: r 0.1s ease, fill 0.1s ease; -} - -.mask-handle-point:hover { - fill: #ffc266; -} - -.mask-active-vertex-ring { - animation: mask-active-pulse 1.1s ease-in-out infinite; -} - -.mask-hover-vertex-ring { - opacity: 0.9; -} - -@keyframes mask-active-pulse { - 0%, 100% { - opacity: 0.55; - stroke-width: 1.5; - } - 50% { - opacity: 1; - stroke-width: 2.5; - } -} - -/* ========== MULTICAM DIALOG STYLES ========== */ -.multicam-dialog-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.7); - display: flex; - align-items: center; - justify-content: center; - z-index: 10001; -} - -.multicam-dialog { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 8px; - width: 480px; - max-width: 90vw; - max-height: 80vh; - display: flex; - flex-direction: column; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); -} - -.multicam-dialog-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid var(--border-color); -} - -.multicam-dialog-header h2 { - margin: 0; - font-size: 16px; - color: var(--text-primary); -} - -.dialog-close-btn { - background: none; - border: none; - color: var(--text-secondary); - font-size: 20px; - cursor: pointer; - padding: 4px 8px; - line-height: 1; -} - -.dialog-close-btn:hover:not(:disabled) { - color: var(--text-primary); -} - -.dialog-close-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.multicam-dialog-content { - padding: 20px; - overflow-y: auto; - flex: 1; -} - -.multicam-section { - margin-bottom: 20px; -} - -.multicam-section h3 { - margin: 0 0 8px; - font-size: 13px; - color: var(--text-primary); - font-weight: 600; -} - -.section-hint { - font-size: 11px; - color: var(--text-secondary); - margin: 0 0 12px; -} - -.section-hint.warning { - color: var(--amber-600); -} - -.multicam-method-selector { - display: flex; - gap: 12px; - margin-bottom: 8px; -} - -.method-option { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - padding: 12px 8px; - background: var(--bg-tertiary); - border: 2px solid var(--border-color); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s ease; -} - -.method-option:hover:not(:has(input:disabled)) { - border-color: var(--accent); -} - -.method-option.selected { - border-color: var(--accent); - background: rgba(59, 130, 246, 0.1); -} - -.method-option:has(input:disabled) { - opacity: 0.5; - cursor: not-allowed; -} - -.method-option input[type="radio"] { - display: none; -} - -.method-option .method-icon { - font-size: 24px; -} - -.method-option .method-label { - font-size: 12px; - font-weight: 600; - color: var(--text-primary); -} - -.method-option .method-count { - font-size: 10px; - color: var(--text-secondary); -} - -.transcript-word-count { - margin-left: auto; - font-size: 10px; - color: var(--text-secondary); - background: var(--bg-secondary); - padding: 2px 6px; - border-radius: 4px; -} - -.multicam-warning { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); - color: var(--danger-light); - padding: 12px; - border-radius: 6px; - font-size: 12px; - text-align: center; -} - -.multicam-clip-list { - display: flex; - flex-direction: column; - gap: 8px; -} - -.multicam-clip-list.no-audio { - opacity: 0.7; -} - -.multicam-clip-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 6px; - cursor: pointer; - transition: all 0.15s ease; -} - -.multicam-clip-item:hover { - border-color: var(--accent); -} - -.multicam-clip-item.disabled { - cursor: default; - opacity: 0.6; -} - -.multicam-clip-item input[type="radio"] { - margin: 0; - accent-color: var(--accent); -} - -.multicam-clip-item .clip-icon { - font-size: 16px; -} - -.multicam-clip-item .clip-name { - flex: 1; - font-size: 12px; - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.master-badge { - background: var(--purple); - color: var(--text-on-accent); - font-size: 10px; - font-weight: 600; - padding: 2px 6px; - border-radius: 3px; -} - -.no-audio-badge { - background: var(--gray-250); - color: var(--text-on-accent); - font-size: 10px; - padding: 2px 6px; - border-radius: 3px; -} - -.multicam-progress-section { - margin-top: 16px; -} - -.multicam-progress { - height: 6px; - background: var(--bg-tertiary); - border-radius: 3px; - overflow: hidden; - margin-bottom: 8px; -} - -.multicam-progress-bar { - height: 100%; - background: linear-gradient(90deg, var(--purple), var(--purple-300)); - border-radius: 3px; - transition: width 0.2s ease-out; -} - -.progress-label { - font-size: 11px; - color: var(--text-secondary); - text-align: center; - display: block; -} - -.multicam-error { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); - color: var(--danger-light); - padding: 10px 12px; - border-radius: 6px; - font-size: 12px; - margin-top: 16px; -} - -.multicam-dialog-footer { - display: flex; - justify-content: flex-end; - gap: 10px; - padding: 16px 20px; - border-top: 1px solid var(--border-color); -} - -.multicam-dialog-footer .btn-cancel { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 8px 16px; - border-radius: 6px; - font-size: 13px; - cursor: pointer; - transition: all 0.15s ease; -} - -.multicam-dialog-footer .btn-cancel:hover:not(:disabled) { - background: var(--bg-hover); -} - -.multicam-dialog-footer .btn-cancel:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.multicam-dialog-footer .btn-sync { - background: var(--purple); - border: none; - color: var(--text-on-accent); - padding: 8px 20px; - border-radius: 6px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: all 0.15s ease; -} - -.multicam-dialog-footer .btn-sync:hover:not(:disabled) { - background: var(--purple-600); -} - -.multicam-dialog-footer .btn-sync:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* ========== WELCOME OVERLAY STYLES ========== */ -.welcome-overlay-backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: transparent; - display: flex; - align-items: center; - justify-content: center; - z-index: 10002; - backdrop-filter: blur(10px); - transition: backdrop-filter 0.2s ease-in; - animation: welcome-backdrop-in 0.4s ease-in-out 0.5s both; -} - -@keyframes welcome-backdrop-in { - from { - backdrop-filter: blur(0px); - } - to { - backdrop-filter: blur(10px); - } -} - -.welcome-overlay-backdrop.closing { - backdrop-filter: blur(0px); - transition: backdrop-filter 120ms ease-out; -} - -.welcome-overlay-backdrop.closing .welcome-overlay { - opacity: 0; - transform: scale(0.96); - transition: opacity 120ms ease-out, transform 120ms ease-out; -} - -/* No fade when another dialog will show immediately after */ -.welcome-overlay-backdrop.closing.no-fade { - backdrop-filter: blur(10px); - transition: none; -} - -.welcome-overlay-backdrop.closing.no-fade .welcome-overlay { - opacity: 0; - transform: scale(0.96); - transition: opacity 80ms ease-out, transform 80ms ease-out; -} - -.welcome-overlay { - background: linear-gradient(165deg, rgba(30, 30, 30, 0.95) 0%, rgba(15, 15, 15, 0.98) 100%); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 24px; - width: 400px; - max-width: 90vw; - padding: 40px 36px 32px; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.02), - 0 40px 80px rgba(0, 0, 0, 0.5), - 0 0 120px rgba(0, 0, 0, 0.3); - animation: welcome-overlay-in 0.3s ease-out 0.9s both; -} - -@keyframes welcome-overlay-in { - from { - opacity: 0; - transform: scale(0.96); - } - to { - opacity: 1; - transform: scale(1); - } -} - -/* Privacy Tagline */ -.welcome-tagline { - display: flex; - align-items: center; - gap: 0; - min-height: 24px; - position: relative; - font-size: 15px; - font-weight: 600; - letter-spacing: 0.5px; - margin-bottom: 28px; - animation: welcome-fade-in 0.4s ease-out 1.0s both; -} - -.welcome-tag-local { - color: var(--danger-light); -} - -.welcome-tag-private { - color: var(--blue-300); -} - -.welcome-tag-free { - color: var(--green-300); -} - -.welcome-tag-dot { - color: rgba(255, 255, 255, 0.3); - font-weight: 400; - display: inline-block; - max-width: 0; - overflow: hidden; - white-space: nowrap; - padding: 0; - animation: dot-expand 0.35s ease-out forwards; -} - -@keyframes dot-expand { - 0% { - max-width: 0; - padding: 0; - opacity: 0; - } - 50% { - opacity: 1; - } - 100% { - max-width: 40px; - padding: 0 12px; - opacity: 1; - } -} - -/* Typewriter cursor */ -.welcome-cursor { - color: rgba(255, 255, 255, 0.8); - font-weight: 300; - margin-left: 1px; - opacity: 0; - transition: opacity 0.05s; -} - -.welcome-cursor.visible { - opacity: 1; -} - - -@keyframes welcome-fade-in { - from { - opacity: 0; - transform: translateY(-8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Title */ -.welcome-title { - margin: 0 0 8px; - font-size: 36px; - font-weight: 700; - letter-spacing: -1.5px; - line-height: 1; - overflow: hidden; -} - -.welcome-title-master { - color: var(--gray-0); - display: inline-block; - animation: welcome-title-master-in 0.8s ease-out 1.05s both; -} - -@keyframes welcome-title-master-in { - from { - opacity: 0; - transform: translateX(-20px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -.welcome-title-selects { - color: rgba(255, 255, 255, 0.7); - display: inline-block; - animation: welcome-title-selects-in 0.8s ease-out 1.25s both; -} - -@keyframes welcome-title-selects-in { - from { - opacity: 0; - transform: translateX(20px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -.welcome-subtitle { - margin: 0 0 36px; - font-size: 14px; - color: rgba(255, 255, 255, 0.35); - font-weight: 400; - letter-spacing: 0.2px; - animation: welcome-fade-up 0.4s ease-out 1.25s both; -} - -@keyframes welcome-fade-up { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Folder Card */ -.welcome-folder-card { - width: 100%; - margin-bottom: 24px; - animation: welcome-fade-up 0.4s ease-out 1.3s both; -} - -.welcome-folder-card-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; - padding: 0 4px; -} - -.welcome-folder-card-label { - font-size: 12px; - font-weight: 600; - color: rgba(255, 255, 255, 0.6); - letter-spacing: 0.3px; -} - -.welcome-folder-card-optional { - font-size: 10px; - font-weight: 500; - color: rgba(255, 255, 255, 0.25); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Folder Button */ -.welcome-folder-btn { - width: 100%; - display: flex; - align-items: center; - gap: 14px; - padding: 14px 16px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 14px; - cursor: pointer; - transition: all 0.2s ease; - text-align: left; -} - -.welcome-folder-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 255, 255, 0.1); -} - -.welcome-folder-btn:hover:not(:disabled) .welcome-folder-btn-arrow { - opacity: 0.6; - transform: translateX(2px); -} - -.welcome-folder-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.welcome-folder-btn.has-folder { - background: rgba(255, 255, 255, 0.05); - border-color: rgba(255, 255, 255, 0.08); -} - -.welcome-folder-btn-icon { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.05); - border-radius: 10px; - flex-shrink: 0; -} - -.welcome-folder-btn-icon svg { - color: rgba(255, 255, 255, 0.5); -} - -.welcome-folder-btn.has-folder .welcome-folder-btn-icon { - background: rgba(100, 200, 100, 0.1); -} - -.welcome-folder-btn.has-folder .welcome-folder-btn-icon svg { - color: rgba(100, 200, 100, 0.7); -} - -.welcome-folder-btn-text { - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} - -.welcome-folder-name { - font-size: 14px; - font-weight: 500; - color: var(--gray-0); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.welcome-folder-change { - font-size: 11px; - color: rgba(255, 255, 255, 0.35); -} - -.welcome-folder-btn-arrow { - flex-shrink: 0; - color: rgba(255, 255, 255, 0.25); - opacity: 0.4; - transition: all 0.2s ease; -} - -/* Folder Buttons Container */ -.welcome-folder-buttons { - display: flex; - flex-direction: column; - gap: 10px; -} - -/* Secondary button style (Open Existing) */ -.welcome-folder-btn-secondary { - background: transparent; - border-color: rgba(255, 255, 255, 0.04); -} - -.welcome-folder-btn-secondary:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.03); -} - -.welcome-folder-btn-secondary .welcome-folder-btn-icon { - background: rgba(255, 255, 255, 0.03); -} - -/* Enter Button */ -.welcome-enter { - display: inline-flex; - align-items: center; - gap: 10px; - padding: 0; - background: none; - border: none; - cursor: pointer; - color: rgba(255, 255, 255, 0.4); - font-size: 13px; - font-weight: 500; - transition: all 0.2s ease; - animation: welcome-fade-up 0.4s ease-out 1.4s both; -} - -.welcome-enter:hover { - color: rgba(255, 255, 255, 0.7); -} - -.welcome-enter:hover kbd { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.15); - color: rgba(255, 255, 255, 0.8); -} - -.welcome-enter kbd { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 24px; - height: 24px; - padding: 0 6px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 6px; - font-family: inherit; - font-size: 14px; - color: rgba(255, 255, 255, 0.5); - transition: all 0.2s ease; -} - -/* Native Helper dialog */ -.native-helper-dialog { - width: 520px; - max-width: min(520px, 94vw); - padding: 0; - align-items: stretch; - text-align: left; - overflow: hidden; -} - -.native-helper-dialog-header { - display: flex; - flex-direction: column; - gap: 12px; - padding: 24px 24px 18px; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); -} - -.native-helper-title-row { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.native-helper-title { - margin: 10px 0 0; - font-size: 34px; - line-height: 1; - letter-spacing: -1px; - color: var(--text-primary); -} - -.native-helper-version-pill { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 28px; - padding: 0 10px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.08); - color: var(--text-secondary); - font-size: 12px; - font-weight: 600; -} - -.native-helper-subtitle { - margin: 0; - max-width: 430px; - color: var(--text-secondary); - font-size: 14px; - line-height: 1.5; -} - -.native-helper-status-pill { - display: inline-flex; - align-items: center; - gap: 8px; - align-self: flex-start; - min-height: 28px; - padding: 0 10px; - border-radius: 999px; - font-size: 12px; - font-weight: 600; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.04); - color: var(--text-secondary); -} - -.native-helper-status-pill-connected { - background: rgba(34, 197, 94, 0.12); - border-color: rgba(74, 222, 128, 0.22); - color: #bbf7d0; -} - -.native-helper-status-pill-offline { - background: rgba(245, 158, 11, 0.12); - border-color: rgba(251, 191, 36, 0.22); - color: #fde68a; -} - -.native-helper-status-pill-disabled { - background: rgba(255, 255, 255, 0.04); - color: var(--text-muted); -} - -.native-helper-status-dot { - width: 7px; - height: 7px; - border-radius: 999px; - background: currentColor; - opacity: 0.9; -} - -.native-helper-dialog-body { - display: flex; - flex-direction: column; - gap: 12px; - padding: 20px 24px; -} - -.native-helper-card, -.native-helper-accordion { - background: rgba(255, 255, 255, 0.035); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 16px; -} - -.native-helper-card { - display: flex; - flex-direction: column; - gap: 12px; - padding: 14px 16px; -} - -.native-helper-card-highlight { - background: - radial-gradient(circle at top right, rgba(96, 165, 250, 0.16), transparent 42%), - rgba(255, 255, 255, 0.04); - border-color: rgba(96, 165, 250, 0.18); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); -} - -.native-helper-toggle-row { - display: grid; - grid-template-columns: 18px 1fr; - gap: 12px; - align-items: flex-start; - cursor: pointer; -} - -.native-helper-checkbox { - width: 16px; - height: 16px; - margin-top: 2px; - accent-color: var(--accent); -} - -.native-helper-toggle-text { - display: flex; - flex-direction: column; - gap: 3px; - min-width: 0; -} - -.native-helper-toggle-title { - color: var(--text-primary); - font-size: 14px; - font-weight: 600; - line-height: 1.35; -} - -.native-helper-toggle-description { - color: var(--text-secondary); - font-size: 12px; - line-height: 1.45; -} - -.native-helper-card-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.native-helper-card-kicker { - color: var(--text-muted); - font-size: 11px; - font-weight: 600; - letter-spacing: 0.5px; - text-transform: uppercase; -} - -.native-helper-card-title { - margin-top: 3px; - color: var(--text-primary); - font-size: 18px; - font-weight: 600; - line-height: 1.2; -} - -.native-helper-chip { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 24px; - padding: 0 9px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.08); - color: var(--text-secondary); - font-size: 11px; - font-weight: 600; - white-space: nowrap; -} - -.native-helper-chip.is-warn { - background: rgba(245, 158, 11, 0.12); - border-color: rgba(251, 191, 36, 0.22); - color: #fde68a; -} - -.native-helper-card-note { - margin: 0; - color: var(--text-secondary); - font-size: 13px; - line-height: 1.5; -} - -.native-helper-pill-row { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.native-helper-pill { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 28px; - padding: 0 10px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.04); - color: var(--text-secondary); - font-size: 12px; - line-height: 1.2; -} - -.native-helper-pill-good { - background: rgba(34, 197, 94, 0.1); - border-color: rgba(74, 222, 128, 0.18); - color: #bbf7d0; -} - -.native-helper-pill-warn { - background: rgba(245, 158, 11, 0.1); - border-color: rgba(251, 191, 36, 0.18); - color: #fde68a; -} - -.native-helper-action-row { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.native-helper-button { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 38px; - padding: 0 14px; - border-radius: 12px; - border: 1px solid transparent; - font-size: 13px; - font-weight: 600; - text-decoration: none; - transition: all 0.15s ease; - cursor: pointer; -} - -.native-helper-button:disabled { - opacity: 0.55; - cursor: default; -} - -.native-helper-button-primary { - background: linear-gradient(180deg, rgba(45, 140, 235, 0.95), rgba(26, 95, 176, 0.95)); - border-color: rgba(96, 165, 250, 0.26); - color: #fff; -} - -.native-helper-button-primary:hover:not(:disabled) { - transform: translateY(-1px); - filter: brightness(1.05); -} - -.native-helper-button-secondary { - background: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.08); - color: var(--text-secondary); -} - -.native-helper-button-secondary:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.07); - color: var(--text-primary); -} - -.native-helper-link { - align-self: flex-start; - color: var(--text-muted); - font-size: 12px; - text-decoration: none; -} - -.native-helper-link:hover { - color: var(--text-primary); -} - -.native-helper-accordion { - overflow: hidden; -} - -.native-helper-accordion-toggle { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 14px 16px; - background: transparent; - border: none; - color: var(--text-primary); - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: background 0.15s ease; -} - -.native-helper-accordion-toggle:hover { - background: rgba(255, 255, 255, 0.03); -} - -.native-helper-accordion-body { - display: flex; - flex-direction: column; - gap: 12px; - padding: 0 16px 16px; - border-top: 1px solid rgba(255, 255, 255, 0.05); -} - -.native-helper-install-list { - margin: 0; - padding-left: 18px; - color: var(--text-secondary); - font-size: 13px; - line-height: 1.55; -} - -.native-helper-install-list li + li { - margin-top: 6px; -} - -.native-helper-install-note { - margin: 0; - color: var(--text-muted); - font-size: 12px; - line-height: 1.5; -} - -.native-helper-command-row { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.native-helper-command { - flex: 1; - min-width: 0; - padding: 10px 12px; - background: rgba(0, 0, 0, 0.22); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 10px; - color: var(--text-primary); - font-family: var(--font-mono); - font-size: 12px; -} - -.native-helper-copy-button { - min-height: 36px; - padding: 0 12px; - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.04); - color: var(--text-secondary); - font-size: 12px; - font-weight: 600; - cursor: pointer; - transition: all 0.15s ease; -} - -.native-helper-copy-button:hover { - background: rgba(255, 255, 255, 0.07); - color: var(--text-primary); -} - -.native-helper-footer { - display: flex; - justify-content: center; - padding: 0 24px 20px; -} - -@media (max-width: 640px) { - .native-helper-dialog { - max-width: 94vw; - } - - .native-helper-dialog-header, - .native-helper-dialog-body, - .native-helper-footer { - padding-left: 18px; - padding-right: 18px; - } - - .native-helper-title-row, - .native-helper-card-head { - flex-direction: column; - align-items: flex-start; - } - - .native-helper-action-row { - flex-direction: column; - } - - .native-helper-button { - width: 100%; - } -} - -/* Note for unsupported browsers */ -.welcome-note { - margin: 0; - padding: 14px 16px; - background: rgba(255, 255, 255, 0.03); - border-radius: 12px; - font-size: 13px; - color: rgba(255, 255, 255, 0.4); - line-height: 1.5; -} - -/* Native Helper project picker (Firefox) */ -.welcome-native-new { - display: flex; - flex-direction: column; - gap: 8px; -} - -.welcome-native-input { - width: 100%; - padding: 10px 14px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - color: #fff; - font-size: 14px; - font-family: inherit; - outline: none; - transition: border-color 0.2s; - box-sizing: border-box; -} - -.welcome-native-input:focus { - border-color: rgba(255, 255, 255, 0.25); -} - -.welcome-native-input::placeholder { - color: rgba(255, 255, 255, 0.3); -} - -.welcome-native-projects { - display: flex; - flex-direction: column; - gap: 4px; - margin-top: 8px; -} - -.welcome-native-projects-label { - font-size: 11px; - color: rgba(255, 255, 255, 0.3); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 4px 0; -} - -.welcome-native-location { - margin-top: 2px; -} - -.welcome-native-location-btn { - display: flex; - align-items: center; - gap: 6px; - width: 100%; - padding: 6px 10px; - background: none; - border: 1px solid transparent; - border-radius: 8px; - color: rgba(255, 255, 255, 0.3); - font-size: 11px; - font-family: inherit; - cursor: pointer; - transition: all 0.15s; - text-align: left; -} - -.welcome-native-location-btn:hover { - background: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.5); -} - -.welcome-native-location-btn span { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.welcome-native-path-input { - font-size: 12px; - padding: 6px 10px; -} - -.welcome-native-actions { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; -} - -.welcome-native-back-btn { - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 8px; - color: rgba(255, 255, 255, 0.5); - font-size: 12px; - padding: 6px 12px; - cursor: pointer; - transition: all 0.15s ease; -} - -.welcome-native-back-btn:hover { - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.7); -} - -/* Browser warning for non-Chromium browsers */ -.welcome-browser-warning { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - gap: 8px; - margin: 8px 0 20px 0; - padding: 20px 24px; - background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(185, 28, 28, 0.1) 100%); - border: 1px solid rgba(239, 68, 68, 0.35); - border-radius: 16px; - max-width: 340px; - box-shadow: 0 0 30px rgba(239, 68, 68, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05); -} - -.welcome-browser-warning-icon { - color: var(--danger); - filter: drop-shadow(0 0 8px rgba(239, 68, 68, 0.5)); -} - -.welcome-browser-warning-label { - font-size: 11px; - font-weight: 600; - color: var(--danger); - text-transform: uppercase; - letter-spacing: 1px; -} - -.welcome-browser-warning-name { - font-size: 22px; - font-weight: 700; - color: var(--gray-0); - margin: -2px 0 4px 0; -} - -.welcome-browser-warning-desc { - font-size: 13px; - color: rgba(255, 255, 255, 0.6); - line-height: 1.5; - max-width: 280px; -} - -.welcome-browser-warning-btn { - display: inline-flex; - align-items: center; - gap: 8px; - margin-top: 8px; - padding: 10px 18px; - background: linear-gradient(135deg, #4285f4 0%, #1a73e8 100%); - border: none; - border-radius: 8px; - color: var(--text-on-accent); - font-size: 13px; - font-weight: 600; - text-decoration: none; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 2px 8px rgba(66, 133, 244, 0.3); -} - -.welcome-browser-warning-btn:hover { - background: linear-gradient(135deg, #5a95f5 0%, #2b7de9 100%); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(66, 133, 244, 0.4); -} - -.welcome-browser-warning-btn svg { - opacity: 0.9; -} - -/* OS warning for non-Linux systems (yellow/orange instead of red) */ -.welcome-os-warning { - background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(180, 83, 9, 0.1) 100%); - border: 1px solid rgba(245, 158, 11, 0.35); - box-shadow: 0 0 30px rgba(245, 158, 11, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05); -} - -.welcome-os-warning .welcome-browser-warning-icon { - color: var(--amber-600); - filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5)); -} - -.welcome-os-warning .welcome-browser-warning-label { - color: var(--amber-600); -} - -/* Error state */ -.welcome-error { - margin-top: 10px; - padding: 0 4px; - font-size: 12px; - color: var(--danger-light); - text-align: left; -} - -/* ============================================ - Info Dialog - About MasterSelects - ============================================ */ - -.info-content { - text-align: center; - padding: 8px 0; -} - -.info-description { - font-size: 13px; - color: var(--gray-100); - line-height: 1.6; - margin: 0 0 20px 0; -} - -.info-features { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - margin-bottom: 20px; -} - -.info-feature { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - background: rgba(255, 255, 255, 0.03); - border-radius: 8px; - font-size: 12px; - color: var(--gray-75); -} - -.info-feature-icon { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 215, 0, 0.05)); - border: 1px solid rgba(255, 215, 0, 0.3); - border-radius: 6px; - font-size: 10px; - font-weight: 700; - color: var(--amber-300); - flex-shrink: 0; -} - -.info-version { - font-size: 11px; - color: var(--gray-250); - margin-top: 8px; -} - -.info-helper-launch { - width: 100%; - margin-top: 14px; - padding: 14px 16px; - display: flex; - align-items: center; - gap: 12px; - background: - linear-gradient(180deg, rgba(22, 29, 46, 0.92), rgba(13, 18, 30, 0.92)), - radial-gradient(circle at top left, rgba(34, 197, 94, 0.18), transparent 55%); - border: 1px solid rgba(56, 189, 248, 0.18); - border-radius: 12px; - color: var(--gray-25); - text-align: left; - cursor: pointer; - transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease; -} - -.info-helper-launch:hover { - transform: translateY(-1px); - border-color: rgba(56, 189, 248, 0.35); - background: - linear-gradient(180deg, rgba(28, 37, 58, 0.94), rgba(16, 22, 36, 0.94)), - radial-gradient(circle at top left, rgba(34, 197, 94, 0.22), transparent 58%); -} - -.info-helper-launch-icon { - width: 38px; - height: 38px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 10px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); - flex-shrink: 0; -} - -.info-helper-launch-copy { - min-width: 0; - display: flex; - flex-direction: column; - gap: 5px; -} - -.info-helper-launch-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} - -.info-helper-launch-title { - font-size: 13px; - font-weight: 600; - color: var(--gray-0); -} - -.info-helper-launch-badge { - padding: 3px 9px; - border-radius: 999px; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.02em; - border: 1px solid transparent; - flex-shrink: 0; -} - -.info-helper-launch-badge-connected { - color: #86efac; - background: rgba(34, 197, 94, 0.14); - border-color: rgba(34, 197, 94, 0.28); -} - -.info-helper-launch-badge-offline { - color: #fca5a5; - background: rgba(239, 68, 68, 0.14); - border-color: rgba(239, 68, 68, 0.28); -} - -.info-helper-launch-badge-disabled { - color: var(--gray-200); - background: rgba(148, 163, 184, 0.12); - border-color: rgba(148, 163, 184, 0.22); -} - -.info-helper-launch-text { - font-size: 12px; - line-height: 1.5; - color: var(--gray-150); -} - -.info-github-link { - display: inline-flex; - align-items: center; - gap: 6px; - margin-top: 12px; - padding: 8px 16px; - background: var(--gray-650); - border: 1px solid var(--gray-500); - border-radius: 6px; - color: var(--gray-75); - font-size: 13px; - text-decoration: none; - transition: all 0.15s ease; -} - -.info-github-link:hover { - background: var(--gray-500); - border-color: var(--gray-150); - color: var(--gray-0); -} - -.info-github-link svg { - opacity: 0.8; -} - -.info-github-link:hover svg { - opacity: 1; -} - -/* ============================================ - Relink Dialog - Media relinking interface - ============================================ */ - -.relink-dialog { - max-width: 500px; - width: 90%; -} - -.relink-title { - font-size: 20px; - font-weight: 600; - color: var(--gray-0); - margin: 0 0 4px 0; - text-align: center; -} - -.relink-subtitle { - font-size: 13px; - color: var(--gray-200); - margin: 0 0 16px 0; - text-align: center; -} - -.relink-file-list { - max-height: 300px; - overflow-y: auto; - background: rgba(0, 0, 0, 0.3); - border-radius: 8px; - margin-bottom: 12px; -} - -.relink-file-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - cursor: pointer; - transition: background 0.15s; -} - -.relink-file-item:hover { - background: rgba(255, 255, 255, 0.05); -} - -.relink-file-item:last-child { - border-bottom: none; -} - -.relink-file-item.found { - cursor: default; -} - -.relink-status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - font-size: 12px; - font-weight: 700; - flex-shrink: 0; -} - -.relink-status-icon.missing { - background: rgba(255, 100, 100, 0.2); - color: var(--danger-light); -} - -.relink-status-icon.found { - background: rgba(100, 255, 100, 0.2); - color: var(--success-light); -} - -.relink-status-icon.searching { - background: rgba(255, 200, 100, 0.2); - color: var(--warning-light); -} - -.relink-file-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; -} - -.relink-file-name { - font-size: 13px; - color: var(--gray-25); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.relink-file-path { - font-size: 11px; - color: var(--gray-250); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.relink-pick-hint { - font-size: 11px; - color: var(--gray-200); - flex-shrink: 0; -} - -.relink-searched { - font-size: 11px; - color: var(--gray-250); - margin-bottom: 12px; - padding: 0 4px; -} - -.relink-actions { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.relink-actions-right { - display: flex; - gap: 8px; -} - -.relink-btn { - padding: 8px 16px; - border: none; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s; - background: rgba(255, 255, 255, 0.1); - color: var(--gray-75); -} - -.relink-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.15); - color: var(--gray-0); -} - -.relink-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.relink-btn-primary { - background: rgba(255, 215, 0, 0.2); - color: var(--amber-300); -} - -.relink-btn-primary:hover:not(:disabled) { - background: rgba(255, 215, 0, 0.3); -} - -.relink-btn-secondary { - background: rgba(100, 150, 255, 0.15); - color: var(--blue-200); -} - -.relink-btn-secondary:hover:not(:disabled) { - background: rgba(100, 150, 255, 0.25); -} - -/* ============================================ - Saved Toast - Center screen notification - ============================================ */ - -.saved-toast { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 10000; - padding: 16px 32px; - background: rgba(0, 0, 0, 0.85); - border: 2px solid var(--amber-300); - border-radius: 12px; - font-size: 24px; - font-weight: 600; - color: var(--amber-300); - text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); - box-shadow: - 0 0 30px rgba(255, 215, 0, 0.3), - 0 4px 20px rgba(0, 0, 0, 0.5); - animation: saved-toast-flash 0.6s ease-out forwards; - pointer-events: none; -} - -/* Combined fade-in and fade-out animation */ -@keyframes saved-toast-flash { - 0% { - opacity: 0; - transform: translate(-50%, -50%) scale(0.8); - } - 25% { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - 70% { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - 100% { - opacity: 0; - transform: translate(-50%, -50%) scale(0.95); - } -} - -/* ===== Changelog Dialog (What's New) ===== */ - -/* Backdrop */ -.whats-new-backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 10002; - backdrop-filter: blur(10px); - opacity: 1; - animation: welcome-backdrop-in 0.14s ease-out both; - transition: backdrop-filter 200ms ease-out, opacity 200ms ease-out; -} - -.whats-new-backdrop.closing { - backdrop-filter: blur(0px); - opacity: 0; -} - -.whats-new-backdrop .welcome-overlay { - animation: welcome-overlay-in 0.16s ease-out both; - transition: opacity 200ms ease-out, transform 200ms ease-out; -} - -.whats-new-backdrop.closing .welcome-overlay { - opacity: 0; - transform: scale(0.96); -} - -/* Dialog container - single scroll container, header/footer stick */ -.changelog-dialog { - width: 680px; - height: 600px; - max-height: 85vh; - padding: 0; - text-align: left; - display: flex; - flex-direction: column; - align-items: stretch; - overflow-y: auto; - background: transparent; - border: none; - box-shadow: none; - isolation: isolate; -} - -/* Header - sticky, content scrolls underneath */ -.changelog-header { - position: sticky; - top: 0; - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 24px 24px; - z-index: 20; - background: linear-gradient(to bottom, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 40%, rgba(22, 22, 22, 0.6) 70%, transparent 100%); - flex-shrink: 0; -} - -.changelog-header-left { - display: flex; - align-items: center; - gap: 12px; - min-width: 0; - flex: 1 1 auto; -} - -.changelog-header-center { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - z-index: 1; -} - -.changelog-header-right { - flex: 0 0 auto; - margin-left: auto; - position: relative; - z-index: 1; -} - -.changelog-heading { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - min-width: 0; -} - -.changelog-brand { - display: inline-flex; - align-items: baseline; - font-size: 36px; - font-weight: 700; - letter-spacing: -1.5px; - line-height: 1; - overflow: hidden; -} - -.changelog-brand-master { - color: var(--gray-0); -} - -.changelog-brand-selects { - color: rgba(255, 255, 255, 0.7); -} - -.changelog-header-title { - margin: 0; - font-size: 14px; - font-weight: 600; - color: rgba(255, 255, 255, 0.42); - letter-spacing: 0.18em; - line-height: 1; - text-transform: uppercase; -} - -.changelog-version { - align-self: center; - font-size: 20px; - font-weight: 600; - color: rgba(255, 255, 255, 0.5); - background: transparent; - padding-top: 0; -} - -.changelog-header-button { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 112px; - height: 42px; - padding: 0 20px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.06); - color: rgba(255, 255, 255, 0.82); - font-size: 14px; - font-weight: 700; - cursor: pointer; - transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; -} - -.changelog-header-button:hover { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.18); - color: rgba(255, 255, 255, 0.96); -} - -.changelog-close { - width: 32px; - height: 32px; - border: none; - background: rgba(255, 255, 255, 0.05); - border-radius: 8px; - color: var(--text-secondary); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.15s; -} - -.changelog-close:hover { - background: rgba(255, 255, 255, 0.1); - color: var(--text-primary); -} - -/* Filter tabs */ -.changelog-tabs { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding: 0 0 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); -} - -.changelog-tab { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 12px; - border: none; - background: rgba(255, 255, 255, 0.04); - border-radius: 8px; - color: var(--text-secondary); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s; -} - -.changelog-tab:hover { - background: rgba(255, 255, 255, 0.08); - color: var(--text-primary); -} - -.changelog-tab.active { - background: rgba(255, 255, 255, 0.12); - color: var(--text-primary); -} - -.changelog-tab-new.active { - background: rgba(81, 207, 102, 0.15); - color: var(--green-300); -} - -.changelog-tab-fix.active { - background: rgba(77, 171, 247, 0.15); - color: var(--blue-300); -} - -.changelog-tab-improve.active { - background: rgba(255, 170, 0, 0.15); - color: var(--warning); -} - -.changelog-tab-refactor.active { - background: rgba(168, 85, 247, 0.15); - color: var(--purple-light); -} - -.changelog-tab-count { - font-size: 10px; - font-weight: 600; - opacity: 0.6; -} - -/* Content - normal flow, no own scroll */ -.changelog-content { - position: relative; - z-index: 0; - padding: 16px 24px 16px; - display: flex; - flex-direction: column; - gap: 24px; - background: rgba(22, 22, 22, 0.95); -} - -.changelog-content::before { - content: ''; - position: absolute; - left: 0; - right: 0; - top: -48px; - height: 48px; - background: rgba(22, 22, 22, 0.95); - pointer-events: none; -} - -.changelog-featured { - display: grid; - grid-template-columns: minmax(0, 1fr) 248px; - grid-template-areas: 'notices side'; - align-items: flex-start; - gap: 18px; - transition: grid-template-columns 0.32s ease, gap 0.32s ease; -} - -.changelog-featured-notices { - grid-area: notices; - min-width: 0; - display: flex; - flex-direction: column; - gap: 12px; - transition: transform 0.32s ease, opacity 0.32s ease; -} - -.changelog-featured-notices .changelog-notice { - margin-bottom: 0; -} - -.changelog-featured-notices .changelog-notice:nth-child(3) { - margin-top: 10px; -} - -.changelog-featured.is-video-expanded { - grid-template-columns: minmax(0, 1fr); - grid-template-areas: - 'side' - 'notices'; - gap: 16px; -} - -.changelog-featured.is-video-expanded .changelog-featured-notices { - transform: translateY(0); - opacity: 1; -} - -.changelog-featured-side { - grid-area: side; - width: 248px; - margin-left: auto; - display: flex; - flex-direction: column; - gap: 12px; - transform-origin: top right; - transition: width 0.32s ease, transform 0.32s ease, opacity 0.32s ease; -} - -.changelog-featured.is-video-expanded .changelog-featured-side { - width: 100%; - margin-left: 0; -} - -.changelog-featured.is-video-expanded .changelog-video-container { - border-radius: 12px; - box-shadow: 0 24px 48px rgba(0, 0, 0, 0.34); -} - -.changelog-featured.is-video-expanded .changelog-calendar { - justify-content: flex-end; -} - -/* Featured Video */ -.changelog-video { - width: 100%; -} - -.changelog-video-notice { - margin-bottom: 0; -} - -.changelog-video-shell { - display: flex; - flex-direction: column; - gap: 8px; -} - -.changelog-video-container { - position: relative; - width: 100%; - aspect-ratio: 16 / 9; - border-radius: 8px; - overflow: hidden; - background: #000; - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 16px 36px rgba(0, 0, 0, 0.28); - transition: border-color 0.16s ease, box-shadow 0.16s ease, border-radius 0.32s ease; -} - -.changelog-video-container:hover { - border-color: rgba(255, 255, 255, 0.14); - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.34); -} - -.changelog-video-frame { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - border: none; - background: #000; -} - -.changelog-video-fallback { - align-self: flex-start; - font-size: 11px; - font-weight: 600; - letter-spacing: 0.01em; - color: rgba(255, 255, 255, 0.54); - text-decoration: none; - transition: color 0.12s ease; -} - -.changelog-video-fallback:hover { - color: rgba(255, 255, 255, 0.82); - text-decoration: underline; -} - -.changelog-calendar { - width: 100%; - display: flex; - justify-content: flex-start; - gap: 3px; - padding: 0; - background: transparent; - border: none; - box-shadow: none; -} - -.changelog-calendar-week { - display: flex; - flex-direction: column; - gap: 3px; - overflow: visible; -} - -.changelog-calendar-day { - position: relative; - width: 9px; - height: 9px; - border-radius: 2px; - background: rgba(255, 255, 255, 0.05); - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02); - transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease; - overflow: visible; -} - -.changelog-calendar-fill { - position: absolute; - inset: 0; - border-radius: inherit; - pointer-events: none; -} - -.changelog-calendar-fill-main { - background: rgba(255, 255, 255, 0.05); -} - -.changelog-calendar-fill-community { - display: none; -} - -.changelog-calendar-day.has-community .changelog-calendar-fill-main { - clip-path: inset(0 50% 0 0 round 2px); -} - -.changelog-calendar-day.has-community .changelog-calendar-fill-community { - display: block; - clip-path: inset(0 0 0 50% round 2px); -} - -.changelog-calendar-day::before { - content: ''; - position: absolute; - inset: -3px; -} - -.changelog-calendar-day:not(.is-future):not(.is-outside-range):hover { - transform: translateY(-1px); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16); -} - -.changelog-calendar-level-0 .changelog-calendar-fill-main { - background: rgba(255, 255, 255, 0.05); -} - -.changelog-calendar-level-1 .changelog-calendar-fill-main { - background: rgba(34, 197, 94, 0.22); -} - -.changelog-calendar-level-2 .changelog-calendar-fill-main { - background: rgba(34, 197, 94, 0.42); -} - -.changelog-calendar-level-3 .changelog-calendar-fill-main { - background: rgba(34, 197, 94, 0.65); -} - -.changelog-calendar-level-4 .changelog-calendar-fill-main { - background: rgba(74, 222, 128, 0.9); -} - -.changelog-calendar-community-level-0 .changelog-calendar-fill-community { - background: rgba(255, 184, 107, 0.14); -} - -.changelog-calendar-community-level-1 .changelog-calendar-fill-community { - background: rgba(245, 158, 11, 0.28); -} - -.changelog-calendar-community-level-2 .changelog-calendar-fill-community { - background: rgba(245, 158, 11, 0.48); -} - -.changelog-calendar-community-level-3 .changelog-calendar-fill-community { - background: rgba(249, 115, 22, 0.68); -} - -.changelog-calendar-community-level-4 .changelog-calendar-fill-community { - background: rgba(251, 146, 60, 0.92); -} - -.changelog-calendar-day.is-future { - opacity: 0.32; -} - -.changelog-calendar-day.is-today { - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.28); -} - -.changelog-calendar-day.is-outside-range { - opacity: 0; - box-shadow: none; -} - -.changelog-calendar-tooltip { - position: absolute; - left: 50%; - bottom: calc(100% + 8px); - transform: translateX(-50%); - padding: 5px 8px; - border-radius: 6px; - background: rgba(10, 10, 10, 0.96); - border: 1px solid rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.92); - font-size: 11px; - line-height: 1.2; - white-space: nowrap; - opacity: 0; - visibility: hidden; - pointer-events: none; - z-index: 20; - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35); -} - -.changelog-calendar-tooltip::after { - content: ''; - position: absolute; - left: 50%; - top: 100%; - width: 8px; - height: 8px; - background: rgba(10, 10, 10, 0.96); - border-right: 1px solid rgba(255, 255, 255, 0.08); - border-bottom: 1px solid rgba(255, 255, 255, 0.08); - transform: translate(-50%, -4px) rotate(45deg); -} - -.changelog-calendar-day:not(.is-future):not(.is-outside-range):hover .changelog-calendar-tooltip { - opacity: 1; - visibility: visible; -} - -@media (max-width: 760px) { - .changelog-header { - display: flex; - flex-direction: column; - gap: 12px; - } - - .changelog-header-center { - position: static; - transform: none; - } - - .changelog-header-right { - margin-left: 0; - } - - .changelog-heading { - flex-direction: column; - align-items: flex-start; - gap: 6px; - } - - .changelog-featured { - grid-template-columns: 1fr; - grid-template-areas: - 'notices' - 'side'; - } - - .changelog-featured-side { - width: 100%; - margin-left: 0; - } - - .changelog-featured.is-video-expanded { - grid-template-areas: - 'side' - 'notices'; - } - - .changelog-notice-scribble { - left: 56px; - bottom: 5px; - } - - .changelog-notice-scribble-text { - font-size: 14px; - } -} - -/* Platform/Build Notice */ -.changelog-notice { - position: relative; - margin-bottom: 8px; -} - -.changelog-notice-body { - display: flex; - align-items: flex-start; - gap: 12px; - padding: 12px 16px; - border-radius: 8px; - min-height: 56px; - position: relative; - overflow: hidden; -} - -.changelog-notice-with-annotation { - padding-bottom: 10px; -} - -.changelog-notice-info { - --changelog-notice-glow-ring: rgba(59, 130, 246, 0.14); - --changelog-notice-glow-soft: rgba(59, 130, 246, 0.1); - --changelog-notice-glow-strong: rgba(96, 165, 250, 0.18); - background: rgba(59, 130, 246, 0.1); - border: 1px solid rgba(59, 130, 246, 0.3); - color: var(--blue-200); -} - -.changelog-notice-warning { - --changelog-notice-glow-ring: rgba(245, 158, 11, 0.14); - --changelog-notice-glow-soft: rgba(245, 158, 11, 0.1); - --changelog-notice-glow-strong: rgba(251, 191, 36, 0.18); - background: rgba(245, 158, 11, 0.1); - border: 1px solid rgba(245, 158, 11, 0.3); - color: var(--amber-300); -} - -.changelog-notice-success { - --changelog-notice-glow-ring: rgba(74, 222, 128, 0.14); - --changelog-notice-glow-soft: rgba(34, 197, 94, 0.12); - --changelog-notice-glow-strong: rgba(74, 222, 128, 0.2); - background: rgba(34, 197, 94, 0.12); - border: 1px solid rgba(74, 222, 128, 0.32); - color: #bbf7d0; -} - -.changelog-notice-danger { - --changelog-notice-glow-ring: rgba(248, 113, 113, 0.14); - --changelog-notice-glow-soft: rgba(239, 68, 68, 0.1); - --changelog-notice-glow-strong: rgba(248, 113, 113, 0.18); - --changelog-notice-sheen-width: 58%; - --changelog-notice-sheen-start: -125%; - --changelog-notice-sheen-end: 190%; - background: rgba(239, 68, 68, 0.12); - border: 1px solid rgba(248, 113, 113, 0.32); - color: #fecaca; -} - -.changelog-notice-animated { - animation: changelog-notice-pulse 6s ease-in-out infinite; - animation-delay: var(--changelog-notice-delay, 0s); - box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.12)), 0 0 24px var(--changelog-notice-glow-soft, rgba(34, 197, 94, 0.12)); -} - -.changelog-notice-animated::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: var(--changelog-notice-sheen-width, 100%); - z-index: 0; - background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.18) 48%, transparent 100%); - transform: translateX(var(--changelog-notice-sheen-start, -130%)); - animation: changelog-notice-sheen 6s ease-in-out infinite; - animation-delay: var(--changelog-notice-delay, 0s); - pointer-events: none; -} - -.changelog-notice-icon { - flex-shrink: 0; - margin-top: 2px; - position: relative; - z-index: 1; -} - -.changelog-notice-content { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; - position: relative; - z-index: 1; -} - -.changelog-notice-title { - font-size: 13px; - font-weight: 600; - line-height: 1.35; -} - -.changelog-notice-message { - font-size: 12px; - opacity: 0.8; - line-height: 1.35; -} - -.changelog-notice-link { - color: currentColor; - font-weight: 700; - text-decoration: underline; - text-decoration-color: rgba(255, 255, 255, 0.28); - text-underline-offset: 2px; -} - -.changelog-notice-link:hover { - text-decoration-color: currentColor; -} - -.changelog-notice-scribble { - position: absolute; - left: 62px; - bottom: 6px; - z-index: 2; - pointer-events: none; - opacity: 0.92; - display: inline-flex; - align-items: flex-end; - gap: 3px; - transform: rotate(10deg); -} - -.changelog-notice-scribble-text { - position: relative; - display: inline-block; - color: #ff5a5f; - font-family: 'Segoe Print', 'Bradley Hand', 'Marker Felt', 'Comic Sans MS', cursive; - font-size: 15px; - font-weight: 700; - line-height: 1; - letter-spacing: -0.03em; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.08), 0 2px 10px rgba(127, 29, 29, 0.24); -} - -.changelog-notice-scribble-text::before { - content: ''; - position: absolute; - left: -3px; - right: -3px; - bottom: 1px; - height: 7px; - border-bottom: 1.5px solid rgba(255, 90, 95, 0.42); - border-radius: 50%; - transform: rotate(-2deg); -} - -.changelog-notice-scribble-arrow { - width: 24px; - height: 12px; - margin-bottom: 4px; - overflow: visible; -} - -.changelog-notice-scribble-arrow path { - stroke: rgba(255, 90, 95, 0.84); - stroke-width: 2.2; - stroke-linecap: round; - stroke-linejoin: round; -} - -@keyframes changelog-notice-pulse { - 0%, 6%, 16%, 100% { - box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.12)), 0 0 18px var(--changelog-notice-glow-soft, rgba(34, 197, 94, 0.08)); - } - 8%, 12% { - box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.22)), 0 0 30px var(--changelog-notice-glow-strong, rgba(74, 222, 128, 0.18)); - } -} - -@keyframes changelog-notice-sheen { - 0%, 4% { - transform: translateX(var(--changelog-notice-sheen-start, -130%)); - } - 14%, 100% { - transform: translateX(var(--changelog-notice-sheen-end, 130%)); - } -} - -/* Time group */ -.changelog-group { - display: flex; - flex-direction: column; - gap: 10px; -} - -.changelog-group-header { - display: flex; - align-items: center; - gap: 10px; -} - -.changelog-group-label { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); -} - -.changelog-group-date { - font-size: 11px; - color: var(--text-secondary); - opacity: 0.7; -} - -.changelog-group-line { - flex: 1; - height: 1px; - background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, transparent 100%); -} - -.changelog-group-items { - display: flex; - gap: 12px; -} - -.changelog-column { - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -/* Section divider within time group */ -.changelog-section-divider { - display: flex; - align-items: center; - gap: 10px; - margin: 8px 0 4px 0; - padding-top: 8px; -} - -.changelog-section-label { - font-size: 12px; - font-weight: 500; - color: var(--amber-600); - white-space: nowrap; -} - -.changelog-section-line { - flex: 1; - height: 1px; - background: linear-gradient(90deg, rgba(245, 159, 0, 0.3) 0%, transparent 100%); -} - -/* Change item */ -.changelog-item { - display: flex; - flex-direction: column; - padding: 8px 12px; - border-radius: 8px; - background: rgba(255, 255, 255, 0.02); - border: 1px solid transparent; - transition: background 0.15s, border-color 0.15s, box-shadow 0.15s; -} - -.changelog-item.has-description { - cursor: pointer; -} - -.changelog-item.has-description:hover { - background: rgba(255, 255, 255, 0.05); -} - -.changelog-item-community { - position: relative; - overflow: hidden; - background: - linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(255, 255, 255, 0.03) 42%, rgba(250, 204, 21, 0.08) 100%); - border-color: rgba(245, 158, 11, 0.26); - box-shadow: - inset 0 0 0 1px rgba(250, 204, 21, 0.06), - 0 0 0 1px rgba(0, 0, 0, 0.08); -} - -.changelog-item-community::after { - content: ''; - position: absolute; - inset: 0 auto 0 -40%; - width: 40%; - background: linear-gradient(90deg, transparent 0%, rgba(255, 235, 170, 0.04) 35%, rgba(255, 235, 170, 0.26) 50%, rgba(255, 235, 170, 0.04) 65%, transparent 100%); - transform: skewX(-18deg); - animation: changelog-community-sheen 6.4s ease-in-out infinite; - pointer-events: none; -} - -.changelog-item-header { - display: flex; - align-items: center; - gap: 10px; -} - -.changelog-icon { - width: 22px; - height: 22px; - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.changelog-icon-new { - background: rgba(81, 207, 102, 0.15); - color: var(--green-300); -} - -.changelog-icon-fix { - background: rgba(77, 171, 247, 0.15); - color: var(--blue-300); -} - -.changelog-icon-improve { - background: rgba(255, 170, 0, 0.15); - color: var(--warning); -} - -.changelog-icon-refactor { - background: rgba(168, 85, 247, 0.15); - color: var(--purple-light); -} - -.changelog-item .changelog-title { - flex: 1; - font-size: 13px; - font-weight: 500; - color: var(--text-primary); - line-height: 1.3; -} - -.changelog-badge { - display: inline-flex; - align-items: center; - justify-content: center; - height: 20px; - padding: 0 8px; - border-radius: 999px; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - white-space: nowrap; - border: 1px solid transparent; - flex-shrink: 0; -} - -.changelog-badge-community { - background: rgba(245, 158, 11, 0.14); - border-color: rgba(251, 191, 36, 0.26); - color: #fde68a; - box-shadow: - 0 0 0 1px rgba(250, 204, 21, 0.04), - 0 0 18px rgba(245, 158, 11, 0.12); - animation: changelog-community-badge 3.8s ease-in-out infinite; -} - -.changelog-expand { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - color: var(--text-secondary); - opacity: 0.5; -} - -.changelog-description-wrapper { - display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 0.2s ease-out; -} - -.changelog-item.expanded .changelog-description-wrapper { - grid-template-rows: 1fr; -} - -.changelog-description { - overflow: hidden; - margin-left: 32px; - font-size: 12px; - color: var(--text-secondary); - line-height: 1.4; - display: flex; - flex-direction: column; - gap: 6px; -} - -.changelog-item.expanded .changelog-description { - padding-top: 6px; -} - -.changelog-contributor-label, -.changelog-contributor-link { - width: fit-content; - font-size: 11px; - font-weight: 600; - letter-spacing: 0.01em; - color: rgba(253, 230, 138, 0.82); - text-decoration: none; -} - -.changelog-contributor-link:hover { - color: #fef3c7; - text-decoration: underline; -} - -.changelog-commit-link { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--text-tertiary); - font-size: 11px; - font-family: monospace; - text-decoration: none; - opacity: 0.7; - transition: opacity 0.15s, color 0.15s; - width: fit-content; -} - -.changelog-commit-link:hover { - opacity: 1; - color: var(--accent); -} - -.changelog-commit-link svg { - flex-shrink: 0; -} - -.changelog-commits { - display: flex; - flex-wrap: wrap; - gap: 4px 8px; - margin-top: 4px; -} - -.changelog-commit-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 18px; - height: 18px; - padding: 0 5px; - border-radius: 9px; - background: rgba(255, 255, 255, 0.06); - color: var(--text-tertiary); - font-size: 10px; - font-weight: 500; - flex-shrink: 0; - margin-left: auto; -} - -@keyframes changelog-community-badge { - 0%, - 100% { - box-shadow: - 0 0 0 1px rgba(250, 204, 21, 0.04), - 0 0 18px rgba(245, 158, 11, 0.12); - } - 50% { - box-shadow: - 0 0 0 1px rgba(253, 224, 71, 0.12), - 0 0 26px rgba(245, 158, 11, 0.24); - } -} - -@keyframes changelog-community-sheen { - 0%, - 72%, - 100% { - transform: translateX(0) skewX(-18deg); - opacity: 0; - } - 8% { - opacity: 1; - } - 26% { - transform: translateX(360%) skewX(-18deg); - opacity: 1; - } - 32% { - transform: translateX(420%) skewX(-18deg); - opacity: 0; - } -} - -/* Known issues */ -.changelog-issues { - margin: 0 24px 16px; - padding: 12px 14px; - background: rgba(255, 68, 68, 0.05); - border: 1px solid rgba(255, 68, 68, 0.12); - border-radius: 10px; -} - -.changelog-issues-header { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--danger-light); - margin-bottom: 8px; -} - -.changelog-issues-list { - display: flex; - flex-direction: column; - gap: 4px; -} - -.changelog-issue-item { - font-size: 12px; - color: var(--text-secondary); - line-height: 1.4; - padding-left: 22px; - position: relative; -} - -.changelog-issue-item::before { - content: '•'; - position: absolute; - left: 8px; - color: var(--danger-light); - opacity: 0.5; -} - -/* Footer - sticky at bottom, content scrolls underneath */ -.changelog-footer { - position: sticky; - bottom: 0; - padding: 24px 24px 16px; - border-top: none; - display: flex; - align-items: center; - justify-content: flex-end; - z-index: 20; - background: linear-gradient(to top, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 30%, rgba(22, 22, 22, 0.6) 60%, transparent 100%); - flex-shrink: 0; -} - -.changelog-dont-show { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: rgba(255, 255, 255, 0.45); - cursor: pointer; - white-space: nowrap; - user-select: none; - flex-shrink: 0; -} - -.changelog-dont-show input[type="checkbox"] { - accent-color: var(--accent, #4f9cf9); - cursor: pointer; - width: 13px; - height: 13px; -} - -.changelog-dont-show:hover { - color: rgba(255, 255, 255, 0.7); -} - -/* ======================================== - Splash Screen Dialog - ======================================== */ - -.splash-dialog { - width: 680px; - max-height: 85vh; - padding: 0; - text-align: left; - display: flex; - flex-direction: column; - align-items: stretch; - overflow-y: auto; - background: transparent; - border: none; - box-shadow: none; - isolation: isolate; -} - -.splash-header { - position: sticky; - top: 0; - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 24px 16px; - z-index: 20; - background: linear-gradient(to bottom, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 40%, rgba(22, 22, 22, 0.6) 70%, transparent 100%); - flex-shrink: 0; -} - -.splash-header-left { - display: flex; - align-items: center; - gap: 12px; - min-width: 0; - flex: 1 1 auto; -} - -.splash-header-right { - flex: 0 0 auto; - margin-left: auto; -} - -.splash-content { - position: relative; - z-index: 0; - padding: 0 24px 16px; - display: flex; - flex-direction: column; - gap: 18px; - background: rgba(22, 22, 22, 0.95); -} - -.splash-video { - width: 100%; -} - -.splash-video .changelog-video-container { - border-radius: 10px; - box-shadow: 0 20px 44px rgba(0, 0, 0, 0.32); -} - -.splash-notices { - display: flex; - flex-direction: column; - gap: 10px; -} - -.splash-footer { - position: sticky; - bottom: 0; - padding: 16px 24px 18px; - border-top: none; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - z-index: 20; - background: linear-gradient(to top, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 30%, rgba(22, 22, 22, 0.6) 60%, transparent 100%); - flex-shrink: 0; -} - -.splash-footer-buttons { - display: flex; - align-items: center; - gap: 10px; -} - -.splash-changelog-button { - display: inline-flex; - align-items: center; - justify-content: center; - height: 42px; - padding: 0 18px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: transparent; - color: rgba(255, 255, 255, 0.6); - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; -} - -.splash-changelog-button:hover { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.85); -} - -[data-theme="light"] .splash-header { - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.97) 0%, rgba(255, 255, 255, 0.92) 40%, rgba(255, 255, 255, 0.6) 70%, transparent 100%); -} - -[data-theme="light"] .splash-content { - background: rgba(255, 255, 255, 0.95); -} - -[data-theme="light"] .splash-footer { - background: linear-gradient(to top, rgba(255, 255, 255, 0.97) 0%, rgba(255, 255, 255, 0.92) 30%, rgba(255, 255, 255, 0.6) 60%, transparent 100%); -} - -[data-theme="light"] .splash-changelog-button { - border-color: rgba(0, 0, 0, 0.12); - color: rgba(0, 0, 0, 0.5); -} - -[data-theme="light"] .splash-changelog-button:hover { - background: rgba(0, 0, 0, 0.04); - border-color: rgba(0, 0, 0, 0.18); - color: rgba(0, 0, 0, 0.7); -} - -@media (max-width: 760px) { - .splash-dialog { - width: 95vw; - } - - .splash-header { - flex-direction: column; - gap: 8px; - } - - .splash-footer { - flex-direction: column; - gap: 12px; - } - - .splash-footer-buttons { - width: 100%; - justify-content: flex-end; - } -} - -/* Changelog calendar section - full width at top of changelog */ -.changelog-calendar-section { - display: flex; - justify-content: center; - padding: 4px 0 8px; -} - -.changelog-calendar-section .changelog-calendar { - justify-content: center; -} - -/* Legacy styles kept for backward compatibility */ -.whats-new-dialog:not(.changelog-dialog):not(.splash-dialog) { - width: 420px; - max-height: 80vh; - padding: 28px 32px 24px; - text-align: left; - overflow-y: auto; -} - -/* ======================================== - IndexedDB Error Dialog - ======================================== */ - -.indexeddb-error-dialog { - width: 480px; - max-width: 90vw; - padding: 32px; - text-align: center; -} - -.indexeddb-error-icon { - margin-bottom: 16px; -} - -.indexeddb-error-title { - font-size: 20px; - font-weight: 600; - color: var(--gray-0); - margin: 0 0 20px 0; -} - -.indexeddb-error-content { - text-align: left; -} - -.indexeddb-error-description { - color: rgba(255, 255, 255, 0.7); - font-size: 14px; - line-height: 1.5; - margin: 0 0 16px 0; -} - -.indexeddb-error-note { - background: rgba(34, 197, 94, 0.1); - border: 1px solid rgba(34, 197, 94, 0.3); - border-radius: 8px; - padding: 12px 14px; - font-size: 13px; - color: rgba(255, 255, 255, 0.85); - margin-bottom: 20px; -} - -.indexeddb-error-note strong { - color: var(--success); -} - -.indexeddb-error-steps { - background: rgba(255, 255, 255, 0.03); - border-radius: 8px; - padding: 16px; - margin-bottom: 16px; -} - -.indexeddb-error-steps h3 { - font-size: 13px; - font-weight: 600; - color: rgba(255, 255, 255, 0.9); - margin: 0 0 12px 0; -} - -.indexeddb-error-steps ol { - margin: 0; - padding-left: 20px; - color: rgba(255, 255, 255, 0.7); - font-size: 13px; - line-height: 1.8; -} - -.indexeddb-error-steps li { - margin-bottom: 4px; -} - -.indexeddb-error-steps kbd { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; -} - -.indexeddb-error-steps strong { - color: rgba(255, 255, 255, 0.9); -} - -.indexeddb-error-alternative { - font-size: 12px; - color: rgba(255, 255, 255, 0.5); - line-height: 1.6; - margin: 0; -} - -.indexeddb-error-alternative code { - display: block; - margin: 8px 0; - padding: 8px 10px; - background: rgba(0, 0, 0, 0.3); - border-radius: 4px; - font-size: 11px; - color: rgba(255, 255, 255, 0.7); - word-break: break-all; -} - -.indexeddb-error-buttons { - display: flex; - gap: 12px; - margin-top: 24px; - justify-content: center; -} - -.indexeddb-error-btn { - padding: 10px 20px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; -} - -.indexeddb-error-btn.secondary { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - color: rgba(255, 255, 255, 0.7); -} - -.indexeddb-error-btn.secondary:hover { - background: rgba(255, 255, 255, 0.12); - color: rgba(255, 255, 255, 0.9); -} - -.indexeddb-error-btn.primary { - background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); - border: none; - color: var(--text-on-accent); -} - -.indexeddb-error-btn.primary:hover { - background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); -} - -/* Linux Vulkan Warning Banner */ -.linux-vulkan-warning { - background: linear-gradient(135deg, #7c2d12 0%, #9a3412 100%); - border-bottom: 1px solid #ea580c; - padding: 8px 16px; - flex-shrink: 0; -} - -.linux-vulkan-warning-content { - display: flex; - align-items: center; - gap: 12px; - max-width: 1200px; - margin: 0 auto; -} - -.linux-vulkan-warning-icon { - font-size: 16px; - flex-shrink: 0; -} - -.linux-vulkan-warning-text { - flex: 1; - font-size: 13px; - color: var(--gray-0); - line-height: 1.4; -} - -.linux-vulkan-warning-text code { - background: rgba(0, 0, 0, 0.3); - padding: 2px 6px; - border-radius: 4px; - font-family: 'Monaco', 'Menlo', monospace; - font-size: 12px; - color: var(--warning-light); - user-select: text; - -webkit-user-select: text; -} - -.linux-vulkan-warning-dismiss { - background: rgba(0, 0, 0, 0.2); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 4px; - color: var(--gray-0); - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - font-size: 14px; - flex-shrink: 0; - transition: all 0.15s ease; -} - -.linux-vulkan-warning-dismiss:hover { - background: rgba(0, 0, 0, 0.4); - border-color: rgba(255, 255, 255, 0.4); -} - -/* ============================================ - Tutorial Overlay - ============================================ */ - -.tutorial-backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 10001; - cursor: pointer; - animation: tutorial-fade-in 300ms ease forwards; -} - -.tutorial-backdrop.closing .tutorial-overlay-svg, -.tutorial-backdrop.closing .tutorial-tooltip, -.tutorial-backdrop.closing .tutorial-highlight-ring { - animation: tutorial-fade-out 400ms ease forwards; -} - -.tutorial-clippy-wrapper { - position: fixed; - width: 340px; - pointer-events: none; - z-index: 10003; - transition: left 400ms ease, top 400ms ease, width 400ms ease; -} - -.tutorial-clippy-wrapper--welcome { - width: 480px; -} - -@keyframes tutorial-fade-in { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes tutorial-fade-out { - from { opacity: 1; } - to { opacity: 0; } -} - -.tutorial-overlay-svg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; -} - -.tutorial-tooltip { - position: fixed; - width: 340px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 16px; - padding: 20px; - z-index: 10002; - pointer-events: none; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); - animation: tutorial-tooltip-in 300ms ease forwards; - overflow: visible; - transition: left 400ms ease, top 400ms ease, width 400ms ease; -} - -.tutorial-tooltip-content { - display: flex; - gap: 14px; - align-items: flex-start; -} - -.tutorial-tooltip-text { - flex: 1; - min-width: 0; -} - -.tutorial-clippy { - position: absolute; - width: 120px; - height: auto; - top: -55px; - right: -35px; - object-fit: contain; - filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5)); - z-index: 1; -} - -@keyframes tutorial-tooltip-in { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } -} - -.tutorial-tooltip-arrow { - position: absolute; - width: 12px; - height: 12px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - transform: rotate(45deg); -} - -/* Arrow positions — points toward the highlighted panel */ -.tutorial-tooltip-arrow--top { - bottom: -7px; - left: 50%; - margin-left: -6px; - border-top: none; - border-left: none; -} - -.tutorial-tooltip-arrow--bottom { - top: -7px; - left: 50%; - margin-left: -6px; - border-bottom: none; - border-right: none; -} - -.tutorial-tooltip-arrow--left { - right: -7px; - top: 50%; - margin-top: -6px; - border-bottom: none; - border-left: none; -} - -.tutorial-tooltip-arrow--right { - left: -7px; - top: 50%; - margin-top: -6px; - border-top: none; - border-right: none; -} - -.tutorial-tooltip-step { - font-size: 11px; - color: var(--accent); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 6px; - font-weight: 600; -} - -.tutorial-tooltip-title { - font-size: 18px; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 8px; -} - -.tutorial-tooltip-desc { - font-size: 13px; - line-height: 1.5; - color: var(--text-secondary); - margin-bottom: 16px; -} - -.tutorial-dots { - display: flex; - gap: 6px; - justify-content: center; - margin-bottom: 10px; -} - -.tutorial-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.15); - transition: background 300ms ease; -} - -.tutorial-dot.active { - background: var(--accent); -} - -.tutorial-dot.completed { - background: rgba(45, 140, 235, 0.4); -} - -.tutorial-tooltip-hint { - text-align: center; - font-size: 11px; - color: var(--text-secondary); - opacity: 0.7; -} - -.tutorial-skip-btn { - display: block; - margin: 10px auto 0; - padding: 4px 16px; - background: transparent; - border: 1px solid var(--text-secondary); - border-radius: 4px; - color: var(--text-secondary); - font-size: 11px; - cursor: pointer; - pointer-events: auto; - opacity: 0.7; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; -} - -.tutorial-skip-btn:hover { - opacity: 1; - color: var(--text-primary); - border-color: var(--text-primary); -} - -/* Part 2: Yellow highlight ring for timeline element steps */ -.tutorial-highlight-ring { - position: fixed; - border: 2px solid var(--amber-300); - border-radius: 8px; - z-index: 10002; - pointer-events: none; - box-shadow: 0 0 12px rgba(255, 215, 0, 0.4), inset 0 0 12px rgba(255, 215, 0, 0.1); - animation: tutorial-ring-pulse 1.5s ease-in-out infinite; - transition: left 400ms ease, top 400ms ease, width 400ms ease, height 400ms ease; -} - -@keyframes tutorial-ring-pulse { - 0%, 100% { box-shadow: 0 0 12px rgba(255, 215, 0, 0.4), inset 0 0 12px rgba(255, 215, 0, 0.1); } - 50% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.7), inset 0 0 20px rgba(255, 215, 0, 0.2); } -} - -/* Tutorial Welcome Screen — wider tooltip for the button grid */ -.tutorial-tooltip--welcome { - width: 480px; - pointer-events: auto; - cursor: default; -} - -.tutorial-welcome-title { - font-size: 17px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.tutorial-welcome-subtitle { - font-size: 12px; - color: var(--text-secondary); - margin-bottom: 16px; -} - -.tutorial-welcome-grid { - display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 8px; -} - -.tutorial-welcome-btn { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - padding: 12px 8px; - border-radius: 10px; - border: 1px solid var(--border-color); - background: var(--bg-primary); - cursor: pointer; - transition: all 0.15s ease; -} - -.tutorial-welcome-btn:hover { - border-color: var(--accent); - background: var(--bg-hover); - transform: translateY(-1px); -} - -.tutorial-welcome-icon { - width: 44px; - height: 44px; - display: flex; - align-items: center; - justify-content: center; -} - -.tutorial-welcome-icon img { - width: 100%; - height: 100%; - object-fit: contain; -} - -.tutorial-welcome-icon--beginner { - width: 44px; - height: 44px; - border-radius: 10px; - background: linear-gradient(135deg, var(--green-500) 0%, #1A8A4A 100%); - display: flex; - align-items: center; - justify-content: center; - font-size: 22px; - color: var(--text-on-accent); -} - -.tutorial-welcome-label { - font-size: 11px; - font-weight: 500; - color: var(--text-primary); - text-align: center; - white-space: nowrap; -} - -/* ============================ - Tutorial Campaign Dialog - ============================ */ - -.tutorial-campaign-backdrop { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.6); - z-index: 10000; - display: flex; - align-items: center; - justify-content: center; - animation: tutorial-fade-in 200ms ease forwards; -} - -.tutorial-campaign-dialog { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 12px; - width: 640px; - max-width: 90vw; - max-height: 80vh; - display: flex; - flex-direction: column; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); - animation: tutorial-tooltip-in 200ms ease forwards; -} - -.tutorial-campaign-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; -} - -.tutorial-campaign-header-text { - display: flex; - align-items: baseline; - gap: 12px; -} - -.tutorial-campaign-header h2 { - margin: 0; - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} - -.tutorial-campaign-progress { - font-size: 12px; - color: var(--text-secondary); -} - -.tutorial-campaign-close { - background: none; - border: none; - color: var(--text-secondary); - font-size: 20px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - transition: color 0.15s; -} - -.tutorial-campaign-close:hover { - color: var(--text-primary); -} - -.tutorial-campaign-body { - overflow-y: auto; - padding: 16px 20px; - display: flex; - flex-direction: column; - gap: 20px; -} - -.tutorial-campaign-category-title { - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); - margin: 0 0 8px; -} - -.tutorial-campaign-grid { - display: flex; - flex-direction: column; - gap: 6px; -} - -.tutorial-campaign-card { - display: flex; - align-items: center; - gap: 12px; - padding: 10px 14px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 8px; - cursor: pointer; - text-align: left; - transition: all 0.15s ease; - width: 100%; -} - -.tutorial-campaign-card:hover { - border-color: var(--accent); - background: var(--bg-hover); -} - -.tutorial-campaign-card.completed { - opacity: 0.7; -} - -.tutorial-campaign-card.completed:hover { - opacity: 1; -} - -.tutorial-campaign-card-icon { - font-size: 22px; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.tutorial-campaign-card-info { - flex: 1; - min-width: 0; -} - -.tutorial-campaign-card-title { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 2px; -} - -.tutorial-campaign-card-desc { - font-size: 11px; - color: var(--text-secondary); - line-height: 1.4; -} - -.tutorial-campaign-card-meta { - font-size: 10px; - color: var(--text-secondary); - margin-top: 4px; - display: flex; - align-items: center; - gap: 8px; -} - -.tutorial-campaign-card-check { - color: var(--green-500); - font-weight: 700; - font-size: 12px; -} - -/* ============================ - Slot Grid View (Resolume-style) - ============================ */ - -.slot-grid-container { - position: absolute; - inset: 0; - z-index: 50; - overflow: auto; - padding: 8px; - background: var(--gray-850); - pointer-events: auto; -} - -.slot-grid-resolume { - display: inline-grid; - gap: 6px; -} - -.slot-grid-corner { - /* Empty top-left corner cell */ -} - -.slot-grid-col-header { - display: flex; - align-items: center; - justify-content: center; - height: 24px; - font-size: 11px; - font-weight: 600; - color: var(--gray-200); - background: var(--bg-input); - border-radius: 3px; -} - -.slot-grid-col-header-clickable { - cursor: pointer; - transition: background 0.15s, color 0.15s; -} - -.slot-grid-col-header-clickable:hover { - background: var(--bg-active); - color: var(--gray-75); -} - -.slot-grid-row-label { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 4px; - width: 40px; - font-size: 11px; - font-weight: 600; - color: var(--gray-200); - background: var(--bg-input); - border-radius: 3px; -} - -.slot-grid-row-letter { - line-height: 1; -} - -.slot-grid-opacity-slider { - -webkit-appearance: none; - appearance: none; - writing-mode: vertical-lr; - direction: rtl; - width: 14px; - height: 60px; - background: transparent; - cursor: pointer; - margin: 0; - padding: 0; -} - -.slot-grid-opacity-slider::-webkit-slider-runnable-track { - width: 4px; - height: 100%; - background: var(--gray-500); - border-radius: 2px; -} - -.slot-grid-opacity-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--gray-100); - margin-left: -3px; - cursor: pointer; -} - -.slot-grid-opacity-slider::-webkit-slider-thumb:hover { - background: var(--gray-0); -} - -.slot-grid-opacity-slider::-moz-range-track { - width: 4px; - height: 100%; - background: var(--gray-500); - border-radius: 2px; -} - -.slot-grid-opacity-slider::-moz-range-thumb { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--gray-100); - border: none; - cursor: pointer; -} - -.slot-grid-opacity-slider::-moz-range-thumb:hover { - background: var(--gray-0); -} - -.slot-grid-item { - position: relative; - border-radius: 4px; - overflow: hidden; - cursor: pointer; - border: 2px solid transparent; - background: var(--bg-input); - transition: border-color 0.15s, box-shadow 0.15s; -} - -.slot-grid-item:hover { - border-color: rgba(255, 255, 255, 0.3); - box-shadow: 0 0 8px rgba(255, 255, 255, 0.1); -} - -.slot-grid-item.active { - border-color: #4a9eff; - box-shadow: 0 0 12px rgba(74, 158, 255, 0.3); -} - -.slot-grid-item.layer-active { - border-color: #4aff6e; - box-shadow: 0 0 12px rgba(74, 255, 110, 0.3); -} - -.slot-grid-item.selected:not(.active) { - border-color: #f0c45a; - box-shadow: 0 0 12px rgba(240, 196, 90, 0.28); -} - -.slot-grid-item.drag-over { - border-color: #ff9800; - box-shadow: 0 0 12px rgba(255, 152, 0, 0.4); -} - -.slot-grid-item.empty { - background: var(--gray-850); - border: 1px dashed rgba(255, 255, 255, 0.06); - cursor: pointer; -} - -.slot-grid-item.empty:hover { - border-color: rgba(255, 255, 255, 0.2); - background: var(--bg-input); - box-shadow: 0 0 6px rgba(255, 255, 255, 0.05); -} - -.slot-grid-item.empty.drag-over { - border: 2px solid #ff9800; - box-shadow: 0 0 12px rgba(255, 152, 0, 0.4); -} - -/* Slot composition name */ -.slot-grid-name { - position: absolute; - top: 3px; - left: 4px; - right: 4px; - font-size: 10px; - color: var(--gray-25); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - pointer-events: none; - z-index: 2; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.7); -} - -/* Slot time display (elapsed / duration) */ -.slot-grid-time { - position: absolute; - bottom: 2px; - left: 4px; - text-align: left; - font-size: 10px; - white-space: nowrap; - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; - color: rgba(255, 255, 255, 0.85); - pointer-events: none; - z-index: 2; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.7); -} - -/* Slot playhead line */ -.slot-grid-playhead { - position: absolute; - top: 18px; - bottom: 18px; - width: 1.5px; - background: #ff3b3b; - pointer-events: none; - z-index: 1; -} - -.slot-grid-context-menu { - position: fixed; - z-index: 9999; - background: var(--bg-active); - border: 1px solid var(--gray-400); - border-radius: 4px; - padding: 2px 0; - min-width: 140px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); -} - -.slot-grid-context-menu button { - display: block; - width: 100%; - padding: 6px 12px; - background: none; - border: none; - color: var(--gray-75); - font-size: 12px; - text-align: left; - cursor: pointer; -} - -.slot-grid-context-menu button:hover { - background: var(--gray-450); - color: var(--gray-0); -} - -/* ============================ - Interactive Tutorial — Fake Cursor - ============================ */ - -.tutorial-fake-cursor { - position: fixed; - z-index: 10004; - pointer-events: none; - transition-property: left, top; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - /* transitionDuration set via inline style */ - filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); -} - -.tutorial-fake-cursor svg { - display: block; -} - -.tutorial-fake-cursor--clicking { - animation: tutorial-cursor-click 250ms ease; -} - -@keyframes tutorial-cursor-click { - 0% { transform: scale(1); } - 40% { transform: scale(0.82); } - 100% { transform: scale(1); } -} - -.tutorial-click-ripple { - position: absolute; - top: 4px; - left: 4px; - width: 20px; - height: 20px; - border-radius: 50%; - background: rgba(255, 215, 0, 0.5); - animation: tutorial-click-ripple-expand 500ms ease forwards; - pointer-events: none; -} - -@keyframes tutorial-click-ripple-expand { - 0% { transform: scale(0.5); opacity: 1; } - 100% { transform: scale(3); opacity: 0; } -} diff --git a/src/App.tsx b/src/App.tsx index b76125bb0..2552d6d7f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,8 @@ import { useSettingsStore } from './stores/settingsStore'; import { projectDB } from './services/projectDB'; import { projectFileService } from './services/projectFileService'; import { APP_VERSION, shouldAutoShowChangelog } from './version'; -import './App.css'; +import './styles/app-shell.css'; +import './styles/shared-controls.css'; // Dev test pages - lazy loaded to avoid bloating main bundle // Access via ?test=parallel-decode diff --git a/src/components/common/IndexedDBErrorDialog.css b/src/components/common/IndexedDBErrorDialog.css new file mode 100644 index 000000000..32897c1df --- /dev/null +++ b/src/components/common/IndexedDBErrorDialog.css @@ -0,0 +1,142 @@ +@layer overlays { +/* ======================================== + IndexedDB Error Dialog + ======================================== */ + +.indexeddb-error-dialog { + width: 480px; + max-width: 90vw; + padding: 32px; + text-align: center; +} + +.indexeddb-error-icon { + margin-bottom: 16px; +} + +.indexeddb-error-title { + font-size: 20px; + font-weight: 600; + color: var(--gray-0); + margin: 0 0 20px 0; +} + +.indexeddb-error-content { + text-align: left; +} + +.indexeddb-error-description { + color: rgba(255, 255, 255, 0.7); + font-size: 14px; + line-height: 1.5; + margin: 0 0 16px 0; +} + +.indexeddb-error-note { + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.3); + border-radius: 8px; + padding: 12px 14px; + font-size: 13px; + color: rgba(255, 255, 255, 0.85); + margin-bottom: 20px; +} + +.indexeddb-error-note strong { + color: var(--success); +} + +.indexeddb-error-steps { + background: rgba(255, 255, 255, 0.03); + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; +} + +.indexeddb-error-steps h3 { + font-size: 13px; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); + margin: 0 0 12px 0; +} + +.indexeddb-error-steps ol { + margin: 0; + padding-left: 20px; + color: rgba(255, 255, 255, 0.7); + font-size: 13px; + line-height: 1.8; +} + +.indexeddb-error-steps li { + margin-bottom: 4px; +} + +.indexeddb-error-steps kbd { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + padding: 2px 6px; + font-family: inherit; + font-size: 12px; +} + +.indexeddb-error-steps strong { + color: rgba(255, 255, 255, 0.9); +} + +.indexeddb-error-alternative { + font-size: 12px; + color: rgba(255, 255, 255, 0.5); + line-height: 1.6; + margin: 0; +} + +.indexeddb-error-alternative code { + display: block; + margin: 8px 0; + padding: 8px 10px; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + font-size: 11px; + color: rgba(255, 255, 255, 0.7); + word-break: break-all; +} + +.indexeddb-error-buttons { + display: flex; + gap: 12px; + margin-top: 24px; + justify-content: center; +} + +.indexeddb-error-btn { + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.indexeddb-error-btn.secondary { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.7); +} + +.indexeddb-error-btn.secondary:hover { + background: rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.9); +} + +.indexeddb-error-btn.primary { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + border: none; + color: var(--text-on-accent); +} + +.indexeddb-error-btn.primary:hover { + background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); +} +} diff --git a/src/components/common/IndexedDBErrorDialog.tsx b/src/components/common/IndexedDBErrorDialog.tsx index 6bea9666c..b7c6c5738 100644 --- a/src/components/common/IndexedDBErrorDialog.tsx +++ b/src/components/common/IndexedDBErrorDialog.tsx @@ -2,6 +2,9 @@ // Provides instructions for clearing site data to fix the issue import { useState, useEffect, useCallback } from 'react'; +import './WelcomeOverlay.css'; +import './WhatsNewDialog.css'; +import './IndexedDBErrorDialog.css'; interface IndexedDBErrorDialogProps { onClose: () => void; diff --git a/src/components/common/InfoDialog.css b/src/components/common/InfoDialog.css new file mode 100644 index 000000000..5f510cf51 --- /dev/null +++ b/src/components/common/InfoDialog.css @@ -0,0 +1,177 @@ +@layer overlays { +/* ============================================ + Info Dialog - About MasterSelects + ============================================ */ + +.info-content { + text-align: center; + padding: 8px 0; +} + +.info-description { + font-size: 13px; + color: var(--gray-100); + line-height: 1.6; + margin: 0 0 20px 0; +} + +.info-features { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 20px; +} + +.info-feature { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + background: rgba(255, 255, 255, 0.03); + border-radius: 8px; + font-size: 12px; + color: var(--gray-75); +} + +.info-feature-icon { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 215, 0, 0.05)); + border: 1px solid rgba(255, 215, 0, 0.3); + border-radius: 6px; + font-size: 10px; + font-weight: 700; + color: var(--amber-300); + flex-shrink: 0; +} + +.info-version { + font-size: 11px; + color: var(--gray-250); + margin-top: 8px; +} + +.info-helper-launch { + width: 100%; + margin-top: 14px; + padding: 14px 16px; + display: flex; + align-items: center; + gap: 12px; + background: + linear-gradient(180deg, rgba(22, 29, 46, 0.92), rgba(13, 18, 30, 0.92)), + radial-gradient(circle at top left, rgba(34, 197, 94, 0.18), transparent 55%); + border: 1px solid rgba(56, 189, 248, 0.18); + border-radius: 12px; + color: var(--gray-25); + text-align: left; + cursor: pointer; + transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease; +} + +.info-helper-launch:hover { + transform: translateY(-1px); + border-color: rgba(56, 189, 248, 0.35); + background: + linear-gradient(180deg, rgba(28, 37, 58, 0.94), rgba(16, 22, 36, 0.94)), + radial-gradient(circle at top left, rgba(34, 197, 94, 0.22), transparent 58%); +} + +.info-helper-launch-icon { + width: 38px; + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 10px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + flex-shrink: 0; +} + +.info-helper-launch-copy { + min-width: 0; + display: flex; + flex-direction: column; + gap: 5px; +} + +.info-helper-launch-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.info-helper-launch-title { + font-size: 13px; + font-weight: 600; + color: var(--gray-0); +} + +.info-helper-launch-badge { + padding: 3px 9px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.02em; + border: 1px solid transparent; + flex-shrink: 0; +} + +.info-helper-launch-badge-connected { + color: #86efac; + background: rgba(34, 197, 94, 0.14); + border-color: rgba(34, 197, 94, 0.28); +} + +.info-helper-launch-badge-offline { + color: #fca5a5; + background: rgba(239, 68, 68, 0.14); + border-color: rgba(239, 68, 68, 0.28); +} + +.info-helper-launch-badge-disabled { + color: var(--gray-200); + background: rgba(148, 163, 184, 0.12); + border-color: rgba(148, 163, 184, 0.22); +} + +.info-helper-launch-text { + font-size: 12px; + line-height: 1.5; + color: var(--gray-150); +} + +.info-github-link { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 12px; + padding: 8px 16px; + background: var(--gray-650); + border: 1px solid var(--gray-500); + border-radius: 6px; + color: var(--gray-75); + font-size: 13px; + text-decoration: none; + transition: all 0.15s ease; +} + +.info-github-link:hover { + background: var(--gray-500); + border-color: var(--gray-150); + color: var(--gray-0); +} + +.info-github-link svg { + opacity: 0.8; +} + +.info-github-link:hover svg { + opacity: 1; +} +} diff --git a/src/components/common/InfoDialog.tsx b/src/components/common/InfoDialog.tsx index ac671ca05..e14f28c40 100644 --- a/src/components/common/InfoDialog.tsx +++ b/src/components/common/InfoDialog.tsx @@ -2,6 +2,8 @@ // Same style as WelcomeOverlay import { useState, useEffect, useCallback } from 'react'; +import './WelcomeOverlay.css'; +import './InfoDialog.css'; import { APP_VERSION } from '../../version'; import { NativeHelperStatus } from './NativeHelperStatus'; diff --git a/src/components/common/LinuxVulkanWarning.css b/src/components/common/LinuxVulkanWarning.css new file mode 100644 index 000000000..1936a8320 --- /dev/null +++ b/src/components/common/LinuxVulkanWarning.css @@ -0,0 +1,61 @@ +@layer overlays { +/* Linux Vulkan Warning Banner */ +.linux-vulkan-warning { + background: linear-gradient(135deg, #7c2d12 0%, #9a3412 100%); + border-bottom: 1px solid #ea580c; + padding: 8px 16px; + flex-shrink: 0; +} + +.linux-vulkan-warning-content { + display: flex; + align-items: center; + gap: 12px; + max-width: 1200px; + margin: 0 auto; +} + +.linux-vulkan-warning-icon { + font-size: 16px; + flex-shrink: 0; +} + +.linux-vulkan-warning-text { + flex: 1; + font-size: 13px; + color: var(--gray-0); + line-height: 1.4; +} + +.linux-vulkan-warning-text code { + background: rgba(0, 0, 0, 0.3); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Monaco', 'Menlo', monospace; + font-size: 12px; + color: var(--warning-light); + user-select: text; + -webkit-user-select: text; +} + +.linux-vulkan-warning-dismiss { + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + color: var(--gray-0); + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 14px; + flex-shrink: 0; + transition: all 0.15s ease; +} + +.linux-vulkan-warning-dismiss:hover { + background: rgba(0, 0, 0, 0.4); + border-color: rgba(255, 255, 255, 0.4); +} +} diff --git a/src/components/common/LinuxVulkanWarning.tsx b/src/components/common/LinuxVulkanWarning.tsx index 5c0f78ed6..21553ba39 100644 --- a/src/components/common/LinuxVulkanWarning.tsx +++ b/src/components/common/LinuxVulkanWarning.tsx @@ -1,6 +1,7 @@ // LinuxVulkanWarning - Warning banner for Linux users about Vulkan // Shows when Linux is detected, can be dismissed and won't show again +import './LinuxVulkanWarning.css'; import { useEngineStore } from '../../stores/engineStore'; export function LinuxVulkanWarning() { diff --git a/src/components/common/NativeHelperStatus.css b/src/components/common/NativeHelperStatus.css new file mode 100644 index 000000000..c008aa7fd --- /dev/null +++ b/src/components/common/NativeHelperStatus.css @@ -0,0 +1,422 @@ +@layer overlays { +/* Native Helper dialog */ +.native-helper-dialog { + width: 520px; + max-width: min(520px, 94vw); + padding: 0; + align-items: stretch; + text-align: left; + overflow: hidden; +} + +.native-helper-dialog-header { + display: flex; + flex-direction: column; + gap: 12px; + padding: 24px 24px 18px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.native-helper-title-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.native-helper-title { + margin: 10px 0 0; + font-size: 34px; + line-height: 1; + letter-spacing: -1px; + color: var(--text-primary); +} + +.native-helper-version-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.08); + color: var(--text-secondary); + font-size: 12px; + font-weight: 600; +} + +.native-helper-subtitle { + margin: 0; + max-width: 430px; + color: var(--text-secondary); + font-size: 14px; + line-height: 1.5; +} + +.native-helper-status-pill { + display: inline-flex; + align-items: center; + gap: 8px; + align-self: flex-start; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 600; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + color: var(--text-secondary); +} + +.native-helper-status-pill-connected { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(74, 222, 128, 0.22); + color: #bbf7d0; +} + +.native-helper-status-pill-offline { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(251, 191, 36, 0.22); + color: #fde68a; +} + +.native-helper-status-pill-disabled { + background: rgba(255, 255, 255, 0.04); + color: var(--text-muted); +} + +.native-helper-status-dot { + width: 7px; + height: 7px; + border-radius: 999px; + background: currentColor; + opacity: 0.9; +} + +.native-helper-dialog-body { + display: flex; + flex-direction: column; + gap: 12px; + padding: 20px 24px; +} + +.native-helper-card, +.native-helper-accordion { + background: rgba(255, 255, 255, 0.035); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 16px; +} + +.native-helper-card { + display: flex; + flex-direction: column; + gap: 12px; + padding: 14px 16px; +} + +.native-helper-card-highlight { + background: + radial-gradient(circle at top right, rgba(96, 165, 250, 0.16), transparent 42%), + rgba(255, 255, 255, 0.04); + border-color: rgba(96, 165, 250, 0.18); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); +} + +.native-helper-toggle-row { + display: grid; + grid-template-columns: 18px 1fr; + gap: 12px; + align-items: flex-start; + cursor: pointer; +} + +.native-helper-checkbox { + width: 16px; + height: 16px; + margin-top: 2px; + accent-color: var(--accent); +} + +.native-helper-toggle-text { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + +.native-helper-toggle-title { + color: var(--text-primary); + font-size: 14px; + font-weight: 600; + line-height: 1.35; +} + +.native-helper-toggle-description { + color: var(--text-secondary); + font-size: 12px; + line-height: 1.45; +} + +.native-helper-card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.native-helper-card-kicker { + color: var(--text-muted); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; +} + +.native-helper-card-title { + margin-top: 3px; + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + line-height: 1.2; +} + +.native-helper-chip { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 24px; + padding: 0 9px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + color: var(--text-secondary); + font-size: 11px; + font-weight: 600; + white-space: nowrap; +} + +.native-helper-chip.is-warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(251, 191, 36, 0.22); + color: #fde68a; +} + +.native-helper-card-note { + margin: 0; + color: var(--text-secondary); + font-size: 13px; + line-height: 1.5; +} + +.native-helper-pill-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.native-helper-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + color: var(--text-secondary); + font-size: 12px; + line-height: 1.2; +} + +.native-helper-pill-good { + background: rgba(34, 197, 94, 0.1); + border-color: rgba(74, 222, 128, 0.18); + color: #bbf7d0; +} + +.native-helper-pill-warn { + background: rgba(245, 158, 11, 0.1); + border-color: rgba(251, 191, 36, 0.18); + color: #fde68a; +} + +.native-helper-action-row { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.native-helper-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 38px; + padding: 0 14px; + border-radius: 12px; + border: 1px solid transparent; + font-size: 13px; + font-weight: 600; + text-decoration: none; + transition: all 0.15s ease; + cursor: pointer; +} + +.native-helper-button:disabled { + opacity: 0.55; + cursor: default; +} + +.native-helper-button-primary { + background: linear-gradient(180deg, rgba(45, 140, 235, 0.95), rgba(26, 95, 176, 0.95)); + border-color: rgba(96, 165, 250, 0.26); + color: #fff; +} + +.native-helper-button-primary:hover:not(:disabled) { + transform: translateY(-1px); + filter: brightness(1.05); +} + +.native-helper-button-secondary { + background: rgba(255, 255, 255, 0.04); + border-color: rgba(255, 255, 255, 0.08); + color: var(--text-secondary); +} + +.native-helper-button-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.07); + color: var(--text-primary); +} + +.native-helper-link { + align-self: flex-start; + color: var(--text-muted); + font-size: 12px; + text-decoration: none; +} + +.native-helper-link:hover { + color: var(--text-primary); +} + +.native-helper-accordion { + overflow: hidden; +} + +.native-helper-accordion-toggle { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 14px 16px; + background: transparent; + border: none; + color: var(--text-primary); + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s ease; +} + +.native-helper-accordion-toggle:hover { + background: rgba(255, 255, 255, 0.03); +} + +.native-helper-accordion-body { + display: flex; + flex-direction: column; + gap: 12px; + padding: 0 16px 16px; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.native-helper-install-list { + margin: 0; + padding-left: 18px; + color: var(--text-secondary); + font-size: 13px; + line-height: 1.55; +} + +.native-helper-install-list li + li { + margin-top: 6px; +} + +.native-helper-install-note { + margin: 0; + color: var(--text-muted); + font-size: 12px; + line-height: 1.5; +} + +.native-helper-command-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.native-helper-command { + flex: 1; + min-width: 0; + padding: 10px 12px; + background: rgba(0, 0, 0, 0.22); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + color: var(--text-primary); + font-family: var(--font-mono); + font-size: 12px; +} + +.native-helper-copy-button { + min-height: 36px; + padding: 0 12px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + color: var(--text-secondary); + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s ease; +} + +.native-helper-copy-button:hover { + background: rgba(255, 255, 255, 0.07); + color: var(--text-primary); +} + +.native-helper-footer { + display: flex; + justify-content: center; + padding: 0 24px 20px; +} + +@media (max-width: 640px) { + .native-helper-dialog { + max-width: 94vw; + } + + .native-helper-dialog-header, + .native-helper-dialog-body, + .native-helper-footer { + padding-left: 18px; + padding-right: 18px; + } + + .native-helper-title-row, + .native-helper-card-head { + flex-direction: column; + align-items: flex-start; + } + + .native-helper-action-row { + flex-direction: column; + } + + .native-helper-button { + width: 100%; + } +} +} diff --git a/src/components/common/NativeHelperStatus.tsx b/src/components/common/NativeHelperStatus.tsx index 74ae33f0b..13781eb5d 100644 --- a/src/components/common/NativeHelperStatus.tsx +++ b/src/components/common/NativeHelperStatus.tsx @@ -5,6 +5,8 @@ */ import { useState, useEffect, useCallback, useMemo, type ReactNode } from 'react'; +import './WelcomeOverlay.css'; +import './NativeHelperStatus.css'; import { NativeHelperClient, isNativeHelperAvailable } from '../../services/nativeHelper'; import type { SystemInfo, ConnectionStatus } from '../../services/nativeHelper'; import { diff --git a/src/components/common/RelinkDialog.css b/src/components/common/RelinkDialog.css new file mode 100644 index 000000000..488e06a4c --- /dev/null +++ b/src/components/common/RelinkDialog.css @@ -0,0 +1,171 @@ +@layer overlays { +/* ============================================ + Relink Dialog - Media relinking interface + ============================================ */ + +.relink-dialog { + max-width: 500px; + width: 90%; +} + +.relink-title { + font-size: 20px; + font-weight: 600; + color: var(--gray-0); + margin: 0 0 4px 0; + text-align: center; +} + +.relink-subtitle { + font-size: 13px; + color: var(--gray-200); + margin: 0 0 16px 0; + text-align: center; +} + +.relink-file-list { + max-height: 300px; + overflow-y: auto; + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; + margin-bottom: 12px; +} + +.relink-file-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + cursor: pointer; + transition: background 0.15s; +} + +.relink-file-item:hover { + background: rgba(255, 255, 255, 0.05); +} + +.relink-file-item:last-child { + border-bottom: none; +} + +.relink-file-item.found { + cursor: default; +} + +.relink-status-icon { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-size: 12px; + font-weight: 700; + flex-shrink: 0; +} + +.relink-status-icon.missing { + background: rgba(255, 100, 100, 0.2); + color: var(--danger-light); +} + +.relink-status-icon.found { + background: rgba(100, 255, 100, 0.2); + color: var(--success-light); +} + +.relink-status-icon.searching { + background: rgba(255, 200, 100, 0.2); + color: var(--warning-light); +} + +.relink-file-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.relink-file-name { + font-size: 13px; + color: var(--gray-25); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.relink-file-path { + font-size: 11px; + color: var(--gray-250); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.relink-pick-hint { + font-size: 11px; + color: var(--gray-200); + flex-shrink: 0; +} + +.relink-searched { + font-size: 11px; + color: var(--gray-250); + margin-bottom: 12px; + padding: 0 4px; +} + +.relink-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.relink-actions-right { + display: flex; + gap: 8px; +} + +.relink-btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; + background: rgba(255, 255, 255, 0.1); + color: var(--gray-75); +} + +.relink-btn:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.15); + color: var(--gray-0); +} + +.relink-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.relink-btn-primary { + background: rgba(255, 215, 0, 0.2); + color: var(--amber-300); +} + +.relink-btn-primary:hover:not(:disabled) { + background: rgba(255, 215, 0, 0.3); +} + +.relink-btn-secondary { + background: rgba(100, 150, 255, 0.15); + color: var(--blue-200); +} + +.relink-btn-secondary:hover:not(:disabled) { + background: rgba(100, 150, 255, 0.25); +} +} diff --git a/src/components/common/RelinkDialog.tsx b/src/components/common/RelinkDialog.tsx index b67b03dab..cd7c12401 100644 --- a/src/components/common/RelinkDialog.tsx +++ b/src/components/common/RelinkDialog.tsx @@ -2,6 +2,8 @@ // Shows list of missing files, allows searching folders, updates status import { useState, useCallback, useEffect } from 'react'; +import './WelcomeOverlay.css'; +import './RelinkDialog.css'; import { Logger } from '../../services/logger'; const log = Logger.create('RelinkDialog'); diff --git a/src/components/common/SavedToast.css b/src/components/common/SavedToast.css new file mode 100644 index 000000000..33f1756fe --- /dev/null +++ b/src/components/common/SavedToast.css @@ -0,0 +1,46 @@ +@layer overlays { +/* ============================================ + Saved Toast - Center screen notification + ============================================ */ + +.saved-toast { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10000; + padding: 16px 32px; + background: rgba(0, 0, 0, 0.85); + border: 2px solid var(--amber-300); + border-radius: 12px; + font-size: 24px; + font-weight: 600; + color: var(--amber-300); + text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); + box-shadow: + 0 0 30px rgba(255, 215, 0, 0.3), + 0 4px 20px rgba(0, 0, 0, 0.5); + animation: saved-toast-flash 0.6s ease-out forwards; + pointer-events: none; +} + +/* Combined fade-in and fade-out animation */ +@keyframes saved-toast-flash { + 0% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.8); + } + 25% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 70% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.95); + } +} +} diff --git a/src/components/common/SavedToast.tsx b/src/components/common/SavedToast.tsx index 534c8d6b2..22cfa31c6 100644 --- a/src/components/common/SavedToast.tsx +++ b/src/components/common/SavedToast.tsx @@ -2,6 +2,7 @@ // Shows a brief "Saved" message in yellow when project is saved import { useEffect, useRef } from 'react'; +import './SavedToast.css'; interface SavedToastProps { visible: boolean; diff --git a/src/components/common/SplashScreen.css b/src/components/common/SplashScreen.css new file mode 100644 index 000000000..a7bb5cbf2 --- /dev/null +++ b/src/components/common/SplashScreen.css @@ -0,0 +1,156 @@ +@layer overlays { +/* ======================================== + Splash Screen Dialog + ======================================== */ + +.splash-dialog { + width: 680px; + max-height: 85vh; + padding: 0; + text-align: left; + display: flex; + flex-direction: column; + align-items: stretch; + overflow-y: auto; + background: transparent; + border: none; + box-shadow: none; + isolation: isolate; +} + +.splash-header { + position: sticky; + top: 0; + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px 16px; + z-index: 20; + background: linear-gradient(to bottom, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 40%, rgba(22, 22, 22, 0.6) 70%, transparent 100%); + flex-shrink: 0; +} + +.splash-header-left { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; + flex: 1 1 auto; +} + +.splash-header-right { + flex: 0 0 auto; + margin-left: auto; +} + +.splash-content { + position: relative; + z-index: 0; + padding: 0 24px 16px; + display: flex; + flex-direction: column; + gap: 18px; + background: rgba(22, 22, 22, 0.95); +} + +.splash-video { + width: 100%; +} + +.splash-video .changelog-video-container { + border-radius: 10px; + box-shadow: 0 20px 44px rgba(0, 0, 0, 0.32); +} + +.splash-notices { + display: flex; + flex-direction: column; + gap: 10px; +} + +.splash-footer { + position: sticky; + bottom: 0; + padding: 16px 24px 18px; + border-top: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + z-index: 20; + background: linear-gradient(to top, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 30%, rgba(22, 22, 22, 0.6) 60%, transparent 100%); + flex-shrink: 0; +} + +.splash-footer-buttons { + display: flex; + align-items: center; + gap: 10px; +} + +.splash-changelog-button { + display: inline-flex; + align-items: center; + justify-content: center; + height: 42px; + padding: 0 18px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: transparent; + color: rgba(255, 255, 255, 0.6); + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +.splash-changelog-button:hover { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.85); +} + +[data-theme="light"] .splash-header { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0.97) 0%, rgba(255, 255, 255, 0.92) 40%, rgba(255, 255, 255, 0.6) 70%, transparent 100%); +} + +[data-theme="light"] .splash-content { + background: rgba(255, 255, 255, 0.95); +} + +[data-theme="light"] .splash-footer { + background: linear-gradient(to top, rgba(255, 255, 255, 0.97) 0%, rgba(255, 255, 255, 0.92) 30%, rgba(255, 255, 255, 0.6) 60%, transparent 100%); +} + +[data-theme="light"] .splash-changelog-button { + border-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.5); +} + +[data-theme="light"] .splash-changelog-button:hover { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.18); + color: rgba(0, 0, 0, 0.7); +} + +@media (max-width: 760px) { + .splash-dialog { + width: 95vw; + } + + .splash-header { + flex-direction: column; + gap: 8px; + } + + .splash-footer { + flex-direction: column; + gap: 12px; + } + + .splash-footer-buttons { + width: 100%; + justify-content: flex-end; + } +} +} diff --git a/src/components/common/SplashScreen.tsx b/src/components/common/SplashScreen.tsx index 846865759..18d381a76 100644 --- a/src/components/common/SplashScreen.tsx +++ b/src/components/common/SplashScreen.tsx @@ -1,6 +1,9 @@ // SplashScreen - Welcome dialog shown on startup with featured video and notices import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import './WelcomeOverlay.css'; +import './WhatsNewDialog.css'; +import './SplashScreen.css'; import { APP_VERSION, FEATURED_VIDEO, diff --git a/src/components/common/Toolbar.css b/src/components/common/Toolbar.css new file mode 100644 index 000000000..fbbe728b6 --- /dev/null +++ b/src/components/common/Toolbar.css @@ -0,0 +1,614 @@ +@layer components { +/* Toolbar */ +.toolbar { + display: flex; + align-items: center; + gap: 0; + padding: 0 var(--sp-2); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + min-height: 28px; + flex-shrink: 0; +} + +.toolbar-section { + display: flex; + align-items: center; + gap: var(--sp-2); + padding: 0 var(--sp-3); +} + +.toolbar-spacer { + flex: 1; +} + +.toolbar-center { + display: flex; + align-items: center; + gap: var(--sp-3); + justify-content: center; +} + +.toolbar-right { + margin-left: 0; +} + +.toolbar-credit-pill { + display: inline-flex; + align-items: center; + gap: 6px; + height: 28px; + padding: 0 10px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 999px; + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); + cursor: pointer; + transition: + border-color var(--transition-fast), + background var(--transition-fast); +} + +.toolbar-credit-pill:hover { + border-color: rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.09); +} + +.toolbar-credit-pill-label { + color: rgba(255, 255, 255, 0.45); + font-size: var(--font-xs); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.toolbar-credit-pill-value { + color: rgba(255, 255, 255, 0.85); + font-size: var(--font-sm); + font-variant-numeric: tabular-nums; +} + +.toolbar-right .version { + color: var(--text-secondary); + font-size: var(--font-sm); + margin-right: var(--sp-3); + opacity: 0.7; +} + +.toolbar-project { + display: flex; + align-items: center; + height: 28px; + padding: 0 var(--sp-2) 0 var(--sp-1); + border-right: 1px solid var(--border-color); +} + +.toolbar-project .project-name { + font-weight: var(--font-semibold); + font-size: var(--font-sm); + color: var(--text-primary); + cursor: pointer; + padding: 3px var(--sp-1-5); + border-radius: var(--radius-sm); + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.toolbar-project .project-name:hover { + background: var(--bg-hover); +} + +.toolbar-project .project-name-input { + font-size: var(--font-sm); + font-weight: var(--font-semibold); + padding: 3px var(--sp-1-5); + border-radius: var(--radius-sm); + border: 1px solid var(--accent); + background: var(--bg-primary); + color: var(--text-primary); + width: 150px; +} + +.toolbar-project .restore-permission-btn { + font-size: var(--font-md); + font-weight: var(--font-medium); + padding: var(--sp-1) var(--sp-3); + border-radius: var(--radius-md); + border: 1px solid var(--accent); + background: var(--accent); + color: var(--text-on-accent); + cursor: pointer; + transition: all var(--transition-normal) ease; +} + +.toolbar-project .restore-permission-btn:hover { + background: var(--accent-hover); + border-color: var(--accent-hover); +} + +.toolbar-project .restore-permission-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Menu Bar */ +.menu-bar { + display: flex; + align-items: center; + height: 28px; +} + +.menu-item { + position: relative; +} + +.menu-trigger { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: var(--font-sm); + padding: var(--sp-1) var(--sp-2-5); + cursor: pointer; + height: 28px; + transition: background var(--transition-fast), color var(--transition-fast); +} + +.menu-trigger:hover, +.menu-trigger.active { + background: var(--bg-hover); + color: var(--text-primary); +} + +.menu-dropdown { + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-lg); + z-index: 10000; + padding: var(--sp-1) 0; + overflow: visible; +} + +.menu-dropdown-wide { + min-width: 220px; +} + +.menu-option { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: var(--sp-1-5) var(--sp-3); + background: transparent; + border: none; + color: var(--text-primary); + font-size: var(--font-sm); + cursor: pointer; + text-align: left; + transition: background var(--transition-fast); +} + +.menu-option:hover { + background: var(--bg-hover); +} + +.menu-option:disabled, +.menu-option.disabled { + color: var(--text-muted); + cursor: default; +} + +.menu-option:disabled:hover, +.menu-option.disabled:hover { + background: transparent; +} + +.menu-option-wip { + opacity: 0.5; +} + +.menu-wip-badge { + font-size: var(--font-sm); + margin-left: auto; + filter: saturate(3) brightness(0.9); +} + +.menu-option .shortcut { + color: var(--text-muted); + font-size: var(--font-sm); + margin-left: var(--sp-6); +} + +.menu-option .menu-hint { + color: var(--text-muted); + font-size: var(--font-xs); + margin-left: auto; + padding-left: 12px; +} + +.menu-option.checked { + color: var(--accent); +} + +.menu-option.project-item { + padding-right: 8px; +} + +.menu-option .delete-btn { + background: transparent; + border: none; + color: var(--text-muted); + font-size: var(--font-2xl); + cursor: pointer; + padding: 2px 6px; + border-radius: var(--radius-sm); + opacity: 0; + transition: opacity var(--transition-fast), background var(--transition-fast); +} + +.menu-option:hover .delete-btn { + opacity: 1; +} + +.menu-option .delete-btn:hover { + background: rgba(255, 68, 68, 0.2); + color: var(--danger); +} + +.menu-separator { + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +.menu-submenu { + padding: 0; +} + +.menu-label { + display: block; + padding: 8px 16px 4px; + font-size: var(--font-sm); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.menu-empty { + display: block; + padding: var(--sp-2) var(--sp-4); + font-size: var(--font-md); + color: var(--text-muted); + font-style: italic; +} + +.menu-info { + display: block; + padding: 4px 16px 8px; + font-size: var(--font-sm); + color: var(--text-secondary); +} + +.menu-sublabel { + display: block; + padding: 6px 16px 2px; + font-size: var(--font-xs); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.4px; +} + +.menu-subseparator { + height: 1px; + margin: 4px 12px; + background: var(--border-color); +} + +/* Nested submenu (flyout) styles */ +.menu-item-with-submenu { + position: relative; +} + +.menu-item-with-submenu > .menu-option { + display: flex; + justify-content: space-between; + align-items: center; +} + +.menu-item-with-submenu > .menu-option::after { + content: '▶'; + font-size: 8px; + color: var(--text-muted); + margin-left: var(--sp-3); +} + +.menu-nested-submenu { + display: none; + position: absolute; + left: 100%; + top: -4px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-xl); + min-width: 140px; + padding: 4px 0; + z-index: 1001; +} + +.menu-item-with-submenu:hover > .menu-nested-submenu { + display: block; +} + +.menu-nested-submenu-layouts { + min-width: 320px; +} + +.menu-nested-submenu-panels { + min-width: 240px; +} + +.menu-nested-submenu-recent { + min-width: 300px; + max-width: 420px; +} + +.menu-option-recent { + align-items: flex-start; + gap: 16px; +} + +.menu-recent-text { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; + gap: 2px; +} + +.menu-recent-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-recent-meta { + overflow: hidden; + color: var(--text-muted); + font-size: var(--font-xs); + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-recent-kind { + flex: 0 0 auto; + color: var(--text-muted); + font-size: var(--font-xs); + line-height: 18px; +} + +.menu-layout-row { + display: flex; + align-items: stretch; + gap: 6px; + padding: 0 6px; +} + +.menu-layout-load { + flex: 1; + min-width: 0; + border-radius: var(--radius-sm); +} + +.menu-layout-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-layout-default-btn { + flex: 0 0 auto; + min-width: 64px; + margin: 2px 0; + padding: 0 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + font-size: var(--font-xs); + cursor: pointer; + transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); +} + +.menu-layout-default-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.menu-layout-default-btn.active { + border-color: var(--accent); + background: rgba(92, 153, 255, 0.12); + color: var(--accent); +} + +.project-name.no-project { + color: var(--text-secondary); + cursor: default; + font-style: italic; +} + +.project-name.no-project:hover { + background: transparent; +} + +.menu-slider { + display: flex; + align-items: center; + padding: var(--sp-2) var(--sp-4); + gap: var(--sp-2); +} + +.menu-slider input[type="range"] { + flex: 1; + height: 4px; + appearance: none; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + cursor: pointer; +} + +.menu-slider input[type="range"]::-webkit-slider-thumb { + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; +} + +.menu-slider input[type="range"]::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; + border: none; +} + +.menu-slider .slider-value { + font-size: var(--font-sm); + color: var(--text-muted); + min-width: 36px; + text-align: right; +} + +.status { + font-size: var(--font-md); +} + +.status.ready { + color: var(--success); +} + +.status.loading { + color: var(--warning); +} + +/* GPU Selector Dropdown */ +.gpu-selector { + position: relative; + margin-right: var(--sp-2); +} + +.gpu-trigger { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + cursor: pointer; + font-size: var(--font-xl); + transition: all var(--transition-normal) ease; +} + +.gpu-trigger:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--text-muted); +} + +.gpu-trigger:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.gpu-trigger.switching { + animation: gpu-spin 0.8s linear infinite; +} + +@keyframes gpu-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.gpu-dropdown { + position: absolute; + top: calc(100% + 4px); + right: 0; + min-width: 200px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-lg); + z-index: 10001; + overflow: hidden; +} + +.gpu-dropdown-header { + padding: var(--sp-2) var(--sp-3); + font-size: var(--font-sm); + font-weight: var(--font-semibold); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); +} + +.gpu-option { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + padding: var(--sp-2-5) var(--sp-3); + background: transparent; + border: none; + cursor: pointer; + transition: background var(--transition-fast) ease; +} + +.gpu-option:hover { + background: var(--bg-hover); +} + +.gpu-option.active { + background: var(--accent-dim); +} + +.gpu-option .gpu-icon { + position: absolute; + right: 12px; + font-size: var(--font-2xl); +} + +.gpu-option .gpu-label { + font-size: var(--font-sm); + font-weight: var(--font-medium); + color: var(--text-primary); + display: flex; + align-items: center; + gap: var(--sp-2); +} + +.gpu-option .gpu-desc { + font-size: var(--font-sm); + color: var(--text-muted); + margin-top: 2px; +} + +.gpu-note { + padding: var(--sp-2) var(--sp-3); + font-size: var(--font-xs); + color: var(--text-muted); + background: var(--bg-tertiary); + border-top: 1px solid var(--border-color); + font-style: italic; +} +} diff --git a/src/components/common/Toolbar.tsx b/src/components/common/Toolbar.tsx index 9aa7545f5..2c67ff3ec 100644 --- a/src/components/common/Toolbar.tsx +++ b/src/components/common/Toolbar.tsx @@ -1,6 +1,7 @@ // Toolbar component - After Effects style menu bar import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; +import './Toolbar.css'; import { useShallow } from 'zustand/react/shallow'; import { Logger } from '../../services/logger'; diff --git a/src/components/common/TutorialCampaignDialog.css b/src/components/common/TutorialCampaignDialog.css new file mode 100644 index 000000000..3b6aa9f5f --- /dev/null +++ b/src/components/common/TutorialCampaignDialog.css @@ -0,0 +1,164 @@ +@layer overlays { +/* ============================ + Tutorial Campaign Dialog + ============================ */ + +.tutorial-campaign-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + animation: tutorial-fade-in 200ms ease forwards; +} + +.tutorial-campaign-dialog { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + width: 640px; + max-width: 90vw; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); + animation: tutorial-tooltip-in 200ms ease forwards; +} + +.tutorial-campaign-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border-color); + flex-shrink: 0; +} + +.tutorial-campaign-header-text { + display: flex; + align-items: baseline; + gap: 12px; +} + +.tutorial-campaign-header h2 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.tutorial-campaign-progress { + font-size: 12px; + color: var(--text-secondary); +} + +.tutorial-campaign-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 20px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + transition: color 0.15s; +} + +.tutorial-campaign-close:hover { + color: var(--text-primary); +} + +.tutorial-campaign-body { + overflow-y: auto; + padding: 16px 20px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.tutorial-campaign-category-title { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); + margin: 0 0 8px; +} + +.tutorial-campaign-grid { + display: flex; + flex-direction: column; + gap: 6px; +} + +.tutorial-campaign-card { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + text-align: left; + transition: all 0.15s ease; + width: 100%; +} + +.tutorial-campaign-card:hover { + border-color: var(--accent); + background: var(--bg-hover); +} + +.tutorial-campaign-card.completed { + opacity: 0.7; +} + +.tutorial-campaign-card.completed:hover { + opacity: 1; +} + +.tutorial-campaign-card-icon { + font-size: 22px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.tutorial-campaign-card-info { + flex: 1; + min-width: 0; +} + +.tutorial-campaign-card-title { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 2px; +} + +.tutorial-campaign-card-desc { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.4; +} + +.tutorial-campaign-card-meta { + font-size: 10px; + color: var(--text-secondary); + margin-top: 4px; + display: flex; + align-items: center; + gap: 8px; +} + +.tutorial-campaign-card-check { + color: var(--green-500); + font-weight: 700; + font-size: 12px; +} +} diff --git a/src/components/common/TutorialCampaignDialog.tsx b/src/components/common/TutorialCampaignDialog.tsx index c7bdffc7b..68015c2ec 100644 --- a/src/components/common/TutorialCampaignDialog.tsx +++ b/src/components/common/TutorialCampaignDialog.tsx @@ -1,4 +1,6 @@ import { useCallback } from 'react'; +import './TutorialOverlay.css'; +import './TutorialCampaignDialog.css'; import { useSettingsStore } from '../../stores/settingsStore'; import { TUTORIAL_CAMPAIGNS, diff --git a/src/components/common/TutorialOverlay.css b/src/components/common/TutorialOverlay.css new file mode 100644 index 000000000..9e565df1f --- /dev/null +++ b/src/components/common/TutorialOverlay.css @@ -0,0 +1,352 @@ +@layer overlays { +/* ============================================ + Tutorial Overlay + ============================================ */ + +.tutorial-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10001; + cursor: pointer; + animation: tutorial-fade-in 300ms ease forwards; +} + +.tutorial-backdrop.closing .tutorial-overlay-svg, +.tutorial-backdrop.closing .tutorial-tooltip, +.tutorial-backdrop.closing .tutorial-highlight-ring { + animation: tutorial-fade-out 400ms ease forwards; +} + +.tutorial-clippy-wrapper { + position: fixed; + width: 340px; + pointer-events: none; + z-index: 10003; + transition: left 400ms ease, top 400ms ease, width 400ms ease; +} + +.tutorial-clippy-wrapper--welcome { + width: 480px; +} + +@keyframes tutorial-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes tutorial-fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +.tutorial-overlay-svg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; +} + +.tutorial-tooltip { + position: fixed; + width: 340px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 16px; + padding: 20px; + z-index: 10002; + pointer-events: none; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); + animation: tutorial-tooltip-in 300ms ease forwards; + overflow: visible; + transition: left 400ms ease, top 400ms ease, width 400ms ease; +} + +.tutorial-tooltip-content { + display: flex; + gap: 14px; + align-items: flex-start; +} + +.tutorial-tooltip-text { + flex: 1; + min-width: 0; +} + +.tutorial-clippy { + position: absolute; + width: 120px; + height: auto; + top: -55px; + right: -35px; + object-fit: contain; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5)); + z-index: 1; +} + +@keyframes tutorial-tooltip-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.tutorial-tooltip-arrow { + position: absolute; + width: 12px; + height: 12px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + transform: rotate(45deg); +} + +/* Arrow positions — points toward the highlighted panel */ +.tutorial-tooltip-arrow--top { + bottom: -7px; + left: 50%; + margin-left: -6px; + border-top: none; + border-left: none; +} + +.tutorial-tooltip-arrow--bottom { + top: -7px; + left: 50%; + margin-left: -6px; + border-bottom: none; + border-right: none; +} + +.tutorial-tooltip-arrow--left { + right: -7px; + top: 50%; + margin-top: -6px; + border-bottom: none; + border-left: none; +} + +.tutorial-tooltip-arrow--right { + left: -7px; + top: 50%; + margin-top: -6px; + border-top: none; + border-right: none; +} + +.tutorial-tooltip-step { + font-size: 11px; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 6px; + font-weight: 600; +} + +.tutorial-tooltip-title { + font-size: 18px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; +} + +.tutorial-tooltip-desc { + font-size: 13px; + line-height: 1.5; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.tutorial-dots { + display: flex; + gap: 6px; + justify-content: center; + margin-bottom: 10px; +} + +.tutorial-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.15); + transition: background 300ms ease; +} + +.tutorial-dot.active { + background: var(--accent); +} + +.tutorial-dot.completed { + background: rgba(45, 140, 235, 0.4); +} + +.tutorial-tooltip-hint { + text-align: center; + font-size: 11px; + color: var(--text-secondary); + opacity: 0.7; +} + +.tutorial-skip-btn { + display: block; + margin: 10px auto 0; + padding: 4px 16px; + background: transparent; + border: 1px solid var(--text-secondary); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + pointer-events: auto; + opacity: 0.7; + transition: opacity 0.15s, color 0.15s, border-color 0.15s; +} + +.tutorial-skip-btn:hover { + opacity: 1; + color: var(--text-primary); + border-color: var(--text-primary); +} + +/* Part 2: Yellow highlight ring for timeline element steps */ +.tutorial-highlight-ring { + position: fixed; + border: 2px solid var(--amber-300); + border-radius: 8px; + z-index: 10002; + pointer-events: none; + box-shadow: 0 0 12px rgba(255, 215, 0, 0.4), inset 0 0 12px rgba(255, 215, 0, 0.1); + animation: tutorial-ring-pulse 1.5s ease-in-out infinite; + transition: left 400ms ease, top 400ms ease, width 400ms ease, height 400ms ease; +} + +@keyframes tutorial-ring-pulse { + 0%, 100% { box-shadow: 0 0 12px rgba(255, 215, 0, 0.4), inset 0 0 12px rgba(255, 215, 0, 0.1); } + 50% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.7), inset 0 0 20px rgba(255, 215, 0, 0.2); } +} + +/* Tutorial Welcome Screen — wider tooltip for the button grid */ +.tutorial-tooltip--welcome { + width: 480px; + pointer-events: auto; + cursor: default; +} + +.tutorial-welcome-title { + font-size: 17px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 4px; +} + +.tutorial-welcome-subtitle { + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.tutorial-welcome-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px; +} + +.tutorial-welcome-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 12px 8px; + border-radius: 10px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + cursor: pointer; + transition: all 0.15s ease; +} + +.tutorial-welcome-btn:hover { + border-color: var(--accent); + background: var(--bg-hover); + transform: translateY(-1px); +} + +.tutorial-welcome-icon { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; +} + +.tutorial-welcome-icon img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.tutorial-welcome-icon--beginner { + width: 44px; + height: 44px; + border-radius: 10px; + background: linear-gradient(135deg, var(--green-500) 0%, #1A8A4A 100%); + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + color: var(--text-on-accent); +} + +.tutorial-welcome-label { + font-size: 11px; + font-weight: 500; + color: var(--text-primary); + text-align: center; + white-space: nowrap; +} + +/* ============================ + Interactive Tutorial - Fake Cursor + ============================ */ + +.tutorial-fake-cursor { + position: fixed; + z-index: 10004; + pointer-events: none; + transition-property: left, top; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + /* transitionDuration set via inline style */ + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); +} + +.tutorial-fake-cursor svg { + display: block; +} + +.tutorial-fake-cursor--clicking { + animation: tutorial-cursor-click 250ms ease; +} + +@keyframes tutorial-cursor-click { + 0% { transform: scale(1); } + 40% { transform: scale(0.82); } + 100% { transform: scale(1); } +} + +.tutorial-click-ripple { + position: absolute; + top: 4px; + left: 4px; + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba(255, 215, 0, 0.5); + animation: tutorial-click-ripple-expand 500ms ease forwards; + pointer-events: none; +} + +@keyframes tutorial-click-ripple-expand { + 0% { transform: scale(0.5); opacity: 1; } + 100% { transform: scale(3); opacity: 0; } +} +} diff --git a/src/components/common/TutorialOverlay.tsx b/src/components/common/TutorialOverlay.tsx index 84ae5b638..a7a38b1ea 100644 --- a/src/components/common/TutorialOverlay.tsx +++ b/src/components/common/TutorialOverlay.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import './TutorialOverlay.css'; import { useDockStore } from '../../stores/dockStore'; import { useSettingsStore } from '../../stores/settingsStore'; import type { PanelType } from '../../types/dock'; diff --git a/src/components/common/WelcomeOverlay.css b/src/components/common/WelcomeOverlay.css new file mode 100644 index 000000000..bf541dc52 --- /dev/null +++ b/src/components/common/WelcomeOverlay.css @@ -0,0 +1,645 @@ +@layer overlays { +/* Light theme: overlay/dialog/changelog overrides */ +[data-theme="light"] .welcome-overlay { + background: linear-gradient(165deg, rgba(255, 255, 255, 0.97) 0%, rgba(240, 240, 240, 0.99) 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.04), + 0 40px 80px rgba(0, 0, 0, 0.15), + 0 0 120px rgba(0, 0, 0, 0.08); +} + +[data-theme="light"] .welcome-enter { + color: rgba(0, 0, 0, 0.4); +} + +[data-theme="light"] .welcome-enter:hover { + color: rgba(0, 0, 0, 0.7); +} + +[data-theme="light"] .welcome-enter kbd { + background: rgba(0, 0, 0, 0.06); + border: 1px solid rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.5); +} + +[data-theme="light"] .welcome-enter:hover kbd { + background: rgba(0, 0, 0, 0.1); + border-color: rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.7); +} + +/* ========== WELCOME OVERLAY STYLES ========== */ +.welcome-overlay-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + z-index: 10002; + backdrop-filter: blur(10px); + transition: backdrop-filter 0.2s ease-in; + animation: welcome-backdrop-in 0.4s ease-in-out 0.5s both; +} + +@keyframes welcome-backdrop-in { + from { + backdrop-filter: blur(0px); + } + to { + backdrop-filter: blur(10px); + } +} + +.welcome-overlay-backdrop.closing { + backdrop-filter: blur(0px); + transition: backdrop-filter 120ms ease-out; +} + +.welcome-overlay-backdrop.closing .welcome-overlay { + opacity: 0; + transform: scale(0.96); + transition: opacity 120ms ease-out, transform 120ms ease-out; +} + +/* No fade when another dialog will show immediately after */ +.welcome-overlay-backdrop.closing.no-fade { + backdrop-filter: blur(10px); + transition: none; +} + +.welcome-overlay-backdrop.closing.no-fade .welcome-overlay { + opacity: 0; + transform: scale(0.96); + transition: opacity 80ms ease-out, transform 80ms ease-out; +} + +.welcome-overlay { + background: linear-gradient(165deg, rgba(30, 30, 30, 0.95) 0%, rgba(15, 15, 15, 0.98) 100%); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 24px; + width: 400px; + max-width: 90vw; + padding: 40px 36px 32px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.02), + 0 40px 80px rgba(0, 0, 0, 0.5), + 0 0 120px rgba(0, 0, 0, 0.3); + animation: welcome-overlay-in 0.3s ease-out 0.9s both; +} + +@keyframes welcome-overlay-in { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Privacy Tagline */ +.welcome-tagline { + display: flex; + align-items: center; + gap: 0; + min-height: 24px; + position: relative; + font-size: 15px; + font-weight: 600; + letter-spacing: 0.5px; + margin-bottom: 28px; + animation: welcome-fade-in 0.4s ease-out 1.0s both; +} + +.welcome-tag-local { + color: var(--danger-light); +} + +.welcome-tag-private { + color: var(--blue-300); +} + +.welcome-tag-free { + color: var(--green-300); +} + +.welcome-tag-dot { + color: rgba(255, 255, 255, 0.3); + font-weight: 400; + display: inline-block; + max-width: 0; + overflow: hidden; + white-space: nowrap; + padding: 0; + animation: dot-expand 0.35s ease-out forwards; +} + +@keyframes dot-expand { + 0% { + max-width: 0; + padding: 0; + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + max-width: 40px; + padding: 0 12px; + opacity: 1; + } +} + +/* Typewriter cursor */ +.welcome-cursor { + color: rgba(255, 255, 255, 0.8); + font-weight: 300; + margin-left: 1px; + opacity: 0; + transition: opacity 0.05s; +} + +.welcome-cursor.visible { + opacity: 1; +} + + +@keyframes welcome-fade-in { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Title */ +.welcome-title { + margin: 0 0 8px; + font-size: 36px; + font-weight: 700; + letter-spacing: -1.5px; + line-height: 1; + overflow: hidden; +} + +.welcome-title-master { + color: var(--gray-0); + display: inline-block; + animation: welcome-title-master-in 0.8s ease-out 1.05s both; +} + +@keyframes welcome-title-master-in { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.welcome-title-selects { + color: rgba(255, 255, 255, 0.7); + display: inline-block; + animation: welcome-title-selects-in 0.8s ease-out 1.25s both; +} + +@keyframes welcome-title-selects-in { + from { + opacity: 0; + transform: translateX(20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.welcome-subtitle { + margin: 0 0 36px; + font-size: 14px; + color: rgba(255, 255, 255, 0.35); + font-weight: 400; + letter-spacing: 0.2px; + animation: welcome-fade-up 0.4s ease-out 1.25s both; +} + +@keyframes welcome-fade-up { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Folder Card */ +.welcome-folder-card { + width: 100%; + margin-bottom: 24px; + animation: welcome-fade-up 0.4s ease-out 1.3s both; +} + +.welcome-folder-card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + padding: 0 4px; +} + +.welcome-folder-card-label { + font-size: 12px; + font-weight: 600; + color: rgba(255, 255, 255, 0.6); + letter-spacing: 0.3px; +} + +.welcome-folder-card-optional { + font-size: 10px; + font-weight: 500; + color: rgba(255, 255, 255, 0.25); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Folder Button */ +.welcome-folder-btn { + width: 100%; + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 14px; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; +} + +.welcome-folder-btn:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.1); +} + +.welcome-folder-btn:hover:not(:disabled) .welcome-folder-btn-arrow { + opacity: 0.6; + transform: translateX(2px); +} + +.welcome-folder-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.welcome-folder-btn.has-folder { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.08); +} + +.welcome-folder-btn-icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + flex-shrink: 0; +} + +.welcome-folder-btn-icon svg { + color: rgba(255, 255, 255, 0.5); +} + +.welcome-folder-btn.has-folder .welcome-folder-btn-icon { + background: rgba(100, 200, 100, 0.1); +} + +.welcome-folder-btn.has-folder .welcome-folder-btn-icon svg { + color: rgba(100, 200, 100, 0.7); +} + +.welcome-folder-btn-text { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.welcome-folder-name { + font-size: 14px; + font-weight: 500; + color: var(--gray-0); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.welcome-folder-change { + font-size: 11px; + color: rgba(255, 255, 255, 0.35); +} + +.welcome-folder-btn-arrow { + flex-shrink: 0; + color: rgba(255, 255, 255, 0.25); + opacity: 0.4; + transition: all 0.2s ease; +} + +/* Folder Buttons Container */ +.welcome-folder-buttons { + display: flex; + flex-direction: column; + gap: 10px; +} + +/* Secondary button style (Open Existing) */ +.welcome-folder-btn-secondary { + background: transparent; + border-color: rgba(255, 255, 255, 0.04); +} + +.welcome-folder-btn-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.03); +} + +.welcome-folder-btn-secondary .welcome-folder-btn-icon { + background: rgba(255, 255, 255, 0.03); +} + +/* Enter Button */ +.welcome-enter { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 0; + background: none; + border: none; + cursor: pointer; + color: rgba(255, 255, 255, 0.4); + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + animation: welcome-fade-up 0.4s ease-out 1.4s both; +} + +.welcome-enter:hover { + color: rgba(255, 255, 255, 0.7); +} + +.welcome-enter:hover kbd { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.8); +} + +.welcome-enter kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 24px; + height: 24px; + padding: 0 6px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; + font-family: inherit; + font-size: 14px; + color: rgba(255, 255, 255, 0.5); + transition: all 0.2s ease; +} + + +/* Note for unsupported browsers */ +.welcome-note { + margin: 0; + padding: 14px 16px; + background: rgba(255, 255, 255, 0.03); + border-radius: 12px; + font-size: 13px; + color: rgba(255, 255, 255, 0.4); + line-height: 1.5; +} + +/* Native Helper project picker (Firefox) */ +.welcome-native-new { + display: flex; + flex-direction: column; + gap: 8px; +} + +.welcome-native-input { + width: 100%; + padding: 10px 14px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + color: #fff; + font-size: 14px; + font-family: inherit; + outline: none; + transition: border-color 0.2s; + box-sizing: border-box; +} + +.welcome-native-input:focus { + border-color: rgba(255, 255, 255, 0.25); +} + +.welcome-native-input::placeholder { + color: rgba(255, 255, 255, 0.3); +} + +.welcome-native-projects { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 8px; +} + +.welcome-native-projects-label { + font-size: 11px; + color: rgba(255, 255, 255, 0.3); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 0; +} + +.welcome-native-location { + margin-top: 2px; +} + +.welcome-native-location-btn { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 6px 10px; + background: none; + border: 1px solid transparent; + border-radius: 8px; + color: rgba(255, 255, 255, 0.3); + font-size: 11px; + font-family: inherit; + cursor: pointer; + transition: all 0.15s; + text-align: left; +} + +.welcome-native-location-btn:hover { + background: rgba(255, 255, 255, 0.04); + border-color: rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.5); +} + +.welcome-native-location-btn span { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.welcome-native-path-input { + font-size: 12px; + padding: 6px 10px; +} + +.welcome-native-actions { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.welcome-native-back-btn { + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + padding: 6px 12px; + cursor: pointer; + transition: all 0.15s ease; +} + +.welcome-native-back-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.7); +} + +/* Browser warning for non-Chromium browsers */ +.welcome-browser-warning { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 8px; + margin: 8px 0 20px 0; + padding: 20px 24px; + background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(185, 28, 28, 0.1) 100%); + border: 1px solid rgba(239, 68, 68, 0.35); + border-radius: 16px; + max-width: 340px; + box-shadow: 0 0 30px rgba(239, 68, 68, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.welcome-browser-warning-icon { + color: var(--danger); + filter: drop-shadow(0 0 8px rgba(239, 68, 68, 0.5)); +} + +.welcome-browser-warning-label { + font-size: 11px; + font-weight: 600; + color: var(--danger); + text-transform: uppercase; + letter-spacing: 1px; +} + +.welcome-browser-warning-name { + font-size: 22px; + font-weight: 700; + color: var(--gray-0); + margin: -2px 0 4px 0; +} + +.welcome-browser-warning-desc { + font-size: 13px; + color: rgba(255, 255, 255, 0.6); + line-height: 1.5; + max-width: 280px; +} + +.welcome-browser-warning-btn { + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: 8px; + padding: 10px 18px; + background: linear-gradient(135deg, #4285f4 0%, #1a73e8 100%); + border: none; + border-radius: 8px; + color: var(--text-on-accent); + font-size: 13px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(66, 133, 244, 0.3); +} + +.welcome-browser-warning-btn:hover { + background: linear-gradient(135deg, #5a95f5 0%, #2b7de9 100%); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(66, 133, 244, 0.4); +} + +.welcome-browser-warning-btn svg { + opacity: 0.9; +} + +/* OS warning for non-Linux systems (yellow/orange instead of red) */ +.welcome-os-warning { + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(180, 83, 9, 0.1) 100%); + border: 1px solid rgba(245, 158, 11, 0.35); + box-shadow: 0 0 30px rgba(245, 158, 11, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.welcome-os-warning .welcome-browser-warning-icon { + color: var(--amber-600); + filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5)); +} + +.welcome-os-warning .welcome-browser-warning-label { + color: var(--amber-600); +} + +/* Error state */ +.welcome-error { + margin-top: 10px; + padding: 0 4px; + font-size: 12px; + color: var(--danger-light); + text-align: left; +} +} diff --git a/src/components/common/WelcomeOverlay.tsx b/src/components/common/WelcomeOverlay.tsx index bd8b47ea0..f0b6f2217 100644 --- a/src/components/common/WelcomeOverlay.tsx +++ b/src/components/common/WelcomeOverlay.tsx @@ -3,6 +3,7 @@ // Supports FSA (Chrome) and Native Helper (Firefox) backends import { useState, useCallback, useEffect, useMemo } from 'react'; +import './WelcomeOverlay.css'; import { Logger } from '../../services/logger'; const log = Logger.create('WelcomeOverlay'); diff --git a/src/components/common/WhatsNewDialog.css b/src/components/common/WhatsNewDialog.css new file mode 100644 index 000000000..6af5b4fb7 --- /dev/null +++ b/src/components/common/WhatsNewDialog.css @@ -0,0 +1,1360 @@ +@layer overlays { +[data-theme="light"] .changelog-header { + background: linear-gradient(to bottom, rgba(245, 245, 245, 0.97) 0%, rgba(245, 245, 245, 0.85) 50%, transparent 100%); +} + +[data-theme="light"] .changelog-header::after { + background: linear-gradient(to bottom, rgba(245, 245, 245, 0.97), transparent); +} + +[data-theme="light"] .changelog-version { + color: rgba(0, 0, 0, 0.4); +} + +[data-theme="light"] .changelog-brand-master { + color: rgba(0, 0, 0, 0.9); +} + +[data-theme="light"] .changelog-brand-selects { + color: rgba(0, 0, 0, 0.34); +} + +[data-theme="light"] .changelog-header-title { + color: rgba(0, 0, 0, 0.4); +} + +[data-theme="light"] .changelog-header-button { + background: rgba(0, 0, 0, 0.05); + border-color: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.68); +} + +[data-theme="light"] .changelog-header-button:hover { + background: rgba(0, 0, 0, 0.08); + border-color: rgba(0, 0, 0, 0.14); + color: rgba(0, 0, 0, 0.88); +} + +[data-theme="light"] .changelog-notice-link { + color: rgba(146, 64, 14, 0.95); +} + +[data-theme="light"] .changelog-close { + background: rgba(0, 0, 0, 0.05); +} + +[data-theme="light"] .changelog-close:hover { + background: rgba(0, 0, 0, 0.1); +} + +[data-theme="light"] .changelog-tabs { + border-bottom: 1px solid rgba(0, 0, 0, 0.08); +} + +[data-theme="light"] .changelog-tab { + background: rgba(0, 0, 0, 0.04); +} + +[data-theme="light"] .changelog-tab:hover { + background: rgba(0, 0, 0, 0.08); +} + +[data-theme="light"] .changelog-tab.active { + background: rgba(0, 0, 0, 0.1); +} + +[data-theme="light"] .changelog-calendar { + background: transparent; + border: none; + box-shadow: none; +} + +[data-theme="light"] .changelog-calendar-day { + background: rgba(0, 0, 0, 0.06); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.03); +} + +[data-theme="light"] .changelog-calendar-day:not(.is-future):not(.is-outside-range):hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18); +} + +[data-theme="light"] .changelog-calendar-tooltip { + background: rgba(255, 255, 255, 0.98); + border: 1px solid rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.82); + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); +} + +[data-theme="light"] .changelog-calendar-tooltip::after { + background: rgba(255, 255, 255, 0.98); + border-right: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); +} + +[data-theme="light"] .changelog-calendar-level-1 .changelog-calendar-fill-main { + background: rgba(46, 125, 50, 0.22); +} + +[data-theme="light"] .changelog-calendar-level-2 .changelog-calendar-fill-main { + background: rgba(46, 125, 50, 0.38); +} + +[data-theme="light"] .changelog-calendar-level-3 .changelog-calendar-fill-main { + background: rgba(46, 125, 50, 0.58); +} + +[data-theme="light"] .changelog-calendar-level-4 .changelog-calendar-fill-main { + background: rgba(46, 125, 50, 0.82); +} + +[data-theme="light"] .changelog-calendar-community-level-0 .changelog-calendar-fill-community { + background: rgba(245, 158, 11, 0.16); +} + +[data-theme="light"] .changelog-calendar-community-level-1 .changelog-calendar-fill-community { + background: rgba(245, 158, 11, 0.3); +} + +[data-theme="light"] .changelog-calendar-community-level-2 .changelog-calendar-fill-community { + background: rgba(217, 119, 6, 0.48); +} + +[data-theme="light"] .changelog-calendar-community-level-3 .changelog-calendar-fill-community { + background: rgba(217, 119, 6, 0.68); +} + +[data-theme="light"] .changelog-calendar-community-level-4 .changelog-calendar-fill-community { + background: rgba(180, 83, 9, 0.9); +} + +[data-theme="light"] .changelog-group-line { + background: linear-gradient(90deg, rgba(0, 0, 0, 0.12) 0%, transparent 100%); +} + +[data-theme="light"] .changelog-item { + background: rgba(0, 0, 0, 0.02); +} + +[data-theme="light"] .changelog-item.has-description:hover { + background: rgba(0, 0, 0, 0.05); +} + +[data-theme="light"] .changelog-item-community { + background: + linear-gradient(135deg, rgba(217, 119, 6, 0.08) 0%, rgba(255, 255, 255, 0.03) 45%, rgba(250, 204, 21, 0.08) 100%); + border: 1px solid rgba(217, 119, 6, 0.2); + box-shadow: inset 0 0 0 1px rgba(250, 204, 21, 0.06); +} + +[data-theme="light"] .changelog-badge-community { + background: rgba(217, 119, 6, 0.14); + border-color: rgba(217, 119, 6, 0.3); + color: rgba(146, 64, 14, 0.96); +} + +[data-theme="light"] .changelog-contributor-link, +[data-theme="light"] .changelog-contributor-label { + color: rgba(146, 64, 14, 0.86); +} + +[data-theme="light"] .changelog-commit-count { + background: rgba(0, 0, 0, 0.06); +} + +[data-theme="light"] .changelog-footer { + background: linear-gradient(to top, rgba(245, 245, 245, 0.97) 0%, rgba(245, 245, 245, 0.85) 40%, transparent 100%); +} + +[data-theme="light"] .changelog-footer::before { + background: linear-gradient(to top, rgba(245, 245, 245, 0.97), transparent); +} + +[data-theme="light"] .changelog-content { + background: rgba(245, 245, 245, 0.97); +} + +[data-theme="light"] .changelog-dont-show { + color: rgba(0, 0, 0, 0.4); +} + +[data-theme="light"] .changelog-dont-show:hover { + color: rgba(0, 0, 0, 0.7); +} + +/* ===== Changelog Dialog (What's New) ===== */ + +/* Backdrop */ +.whats-new-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10002; + backdrop-filter: blur(10px); + opacity: 1; + animation: welcome-backdrop-in 0.14s ease-out both; + transition: backdrop-filter 200ms ease-out, opacity 200ms ease-out; +} + +.whats-new-backdrop.closing { + backdrop-filter: blur(0px); + opacity: 0; +} + +.whats-new-backdrop .welcome-overlay { + animation: welcome-overlay-in 0.16s ease-out both; + transition: opacity 200ms ease-out, transform 200ms ease-out; +} + +.whats-new-backdrop.closing .welcome-overlay { + opacity: 0; + transform: scale(0.96); +} + +/* Dialog container - single scroll container, header/footer stick */ +.changelog-dialog { + width: 680px; + height: 600px; + max-height: 85vh; + padding: 0; + text-align: left; + display: flex; + flex-direction: column; + align-items: stretch; + overflow-y: auto; + background: transparent; + border: none; + box-shadow: none; + isolation: isolate; +} + +/* Header - sticky, content scrolls underneath */ +.changelog-header { + position: sticky; + top: 0; + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px 24px; + z-index: 20; + background: linear-gradient(to bottom, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 40%, rgba(22, 22, 22, 0.6) 70%, transparent 100%); + flex-shrink: 0; +} + +.changelog-header-left { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; + flex: 1 1 auto; +} + +.changelog-header-center { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + z-index: 1; +} + +.changelog-header-right { + flex: 0 0 auto; + margin-left: auto; + position: relative; + z-index: 1; +} + +.changelog-heading { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + min-width: 0; +} + +.changelog-brand { + display: inline-flex; + align-items: baseline; + font-size: 36px; + font-weight: 700; + letter-spacing: -1.5px; + line-height: 1; + overflow: hidden; +} + +.changelog-brand-master { + color: var(--gray-0); +} + +.changelog-brand-selects { + color: rgba(255, 255, 255, 0.7); +} + +.changelog-header-title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: rgba(255, 255, 255, 0.42); + letter-spacing: 0.18em; + line-height: 1; + text-transform: uppercase; +} + +.changelog-version { + align-self: center; + font-size: 20px; + font-weight: 600; + color: rgba(255, 255, 255, 0.5); + background: transparent; + padding-top: 0; +} + +.changelog-header-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 112px; + height: 42px; + padding: 0 20px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.82); + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +.changelog-header-button:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.18); + color: rgba(255, 255, 255, 0.96); +} + +.changelog-close { + width: 32px; + height: 32px; + border: none; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} + +.changelog-close:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--text-primary); +} + +/* Filter tabs */ +.changelog-tabs { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 0 0 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.changelog-tab { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + border: none; + background: rgba(255, 255, 255, 0.04); + border-radius: 8px; + color: var(--text-secondary); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; +} + +.changelog-tab:hover { + background: rgba(255, 255, 255, 0.08); + color: var(--text-primary); +} + +.changelog-tab.active { + background: rgba(255, 255, 255, 0.12); + color: var(--text-primary); +} + +.changelog-tab-new.active { + background: rgba(81, 207, 102, 0.15); + color: var(--green-300); +} + +.changelog-tab-fix.active { + background: rgba(77, 171, 247, 0.15); + color: var(--blue-300); +} + +.changelog-tab-improve.active { + background: rgba(255, 170, 0, 0.15); + color: var(--warning); +} + +.changelog-tab-refactor.active { + background: rgba(168, 85, 247, 0.15); + color: var(--purple-light); +} + +.changelog-tab-count { + font-size: 10px; + font-weight: 600; + opacity: 0.6; +} + +/* Content - normal flow, no own scroll */ +.changelog-content { + position: relative; + z-index: 0; + padding: 16px 24px 16px; + display: flex; + flex-direction: column; + gap: 24px; + background: rgba(22, 22, 22, 0.95); +} + +.changelog-content::before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: -48px; + height: 48px; + background: rgba(22, 22, 22, 0.95); + pointer-events: none; +} + +.changelog-featured { + display: grid; + grid-template-columns: minmax(0, 1fr) 248px; + grid-template-areas: 'notices side'; + align-items: flex-start; + gap: 18px; + transition: grid-template-columns 0.32s ease, gap 0.32s ease; +} + +.changelog-featured-notices { + grid-area: notices; + min-width: 0; + display: flex; + flex-direction: column; + gap: 12px; + transition: transform 0.32s ease, opacity 0.32s ease; +} + +.changelog-featured-notices .changelog-notice { + margin-bottom: 0; +} + +.changelog-featured-notices .changelog-notice:nth-child(3) { + margin-top: 10px; +} + +.changelog-featured.is-video-expanded { + grid-template-columns: minmax(0, 1fr); + grid-template-areas: + 'side' + 'notices'; + gap: 16px; +} + +.changelog-featured.is-video-expanded .changelog-featured-notices { + transform: translateY(0); + opacity: 1; +} + +.changelog-featured-side { + grid-area: side; + width: 248px; + margin-left: auto; + display: flex; + flex-direction: column; + gap: 12px; + transform-origin: top right; + transition: width 0.32s ease, transform 0.32s ease, opacity 0.32s ease; +} + +.changelog-featured.is-video-expanded .changelog-featured-side { + width: 100%; + margin-left: 0; +} + +.changelog-featured.is-video-expanded .changelog-video-container { + border-radius: 12px; + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.34); +} + +.changelog-featured.is-video-expanded .changelog-calendar { + justify-content: flex-end; +} + +/* Featured Video */ +.changelog-video { + width: 100%; +} + +.changelog-video-notice { + margin-bottom: 0; +} + +.changelog-video-shell { + display: flex; + flex-direction: column; + gap: 8px; +} + +.changelog-video-container { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + border-radius: 8px; + overflow: hidden; + background: #000; + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 16px 36px rgba(0, 0, 0, 0.28); + transition: border-color 0.16s ease, box-shadow 0.16s ease, border-radius 0.32s ease; +} + +.changelog-video-container:hover { + border-color: rgba(255, 255, 255, 0.14); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.34); +} + +.changelog-video-frame { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: none; + background: #000; +} + +.changelog-video-fallback { + align-self: flex-start; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.01em; + color: rgba(255, 255, 255, 0.54); + text-decoration: none; + transition: color 0.12s ease; +} + +.changelog-video-fallback:hover { + color: rgba(255, 255, 255, 0.82); + text-decoration: underline; +} + +.changelog-calendar { + width: 100%; + display: flex; + justify-content: flex-start; + gap: 3px; + padding: 0; + background: transparent; + border: none; + box-shadow: none; +} + +.changelog-calendar-week { + display: flex; + flex-direction: column; + gap: 3px; + overflow: visible; +} + +.changelog-calendar-day { + position: relative; + width: 9px; + height: 9px; + border-radius: 2px; + background: rgba(255, 255, 255, 0.05); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02); + transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease; + overflow: visible; +} + +.changelog-calendar-fill { + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; +} + +.changelog-calendar-fill-main { + background: rgba(255, 255, 255, 0.05); +} + +.changelog-calendar-fill-community { + display: none; +} + +.changelog-calendar-day.has-community .changelog-calendar-fill-main { + clip-path: inset(0 50% 0 0 round 2px); +} + +.changelog-calendar-day.has-community .changelog-calendar-fill-community { + display: block; + clip-path: inset(0 0 0 50% round 2px); +} + +.changelog-calendar-day::before { + content: ''; + position: absolute; + inset: -3px; +} + +.changelog-calendar-day:not(.is-future):not(.is-outside-range):hover { + transform: translateY(-1px); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16); +} + +.changelog-calendar-level-0 .changelog-calendar-fill-main { + background: rgba(255, 255, 255, 0.05); +} + +.changelog-calendar-level-1 .changelog-calendar-fill-main { + background: rgba(34, 197, 94, 0.22); +} + +.changelog-calendar-level-2 .changelog-calendar-fill-main { + background: rgba(34, 197, 94, 0.42); +} + +.changelog-calendar-level-3 .changelog-calendar-fill-main { + background: rgba(34, 197, 94, 0.65); +} + +.changelog-calendar-level-4 .changelog-calendar-fill-main { + background: rgba(74, 222, 128, 0.9); +} + +.changelog-calendar-community-level-0 .changelog-calendar-fill-community { + background: rgba(255, 184, 107, 0.14); +} + +.changelog-calendar-community-level-1 .changelog-calendar-fill-community { + background: rgba(245, 158, 11, 0.28); +} + +.changelog-calendar-community-level-2 .changelog-calendar-fill-community { + background: rgba(245, 158, 11, 0.48); +} + +.changelog-calendar-community-level-3 .changelog-calendar-fill-community { + background: rgba(249, 115, 22, 0.68); +} + +.changelog-calendar-community-level-4 .changelog-calendar-fill-community { + background: rgba(251, 146, 60, 0.92); +} + +.changelog-calendar-day.is-future { + opacity: 0.32; +} + +.changelog-calendar-day.is-today { + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.28); +} + +.changelog-calendar-day.is-outside-range { + opacity: 0; + box-shadow: none; +} + +.changelog-calendar-tooltip { + position: absolute; + left: 50%; + bottom: calc(100% + 8px); + transform: translateX(-50%); + padding: 5px 8px; + border-radius: 6px; + background: rgba(10, 10, 10, 0.96); + border: 1px solid rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.92); + font-size: 11px; + line-height: 1.2; + white-space: nowrap; + opacity: 0; + visibility: hidden; + pointer-events: none; + z-index: 20; + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35); +} + +.changelog-calendar-tooltip::after { + content: ''; + position: absolute; + left: 50%; + top: 100%; + width: 8px; + height: 8px; + background: rgba(10, 10, 10, 0.96); + border-right: 1px solid rgba(255, 255, 255, 0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + transform: translate(-50%, -4px) rotate(45deg); +} + +.changelog-calendar-day:not(.is-future):not(.is-outside-range):hover .changelog-calendar-tooltip { + opacity: 1; + visibility: visible; +} + +@media (max-width: 760px) { + .changelog-header { + display: flex; + flex-direction: column; + gap: 12px; + } + + .changelog-header-center { + position: static; + transform: none; + } + + .changelog-header-right { + margin-left: 0; + } + + .changelog-heading { + flex-direction: column; + align-items: flex-start; + gap: 6px; + } + + .changelog-featured { + grid-template-columns: 1fr; + grid-template-areas: + 'notices' + 'side'; + } + + .changelog-featured-side { + width: 100%; + margin-left: 0; + } + + .changelog-featured.is-video-expanded { + grid-template-areas: + 'side' + 'notices'; + } + + .changelog-notice-scribble { + left: 56px; + bottom: 5px; + } + + .changelog-notice-scribble-text { + font-size: 14px; + } +} + +/* Platform/Build Notice */ +.changelog-notice { + position: relative; + margin-bottom: 8px; +} + +.changelog-notice-body { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 16px; + border-radius: 8px; + min-height: 56px; + position: relative; + overflow: hidden; +} + +.changelog-notice-with-annotation { + padding-bottom: 10px; +} + +.changelog-notice-info { + --changelog-notice-glow-ring: rgba(59, 130, 246, 0.14); + --changelog-notice-glow-soft: rgba(59, 130, 246, 0.1); + --changelog-notice-glow-strong: rgba(96, 165, 250, 0.18); + background: rgba(59, 130, 246, 0.1); + border: 1px solid rgba(59, 130, 246, 0.3); + color: var(--blue-200); +} + +.changelog-notice-warning { + --changelog-notice-glow-ring: rgba(245, 158, 11, 0.14); + --changelog-notice-glow-soft: rgba(245, 158, 11, 0.1); + --changelog-notice-glow-strong: rgba(251, 191, 36, 0.18); + background: rgba(245, 158, 11, 0.1); + border: 1px solid rgba(245, 158, 11, 0.3); + color: var(--amber-300); +} + +.changelog-notice-success { + --changelog-notice-glow-ring: rgba(74, 222, 128, 0.14); + --changelog-notice-glow-soft: rgba(34, 197, 94, 0.12); + --changelog-notice-glow-strong: rgba(74, 222, 128, 0.2); + background: rgba(34, 197, 94, 0.12); + border: 1px solid rgba(74, 222, 128, 0.32); + color: #bbf7d0; +} + +.changelog-notice-danger { + --changelog-notice-glow-ring: rgba(248, 113, 113, 0.14); + --changelog-notice-glow-soft: rgba(239, 68, 68, 0.1); + --changelog-notice-glow-strong: rgba(248, 113, 113, 0.18); + --changelog-notice-sheen-width: 58%; + --changelog-notice-sheen-start: -125%; + --changelog-notice-sheen-end: 190%; + background: rgba(239, 68, 68, 0.12); + border: 1px solid rgba(248, 113, 113, 0.32); + color: #fecaca; +} + +.changelog-notice-animated { + animation: changelog-notice-pulse 6s ease-in-out infinite; + animation-delay: var(--changelog-notice-delay, 0s); + box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.12)), 0 0 24px var(--changelog-notice-glow-soft, rgba(34, 197, 94, 0.12)); +} + +.changelog-notice-animated::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: var(--changelog-notice-sheen-width, 100%); + z-index: 0; + background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.18) 48%, transparent 100%); + transform: translateX(var(--changelog-notice-sheen-start, -130%)); + animation: changelog-notice-sheen 6s ease-in-out infinite; + animation-delay: var(--changelog-notice-delay, 0s); + pointer-events: none; +} + +.changelog-notice-icon { + flex-shrink: 0; + margin-top: 2px; + position: relative; + z-index: 1; +} + +.changelog-notice-content { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + position: relative; + z-index: 1; +} + +.changelog-notice-title { + font-size: 13px; + font-weight: 600; + line-height: 1.35; +} + +.changelog-notice-message { + font-size: 12px; + opacity: 0.8; + line-height: 1.35; +} + +.changelog-notice-link { + color: currentColor; + font-weight: 700; + text-decoration: underline; + text-decoration-color: rgba(255, 255, 255, 0.28); + text-underline-offset: 2px; +} + +.changelog-notice-link:hover { + text-decoration-color: currentColor; +} + +.changelog-notice-scribble { + position: absolute; + left: 62px; + bottom: 6px; + z-index: 2; + pointer-events: none; + opacity: 0.92; + display: inline-flex; + align-items: flex-end; + gap: 3px; + transform: rotate(10deg); +} + +.changelog-notice-scribble-text { + position: relative; + display: inline-block; + color: #ff5a5f; + font-family: 'Segoe Print', 'Bradley Hand', 'Marker Felt', 'Comic Sans MS', cursive; + font-size: 15px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.03em; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.08), 0 2px 10px rgba(127, 29, 29, 0.24); +} + +.changelog-notice-scribble-text::before { + content: ''; + position: absolute; + left: -3px; + right: -3px; + bottom: 1px; + height: 7px; + border-bottom: 1.5px solid rgba(255, 90, 95, 0.42); + border-radius: 50%; + transform: rotate(-2deg); +} + +.changelog-notice-scribble-arrow { + width: 24px; + height: 12px; + margin-bottom: 4px; + overflow: visible; +} + +.changelog-notice-scribble-arrow path { + stroke: rgba(255, 90, 95, 0.84); + stroke-width: 2.2; + stroke-linecap: round; + stroke-linejoin: round; +} + +@keyframes changelog-notice-pulse { + 0%, 6%, 16%, 100% { + box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.12)), 0 0 18px var(--changelog-notice-glow-soft, rgba(34, 197, 94, 0.08)); + } + 8%, 12% { + box-shadow: 0 0 0 1px var(--changelog-notice-glow-ring, rgba(74, 222, 128, 0.22)), 0 0 30px var(--changelog-notice-glow-strong, rgba(74, 222, 128, 0.18)); + } +} + +@keyframes changelog-notice-sheen { + 0%, 4% { + transform: translateX(var(--changelog-notice-sheen-start, -130%)); + } + 14%, 100% { + transform: translateX(var(--changelog-notice-sheen-end, 130%)); + } +} + +/* Time group */ +.changelog-group { + display: flex; + flex-direction: column; + gap: 10px; +} + +.changelog-group-header { + display: flex; + align-items: center; + gap: 10px; +} + +.changelog-group-label { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} + +.changelog-group-date { + font-size: 11px; + color: var(--text-secondary); + opacity: 0.7; +} + +.changelog-group-line { + flex: 1; + height: 1px; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, transparent 100%); +} + +.changelog-group-items { + display: flex; + gap: 12px; +} + +.changelog-column { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +} + +/* Section divider within time group */ +.changelog-section-divider { + display: flex; + align-items: center; + gap: 10px; + margin: 8px 0 4px 0; + padding-top: 8px; +} + +.changelog-section-label { + font-size: 12px; + font-weight: 500; + color: var(--amber-600); + white-space: nowrap; +} + +.changelog-section-line { + flex: 1; + height: 1px; + background: linear-gradient(90deg, rgba(245, 159, 0, 0.3) 0%, transparent 100%); +} + +/* Change item */ +.changelog-item { + display: flex; + flex-direction: column; + padding: 8px 12px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.02); + border: 1px solid transparent; + transition: background 0.15s, border-color 0.15s, box-shadow 0.15s; +} + +.changelog-item.has-description { + cursor: pointer; +} + +.changelog-item.has-description:hover { + background: rgba(255, 255, 255, 0.05); +} + +.changelog-item-community { + position: relative; + overflow: hidden; + background: + linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(255, 255, 255, 0.03) 42%, rgba(250, 204, 21, 0.08) 100%); + border-color: rgba(245, 158, 11, 0.26); + box-shadow: + inset 0 0 0 1px rgba(250, 204, 21, 0.06), + 0 0 0 1px rgba(0, 0, 0, 0.08); +} + +.changelog-item-community::after { + content: ''; + position: absolute; + inset: 0 auto 0 -40%; + width: 40%; + background: linear-gradient(90deg, transparent 0%, rgba(255, 235, 170, 0.04) 35%, rgba(255, 235, 170, 0.26) 50%, rgba(255, 235, 170, 0.04) 65%, transparent 100%); + transform: skewX(-18deg); + animation: changelog-community-sheen 6.4s ease-in-out infinite; + pointer-events: none; +} + +.changelog-item-header { + display: flex; + align-items: center; + gap: 10px; +} + +.changelog-icon { + width: 22px; + height: 22px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.changelog-icon-new { + background: rgba(81, 207, 102, 0.15); + color: var(--green-300); +} + +.changelog-icon-fix { + background: rgba(77, 171, 247, 0.15); + color: var(--blue-300); +} + +.changelog-icon-improve { + background: rgba(255, 170, 0, 0.15); + color: var(--warning); +} + +.changelog-icon-refactor { + background: rgba(168, 85, 247, 0.15); + color: var(--purple-light); +} + +.changelog-item .changelog-title { + flex: 1; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + line-height: 1.3; +} + +.changelog-badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 20px; + padding: 0 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + white-space: nowrap; + border: 1px solid transparent; + flex-shrink: 0; +} + +.changelog-badge-community { + background: rgba(245, 158, 11, 0.14); + border-color: rgba(251, 191, 36, 0.26); + color: #fde68a; + box-shadow: + 0 0 0 1px rgba(250, 204, 21, 0.04), + 0 0 18px rgba(245, 158, 11, 0.12); + animation: changelog-community-badge 3.8s ease-in-out infinite; +} + +.changelog-expand { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + opacity: 0.5; +} + +.changelog-description-wrapper { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.2s ease-out; +} + +.changelog-item.expanded .changelog-description-wrapper { + grid-template-rows: 1fr; +} + +.changelog-description { + overflow: hidden; + margin-left: 32px; + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; + display: flex; + flex-direction: column; + gap: 6px; +} + +.changelog-item.expanded .changelog-description { + padding-top: 6px; +} + +.changelog-contributor-label, +.changelog-contributor-link { + width: fit-content; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.01em; + color: rgba(253, 230, 138, 0.82); + text-decoration: none; +} + +.changelog-contributor-link:hover { + color: #fef3c7; + text-decoration: underline; +} + +.changelog-commit-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--text-tertiary); + font-size: 11px; + font-family: monospace; + text-decoration: none; + opacity: 0.7; + transition: opacity 0.15s, color 0.15s; + width: fit-content; +} + +.changelog-commit-link:hover { + opacity: 1; + color: var(--accent); +} + +.changelog-commit-link svg { + flex-shrink: 0; +} + +.changelog-commits { + display: flex; + flex-wrap: wrap; + gap: 4px 8px; + margin-top: 4px; +} + +.changelog-commit-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 9px; + background: rgba(255, 255, 255, 0.06); + color: var(--text-tertiary); + font-size: 10px; + font-weight: 500; + flex-shrink: 0; + margin-left: auto; +} + +@keyframes changelog-community-badge { + 0%, + 100% { + box-shadow: + 0 0 0 1px rgba(250, 204, 21, 0.04), + 0 0 18px rgba(245, 158, 11, 0.12); + } + 50% { + box-shadow: + 0 0 0 1px rgba(253, 224, 71, 0.12), + 0 0 26px rgba(245, 158, 11, 0.24); + } +} + +@keyframes changelog-community-sheen { + 0%, + 72%, + 100% { + transform: translateX(0) skewX(-18deg); + opacity: 0; + } + 8% { + opacity: 1; + } + 26% { + transform: translateX(360%) skewX(-18deg); + opacity: 1; + } + 32% { + transform: translateX(420%) skewX(-18deg); + opacity: 0; + } +} + +/* Known issues */ +.changelog-issues { + margin: 0 24px 16px; + padding: 12px 14px; + background: rgba(255, 68, 68, 0.05); + border: 1px solid rgba(255, 68, 68, 0.12); + border-radius: 10px; +} + +.changelog-issues-header { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--danger-light); + margin-bottom: 8px; +} + +.changelog-issues-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.changelog-issue-item { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; + padding-left: 22px; + position: relative; +} + +.changelog-issue-item::before { + content: '•'; + position: absolute; + left: 8px; + color: var(--danger-light); + opacity: 0.5; +} + +/* Footer - sticky at bottom, content scrolls underneath */ +.changelog-footer { + position: sticky; + bottom: 0; + padding: 24px 24px 16px; + border-top: none; + display: flex; + align-items: center; + justify-content: flex-end; + z-index: 20; + background: linear-gradient(to top, rgba(22, 22, 22, 0.97) 0%, rgba(22, 22, 22, 0.92) 30%, rgba(22, 22, 22, 0.6) 60%, transparent 100%); + flex-shrink: 0; +} + +.changelog-dont-show { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: rgba(255, 255, 255, 0.45); + cursor: pointer; + white-space: nowrap; + user-select: none; + flex-shrink: 0; +} + +.changelog-dont-show input[type="checkbox"] { + accent-color: var(--accent, #4f9cf9); + cursor: pointer; + width: 13px; + height: 13px; +} + +.changelog-dont-show:hover { + color: rgba(255, 255, 255, 0.7); +} + +/* Changelog calendar section - full width at top of changelog */ +.changelog-calendar-section { + display: flex; + justify-content: center; + padding: 4px 0 8px; +} + +.changelog-calendar-section .changelog-calendar { + justify-content: center; +} + +/* Legacy styles kept for backward compatibility */ +.whats-new-dialog:not(.changelog-dialog):not(.splash-dialog) { + width: 420px; + max-height: 80vh; + padding: 28px 32px 24px; + text-align: left; + overflow-y: auto; +} +} diff --git a/src/components/common/WhatsNewDialog.tsx b/src/components/common/WhatsNewDialog.tsx index 0e1f29478..ff867d5e4 100644 --- a/src/components/common/WhatsNewDialog.tsx +++ b/src/components/common/WhatsNewDialog.tsx @@ -3,6 +3,8 @@ // for use by SplashScreen. import { useState, useEffect, useCallback, useMemo, type CSSProperties } from 'react'; +import './WelcomeOverlay.css'; +import './WhatsNewDialog.css'; import { APP_VERSION, getChangelogCalendar, diff --git a/src/components/export/ExportPanel.css b/src/components/export/ExportPanel.css new file mode 100644 index 000000000..b17d5f090 --- /dev/null +++ b/src/components/export/ExportPanel.css @@ -0,0 +1,1151 @@ +@layer features { +/* Export Panel */ +.export-panel { + display: flex; + flex-direction: column; + height: 100%; +} + +.export-panel .panel-header { + padding: var(--sp-3); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.export-panel .panel-header h3 { + margin: 0; + font-size: var(--font-xl); +} + +.export-form { + padding: var(--sp-3); + display: flex; + flex-direction: column; + gap: var(--sp-3); +} + +.export-section { + display: flex; + flex-direction: column; + gap: var(--sp-2); +} + +.export-section-header { + font-size: var(--font-sm); + font-weight: var(--font-semibold); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + padding-bottom: 4px; + border-bottom: 1px solid var(--border-color); + margin-bottom: var(--sp-1); +} + +.info-icon-wrapper { + position: relative; + margin-left: var(--sp-1); +} + +.info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + font-size: var(--font-sm); + font-style: italic; + font-family: Georgia, serif; + color: var(--text-muted); + cursor: pointer; + border-radius: 50%; + transition: all var(--transition-slow) ease; +} + +.info-icon:hover { + color: var(--gray-100); + background: rgba(255, 255, 255, 0.08); + transform: scale(1.1); +} + +.info-tooltip { + position: absolute; + left: 50%; + top: calc(100% + 8px); + transform: translateX(-50%); + width: 200px; + padding: var(--sp-2) var(--sp-2-5); + background: var(--gray-300); + color: var(--text-primary); + font-size: var(--font-sm); + font-style: normal; + font-weight: normal; + line-height: 1.4; + border-radius: var(--radius-lg); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + z-index: 100; +} + +.info-tooltip::before { + content: ''; + position: absolute; + left: 50%; + top: -6px; + transform: translateX(-50%); + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid var(--gray-300); +} + +.export-panel .control-row { + display: flex; + align-items: center; + gap: var(--sp-2); +} + +.export-panel .control-row label { + display: flex; + align-items: center; + min-width: 80px; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.export-panel select, +.export-panel input[type="text"] { + flex: 1; + padding: var(--sp-1-5) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); +} + +.export-input-group { + flex: 1; + display: flex; + align-items: center; + min-width: 0; +} + +.export-input-group input { + flex: 1; + min-width: 60px; + border-radius: 4px 0 0 4px; + text-overflow: ellipsis; + overflow: hidden; +} + +.export-extension { + padding: var(--sp-1-5) var(--sp-2); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-left: none; + border-radius: 0 4px 4px 0; + color: var(--text-secondary); + font-size: var(--font-md); +} + +.export-extension-select { + padding: 6px 20px 6px 8px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-left: none; + border-radius: 0 4px 4px 0; + color: var(--text-secondary); + font-size: var(--font-md); + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M3 4.5L6 8l3-3.5H3z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 4px center; + width: auto; + flex-shrink: 0; +} + +.export-extension-select:hover { + background-color: var(--bg-hover); + color: var(--text-primary); +} + +.export-extension-select:focus { + outline: none; + border-color: var(--accent-color); +} + +.export-summary { + padding: var(--sp-2-5); + background: var(--bg-tertiary); + border-radius: var(--radius-md); + font-size: var(--font-sm); + color: var(--text-secondary); + display: flex; + flex-direction: column; + gap: var(--sp-1); +} + +.export-error { + padding: var(--sp-2-5); + background: rgba(239, 68, 68, 0.1); + border: 1px solid var(--danger); + border-radius: var(--radius-md); + color: var(--danger); + font-size: var(--font-md); +} + +.export-start-btn { + width: 100%; + padding: var(--sp-2-5); + background: var(--accent); + color: var(--bg-primary); + border: none; + border-radius: var(--radius-md); + font-weight: var(--font-semibold); + cursor: pointer; +} + +.export-start-btn:hover { + background: var(--accent-hover); +} + +.export-start-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.export-progress-container { + padding: var(--sp-3); + display: flex; + flex-direction: column; + gap: var(--sp-3); +} + +.export-progress-bar { + height: 8px; + background: var(--bg-tertiary); + border-radius: var(--radius-md); + overflow: hidden; +} + +.export-progress-fill { + height: 100%; + background: var(--accent); + transition: width var(--transition-fast) ease-out; +} + +.export-progress-info { + display: flex; + justify-content: space-between; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.export-eta { + text-align: center; + font-size: var(--font-lg); + color: var(--text-primary); +} + +.export-cancel-btn { + width: 100%; + padding: var(--sp-2); + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + cursor: pointer; +} + +.export-cancel-btn:hover { + background: var(--bg-hover); +} + +/* Export Panel refresh */ +.export-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-3); + padding: var(--sp-3) var(--sp-3) 0; +} + +.export-mode-tabs { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 4px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 14px; +} + +.export-mode-tab { + padding: 8px 12px; + border: none; + border-radius: 10px; + background: transparent; + color: var(--text-secondary); + font-size: var(--font-md); + font-weight: var(--font-medium); + cursor: pointer; + transition: background var(--transition-fast) ease, color var(--transition-fast) ease; +} + +.export-mode-tab:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.06); + color: var(--text-primary); +} + +.export-mode-tab.is-active { + background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 72%, transparent) 0%, color-mix(in srgb, var(--accent-hover) 88%, transparent) 100%); + color: var(--bg-primary); +} + +.export-mode-tab:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.export-toolbar-cta { + width: auto; + min-width: 152px; +} + +.export-form { + padding: 0 var(--sp-3) var(--sp-3); + display: flex; + flex-direction: column; + gap: var(--sp-3); + overflow-y: auto; +} + +.export-summary-sticky { + position: sticky; + top: 0; + z-index: 8; + backdrop-filter: blur(14px); +} + +.export-summary-badges { + margin: 0 calc(var(--sp-3) * -1) 0; + padding: 10px 12px 12px; + border-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2); +} + +.export-summary-badges .export-pill-row { + gap: 6px; + flex: 1 1 auto; + min-width: 0; +} + +.export-summary-actions { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: stretch; + gap: 12px; +} + +.export-summary-cta { + display: inline-flex; + align-items: center; + justify-content: center; + align-self: stretch; + min-width: 108px; + white-space: nowrap; + padding: 10px 18px; + border-radius: 16px; + border: 1px solid color-mix(in srgb, var(--success) 42%, transparent); + background: color-mix(in srgb, var(--success) 14%, rgba(255, 255, 255, 0.02)); + color: var(--success-light); + font-weight: var(--font-semibold); + letter-spacing: 0.01em; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); + transition: + transform var(--transition-fast) ease, + border-color var(--transition-fast) ease, + color var(--transition-fast) ease, + background var(--transition-fast) ease; +} + +.export-summary-cta:hover:not(:disabled) { + background: color-mix(in srgb, var(--success) 18%, rgba(255, 255, 255, 0.03)); + border-color: color-mix(in srgb, var(--success) 56%, transparent); + color: color-mix(in srgb, var(--success-light) 88%, #ffffff 12%); + transform: translateY(-1px); +} + +.export-summary-cta:disabled { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.08); + color: var(--text-muted); + box-shadow: none; +} + +.export-hero-card, +.export-section { + padding: 14px; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.025) 0%, rgba(255, 255, 255, 0.01) 100%), + var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 14px; + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); +} + +.export-hero-card { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 10px; +} + +.export-hero-main { + display: flex; + flex-direction: column; + gap: 6px; +} + +.export-hero-kicker { + font-size: 11px; + font-weight: var(--font-semibold); + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); +} + +.export-hero-title-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.export-hero-title-row h3 { + margin: 0; + font-size: 20px; + line-height: 1.1; + color: var(--text-primary); +} + +.export-hero-main p { + margin: 0; + max-width: 54ch; + font-size: var(--font-md); + line-height: 1.5; + color: var(--text-secondary); +} + +.export-badge { + display: inline-flex; + align-items: center; + padding: 4px 10px; + border-radius: 999px; + background: color-mix(in srgb, var(--accent) 18%, transparent); + border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); + color: var(--accent); + font-size: 11px; + font-weight: var(--font-semibold); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.export-hero-metrics { + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: flex-start; + gap: 4px; + min-width: 120px; +} + +.export-hero-metric-label, +.export-hero-metric-subtle { + font-size: 11px; + color: var(--text-muted); +} + +.export-hero-metrics strong { + font-size: 24px; + line-height: 1; + color: var(--text-primary); +} + +.export-pill-row { + grid-column: 1 / -1; + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.export-pill { + display: inline-flex; + align-items: center; + padding: 6px 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.045); + border: 1px solid rgba(255, 255, 255, 0.08); + color: var(--text-secondary); + font-size: 12px; + font: inherit; + line-height: 1.2; + white-space: nowrap; + cursor: pointer; + appearance: none; + transition: + transform var(--transition-fast) ease, + border-color var(--transition-fast) ease, + color var(--transition-fast) ease, + background var(--transition-fast) ease; +} + +.export-pill:hover { + transform: translateY(-1px); + color: var(--text-primary); + border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); +} + +.export-pill:focus-visible { + outline: none; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent); +} + +.export-pill-warning { + color: var(--warning-light); + border-color: color-mix(in srgb, var(--warning) 35%, transparent); + background: var(--warning-subtle); +} + +.export-hero-footnote { + grid-column: 1 / -1; + font-size: 12px; + color: var(--text-muted); +} + +.export-workflow-section, +.export-workflow-section .export-legacy-control { + display: none; +} + +.export-command-row { + display: flex; + flex-direction: column; + gap: 10px; +} + +.export-command-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.export-command-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + width: 100%; +} + +.export-preset-picker { + display: inline-flex; + align-items: center; + flex: 1 1 220px; + min-width: 0; + max-width: 100%; +} + +.export-preset-picker select { + width: 100%; + min-width: 0; + max-width: 100%; + padding: 8px 34px 8px 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 999px; + background: var(--bg-primary); + color: var(--text-primary); + font: inherit; +} + +.export-channel-grid { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 12px; + align-items: start; +} + +.export-channel-card { + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px; + background: rgba(255, 255, 255, 0.025); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 14px; + scroll-margin-top: 104px; +} + +.export-channel-card.is-disabled { + opacity: 0.82; +} + +.export-channel-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.export-channel-title { + display: flex; + flex-direction: column; + gap: 4px; +} + +.export-channel-title span { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.export-channel-title strong { + font-size: 18px; + line-height: 1.1; + color: var(--text-primary); +} + +.export-method-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} + +.export-method-card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 6px; + min-height: 0; + padding: 10px 12px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 12px; + color: var(--text-secondary); + text-align: left; + cursor: pointer; + transition: + border-color var(--transition-fast) ease, + transform var(--transition-fast) ease, + box-shadow var(--transition-fast) ease, + color var(--transition-fast) ease; +} + +.export-method-card strong { + color: var(--text-primary); + font-size: 14px; +} + +.export-method-card span:last-child { + font-size: 12px; + line-height: 1.45; +} + +.export-method-card:hover { + transform: translateY(-1px); + border-color: color-mix(in srgb, var(--accent) 40%, var(--border-color)); + color: var(--text-primary); +} + +.export-method-card.is-active { + border-color: color-mix(in srgb, var(--accent) 70%, transparent); + background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 11%, transparent) 0%, rgba(255, 255, 255, 0.02) 100%), var(--bg-primary); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); +} + +.export-method-chip { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.05); + color: var(--text-muted); + font-size: 10px; + font-weight: var(--font-semibold); + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.export-status-row { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 10px; +} + +.export-status-button { + padding: 6px 12px; + font-size: 11px; +} + +.export-status-ok { + color: var(--success); + font-size: 12px; + font-weight: var(--font-semibold); +} + +.export-error-inline { + margin-top: 4px; + font-size: 11px; +} + +.export-basics-section { + padding: 0; + background: transparent; + border: 0; + box-shadow: none; + gap: 14px; +} + +.export-basics-section > .export-section-header { + display: none; +} + +.export-quick-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 10px; +} + +.export-quick-grid-stack { + grid-template-columns: 1fr; +} + +.export-field-card { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; + scroll-margin-top: 104px; +} + +.export-scroll-highlight { + animation: export-scroll-highlight 1.1s ease; +} + +@keyframes export-scroll-highlight { + 0%, + 100% { + border-color: rgba(255, 255, 255, 0.06); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + } + + 30%, + 70% { + border-color: color-mix(in srgb, var(--accent) 58%, rgba(255, 255, 255, 0.06)); + box-shadow: + 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent), + 0 0 22px color-mix(in srgb, var(--accent) 16%, transparent); + } +} + +.export-subcard { + background: rgba(255, 255, 255, 0.02); +} + +.export-field-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 10px; +} + +.export-field-head span { + font-size: 12px; + color: var(--text-muted); +} + +.export-field-head strong { + font-size: 13px; + color: var(--text-primary); +} + +.export-chip-row, +.export-toggle-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.export-container-groups { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px 18px; +} + +.export-container-group { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.export-container-group-label { + font-size: 10px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.export-container-group .export-chip-row { + display: grid; + grid-template-columns: repeat(2, max-content); + justify-content: start; + align-content: start; +} + +.export-chip, +.export-toggle { + padding: 8px 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 999px; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 12px; + font-weight: var(--font-medium); + cursor: pointer; + transition: + border-color var(--transition-fast) ease, + background var(--transition-fast) ease, + color var(--transition-fast) ease, + transform var(--transition-fast) ease; +} + +.export-chip:hover, +.export-toggle:hover { + transform: translateY(-1px); + color: var(--text-primary); + border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); +} + +.export-chip.is-active, +.export-toggle.is-active { + color: var(--accent); + background: color-mix(in srgb, var(--accent) 12%, transparent); + border-color: color-mix(in srgb, var(--accent) 30%, transparent); +} + +.export-toggle:disabled, +.export-chip:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.export-chip-static { + cursor: default; +} + +.export-chip-static:hover { + transform: none; + color: var(--text-secondary); +} + +.export-inline-inputs { + display: flex; + align-items: center; + gap: 8px; +} + +.export-inline-inputs-single { + max-width: 140px; +} + +.export-inline-inputs span { + color: var(--text-muted); +} + +.export-slider-control { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.export-slider-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.export-slider-head label { + font-size: 12px; + color: var(--text-muted); +} + +.export-slider-value { + min-width: 0; + font-size: 12px; + color: var(--text-primary); + text-align: right; +} + +.export-slider-input { + width: 100%; + min-width: 0; +} + +.export-preset-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; +} + +.export-preset-card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + padding: 12px; + background: var(--bg-primary); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 12px; + color: var(--text-secondary); + text-align: left; + cursor: pointer; + transition: + border-color var(--transition-fast) ease, + background var(--transition-fast) ease, + transform var(--transition-fast) ease; +} + +.export-preset-card strong { + color: var(--text-primary); + font-size: 13px; +} + +.export-preset-card span { + font-size: 11px; + color: var(--text-muted); +} + +.export-preset-card:hover { + transform: translateY(-1px); + border-color: color-mix(in srgb, var(--accent) 28%, var(--border-color)); +} + +.export-preset-card.is-active { + border-color: color-mix(in srgb, var(--accent) 34%, transparent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} + +.export-audio-grid { + display: grid; + gap: 10px; +} + +.export-inline-note { + padding: 10px 12px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.035); + border: 1px solid rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + font-size: 12px; + line-height: 1.45; +} + +.export-inline-note-warning { + background: var(--warning-subtle); + border-color: color-mix(in srgb, var(--warning) 35%, transparent); + color: var(--warning-light); +} + +.export-stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; +} + +.export-stat-card { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; +} + +.export-stat-card span { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.export-stat-card strong { + font-size: 13px; + color: var(--text-primary); +} + +.export-stats-grid-compact { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.export-advanced-section { + display: none; + gap: 10px; +} + +.export-advanced-section .export-section-header { + color: var(--text-primary); + border-bottom-style: dashed; +} + +.export-panel .control-row { + display: flex; + align-items: center; + gap: 12px; +} + +.export-panel .control-row label { + display: flex; + align-items: center; + min-width: 96px; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.export-panel select, +.export-panel input[type="text"], +.export-panel input[type="number"] { + flex: 1; + min-width: 0; + padding: 10px 12px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 10px; + color: var(--text-primary); + font-size: var(--font-md); +} + +.export-input-group { + flex: 1; + display: flex; + align-items: center; + min-width: 0; +} + +.export-range-row { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.export-range-row input[type="range"] { + flex: 1; +} + +.export-range-row span { + min-width: 78px; + text-align: right; +} + +.export-summary { + padding: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; +} + +.export-progress-container { + padding: var(--sp-3); + display: flex; + flex-direction: column; + gap: var(--sp-3); +} + +.export-progress-bar { + height: 10px; + background: rgba(255, 255, 255, 0.06); + border-radius: 999px; + overflow: hidden; +} + +.export-progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--accent), var(--accent-hover)); + transition: width var(--transition-fast) ease-out; +} + +.export-progress-info { + display: flex; + justify-content: space-between; + gap: 12px; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.export-eta { + text-align: left; + font-size: var(--font-md); + color: var(--text-primary); +} + +.export-cancel-btn { + width: 100%; + padding: var(--sp-2); + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 12px; + cursor: pointer; +} + +@media (max-width: 900px) { + .export-toolbar, + .export-hero-card { + grid-template-columns: 1fr; + flex-direction: column; + align-items: stretch; + } + + .export-toolbar-cta { + width: 100%; + } + + .export-summary-actions { + grid-template-columns: 1fr; + } + + .export-command-bar { + flex-direction: column; + align-items: stretch; + } + + .export-summary-cta { + width: 100%; + min-height: 44px; + } + + .export-method-grid, + .export-channel-grid, + .export-preset-grid, + .export-quick-grid, + .export-stats-grid { + grid-template-columns: 1fr; + } + + .export-hero-metrics { + align-items: flex-start; + } +} +} diff --git a/src/components/export/ExportPanel.tsx b/src/components/export/ExportPanel.tsx index e4a936c13..16e1555ba 100644 --- a/src/components/export/ExportPanel.tsx +++ b/src/components/export/ExportPanel.tsx @@ -1,6 +1,7 @@ // Export Panel - embedded panel for frame-by-frame video export import { useCallback, useRef, useState } from 'react'; +import './ExportPanel.css'; import { Logger } from '../../services/logger'; import { downloadFCPXML } from '../../services/export/fcpxmlExport'; import { projectFileService } from '../../services/projectFileService'; diff --git a/src/components/panels/ClipPropertiesPanel.tsx b/src/components/panels/ClipPropertiesPanel.tsx index ca623d6a5..d489d0f88 100644 --- a/src/components/panels/ClipPropertiesPanel.tsx +++ b/src/components/panels/ClipPropertiesPanel.tsx @@ -8,6 +8,8 @@ import type { BlendMode, AnimatableProperty, MaskMode, ClipMask } from '../../ty import { KeyframeToggle } from './properties/shared'; import { BLEND_MODE_GROUPS, formatBlendModeName } from './properties/sharedConstants'; import { EditableDraggableNumber as DraggableNumber } from '../common/EditableDraggableNumber'; +import './properties/PropertiesPanel.css'; +import './properties/ClipPropertiesPanel.css'; // Precision slider with modifier key support // Shift = half speed, Ctrl = super slow (10x slower) diff --git a/src/components/panels/MediaPanel.css b/src/components/panels/MediaPanel.css new file mode 100644 index 000000000..1350936ae --- /dev/null +++ b/src/components/panels/MediaPanel.css @@ -0,0 +1,1460 @@ +@layer features { +/* ========== MEDIA PANEL ========== */ +.media-panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--bg-secondary); + transition: background 0.15s ease, box-shadow 0.15s ease; + position: relative; +} + +.media-panel.drop-target { + background: rgba(74, 144, 226, 0.1); +} + +/* Drop overlay - visible when dragging files over the panel */ +.media-panel-drop-overlay { + position: absolute; + inset: 0; + background: rgba(74, 144, 226, 0.15); + border: 3px dashed var(--accent-color); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + pointer-events: none; + animation: dropPulse 1.5s ease-in-out infinite; +} + +@keyframes dropPulse { + 0%, 100% { background: rgba(74, 144, 226, 0.1); } + 50% { background: rgba(74, 144, 226, 0.25); } +} + +.drop-overlay-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + color: var(--accent-color); +} + +.drop-overlay-content svg { + animation: dropBounce 0.8s ease-in-out infinite; +} + +@keyframes dropBounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.drop-overlay-content span { + font-size: 16px; + font-weight: 600; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); +} + +.media-panel-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + flex-shrink: 0; +} + +.media-panel-title { + font-weight: 600; + font-size: 13px; +} + +.media-panel-count { + font-size: 11px; + color: var(--text-secondary); + margin-right: auto; + white-space: nowrap; +} + +.media-panel-search { + position: relative; + display: flex; + align-items: center; + width: min(260px, 28vw); + min-width: 140px; + height: 24px; + color: var(--text-muted); + flex: 0 1 260px; +} + +.media-panel-search svg { + position: absolute; + left: 8px; + pointer-events: none; +} + +.media-panel-search input { + width: 100%; + height: 24px; + padding: 0 24px 0 26px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 11px; + outline: none; +} + +.media-panel-search input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 1px rgba(45, 140, 235, 0.28); +} + +.media-panel-search-clear { + position: absolute; + right: 4px; + width: 17px; + height: 17px; + border: 0; + border-radius: 3px; + background: transparent; + color: var(--text-secondary); + font-size: 12px; + line-height: 17px; + cursor: pointer; +} + +.media-panel-search-clear:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.media-panel-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.media-panel-actions .btn { + height: 24px; + display: inline-flex; + align-items: center; + box-sizing: border-box; +} + +.media-panel-content { + flex: 1; + overflow: auto; + display: flex; + flex-direction: column; +} + +.media-panel-view-transition-layer { + position: fixed; + overflow: hidden; + pointer-events: none; + z-index: 10000; + contain: layout paint; +} + +.media-panel-view-transition-clone { + position: absolute !important; + margin: 0 !important; + box-sizing: border-box; + overflow: hidden; + pointer-events: none !important; + user-select: none !important; + transform-origin: 0 0; + will-change: transform, opacity; +} + +.media-panel-view-transition-clone, +.media-panel-view-transition-clone * { + pointer-events: none !important; + transition: none !important; +} + +.media-panel-view-transition-hidden { + visibility: hidden !important; +} + +/* Table wrapper for horizontal scrolling */ +.media-panel-table-wrapper { + min-width: 650px; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + +/* Column headers */ +.media-column-headers { + display: flex; + align-items: center; + padding: 4px 0; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); + position: sticky; + top: 0; + z-index: 10; + font-size: 10px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Column base styles */ +.media-col { + flex-shrink: 0; + padding: 0 6px; + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Column widths */ +.media-col-name { + display: flex; + align-items: center; + gap: 4px; + overflow: hidden; + position: relative; + flex-shrink: 0; +} + +/* Resize handle for name column */ +.media-col-resize-handle { + position: absolute; + right: -1px; + top: 2px; + bottom: 2px; + width: 3px; + cursor: col-resize; + background: var(--border-color); + border-radius: 1px; + transition: background 0.15s; + z-index: 5; +} + +.media-col-resize-handle:hover, +.media-col-resize-handle:active { + background: var(--accent-color); +} + +/* Column header drag styles */ +.media-column-headers .media-col { + cursor: grab; + user-select: none; + transition: background 0.1s, opacity 0.1s; + position: relative; +} + +.media-column-headers .media-col:active { + cursor: grabbing; +} + +.media-column-headers .media-col.dragging { + opacity: 0.5; +} + +.media-column-headers .media-col.drag-over { + background: rgba(74, 144, 226, 0.3); +} + +.media-column-headers .media-col.sorted { + color: var(--text-primary); +} + +.media-sort-indicator { + font-size: 7px; + margin-left: 2px; + opacity: 0.7; + display: inline-block; + vertical-align: middle; +} + +.media-column-headers .media-col:hover { + color: var(--text-primary); +} + +/* Label color column */ +.media-col-label { + width: 30px; + min-width: 30px; + max-width: 30px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 2px; + cursor: pointer; +} + +.media-label-dot { + width: 8px; + height: 8px; + border-radius: 2px; + display: block; + flex-shrink: 0; +} + +.media-label-dot:hover { + outline: 1px solid rgba(255, 255, 255, 0.3); +} + +/* Label color picker popup */ +.label-picker-backdrop { + position: fixed; + inset: 0; + z-index: 10001; +} + +.label-picker-popup { + background: var(--bg-secondary, #1e1e1e); + border: 1px solid var(--border-color, #3a3a3a); + border-radius: 4px; + padding: 4px; + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 2px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); +} + +.label-picker-swatch { + width: 16px; + height: 16px; + border-radius: 2px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.1s; +} + +.label-picker-swatch:hover { + transform: scale(1.2); + outline: 1px solid rgba(255, 255, 255, 0.5); +} + +.label-picker-swatch.none { + border: 1px solid var(--border-color, #555); +} + +.label-picker-x { + font-size: 10px; + line-height: 1; + color: var(--text-secondary); +} + +.media-col-duration { + width: 56px; + min-width: 56px; + text-align: right; +} + +.media-col-resolution { + width: 118px; + min-width: 118px; + text-align: right; +} + +.media-col-fps { + width: 40px; + min-width: 40px; + text-align: right; +} + +.media-col-container { + width: 72px; + min-width: 72px; + text-align: center; +} + +.media-col-codec { + width: 68px; + min-width: 68px; + text-align: center; +} + +.media-col-audio { + width: 44px; + min-width: 44px; + text-align: center; +} + +.media-col-bitrate { + width: 70px; + min-width: 70px; + text-align: right; +} + +.media-col-size { + width: 70px; + min-width: 70px; + text-align: right; + padding-right: 12px; +} + +.media-panel-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-secondary); + text-align: center; + padding: 20px; +} + +.media-panel-empty .drop-icon { + margin-bottom: 12px; + opacity: 0.4; +} + +.media-panel-empty .drop-icon svg { + stroke: currentColor; +} + +.media-panel-empty .hint { + font-size: 12px; + margin-top: 8px; + opacity: 0.7; +} + +.media-item-list { + padding: 4px 0; + user-select: none; + flex: 1; + min-height: 0; + overflow: auto; + contain: layout paint; +} + +.media-classic-virtual-spacer { + flex: 0 0 auto; + pointer-events: none; +} + +/* View mode toggle buttons */ +.media-view-toggle { + padding: 2px 4px !important; + min-width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0.5; + box-sizing: border-box; + border: 1px solid var(--border-color); +} + +.media-view-toggle.active { + opacity: 1; + background: var(--bg-hover); +} + +.media-view-segment { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: rgba(0, 0, 0, 0.12); +} + +.media-view-segment .media-view-toggle { + border: 0; + background: transparent; +} + +.media-view-segment .media-view-toggle.active { + background: var(--bg-hover); +} + +/* Grid View */ +.media-grid-wrapper { + flex: 1; + min-height: 0; + overflow: auto; + padding: 8px; + user-select: none; +} + +.media-grid-breadcrumb { + display: flex; + align-items: center; + gap: 2px; + padding: 4px 2px 8px; + font-size: 11px; + color: var(--text-muted); + flex-wrap: wrap; +} + +.media-grid-breadcrumb-sep { + color: var(--text-muted); + opacity: 0.4; + margin: 0 1px; +} + +.media-grid-breadcrumb-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 2px 4px; + border-radius: 3px; + font-size: 11px; + transition: background 0.1s, color 0.1s; +} + +.media-grid-breadcrumb-btn:hover { + background: rgba(255, 255, 255, 0.08); + color: var(--text-primary); +} + +.media-grid-breadcrumb-btn.active { + color: var(--text-primary); + cursor: default; +} + +.media-grid-breadcrumb-btn.active:hover { + background: none; +} + +.media-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 8px; +} + +.media-grid-item { + display: flex; + flex-direction: column; + border-radius: 4px; + cursor: pointer; + overflow: hidden; + border: 2px solid transparent; + transition: border-color 0.1s, background 0.1s; +} + +.media-grid-item:hover { + background: var(--bg-hover); +} + +.media-grid-item.selected { + border-color: var(--accent); + background: rgba(45, 140, 235, 0.15); +} + +.media-grid-thumb { + position: relative; + aspect-ratio: 16 / 9; + background: var(--bg-secondary); + overflow: hidden; + border-radius: 3px 3px 0 0; +} + +.media-grid-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.media-grid-thumb-placeholder { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.7; +} + +.media-grid-duration { + position: absolute; + bottom: 3px; + right: 3px; + background: rgba(0, 0, 0, 0.75); + color: var(--text-primary); + font-size: 10px; + padding: 1px 4px; + border-radius: 2px; + font-variant-numeric: tabular-nums; +} + +.media-grid-badge { + position: absolute; + top: 3px; + right: 3px; + background: rgba(255, 255, 255, 0.15); + color: var(--text-secondary); + font-size: 10px; + padding: 1px 5px; + border-radius: 8px; + font-variant-numeric: tabular-nums; + min-width: 16px; + text-align: center; +} + +.media-grid-name { + padding: 3px 6px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + color: var(--text-secondary); + max-width: 100%; + align-self: center; + width: fit-content; + border-radius: 3px; +} + +.media-grid-item.selected .media-grid-name { + color: var(--text-primary); + background: rgba(45, 140, 235, 0.25); +} + +.media-marquee { + position: absolute; + border: 1px solid rgba(45, 140, 235, 0.6); + background: rgba(45, 140, 235, 0.15); + pointer-events: none; + z-index: 5; +} + +/* Media Board View */ +.media-board-wrapper { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: hidden; + background: + linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px), + var(--bg-primary); + background-size: 32px 32px; + background-position: + var(--media-board-grid-x, 0px) var(--media-board-grid-y, 0px), + var(--media-board-grid-x, 0px) var(--media-board-grid-y, 0px), + 0 0; +} + +.media-board-wrapper.board-interacting { + background: var(--bg-primary); +} + +.media-board-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + min-height: 34px; + padding: 5px 8px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); + flex-shrink: 0; +} + +.media-board-toolbar-title { + display: flex; + align-items: baseline; + gap: 8px; + min-width: 0; +} + +.media-board-toolbar-title span:first-child { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.media-board-toolbar-title span:last-child { + font-size: 11px; + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.media-board-toolbar-actions { + display: inline-flex; + align-items: center; + gap: 4px; + flex-shrink: 0; +} + +.media-board-canvas { + position: relative; + flex: 1; + min-height: 0; + overflow: hidden; + cursor: grab; + user-select: none; +} + +.media-board-canvas:active { + cursor: grabbing; +} + +.media-board-wrapper.board-interacting .media-board-canvas-inner { + will-change: transform; +} + +.media-board-wrapper.board-interacting .media-board-node, +.media-board-wrapper.board-interacting .media-board-group, +.media-board-wrapper.board-interacting .media-board-insert-gap { + box-shadow: none; + transition: none; +} + +.media-board-wrapper.board-interacting .media-board-node-body, +.media-board-wrapper.board-interacting .media-board-node-timeline-drag { + transition: none; +} + +.media-board-wrapper.board-interacting .media-board-node-thumb img { + pointer-events: none; +} + +.media-board-canvas-inner { + position: absolute; + left: 0; + top: 0; + width: 1px; + height: 1px; + transform-origin: 0 0; +} + +.media-board-overview-canvas { + position: absolute; + display: block; + pointer-events: none; + z-index: 4; +} + +.media-board-group { + position: absolute; + contain: layout paint style; + border: 1px solid rgba(255, 255, 255, 0.09); + border-radius: 6px; + background: rgba(255, 255, 255, 0.035); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); + pointer-events: auto; + transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease, border-color 0.1s, box-shadow 0.1s, opacity 0.2s ease, filter 0.2s ease; +} + +.media-board-group.folder-group { + cursor: grab; +} + +.media-board-group.root-group { + background: rgba(255, 255, 255, 0.025); + border-color: rgba(255, 255, 255, 0.12); +} + +.media-board-group.folder-group.depth-1 { + background: rgba(255, 255, 255, 0.04); +} + +.media-board-group.folder-group.depth-2, +.media-board-group.folder-group.depth-3 { + background: rgba(255, 255, 255, 0.05); +} + +.media-board-group.folder-group .media-board-group-header { + cursor: grab; +} + +.media-board-group.folder-group.drag-preview { + border-color: rgba(45, 140, 235, 0.45); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.06), + 0 12px 26px rgba(0, 0, 0, 0.24); + z-index: 15; +} + +.media-board-group.folder-group.selected { + border-color: var(--accent); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.06), + 0 0 0 2px rgba(45, 140, 235, 0.24), + 0 10px 22px rgba(0, 0, 0, 0.24); +} + +.media-board-group.folder-group.drag-source-preview { + z-index: 14; +} + +.media-board-group-header { + height: 42px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 0 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.075); + color: var(--text-secondary); + font-size: 12px; +} + +.media-board-group-header span:first-child { + color: var(--text-primary); + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.media-board-group.folder-group .media-board-group-header span:first-child { + cursor: text; +} + +.media-board-group-header span:last-child { + min-width: 22px; + height: 18px; + border-radius: 9px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.08); + color: var(--text-secondary); + font-size: 10px; + font-variant-numeric: tabular-nums; +} + +.media-board-group-rename { + flex: 0 1 auto; + min-width: 4ch; + max-width: min(42ch, calc(100% - 34px)); + height: 24px; + padding: 2px 6px; + border: 1px solid var(--accent); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + font-weight: 600; + outline: none; +} + +.media-board-insert-gap { + position: absolute; + contain: layout paint style; + border: 1px dashed rgba(45, 140, 235, 0.68); + border-radius: 6px; + background: rgba(45, 140, 235, 0.12); + box-shadow: inset 0 0 0 1px rgba(45, 140, 235, 0.1); + pointer-events: none; + transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease; + z-index: 3; +} + +.media-board-node { + position: absolute; + display: block; + contain: layout paint style; + overflow: hidden; + border: 1px solid var(--border-color); + border-top: 3px solid var(--border-color); + border-radius: 6px; + background: var(--bg-secondary); + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22); + cursor: grab; + transition: left 0.16s ease, top 0.16s ease, width 0.16s ease, height 0.16s ease, border-color 0.1s, box-shadow 0.1s, background 0.1s, opacity 0.2s ease, filter 0.2s ease; + pointer-events: auto; + z-index: 5; +} + +.media-board-node:hover { + background: var(--bg-hover); + border-color: rgba(255, 255, 255, 0.18); +} + +.media-board-node.drag-preview { + box-shadow: 0 14px 30px rgba(0, 0, 0, 0.34); + z-index: 20; +} + +.media-board-node.drag-source-preview { + z-index: 18; +} + +.media-board-node.selected { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(45, 140, 235, 0.3), 0 10px 22px rgba(0, 0, 0, 0.26); +} + +.media-board-node.no-file { + opacity: 0.55; +} + +.media-board-node.search-dimmed, +.media-board-group.search-dimmed { + opacity: 0.26; + filter: grayscale(1) saturate(0.2); +} + +.media-board-node.lod-compact { + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18); +} + +.media-board-node.lod-compact .media-board-node-timeline-drag { + display: none; +} + +.media-board-node.lod-thumbnail-paused .media-board-node-placeholder { + opacity: 0.54; +} + +.media-board-node-thumb { + position: relative; + width: 100%; + height: 100%; + background: var(--bg-primary); + overflow: hidden; +} + +.media-board-node-thumb img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.media-board-node-placeholder { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.74; +} + +.media-board-solid-preview { + width: 100%; + height: 100%; +} + +.media-board-text-preview { + height: 100%; + display: flex; + align-items: center; + padding: 10px; + background: rgba(255, 255, 255, 0.035); + font-size: 17px; + font-weight: 700; + line-height: 1.1; + overflow: hidden; + word-break: break-word; +} + +.media-board-duration, +.media-board-progress { + position: absolute; + right: 6px; + bottom: 5px; + padding: 1px 5px; + border-radius: 3px; + background: rgba(0, 0, 0, 0.72); + color: var(--text-primary); + font-size: 10px; + font-variant-numeric: tabular-nums; +} + +.media-board-progress { + left: 6px; + right: auto; + color: var(--accent); +} + +.media-board-node-timeline-drag { + position: absolute; + top: 5px; + right: 5px; + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: rgba(0, 0, 0, 0.58); + color: var(--text-primary); + cursor: grab; + opacity: 0; + transition: opacity 0.1s, background 0.1s; +} + +.media-board-node:hover .media-board-node-timeline-drag, +.media-board-node.selected .media-board-node-timeline-drag { + opacity: 1; +} + +.media-board-node-timeline-drag:hover { + background: rgba(45, 140, 235, 0.78); +} + +.media-board-node-body { + position: absolute; + left: 0; + right: 0; + bottom: 0; + min-height: 0; + padding: 18px 8px 8px; + background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.82)); + opacity: 0; + pointer-events: none; + transform: translateY(8px); + transition: opacity 0.12s ease, transform 0.12s ease; +} + +.media-board-node:hover .media-board-node-body, +.media-board-node.selected .media-board-node-body, +.media-board-node.drag-preview .media-board-node-body { + opacity: 1; + transform: translateY(0); +} + +.media-board-node-name { + color: var(--text-primary); + font-size: 12px; + font-weight: 500; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.media-board-node-meta { + display: flex; + align-items: center; + gap: 5px; + margin-top: 5px; + color: var(--text-muted); + font-size: 10px; + line-height: 1; + overflow: hidden; +} + +.media-board-node-meta span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.media-board-marquee { + position: absolute; + border: 1px solid rgba(45, 140, 235, 0.72); + background: rgba(45, 140, 235, 0.16); + pointer-events: none; + z-index: 50; +} + +.media-item { + display: flex; + align-items: center; + padding: 1px 0; + cursor: pointer; + transition: background 0.1s; + border-left: 2px solid transparent; + height: 20px; +} + +.media-item:hover { + background: var(--bg-hover); +} + +.media-item.selected { + background: rgba(45, 140, 235, 0.25); + border-left-color: var(--accent); +} + +.media-item.selected:hover { + background: rgba(45, 140, 235, 0.35); +} + +.media-item.no-file { + opacity: 0.5; + cursor: not-allowed; +} + +.media-item.no-file .media-col-name::after { + content: ' (reload required)'; + font-size: 10px; + color: var(--text-secondary); +} + +.media-item.importing { + opacity: 0.5; +} + +.media-item.importing .media-col-name::after { + content: ' (importing...)'; + font-size: 10px; + color: var(--accent); + animation: importPulse 1.5s ease-in-out infinite; +} + +@keyframes importPulse { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 1; } +} + +.media-folder-arrow { + font-size: 8px; + color: var(--text-secondary); + flex-shrink: 0; + width: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: transform 0.15s; + user-select: none; +} + +.media-folder-arrow.expanded { + transform: rotate(90deg); +} + +.media-folder-arrow:hover { + color: var(--text-primary); +} + +.media-folder-icon { + font-size: 12px; + filter: grayscale(1) brightness(0.7); + line-height: 1; +} + +.media-item-icon { + flex-shrink: 0; + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.media-item-name { + flex: 1; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 2px 4px; + border-radius: 3px; + cursor: default; + color: var(--text-primary); + min-width: 0; +} + +.media-item-name.editable { + cursor: text; +} + +.media-item-name.editable:hover { + background: rgba(255, 255, 255, 0.1); +} + +.media-item-rename { + background: var(--bg-primary); + border: 1px solid var(--accent); + border-radius: 2px; + padding: 2px 6px; + font-size: 12px; + color: var(--text-primary); + outline: none; + width: auto; + min-width: 30px; + max-width: 100%; + font-family: inherit; + field-sizing: content; +} + +/* Legacy - replaced by media-col system */ +.media-item-info { + font-size: 10px; + color: var(--text-secondary); + flex-shrink: 0; + display: none; +} + +.media-item-proxy-badge { + background: var(--success); + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + flex-shrink: 0; +} + +/* Full coverage badges (solid) */ +.media-item-transcript-badge { + background: var(--accent); + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + flex-shrink: 0; + cursor: pointer; +} + +.media-item-proxy-error { + background: var(--danger); + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + flex-shrink: 0; +} + +.media-item-transcript-badge:hover { + filter: brightness(1.2); +} + +.media-item-analysis-badge { + background: #e88a1a; + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + flex-shrink: 0; + cursor: pointer; +} + +.media-item-analysis-badge:hover { + filter: brightness(1.2); +} + +/* Partial coverage fill badges */ +.media-item-transcript-fill, +.media-item-analysis-fill { + margin-left: 4px; + flex-shrink: 0; + cursor: pointer; +} + +.media-item-transcript-fill:hover, +.media-item-analysis-fill:hover { + filter: brightness(1.2); +} + +.coverage-fill-badge { + position: relative; + width: 14px; + height: 14px; + border-radius: 3px; + overflow: hidden; + font-size: 9px; + font-weight: bold; + line-height: 14px; + text-align: center; + display: inline-block; +} + +.coverage-fill-badge .coverage-fill-bg { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.coverage-fill-badge .coverage-fill-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + align-items: flex-end; + justify-content: center; + overflow: hidden; + transition: height 0.3s ease-out; +} + +/* Transcript fill colors */ +.coverage-fill-badge.transcript-fill .coverage-fill-bg { + background: rgba(99, 102, 241, 0.2); + color: rgba(99, 102, 241, 0.4); +} + +.coverage-fill-badge.transcript-fill .coverage-fill-progress { + background: var(--accent); + color: var(--text-inverse); +} + +/* Analysis fill colors */ +.coverage-fill-badge.analysis-fill .coverage-fill-bg { + background: rgba(232, 138, 26, 0.2); + color: rgba(232, 138, 26, 0.4); +} + +.coverage-fill-badge.analysis-fill .coverage-fill-progress { + background: #e88a1a; + color: var(--text-inverse); +} + +.media-item-proxy-generating { + display: flex; + align-items: center; + gap: 4px; + margin-left: 4px; + flex-shrink: 0; +} + +.media-item-proxy-generating .proxy-percent { + font-size: 9px; + color: var(--warning-light); + font-weight: bold; +} + +.media-folder-children { + /* Children indentation handled by paddingLeft */ +} + +/* Drag target styling */ +.media-item.drag-target, +.media-grid-item.drag-target { + background: var(--accent) !important; + outline: 2px dashed var(--accent); + outline-offset: -2px; +} + +.media-item.dragging { + opacity: 0.5; +} + +/* Add dropdown */ +.add-dropdown-container { + position: relative; +} + +.add-dropdown-trigger.active { + background: var(--bg-hover); +} + +.add-dropdown-menu { + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); + min-width: 180px; + z-index: 1000; + overflow: visible; +} + +.add-dropdown-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + font-size: 12px; + color: var(--text-primary); + cursor: pointer; + transition: background 0.1s; +} + +.add-dropdown-item:hover { + background: var(--bg-hover); +} + +.add-dropdown-icon { + width: 20px; + text-align: center; + font-size: 14px; +} + +.add-dropdown-hint { + margin-left: auto; + font-size: 10px; + color: var(--text-muted); + font-style: italic; +} + +.add-dropdown-separator { + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +.add-dropdown-item.has-submenu { + position: relative; +} + +.add-dropdown-submenu { + display: none; + position: absolute; + left: 100%; + top: -4px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 4px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + min-width: 140px; + padding: 4px 0; +} + +.add-dropdown-item.has-submenu:hover > .add-dropdown-submenu { + z-index: 10001; +} + +.add-dropdown-item.has-submenu:hover > .add-dropdown-submenu { + display: block; +} + +/* Context Menu */ +.media-context-menu { + position: fixed; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); + min-width: 160px; + z-index: 10000; + overflow: visible; +} + +.media-context-menu button { + display: block; + width: 100%; + padding: 8px 12px; + text-align: left; + background: none; + border: none; + color: var(--text-primary); + font-size: 12px; + cursor: pointer; + transition: background 0.1s; +} + +.media-context-menu button:hover { + background: var(--bg-hover); +} + +.media-context-menu button.danger { + color: var(--danger); +} + +.media-context-menu button.danger:hover { + background: rgba(255, 68, 68, 0.15); +} + +} diff --git a/src/components/panels/MediaPanel.tsx b/src/components/panels/MediaPanel.tsx index 94321dfbe..1c9610612 100644 --- a/src/components/panels/MediaPanel.tsx +++ b/src/components/panels/MediaPanel.tsx @@ -1,6 +1,7 @@ // Media Panel - Project browser like After Effects import React, { useCallback, useMemo, useRef, useState, useEffect, useLayoutEffect } from 'react'; +import './MediaPanel.css'; import { Logger } from '../../services/logger'; import { FileTypeIcon } from './media/FileTypeIcon'; import { LABEL_COLORS, getLabelHex } from './media/labelColors'; diff --git a/src/components/panels/properties/AnalysisTranscriptTabs.css b/src/components/panels/properties/AnalysisTranscriptTabs.css new file mode 100644 index 000000000..e3b0f1c97 --- /dev/null +++ b/src/components/panels/properties/AnalysisTranscriptTabs.css @@ -0,0 +1,239 @@ +@layer features { + +/* Transcript Tab (embedded in PropertiesPanel) */ +.properties-tab-content.transcript-tab { + display: flex; + flex-direction: column; + gap: var(--sp-2); +} + +.transcript-tab-actions { + display: flex; + gap: var(--sp-1-5); + margin-top: var(--sp-1-5); +} + +.transcript-progress-bar { + height: 4px; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + overflow: hidden; +} + +.transcript-progress-fill { + height: 100%; + background: var(--accent); + transition: width 0.3s ease; +} + +.transcript-progress-text { + font-size: var(--font-sm); + color: var(--text-secondary); +} + +.transcript-search-input { + width: 100%; + padding: var(--sp-1-5) var(--sp-2-5); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); +} + +.transcript-content-embedded { + flex: 1; + min-height: 100px; + max-height: 200px; + overflow-y: auto; + padding: var(--sp-2); + background: var(--bg-tertiary); + border-radius: var(--radius-md); +} + +.transcript-empty-state { + color: var(--text-secondary); + font-size: var(--font-md); + text-align: center; + padding: var(--sp-5); +} + +.transcript-words-flow { + display: flex; + flex-wrap: wrap; + gap: var(--sp-1); + line-height: 1.6; +} + +.transcript-word-inline { + cursor: pointer; + padding: 1px 2px; + border-radius: var(--radius-sm); + font-size: var(--font-md); + transition: background var(--transition-normal); +} + +.transcript-word-inline:hover { + background: var(--bg-hover); +} + +.transcript-word-inline.active { + background: var(--accent); + color: var(--bg-primary); +} + +.transcript-word-inline.highlighted { + background: rgba(255, 200, 0, 0.3); +} + +.transcript-status-bar { + font-size: var(--font-sm); + color: var(--text-secondary); + text-align: right; +} + +/* Analysis Tab (embedded in PropertiesPanel) */ +.properties-tab-content.analysis-tab { + display: flex; + flex-direction: column; + gap: 8px; +} + +.analysis-tab-actions { + display: flex; + gap: 6px; +} + +/* Coverage bar for analysis/transcript tabs */ +.coverage-bar { + display: flex; + align-items: center; + gap: 6px; +} + +.coverage-bar-bg { + flex: 1; + height: 4px; + background: rgba(255, 255, 255, 0.08); + border-radius: 2px; + overflow: hidden; +} + +.coverage-bar-fill { + height: 100%; + border-radius: 2px; + transition: width 0.3s ease; +} + +.coverage-bar-fill.analysis-fill { + background: #e88a1a; +} + +.coverage-bar-fill.transcript-fill { + background: var(--accent); +} + +.coverage-bar-text { + font-size: 10px; + color: var(--text-muted); + white-space: nowrap; +} + +/* Accent button for Continue actions */ +.btn-accent { + background: var(--accent) !important; + color: #fff !important; + border-color: var(--accent) !important; +} + +.btn-accent:hover { + filter: brightness(1.15); +} + +.analysis-progress-bar { + height: 4px; + background: var(--bg-tertiary); + border-radius: 2px; + overflow: hidden; +} + +.analysis-progress-fill { + height: 100%; + background: var(--accent); + transition: width 0.3s ease; +} + +.analysis-progress-text { + font-size: 11px; + color: var(--text-secondary); +} + +.analysis-realtime-grid { + display: flex; + flex-direction: column; + gap: 6px; +} + +.analysis-metric { + display: flex; + align-items: center; + gap: 8px; +} + +.analysis-metric .metric-label { + width: 60px; + font-size: 11px; + color: var(--text-secondary); +} + +.analysis-metric .metric-bar { + flex: 1; + height: 8px; + background: var(--bg-tertiary); + border-radius: 4px; + overflow: hidden; +} + +.analysis-metric .metric-fill { + height: 100%; + transition: width 0.15s ease; +} + +.analysis-metric .metric-fill.focus { + background: var(--success-light); +} + +.analysis-metric .metric-fill.motion { + background: #60a5fa; +} + +.analysis-metric .metric-value { + width: 40px; + font-size: 11px; + text-align: right; +} + +.analysis-stats-grid { + display: flex; + flex-direction: column; + gap: 4px; +} + +.analysis-stats-grid .stat-row { + display: flex; + justify-content: space-between; + font-size: 11px; +} + +.analysis-stats-grid .stat-row span:first-child { + color: var(--text-secondary); +} + +.analysis-empty-state { + color: var(--text-secondary); + font-size: 12px; + text-align: center; + padding: 20px; +} + +} diff --git a/src/components/panels/properties/ClipPropertiesPanel.css b/src/components/panels/properties/ClipPropertiesPanel.css new file mode 100644 index 000000000..894aa1de7 --- /dev/null +++ b/src/components/panels/properties/ClipPropertiesPanel.css @@ -0,0 +1,34 @@ +@layer features { + +/* Clip Properties Panel */ +.clip-properties-panel { + display: flex; + flex-direction: column; + height: 100%; +} + +.clip-properties-panel .panel-header { + padding: var(--sp-3); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.clip-properties-panel .panel-header h3 { + margin: 0; + font-size: var(--font-lg); + font-weight: var(--font-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.properties-actions { + padding: var(--sp-2); + text-align: center; +} + +.properties-actions .btn { + width: 100%; +} + +} diff --git a/src/components/panels/properties/EffectsTab.css b/src/components/panels/properties/EffectsTab.css new file mode 100644 index 000000000..3211c3cff --- /dev/null +++ b/src/components/panels/properties/EffectsTab.css @@ -0,0 +1,62 @@ +@layer features { + +.properties-tab-content.effects-tab .effect-add-row { + display: flex; + gap: var(--sp-1-5); + align-items: center; + margin-bottom: var(--sp-2); +} + +.properties-tab-content.effects-tab .effect-add-row select { + flex: 1; + min-width: 0; + padding: var(--sp-1-5) var(--sp-2-5); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); +} + +.effect-mode-toggle { + display: flex; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + overflow: hidden; + flex-shrink: 0; +} + +.effect-mode-btn { + display: flex; + align-items: center; + justify-content: center; + padding: var(--sp-1-5) var(--sp-2); + background: var(--bg-tertiary); + border: none; + color: var(--text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.effect-mode-btn:not(:last-child) { + border-right: 1px solid var(--border-color); +} + +.effect-mode-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.effect-mode-btn.active { + background: var(--accent-color); + color: #fff; +} + +.effect-mode-label { + flex: 1; + font-size: var(--font-md); + color: var(--text-secondary); + font-weight: 500; +} + +} diff --git a/src/components/panels/properties/PropertiesPanel.css b/src/components/panels/properties/PropertiesPanel.css new file mode 100644 index 000000000..d96c20e09 --- /dev/null +++ b/src/components/panels/properties/PropertiesPanel.css @@ -0,0 +1,1247 @@ +@layer features { + +.properties-content { + flex: 1; + overflow-y: auto; + padding: var(--sp-2); +} + +.properties-section { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--sp-2) var(--sp-2-5); + margin-bottom: var(--sp-1); +} + +.properties-section h4 { + margin: 0 0 6px 0; + font-size: var(--font-xs); + font-weight: var(--font-semibold); + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); +} + +.properties-section .control-row { + display: flex; + align-items: center; + gap: var(--sp-1-5); + margin-bottom: 3px; +} + +.properties-section .control-row:last-child { + margin-bottom: 0; +} + +.properties-section .control-row label { + min-width: 20px; + font-size: var(--font-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +/* Fixed-width property label for uniform alignment */ +.properties-section .control-row .prop-label { + width: 52px; + min-width: 52px; + font-size: var(--font-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +/* Multi-value inline row (X Y Z side by side) */ +.multi-value-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); + flex: 1; + column-gap: 12px; + row-gap: 3px; + min-width: 0; +} + +/* Labeled value cell: [label ... value] left/right aligned */ +.labeled-value { + min-width: 0; + display: grid; + grid-template-columns: 32px max-content; + align-items: center; + justify-content: start; + column-gap: 10px; + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius-sm); + overflow: visible; + transition: background var(--transition-fast), border-color var(--transition-fast); + cursor: default; +} + +.labeled-value:hover { + border-color: transparent; +} + +.labeled-value-label { + font-size: var(--font-2xs); + color: var(--text-secondary); + padding: 3px 0; + width: 32px; + user-select: none; + opacity: 0.75; + text-align: left; +} + +.midi-parameter-label { + cursor: context-menu; +} + +.midi-parameter-label:hover { + color: var(--text-primary); +} + +.midi-parameter-menu { + min-width: 210px; +} + +.labeled-value .draggable-number-anchor { + flex: 0 0 auto; + min-width: 0; + justify-content: flex-end; +} + +/* Override draggable-number style when inside labeled-value */ +.labeled-value .draggable-number { + display: block; + width: max-content; + min-width: 0; + border: 1px solid transparent; + background: transparent; + border-radius: 2px; + padding: 3px 4px; + text-align: right; + color: #5b8abf; +} + +.labeled-value .draggable-number:hover { + background: rgba(45, 140, 235, 0.08); + border-color: rgba(45, 140, 235, 0.28); + color: #7aabe0; +} + +.labeled-value:active .draggable-number, +.labeled-value .draggable-number:active { + color: #9cc4f0; + background: rgba(45, 140, 235, 0.12); +} + +.labeled-value .draggable-number-input { + min-width: 0; + background: var(--bg-primary); + border-color: var(--accent); + color: #d7e7fb; +} + +/* AE-style rotation: "2x +30.0°" */ +.rotation-value-ae { + grid-template-columns: 32px max-content max-content; + column-gap: 6px; +} + +.rotation-value-ae .draggable-number:first-of-type { + opacity: 1; + font-size: 11px; + min-width: 28px; + flex-shrink: 0; +} + +.rotation-value-ae .draggable-number:first-of-type:hover { + opacity: 1; +} + +.rotation-row { + grid-template-columns: repeat(auto-fit, minmax(126px, 1fr)); + flex-wrap: wrap; +} + +.properties-section .control-row input[type="range"] { + flex: 1; +} + +.scene-nav-speed-control { + display: inline-grid; + grid-template-columns: minmax(76px, 112px) 34px; + align-items: center; + gap: 6px; + min-width: 120px; +} + +.scene-nav-speed-control input[type="range"] { + width: 100%; + min-width: 0; +} + +.scene-nav-speed-control span { + color: #7aabe0; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 11px; + font-variant-numeric: tabular-nums; + text-align: right; +} + +.properties-section .control-row .value { + min-width: 45px; + text-align: right; + font-size: var(--font-sm); + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + color: var(--text-primary); +} + +.properties-section .control-row select { + flex: 1; + padding: var(--sp-1) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); +} + +.transform-tab-compact { + container-type: inline-size; +} + +.transform-tab-compact .properties-section { + border-radius: var(--radius-md); + margin-bottom: 4px; + padding: 6px 8px; +} + +.transform-tab-compact .properties-section .control-row { + min-width: 0; +} + +.transform-tab-compact .transform-option-row { + flex-wrap: wrap; + row-gap: 4px; +} + +.transform-tab-compact .transform-option-row .prop-label { + flex: 0 1 auto; + width: auto; + min-width: 50px; + max-width: 84px; + overflow: hidden; + text-overflow: ellipsis; +} + +.transform-tab-compact .scene-nav-row .btn-xs { + flex: 0 0 auto; +} + +.transform-tab-compact .scene-nav-icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + min-width: 24px; + height: 22px; + padding: 2px; +} + +.transform-tab-compact .scene-nav-action-icon { + width: 14px; + height: 14px; + fill: none; + stroke: currentColor; + stroke-width: 1.6; + stroke-linecap: round; + stroke-linejoin: round; +} + +.transform-tab-compact .transform-param-row { + display: grid; + grid-template-columns: 18px minmax(42px, max-content) minmax(0, 1fr); + align-items: center; + column-gap: 6px; + row-gap: 2px; +} + +.transform-tab-compact .transform-param-row > .keyframe-toggle { + grid-column: 1; +} + +.transform-tab-compact .transform-param-row > .prop-label { + grid-column: 2; + width: auto; + min-width: 0; + max-width: 72px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; +} + +.transform-tab-compact .transform-param-row > .multi-value-row, +.transform-tab-compact .transform-param-row > .labeled-value, +.transform-tab-compact .transform-param-row > .draggable-number-anchor { + grid-column: 3; + min-width: 0; +} + +.transform-tab-compact .transform-param-row > .labeled-value, +.transform-tab-compact .transform-param-row > .draggable-number-anchor { + justify-self: start; +} + +.transform-tab-compact .multi-value-row { + grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); + column-gap: 8px; + row-gap: 2px; + align-items: center; +} + +.transform-tab-compact .rotation-row { + grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); +} + +.transform-tab-compact .labeled-value { + grid-template-columns: minmax(12px, max-content) max-content; + column-gap: 6px; +} + +.transform-tab-compact .labeled-value.with-keyframe-toggle { + grid-template-columns: 18px minmax(12px, max-content) max-content; + column-gap: 4px; +} + +.transform-tab-compact .rotation-value-ae { + grid-template-columns: minmax(28px, max-content) max-content max-content; + column-gap: 5px; +} + +.transform-tab-compact .rotation-value-ae.with-keyframe-toggle { + grid-template-columns: 18px minmax(28px, max-content) max-content max-content; +} + +.transform-tab-compact .labeled-value-label { + width: auto; + min-width: 12px; + padding: 2px 0; +} + +.transform-tab-compact .labeled-value .draggable-number { + padding: 2px 3px; +} + +@container (max-width: 240px) { + .transform-tab-compact .properties-section { + padding: 5px 6px; + } + + .transform-tab-compact .transform-option-row .prop-label { + min-width: 0; + } + + .transform-tab-compact .scene-nav-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + } + + .transform-tab-compact .scene-nav-speed-control { + grid-column: 1 / -1; + grid-template-columns: minmax(0, 1fr) 34px; + width: 100%; + } + + .transform-tab-compact .transform-param-row { + grid-template-columns: 18px minmax(0, 1fr); + grid-template-areas: + "key label" + "values values"; + } + + .transform-tab-compact .transform-param-row > .keyframe-toggle { + grid-area: key; + } + + .transform-tab-compact .transform-param-row > .prop-label { + grid-area: label; + max-width: none; + } + + .transform-tab-compact .transform-param-row > .multi-value-row, + .transform-tab-compact .transform-param-row > .labeled-value, + .transform-tab-compact .transform-param-row > .draggable-number-anchor { + grid-area: values; + box-sizing: border-box; + padding-left: 22px; + } + + .transform-tab-compact .multi-value-row { + grid-template-columns: 1fr; + row-gap: 3px; + } + + .transform-tab-compact .rotation-row { + grid-template-columns: 1fr; + } + + .transform-tab-compact .labeled-value { + grid-template-columns: 34px max-content; + column-gap: 5px; + } + + .transform-tab-compact .rotation-value-ae { + grid-template-columns: 34px 22px max-content; + column-gap: 4px; + } + + .transform-tab-compact .labeled-value .draggable-number { + text-align: left; + } +} + +/* Unified Properties Panel with Tabs */ +.properties-panel { + display: flex; + flex-direction: column; + height: 100%; +} + +.properties-panel .panel-header { + padding: var(--sp-2) var(--sp-3); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.properties-panel .panel-header h3 { + margin: 0; + font-size: var(--font-lg); + font-weight: var(--font-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.solid-color-bar { + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.properties-tabs { + display: flex; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + min-height: 32px; + flex-shrink: 0; +} + +.properties-tabs .tab-btn { + display: flex; + align-items: center; + gap: var(--sp-1-5); + padding: 0 16px; + height: 32px; + background: transparent; + border: none; + border-right: 1px solid var(--border-color); + color: var(--text-secondary); + font-size: var(--font-md); + font-weight: var(--font-medium); + cursor: pointer; + white-space: nowrap; + transition: background var(--transition-normal), color var(--transition-normal); +} + +.properties-tabs .tab-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.properties-tabs .tab-btn.active { + background: var(--bg-secondary); + color: var(--text-primary); + border-bottom: 2px solid var(--accent); + margin-bottom: -1px; +} + +.properties-tabs .tab-btn .badge { + background: var(--accent); + color: var(--text-inverse); + font-size: var(--font-xs); + font-weight: var(--font-semibold); + padding: 1px 5px; + border-radius: var(--radius-xl); + min-width: 16px; + text-align: center; +} + +.properties-panel .properties-content { + flex: 1; + overflow-y: auto; + padding: 0; +} + +.properties-tab-content { + padding: var(--sp-2); +} + +.lottie-tab { + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px; +} + +.lottie-tab .properties-section { + margin-bottom: 0; + border-radius: var(--radius-md); + padding: 8px; +} + +.lottie-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + min-width: 0; +} + +.lottie-summary-text { + min-width: 0; +} + +.lottie-title { + overflow: hidden; + color: var(--text-primary); + font-size: 12px; + font-weight: var(--font-semibold); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lottie-meta { + margin-top: 2px; + color: var(--text-muted); + font-size: 11px; +} + +.lottie-loop-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 0 0 auto; + color: var(--text-secondary); + font-size: 11px; +} + +.lottie-loop-toggle input { + accent-color: var(--accent); +} + +.lottie-controls-section, +.lottie-state-section { + display: grid; + gap: 7px; +} + +.lottie-field-row { + display: grid; + grid-template-columns: 96px minmax(0, 1fr); + align-items: center; + gap: 8px; + min-width: 0; +} + +.lottie-field-label { + overflow: hidden; + color: var(--text-secondary); + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.lottie-select, +.lottie-input { + width: 100%; + max-width: 360px; + min-width: 0; + height: 24px; + padding: 3px 8px; + border: 1px solid var(--border-color); + border-radius: 3px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + outline: none; +} + +.lottie-select { + cursor: pointer; +} + +.lottie-select:focus, +.lottie-input:focus { + border-color: color-mix(in srgb, var(--accent) 70%, var(--border-color)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 24%, transparent); +} + +.lottie-segmented-control, +.lottie-boolean-control { + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + width: 100%; + max-width: 360px; + min-width: 0; + overflow: hidden; + border: 1px solid var(--border-color); + border-radius: 3px; + background: var(--bg-primary); +} + +.lottie-playback-mode { + max-width: 440px; + grid-auto-columns: minmax(72px, 1fr); +} + +.lottie-segmented-control button, +.lottie-boolean-control button { + height: 24px; + min-width: 0; + padding: 0 8px; + border: 0; + border-right: 1px solid var(--border-color); + background: transparent; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; +} + +.lottie-segmented-control button:last-child, +.lottie-boolean-control button:last-child { + border-right: 0; +} + +.lottie-segmented-control button:hover, +.lottie-boolean-control button:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.lottie-segmented-control button.active, +.lottie-boolean-control button.active { + background: color-mix(in srgb, var(--accent) 22%, var(--bg-primary)); + color: var(--text-primary); +} + +.lottie-input-list, +.lottie-cue-panel, +.lottie-cue-list { + display: grid; + gap: 6px; +} + +.lottie-subsection-title { + margin-top: 2px; + color: var(--text-secondary); + font-size: 10px; + font-weight: var(--font-semibold); + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.lottie-input-row { + display: grid; + grid-template-columns: minmax(94px, 1fr) 18px minmax(80px, 112px); + align-items: center; + gap: 7px; + min-height: 28px; + min-width: 0; + padding: 4px 6px; + border: 1px solid transparent; + border-radius: 3px; + background: color-mix(in srgb, var(--bg-primary) 70%, transparent); +} + +.lottie-input-row.has-keyframes { + border-color: color-mix(in srgb, var(--warning) 36%, transparent); +} + +.lottie-input-label { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: baseline; + gap: 6px; + min-width: 0; +} + +.lottie-input-label span:first-child { + overflow: hidden; + color: var(--text-primary); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.lottie-input-label span:last-child { + color: var(--text-muted); + font-size: 10px; +} + +.lottie-input-row .draggable-number-anchor { + justify-self: end; +} + +.lottie-input-row .draggable-number { + min-width: 54px; + padding: 3px 6px; + border: 1px solid transparent; + border-radius: 3px; + color: #7aabe0; + text-align: right; +} + +.lottie-input-row .draggable-number:hover { + border-color: rgba(45, 140, 235, 0.28); + background: rgba(45, 140, 235, 0.08); + color: #9cc4f0; +} + +.lottie-input-static, +.lottie-input-note { + grid-column: 2 / 4; +} + +.lottie-input-note { + justify-self: start; + color: var(--text-muted); + font-size: 11px; +} + +.lottie-cue-add-btn { + justify-self: start; +} + +.lottie-cue-row { + display: grid; + grid-template-columns: 68px minmax(0, 1fr) 24px; + align-items: center; + gap: 6px; + max-width: 360px; +} + +.lottie-cue-row .lottie-select { + max-width: none; +} + +.lottie-time-input { + text-align: right; +} + +.lottie-cue-remove { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 1px solid var(--border-color); + border-radius: 3px; + background: var(--bg-primary); + color: var(--text-secondary); + cursor: pointer; +} + +.lottie-cue-remove:hover { + border-color: color-mix(in srgb, var(--danger) 48%, var(--border-color)); + color: var(--danger-light); +} + +.lottie-resolution-row { + display: grid; + grid-template-columns: minmax(64px, 88px) auto minmax(64px, 88px) 38px auto; + align-items: center; + gap: 6px; + width: 100%; + max-width: 360px; + min-width: 0; +} + +.lottie-resolution-row span { + color: var(--text-muted); + font-size: 11px; +} + +.lottie-resolution-input { + text-align: right; +} + +.lottie-link-toggle { + height: 24px; + padding: 0 8px; + border: 1px solid var(--border-color); + border-radius: 3px; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 10px; + cursor: pointer; +} + +.lottie-link-toggle.active { + border-color: color-mix(in srgb, var(--accent) 42%, var(--border-color)); + background: color-mix(in srgb, var(--accent) 18%, var(--bg-primary)); + color: var(--text-primary); +} + +.lottie-state-control { + display: grid; + grid-template-columns: 18px minmax(0, 1fr); + align-items: center; + gap: 7px; + width: 100%; + max-width: 360px; + min-width: 0; +} + +.lottie-state-control .lottie-select { + max-width: none; +} + +.lottie-color-row { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + align-items: center; + gap: 7px; + width: 100%; + max-width: 360px; + min-width: 0; +} + +.lottie-color-swatch { + position: relative; + width: 26px; + height: 22px; + overflow: hidden; + border: 1px solid var(--border-color); + border-radius: 3px; + background: + linear-gradient(45deg, #555 25%, transparent 25%), + linear-gradient(-45deg, #555 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #555 75%), + linear-gradient(-45deg, transparent 75%, #555 75%); + background-color: #222; + background-position: 0 0, 0 6px, 6px -6px, -6px 0; + background-size: 12px 12px; +} + +.lottie-color-swatch input { + position: absolute; + inset: -3px; + width: calc(100% + 6px); + height: calc(100% + 6px); + padding: 0; + border: 0; + opacity: 0.85; + cursor: pointer; +} + +.slot-clip-tab { + display: flex; + flex-direction: column; + gap: var(--sp-2); +} + +.slot-clip-summary { + gap: var(--sp-1); +} + +.slot-clip-summary-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--sp-2); +} + +.slot-clip-summary-title { + min-width: 0; +} + +.slot-clip-title { + margin-top: 2px; + font-size: var(--font-md); + font-weight: var(--font-semibold); + color: var(--text-primary); + word-break: break-word; +} + +.slot-clip-slot-badge { + padding: 5px 9px; + border-radius: 999px; + background: rgba(240, 196, 90, 0.14); + border: 1px solid rgba(240, 196, 90, 0.32); + color: #f0c45a; + font-size: var(--font-sm); + font-weight: var(--font-semibold); +} + +.slot-clip-summary-meta { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.slot-clip-summary-meta span { + padding: 3px 7px; + border-radius: var(--radius-sm); + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); + font-size: var(--font-xs); +} + +.slot-clip-summary-meta .slot-clip-status-active { + border-color: rgba(58, 206, 128, 0.38); + color: #56d98f; + background: rgba(58, 206, 128, 0.11); +} + +.slot-clip-transport { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--sp-1); +} + +.slot-clip-transport-btn, +.slot-clip-end-btn, +.slot-clip-handle { + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); +} + +.slot-clip-transport-btn { + min-height: 34px; + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-sm); + font-weight: var(--font-medium); +} + +.slot-clip-transport-btn.primary { + border-color: rgba(74, 158, 255, 0.5); + background: rgba(74, 158, 255, 0.16); +} + +.slot-clip-transport-btn:hover:not(:disabled), +.slot-clip-end-btn:hover { + background: var(--bg-hover); +} + +.slot-clip-transport-btn:disabled { + opacity: 0.45; + cursor: default; +} + +.slot-clip-section-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-2); +} + +.slot-clip-section-header span { + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + color: var(--text-secondary); + font-size: var(--font-xs); +} + +.slot-clip-timeline-shell { + display: flex; + flex-direction: column; + border-radius: var(--radius-md); + border: 1px solid var(--border-color); + background: var(--bg-primary); + overflow: hidden; +} + +.slot-clip-ruler { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: center; + min-height: 22px; + padding: 0 8px; + color: var(--text-tertiary); + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-size: 10px; + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid var(--border-color); +} + +.slot-clip-ruler span:nth-child(2) { + text-align: center; +} + +.slot-clip-ruler span:nth-child(3) { + text-align: right; +} + +.slot-clip-track-stack { + position: relative; + min-height: 116px; + background: + repeating-linear-gradient( + 90deg, + rgba(255, 255, 255, 0.035) 0, + rgba(255, 255, 255, 0.035) 1px, + transparent 1px, + transparent 10% + ), + linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(0, 0, 0, 0.12)); + overflow: hidden; +} + +.slot-clip-track-row { + position: relative; + display: grid; + grid-template-columns: 30px minmax(0, 1fr); + min-height: 29px; + border-bottom: 1px solid rgba(255, 255, 255, 0.055); +} + +.slot-clip-track-row:last-of-type { + border-bottom: none; +} + +.slot-clip-track-label { + position: relative; + z-index: 4; + display: flex; + align-items: center; + justify-content: center; + border-right: 1px solid var(--border-color); + background: rgba(0, 0, 0, 0.32); + color: var(--text-secondary); + font-size: 10px; + font-weight: var(--font-semibold); +} + +.slot-clip-track-label-audio { + color: #57cf87; +} + +.slot-clip-track-label-video { + color: #63adff; +} + +.slot-clip-track-lane { + position: relative; + min-width: 0; + overflow: hidden; +} + +.slot-clip-clip { + position: absolute; + top: 5px; + bottom: 5px; + min-width: 3px; + border-radius: var(--radius-sm); + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(98, 157, 255, 0.64); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + overflow: hidden; +} + +.slot-clip-clip span { + display: block; + padding: 2px 5px; + color: rgba(255, 255, 255, 0.86); + font-size: 10px; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.slot-clip-clip-audio { + background: rgba(56, 190, 108, 0.68); +} + +.slot-clip-clip-image, +.slot-clip-clip-lottie { + background: rgba(153, 122, 255, 0.66); +} + +.slot-clip-clip-text, +.slot-clip-clip-solid { + background: rgba(238, 156, 68, 0.66); +} + +.slot-clip-clip-model, +.slot-clip-clip-gaussian-avatar, +.slot-clip-clip-gaussian-splat, +.slot-clip-clip-splat-effector, +.slot-clip-clip-camera { + background: rgba(90, 202, 200, 0.64); +} + +.slot-clip-empty-timeline { + display: flex; + align-items: center; + justify-content: center; + min-height: 116px; + color: var(--text-tertiary); + font-size: var(--font-sm); +} + +.slot-clip-trim-mask { + position: absolute; + top: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.45); + pointer-events: none; + z-index: 2; +} + +.slot-clip-trim-mask-left { + left: 0; +} + +.slot-clip-trim-mask-right { + right: 0; +} + +.slot-clip-range-window { + position: absolute; + top: 0; + bottom: 0; + background: rgba(74, 158, 255, 0.08); + border-left: 1px solid rgba(240, 196, 90, 0.7); + border-right: 1px solid rgba(240, 196, 90, 0.7); + pointer-events: none; + z-index: 3; +} + +.slot-clip-range-progress { + position: absolute; + top: 0; + bottom: 0; + background: rgba(74, 158, 255, 0.12); + pointer-events: none; + z-index: 3; +} + +.slot-clip-playhead { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + margin-left: -1px; + background: #ff6b57; + box-shadow: 0 0 0 1px rgba(255, 107, 87, 0.18); + pointer-events: none; + z-index: 5; +} + +.slot-clip-handle { + position: absolute; + top: 0; + bottom: 0; + width: 14px; + margin-left: -7px; + background: transparent; + border: none; + cursor: ew-resize; + z-index: 6; +} + +.slot-clip-handle::before { + content: ''; + position: absolute; + top: 6px; + bottom: 6px; + left: 5px; + width: 4px; + border-radius: var(--radius-sm); + background: #f0c45a; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.22); +} + +.slot-clip-handle.dragging { + background: rgba(240, 196, 90, 0.08); +} + +.slot-clip-timecodes { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(86px, 1fr)); + gap: var(--sp-1); +} + +.slot-clip-timecode { + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px 10px; + border-radius: var(--radius-md); + background: var(--bg-primary); + border: 1px solid var(--border-color); +} + +.slot-clip-timecode span { + font-size: var(--font-xs); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.slot-clip-timecode strong { + font-size: var(--font-sm); + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + color: var(--text-primary); +} + +.slot-clip-end-behavior { + display: flex; + flex-wrap: wrap; + gap: var(--sp-1); +} + +.slot-clip-end-btn { + flex: 1 1 110px; + min-height: 34px; + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-sm); +} + +.slot-clip-end-btn.active { + border-color: rgba(74, 158, 255, 0.55); + background: rgba(74, 158, 255, 0.16); + color: var(--text-primary); +} + +.properties-tab-content.masks-tab .section-header-with-button { + margin-bottom: var(--sp-2); +} + +/* Clip type badge in header */ +.properties-panel .panel-header { + display: flex; + align-items: center; + gap: 8px; +} + +.clip-type-badge { + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + background: var(--bg-hover); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* MIDI */ +.midi-unsupported { + font-size: 12px; + color: var(--text-secondary); +} + +} diff --git a/src/components/panels/properties/TextTab.css b/src/components/panels/properties/TextTab.css new file mode 100644 index 000000000..d549b557a --- /dev/null +++ b/src/components/panels/properties/TextTab.css @@ -0,0 +1,318 @@ +@layer features { + +/* Text Tab (embedded in PropertiesPanel) */ +/* ========== TEXT TAB (Compact AE-style) ========== */ +.tt { + display: flex; + flex-direction: column; + gap: 2px; +} + +.tt-section { + padding: var(--sp-2) var(--sp-2-5); + border-bottom: 1px solid var(--border-color); +} + +.tt-section:last-child { + border-bottom: none; +} + +.tt-section-header { + font-size: var(--font-sm); + font-weight: var(--font-semibold); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: var(--sp-1-5); +} + +/* Textarea */ +.tt-textarea { + width: 100%; + min-height: 48px; + padding: var(--sp-1-5) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: var(--font-md); + font-family: inherit; + resize: vertical; + line-height: 1.4; +} + +.tt-textarea:focus { + outline: none; + border-color: var(--accent); +} + +/* Full-width select */ +.tt-select-full { + width: 100%; + padding: var(--sp-1) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: var(--font-sm); + cursor: pointer; + margin-bottom: var(--sp-1); +} + +.tt-select-full:focus { + outline: none; + border-color: var(--accent); +} + +/* Two-column row */ +.tt-row-2col { + display: flex; + gap: var(--sp-1); + margin-bottom: var(--sp-1); +} + +.tt-row-2col > * { + flex: 1; + min-width: 0; +} + +.tt-row-2col .tt-select-full { + margin-bottom: 0; +} + +/* Compact number input with icon */ +.tt-compact-num { + display: flex; + align-items: center; + gap: 2px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + padding: 2px 4px; + cursor: ew-resize; + height: 24px; +} + +.tt-compact-num:hover { + border-color: var(--accent); +} + +.tt-num-icon { + display: flex; + align-items: center; + color: var(--text-secondary); + flex-shrink: 0; + pointer-events: none; +} + +.tt-compact-num input[type="number"] { + flex: 1; + width: 100%; + min-width: 0; + background: transparent; + border: none; + color: var(--accent); + font-size: 11px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + padding: 0 2px; + -moz-appearance: textfield; +} + +.tt-compact-num input[type="number"]::-webkit-inner-spin-button, +.tt-compact-num input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.tt-compact-num input[type="number"]:focus { + outline: none; + color: var(--text-primary); +} + +.tt-num-unit { + font-size: 9px; + color: var(--text-secondary); + flex-shrink: 0; + pointer-events: none; +} + +/* Color row */ +.tt-color-row { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; + height: 24px; +} + +.tt-color-row:last-child { + margin-bottom: 0; +} + +.tt-color-swatch { + width: 20px; + height: 20px; + padding: 0; + border: 1px solid var(--border-color); + border-radius: 3px; + cursor: pointer; + flex-shrink: 0; + -webkit-appearance: none; + background: none; +} + +.tt-color-swatch::-webkit-color-swatch-wrapper { + padding: 1px; +} + +.tt-color-swatch::-webkit-color-swatch { + border: none; + border-radius: 2px; +} + +.tt-color-swatch:disabled { + opacity: 0.4; +} + +.tt-color-label { + font-size: 11px; + color: var(--text-secondary); + min-width: 36px; +} + +.tt-color-hex { + flex: 1; + min-width: 0; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + color: var(--text-primary); + font-size: 10px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + padding: 2px 6px; + height: 22px; +} + +.tt-color-hex:focus { + outline: none; + border-color: var(--accent); +} + +/* Toggle checkbox */ +.tt-toggle { + display: flex; + align-items: center; + cursor: pointer; + flex-shrink: 0; +} + +.tt-toggle input { + display: none; +} + +.tt-toggle-box { + width: 14px; + height: 14px; + border: 1px solid var(--border-color); + border-radius: 2px; + background: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} + +.tt-toggle input:checked + .tt-toggle-box { + background: var(--accent); + border-color: var(--accent); +} + +.tt-toggle input:checked + .tt-toggle-box::after { + content: ''; + width: 8px; + height: 5px; + border-left: 1.5px solid var(--text-inverse); + border-bottom: 1.5px solid var(--text-inverse); + transform: rotate(-45deg) translateY(-1px); +} + +/* Toggle in section header */ +.tt-toggle-header { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.tt-toggle-header input { + width: 12px; + height: 12px; + cursor: pointer; + accent-color: var(--accent); +} + +/* Alignment buttons row */ +.tt-align-row { + display: flex; + gap: 1px; + align-items: center; +} + +.tt-align-row button { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 24px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.1s; + padding: 0; +} + +.tt-align-row button:first-child { + border-radius: 3px 0 0 3px; +} + +.tt-align-row button:nth-child(3) { + border-radius: 0 3px 3px 0; +} + +.tt-align-row button:nth-child(5) { + border-radius: 3px 0 0 3px; +} + +.tt-align-row button:last-child { + border-radius: 0 3px 3px 0; +} + +.tt-align-row button:not(:first-child) { + margin-left: -1px; +} + +.tt-align-row button:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + z-index: 1; +} + +.tt-align-row button.active { + background: var(--accent); + color: var(--text-inverse); + border-color: var(--accent); + z-index: 2; +} + +.tt-align-sep { + width: 8px; + flex-shrink: 0; +} + +} diff --git a/src/components/panels/properties/VolumeBlendshapeTabs.css b/src/components/panels/properties/VolumeBlendshapeTabs.css new file mode 100644 index 000000000..a0d21319e --- /dev/null +++ b/src/components/panels/properties/VolumeBlendshapeTabs.css @@ -0,0 +1,226 @@ +@layer features { +/* EQ Container */ +.eq-container { + display: flex; + gap: var(--sp-1); + padding: var(--sp-2) var(--sp-4); + justify-content: center; +} + +.eq-band { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--sp-1); +} + +.eq-slider { + writing-mode: vertical-lr; + direction: rtl; + width: 20px; + height: 80px; + appearance: none; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + cursor: pointer; +} + +.eq-slider::-webkit-slider-thumb { + appearance: none; + width: 16px; + height: 8px; + border-radius: var(--radius-sm); + background: var(--accent); + cursor: pointer; +} + +.eq-slider::-moz-range-thumb { + width: 16px; + height: 8px; + border-radius: var(--radius-sm); + background: var(--accent); + cursor: pointer; + border: none; +} + +.eq-label { + font-size: var(--font-2xs); + color: var(--text-muted); + text-align: center; +} + +/* Volume Tab (Audio) */ +.volume-tab .section-header-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +.volume-tab .section-header-row h4 { + margin: 0; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); +} + +/* EQ Section */ +.eq-section { + padding-bottom: 16px; +} + +.eq-bands { + display: flex; + gap: 4px; + margin-bottom: 8px; +} + +.eq-band { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.eq-band-kf { + height: 20px; + display: flex; + align-items: center; +} + +.eq-band-value { + font-size: 9px; + font-family: 'SF Mono', monospace; + color: var(--text-secondary); + text-align: center; + min-width: 28px; +} + +.eq-slider { + writing-mode: vertical-lr; + direction: rtl; + height: 100px; + width: 18px; + margin: 0; + cursor: pointer; +} + +.eq-band-label { + font-size: 9px; + color: var(--text-secondary); + text-align: center; +} + +.eq-scale { + display: flex; + justify-content: space-between; + font-size: 9px; + color: var(--text-secondary); + padding: 0 4px; +} + +/* Blendshapes Tab */ +.blendshapes-tab .section-header-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.blendshapes-tab .section-header-row h4 { + margin: 0; + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + flex: 1; +} + +.blendshape-presets { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.blendshape-presets .btn { + font-size: 10px; + padding: 2px 8px; +} + +.blendshape-group-header { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 0; +} + +.blendshape-group-header h4 { + flex: 1; +} + +.blendshape-collapse-arrow { + font-size: 8px; + color: var(--text-secondary); + width: 10px; + text-align: center; +} + +.blendshape-sliders { + display: flex; + flex-direction: column; + gap: 1px; +} + +.blendshape-row { + display: flex; + align-items: center; + gap: 6px; + padding: 1px 0; +} + +.blendshape-label { + font-size: 10px; + min-width: 90px !important; + max-width: 90px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.blendshape-slider { + flex: 1; + height: 3px; + -webkit-appearance: none; + appearance: none; + background: var(--bg-tertiary); + border-radius: 2px; + cursor: pointer; +} + +.blendshape-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; +} + +.blendshape-slider::-moz-range-thumb { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; + border: none; +} + +.blendshape-row .draggable-number { + min-width: 36px; + text-align: right; + font-size: 10px; +} + +} diff --git a/src/components/panels/properties/index.tsx b/src/components/panels/properties/index.tsx index b3e9e8bb3..35820bb26 100644 --- a/src/components/panels/properties/index.tsx +++ b/src/components/panels/properties/index.tsx @@ -5,6 +5,11 @@ import { useTimelineStore } from '../../../stores/timeline'; import { useEngineStore } from '../../../stores/engineStore'; import { DEFAULT_TEXT_3D_PROPERTIES } from '../../../stores/timeline/constants'; import { TextTab } from '../TextTab'; +import './PropertiesPanel.css'; +import './EffectsTab.css'; +import './AnalysisTranscriptTabs.css'; +import './TextTab.css'; +import './VolumeBlendshapeTabs.css'; // Tab type type PropertiesTab = 'transform' | 'color' | 'effects' | 'masks' | 'transcript' | 'analysis' | 'text' | '3d-text' | 'math' | 'motion' | 'blendshapes' | 'gaussian-splat' | 'camera' | 'splat-effector' | 'lottie' | 'slot-clip'; diff --git a/src/components/preview/MaskOverlay.css b/src/components/preview/MaskOverlay.css new file mode 100644 index 000000000..f36549779 --- /dev/null +++ b/src/components/preview/MaskOverlay.css @@ -0,0 +1,443 @@ +@layer features { +/* ========== MASK SECTION STYLES ========== */ +.masks-section .section-header-with-button { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.masks-section .section-header-with-button h4 { + margin: 0; +} + +.mask-add-menu-container { + position: relative; +} + +.mask-add-menu { + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + min-width: 120px; + z-index: 100; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.mask-add-menu button { + display: block; + width: 100%; + text-align: left; + padding: 8px 12px; + background: none; + border: none; + color: var(--text-primary); + cursor: pointer; + font-size: 12px; +} + +.mask-add-menu button:hover { + background: var(--bg-hover); +} + +.mask-add-menu button:first-child { + border-radius: 6px 6px 0 0; +} + +.mask-add-menu button:last-child { + border-radius: 0 0 6px 6px; +} + +.mask-empty { + color: var(--text-secondary); + font-size: 12px; + padding: 12px 0; + text-align: center; +} + +.masks-tab { + gap: 10px; +} + +.mask-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; +} + +.mask-toolbar-group, +.mask-item-actions, +.mask-active-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.mask-icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + background: transparent; + border: 1px solid transparent; + border-radius: 5px; + color: var(--text-secondary); + cursor: pointer; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.12s ease; +} + +.mask-icon-btn:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--border-color); + color: var(--text-primary); +} + +.mask-icon-btn.active { + background: rgba(45, 140, 235, 0.18); + border-color: rgba(45, 140, 235, 0.7); + color: #72b7ff; +} + +.mask-icon-btn.danger:hover:not(:disabled) { + background: rgba(239, 68, 68, 0.16); + border-color: rgba(239, 68, 68, 0.5); + color: var(--danger); +} + +.mask-icon-btn.cancel { + color: #ff9d5c; +} + +.mask-icon-btn:disabled { + opacity: 0.35; + cursor: default; +} + +.mask-icon-btn.expanded svg { + transform: rotate(0deg); +} + +.mask-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.mask-item { + position: relative; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + overflow: visible; +} + +.mask-item.active { + border-color: rgba(45, 140, 235, 0.85); + box-shadow: inset 3px 0 0 rgba(45, 140, 235, 0.9); +} + +.mask-item.color-menu-open { + z-index: 25; +} + +.mask-item.disabled { + opacity: 0.68; +} + +.mask-item-header { + display: flex; + align-items: center; + gap: 6px; + min-height: 38px; + padding: 5px 6px; + cursor: pointer; +} + +.mask-item-header:hover { + background: var(--bg-hover); +} + +.mask-outline-swatch { + width: 14px; + height: 14px; + flex: 0 0 14px; + padding: 0; + border: 1px solid rgba(255, 255, 255, 0.48); + border-radius: 4px; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.38); + cursor: pointer; +} + +.mask-outline-swatch:hover, +.mask-outline-swatch[aria-expanded="true"] { + border-color: rgba(255, 255, 255, 0.82); +} + +.mask-outline-color-menu { + position: absolute; + top: 34px; + left: 40px; + z-index: 30; + display: grid; + grid-template-columns: repeat(6, 18px) 26px; + align-items: center; + gap: 6px; + padding: 7px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: var(--shadow-lg); +} + +.mask-outline-color-option { + position: relative; + width: 18px; + height: 18px; + padding: 0; + border: 1px solid rgba(255, 255, 255, 0.42); + border-radius: 4px; + cursor: pointer; +} + +.mask-outline-color-option:hover, +.mask-outline-color-option.active { + border-color: #fff; + box-shadow: 0 0 0 2px rgba(45, 140, 235, 0.45); +} + +.mask-outline-color-input { + width: 26px; + height: 22px; + padding: 0; + border: 1px solid var(--border-color); + border-radius: 4px; + background: transparent; + cursor: pointer; +} + +.mask-name { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1px; + padding: 0; + background: transparent; + border: 0; + color: var(--text-primary); + cursor: pointer; + text-align: left; +} + +.mask-name span { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; +} + +.mask-name small { + color: var(--text-secondary); + font-size: 10px; +} + +.mask-name-input { + flex: 1; + min-width: 0; + font-size: 12px; + padding: 4px 6px; + border: 1px solid var(--accent); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + outline: none; +} + +.mask-active-card { + padding: 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; +} + +.mask-active-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} + +.mask-active-header strong { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text-primary); + margin-bottom: 2px; +} + +.mask-active-header span { + color: var(--text-secondary); + font-size: 10px; +} + +.mask-path-row { + display: flex; + align-items: center; + gap: var(--sp-2); +} + +.mask-path-row label { + min-width: 70px; +} + +.mask-path-row span { + margin-left: auto; + min-width: auto; + color: var(--text-secondary); + font-size: 10px; + white-space: nowrap; +} + +.mask-vertex-tools { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 6px; + padding: 7px 0 9px; + border-top: 1px solid var(--border-color); +} + +.mask-vertex-tools > span { + color: var(--text-secondary); + font-size: 10px; + white-space: nowrap; +} + +.mask-mode-segmented { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 4px; + margin-bottom: 10px; +} + +.mask-mode-segmented.compact { + margin-bottom: 0; +} + +.mask-mode-segmented button { + min-width: 0; + height: 26px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-tertiary); + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; +} + +.mask-mode-segmented button.active { + border-color: rgba(45, 140, 235, 0.8); + background: rgba(45, 140, 235, 0.15); + color: #9ccdff; +} + +.mask-property-groups { + display: flex; + flex-direction: column; + gap: 10px; +} + +.mask-property-group { + padding-top: 8px; + border-top: 1px solid var(--border-color); +} + +.mask-property-group h5 { + margin: 0 0 6px; + color: var(--text-secondary); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} + +.mask-property-group .control-row { + margin-bottom: 6px; +} + +.mask-property-group .control-row:last-child { + margin-bottom: 0; +} + +.mask-empty { + color: var(--text-secondary); + font-size: 12px; + padding: 14px 0; + text-align: center; +} + +/* ========== MASK OVERLAY STYLES ========== */ +.mask-overlay-svg { + pointer-events: auto; + z-index: 50; + overflow: visible; +} + +.mask-overlay-svg text { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); +} + +.mask-edge-highlight { + filter: drop-shadow(0 0 4px rgba(255, 153, 0, 0.65)); +} + +.mask-edge-insert-preview { + animation: mask-active-pulse 0.9s ease-in-out infinite; + filter: drop-shadow(0 0 5px rgba(255, 153, 0, 0.75)); +} + +.mask-vertex-point { + transition: fill 0.1s ease, stroke 0.1s ease; +} + +.mask-handle-point { + transition: r 0.1s ease, fill 0.1s ease; +} + +.mask-handle-point:hover { + fill: #ffc266; +} + +.mask-active-vertex-ring { + animation: mask-active-pulse 1.1s ease-in-out infinite; +} + +.mask-hover-vertex-ring { + opacity: 0.9; +} + +@keyframes mask-active-pulse { + 0%, 100% { + opacity: 0.55; + stroke-width: 1.5; + } + 50% { + opacity: 1; + stroke-width: 2.5; + } +} +} diff --git a/src/components/preview/MaskOverlay.tsx b/src/components/preview/MaskOverlay.tsx index 26504ee9f..be523bb9d 100644 --- a/src/components/preview/MaskOverlay.tsx +++ b/src/components/preview/MaskOverlay.tsx @@ -1,5 +1,6 @@ // MaskOverlay - SVG overlay for mask drawing and editing on preview canvas +import './MaskOverlay.css'; import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; import { useTimelineStore } from '../../stores/timeline'; import { getShortcutRegistry } from '../../services/shortcutRegistry'; diff --git a/src/components/preview/Preview.css b/src/components/preview/Preview.css new file mode 100644 index 000000000..b8ace2a74 --- /dev/null +++ b/src/components/preview/Preview.css @@ -0,0 +1,641 @@ +@layer features { +/* Floating Draggable Preview */ +.floating-preview { + position: fixed; + z-index: 1000; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-xl); + overflow: hidden; + min-width: 280px; + max-width: 800px; +} + +.floating-preview-header { + display: flex; + align-items: center; + gap: var(--sp-1-5); + padding: 5px 10px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + cursor: grab; + user-select: none; + font-size: var(--font-sm); + font-weight: var(--font-medium); +} + +.floating-preview-header:active { + cursor: grabbing; +} + +.drag-handle { + color: var(--text-secondary); + font-size: var(--font-md); + letter-spacing: -2px; +} + +.dragging-preview { + cursor: grabbing; + user-select: none; +} + +.dragging-preview .floating-preview { + opacity: 0.95; + box-shadow: var(--shadow-xl); +} + +/* Preview - maintains video aspect ratio, always shows full video */ +.preview-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--bg-canvas); + height: 100%; + width: 100%; + overflow: hidden; + position: relative; +} + +.preview-stats { + position: absolute; + top: 8px; + right: 8px; + font-size: var(--font-sm); + color: var(--text-secondary); + background: var(--preview-overlay-bg); + padding: var(--sp-1) var(--sp-2); + border-radius: var(--radius-md); + z-index: 10; + cursor: pointer; + font-family: 'SF Mono', 'Consolas', monospace; + user-select: none; + backdrop-filter: blur(4px); + transition: all var(--transition-normal) ease; +} + +.preview-stats:hover { + background: var(--preview-overlay-bg-hover); +} + +.preview-top-right-overlays { + position: absolute; + top: 8px; + right: 8px; + z-index: 22; + display: flex; + align-items: flex-start; + justify-content: flex-end; + gap: 8px; + pointer-events: none; +} + +.preview-top-right-overlays > * { + pointer-events: auto; +} + +.preview-top-right-overlays .preview-stats { + position: static; +} + +.preview-stats-compact { + min-width: 80px; +} + +.preview-stats-expanded { + min-width: 200px; + padding: var(--sp-2) var(--sp-3); +} + +.stats-header { + margin-bottom: var(--sp-2); + padding-bottom: 6px; + border-bottom: 1px solid rgba(255, 255, 255, 0.15); +} + +.stats-section { + margin-bottom: var(--sp-2); + padding-bottom: 6px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.stats-section:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.stats-label { + font-size: var(--font-2xs); + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.5; + margin-bottom: var(--sp-1); +} + +.stats-row { + display: flex; + justify-content: space-between; + gap: var(--sp-3); + font-size: var(--font-xs); + line-height: 1.6; +} + +.stats-bar-container { + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: var(--radius-sm); + margin: 4px 0; + display: flex; + overflow: hidden; +} + +[data-theme="light"] .stats-header { + border-bottom: 1px solid rgba(0, 0, 0, 0.12); +} + +[data-theme="light"] .stats-section { + border-bottom: 1px solid rgba(0, 0, 0, 0.06); +} + +[data-theme="light"] .stats-bar-container { + background: rgba(0, 0, 0, 0.08); +} + +.stats-bar { + height: 100%; + transition: width var(--transition-slow) ease; +} + +.stats-bar-import { + background: #4af; +} + +.stats-bar-render { + background: #4f4; +} + +.stats-bar-submit { + background: #fa4; +} + +.preview-canvas-wrapper { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-primary); +} + +/* Checkerboard pattern on canvas itself for transparency visualization */ +.preview-canvas-wrapper.show-transparency-grid .preview-canvas { + background-color: #404040; + background-image: + linear-gradient(45deg, #303030 25%, transparent 25%), + linear-gradient(-45deg, #303030 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #303030 75%), + linear-gradient(-45deg, transparent 75%, #303030 75%); + background-size: 48px 48px; + background-position: 0 0, 0 24px, 24px -24px, -24px 0px; +} + +.preview-canvas { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; + background: var(--bg-canvas); + display: block; +} + +.preview-container { + position: relative; + overflow: hidden; +} + +.preview-controls { + position: absolute; + top: 8px; + left: 8px; + display: flex; + align-items: center; + gap: 8px; + z-index: 20; +} + +.preview-edit-btn { + padding: 4px 10px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; + backdrop-filter: blur(4px); +} + +.preview-edit-btn:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); + border-color: var(--gray-300); +} + +.preview-edit-btn:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.preview-edit-btn.active { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); +} + +.preview-scene-toggle-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 24px; + padding: 0; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s; + backdrop-filter: blur(4px); +} + +.preview-scene-toggle-btn:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); + border-color: var(--gray-300); +} + +.preview-scene-toggle-btn.active { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); +} + +.preview-zoom-label { + font-size: 11px; + color: var(--text-secondary); + background: var(--preview-overlay-bg); + padding: 4px 8px; + border-radius: 4px; + font-family: monospace; + backdrop-filter: blur(4px); +} + +.preview-reset-btn { + padding: 4px 8px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; + backdrop-filter: blur(4px); +} + +.preview-reset-btn:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); +} + +/* Preview Composition Selector */ +.preview-comp-selector { + position: absolute; + top: 8px; + right: 8px; + display: flex; + align-items: center; + gap: 4px; + z-index: 20; +} + +.preview-comp-dropdown-wrapper { + position: relative; +} + +.preview-comp-dropdown-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; + backdrop-filter: blur(4px); +} + +.preview-comp-dropdown-btn:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); + border-color: var(--gray-300); +} + +.preview-comp-name { + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.preview-comp-arrow { + font-size: 8px; + opacity: 0.7; +} + +.preview-comp-dropdown { + position: absolute; + top: 100%; + left: 0; + margin-top: 4px; + min-width: 160px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + padding: 4px 0; + z-index: 100; + max-height: min(70vh, 420px); + overflow-y: auto; +} + +.preview-comp-option { + display: block; + width: 100%; + padding: 6px 12px; + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 11px; + text-align: left; + cursor: pointer; + transition: all 0.15s; +} + +.preview-comp-option:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.preview-comp-option.active { + color: var(--accent); + background: rgba(45, 140, 235, 0.1); +} + +.preview-comp-option.layer-option { + padding-left: 24px; + font-size: 10px; +} + +.preview-comp-group-label { + padding: 6px 12px 4px; + color: var(--text-muted); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.preview-comp-separator { + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +.preview-add-btn { + padding: 4px 8px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 14px; + font-weight: bold; + cursor: pointer; + transition: all 0.15s; + line-height: 1; + backdrop-filter: blur(4px); +} + +.preview-add-btn:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); +} + +.preview-close-btn { + padding: 4px 8px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 14px; + font-weight: bold; + cursor: pointer; + transition: all 0.15s; + line-height: 1; + backdrop-filter: blur(4px); +} + +.preview-close-btn:hover { + background: var(--red-700); + border-color: var(--red-700); + color: var(--text-on-accent); +} + +.preview-splat-progress-overlay { + position: absolute; + left: 50%; + bottom: 42px; + width: min(460px, calc(100% - 32px)); + transform: translateX(-50%); + padding: 10px 12px; + color: var(--text-primary); + background: rgba(10, 12, 16, 0.86); + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 6px; + box-shadow: 0 10px 32px rgba(0, 0, 0, 0.32); + backdrop-filter: blur(8px); + z-index: 24; + pointer-events: none; +} + +.preview-splat-progress-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + font-size: 12px; + font-weight: 600; + line-height: 1.3; +} + +.preview-splat-progress-name { + margin-top: 3px; + color: var(--text-secondary); + font-size: 11px; + line-height: 1.35; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.preview-splat-progress-track { + height: 5px; + margin-top: 8px; + overflow: hidden; + background: rgba(255, 255, 255, 0.14); + border-radius: 999px; +} + +.preview-splat-progress-fill { + height: 100%; + width: 0; + background: var(--accent); + border-radius: inherit; + transition: width 0.16s ease-out; +} + +.preview-splat-progress-overlay.error .preview-splat-progress-fill { + background: var(--danger); +} + +.preview-canvas-wrapper { + transform-origin: center center; + transition: transform 0.1s ease-out; +} + +/* Preview Bottom Controls (Quality Selector) */ +.preview-controls-bottom { + position: absolute; + bottom: 8px; + left: 8px; + display: flex; + align-items: center; + gap: 8px; + z-index: 20; +} + +.preview-transparency-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + padding: 0; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + transition: all 0.15s ease; + backdrop-filter: blur(4px); +} + +.preview-transparency-toggle:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); + border-color: var(--gray-300); +} + +.preview-transparency-toggle.active { + background: rgba(45, 140, 235, 0.2); + border-color: var(--accent); + color: var(--accent); +} + +.preview-quality-dropdown-wrapper { + position: relative; +} + +.preview-quality-dropdown-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--preview-overlay-bg); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; + backdrop-filter: blur(4px); +} + +.preview-quality-dropdown-btn:hover { + background: var(--preview-overlay-bg-hover); + color: var(--text-primary); + border-color: var(--gray-300); +} + +.preview-quality-label { + min-width: 45px; +} + +.preview-quality-dropdown { + position: absolute; + bottom: 100%; + left: 0; + margin-bottom: 4px; + min-width: 120px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + padding: 4px 0; + z-index: 100; +} + +.preview-quality-option { + display: block; + width: 100%; + padding: 6px 12px; + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 11px; + text-align: left; + cursor: pointer; + transition: all 0.15s; +} + +.preview-quality-option:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.preview-quality-option.active { + color: var(--accent); + background: rgba(45, 140, 235, 0.1); +} + +.preview-quality-desc { + opacity: 0.6; + margin-left: 4px; +} + +@media (max-width: 900px) { + .floating-preview { + width: 280px !important; + left: 8px !important; + top: 60px !important; + } +} +} diff --git a/src/components/preview/Preview.tsx b/src/components/preview/Preview.tsx index f034501b8..cbccf0fa2 100644 --- a/src/components/preview/Preview.tsx +++ b/src/components/preview/Preview.tsx @@ -1,5 +1,7 @@ // Preview canvas component with After Effects-style editing overlay +import './Preview.css'; +import './PreviewEditMode.css'; import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { Logger } from '../../services/logger'; diff --git a/src/components/preview/PreviewEditMode.css b/src/components/preview/PreviewEditMode.css new file mode 100644 index 000000000..43a6605ad --- /dev/null +++ b/src/components/preview/PreviewEditMode.css @@ -0,0 +1,421 @@ +@layer features { +.preview-overlay { + position: absolute; + top: 0; + left: 0; + pointer-events: auto; + z-index: 10; +} + +.preview-edit-hint { + position: absolute; + bottom: 8px; + left: 50%; + transform: translateX(-50%); + font-size: 10px; + color: var(--text-secondary); + background: var(--bg-overlay); + padding: 4px 12px; + border-radius: 4px; + white-space: nowrap; + z-index: 20; + pointer-events: none; +} + +.preview-scene-object-overlay { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + z-index: 14; + pointer-events: none; +} + +.preview-scene-object-handle { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + transform: translate(-50%, -50%); + color: #f6f8fb; + background: rgba(12, 15, 20, 0.82); + border: 1px solid rgba(255, 255, 255, 0.38); + border-radius: 50%; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42); + cursor: pointer; + font-size: 10px; + font-weight: 700; + line-height: 1; + pointer-events: auto; + user-select: none; +} + +.preview-scene-object-handle:hover, +.preview-scene-object-handle.selected { + border-color: #ffffff; + background: #2563eb; +} + +.preview-scene-object-handle.kind-effector { + background: #7c2d12; +} + +.preview-scene-object-handle.kind-splat { + background: #166534; +} + +.preview-scene-object-handle.kind-camera { + background: #0f766e; +} + +.preview-scene-object-handle.kind-model, +.preview-scene-object-handle.kind-plane { + background: #581c87; +} + +.preview-camera-wireframe { + position: absolute; + inset: 0; + overflow: visible; + pointer-events: none; +} + +.preview-camera-wireframe-line { + fill: none; + stroke: rgba(20, 184, 166, 0.78); + stroke-width: 1.5; + stroke-linecap: round; + stroke-linejoin: round; + vector-effect: non-scaling-stroke; + filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.95)); +} + +.preview-camera-wireframe-line.role-body { + stroke: rgba(45, 212, 191, 0.9); + stroke-width: 1.9; +} + +.preview-camera-wireframe-line.role-direction { + stroke: rgba(96, 165, 250, 0.92); + stroke-width: 2.2; +} + +.preview-camera-wireframe-line.selected { + stroke-opacity: 1; + filter: + drop-shadow(0 1px 1px rgba(0, 0, 0, 0.95)) + drop-shadow(0 0 5px rgba(45, 212, 191, 0.58)); +} + +.preview-scene-world-grid { + position: absolute; + inset: 0; + overflow: visible; + pointer-events: none; +} + +.preview-scene-world-grid-line { + fill: none; + stroke: rgba(148, 163, 184, 0.22); + stroke-width: 1; + stroke-linecap: round; + stroke-linejoin: round; + vector-effect: non-scaling-stroke; + filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.7)); +} + +.preview-scene-world-grid-line.major { + stroke: rgba(148, 163, 184, 0.36); +} + +.preview-scene-world-grid-line.axis-x { + stroke: rgba(239, 68, 68, 0.58); + stroke-width: 1.6; +} + +.preview-scene-world-grid-line.axis-y { + stroke: rgba(34, 197, 94, 0.58); + stroke-width: 1.6; +} + +.preview-scene-world-grid-line.axis-z { + stroke: rgba(59, 130, 246, 0.58); + stroke-width: 1.6; +} + +.preview-scene-object-handle.selected { + background: #2563eb; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.92), 0 8px 20px rgba(0, 0, 0, 0.45); +} + +.preview-scene-object-handle.gizmo-center { + width: 28px; + height: 28px; + z-index: 5; + color: #f8fafc; + background: rgba(8, 11, 16, 0.18); + border: 2px solid rgba(248, 250, 252, 0.88); + box-shadow: + 0 0 0 1px rgba(8, 11, 16, 0.86), + 0 0 14px rgba(59, 130, 246, 0.28), + 0 8px 20px rgba(0, 0, 0, 0.52); +} + +.preview-scene-object-handle.gizmo-center.center-draggable { + cursor: grab; +} + +.preview-scene-object-handle.gizmo-center.center-draggable:active { + cursor: grabbing; +} + +.preview-scene-object-handle.gizmo-center span { + display: block; + width: 10px; + height: 10px; + color: transparent; + background: #2563eb; + border-radius: 999px; + font-size: 0; +} + +.preview-scene-object-handle.gizmo-center.mode-scale span { + border-radius: 2px; +} + +.preview-scene-object-handle.gizmo-center.mode-rotate span { + width: 13px; + height: 13px; + background: transparent; + border: 2px solid #2563eb; +} + +.preview-scene-object-context-menu { + position: absolute; + z-index: 30; + min-width: 154px; + padding: 4px; + background: rgba(12, 15, 20, 0.96); + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 6px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45); + pointer-events: auto; +} + +.preview-scene-object-context-menu button { + width: 100%; + height: 28px; + padding: 0 10px; + color: #f8fafc; + background: transparent; + border: 0; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + text-align: left; + cursor: pointer; +} + +.preview-scene-object-context-menu button:hover { + background: rgba(59, 130, 246, 0.28); +} + +.preview-scene-gizmo-axis-layer { + position: absolute; + inset: 0; + pointer-events: none; +} + +.preview-scene-gizmo-axis { + position: absolute; + height: 34px; + margin: -17px 0 0 0; + padding: 0; + background: transparent; + border: 0; + cursor: grab; + pointer-events: auto; + transform-origin: 0 50%; +} + +.preview-scene-gizmo-axis:active { + cursor: grabbing; +} + +.preview-scene-gizmo-axis-line { + position: absolute; + left: 0; + right: 0; + top: 50%; + height: 5px; + border-radius: 999px; + background: currentColor; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.78), + 0 0 9px color-mix(in srgb, currentColor 58%, transparent); + transform: translateY(-50%); +} + +.preview-scene-gizmo-axis-line, +.preview-scene-gizmo-end, +.preview-scene-gizmo-label, +.preview-scene-gizmo-rotate-stroke { + opacity: 0; +} + +.preview-scene-gizmo-axis.mode-rotate .preview-scene-gizmo-axis-line { + background: repeating-linear-gradient( + 90deg, + currentColor 0, + currentColor 7px, + transparent 7px, + transparent 12px + ); +} + +.preview-scene-gizmo-axis.mode-scale .preview-scene-gizmo-axis-line { + height: 4px; +} + +.preview-scene-gizmo-end { + position: absolute; + right: -7px; + top: 50%; + width: 14px; + height: 14px; + background: currentColor; + border: 2px solid rgba(8, 11, 16, 0.92); + border-radius: 2px; + box-shadow: + 0 1px 4px rgba(0, 0, 0, 0.55), + 0 0 8px color-mix(in srgb, currentColor 52%, transparent); + transform: translateY(-50%); +} + +.preview-scene-gizmo-axis.mode-move .preview-scene-gizmo-end { + right: -14px; + width: 0; + height: 0; + background: transparent; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 15px solid currentColor; + border-right: 0; + border-radius: 0; + box-shadow: none; + filter: + drop-shadow(0 0 1px rgba(0, 0, 0, 0.95)) + drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5)); +} + +.preview-scene-gizmo-axis.mode-scale .preview-scene-gizmo-end { + right: -8px; + width: 16px; + height: 16px; +} + +.preview-scene-gizmo-label { + position: absolute; + transform: translate(-50%, -50%); + font-size: 11px; + font-weight: 800; + pointer-events: none; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 0.9), + 0 -1px 2px rgba(0, 0, 0, 0.9); +} + +.preview-scene-gizmo-axis.axis-x, +.preview-scene-gizmo-label.axis-x { + color: #ef4444; +} + +.preview-scene-gizmo-axis.axis-y, +.preview-scene-gizmo-label.axis-y { + color: #22c55e; +} + +.preview-scene-gizmo-axis.axis-z, +.preview-scene-gizmo-label.axis-z { + color: #3b82f6; +} + +.preview-scene-gizmo-rotate { + position: absolute; + width: 128px; + height: 128px; + overflow: visible; + transform: translate(-50%, -50%); + pointer-events: none; +} + +.preview-scene-gizmo-rotate-hit { + fill: none; + stroke: transparent; + stroke-width: 30px; + cursor: grab; + pointer-events: stroke; +} + +.preview-scene-gizmo-rotate-hit:active { + cursor: grabbing; +} + +.preview-scene-gizmo-rotate-stroke { + fill: none; + stroke: currentColor; + stroke-width: 4px; + pointer-events: none; + filter: + drop-shadow(0 0 1px rgba(0, 0, 0, 0.95)) + drop-shadow(0 0 6px color-mix(in srgb, currentColor 55%, transparent)); +} + +.preview-scene-gizmo-rotate-ring.axis-x { + color: #ef4444; +} + +.preview-scene-gizmo-rotate-ring.axis-y { + color: #22c55e; +} + +.preview-scene-gizmo-rotate-ring.axis-z { + color: #3b82f6; +} + +.preview-scene-gizmo-toolbar { + display: flex; + gap: 3px; + padding: 3px; + background: rgba(8, 11, 16, 0.82); + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 5px; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.36); + pointer-events: auto; +} + +.preview-scene-gizmo-toolbar-slot:empty { + display: none; +} + +.preview-scene-gizmo-toolbar button { + min-width: 44px; + height: 22px; + padding: 0 7px; + color: var(--text-secondary); + background: transparent; + border: 0; + border-radius: 3px; + cursor: pointer; + font-size: 10px; +} + +.preview-scene-gizmo-toolbar button:hover, +.preview-scene-gizmo-toolbar button.active { + color: #ffffff; + background: rgba(255, 255, 255, 0.15); +} +} diff --git a/src/components/preview/SourceMonitor.css b/src/components/preview/SourceMonitor.css new file mode 100644 index 000000000..f3d0720ee --- /dev/null +++ b/src/components/preview/SourceMonitor.css @@ -0,0 +1,166 @@ +@layer features { +/* ========== SOURCE MONITOR ========== */ +.source-monitor { + position: absolute; + inset: 28px 0 0 0; /* below preview-controls bar */ + display: flex; + flex-direction: column; + background: var(--bg-primary); + z-index: 15; +} + +.source-monitor-media { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-height: 0; + overflow: hidden; + background: var(--bg-canvas); +} + +.source-monitor-video, +.source-monitor-canvas, +.source-monitor-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + display: block; +} + +.source-monitor-video, +.source-monitor-canvas { + cursor: pointer; +} + +/* Toolbar - matches timeline-toolbar style */ +.source-monitor-toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 3px 8px; + background: var(--bg-tertiary); + border-top: 1px solid var(--border-color); + min-height: 30px; + flex-shrink: 0; +} + +.source-monitor-transport { + display: flex; + gap: 2px; +} + +.source-monitor-backend { + display: flex; + gap: 4px; +} + +.source-monitor-transport .btn { + min-width: 24px; + padding: 3px 4px; + font-size: 10px; + line-height: 1; +} + +.source-monitor-timecode { + display: flex; + align-items: center; + gap: 4px; +} + +.source-monitor-timecode .timeline-time { + min-width: auto; + padding: 2px 5px; + font-size: 11px; +} + +.source-monitor-time-sep { + color: var(--text-secondary); + font-size: 10px; + opacity: 0.5; +} + +/* Scrub bar */ +.source-monitor-scrub { + flex: 1; + height: 24px; + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + padding: 0 2px; +} + +.source-monitor-scrub-track { + flex: 1; + height: 4px; + background: var(--bg-primary); + border-radius: 2px; + position: relative; +} + +.source-monitor-scrub:hover .source-monitor-scrub-track { + height: 6px; +} + +.source-monitor-scrub-fill { + height: 100%; + background: var(--accent); + border-radius: 2px; + position: absolute; + top: 0; + left: 0; + pointer-events: none; +} + +.source-monitor-scrub-handle { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--text-primary); + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + opacity: 0; + transition: opacity 0.15s; +} + +.source-monitor-scrub:hover .source-monitor-scrub-handle { + opacity: 1; +} + +.source-monitor-status { + font-size: 10px; + color: var(--text-secondary); + white-space: nowrap; +} + +/* Header controls */ +.preview-source-label { + font-size: 11px; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + padding: 0 4px; +} + +.preview-close-source-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 13px; + cursor: pointer; + padding: 2px 6px; + border-radius: 2px; + line-height: 1; +} + +.preview-close-source-btn:hover { + color: var(--text-primary); + background: var(--bg-hover); +} +} diff --git a/src/components/preview/SourceMonitor.tsx b/src/components/preview/SourceMonitor.tsx index 963eca2ae..1261c6c96 100644 --- a/src/components/preview/SourceMonitor.tsx +++ b/src/components/preview/SourceMonitor.tsx @@ -1,6 +1,7 @@ // Source Monitor - displays raw media files (video/image) in the Preview panel // Always uses HTML video backend for simplicity and reliability +import './SourceMonitor.css'; import { useCallback, useEffect, useRef, useState } from 'react'; import { getShortcutRegistry } from '../../services/shortcutRegistry'; import type { MediaFile } from '../../stores/mediaStore'; diff --git a/src/components/timeline/MarkerContextMenu.tsx b/src/components/timeline/MarkerContextMenu.tsx index 98ef7f25b..63713a62e 100644 --- a/src/components/timeline/MarkerContextMenu.tsx +++ b/src/components/timeline/MarkerContextMenu.tsx @@ -1,3 +1,4 @@ +import './TimelineMarkers.css'; import { useEffect } from 'react'; import type { TimelineMarker } from '../../stores/timeline/types'; import { useContextMenuPosition } from '../../hooks/useContextMenuPosition'; diff --git a/src/components/timeline/MulticamDialog.css b/src/components/timeline/MulticamDialog.css new file mode 100644 index 000000000..660fa411e --- /dev/null +++ b/src/components/timeline/MulticamDialog.css @@ -0,0 +1,312 @@ +@layer features { + +/* ========== MULTICAM DIALOG STYLES ========== */ +.multicam-dialog-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 10001; +} + +.multicam-dialog { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + width: 480px; + max-width: 90vw; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); +} + +.multicam-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border-color); +} + +.multicam-dialog-header h2 { + margin: 0; + font-size: 16px; + color: var(--text-primary); +} + +.dialog-close-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 20px; + cursor: pointer; + padding: 4px 8px; + line-height: 1; +} + +.dialog-close-btn:hover:not(:disabled) { + color: var(--text-primary); +} + +.dialog-close-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.multicam-dialog-content { + padding: 20px; + overflow-y: auto; + flex: 1; +} + +.multicam-section { + margin-bottom: 20px; +} + +.multicam-section h3 { + margin: 0 0 8px; + font-size: 13px; + color: var(--text-primary); + font-weight: 600; +} + +.section-hint { + font-size: 11px; + color: var(--text-secondary); + margin: 0 0 12px; +} + +.section-hint.warning { + color: var(--amber-600); +} + +.multicam-method-selector { + display: flex; + gap: 12px; + margin-bottom: 8px; +} + +.method-option { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 12px 8px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + transition: all 0.15s ease; +} + +.method-option:hover:not(:has(input:disabled)) { + border-color: var(--accent); +} + +.method-option.selected { + border-color: var(--accent); + background: rgba(59, 130, 246, 0.1); +} + +.method-option:has(input:disabled) { + opacity: 0.5; + cursor: not-allowed; +} + +.method-option input[type="radio"] { + display: none; +} + +.method-option .method-icon { + font-size: 24px; +} + +.method-option .method-label { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.method-option .method-count { + font-size: 10px; + color: var(--text-secondary); +} + +.transcript-word-count { + margin-left: auto; + font-size: 10px; + color: var(--text-secondary); + background: var(--bg-secondary); + padding: 2px 6px; + border-radius: 4px; +} + +.multicam-warning { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: var(--danger-light); + padding: 12px; + border-radius: 6px; + font-size: 12px; + text-align: center; +} + +.multicam-clip-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.multicam-clip-list.no-audio { + opacity: 0.7; +} + +.multicam-clip-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease; +} + +.multicam-clip-item:hover { + border-color: var(--accent); +} + +.multicam-clip-item.disabled { + cursor: default; + opacity: 0.6; +} + +.multicam-clip-item input[type="radio"] { + margin: 0; + accent-color: var(--accent); +} + +.multicam-clip-item .clip-icon { + font-size: 16px; +} + +.multicam-clip-item .clip-name { + flex: 1; + font-size: 12px; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.master-badge { + background: var(--purple); + color: var(--text-on-accent); + font-size: 10px; + font-weight: 600; + padding: 2px 6px; + border-radius: 3px; +} + +.no-audio-badge { + background: var(--gray-250); + color: var(--text-on-accent); + font-size: 10px; + padding: 2px 6px; + border-radius: 3px; +} + +.multicam-progress-section { + margin-top: 16px; +} + +.multicam-progress { + height: 6px; + background: var(--bg-tertiary); + border-radius: 3px; + overflow: hidden; + margin-bottom: 8px; +} + +.multicam-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--purple), var(--purple-300)); + border-radius: 3px; + transition: width 0.2s ease-out; +} + +.progress-label { + font-size: 11px; + color: var(--text-secondary); + text-align: center; + display: block; +} + +.multicam-error { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: var(--danger-light); + padding: 10px 12px; + border-radius: 6px; + font-size: 12px; + margin-top: 16px; +} + +.multicam-dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 20px; + border-top: 1px solid var(--border-color); +} + +.multicam-dialog-footer .btn-cancel { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + color: var(--text-primary); + padding: 8px 16px; + border-radius: 6px; + font-size: 13px; + cursor: pointer; + transition: all 0.15s ease; +} + +.multicam-dialog-footer .btn-cancel:hover:not(:disabled) { + background: var(--bg-hover); +} + +.multicam-dialog-footer .btn-cancel:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.multicam-dialog-footer .btn-sync { + background: var(--purple); + border: none; + color: var(--text-on-accent); + padding: 8px 20px; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s ease; +} + +.multicam-dialog-footer .btn-sync:hover:not(:disabled) { + background: var(--purple-600); +} + +.multicam-dialog-footer .btn-sync:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +} diff --git a/src/components/timeline/MulticamDialog.tsx b/src/components/timeline/MulticamDialog.tsx index 75f994aba..04d0c41fa 100644 --- a/src/components/timeline/MulticamDialog.tsx +++ b/src/components/timeline/MulticamDialog.tsx @@ -5,6 +5,7 @@ import { useState, useMemo, useCallback, useEffect } from 'react'; import { useTimelineStore } from '../../stores/timeline'; import type { TimelineClip, TranscriptWord } from '../../types'; import { Logger } from '../../services/logger'; +import './MulticamDialog.css'; const log = Logger.create('MulticamDialog'); diff --git a/src/components/timeline/SlotGrid.css b/src/components/timeline/SlotGrid.css new file mode 100644 index 000000000..101b133b4 --- /dev/null +++ b/src/components/timeline/SlotGrid.css @@ -0,0 +1,267 @@ +@layer features { +/* Slot Grid Toolbar */ +.slot-grid-toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 3px 10px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + min-height: 30px; +} + +.slot-grid-toolbar-back { + display: flex; + align-items: center; + gap: 5px; +} + +.slot-grid-toolbar-title { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + letter-spacing: 0.5px; + text-transform: uppercase; +} + +/* ============================ + Slot Grid View (Resolume-style) + ============================ */ + +.slot-grid-container { + position: absolute; + inset: 0; + z-index: 50; + overflow: auto; + padding: 8px; + background: var(--gray-850); + pointer-events: auto; +} + +.slot-grid-resolume { + display: inline-grid; + gap: 6px; +} + +.slot-grid-corner { + /* Empty top-left corner cell */ +} + +.slot-grid-col-header { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + font-size: 11px; + font-weight: 600; + color: var(--gray-200); + background: var(--bg-input); + border-radius: 3px; +} + +.slot-grid-col-header-clickable { + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.slot-grid-col-header-clickable:hover { + background: var(--bg-active); + color: var(--gray-75); +} + +.slot-grid-row-label { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + width: 40px; + font-size: 11px; + font-weight: 600; + color: var(--gray-200); + background: var(--bg-input); + border-radius: 3px; +} + +.slot-grid-row-letter { + line-height: 1; +} + +.slot-grid-opacity-slider { + -webkit-appearance: none; + appearance: none; + writing-mode: vertical-lr; + direction: rtl; + width: 14px; + height: 60px; + background: transparent; + cursor: pointer; + margin: 0; + padding: 0; +} + +.slot-grid-opacity-slider::-webkit-slider-runnable-track { + width: 4px; + height: 100%; + background: var(--gray-500); + border-radius: 2px; +} + +.slot-grid-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--gray-100); + margin-left: -3px; + cursor: pointer; +} + +.slot-grid-opacity-slider::-webkit-slider-thumb:hover { + background: var(--gray-0); +} + +.slot-grid-opacity-slider::-moz-range-track { + width: 4px; + height: 100%; + background: var(--gray-500); + border-radius: 2px; +} + +.slot-grid-opacity-slider::-moz-range-thumb { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--gray-100); + border: none; + cursor: pointer; +} + +.slot-grid-opacity-slider::-moz-range-thumb:hover { + background: var(--gray-0); +} + +.slot-grid-item { + position: relative; + border-radius: 4px; + overflow: hidden; + cursor: pointer; + border: 2px solid transparent; + background: var(--bg-input); + transition: border-color 0.15s, box-shadow 0.15s; +} + +.slot-grid-item:hover { + border-color: rgba(255, 255, 255, 0.3); + box-shadow: 0 0 8px rgba(255, 255, 255, 0.1); +} + +.slot-grid-item.active { + border-color: #4a9eff; + box-shadow: 0 0 12px rgba(74, 158, 255, 0.3); +} + +.slot-grid-item.layer-active { + border-color: #4aff6e; + box-shadow: 0 0 12px rgba(74, 255, 110, 0.3); +} + +.slot-grid-item.selected:not(.active) { + border-color: #f0c45a; + box-shadow: 0 0 12px rgba(240, 196, 90, 0.28); +} + +.slot-grid-item.drag-over { + border-color: #ff9800; + box-shadow: 0 0 12px rgba(255, 152, 0, 0.4); +} + +.slot-grid-item.empty { + background: var(--gray-850); + border: 1px dashed rgba(255, 255, 255, 0.06); + cursor: pointer; +} + +.slot-grid-item.empty:hover { + border-color: rgba(255, 255, 255, 0.2); + background: var(--bg-input); + box-shadow: 0 0 6px rgba(255, 255, 255, 0.05); +} + +.slot-grid-item.empty.drag-over { + border: 2px solid #ff9800; + box-shadow: 0 0 12px rgba(255, 152, 0, 0.4); +} + +/* Slot composition name */ +.slot-grid-name { + position: absolute; + top: 3px; + left: 4px; + right: 4px; + font-size: 10px; + color: var(--gray-25); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; + z-index: 2; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.7); +} + +/* Slot time display (elapsed / duration) */ +.slot-grid-time { + position: absolute; + bottom: 2px; + left: 4px; + text-align: left; + font-size: 10px; + white-space: nowrap; + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + color: rgba(255, 255, 255, 0.85); + pointer-events: none; + z-index: 2; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.7); +} + +/* Slot playhead line */ +.slot-grid-playhead { + position: absolute; + top: 18px; + bottom: 18px; + width: 1.5px; + background: #ff3b3b; + pointer-events: none; + z-index: 1; +} + +.slot-grid-context-menu { + position: fixed; + z-index: 9999; + background: var(--bg-active); + border: 1px solid var(--gray-400); + border-radius: 4px; + padding: 2px 0; + min-width: 140px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); +} + +.slot-grid-context-menu button { + display: block; + width: 100%; + padding: 6px 12px; + background: none; + border: none; + color: var(--gray-75); + font-size: 12px; + text-align: left; + cursor: pointer; +} + +.slot-grid-context-menu button:hover { + background: var(--gray-450); + color: var(--gray-0); +} + +} diff --git a/src/components/timeline/SlotGrid.tsx b/src/components/timeline/SlotGrid.tsx index 09c0892d6..989875f1b 100644 --- a/src/components/timeline/SlotGrid.tsx +++ b/src/components/timeline/SlotGrid.tsx @@ -17,6 +17,7 @@ import { animateSlotGrid } from './slotGridAnimation'; import { MiniTimeline } from './MiniTimeline'; import type { Composition } from '../../stores/mediaStore'; import type { SlotDeckState } from '../../stores/mediaStore/types'; +import './SlotGrid.css'; interface SlotGridProps { opacity: number; diff --git a/src/components/timeline/Timeline.css b/src/components/timeline/Timeline.css new file mode 100644 index 000000000..2b1e4a77f --- /dev/null +++ b/src/components/timeline/Timeline.css @@ -0,0 +1,183 @@ +@layer features { +/* ========== TIMELINE ========== */ +.timeline-container { + display: flex; + flex-direction: column; + height: 100%; + background: var(--bg-primary); +} + +.timeline-container.timeline-empty { + justify-content: center; + align-items: center; +} + +.timeline-empty-message { + text-align: center; + color: var(--text-secondary); +} + +.timeline-empty-message p { + margin: 0; +} + +.timeline-empty-message p:first-child { + font-size: 14px; + color: var(--text-primary); + margin-bottom: 8px; +} + +.timeline-empty-message .hint { + font-size: 11px; +} + +.toolbar-slide-wrapper { + flex-shrink: 0; +} + +.timeline-body { + display: flex; + flex-direction: row; + flex: 1; + overflow: hidden; + position: relative; +} + +.timeline-body-content { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + position: relative; +} + +/* Sticky header row - spans full width */ +.timeline-header-row { + display: flex; + position: sticky; + top: 0; + z-index: 20; + background: var(--bg-tertiary); + flex-shrink: 0; +} + +.timeline-header-row .ruler-header { + width: 150px; + flex-shrink: 0; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + color: var(--text-secondary); + background: var(--bg-tertiary); + border-right: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); +} + +.time-ruler-wrapper { + flex: 1; + overflow: hidden; + height: 30px; + border-bottom: 1px solid var(--border-color); + transition: none; +} + +.time-ruler-wrapper.comp-switching { + opacity: 1; +} + +.timeline-scroll-wrapper { + flex: 1; + overflow: hidden; + position: relative; +} + +/* Content row - track headers and lanes side by side */ +.timeline-content-row { + display: flex; + flex: 1; + min-height: 100%; + align-items: stretch; +} + +.ruler-header { + height: 30px; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + color: var(--text-secondary); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.ruler-header > span { + flex: 1; + text-align: center; +} + +.time-ruler-container { + position: relative; + height: 30px; + display: flex; + overflow: hidden; +} + +.time-ruler { + height: 30px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + position: relative; + cursor: pointer; + flex: 1; +} + +.time-ruler:hover { + background: var(--bg-hover); +} + +.time-marker { + position: absolute; + top: 0; + height: 100%; + border-left: 1px solid var(--border-color); +} + +.time-marker.main { + border-left-color: var(--border-strong); +} + +.time-marker.sub { + border-left-color: var(--bg-active); + height: 50%; + top: 50%; +} + +.time-label { + position: absolute; + top: 4px; + left: 4px; + font-size: 10px; + color: var(--text-secondary); + white-space: nowrap; +} + +.timeline-ruler-cache-indicator { + position: absolute; + bottom: 1px; + height: 3px; + background: var(--amber-300); + opacity: 0.82; + cursor: pointer; + z-index: 2; +} + +.timeline-ruler-cache-indicator.cache { + box-shadow: 0 0 5px rgba(251, 191, 36, 0.32); +} + +.timeline-ruler-cache-indicator.proxy { + box-shadow: 0 0 5px rgba(251, 191, 36, 0.32); +} +} diff --git a/src/components/timeline/Timeline.tsx b/src/components/timeline/Timeline.tsx index 70666217f..6bf41475f 100644 --- a/src/components/timeline/Timeline.tsx +++ b/src/components/timeline/Timeline.tsx @@ -2,6 +2,8 @@ // Composes TimelineRuler, TimelineControls, TimelineHeader, TimelineTrack, TimelineClip, TimelineKeyframes import { useRef, useState, useCallback, useEffect, useMemo } from 'react'; +import './Timeline.css'; +import './TimelineTracks.css'; import { useShallow } from 'zustand/react/shallow'; import { useTimelineStore } from '../../stores/timeline'; import { diff --git a/src/components/timeline/TimelineClip.css b/src/components/timeline/TimelineClip.css new file mode 100644 index 000000000..b5e2d9e32 --- /dev/null +++ b/src/components/timeline/TimelineClip.css @@ -0,0 +1,1359 @@ +@layer features { +/* Timeline clips */ +.timeline-clip { + position: absolute; + top: 4px; + bottom: 4px; + border-radius: 2px; + cursor: grab; + overflow: hidden; + transition: box-shadow 0.15s; + /* Fallback background in case type class is missing */ + background: #3a4050; + border: 1px solid #4a5060; +} + +.timeline-clip.video { + background: #3d5a80; + border: 1px solid #4a6a90; +} + +.timeline-clip.audio { + background: #2d6b4a; + border: 1px solid #3a7a58; +} + +.timeline-clip.text { + background: #5c3d7a; + border: 1px solid #6d4e8a; +} + +.timeline-clip.text .clip-content { + background: rgba(92, 61, 122, 0.9); +} + +.timeline-clip.math-scene { + background: #1f6f68; + border: 1px solid #31a89b; +} + +.timeline-clip.math-scene .clip-content { + background: rgba(18, 79, 75, 0.9); +} + +.timeline-clip.camera, +.timeline-clip.gaussian-splat, +.timeline-clip.model { + background: linear-gradient(180deg, #444b53, #31363d); + border: 1px solid #5f6771; +} + +.timeline-clip.camera .clip-content, +.timeline-clip.gaussian-splat .clip-content, +.timeline-clip.model .clip-content { + background: linear-gradient(to bottom, rgba(8, 10, 12, 0.72) 0%, rgba(8, 10, 12, 0.28) 52%, transparent 100%); +} + +.clip-text-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + background: rgba(255, 255, 255, 0.2); + border-radius: 2px; + font-size: 10px; + font-weight: bold; + margin-right: 4px; + flex-shrink: 0; +} + +.timeline-clip.solid { + border: 1px solid rgba(255, 255, 255, 0.25); +} + +.timeline-clip.solid .clip-content { + background: rgba(0, 0, 0, 0.3); +} + +.timeline-clip.solid .clip-name, +.timeline-clip.solid .clip-duration { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); +} + +.timeline-clip.loading.solid { + opacity: 0.5; +} + +.timeline-clip.loading.text { + background: rgba(147, 51, 234, 0.3) !important; + border-color: rgba(168, 85, 247, 0.8) !important; +} + +/* Loading state for clips - dashed style like drag preview */ +.timeline-clip.loading { + background: rgba(40, 60, 90, 0.85) !important; + border: 2px dashed var(--accent) !important; + box-shadow: none !important; + display: block; +} + +.timeline-clip.loading:not(.entrance-animate):not(.exit-animate) { + /* Override global .loading class which sets transform for centered spinners. */ + transform: none !important; +} + +.timeline-clip.loading.audio { + display: none; +} + +.timeline-clip.loading .clip-thumbnails, +.timeline-clip.loading .clip-waveform { + display: none; +} + +.timeline-clip.loading .clip-content { + background: transparent !important; + height: 100%; + justify-content: center; + align-items: center; + flex-direction: row; + gap: 6px; +} + +.timeline-clip.loading .clip-name-row { + justify-content: center; +} + +.timeline-clip.loading .clip-meta { + align-items: center; + max-width: none; +} + +.timeline-clip.loading .clip-name { + opacity: 0.8; +} + +.timeline-clip.loading .clip-duration { + display: none; +} + +.timeline-clip.loading::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.08), + transparent + ); + animation: clip-loading-shimmer 1.5s infinite; +} + +@keyframes clip-loading-shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Clip entrance animation on composition switch - GPU accelerated */ +@keyframes clip-entrance-from-left { + from { + opacity: 1; + transform: translate3d(-72px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes clip-entrance-from-right { + from { + opacity: 1; + transform: translate3d(72px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +/* Clip exit animation - flies out without fading */ +@keyframes clip-exit-right { + from { + opacity: 1; + transform: translate3d(0, 0, 0); + } + to { + opacity: 1; + transform: translate3d(calc(140vw + 100%), 0, 0); + } +} + +@keyframes clip-exit-left { + from { + opacity: 1; + transform: translate3d(0, 0, 0); + } + to { + opacity: 1; + transform: translate3d(calc(-140vw - 100%), 0, 0); + } +} + +.timeline-clip.entrance-animate-left { + animation: clip-entrance-from-left 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +.timeline-clip.entrance-animate-right { + animation: clip-entrance-from-right 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +.timeline-clip.exit-animate-right { + animation: clip-exit-right 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; +} + +.timeline-clip.exit-animate-left { + animation: clip-exit-left 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; +} + +.clip-loading-spinner { + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: var(--text-on-accent); + border-radius: 50%; + animation: clip-spinner 0.8s linear infinite; + margin-right: 6px; + flex-shrink: 0; +} + +/* Pending download state for YouTube clips */ +.timeline-clip.pending-download { + background: linear-gradient(180deg, rgba(255, 0, 0, 0.2), rgba(180, 0, 0, 0.15)) !important; + border: 2px dashed #ff0000 !important; + box-shadow: none !important; +} + +.timeline-clip.pending-download .clip-thumbnails, +.timeline-clip.pending-download .clip-waveform { + display: none; +} + +.timeline-clip.pending-download .clip-content { + background: transparent !important; +} + +.timeline-clip.pending-download .clip-name { + opacity: 0.9; +} + +/* YouTube thumbnail preview in pending clip */ +.clip-youtube-preview { + position: absolute; + inset: 0; + background-size: cover; + background-position: center; + opacity: 0.5; + z-index: 1; +} + +/* Download progress bar at bottom */ +.clip-download-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4px; + background: rgba(0, 0, 0, 0.5); + z-index: 15; +} + +.clip-download-progress-bar { + height: 100%; + background: linear-gradient(90deg, #ff0000, #ff4444); + transition: width 0.2s ease-out; + box-shadow: 0 0 4px rgba(255, 0, 0, 0.6); +} + +/* Download status text */ +.clip-download-status { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.7); + color: var(--text-on-accent); + padding: 4px 8px; + border-radius: 4px; + font-size: 10px; + font-weight: 500; + z-index: 20; + display: flex; + align-items: center; + gap: 6px; +} + +.clip-download-status .download-spinner { + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: #ff0000; + border-radius: 50%; + animation: clip-spinner 0.8s linear infinite; +} + +/* Download error state */ +.timeline-clip.download-error { + background: rgba(239, 68, 68, 0.2) !important; + border: 2px dashed var(--danger) !important; +} + +.clip-download-error-badge { + position: absolute; + top: 2px; + left: 2px; + background: var(--danger); + color: var(--text-on-accent); + font-size: 10px; + font-weight: bold; + padding: 1px 5px; + border-radius: 2px; + z-index: 20; + cursor: help; +} + +/* Proxy progress bar in timeline clips */ +.clip-proxy-progress { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: rgba(0, 0, 0, 0.5); + z-index: 10; +} + +.clip-proxy-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--success), var(--success-light)); + transition: width 0.2s ease-out; + box-shadow: 0 0 4px rgba(34, 197, 94, 0.6); +} + +.clip-proxy-badge { + position: absolute; + top: 2px; + right: 2px; + background: var(--success); + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 4px; + border-radius: 2px; + z-index: 10; +} + +.clip-proxy-error { + position: absolute; + top: 2px; + right: 2px; + background: var(--danger); + color: var(--text-inverse); + font-size: 9px; + font-weight: bold; + padding: 1px 4px; + border-radius: 2px; + z-index: 10; +} + +/* Proxy generating indicator with fill animation */ +.clip-proxy-generating { + position: absolute; + top: 2px; + right: 2px; + display: flex; + align-items: center; + gap: 3px; + z-index: 10; +} + +.clip-proxy-generating .proxy-percent { + font-size: 8px; + color: var(--warning-light); + font-weight: bold; +} + +/* Proxy fill badge - shared styles */ +.proxy-fill-badge { + position: relative; + width: 14px; + height: 14px; + border-radius: 2px; + overflow: hidden; + font-size: 9px; + font-weight: bold; + line-height: 14px; + text-align: center; +} + +.proxy-fill-bg { + position: absolute; + inset: 0; + background: rgba(251, 191, 36, 0.2); + color: rgba(251, 191, 36, 0.4); + display: flex; + align-items: center; + justify-content: center; +} + +.proxy-fill-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: var(--warning-light); + color: var(--text-inverse); + display: flex; + align-items: flex-end; + justify-content: center; + overflow: hidden; + transition: height 0.3s ease-out; +} + +.timeline-clip.has-proxy { + border-color: var(--success) !important; +} + +.timeline-clip.generating-proxy { + border-color: var(--warning-light) !important; +} + +.timeline-clip.proxy-error { + border-color: var(--danger) !important; +} + +/* Loading state takes visual priority over proxy states */ +.timeline-clip.loading.has-proxy, +.timeline-clip.loading.generating-proxy { + border-color: var(--accent) !important; +} + +.timeline-clip.loading .clip-proxy-generating, +.timeline-clip.loading .clip-proxy-badge { + display: none; +} + +/* Reversed clip styles */ +.clip-reversed-badge { + position: absolute; + top: 2px; + right: 20px; + background: #f97316; + color: var(--text-inverse); + font-size: 10px; + font-weight: bold; + padding: 1px 4px; + border-radius: 2px; + z-index: 10; +} + +.timeline-clip.reversed { + border-color: #f97316 !important; +} + +.timeline-clip.reversed .clip-thumbnails { + transform: scaleX(-1); +} + +/* Needs reload indicator - file permission required after refresh */ +.clip-reload-badge { + position: absolute; + top: 2px; + left: 2px; + background: var(--danger); + color: var(--text-on-accent); + font-size: 10px; + font-weight: bold; + padding: 1px 5px; + border-radius: 2px; + z-index: 10; + cursor: help; +} + +.timeline-clip.needs-reload { + opacity: 0.6; + border-color: var(--danger) !important; +} + +.timeline-clip.needs-reload .clip-thumbnails { + filter: grayscale(1); +} + +/* Linked group (multicam) styles */ +.clip-linked-group-badge { + position: absolute; + top: 2px; + right: 38px; + background: var(--purple); + color: var(--text-on-accent); + font-size: 10px; + font-weight: bold; + padding: 1px 4px; + border-radius: 2px; + z-index: 10; +} + +.timeline-clip.linked-group { + border-color: var(--purple) !important; + box-shadow: 0 0 6px rgba(139, 92, 246, 0.4); +} + +/* Transcript badge on timeline clip */ +.clip-transcript-badge { + position: absolute; + bottom: 2px; + right: 20px; + background: var(--accent); + color: var(--text-inverse); + font-size: 8px; + font-weight: bold; + padding: 0px 3px; + border-radius: 2px; + z-index: 10; + pointer-events: none; + line-height: 13px; +} + +/* Analysis badge on timeline clip */ +.clip-analysis-badge { + position: absolute; + bottom: 2px; + right: 2px; + background: #e88a1a; + color: var(--text-inverse); + font-size: 8px; + font-weight: bold; + padding: 0px 3px; + border-radius: 2px; + z-index: 10; + pointer-events: none; + line-height: 13px; +} + +/* Clip badge fill effect (partial coverage) */ +.clip-badge-fill { + background: transparent; + overflow: hidden; + width: 13px; + height: 13px; + padding: 0; +} + +.clip-badge-fill .clip-badge-bg { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.35); + font-size: 8px; + font-weight: bold; +} + +.clip-badge-fill .clip-badge-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + align-items: flex-end; + justify-content: center; + overflow: hidden; + color: var(--text-inverse); + font-size: 8px; + font-weight: bold; +} + +.clip-badge-fill .clip-badge-transcript-fill { + background: var(--accent); +} + +.clip-badge-fill .clip-badge-analysis-fill { + background: #e88a1a; +} + +/* Pick Whip (Layer Parenting) styles */ +.pick-whip { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 6px; + cursor: grab; + opacity: 0.8; + transition: opacity 0.15s, transform 0.15s; + flex-shrink: 0; + pointer-events: auto; /* Override parent's pointer-events: none */ + color: var(--expr-color); /* Yellow/gold color */ +} + +.pick-whip:hover { + opacity: 1; + transform: scale(1.15); + color: var(--expr-color-light); +} + +.pick-whip.dragging { + cursor: grabbing; + opacity: 1; +} + +.pick-whip.has-parent { + opacity: 1; + color: var(--expr-connected); /* Green when connected */ +} + +.pick-whip.has-parent:hover { + color: var(--danger); +} + +.pick-whip-icon { + color: inherit; +} + +/* Parent-child link connection lines */ +/* Cable shadow for 3D depth effect */ +.parent-child-link-shadow { + stroke: rgba(0, 0, 0, 0.4); + stroke-width: 5; + stroke-linecap: round; + filter: blur(2px); + transform: translate(1px, 2px); +} + +/* Main cable - thick yellow like audio cables */ +.parent-child-link { + stroke: var(--expr-color); + stroke-width: 3; + stroke-linecap: round; + fill: none; + filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3)); +} + +/* Cable connectors */ +.parent-child-link-endpoint { + fill: var(--expr-color-dark); + stroke: var(--expr-color-stroke); + stroke-width: 1.5; + filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); +} + +.parent-child-link-start { + fill: var(--expr-color-dark); + stroke: var(--expr-color-stroke); + stroke-width: 1.5; + filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); +} + +/* Hover effects */ +.parent-child-link-group:hover .parent-child-link { + stroke: var(--expr-color-light); + stroke-width: 3.5; +} + +.parent-child-link-group:hover .parent-child-link-shadow { + stroke-width: 6; +} + +.parent-child-link-group:hover .parent-child-link-endpoint, +.parent-child-link-group:hover .parent-child-link-start { + fill: var(--expr-color-light); + stroke: var(--expr-color-dark); + r: 6; +} + +/* Clip with parent indicator */ +.timeline-clip.has-parent { + border-color: var(--expr-color) !important; + box-shadow: 0 0 6px rgba(229, 192, 123, 0.3); +} + +.timeline-clip.has-parent::after { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 3px; + height: 100%; + background: var(--expr-color); + opacity: 0.6; +} + +@keyframes clip-spinner { + to { transform: rotate(360deg); } +} + +/* Transcript word markers */ +.clip-transcript-markers { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4px; + pointer-events: none; + z-index: 5; +} + +.transcript-marker { + position: absolute; + bottom: 0; + height: 4px; + background: var(--warning-light); + border-radius: 1px 1px 0 0; + opacity: 0.9; + pointer-events: auto; + cursor: pointer; + transition: opacity 0.1s, height 0.1s; +} + +.transcript-marker:hover { + opacity: 1; + height: 6px; + background: var(--warning); +} + +/* Transcribing progress indicator */ +.clip-transcribing-indicator { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: rgba(0, 0, 0, 0.4); + z-index: 6; +} + +.transcribing-progress { + height: 100%; + background: linear-gradient(90deg, var(--purple-light), #d946ef); + transition: width 0.2s ease-out; + box-shadow: 0 0 4px rgba(168, 85, 247, 0.6); +} + +/* Analysis overlay - full height graph covering entire clip */ +.clip-analysis-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + pointer-events: none; + overflow: hidden; +} + +.analysis-overlay-canvas { + display: block; + position: absolute; + bottom: 0; + left: 0; +} + +/* Real-time position indicator on analysis overlay */ +.analysis-position-indicator { + position: absolute; + top: 0; + bottom: 0; + width: 1px; + pointer-events: none; + z-index: 10; +} + +.analysis-indicator-line { + position: absolute; + top: 0; + bottom: 0; + width: 1px; + background: rgba(255, 255, 255, 0.8); + box-shadow: 0 0 4px rgba(255, 255, 255, 0.5); +} + +.analysis-indicator-values { + position: absolute; + top: -16px; + left: 4px; + display: flex; + gap: 6px; + font-size: 9px; + font-weight: 600; + white-space: nowrap; +} + +.analysis-indicator-values .focus-value { + color: var(--success-light); + text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); +} + +.analysis-indicator-values .motion-value { + color: #60a5fa; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); +} + +/* Analysis legend labels on clip */ +.analysis-legend-labels { + position: absolute; + top: 2px; + right: 4px; + display: flex; + gap: 8px; + font-size: 8px; + font-weight: 500; + pointer-events: none; + z-index: 4; +} + +.analysis-legend-labels .legend-focus { + color: var(--success-light); + text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); +} + +.analysis-legend-labels .legend-motion { + color: #60a5fa; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); +} + +.analysis-legend-labels .legend-progress { + color: var(--warning-light); + font-weight: bold; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.9); +} + +/* Analyzing progress indicator */ +.clip-analyzing-indicator { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: rgba(0, 0, 0, 0.4); + z-index: 6; +} + +.analyzing-progress { + height: 100%; + background: linear-gradient(90deg, var(--success), var(--success-light)); + transition: width 0.2s ease-out; + box-shadow: 0 0 4px rgba(34, 197, 94, 0.6); +} + +/* Waveform generation progress indicator */ +.clip-waveform-indicator { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4px; + background: rgba(0, 0, 0, 0.4); + z-index: 6; +} + +.waveform-progress { + height: 100%; + background: linear-gradient(90deg, var(--danger), var(--danger-light), var(--danger)); + background-size: 200% 100%; + animation: waveform-progress-pulse 1s ease-in-out infinite; + box-shadow: 0 0 4px rgba(239, 68, 68, 0.6); +} + +@keyframes waveform-progress-pulse { + 0%, 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} + +/* Audio clip generating waveform */ +.timeline-clip.audio.generating-waveform { + border-color: var(--danger) !important; +} + +/* Clip with transcript indicator */ +.timeline-clip.has-transcript::before { + content: 'T'; + position: absolute; + top: 2px; + left: 2px; + background: var(--purple-light); + color: var(--text-on-accent); + font-size: 8px; + font-weight: bold; + padding: 1px 3px; + border-radius: 2px; + z-index: 10; +} + +.timeline-clip.image { + background: #5c3d7a; + border: 1px solid #6d4e8a; +} + +.timeline-clip:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.timeline-clip.selected { + box-shadow: 0 0 0 2px var(--accent-timeline); +} + +/* Nested composition clips always have orange outline */ +.timeline-clip.composition { + box-shadow: 0 0 0 2px #f97316; +} + +.timeline-clip.composition.selected { + box-shadow: 0 0 0 2px #f97316, 0 0 0 4px var(--accent-timeline); +} + +.timeline-clip.dragging { + opacity: 0.9; + cursor: grabbing; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + z-index: 100; +} + +.timeline-clip.linked-dragging { + opacity: 0.9; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + z-index: 99; +} + +/* Visual feedback when forcing overlap - red glow to indicate clip will be trimmed */ +.timeline-clip.forcing-overlap { + box-shadow: 0 0 0 2px var(--danger), 0 4px 12px rgba(239, 68, 68, 0.5); +} + +/* Cut tool mode */ +.timeline-clip.cut-mode { + cursor: crosshair !important; +} + +.timeline-clip.cut-mode:hover { + box-shadow: 0 0 0 1px #f97316; +} + +/* Cut indicator line */ +.cut-indicator { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background: #f97316; + pointer-events: none; + z-index: 100; + box-shadow: 0 0 4px rgba(249, 115, 22, 0.8); +} + +.cut-indicator::before, +.cut-indicator::after { + content: ''; + position: absolute; + left: -4px; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} + +.cut-indicator::before { + top: 0; + border-top: 6px solid #f97316; +} + +.cut-indicator::after { + bottom: 0; + border-bottom: 6px solid #f97316; +} + + +.clip-content { + position: relative; + z-index: 5; + padding: 2px 6px; + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.2) 50%, transparent 100%); + pointer-events: none; + overflow: hidden; +} + +.clip-meta { + display: flex; + flex-direction: column; + gap: 1px; + width: max-content; + max-width: calc(100% - 12px); + min-width: 0; + will-change: transform; +} + +.clip-name-row { + display: flex; + flex-direction: row; + align-items: center; + gap: 2px; + min-width: 0; + width: 100%; +} + +.clip-name { + font-size: 10px; + line-height: 1.2; + font-weight: 600; + color: var(--text-on-accent); + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); +} + +.clip-duration { + font-size: 9px; + color: rgba(255, 255, 255, 0.8); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); +} + +.clip-type-icon { + width: 14px; + height: 14px; + flex-shrink: 0; + color: rgba(235, 239, 244, 0.9); + filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45)); +} + +.clip-static-artwork { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 2; +} + +.clip-static-artwork-icon { + width: 42px; + height: 42px; + max-width: calc(100% - 18px); + max-height: calc(100% - 18px); + color: rgba(205, 211, 217, 0.5); + filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.28)); +} + +/* Clip thumbnails filmstrip */ +.clip-thumbnails { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + overflow: hidden; + opacity: 0.85; +} + +.clip-thumb { + height: 100%; + width: auto; + object-fit: cover; + flex-shrink: 0; + pointer-events: none; +} + +.clip-thumb-placeholder { + height: 100%; + width: 71px; + flex-shrink: 0; + background: #1a1a1a; +} + +.timeline-clip.video .clip-thumbnails, +.timeline-clip.image .clip-thumbnails { + background: rgba(0, 0, 0, 0.3); +} + +.timeline-clip.audio .clip-thumbnails { + background: linear-gradient(180deg, #16a34a, #15803d); +} + +/* Trim handles */ +.trim-handle { + position: absolute; + top: 0; + bottom: 0; + width: 10px; + cursor: ew-resize; + background: rgba(255, 255, 255, 0.2); + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 10; +} + +.trim-handle:hover { + background: rgba(255, 255, 255, 0.5); +} + +.trim-handle.left { + left: 0; + border-radius: 4px 0 0 4px; +} + +.trim-handle.right { + right: 0; + border-radius: 0 4px 4px 0; +} + +/* Trimming state */ +.timeline-clip.trimming, +.timeline-clip.linked-trimming { + z-index: 100; + box-shadow: 0 0 0 2px var(--warning); +} + +.timeline-clip.trimming .trim-handle, +.timeline-clip.linked-trimming .trim-handle { + opacity: 1; + background: var(--warning); +} + +.timeline-clip:hover .trim-handle { + opacity: 1; +} + +/* Fade handles - corner handles for fade-in/out */ +.fade-handle { + position: absolute; + top: 0; + width: 12px; + height: 12px; + cursor: ew-resize; + background: rgba(251, 191, 36, 0.8); + border-radius: 0 0 4px 0; + opacity: 0; + transition: opacity 0.15s, background 0.15s, transform 0.15s; + z-index: 15; +} + +.fade-handle:hover { + background: rgba(251, 191, 36, 1); + transform: scale(1.1); +} + +.fade-handle.left { + left: 0; + border-radius: 0 0 4px 0; +} + +.fade-handle.right { + right: 0; + left: auto; + border-radius: 0 0 0 4px; +} + +/* When fade is active, position handle at end of fade region */ +.fade-handle.left.active { + border-radius: 2px; +} + +.fade-handle.right.active { + border-radius: 2px; +} + +.timeline-clip:hover .fade-handle { + opacity: 1; +} + +.timeline-clip.fading .fade-handle { + opacity: 1; + background: var(--warning); +} + +/* Fade curve container - SVG bezier curve showing opacity animation */ + +.fade-curve-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 5; + overflow: hidden; +} + +.fade-curve-svg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +/* Audio Waveform */ +.clip-waveform { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.waveform-container { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; +} + +.waveform-bar { + position: absolute; + bottom: 50%; + transform: translateY(50%); + background: rgba(34, 197, 94, 0.8); + border-radius: 1px; +} + +.timeline-clip.audio .waveform-bar { + background: rgba(255, 255, 255, 0.6); +} + +.waveform-canvas { + position: absolute; + top: 0; + left: 0; + pointer-events: none; +} + +/* Nested composition mixdown waveform - shown at bottom of clip */ +.clip-mixdown-waveform { + position: absolute; + bottom: 2px; + left: 0; + right: 0; + height: 30px; + overflow: hidden; + opacity: 0.7; + pointer-events: none; + z-index: 5; +} + +.clip-mixdown-waveform .waveform-canvas { + filter: hue-rotate(180deg); /* Different color from regular waveforms */ +} + +/* Nested composition mixdown generating indicator */ +.clip-mixdown-indicator { + position: absolute; + bottom: 4px; + left: 4px; + font-size: 9px; + color: var(--text-secondary); + background: rgba(0, 0, 0, 0.5); + padding: 2px 6px; + border-radius: 3px; + z-index: 6; +} + +/* Nested composition clip boundary markers */ +.nested-clip-boundaries { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 8; +} + +.nested-boundary-line { + position: absolute; + top: 0; + bottom: 0; + width: 1px; + background: rgba(255, 60, 60, 0.8); + box-shadow: 1px 0 0 rgba(255, 255, 255, 0.15); +} + +/* Keyframe tick marks on clip bar */ +.clip-keyframe-ticks { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 8px; + pointer-events: none; + z-index: 6; +} + +.keyframe-tick { + position: absolute; + bottom: -4px; + width: 16px; + height: 16px; + padding: 0; + border: 0; + background: transparent; + appearance: none; + transform: translateX(-50%); + pointer-events: auto; + cursor: grab; + z-index: 1; +} + +.keyframe-tick::after { + content: ''; + position: absolute; + left: 50%; + bottom: 5px; + width: 4px; + height: 4px; + background: var(--warning); + transform: translateX(-50%) rotate(45deg); + opacity: 0.7; + transition: opacity 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease; +} + +.keyframe-tick.state-change::after { + background: var(--cyan-500); +} + +.keyframe-tick:hover::after, +.keyframe-tick:focus-visible::after, +.keyframe-tick.dragging::after { + transform: translateX(-50%) rotate(45deg) scale(2.15); + opacity: 1; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35); +} + +.keyframe-tick:active, +.keyframe-tick.dragging { + cursor: grabbing; +} + +.timeline-clip.selected .keyframe-tick::after { + opacity: 1; +} +} diff --git a/src/components/timeline/TimelineClip.tsx b/src/components/timeline/TimelineClip.tsx index 9cdf48418..ce40263c1 100644 --- a/src/components/timeline/TimelineClip.tsx +++ b/src/components/timeline/TimelineClip.tsx @@ -1,5 +1,6 @@ // TimelineClip component - Clip rendering within tracks +import './TimelineClip.css'; import { memo, useRef, useState, useEffect } from 'react'; import type { TimelineClipProps } from './types'; import { THUMB_WIDTH } from './constants'; diff --git a/src/components/timeline/TimelineControls.css b/src/components/timeline/TimelineControls.css new file mode 100644 index 000000000..260a248e0 --- /dev/null +++ b/src/components/timeline/TimelineControls.css @@ -0,0 +1,186 @@ +@layer features { +.timeline-toolbar { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + flex-shrink: 0; + min-height: 30px; +} + +.timeline-controls { + display: flex; + gap: 4px; +} + +.timeline-time { + font-family: monospace; + font-size: 12px; + color: var(--text-primary); + background: var(--bg-primary); + padding: 3px 6px; + border-radius: 2px; + min-width: 140px; + text-align: center; +} + +.duration-display { + cursor: pointer; + padding: 1px 4px; + border-radius: 2px; + transition: background 0.15s; +} + +.duration-display:hover { + background: var(--accent-primary); + color: var(--text-on-accent); +} + +.duration-input { + font-family: monospace; + font-size: 12px; + width: 70px; + padding: 1px 4px; + border: 1px solid var(--accent-primary); + border-radius: 2px; + background: var(--bg-secondary); + color: var(--text-primary); + text-align: center; + outline: none; +} + +.timeline-zoom { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--text-secondary); +} + +.timeline-zoom .btn-icon { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + padding: 0; + border-radius: 2px; + color: var(--text-secondary); + opacity: 0.6; +} + +.timeline-zoom .btn-icon:hover { + opacity: 1; + background: var(--bg-hover); +} + +.timeline-zoom .btn-icon.btn-active { + color: var(--accent); + opacity: 1; + background: var(--accent-dim); +} + +.timeline-inout-controls { + display: flex; + align-items: center; + gap: 4px; +} + +.timeline-inout-controls .btn-sm { + min-width: 24px; + padding: 4px 8px; + font-weight: bold; +} + +.timeline-inout-controls .btn-active { + background: var(--warning); + color: var(--text-on-accent); +} + +.timeline-tracks-controls { + margin-left: auto; + display: flex; + gap: 8px; +} + +/* RAM Preview */ +.timeline-ram-preview { + display: flex; + align-items: center; + gap: 6px; +} + +.timeline-slot-toggle { + display: flex; + align-items: center; + margin-right: 4px; +} + +.ram-preview-progress { + width: 80px; + height: 6px; + background: var(--bg-primary); + border-radius: 3px; + overflow: hidden; +} + +.ram-preview-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--success), var(--success-light)); + border-radius: 3px; + transition: width 0.1s ease-out; +} + +/* View dropdown */ +.view-dropdown { + position: relative; +} + +.view-dropdown .btn { + display: flex; + align-items: center; +} + +.view-dropdown-menu { + position: absolute; + top: 100%; + left: 0; + margin-top: 4px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 4px 0; + min-width: 160px; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.view-dropdown-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + cursor: pointer; + font-size: 12px; + color: var(--text-primary); + transition: background 0.1s; +} + +.view-dropdown-item:hover { + background: var(--bg-tertiary); +} + +.view-check { + width: 14px; + font-size: 11px; + text-align: center; + color: transparent; + flex-shrink: 0; +} + +.view-check.checked { + color: var(--accent-color, #4a9eff); +} +} diff --git a/src/components/timeline/TimelineControls.tsx b/src/components/timeline/TimelineControls.tsx index f42d6de41..0ebdcf0b8 100644 --- a/src/components/timeline/TimelineControls.tsx +++ b/src/components/timeline/TimelineControls.tsx @@ -1,6 +1,7 @@ // TimelineControls component - Playback controls and toolbar import { memo, useState, useRef, useEffect, useCallback } from 'react'; +import './TimelineControls.css'; import type { TimelineControlsProps } from './types'; import { useSettingsStore } from '../../stores/settingsStore'; import { engine } from '../../engine/WebGPUEngine'; diff --git a/src/components/timeline/TimelineInteractions.css b/src/components/timeline/TimelineInteractions.css new file mode 100644 index 000000000..b6c2ecc42 --- /dev/null +++ b/src/components/timeline/TimelineInteractions.css @@ -0,0 +1,483 @@ +@layer features { +/* ============================================ + AI Action Visual Feedback Animations + ============================================ */ + +/* Split Glow - vertical blue glow line at cut position */ +@keyframes ai-split-glow-fade { + 0% { + opacity: 0; + transform: scaleY(0.8); + box-shadow: 0 0 0 0 transparent; + } + 5% { + opacity: 1; + transform: scaleY(1); + box-shadow: 0 0 16px 6px rgba(59, 130, 246, 0.95), 0 0 32px 12px rgba(59, 130, 246, 0.5); + } + 30% { + opacity: 1; + transform: scaleY(1); + box-shadow: 0 0 20px 8px rgba(59, 130, 246, 0.8), 0 0 40px 16px rgba(59, 130, 246, 0.35); + } + 100% { + opacity: 0; + transform: scaleY(0.95); + box-shadow: 0 0 0 0 transparent; + } +} + +.ai-split-glow { + position: absolute; + width: 3px; + margin-left: -1.5px; + background: #60a5fa; + pointer-events: none; + z-index: 200; + opacity: 0; + animation: ai-split-glow-fade 1s cubic-bezier(0.22, 1, 0.36, 1) both; + box-shadow: 0 0 16px 6px rgba(59, 130, 246, 0.95), 0 0 32px 12px rgba(59, 130, 246, 0.5); +} + +.ai-split-glow::before, +.ai-split-glow::after { + content: ''; + position: absolute; + left: -4px; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} + +.ai-split-glow::before { + top: 0; + border-top: 6px solid #3b82f6; +} + +.ai-split-glow::after { + bottom: 0; + border-bottom: 6px solid #3b82f6; +} + +/* Delete Ghost - fading red outline at old clip position */ +@keyframes ai-delete-fade { + 0% { + opacity: 0.8; + transform: scale(1); + } + 100% { + opacity: 0; + transform: scale(0.97) translateY(3px); + } +} + +.ai-delete-ghost { + position: absolute; + border-radius: 4px; + background: rgba(239, 68, 68, 0.25); + border: 2px dashed rgba(239, 68, 68, 0.7); + pointer-events: none; + z-index: 199; + animation: ai-delete-fade 0.6s cubic-bezier(0.55, 0, 1, 0.45) both; + display: flex; + align-items: center; + padding: 0 8px; + box-shadow: 0 0 12px rgba(239, 68, 68, 0.4); +} + +.ai-delete-ghost .ghost-name { + color: rgba(255, 255, 255, 0.5); + font-size: 10px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-decoration: line-through; +} + +/* Trim Highlight - green edge glow at trimmed side */ +@keyframes ai-trim-pulse { + 0% { + opacity: 1; + box-shadow: 0 0 10px 3px rgba(34, 197, 94, 0.8); + } + 100% { + opacity: 0; + box-shadow: 0 0 0 0 transparent; + } +} + +.ai-trim-highlight { + position: absolute; + width: 4px; + margin-left: -2px; + background: #22c55e; + pointer-events: none; + z-index: 200; + animation: ai-trim-pulse 0.8s ease-out both; + box-shadow: 0 0 14px 4px rgba(34, 197, 94, 0.85); + border-radius: 1px; +} + +/* Silent Zone - yellow/amber highlight for detected silent sections */ +@keyframes ai-silent-zone-fade { + 0% { + opacity: 0.7; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 0; + } +} + +.ai-silent-zone { + position: absolute; + border-radius: 3px; + background: repeating-linear-gradient( + -45deg, + rgba(251, 191, 36, 0.2), + rgba(251, 191, 36, 0.2) 4px, + rgba(251, 191, 36, 0.1) 4px, + rgba(251, 191, 36, 0.1) 8px + ); + border: 1.5px dashed rgba(251, 191, 36, 0.6); + pointer-events: none; + z-index: 198; + animation: ai-silent-zone-fade 2s ease-out both; + box-shadow: inset 0 0 8px rgba(251, 191, 36, 0.3); +} + +/* Low Quality Zone - red/orange highlight for detected low quality sections */ +@keyframes ai-low-quality-zone-fade { + 0% { + opacity: 0.7; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 0; + } +} + +.ai-low-quality-zone { + position: absolute; + border-radius: 3px; + background: repeating-linear-gradient( + -45deg, + rgba(239, 68, 68, 0.2), + rgba(239, 68, 68, 0.2) 4px, + rgba(239, 68, 68, 0.1) 4px, + rgba(239, 68, 68, 0.1) 8px + ); + border: 1.5px dashed rgba(239, 68, 68, 0.6); + pointer-events: none; + z-index: 198; + animation: ai-low-quality-zone-fade 2s ease-out both; + box-shadow: inset 0 0 8px rgba(239, 68, 68, 0.3); +} + +/* AI Move Animation - blue shadow during FLIP transition */ +.timeline-clip.ai-moving { + z-index: 100; + box-shadow: 0 2px 12px rgba(59, 130, 246, 0.4), 0 0 0 1px rgba(59, 130, 246, 0.3); +} + +/* ============================================ + AI Preview Canvas Flash Overlays + ============================================ */ + +/* Base flash overlay */ +.ai-preview-flash { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 500; + border-radius: inherit; +} + +/* Capture frame shutter effect - white flash */ +@keyframes ai-shutter-flash { + 0% { + opacity: 0.9; + background: white; + } + 40% { + opacity: 0.3; + background: white; + } + 100% { + opacity: 0; + background: transparent; + } +} + +.ai-preview-flash-shutter { + animation: ai-shutter-flash 0.4s ease-out both; +} + +/* Undo flash - brief orange border pulse */ +@keyframes ai-undo-flash { + 0% { + box-shadow: inset 0 0 0 3px rgba(251, 146, 60, 0.8); + background: rgba(251, 146, 60, 0.1); + } + 100% { + box-shadow: inset 0 0 0 0 transparent; + background: transparent; + } +} + +.ai-preview-flash-undo { + animation: ai-undo-flash 0.6s ease-out both; +} + +/* Redo flash - brief green border pulse */ +@keyframes ai-redo-flash { + 0% { + box-shadow: inset 0 0 0 3px rgba(34, 197, 94, 0.8); + background: rgba(34, 197, 94, 0.1); + } + 100% { + box-shadow: inset 0 0 0 0 transparent; + background: transparent; + } +} + +.ai-preview-flash-redo { + animation: ai-redo-flash 0.6s ease-out both; +} + +/* Import flash - brief blue border pulse */ +@keyframes ai-import-flash { + 0% { + box-shadow: inset 0 0 0 3px rgba(59, 130, 246, 0.8); + background: rgba(59, 130, 246, 0.1); + } + 100% { + box-shadow: inset 0 0 0 0 transparent; + background: transparent; + } +} + +.ai-preview-flash-import { + animation: ai-import-flash 0.6s ease-out both; +} + +/* Premiere-style drag feedback */ +.timeline-tracks.dragging-clip { + cursor: grabbing; + user-select: none; +} + +/* Marquee selection */ +.timeline-tracks.marquee-selecting { + cursor: crosshair; +} + +.marquee-selection { + position: absolute; + background: rgba(59, 130, 246, 0.15); + border: 1px solid rgba(59, 130, 246, 0.6); + border-radius: 2px; + pointer-events: none; + z-index: 1000; +} + +/* Disable all text selection in timeline */ +.timeline-container, +.timeline-container * { + user-select: none !important; + -webkit-user-select: none !important; +} + +.track-lane.drag-target { + background: rgba(59, 130, 246, 0.15); +} + +.track-lane.external-drag-target { + background: rgba(34, 197, 94, 0.15); +} + +/* External file drag preview */ +.timeline-clip-preview { + position: absolute; + top: 4px; + bottom: 4px; + border-radius: 2px; + background: rgba(34, 197, 94, 0.5); + border: 2px dashed var(--success); + pointer-events: none; + display: flex; + align-items: center; + justify-content: center; +} + +.timeline-clip-preview .clip-content { + padding: 4px 8px; +} + +.timeline-clip-preview .clip-name { + color: var(--text-on-accent); + font-size: 11px; + font-weight: 500; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); +} + +.timeline-clip-preview.audio { + background: rgba(59, 130, 246, 0.5); + border-color: rgba(59, 130, 246, 0.8); +} + +.track-lane.new-track-preview { + background: rgba(59, 130, 246, 0.1); + border: 2px dashed rgba(59, 130, 246, 0.5); + border-radius: 2px; + margin-top: 4px; + position: relative; +} + +/* New track drop zones - appear when dragging media */ +.new-track-drop-zone { + height: 36px; + margin-top: 4px; + border: 2px dashed rgba(255, 255, 255, 0.3); + border-radius: 4px; + background: rgba(255, 255, 255, 0.05); + display: flex; + align-items: center; + justify-content: center; + position: relative; + transition: all 0.15s ease; +} + +.new-track-drop-zone .drop-zone-label { + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + font-weight: 500; + pointer-events: none; + z-index: 1; +} + +.new-track-drop-zone.video { + height: 60px; /* Match default video track height */ + border-color: rgba(37, 99, 235, 0.4); +} + +.new-track-drop-zone.audio { + height: 40px; /* Match default audio track height */ + border-color: rgba(22, 163, 74, 0.4); +} + +.new-track-drop-zone.active, +.new-track-drop-zone:hover { + background: rgba(255, 255, 255, 0.1); +} + +.new-track-drop-zone.video.active, +.new-track-drop-zone.video:hover { + border-color: rgba(37, 99, 235, 0.8); + background: rgba(37, 99, 235, 0.15); +} + +.new-track-drop-zone.audio.active, +.new-track-drop-zone.audio:hover { + border-color: rgba(22, 163, 74, 0.8); + background: rgba(22, 163, 74, 0.15); +} + +.new-track-drop-zone.active .drop-zone-label, +.new-track-drop-zone:hover .drop-zone-label { + color: rgba(255, 255, 255, 0.8); +} + +.new-track-drop-zone .timeline-clip-preview { + position: absolute; + top: 4px; + bottom: 4px; + border-radius: 3px; + pointer-events: none; +} + +/* Snap indicator line */ +.snap-line { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background: var(--success, #4ade80); + z-index: 99; + pointer-events: none; + box-shadow: 0 0 8px var(--success, #4ade80); +} + +.snap-line-guide { + opacity: 0.35; + box-shadow: none; +} + + +/* Timeline export progress overlay */ +.timeline-export-overlay { + position: absolute; + top: 0; + height: 500px; + background: rgba(250, 204, 21, 0.4); + border-right: 3px solid rgba(250, 204, 21, 1); + pointer-events: none; + z-index: 200; +} + +/* Timeline export percentage */ +.timeline-export-text { + position: absolute; + top: 50px; + font-size: 24px; + font-weight: bold; + color: rgba(250, 204, 21, 0.9); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.6); + pointer-events: none; + z-index: 201; + white-space: nowrap; +} + +/* RAM Preview cached range indicator (green = fully cached) */ +.ram-preview-indicator { + position: absolute; + top: 0; + height: 4px; + background: linear-gradient(90deg, var(--success), var(--success-light)); + opacity: 0.8; + pointer-events: none; + z-index: 90; +} + +/* RAM Preview progress text (during rendering) */ +.ram-preview-progress-text { + position: absolute; + top: 6px; + font-size: 10px; + font-weight: bold; + color: var(--success); + background: var(--bg-overlay); + padding: 2px 6px; + border-radius: 3px; + pointer-events: none; + z-index: 95; + white-space: nowrap; +} + +/* Playback cache indicator (green line for frames cached during playback) */ +.playback-cache-indicator { + position: absolute; + top: 0; + height: 3px; + background: var(--success); + opacity: 0.6; + pointer-events: none; + z-index: 89; +} +} diff --git a/src/components/timeline/TimelineKeyframes.css b/src/components/timeline/TimelineKeyframes.css new file mode 100644 index 000000000..0955af750 --- /dev/null +++ b/src/components/timeline/TimelineKeyframes.css @@ -0,0 +1,716 @@ +@layer features { + +/* ========== KEYFRAME DIAMONDS (Timeline) ========== */ +.keyframe-diamond { + position: absolute; + width: 8px; + height: 8px; + background: var(--amber-600); + color: var(--amber-600); + /* Center diamond horizontally on keyframe time position */ + transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%); + top: 50%; + cursor: pointer; + z-index: 5; + /* Only transition visual properties, not position (left) - for smooth clip dragging */ + transition: background 0.1s ease, box-shadow 0.1s ease, filter 700ms ease, transform 0.1s ease; +} + +.keyframe-diamond:hover { + background: var(--warning-light); + color: var(--warning-light); + box-shadow: 0 0 6px var(--amber-600); + transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%) scale(1.2); +} + +.keyframe-diamond.selected { + background: var(--red-500); + color: var(--red-500); + box-shadow: 0 0 8px var(--red-500); +} + +.keyframe-diamond.state-change { + background: var(--cyan-500); + color: var(--cyan-500); + box-shadow: 0 0 5px rgba(6, 182, 212, 0.45); +} + +.keyframe-diamond.state-change:hover, +.keyframe-diamond.state-change.selected { + background: #36b8ff; + box-shadow: 0 0 8px rgba(54, 184, 255, 0.75); +} + +.keyframe-diamond.dragging { + background: var(--cyan-500); + color: var(--cyan-500); + box-shadow: 0 0 10px var(--cyan-500); + transform: translateX(calc(-50% - 3px)) rotate(45deg) translateY(-50%) scale(1.3); +} + +.keyframe-diamond.row-highlighted { + filter: drop-shadow(0 0 5px rgba(245, 158, 11, 0.85)) brightness(1.35) saturate(1.15); + transition: background 0.1s ease, box-shadow 0.1s ease, filter 120ms ease, transform 0.1s ease; +} + +/* Keyframe easing type visual styles */ +/* Linear: Diamond shape (default) */ +.keyframe-diamond.easing-linear { + border-radius: 0; +} + +/* Ease-in: Left half rounded (slow start) */ +.keyframe-diamond.easing-ease-in { + border-radius: 50% 0 0 50%; + background: linear-gradient(135deg, var(--purple-light) 0%, var(--amber-600) 100%); +} + +.keyframe-diamond.easing-ease-in:hover { + background: linear-gradient(135deg, var(--purple-300) 0%, var(--warning-light) 100%); + color: var(--warning-light); +} + +.keyframe-diamond.easing-ease-in.selected { + background: linear-gradient(135deg, var(--red-500) 0%, var(--danger-light) 100%); + color: var(--red-500); +} + +/* Ease-out: Right half rounded (slow end) */ +.keyframe-diamond.easing-ease-out { + border-radius: 0 50% 50% 0; + background: linear-gradient(135deg, var(--amber-600) 0%, var(--cyan-500) 100%); +} + +.keyframe-diamond.easing-ease-out:hover { + background: linear-gradient(135deg, var(--warning-light) 0%, var(--cyan-400) 100%); + color: var(--warning-light); +} + +.keyframe-diamond.easing-ease-out.selected { + background: linear-gradient(135deg, var(--danger-light) 0%, var(--red-500) 100%); + color: var(--red-500); +} + +/* Ease-in-out: Fully rounded (circle/pill) */ +.keyframe-diamond.easing-ease-in-out { + border-radius: 50%; + background: linear-gradient(135deg, var(--purple-light) 0%, var(--cyan-500) 100%); +} + +.keyframe-diamond.easing-ease-in-out:hover { + background: linear-gradient(135deg, var(--purple-300) 0%, var(--cyan-400) 100%); + color: var(--cyan-400); +} + +.keyframe-diamond.easing-ease-in-out.selected { + background: linear-gradient(135deg, var(--red-500) 0%, var(--danger-light) 100%); + color: var(--red-500); +} + +.keyframe-diamond.state-change, +.keyframe-diamond.state-change.easing-linear, +.keyframe-diamond.state-change.easing-ease-in, +.keyframe-diamond.state-change.easing-ease-out, +.keyframe-diamond.state-change.easing-ease-in-out { + background: var(--cyan-500); +} + +.keyframe-diamond.state-change:hover, +.keyframe-diamond.state-change.selected { + background: #36b8ff; + color: #36b8ff; +} + +.keyframe-rotation-path-label { + position: absolute; + left: 10px; + top: -5px; + display: block; + transform: rotate(-45deg); + transform-origin: center; + color: currentColor; + font-size: 6px; + font-weight: 700; + line-height: 1; + letter-spacing: 0; + opacity: 0.78; + pointer-events: none; + text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); + transition: opacity 0.1s ease; +} + +.keyframe-diamond:hover .keyframe-rotation-path-label { + opacity: 1; +} + +/* Keyframe context menu */ +.keyframe-context-menu { + background: var(--bg-tertiary); + border: 1px solid var(--gray-450); + border-radius: 6px; + padding: 4px 0; + min-width: 140px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); +} + +.keyframe-context-menu .context-menu-title { + padding: 6px 12px; + font-size: 11px; + color: var(--gray-200); + border-bottom: 1px solid var(--gray-450); + margin-bottom: 4px; +} + +.keyframe-context-menu .context-menu-subtitle { + padding: 7px 12px 4px; + margin-top: 4px; + border-top: 1px solid var(--gray-450); + font-size: 10px; + color: var(--text-secondary); + text-transform: uppercase; +} + +.keyframe-context-menu .context-menu-item { + padding: 6px 12px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: var(--text-primary); +} + +.keyframe-context-menu .context-menu-item:hover { + background: var(--bg-active); +} + +.keyframe-context-menu .context-menu-item.active { + color: var(--amber-600); +} + +.keyframe-context-menu .context-menu-item .checkmark { + color: var(--success); + font-size: 14px; +} + +/* ========== EXPANDABLE CLIP PROPERTIES (Timeline) ========== */ +.clip-expand-arrow { + cursor: pointer; + transition: transform 0.2s ease; + color: var(--text-secondary); + font-size: 10px; + margin-right: 4px; +} + +.clip-expand-arrow:hover { + color: var(--text-primary); +} + +.clip-expand-arrow.expanded { + transform: rotate(90deg); +} + +.clip-expand-arrow.has-keyframes { + color: var(--amber-600); +} + +.timeline-clip.has-keyframes .clip-content { + border-left: 2px solid var(--amber-600); +} + +.timeline-clip.expanded { + overflow: visible; + z-index: 10; +} + +.clip-property-rows { + position: absolute; + left: 0; + right: 0; + top: 100%; + background: rgba(0, 0, 0, 0.85); + border: 1px solid var(--border-color); + border-top: none; + border-radius: 0 0 4px 4px; + z-index: 3; + overflow: hidden; +} + +.property-group { + border-bottom: 1px solid var(--border-color); +} + +.property-group:last-child { + border-bottom: none; +} + +.property-group-header { + display: flex; + align-items: center; + height: 18px; + padding: 0 8px; + margin: 0; + box-sizing: border-box; + cursor: pointer; + background: var(--bg-tertiary); + font-size: 10px; + color: var(--text-secondary); + user-select: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.property-group-header:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.property-group-arrow { + margin-right: 4px; + transition: transform 0.15s ease; +} + +.property-group-arrow.expanded { + transform: rotate(90deg); +} + +.property-row { + display: flex; + align-items: center; + height: 18px; + padding: 0 8px 0 20px; + background: var(--bg-secondary); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.property-row:last-child { + border-bottom: none; +} + +.property-label { + width: 50px; + font-size: 9px; + color: var(--text-secondary); + text-transform: uppercase; + flex-shrink: 0; +} + +.keyframe-track { + flex: 1; + height: 100%; + position: relative; +} + +/* In track-level keyframe rows, keyframe-track should fill the full width */ +.keyframe-track-row .keyframe-track { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.keyframe-track-line { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: rgba(255, 255, 255, 0.1); +} + +/* ========== TRACK-BASED KEYFRAME EXPANSION ========== */ +.track-header-top { + display: flex; + align-items: center; + justify-content: flex-start; + padding: 0 8px; + padding-right: 30px; + margin: 0; + box-sizing: border-box; + position: relative; + width: 100%; +} + +.track-header-main { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} + +.track-expand-arrow { + cursor: pointer; + transition: transform 0.2s ease; + color: var(--text-secondary); + font-size: 10px; + flex-shrink: 0; +} + +.track-expand-arrow:hover { + color: var(--text-primary); +} + +.track-expand-arrow.expanded { + transform: rotate(90deg); +} + +.track-expand-arrow.has-keyframes { + color: var(--amber-600); +} + +.track-header.expanded { + flex-direction: column; + align-items: stretch; + padding: 0; +} + +.track-header.expanded .track-header-top { + flex-shrink: 0; +} + +.track-clip-row { + position: relative; + width: 100%; + flex-shrink: 0; + margin: 0; + padding: 0; + box-sizing: border-box; + overflow: hidden; +} + +/* Property labels in track header (left column) */ +.track-property-labels { + background: rgba(0, 0, 0, 0.4); + border-top: 1px solid var(--border-color); + width: 100%; + box-sizing: border-box; + margin: 0; + padding: 0; +} + +.property-label-row { + height: 18px; + display: flex; + align-items: center; + padding: 0 8px; + box-sizing: border-box; + font-size: 9px; + color: var(--text-secondary); + text-transform: uppercase; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.property-label-row.sub { + padding-left: 20px; +} + +.property-label-row.flat { + padding-left: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + align-items: center; + gap: 4px; +} + +.property-label-row.flat .property-label { + flex-shrink: 0; + min-width: 45px; +} + +.property-label-row.flat.dragging { + background: rgba(45, 140, 235, 0.1); +} + +.property-label-row.flat.keyframe-row-highlighted { + background: rgba(245, 158, 11, 0.12); + color: var(--text-primary); + transition: background 120ms ease, color 120ms ease; +} + +.property-label-row.flat:not(.keyframe-row-highlighted) { + transition: background 700ms ease, color 700ms ease; +} + +.property-keyframe-controls { + display: flex; + align-items: center; + gap: 1px; + margin-left: auto; +} + +.kf-nav-btn, .kf-add-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 8px; + padding: 2px 3px; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.1s; +} + +.kf-nav-btn:hover, .kf-add-btn:hover { + opacity: 1; + color: var(--accent-timeline); +} + +.kf-nav-btn.disabled { + opacity: 0.2; + cursor: default; +} + +.kf-add-btn { + font-size: 10px; + color: var(--text-secondary); +} + +.kf-add-btn.has-keyframe { + color: var(--accent-timeline); + opacity: 1; +} + +.property-value { + font-size: 9px; + color: var(--accent-timeline); + font-family: monospace; + min-width: 40px; + text-align: right; + cursor: ns-resize; + padding: 0 4px; + border-radius: 2px; + user-select: none; +} + +.property-value:hover { + background: rgba(45, 140, 235, 0.1); +} + +.property-label-group { + border-bottom: 1px solid var(--border-color); + box-sizing: border-box; + margin: 0; + padding: 0; +} + +.property-label-group:last-child { + border-bottom: none; +} + +/* Keyframe tracks in timeline area (right column) */ +.track-property-tracks { + background: rgba(0, 0, 0, 0.4); + border-top: 1px solid var(--border-color); + position: relative; + width: 100%; + box-sizing: border-box; + /* Align with left column */ + margin-top: 1px; + padding: 0; +} + +.keyframe-track-row { + height: 18px; + position: relative; + box-sizing: border-box; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + /* Ensure track row starts at left edge */ + margin: 0; + padding: 0; +} + +.keyframe-track-row.group-header { + background: var(--bg-tertiary); +} + +.keyframe-track-row.flat { + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.keyframe-track-group { + border-bottom: 1px solid var(--border-color); + box-sizing: border-box; + margin: 0; + padding: 0; +} + +.keyframe-track-group:last-child { + border-bottom: none; +} + +.track-lane.expanded { + display: flex; + flex-direction: column; + align-items: stretch; + overflow: visible; +} + +/* ========== CURVE EDITOR ========== */ +.curve-editor-svg { + display: block; + background: rgba(0, 0, 0, 0.3); + border-top: 1px solid var(--border-color); +} + +.curve-editor-grid { + stroke: var(--border-color); + stroke-width: 0.5; + opacity: 0.3; +} + +.curve-editor-grid-major { + stroke: var(--border-color); + stroke-width: 1; + opacity: 0.5; +} + +.curve-editor-curve { + fill: none; + stroke: var(--amber-600); + stroke-width: 2; +} + +.curve-editor-curve.step { + stroke: var(--cyan-500); + stroke-linejoin: miter; +} + +.curve-editor-keyframe { + fill: var(--amber-600); + stroke: var(--bg-primary); + stroke-width: 2; + cursor: pointer; + transition: r 0.1s ease; +} + +.curve-editor-keyframe:hover { + r: 6; +} + +.curve-editor-keyframe.selected { + fill: var(--red-500); + stroke: var(--gray-0); +} + +.curve-editor-keyframe.state-change { + fill: var(--cyan-500); +} + +.curve-editor-keyframe.state-change.selected { + fill: #36b8ff; +} + +.curve-editor-handle { + fill: var(--cyan-500); + stroke: var(--bg-primary); + stroke-width: 1; + cursor: pointer; +} + +.curve-editor-handle:hover { + fill: var(--cyan-400); +} + +.curve-editor-handle-line { + stroke: var(--cyan-500); + stroke-width: 1; + opacity: 0.6; +} + +.curve-editor-value-label { + font-size: 9px; + fill: var(--text-secondary); +} + +/* Curve editor header (left side Y-axis) */ +.curve-editor-header { + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + background: rgba(0, 0, 0, 0.3); + border-top: 1px solid var(--border-color); +} + +.curve-editor-close-btn { + position: absolute; + top: 2px; + right: 2px; + width: 16px; + height: 16px; + padding: 0; + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 14px; + line-height: 1; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.1s; +} + +.curve-editor-close-btn:hover { + opacity: 1; + color: var(--text-primary); +} + +.curve-editor-y-axis { + position: relative; + width: 100%; + height: 100%; +} + +.curve-editor-tick { + position: absolute; + right: 4px; + transform: translateY(-50%); +} + +.curve-editor-tick-label { + font-size: 9px; + color: var(--text-secondary); + font-family: monospace; +} + +/* Property row when curve is expanded */ +.property-label-row.flat.curve-expanded { + background: rgba(245, 158, 11, 0.1); + border-left: 2px solid var(--amber-600); +} + +/* Keyframe track row when curve is expanded */ +.keyframe-track-row.flat.curve-expanded { + height: auto; + flex-direction: column; +} + +.keyframe-track-row.flat.curve-expanded .keyframe-track { + position: relative; + height: 18px; + flex-shrink: 0; +} + +/* ============================================ + AI Keyframe Animations + ============================================ */ + +/* Keyframe diamond pop animation */ +@keyframes ai-keyframe-pop { + 0% { + transform: scale(0) rotate(45deg); + opacity: 0; + } + 60% { + transform: scale(1.4) rotate(45deg); + opacity: 1; + } + 100% { + transform: scale(1) rotate(45deg); + opacity: 1; + } +} + +.keyframe-diamond.ai-keyframe-added { + animation: ai-keyframe-pop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +} diff --git a/src/components/timeline/TimelineKeyframes.tsx b/src/components/timeline/TimelineKeyframes.tsx index ed22a8f2b..706c0a4ec 100644 --- a/src/components/timeline/TimelineKeyframes.tsx +++ b/src/components/timeline/TimelineKeyframes.tsx @@ -8,6 +8,7 @@ import type { EasingType, AnimatableProperty, RotationInterpolationMode } from ' import { useContextMenuPosition } from '../../hooks/useContextMenuPosition'; import { normalizeEasingType } from '../../utils/easing'; import { parseVectorAnimationStateProperty } from '../../types/vectorAnimation'; +import './TimelineKeyframes.css'; interface KeyframeData { id: string; diff --git a/src/components/timeline/TimelineMarkers.css b/src/components/timeline/TimelineMarkers.css new file mode 100644 index 000000000..577dd2aa7 --- /dev/null +++ b/src/components/timeline/TimelineMarkers.css @@ -0,0 +1,433 @@ +@layer features { +/* ============================================ + AI Marker Animations + ============================================ */ + +/* Marker pop-in animation */ +@keyframes ai-marker-pop { + 0% { + transform: scaleY(0); + opacity: 0; + } + 60% { + transform: scaleY(1.2); + opacity: 1; + } + 100% { + transform: scaleY(1); + opacity: 1; + } +} + +.timeline-marker.ai-marker-added { + animation: ai-marker-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; + transform-origin: bottom center; +} + +/* Marker fade-out animation */ +@keyframes ai-marker-fade { + 0% { + opacity: 1; + transform: scaleY(1); + } + 100% { + opacity: 0; + transform: scaleY(0); + } +} + +.timeline-marker.ai-marker-removed { + animation: ai-marker-fade 0.3s ease-in both; + transform-origin: bottom center; +} + +/* Ruler Marker Button (M) */ +.ruler-marker-btn { + position: absolute; + right: 6px; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 2px; + color: var(--text-secondary); + font-size: 10px; + font-weight: bold; + cursor: grab; + transition: all 0.15s ease; + z-index: 10; +} + +.ruler-marker-btn:hover { + background: var(--accent-timeline); + border-color: var(--accent-timeline); + color: var(--text-on-accent); + box-shadow: 0 0 8px var(--accent-timeline); +} + +.ruler-marker-btn.dragging { + background: var(--accent-timeline); + border-color: var(--accent-timeline); + color: var(--text-on-accent); + cursor: grabbing; + transform: translateY(-50%) scale(1.15); + box-shadow: 0 0 12px var(--accent-timeline); +} + +/* Playhead */ +.playhead { + position: absolute; + top: 0; + width: 2px; + min-height: 100%; + height: max-content; + z-index: 30; + pointer-events: none; + transform: translateX(-1px); + --timeline-switch-base-x: -1px; +} + +.playhead-head { + width: 14px; + height: 14px; + background: var(--accent-timeline); + border-radius: 2px; + transform: translateX(-6px); + cursor: grab; + pointer-events: auto; + position: sticky; + top: 4px; + z-index: 35; +} + +.playhead-head:hover { + background: var(--accent-hover); +} + +.playhead-head:active { + cursor: grabbing; +} + +.playhead-line { + width: 2px; + min-height: calc(100vh - 50px); + height: 100%; + background: var(--accent-timeline); + margin-top: -2px; +} + + +/* In/Out Work Area Markers */ +.work-area-overlay { + position: absolute; + top: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + pointer-events: none; + z-index: 10; +} + +.work-area-overlay::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 100%; + background: repeating-linear-gradient( + 45deg, + transparent, + transparent 10px, + rgba(0, 0, 0, 0.1) 10px, + rgba(0, 0, 0, 0.1) 20px + ); +} + +.in-out-marker { + position: absolute; + top: 0; + bottom: 0; + z-index: 95; + pointer-events: none; + --timeline-switch-base-x: 0px; +} + +.in-out-marker .marker-flag { + position: absolute; + top: 0; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: bold; + color: var(--text-on-accent); + border-radius: 2px; + cursor: pointer; + pointer-events: auto; +} + +.in-marker .marker-flag { + background: var(--amber-600); + left: 0; + border-radius: 0 2px 2px 0; +} + +.out-marker .marker-flag { + background: var(--amber-600); + right: 0; + border-radius: 2px 0 0 2px; +} + +.in-out-marker .marker-line { + position: absolute; + top: 16px; + bottom: 0; + width: 2px; + background: var(--amber-600); +} + +.in-marker .marker-line { + left: 0; +} + +.out-marker .marker-line { + right: 0; +} + +.in-out-marker .marker-flag:hover { + transform: scale(1.1); + box-shadow: 0 0 6px rgba(245, 158, 11, 0.6); +} + +.in-out-marker.dragging .marker-flag { + transform: scale(1.2); + box-shadow: 0 0 8px rgba(245, 158, 11, 0.8); +} + +.in-out-marker.dragging .marker-line { + width: 3px; + box-shadow: 0 0 6px rgba(245, 158, 11, 0.5); +} + +/* Timeline Markers - like playhead, spans from ruler through all tracks */ +.timeline-marker { + position: absolute; + top: 0; + width: 2px; + min-height: 100%; + height: max-content; + z-index: 29; + pointer-events: none; + transform: translateX(-1px); + --timeline-switch-base-x: -1px; + --marker-color: var(--accent-timeline); +} + +.timeline-marker-head { + width: 14px; + height: 14px; + background: var(--marker-color); + border-radius: 3px; + transform: translateX(-6px); + cursor: grab; + pointer-events: auto; + position: sticky; + top: 4px; + z-index: 35; + display: flex; + align-items: center; + justify-content: center; + font-size: 9px; + font-weight: bold; + color: var(--text-on-accent); + transition: transform 0.1s, filter 0.1s, box-shadow 0.1s; +} + +.timeline-marker-head:hover { + transform: translateX(-6px) scale(1.15); + filter: brightness(1.2); + box-shadow: 0 0 6px var(--marker-color); +} + +.timeline-marker.dragging .timeline-marker-head { + cursor: grabbing; + transform: translateX(-6px) scale(1.2); + filter: brightness(1.3); + box-shadow: 0 0 8px var(--marker-color); +} + +.timeline-marker.is-stop-marker .timeline-marker-head { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--warning-light) 45%, transparent), + 0 0 10px color-mix(in srgb, var(--warning-light) 20%, transparent); +} + +.timeline-marker .timeline-marker-line { + width: 2px; + min-height: calc(100vh - 50px); + height: 100%; + background: var(--marker-color); + margin-top: -2px; + opacity: 0.7; +} + +.timeline-marker.is-stop-marker .timeline-marker-line { + background: + repeating-linear-gradient( + to bottom, + var(--marker-color) 0 10px, + color-mix(in srgb, var(--warning-light) 42%, var(--marker-color)) 10px 16px + ); +} + +.timeline-marker:hover .timeline-marker-line, +.timeline-marker.dragging .timeline-marker-line { + opacity: 1; + box-shadow: 0 0 4px var(--marker-color); +} + +@keyframes timeline-switch-enter-left { + from { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) - 72px), 0, 0); } + to { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } +} + +@keyframes timeline-switch-enter-right { + from { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) + 72px), 0, 0); } + to { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } +} + +@keyframes timeline-switch-exit-right { + from { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } + to { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) + 140vw + 100%), 0, 0); } +} + +@keyframes timeline-switch-exit-left { + from { transform: translate3d(var(--timeline-switch-base-x, 0px), 0, 0); } + to { transform: translate3d(calc(var(--timeline-switch-base-x, 0px) - 140vw - 100%), 0, 0); } +} + +.playhead.timeline-switch-enter-left, +.timeline-marker.timeline-switch-enter-left, +.in-out-marker.timeline-switch-enter-left { + animation: timeline-switch-enter-left 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +.playhead.timeline-switch-enter-right, +.timeline-marker.timeline-switch-enter-right, +.in-out-marker.timeline-switch-enter-right { + animation: timeline-switch-enter-right 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +.playhead.timeline-switch-exit-right, +.timeline-marker.timeline-switch-exit-right, +.in-out-marker.timeline-switch-exit-right { + animation: timeline-switch-exit-right 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; +} + +.playhead.timeline-switch-exit-left, +.timeline-marker.timeline-switch-exit-left, +.in-out-marker.timeline-switch-exit-left { + animation: timeline-switch-exit-left 0.175s cubic-bezier(0.7, 0, 0.84, 0) forwards; +} + +/* Ghost marker for drag-to-create */ +.timeline-marker.ghost { + opacity: 0.7; + pointer-events: none; + z-index: 100; +} + +.timeline-marker.ghost .timeline-marker-head { + animation: ghost-pulse 0.5s ease-in-out infinite; +} + +.timeline-marker.ghost .timeline-marker-line { + opacity: 0.5; + background: linear-gradient(180deg, var(--marker-color) 0%, transparent 100%); +} + +@keyframes ghost-pulse { + 0%, 100% { transform: scale(1); filter: brightness(1); } + 50% { transform: scale(1.2); filter: brightness(1.3); } +} + +/* Drop animation */ +.timeline-marker.drop-animation { + opacity: 1; + animation: marker-drop 0.3s ease-out forwards; +} + +.timeline-marker.drop-animation .timeline-marker-head { + animation: marker-drop-icon 0.3s ease-out forwards; +} + +.timeline-marker.drop-animation .timeline-marker-line { + animation: marker-drop-line 0.3s ease-out forwards; +} + +@keyframes marker-drop { + 0% { opacity: 0.7; } + 50% { opacity: 1; } + 100% { opacity: 0; } +} + +@keyframes marker-drop-icon { + 0% { transform: scale(1.2); filter: brightness(1.5); } + 50% { transform: scale(1.4); filter: brightness(2); } + 100% { transform: scale(1); filter: brightness(1); } +} + +@keyframes marker-drop-line { + 0% { + opacity: 0.5; + box-shadow: 0 0 8px var(--marker-color); + } + 50% { + opacity: 1; + box-shadow: 0 0 16px var(--marker-color), 0 0 32px var(--marker-color); + } + 100% { + opacity: 0; + box-shadow: 0 0 0px var(--marker-color); + } +} + + +.add-marker-btn { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + padding: 0; + background: transparent; + border: 1px solid transparent; + border-radius: 3px; + color: var(--text-secondary); + cursor: grab; + transition: all 0.15s ease; + font-size: 10px; + font-weight: bold; +} + +.add-marker-btn:hover { + background: var(--bg-hover); + border-color: var(--border-color); + color: var(--accent-timeline); +} + +.add-marker-btn.dragging { + background: var(--accent-timeline); + border-color: var(--accent-timeline); + color: var(--text-on-accent); + cursor: grabbing; + transform: scale(1.1); + box-shadow: 0 0 12px var(--accent-timeline); +} +} diff --git a/src/components/timeline/TimelineNavigator.css b/src/components/timeline/TimelineNavigator.css new file mode 100644 index 000000000..453d0fc93 --- /dev/null +++ b/src/components/timeline/TimelineNavigator.css @@ -0,0 +1,174 @@ +@layer features { +/* Timeline Navigator - Horizontal scrollbar with zoom handles */ +.timeline-navigator { + height: 16px; + background: var(--bg-primary); + border-top: 1px solid var(--border-color); + padding: 3px 14px 3px 150px; /* Left: track headers width, Right: vertical scrollbar width */ + flex-shrink: 0; +} + +.timeline-navigator-track { + height: 100%; + background: var(--bg-tertiary); + border-radius: 3px; + position: relative; + cursor: pointer; +} + +.timeline-navigator-thumb { + position: absolute; + top: 0; + height: 100%; + background: var(--scrollbar-thumb); + border: none; + border-radius: 3px; + cursor: grab; + display: flex; + align-items: center; + justify-content: space-between; + transition: background 0.1s; +} + +.timeline-navigator-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +.timeline-navigator-thumb.dragging { + cursor: grabbing; + background: var(--scrollbar-thumb-hover); +} + +.timeline-navigator-handle { + width: 14px; + height: 100%; + cursor: ew-resize; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.15s; +} + +.timeline-navigator-handle:hover::before { + background: var(--text-muted); +} + +.timeline-navigator-handle-left { + border-radius: 3px 0 0 3px; +} + +.timeline-navigator-handle-right { + border-radius: 0 3px 3px 0; +} + +.timeline-navigator-handle::before { + content: ''; + width: 6px; + height: 6px; + background: var(--bg-active); + border-radius: 50%; +} + +.timeline-navigator-grip { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.timeline-navigator-grip::before, +.timeline-navigator-grip::after { + display: none; +} + +/* Vertical Scrollbar - matches horizontal TimelineNavigator style */ +.vertical-scrollbar { + width: 10px; + background: var(--bg-primary); + border-left: 1px solid var(--border-color); + flex-shrink: 0; + padding: 1px; +} + +.vertical-scrollbar-track { + width: 100%; + height: 100%; + background: var(--bg-tertiary); + border-radius: 3px; + position: relative; + cursor: pointer; +} + +.vertical-scrollbar-thumb { + position: absolute; + left: 0; + width: 100%; + background: var(--scrollbar-thumb); + border-radius: 3px; + cursor: grab; + display: flex; + flex-direction: column; + align-items: center; + transition: background 0.1s; +} + +.vertical-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +.vertical-scrollbar-thumb.dragging { + cursor: grabbing; + background: var(--scrollbar-thumb-hover); +} + +.vertical-scrollbar-handle { + width: 100%; + height: 10px; + cursor: ns-resize; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.7; + transition: opacity 0.15s; +} + +.vertical-scrollbar-handle:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.1); +} + +.vertical-scrollbar-handle-top { + border-radius: 3px 3px 0 0; +} + +.vertical-scrollbar-handle-bottom { + border-radius: 0 0 3px 3px; +} + +.vertical-scrollbar-handle::before { + content: ''; + width: 6px; + height: 6px; + background: var(--bg-canvas); + border-radius: 50%; +} + +.vertical-scrollbar-grip { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; +} + +.vertical-scrollbar-grip::before, +.vertical-scrollbar-grip::after { + content: ''; + width: 6px; + height: 2px; + background: var(--text-secondary); + border-radius: 1px; + opacity: 0.5; +} +} diff --git a/src/components/timeline/TimelineNavigator.tsx b/src/components/timeline/TimelineNavigator.tsx index 99bcae2ac..1cb8a0f1b 100644 --- a/src/components/timeline/TimelineNavigator.tsx +++ b/src/components/timeline/TimelineNavigator.tsx @@ -2,6 +2,7 @@ // Shows the visible range and allows dragging/resizing import { useRef, useState, useCallback, useEffect } from 'react'; +import './TimelineNavigator.css'; // Padding in pixels to show beyond the end of the composition (must match useTimelineZoom) const END_PADDING = 100; diff --git a/src/components/timeline/TimelineTracks.css b/src/components/timeline/TimelineTracks.css new file mode 100644 index 000000000..cced16db9 --- /dev/null +++ b/src/components/timeline/TimelineTracks.css @@ -0,0 +1,244 @@ +@layer features { +/* Composition switch: track rows stay visible while their labels/heights update. */ +.phase-exiting > .track-header, +.phase-exiting > .track-lane, +.phase-entering > .track-header, +.phase-entering > .track-lane { + animation: none; + transition-duration: var(--composition-switch-layout-duration, 475ms), 160ms, 160ms; +} + +/* Track headers */ +.track-headers { + width: 150px; + flex-shrink: 0; + background: var(--bg-secondary); + border-right: 1px solid var(--border-color); +} + +.track-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); + position: relative; + overflow: hidden; + transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; +} + +.track-header.video { + background: linear-gradient(90deg, rgba(61, 90, 128, 0.15), transparent); +} + +.track-header.audio { + background: linear-gradient(90deg, rgba(45, 107, 74, 0.15), transparent); +} + +/* Green separator line between video and audio track sections */ +.track-header.video + .track-header.audio, +.track-lane.video + .track-lane.audio { + border-top: 2px solid var(--success); +} + +/* Preview header shown when dragging media to create new track */ +.track-header-preview { + display: flex; + align-items: center; + justify-content: center; + border: 2px dashed rgba(255, 255, 255, 0.3); + border-radius: 2px; + margin: 4px 4px 0 4px; + background: rgba(255, 255, 255, 0.05); + transition: all 0.15s ease; +} + +.track-header-preview.video { + border-color: rgba(37, 99, 235, 0.4); +} + +.track-header-preview.video.active { + border-color: rgba(37, 99, 235, 0.8); + background: rgba(37, 99, 235, 0.15); +} + +.track-header-preview.audio { + border-color: rgba(22, 163, 74, 0.4); +} + +.track-header-preview.audio.active { + border-color: rgba(22, 163, 74, 0.8); + background: rgba(22, 163, 74, 0.15); +} + +.track-header-preview-label { + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + font-weight: 500; +} + +.track-header-preview.active .track-header-preview-label { + color: rgba(255, 255, 255, 0.8); +} + +.track-header.dimmed, +.track-lane.dimmed { + opacity: 0.4; +} + +.track-name { + font-size: 11px; + font-weight: 500; + color: var(--text-primary); + cursor: text; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 62px; +} + +.track-layer-id { + flex-shrink: 0; + font-size: 10px; + color: var(--text-secondary); + line-height: 1; +} + +.track-name-input { + font-size: 12px; + font-weight: 500; + color: var(--text-primary); + background: var(--bg-primary); + border: 1px solid var(--accent-timeline); + border-radius: 2px; + padding: 0 4px; + width: 80px; + outline: none; +} + +.track-controls { + display: flex; + flex-direction: row; + align-items: center; + gap: 2px; + margin-left: auto; + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); +} + +/* Track pick whip for layer parenting */ +.track-pick-whip { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + cursor: grab; + opacity: 0.5; + transition: opacity 0.15s, transform 0.15s; + color: var(--expr-color); +} + +.track-pick-whip:hover { + opacity: 1; + transform: scale(1.1); +} + +.track-pick-whip.dragging { + cursor: grabbing; + opacity: 1; + transform: scale(1.2); +} + +.track-pick-whip.has-parent { + opacity: 1; + color: var(--expr-color); +} + +.track-pick-whip.has-children { + opacity: 0.8; + color: var(--expr-connected); +} + +.track-pick-whip.has-parent.has-children { + opacity: 1; + color: var(--expr-color); +} + +.track-pick-whip-icon { + width: 14px; + height: 14px; +} + +/* Timeline tracks area */ +.timeline-tracks { + flex: 1; + position: relative; + overflow-x: hidden; /* Hidden - using TimelineNavigator instead */ + overflow-y: visible; +} + +.track-lanes-scroll { + position: relative; +} + +/* Track lanes */ +.track-lane { + position: relative; + border-bottom: 1px solid var(--border-color); + background: var(--bg-secondary); + min-width: 100%; + overflow: hidden; + transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; +} + +.track-lane.video { + background: repeating-linear-gradient( + 90deg, + var(--bg-secondary), + var(--bg-secondary) calc(var(--grid-size, 50px) - 1px), + var(--timeline-grid-video) calc(var(--grid-size, 50px) - 1px), + var(--timeline-grid-video) var(--grid-size, 50px) + ); +} + +.track-lane.audio { + background: repeating-linear-gradient( + 90deg, + var(--bg-secondary), + var(--bg-secondary) calc(var(--grid-size, 50px) - 1px), + var(--timeline-grid-audio) calc(var(--grid-size, 50px) - 1px), + var(--timeline-grid-audio) var(--grid-size, 50px) + ); +} + +.composition-exit-clips-overlay { + position: absolute; + top: 0; + left: 0; + min-width: inherit; + width: 100%; + pointer-events: none; + z-index: 12; +} + +.composition-exit-track-row { + position: relative; + width: 100%; + overflow: visible; +} + +/* Track header resize cursor on shift hover */ +.track-header { + cursor: default; + transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease; +} + +.track-header:hover { + background: var(--bg-hover); +} +} diff --git a/src/components/timeline/VerticalScrollbar.tsx b/src/components/timeline/VerticalScrollbar.tsx index 66abdf766..3a4d265f8 100644 --- a/src/components/timeline/VerticalScrollbar.tsx +++ b/src/components/timeline/VerticalScrollbar.tsx @@ -2,6 +2,7 @@ // Matches the horizontal TimelineNavigator style import { useRef, useState, useCallback, useEffect } from 'react'; +import './TimelineNavigator.css'; interface VerticalScrollbarProps { scrollY: number; diff --git a/src/components/timeline/components/TimelineOverlays.tsx b/src/components/timeline/components/TimelineOverlays.tsx index 2fc7ed257..75e6c2877 100644 --- a/src/components/timeline/components/TimelineOverlays.tsx +++ b/src/components/timeline/components/TimelineOverlays.tsx @@ -1,5 +1,6 @@ // Timeline overlay elements (markers, work area, cache indicators, etc.) +import '../TimelineInteractions.css'; import React from 'react'; import type { ClipDragState } from '../types'; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 1dd5dc9e5..000000000 --- a/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -/* WebVJ Mixer - Base Styles */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body, #root { - width: 100%; - height: 100%; - overflow: auto; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; - font-size: 14px; - line-height: 1.5; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -#root { - display: flex; - flex-direction: column; -} diff --git a/src/main.tsx b/src/main.tsx index 5017c60f5..cfb80d5aa 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import { createRoot } from 'react-dom/client' -import './index.css' +import './styles/tokens.css' +import './styles/base.css' import RootApp from './RootApp.tsx' import { resolveEntryExperience } from './routing/entryExperience' diff --git a/src/styles/app-shell.css b/src/styles/app-shell.css new file mode 100644 index 000000000..3a587b88f --- /dev/null +++ b/src/styles/app-shell.css @@ -0,0 +1,896 @@ +@layer shell { +.app { + display: flex; + flex-direction: column; + min-height: 100vh; + min-width: 900px; + height: 100vh; + width: 100vw; + overflow: auto; +} + +.project-load-progress { + position: fixed; + left: 50%; + bottom: 18px; + z-index: var(--z-overlay); + width: min(420px, calc(100vw - 32px)); + transform: translateX(-50%); + padding: 10px 12px; + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + background: color-mix(in srgb, var(--bg-elevated) 94%, transparent); + box-shadow: var(--shadow-lg); + pointer-events: none; +} + +.project-load-progress-head, +.project-load-progress-detail { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-3); + min-width: 0; +} + +.project-load-progress-head { + color: var(--text-primary); + font-size: var(--font-md); + font-weight: var(--font-semibold); +} + +.project-load-progress-head span:first-child, +.project-load-progress-detail span:first-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-load-progress-bar { + height: 4px; + margin: 8px 0 6px; + overflow: hidden; + border-radius: var(--radius-full); + background: var(--bg-input); +} + +.project-load-progress-bar > div { + height: 100%; + border-radius: inherit; + background: var(--accent); + transition: width var(--transition-normal); +} + +.project-load-progress-detail { + color: var(--text-secondary); + font-size: var(--font-sm); +} +/* Project Section */ +.project-section { + position: relative; +} + +.project-name { + font-size: var(--font-sm); + color: var(--text-primary); + cursor: pointer; + padding: 3px 6px; + border-radius: var(--radius-sm); + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-name:hover { + background: var(--bg-hover); +} + +.project-name-input { + font-size: var(--font-sm); + padding: 3px 6px; + border-radius: var(--radius-sm); + border: 1px solid var(--accent); + background: var(--bg-primary); + color: var(--text-primary); + width: 150px; +} + +.project-buttons { + display: flex; + gap: var(--sp-1); +} + +.project-menu { + position: absolute; + top: 100%; + left: 0; + margin-top: var(--sp-1); + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + min-width: 280px; + max-height: 300px; + overflow-y: auto; + z-index: 1000; + box-shadow: var(--shadow-md); +} + +.project-menu-header { + padding: var(--sp-2) var(--sp-3); + font-size: var(--font-md); + color: var(--text-secondary); + border-bottom: 1px solid var(--border-color); +} + +.project-menu-empty { + padding: var(--sp-4); + text-align: center; + color: var(--text-secondary); + font-size: var(--font-lg); +} + +.project-menu-item { + display: flex; + align-items: center; + gap: var(--sp-2); + padding: var(--sp-2-5) var(--sp-3); + cursor: pointer; + border-bottom: 1px solid var(--border-color); +} + +.project-menu-item:last-child { + border-bottom: none; +} + +.project-menu-item:hover { + background: var(--bg-hover); +} + +.project-menu-name { + flex: 1; + font-size: var(--font-lg); + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-menu-date { + font-size: var(--font-sm); + color: var(--text-secondary); +} + +.project-menu-delete { + width: 20px; + height: 20px; + padding: 0; + border: none; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-size: var(--font-2xl); + line-height: 1; + border-radius: var(--radius-md); + opacity: 0; + transition: opacity var(--transition-normal); +} + +.project-menu-item:hover .project-menu-delete { + opacity: 1; +} + +.project-menu-delete:hover { + background: rgba(255, 0, 0, 0.2); + color: var(--danger-light); +} + +/* Main Content */ +.main-content { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Left Column - Toolbar + Slots */ +.left-column { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + overflow: hidden; +} + +/* Slots Area - Main largest element */ +.slots-area { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + min-height: 0; + background: var(--bg-secondary); + overflow: hidden; +} + +/* Resize Handle */ +.resize-handle { + width: 2px; + background: var(--border-color); + cursor: col-resize; + flex-shrink: 0; + transition: background var(--transition-slow); +} + +.resize-handle:hover, +.resizing .resize-handle { + background: var(--accent); +} + +.resizing { + cursor: col-resize; + user-select: none; +} + +/* Right Column - Preview + Effects */ +.right-column { + width: 360px; + flex-shrink: 0; + display: flex; + flex-direction: column; + background: var(--bg-secondary); +} + +/* Panel Headers */ +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--sp-3) var(--sp-4); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.panel-header h3, +.panel-header h4 { + margin: 0; + font-size: var(--font-xl); + font-weight: var(--font-semibold); +} + +.panel-empty { + padding: var(--sp-6) var(--sp-4); + text-align: center; + color: var(--text-secondary); +} + +/* Slots Panel */ +.layer-panel { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + overflow: hidden; +} + +.slots-panel .panel-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.slot-count { + font-size: var(--font-md); + color: var(--text-secondary); +} + +/* Grid Controls */ +.grid-controls { + display: flex; + gap: var(--sp-1); +} + +.grid-btn { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-xl); + width: 28px; + height: 28px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-normal); +} + +.grid-btn:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); +} + +/* Unified Grid with Row and Column Triggers */ +.slots-grid-with-triggers { + flex: 1; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + padding: var(--sp-2); + display: grid; + /* grid-template-columns set dynamically via inline style */ + grid-auto-rows: min-content; + gap: var(--sp-1-5); + align-content: start; +} + +.trigger-corner { + /* Empty corner cell */ +} + +.row-trigger-btn, +.column-trigger-btn { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-sm); + font-weight: var(--font-semibold); + cursor: pointer; + transition: all var(--transition-normal); + display: flex; + align-items: center; + justify-content: center; +} + +.column-trigger-btn { + height: 28px; +} + +.row-trigger-btn { + /* Height matches slot via aspect-ratio */ +} + +.row-trigger-btn:hover, +.column-trigger-btn:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); +} + +.row-trigger-btn:active, +.column-trigger-btn:active { + transform: scale(0.95); +} + +.row-trigger-btn.active, +.column-trigger-btn.active { + background: var(--accent); + border-color: var(--accent); + color: var(--text-inverse); + box-shadow: 0 0 10px var(--accent); +} + +/* Slot Item */ +.slot-item { + position: relative; + aspect-ratio: 1; + background: var(--bg-tertiary); + border: 2px dashed var(--border-color); + border-radius: var(--radius-lg); + overflow: hidden; + cursor: pointer; + transition: all var(--transition-slow); + display: flex; + flex-direction: column; +} + +.slot-item.empty { + opacity: 0.4; +} + +.slot-item.has-layer { + border-style: solid; + opacity: 0.6; +} + +.slot-item.has-media { + opacity: 1; + border-color: var(--border-color); +} + +.slot-item:hover { + border-color: var(--gray-300); + opacity: 1; +} + +.slot-item.selected { + border-color: var(--accent); + box-shadow: 0 0 0 1px var(--accent); +} + +/* Drag & Drop states */ +.slot-item.dragging { + opacity: 0.5; + transform: scale(0.95); +} + +.slot-item.drag-over { + border-color: var(--accent); + border-style: solid; + background: rgba(45, 140, 235, 0.1); +} + +.slot-item.column-highlight, +.slot-item.row-highlight { + border-color: var(--accent); + box-shadow: 0 0 12px var(--accent), inset 0 0 20px rgba(45, 140, 235, 0.2); + opacity: 1; +} + +.slot-item[draggable="true"] { + cursor: grab; +} + +.slot-item[draggable="true"]:active { + cursor: grabbing; +} + +/* Slot Header */ +.slot-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-1-5) var(--sp-2); + background: transparent; + font-size: 22px; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; +} + +.slot-number { + color: var(--text-secondary); + font-weight: var(--font-semibold); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); +} + +/* Slot Preview */ +.slot-preview { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-canvas); +} + +.slot-thumbnail { + width: 100%; + height: 100%; + object-fit: cover; +} + +.slot-empty-media { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + font-size: 20px; + transition: all var(--transition-slow); +} + +.slot-empty-media:hover { + background: var(--bg-hover); + color: var(--accent); +} + +/* Slot Placeholder (empty slot) */ +.slot-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: flex-start; + justify-content: flex-end; + padding: var(--sp-1) var(--sp-1-5); + cursor: pointer; +} + +.slot-placeholder:hover { + background: var(--bg-hover); +} + +.slot-add { + font-size: 32px; + color: var(--text-secondary); + opacity: 0; + cursor: pointer; + transition: opacity var(--transition-normal), color var(--transition-normal), transform var(--transition-normal); +} + +.slot-placeholder:hover .slot-add { + opacity: 0.6; +} + +.slot-add:hover { + opacity: 1 !important; + color: var(--accent); + transform: scale(1.2); +} + +/* Slot Controls */ +.slot-controls { + position: absolute; + bottom: 4px; + right: 4px; + z-index: 1; +} + + +.btn-clear { + background: rgba(0, 0, 0, 0.5); + border: none; + color: var(--danger); + cursor: pointer; + font-size: 20px; + padding: var(--sp-1) var(--sp-2); + line-height: 1; + border-radius: var(--radius-md); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); +} + +.btn-clear:hover { + color: var(--danger-light); + background: rgba(0, 0, 0, 0.7); +} + +/* Visibility button in slots */ +.visibility-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 2px; + font-size: 20px; + line-height: 1; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); +} + +.visibility-btn.visible { + color: var(--accent); +} + +/* Layer Settings in Effects Panel */ +.layer-settings { + padding: var(--sp-3); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + + +/* Effects Panel */ +.effects-panel { + display: flex; + flex-direction: column; + height: 100%; +} + +.effect-add select { + padding: var(--sp-1) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); + cursor: pointer; +} + +.effects-list { + flex: 1; + overflow-y: auto; + padding: var(--sp-2); +} + +.effect-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + margin-bottom: var(--sp-2); + overflow: hidden; + transition: opacity var(--transition-normal); +} + +.effect-item.bypassed { + opacity: 0.5; +} + +.effect-item.bypassed .effect-params { + pointer-events: none; +} + +.effect-item.dragging { + opacity: 0.4; +} + +.effect-item.drop-target { + border-top: 2px solid var(--accent-color, #4fc3f7); +} + +.effect-drag-handle { + cursor: grab; + opacity: 0.4; + font-size: 12px; + user-select: none; + line-height: 1; +} + +.effect-drag-handle:hover { + opacity: 0.8; +} + +.effect-header { + display: flex; + align-items: center; + padding: var(--sp-2) var(--sp-3); + background: var(--bg-secondary); + gap: var(--sp-2); +} + +.effect-bypass-btn { + background: none; + border: none; + padding: var(--sp-1); + cursor: pointer; + color: var(--accent-color); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-normal); +} + +.effect-bypass-btn:hover { + background: var(--bg-tertiary); +} + +.effect-bypass-btn.bypassed { + color: var(--text-secondary); +} + +.effect-name { + font-weight: var(--font-medium); + text-transform: capitalize; + flex: 1; +} + +.effect-params { + padding: var(--sp-2) var(--sp-3); +} + +/* Effect Quality Section */ +.effect-quality-section { + margin-top: var(--sp-2); + border-top: 1px solid var(--border-color); + padding-top: 8px; +} + +.effect-quality-header { + display: flex; + align-items: center; + gap: var(--sp-1-5); + cursor: pointer; + padding: 4px 0; + user-select: none; +} + +.effect-quality-header:hover { + color: var(--accent-color); +} + +.effect-quality-toggle { + font-size: var(--font-xs); + width: 12px; +} + +.effect-quality-title { + font-size: var(--font-sm); + font-weight: var(--font-medium); + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); + flex: 1; +} + +.effect-quality-reset { + font-size: var(--font-xs); + padding: 2px 6px; + opacity: 0.7; +} + +.effect-quality-reset:hover { + opacity: 1; +} + +.effect-quality-params { + padding: 8px 0 0 18px; +} + +.effect-quality-warning { + font-size: var(--font-xs); + color: var(--warning-color, #f0ad4e); + margin-top: var(--sp-2); + opacity: 0.8; +} + + +.effect-info { + margin: 0; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.transform-section { + padding: var(--sp-3); + border-top: 1px solid var(--border-color); + background: var(--bg-tertiary); +} + +.transform-section h4 { + margin: 0 0 12px 0; + font-size: var(--font-lg); + color: var(--text-secondary); +} +/* Slot Groups - Connected Rectangle Visual */ + +/* Group colors - 6 different colors that cycle */ +.slot-item.group-0 { --group-color: var(--group-0); } +.slot-item.group-1 { --group-color: var(--group-1); } +.slot-item.group-2 { --group-color: var(--group-2); } +.slot-item.group-3 { --group-color: var(--group-3); } +.slot-item.group-4 { --group-color: var(--group-4); } +.slot-item.group-5 { --group-color: var(--group-5); } + +/* Base group styling - colored border */ +.slot-item.in-group { + border: 3px solid var(--group-color); + border-radius: 6px; + margin: 0; +} + +/* Left edge of group - has border on all sides except right */ +.slot-item.in-group.group-left { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + margin-right: -3px; /* Overlap with next slot */ + z-index: 1; +} + +/* Right edge of group - has border on all sides except left */ +.slot-item.in-group.group-right { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +/* Middle of group (3+ slots) - no left/right borders */ +.slot-item.in-group.group-middle { + border-left: none; + border-right: none; + border-radius: 0; + margin-left: -3px; + margin-right: -3px; +} + +/* Group highlight on hover - glow effect */ +.slot-item.group-highlight { + box-shadow: 0 0 20px var(--group-color), 0 0 40px var(--group-color); + z-index: 2; +} + +.slot-item.group-highlight.group-left { + box-shadow: -5px 0 20px var(--group-color), 0 -5px 20px var(--group-color), 0 5px 20px var(--group-color); +} + +.slot-item.group-highlight.group-right { + box-shadow: 5px 0 20px var(--group-color), 0 -5px 20px var(--group-color), 0 5px 20px var(--group-color); +} + +/* Half-slot drop zone indicators */ +.drop-zone-indicator { + position: absolute; + top: 0; + bottom: 0; + width: 50%; + pointer-events: none; + z-index: 5; + transition: all 0.15s; +} + +.drop-zone-indicator.left { + left: 0; + border-right: 2px dashed rgba(255, 255, 255, 0.3); +} + +.drop-zone-indicator.right { + right: 0; + border-left: 2px dashed rgba(255, 255, 255, 0.3); +} + +.drop-zone-indicator.active { + background: rgba(45, 140, 235, 0.2); +} + +.drop-zone-indicator.left.active { + border-right-color: var(--accent); +} + +.drop-zone-indicator.right.active { + border-left-color: var(--accent); +} + +/* Copy mode indicator (Ctrl+drag) */ +.slot-item.copy-mode { + border-color: var(--success); + background: rgba(68, 255, 68, 0.1); +} + +.slot-item.copy-mode::after { + content: '+'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 32px; + font-weight: bold; + color: var(--success); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + z-index: 10; + pointer-events: none; +} + +/* Drop zone left/right visual feedback */ +.slot-item.drop-zone-left { + border-left-color: var(--accent); + border-left-width: 4px; +} + +.slot-item.drop-zone-right { + border-right-color: var(--accent); + border-right-width: 4px; +} + +/* Responsive */ +@media (max-width: 1200px) { + .right-column { + width: 320px; + } +} + +@media (max-width: 900px) { + .main-content { + flex-direction: column; + } + + .slots-area { + flex: 1; + min-height: 300px; + } + + .right-column { + width: 100%; + border-left: none; + border-top: 1px solid var(--border-color); + max-height: 250px; + } +} + +} diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 000000000..87ecf1470 --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,64 @@ +@layer base { +/* WebVJ Mixer - Base Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #root { + width: 100%; + height: 100%; + overflow: auto; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#root { + display: flex; + flex-direction: column; +} + +/* ═══════════════════════════════════════════ + THEME TRANSITION + Brief smooth transition when switching themes + ═══════════════════════════════════════════ */ + +html.theme-transitioning, +html.theme-transitioning *, +html.theme-transitioning *::before, +html.theme-transitioning *::after { + transition: background-color 0.2s ease, color 0.15s ease, + border-color 0.2s ease, box-shadow 0.2s ease !important; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + background: var(--bg-primary); + color: var(--text-primary); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: var(--font-md); + overflow: hidden; + user-select: none; + -webkit-user-select: none; +} + +/* Allow text selection in inputs */ +input, textarea { + user-select: text; + -webkit-user-select: text; +} + +} diff --git a/src/styles/shared-controls.css b/src/styles/shared-controls.css new file mode 100644 index 000000000..5ff310d7c --- /dev/null +++ b/src/styles/shared-controls.css @@ -0,0 +1,653 @@ +@layer shared { +/* Control Rows */ +.control-row { + display: flex; + align-items: center; + gap: var(--sp-2); + margin-bottom: var(--sp-2); +} + +.control-row:last-child { + margin-bottom: 0; +} + +.control-row label { + min-width: 70px; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.control-row input[type="range"] { + flex: 1; + height: 4px; + -webkit-appearance: none; + background: var(--bg-primary); + border-radius: var(--radius-sm); + cursor: pointer; +} + +.control-row input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 14px; + height: 14px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; +} + +.control-row input[type="range"]::-moz-range-thumb { + width: 14px; + height: 14px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; + border: none; +} + +.control-row span { + min-width: 40px; + text-align: right; + font-size: var(--font-md); + color: var(--text-secondary); +} + +.control-row select { + flex: 1; + padding: var(--sp-1) var(--sp-2); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--font-md); + cursor: pointer; +} + +.control-row select:focus { + outline: none; + border-color: var(--accent); +} + +.btn-xs { + font-size: var(--font-xs); + padding: 2px 6px; + border-radius: var(--radius-sm); +} + +/* Buttons */ +.btn { + padding: 4px 10px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 2px; + color: var(--text-primary); + font-size: 11px; + cursor: pointer; + transition: all 0.2s; +} + +.btn:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--border-strong); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-active { + background: var(--accent); + border-color: var(--accent); + color: var(--text-on-accent); +} + +.btn-active:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-export { + background: linear-gradient(135deg, var(--accent) 0%, #2070C0 100%); + border-color: var(--accent); + color: var(--text-on-accent); + font-weight: var(--font-semibold); +} + +.btn-export:hover:not(:disabled) { + background: linear-gradient(135deg, var(--accent-hover) 0%, var(--accent) 100%); + border-color: var(--accent-hover); +} + +.btn-sm { + padding: 3px 6px; + font-size: 11px; +} + +.btn-danger { + color: var(--danger); +} + +.btn-danger:hover:not(:disabled) { + background: var(--danger); + border-color: var(--danger); + color: var(--text-on-accent); +} + +.btn-reload-all { + background: var(--warning); + border-color: var(--warning); + color: var(--text-inverse); + font-weight: var(--font-medium); +} + +.btn-reload-all:hover { + background: #cc8800; + border-color: #cc8800; +} + +.btn-icon { + background: none; + border: none; + font-size: 10px; + cursor: pointer; + opacity: 0.7; + padding: 1px 3px; + line-height: 1; +} + +.btn-icon:hover { + opacity: 1; +} + +.btn-icon.muted, +.btn-icon.hidden { + opacity: 0.3; +} + +.btn-icon.solo-active { + color: var(--warning); + opacity: 1; + font-weight: bold; +} + +.btn-danger { + background: var(--danger); + color: var(--text-on-accent); +} + +.btn-danger:hover { + background: var(--red-300); +} + +/* View Tabs */ +.view-tabs { + display: flex; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + padding: 0 8px; +} + +.tab-btn { + padding: 10px 20px; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + color: var(--text-secondary); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.tab-btn:hover { + color: var(--text-primary); + background: var(--bg-hover); +} + +.tab-btn.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +/* MIDI */ +.midi-unsupported { + font-size: 12px; + color: var(--text-secondary); +} + +/* Checkboxes */ +input[type="checkbox"] { + width: 16px; + height: 16px; + margin-right: 8px; + cursor: pointer; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + + +.context-menu-divider { + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +/* ========== PRECISION SLIDER ========== */ +.precision-slider { + flex: 1; + height: 20px; + cursor: ew-resize; + display: flex; + align-items: center; +} + +.precision-slider-track { + width: 100%; + height: 4px; + background: var(--bg-primary); + border-radius: 2px; + position: relative; +} + +.precision-slider-fill { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: var(--accent); + border-radius: 2px; + pointer-events: none; +} + +.precision-slider-thumb { + position: absolute; + top: 50%; + width: 12px; + height: 12px; + background: var(--accent); + border-radius: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.precision-slider:hover .precision-slider-thumb { + box-shadow: 0 0 0 3px rgba(45, 140, 235, 0.3); +} + +.precision-slider:active { + cursor: grabbing; +} + +.precision-slider:active .precision-slider-thumb { + background: var(--accent-hover); + box-shadow: 0 0 0 4px rgba(45, 140, 235, 0.4); +} + +/* ========== DRAGGABLE NUMBER ========== */ +.draggable-number { + display: inline-block; + padding: 3px 6px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + color: #5b8abf; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-variant-numeric: tabular-nums; + font-size: 11px; + cursor: ew-resize; + user-select: none; + min-width: 50px; + text-align: right; + transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease; +} + +.draggable-number-anchor { + position: relative; + display: inline-flex; + align-items: center; +} + +/* When used as primary input in control-row (replacing slider + value) */ +.control-row > .draggable-number, +.control-row > .draggable-number-anchor { + flex: 1; +} + +.control-row > .draggable-number-anchor { + justify-content: flex-end; +} + +.control-row > .draggable-number-anchor > .draggable-number, +.control-row > .draggable-number-anchor > .draggable-number-input { + text-align: right; +} + +.draggable-number:hover { + border-color: #2d5a8e; + background: var(--bg-secondary); + color: #7aabe0; +} + +.draggable-number:active { + color: #9cc4f0; + cursor: grabbing; +} + +.draggable-number.editing, +.draggable-number-input { + cursor: text; + user-select: text; +} + +.draggable-number-input { + outline: none; + box-sizing: border-box; + text-align: right; + color: #d7e7fb; + background: var(--bg-secondary); + border-color: #2d5a8e; +} + +.draggable-number-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 1px rgba(45, 140, 235, 0.35); +} + +.draggable-number-bounds-popover { + position: fixed; + z-index: 40; + min-width: 160px; + padding: 8px; + background: rgba(20, 24, 30, 0.98); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35); +} + +.draggable-number-bounds-title { + margin-bottom: 6px; + color: var(--text-primary); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.draggable-number-bounds-row { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; +} + +.draggable-number-bounds-row label { + width: 46px; + color: var(--text-secondary); + font-size: 11px; +} + +.draggable-number-bounds-row input { + flex: 1; + min-width: 0; + padding: 3px 6px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-primary); + font-size: 11px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; +} + +.draggable-number-bounds-row input:focus { + outline: none; + border-color: var(--accent); +} + +.draggable-number-bounds-actions { + display: flex; + justify-content: flex-end; + gap: 6px; + margin-top: 8px; +} + +/* ========== KEYFRAME TOGGLE ========== */ +.keyframe-toggle { + width: 18px; + height: 18px; + padding: 2px; + background: transparent; + border: 1px solid var(--border-color); + border-radius: 3px; + cursor: pointer; + color: var(--text-secondary); + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; +} + +.keyframe-toggle:hover { + border-color: var(--accent); + color: var(--accent); +} + +.keyframe-toggle.has-keyframes { + color: var(--amber-600); + border-color: var(--amber-600); +} + +.keyframe-toggle.has-keyframes:hover { + color: var(--warning-light); + border-color: var(--warning-light); +} + +.keyframe-toggle.recording { + background: transparent; + color: var(--amber-600); + border-color: var(--amber-600); + animation: keyframe-recording-activate 700ms ease-out; +} + +.keyframe-toggle.recording:hover { + background: transparent; + color: var(--warning-light); + border-color: var(--warning-light); +} + +.keyframe-toggle.recording-feedback { + animation: keyframe-recording-pulse 900ms ease-in-out infinite; +} + +@keyframes keyframe-recording-activate { + 0%, 45% { + background: var(--amber-600); + color: var(--text-inverse); + border-color: var(--amber-600); + box-shadow: 0 0 6px var(--amber-600); + } + 100% { + background: transparent; + color: var(--amber-600); + border-color: var(--amber-600); + box-shadow: none; + } +} + +@keyframes keyframe-recording-pulse { + 0%, 100% { + background: transparent; + border-color: var(--amber-600); + color: var(--amber-600); + opacity: 1; + filter: none; + box-shadow: none; + } + 50% { + background: rgba(245, 158, 11, 0.22); + border-color: var(--warning-light); + color: var(--warning-light); + opacity: 1; + filter: drop-shadow(0 0 4px rgba(245, 158, 11, 0.85)); + box-shadow: 0 0 8px rgba(245, 158, 11, 0.45); + } +} + +.keyframe-toggle-placeholder { + width: 18px; + height: 18px; + flex-shrink: 0; +} + + +/* ========== CONTEXT MENU ========== */ +.timeline-context-menu, +.media-context-menu { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + min-width: 180px; + padding: 4px 0; + font-size: 12px; +} + +.context-menu-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + cursor: pointer; + transition: background 0.1s; + color: var(--text-primary); +} + +.context-menu-item:hover { + background: var(--bg-hover); +} + +.context-menu-item.disabled { + color: var(--text-secondary); + cursor: not-allowed; + opacity: 0.5; +} + +.context-menu-item.disabled:hover { + background: transparent; +} + +.context-menu-item.danger { + color: var(--danger); +} + +.context-menu-item.danger:hover { + background: rgba(239, 68, 68, 0.15); +} + +.context-menu-separator { + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +.context-menu-icon { + width: 20px; + text-align: center; + font-size: 14px; + margin-right: 8px; + flex-shrink: 0; +} + +.context-menu-hint { + margin-left: auto; + font-size: 10px; + color: var(--text-muted); + font-style: italic; +} + +/* Submenu styles */ +.context-menu-item.has-submenu { + position: relative; +} + +.submenu-arrow { + font-size: 8px; + color: var(--text-secondary); +} + +.context-submenu { + display: none; + position: absolute; + left: 100%; + top: -4px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + min-width: 160px; + padding: 4px 0; +} + +.context-menu-item.has-submenu:hover > .context-submenu { + z-index: 10001; +} + +.context-menu-item.has-submenu:hover .context-submenu { + display: block; +} + +/* Clip color submenu grid */ +.clip-color-submenu { + padding: 6px !important; + min-width: auto !important; +} + +.clip-color-grid { + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 3px; +} + +.clip-color-grid .label-picker-swatch { + width: 18px; + height: 18px; + border-radius: 3px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.1s; +} + +.clip-color-grid .label-picker-swatch:hover { + transform: scale(1.25); + outline: 1px solid rgba(255, 255, 255, 0.5); +} + +.clip-color-grid .label-picker-swatch.active { + outline: 2px solid #fff; + outline-offset: 1px; +} + +.clip-color-grid .label-picker-swatch.none { + border: 1px solid var(--border-color, #555); +} + +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css new file mode 100644 index 000000000..486936f12 --- /dev/null +++ b/src/styles/tokens.css @@ -0,0 +1,439 @@ +@layer tokens, base, shell, shared, components, features, overlays; + +@layer tokens { +/* WebVJ Mixer - Dark Theme Styles */ + +:root { + /* ═══════════════════════════════════════════ + PRIMITIVE COLORS (raw palette) + ═══════════════════════════════════════════ */ + + /* Gray scale */ + --gray-950: #0a0a0a; + --gray-900: #0f0f0f; + --gray-850: #141414; + --gray-800: #161616; + --gray-750: #1a1a1a; + --gray-700: #1e1e1e; + --gray-650: #222222; + --gray-600: #282828; + --gray-550: #2a2a2a; + --gray-500: #333333; + --gray-450: #3a3a3a; + --gray-400: #444444; + --gray-350: #4a4a4a; + --gray-300: #555555; + --gray-250: #666666; + --gray-200: #888888; + --gray-150: #999999; + --gray-100: #aaaaaa; + --gray-75: #cccccc; + --gray-50: #d4d4d4; + --gray-25: #e0e0e0; + --gray-0: #ffffff; + + /* Blue (accent) */ + --blue-700: #1a5fb0; + --blue-600: #2080CC; + --blue-500: #2D8CEB; + --blue-400: #4DA3F0; + --blue-300: #60a5fa; + --blue-200: #93c5fd; + + /* Red (danger) */ + --red-700: #c53030; + --red-600: #E74C3C; + --red-500: #ef4444; + --red-400: #f87171; + --red-300: #ff6b6b; + + /* Green (success) */ + --green-700: #15803d; + --green-600: #22c55e; + --green-500: #27AE60; + --green-400: #4ade80; + --green-300: #51cf66; + + /* Yellow/Amber (warning) */ + --amber-700: #b45309; + --amber-600: #f59e0b; + --amber-500: #F39C12; + --amber-400: #fbbf24; + --amber-300: #fcd34d; + + /* Purple */ + --purple-600: #7c3aed; + --purple-500: #8b5cf6; + --purple-400: #a855f7; + --purple-300: #c084fc; + + /* Cyan */ + --cyan-500: #22d3ee; + --cyan-400: #67e8f9; + + /* ═══════════════════════════════════════════ + SEMANTIC TOKENS (meaning-based) + ═══════════════════════════════════════════ */ + + /* Backgrounds */ + --bg-primary: var(--gray-900); + --bg-secondary: var(--gray-800); + --bg-tertiary: var(--gray-700); + --bg-hover: var(--gray-600); + --bg-active: var(--gray-550); + --bg-elevated: var(--gray-650); + --bg-input: var(--gray-750); + --bg-overlay: rgba(0, 0, 0, 0.7); + --bg-overlay-light: rgba(0, 0, 0, 0.4); + --bg-canvas: #000000; + + /* Borders */ + --border-color: var(--gray-950); + --border-subtle: var(--gray-500); + --border-strong: var(--gray-400); + --border-focus: var(--blue-500); + + /* Text */ + --text-primary: var(--gray-50); + --text-secondary: var(--gray-150); + --text-muted: var(--gray-250); + --text-disabled: var(--gray-300); + --text-inverse: #000000; + --text-on-accent: #ffffff; + + /* Accent (primary action color) */ + --accent: var(--blue-500); + --accent-hover: var(--blue-400); + --accent-dim: rgba(45, 140, 235, 0.15); + --accent-subtle: rgba(45, 140, 235, 0.1); + + /* Timeline accent (slightly different blue for timeline elements) */ + --accent-timeline: #2997E5; + --accent-timeline-hover: var(--blue-600); + + /* Status colors */ + --danger: var(--red-600); + --danger-light: var(--red-400); + --danger-dim: rgba(239, 68, 68, 0.15); + --danger-subtle: rgba(239, 68, 68, 0.1); + --danger-border: rgba(239, 68, 68, 0.3); + + --success: var(--green-600); + --success-light: var(--green-400); + --success-dim: rgba(34, 197, 94, 0.15); + --success-subtle: rgba(34, 197, 94, 0.1); + + --warning: var(--amber-500); + --warning-light: var(--amber-400); + --warning-dim: rgba(245, 158, 11, 0.15); + + --info: var(--blue-300); + --info-dim: rgba(96, 165, 250, 0.15); + + /* Purple (expressions, AI, special) */ + --purple: var(--purple-500); + --purple-light: var(--purple-400); + --purple-dim: rgba(139, 92, 246, 0.15); + + /* ═══════════════════════════════════════════ + SPACING SCALE (4px base grid) + ═══════════════════════════════════════════ */ + --sp-0: 0px; + --sp-0-5: 2px; + --sp-1: 4px; + --sp-1-5: 6px; + --sp-2: 8px; + --sp-2-5: 10px; + --sp-3: 12px; + --sp-4: 16px; + --sp-5: 20px; + --sp-6: 24px; + --sp-8: 32px; + --sp-10: 40px; + + /* ═══════════════════════════════════════════ + TYPOGRAPHY + ═══════════════════════════════════════════ */ + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + + --font-2xs: 9px; + --font-xs: 10px; + --font-sm: 11px; + --font-md: 12px; + --font-lg: 13px; + --font-xl: 14px; + --font-2xl: 16px; + --font-3xl: 18px; + + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + --leading-tight: 1.2; + --leading-normal: 1.4; + --leading-relaxed: 1.5; + + --tracking-tight: -0.01em; + --tracking-normal: 0; + --tracking-wide: 0.5px; + + /* ═══════════════════════════════════════════ + BORDER RADIUS + ═══════════════════════════════════════════ */ + --radius-xs: 1px; + --radius-sm: 2px; + --radius-md: 4px; + --radius-lg: 6px; + --radius-xl: 8px; + --radius-2xl: 12px; + --radius-full: 9999px; + + /* ═══════════════════════════════════════════ + SHADOWS + ═══════════════════════════════════════════ */ + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4); + --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.5); + --shadow-dialog: 0 20px 50px rgba(0, 0, 0, 0.5); + + /* ═══════════════════════════════════════════ + TRANSITIONS + ═══════════════════════════════════════════ */ + --transition-fast: 0.1s ease; + --transition-normal: 0.15s ease; + --transition-slow: 0.2s ease; + --transition-slower: 0.3s ease; + + /* ═══════════════════════════════════════════ + Z-INDEX SCALE + ═══════════════════════════════════════════ */ + --z-base: 1; + --z-dropdown: 100; + --z-sticky: 200; + --z-overlay: 1000; + --z-modal: 10000; + --z-tooltip: 10001; + + /* ═══════════════════════════════════════════ + COMPONENT TOKENS + ═══════════════════════════════════════════ */ + + /* Toolbar */ + --toolbar-height: 28px; + --toolbar-bg: var(--bg-tertiary); + --toolbar-font-size: var(--font-sm); + + /* Tab bar */ + --tab-height: 26px; + --tab-font-size: var(--font-sm); + --tab-active-bg: var(--gray-550); + + /* Panel headers */ + --panel-header-bg: var(--bg-tertiary); + --panel-header-font-size: var(--font-xl); + --panel-header-weight: var(--font-semibold); + --panel-header-padding: var(--sp-3) var(--sp-4); + + /* Inputs */ + --input-bg: var(--bg-tertiary); + --input-border: var(--border-color); + --input-border-focus: var(--accent); + --input-radius: var(--radius-md); + --input-padding: var(--sp-1-5) var(--sp-2); + --input-font-size: var(--font-md); + + /* Buttons */ + --btn-radius: var(--radius-md); + --btn-padding: var(--sp-2) var(--sp-4); + --btn-font-size: var(--font-md); + --btn-font-weight: var(--font-medium); + + /* Scrollbar */ + --scrollbar-width: 6px; + --scrollbar-track: var(--bg-secondary); + --scrollbar-thumb: var(--border-strong); + --scrollbar-thumb-hover: var(--text-muted); + + /* Slot/group colors (fixed palette) */ + --group-0: #ff6b6b; + --group-1: #4ecdc4; + --group-2: #ffe66d; + --group-3: #95e1d3; + --group-4: #dda0dd; + --group-5: #87ceeb; + + /* Pick whip / expression colors */ + --expr-color: #e5c07b; + --expr-color-light: #f0d090; + --expr-color-dark: #d4a84b; + --expr-color-stroke: #b8922f; + --expr-connected: #98c379; + + /* Marker type colors */ + --marker-green: var(--success); + --marker-amber: var(--amber-600); + --marker-red: var(--danger); + --marker-purple: var(--purple); + --marker-cyan: var(--cyan-500); + + /* Timeline grid lines (subtle tinted vertical lines) */ + --timeline-grid-video: #282830; + --timeline-grid-audio: #283028; + + /* Preview overlay buttons (over dark canvas) */ + --preview-overlay-bg: rgba(0, 0, 0, 0.6); + --preview-overlay-bg-hover: rgba(0, 0, 0, 0.8); + + /* Chat bubble (user message) */ + --chat-user-bg: var(--gray-500); + --chat-user-border: var(--gray-450); + + /* Backward compat aliases for panels that used different names */ + --accent-primary: var(--accent); + --accent-success: var(--success); + --accent-danger: var(--red-300); + --warning-color: var(--warning); + --accent-color: var(--accent); +} + +/* ═══════════════════════════════════════════ + THEME: LIGHT + Overrides semantic + component tokens only. + Primitives stay fixed, --bg-canvas stays #000. + ═══════════════════════════════════════════ */ + +[data-theme="light"] { + /* Backgrounds */ + --bg-primary: #f5f5f5; + --bg-secondary: #e8e8e8; + --bg-tertiary: #dedede; + --bg-hover: #d0d0d0; + --bg-active: #c8c8c8; + --bg-elevated: #ffffff; + --bg-input: #ffffff; + --bg-overlay: rgba(0, 0, 0, 0.4); + --bg-overlay-light: rgba(0, 0, 0, 0.2); + + /* Borders */ + --border-color: #c0c0c0; + --border-subtle: #d0d0d0; + --border-strong: #a0a0a0; + + /* Text */ + --text-primary: #1a1a1a; + --text-secondary: #4a4a4a; + --text-muted: #777777; + --text-disabled: #aaaaaa; + --text-inverse: #ffffff; + + /* Accent */ + --accent: #1a73e8; + --accent-hover: #1565c0; + --accent-dim: rgba(26, 115, 232, 0.12); + --accent-subtle: rgba(26, 115, 232, 0.08); + --accent-timeline: #1a73e8; + --accent-timeline-hover: #1565c0; + + /* Status colors */ + --danger: #d32f2f; + --danger-light: #ef5350; + --danger-dim: rgba(211, 47, 47, 0.1); + --danger-subtle: rgba(211, 47, 47, 0.06); + --danger-border: rgba(211, 47, 47, 0.25); + --success: #2e7d32; + --success-light: #43a047; + --success-dim: rgba(46, 125, 50, 0.1); + --success-subtle: rgba(46, 125, 50, 0.06); + --warning: #ed6c02; + --warning-light: #ff9800; + --warning-dim: rgba(237, 108, 2, 0.1); + --info: #0288d1; + --info-dim: rgba(2, 136, 209, 0.1); + --purple: #7b1fa2; + --purple-light: #9c27b0; + --purple-dim: rgba(123, 31, 162, 0.1); + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.14); + --shadow-dialog: 0 12px 40px rgba(0, 0, 0, 0.18); + + /* Component tokens */ + --tab-active-bg: #d0d0d0; + --scrollbar-track: #e8e8e8; + --scrollbar-thumb: #bbb; + --scrollbar-thumb-hover: #999; + + /* Timeline grid - light tinted lines */ + --timeline-grid-video: #d0d0d8; + --timeline-grid-audio: #c8d8c8; + + /* Preview overlay - frosted glass on dark canvas */ + --preview-overlay-bg: rgba(255, 255, 255, 0.2); + --preview-overlay-bg-hover: rgba(255, 255, 255, 0.35); + + /* Chat bubble */ + --chat-user-bg: #d0d8e8; + --chat-user-border: #b8c4d8; + + /* Compat aliases */ + --accent-primary: var(--accent); + --accent-danger: #ef5350; +} + +/* ═══════════════════════════════════════════ + THEME: MIDNIGHT (OLED) + True black backgrounds, slightly brighter text. + ═══════════════════════════════════════════ */ + +[data-theme="midnight"] { + /* True black backgrounds */ + --bg-primary: #000000; + --bg-secondary: #080808; + --bg-tertiary: #111111; + --bg-hover: #1a1a1a; + --bg-active: #1e1e1e; + --bg-elevated: #141414; + --bg-input: #0a0a0a; + --bg-overlay: rgba(0, 0, 0, 0.85); + --bg-overlay-light: rgba(0, 0, 0, 0.6); + + /* Borders */ + --border-color: #1a1a1a; + --border-subtle: #2a2a2a; + --border-strong: #3a3a3a; + + /* Text */ + --text-primary: #e0e0e0; + --text-secondary: #a0a0a0; + + /* Accent */ + --accent: #3d9df5; + --accent-hover: #5bb0ff; + --accent-dim: rgba(61, 157, 245, 0.15); + --accent-subtle: rgba(61, 157, 245, 0.1); + + /* Shadows */ + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.6); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.7); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.7); + --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.8); + --shadow-dialog: 0 20px 50px rgba(0, 0, 0, 0.8); + + /* Component tokens */ + --tab-active-bg: #1e1e1e; + --scrollbar-track: #080808; + --scrollbar-thumb: #2a2a2a; + --scrollbar-thumb-hover: #3a3a3a; + + /* Timeline grid - deeper black tinted lines */ + --timeline-grid-video: #101018; + --timeline-grid-audio: #081008; +} + +} From f10c95d3377950ab99e7d8afa49eb424c9b48002 Mon Sep 17 00:00:00 2001 From: Sportinger Date: Tue, 12 May 2026 00:29:08 +0200 Subject: [PATCH 3/3] chore(release): bump version for css modularization --- src/changelog-data.json | 8 ++++++++ src/version.ts | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/changelog-data.json b/src/changelog-data.json index 2e560e33f..b3af0986d 100644 --- a/src/changelog-data.json +++ b/src/changelog-data.json @@ -1,4 +1,12 @@ [ + { + "date": "2026-05-12", + "type": "refactor", + "title": "Modular Editor CSS", + "description": "The former App.css monolith has been split into owner-focused CSS files for the timeline, media panel, properties panel, preview, export, common dialogs, and shared app foundations while preserving the final bundled CSS output.", + "section": "Architecture / CSS", + "commits": ["6edc4570"] + }, { "date": "2026-05-09", "type": "improve", diff --git a/src/version.ts b/src/version.ts index 335273ce9..a2779cf12 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,6 +1,6 @@ // App version // Format: MAJOR.MINOR.PATCH -export const APP_VERSION = '1.7.8'; +export const APP_VERSION = '1.7.9'; export interface ChangelogNotice { type: 'info' | 'warning' | 'success' | 'danger'; @@ -36,15 +36,15 @@ export const FEATURED_VIDEO: { // Build/Platform notice shown at top of changelog (set to null to hide) export const BUILD_NOTICE: ChangelogNotice | null = { type: 'success', - title: 'Board search and fast large projects', - message: 'Project search now supports filters like *.mp4, while the board keeps layouts stable and restores cached placement data for faster large-project loading.', + title: 'Modular editor styles', + message: 'The former App.css monolith has been split into owner-focused style files for timeline, panels, preview, export, dialogs, and shared editor foundations.', animated: true, }; export const WIP_NOTICE: ChangelogNotice | null = { type: 'info', - title: 'Large media board polish', - message: 'Board folders, loose items, search dimming, and overview rendering received another round of interaction and performance hardening.', + title: 'CSS ownership cleanup', + message: 'Styles now live beside their owning components while Vite still merges the final CSS bundle for production.', animated: true, };