feat: scaffold Tauri v2 + Svelte 5 project with base layout and utilities
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
47
src/App.svelte
Normal file
47
src/App.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import UrlInput from '$lib/components/UrlInput.svelte';
|
||||
import VideoPlayer from '$lib/components/VideoPlayer.svelte';
|
||||
import TransportControls from '$lib/components/TransportControls.svelte';
|
||||
import Timeline from '$lib/components/Timeline.svelte';
|
||||
import ClipList from '$lib/components/ClipList.svelte';
|
||||
import StatusBar from '$lib/components/StatusBar.svelte';
|
||||
</script>
|
||||
|
||||
<div class="app-shell">
|
||||
<header class="toolbar">
|
||||
<UrlInput />
|
||||
</header>
|
||||
|
||||
<main class="content">
|
||||
<VideoPlayer />
|
||||
<TransportControls />
|
||||
<Timeline />
|
||||
<ClipList />
|
||||
</main>
|
||||
|
||||
<StatusBar />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
72
src/app.css
Normal file
72
src/app.css
Normal file
@@ -0,0 +1,72 @@
|
||||
:root {
|
||||
--bg-primary: #1e1e2e;
|
||||
--bg-secondary: #2a2a3e;
|
||||
--bg-tertiary: #353550;
|
||||
--text-primary: #cdd6f4;
|
||||
--text-secondary: #a6adc8;
|
||||
--text-muted: #6c7086;
|
||||
--accent: #89b4fa;
|
||||
--accent-hover: #74c7ec;
|
||||
--danger: #f38ba8;
|
||||
--success: #a6e3a1;
|
||||
--warning: #f9e2af;
|
||||
--border: #45475a;
|
||||
--clip-handle: #ffffff;
|
||||
--timeline-bg: #181825;
|
||||
--placeholder-gradient: linear-gradient(90deg, #2a2a3e 25%, #353550 50%, #2a2a3e 75%);
|
||||
--font-mono: 'SF Mono', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--accent);
|
||||
color: var(--bg-primary);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='text']:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
16
src/lib/components/ClipList.svelte
Normal file
16
src/lib/components/ClipList.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="clip-list">
|
||||
<div class="placeholder-text">No clips yet — press I to mark in-point</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.clip-list {
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
min-height: 80px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.placeholder-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
16
src/lib/components/StatusBar.svelte
Normal file
16
src/lib/components/StatusBar.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="status-bar">
|
||||
<span class="status-text">Ready</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
min-height: 24px;
|
||||
}
|
||||
</style>
|
||||
17
src/lib/components/Timeline.svelte
Normal file
17
src/lib/components/Timeline.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<div class="timeline">
|
||||
<div class="placeholder-text">Timeline</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline {
|
||||
height: 160px;
|
||||
background: var(--timeline-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.placeholder-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
19
src/lib/components/TransportControls.svelte
Normal file
19
src/lib/components/TransportControls.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="transport-controls">
|
||||
<span class="placeholder-text">Transport controls</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.transport-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
min-height: 40px;
|
||||
}
|
||||
.placeholder-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
12
src/lib/components/UrlInput.svelte
Normal file
12
src/lib/components/UrlInput.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="url-input">
|
||||
<input type="text" placeholder="Paste a video URL..." />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.url-input {
|
||||
flex: 1;
|
||||
}
|
||||
.url-input input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
18
src/lib/components/VideoPlayer.svelte
Normal file
18
src/lib/components/VideoPlayer.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<div class="video-player">
|
||||
<div class="placeholder">Paste a URL above to load a video</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.video-player {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #000;
|
||||
min-height: 200px;
|
||||
}
|
||||
.placeholder {
|
||||
color: var(--text-muted);
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
14
src/lib/utils/colors.ts
Normal file
14
src/lib/utils/colors.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const CLIP_COLORS = [
|
||||
'#4A90D9',
|
||||
'#E5734A',
|
||||
'#5CB85C',
|
||||
'#D94A7A',
|
||||
'#D9A54A',
|
||||
'#7B68EE',
|
||||
'#20B2AA',
|
||||
'#FF6B6B',
|
||||
] as const;
|
||||
|
||||
export function getClipColor(index: number): string {
|
||||
return CLIP_COLORS[index % CLIP_COLORS.length];
|
||||
}
|
||||
36
src/lib/utils/time.ts
Normal file
36
src/lib/utils/time.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export function formatTime(totalSeconds: number): string {
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(totalSeconds % 60);
|
||||
const millis = Math.round((totalSeconds % 1) * 1000);
|
||||
|
||||
const mm = String(minutes).padStart(2, '0');
|
||||
const ss = String(seconds).padStart(2, '0');
|
||||
const ms = String(millis).padStart(3, '0');
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${mm}:${ss}.${ms}`;
|
||||
}
|
||||
return `${mm}:${ss}.${ms}`;
|
||||
}
|
||||
|
||||
export function parseTime(str: string): number | null {
|
||||
const hmsMatch = str.match(/^(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/);
|
||||
if (hmsMatch) {
|
||||
const h = parseInt(hmsMatch[1], 10);
|
||||
const m = parseInt(hmsMatch[2], 10);
|
||||
const s = parseInt(hmsMatch[3], 10);
|
||||
const ms = hmsMatch[4] ? parseInt(hmsMatch[4].padEnd(3, '0'), 10) : 0;
|
||||
return h * 3600 + m * 60 + s + ms / 1000;
|
||||
}
|
||||
|
||||
const msMatch = str.match(/^(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/);
|
||||
if (msMatch) {
|
||||
const m = parseInt(msMatch[1], 10);
|
||||
const s = parseInt(msMatch[2], 10);
|
||||
const ms = msMatch[3] ? parseInt(msMatch[3].padEnd(3, '0'), 10) : 0;
|
||||
return m * 60 + s + ms / 1000;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
10
src/main.ts
Normal file
10
src/main.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { mount } from 'svelte';
|
||||
import App from './App.svelte';
|
||||
import './app.css';
|
||||
|
||||
const target = document.getElementById('app');
|
||||
if (!target) {
|
||||
throw new Error('Missing #app mount target');
|
||||
}
|
||||
|
||||
mount(App, { target });
|
||||
2
src/vite-env.d.ts
vendored
Normal file
2
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
Reference in New Issue
Block a user