+
+
+ {
+ 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)),
+ );
+ }
+ }}
+ >
+
+
+
-
-
-
-
-
{
- 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);
- }
- }}
- >
-
- (showExportDialog = true)} />
-
+
+
+
+
+
{
+ 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);
+ }
+ }}
+ >
+
+
-
- {:else}
-
-
-
-
{
- 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)),
- );
- }
- }}
- >
-
-
-
-
-
-
-
{
- 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);
- }
- }}
- >
-
-
-
-
-
- {/if}
+
+
+
+
+ (showExportDialog = true)} />
+
+
(showAboutDialog = true)} />
@@ -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;
diff --git a/src/lib/timeline/renderer.ts b/src/lib/timeline/renderer.ts
index 782f63c..07db195 100644
--- a/src/lib/timeline/renderer.ts
+++ b/src/lib/timeline/renderer.ts
@@ -28,8 +28,8 @@ const WAVEFORM_LANE_HEIGHT = 40;
const CONTROLS_LANE_HEIGHT = 30;
const TICK_HEIGHT = 10;
const BASE_FONT_SIZE = 12;
-const MAGNIFY_BONUS = 6;
-const MAGNIFY_RADIUS = 80;
+const MAGNIFY_BONUS = 10;
+const MAGNIFY_RADIUS = 100;
export function drawTimeline(
ctx: CanvasRenderingContext2D,
@@ -82,6 +82,21 @@ function drawTimeTicks(
ctx.textAlign = 'center';
const baselineY = state.height - CONTROLS_LANE_HEIGHT + TICK_HEIGHT + 14;
+ // Draw minor ticks first (behind major ticks)
+ const minorInterval = tickInterval / 4;
+ const minorTickHeight = TICK_HEIGHT * 0.5;
+ for (let t = startTick - tickInterval; t <= state.visibleEnd + minorInterval; t += minorInterval) {
+ if (t < 0 || t > duration) continue;
+ if (Math.abs(t % tickInterval) < 0.001 || Math.abs(t % tickInterval - tickInterval) < 0.001) continue;
+ const x = timeToX(t, state);
+ ctx.strokeStyle = '#35354a';
+ ctx.beginPath();
+ ctx.moveTo(x, state.height - CONTROLS_LANE_HEIGHT);
+ ctx.lineTo(x, state.height - CONTROLS_LANE_HEIGHT + minorTickHeight);
+ ctx.stroke();
+ }
+
+ // Draw major ticks with labels
for (let t = startTick; t <= state.visibleEnd; t += tickInterval) {
if (t < 0 || t > duration) continue;
const x = timeToX(t, state);
@@ -93,15 +108,25 @@ function drawTimeTicks(
ctx.stroke();
let fontSize = BASE_FONT_SIZE;
+ let scale = 0;
if (mouseX !== null) {
const distance = Math.abs(x - mouseX);
if (distance < MAGNIFY_RADIUS) {
- const scale = (1 + Math.cos((Math.PI * distance) / MAGNIFY_RADIUS)) / 2;
+ scale = (1 + Math.cos((Math.PI * distance) / MAGNIFY_RADIUS)) / 2;
fontSize = BASE_FONT_SIZE + MAGNIFY_BONUS * scale;
}
}
- ctx.fillStyle = '#6c7086';
+ // Interpolate color from muted (#6c7086) to bright (#cdd6f4)
+ if (scale > 0) {
+ const r = Math.round(108 + (205 - 108) * scale);
+ const g = Math.round(112 + (214 - 112) * scale);
+ const b = Math.round(134 + (244 - 134) * scale);
+ ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
+ } else {
+ ctx.fillStyle = '#6c7086';
+ }
+
ctx.font = `${Math.round(fontSize)}px -apple-system, sans-serif`;
const label = formatTickLabel(t);