fix: CSS-grid layout to preserve video on toggle + magnification enhancements (brighter, larger, minor ticks)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 17:30:47 -04:00
parent 124542b4c4
commit 23b30be357
2 changed files with 162 additions and 153 deletions

View File

@@ -37,6 +37,8 @@
let sidebarResizeStartWidth = $state(0);
let windowWidth = $state(window.innerWidth);
let isLeft = $derived(preferences.clipListPosition === 'left');
$effect(() => {
loadPreferences();
});
@@ -238,106 +240,79 @@
<button class="prefs-btn" onclick={() => (showPreferences = true)} title="Preferences">⚙</button>
</header>
{#if preferences.clipListPosition === 'bottom'}
<main class="content">
<main class="content" class:layout-left={isLeft} style:--sidebar-width="{preferences.clipListWidth}px">
<!-- Sidebar clip list: visible only in left mode -->
<div class="sidebar-area" class:hidden={!isLeft}>
<ClipList onExport={() => (showExportDialog = true)} position="left" />
</div>
<div
class="sidebar-handle"
class:hidden={!isLeft}
class:active={isSidebarResizing}
role="slider"
aria-orientation="vertical"
aria-label="Resize clip list sidebar"
aria-valuenow={preferences.clipListWidth}
aria-valuemin={150}
aria-valuemax={Math.round(windowWidth * 0.4)}
tabindex="0"
onmousedown={handleSidebarResizeStart}
onkeydown={(e) => {
const maxWidth = Math.round(windowWidth * 0.4);
const minWidth = Math.min(150, maxWidth);
if (e.key === 'ArrowRight') {
e.preventDefault();
setClipListWidth(
Math.max(minWidth, Math.min(maxWidth, preferences.clipListWidth + 10)),
);
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
setClipListWidth(
Math.max(minWidth, Math.min(maxWidth, preferences.clipListWidth - 10)),
);
}
}}
></div>
<!-- Video + Transport: always rendered, never destroyed -->
<div class="video-area">
<VideoPlayer />
<TransportControls bind:this={transportControls} />
<div class="timeline-clip-area" bind:this={splitAreaEl}>
<div class="timeline-pane" style="height: {timelineHeight}px">
<Timeline />
</div>
<div
class="resize-handle"
class:active={isResizing}
role="slider"
aria-orientation="horizontal"
aria-label="Resize timeline and clip list"
aria-valuenow={timelineHeight}
aria-valuemin={40}
aria-valuemax={500}
tabindex="0"
onmousedown={handleResizeStart}
onkeydown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
timelineHeight = Math.min(500, timelineHeight + 10);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
timelineHeight = Math.max(40, timelineHeight - 10);
}
}}
></div>
<div class="cliplist-pane">
<ClipList onExport={() => (showExportDialog = true)} />
</div>
</div>
<!-- Timeline section: always full width -->
<div class="lower-section" bind:this={splitAreaEl}>
<div
class="resize-handle"
class:active={isResizing}
role="slider"
aria-orientation="horizontal"
aria-label="Resize timeline"
aria-valuenow={timelineHeight}
aria-valuemin={40}
aria-valuemax={500}
tabindex="0"
onmousedown={handleResizeStart}
onkeydown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
timelineHeight = Math.min(500, timelineHeight + 10);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
timelineHeight = Math.max(40, timelineHeight - 10);
}
}}
></div>
<div class="timeline-pane" style="height: {timelineHeight}px">
<Timeline />
</div>
</main>
{:else}
<main class="content content-left">
<div class="upper-section">
<div class="cliplist-sidebar" style="width: {preferences.clipListWidth}px">
<ClipList onExport={() => (showExportDialog = true)} position="left" />
</div>
<div
class="sidebar-resize-handle"
class:active={isSidebarResizing}
role="slider"
aria-orientation="vertical"
aria-label="Resize clip list sidebar"
aria-valuenow={preferences.clipListWidth}
aria-valuemin={150}
aria-valuemax={Math.round(windowWidth * 0.4)}
tabindex="0"
onmousedown={handleSidebarResizeStart}
onkeydown={(e) => {
const maxWidth = Math.round(windowWidth * 0.4);
const minWidth = Math.min(150, maxWidth);
if (e.key === 'ArrowRight') {
e.preventDefault();
setClipListWidth(
Math.max(minWidth, Math.min(maxWidth, preferences.clipListWidth + 10)),
);
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
setClipListWidth(
Math.max(minWidth, Math.min(maxWidth, preferences.clipListWidth - 10)),
);
}
}}
></div>
<div class="right-column">
<VideoPlayer />
<TransportControls bind:this={transportControls} />
</div>
</div>
<div class="timeline-clip-area" bind:this={splitAreaEl}>
<div
class="resize-handle"
class:active={isResizing}
role="slider"
aria-orientation="horizontal"
aria-label="Resize upper section and timeline"
aria-valuenow={timelineHeight}
aria-valuemin={40}
aria-valuemax={500}
tabindex="0"
onmousedown={handleResizeStart}
onkeydown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
timelineHeight = Math.min(500, timelineHeight + 10);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
timelineHeight = Math.max(40, timelineHeight - 10);
}
}}
></div>
<div class="timeline-pane" style="height: {timelineHeight}px">
<Timeline />
</div>
</div>
</main>
{/if}
</div>
<!-- Bottom clip list: visible only in bottom mode -->
<div class="bottom-cliplist" class:hidden={isLeft}>
<ClipList onExport={() => (showExportDialog = true)} />
</div>
</main>
<StatusBar onOpenAbout={() => (showAboutDialog = true)} />
</div>
@@ -358,20 +333,78 @@
gap: 8px;
}
/* --- Grid layout: bottom mode (default) --- */
.content {
flex: 1;
display: flex;
flex-direction: column;
display: grid;
grid-template-areas:
"video"
"lower"
"bottom-clips";
grid-template-columns: 1fr;
grid-template-rows: 1fr auto auto;
overflow: hidden;
}
.timeline-clip-area {
.video-area {
grid-area: video;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.lower-section {
grid-area: lower;
display: flex;
flex-direction: column;
flex-shrink: 0;
min-height: 80px;
}
.bottom-cliplist {
grid-area: bottom-clips;
min-height: 20px;
overflow: hidden;
}
/* Sidebar elements hidden in bottom mode */
.sidebar-area {
grid-area: sidebar;
overflow-y: auto;
overflow-x: hidden;
border-right: 1px solid var(--border);
background: var(--bg-secondary);
}
.sidebar-handle {
grid-area: handle;
width: 5px;
background: var(--border);
cursor: ew-resize;
flex-shrink: 0;
transition: background 0.1s;
}
.sidebar-handle:hover,
.sidebar-handle.active {
background: var(--accent);
}
.hidden {
display: none !important;
}
/* --- Grid layout: left mode --- */
.content.layout-left {
grid-template-areas:
"sidebar handle video"
"lower lower lower"
"bottom-clips bottom-clips bottom-clips";
grid-template-columns: var(--sidebar-width, 220px) 5px 1fr;
grid-template-rows: 1fr auto 0;
}
.timeline-pane {
flex-shrink: 0;
min-height: 40px;
@@ -391,55 +424,6 @@
background: var(--accent, #89b4fa);
}
.cliplist-pane {
flex: 1;
min-height: 20px;
overflow: hidden;
}
.content-left {
display: flex;
flex-direction: column;
overflow: hidden;
}
.upper-section {
flex: 1;
display: flex;
flex-direction: row;
min-height: 0;
overflow: hidden;
}
.cliplist-sidebar {
flex-shrink: 0;
overflow-y: auto;
overflow-x: hidden;
border-right: 1px solid var(--border);
background: var(--bg-secondary);
}
.sidebar-resize-handle {
width: 5px;
background: var(--border);
cursor: ew-resize;
flex-shrink: 0;
transition: background 0.1s;
}
.sidebar-resize-handle:hover,
.sidebar-resize-handle.active {
background: var(--accent);
}
.right-column {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
}
.layout-btn {
font-size: 16px;
padding: 4px 8px;