Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
:root {
--bg: #ffffff;
--surface: #f1f5f9;
--border: #cbd5e1;
--border: #94a3b8;
--text: #0f172a;
--muted: #64748b;
--muted: #475569;
--accent: #3b82f6;
--accent-hover: #1d4ed8;
--accent-muted: rgba(59, 130, 246, 0.12);
Expand Down
34 changes: 17 additions & 17 deletions src/components/ExportSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ export default function ExportSettings({

return (
<>
<div>
<div className="bg-[var(--surface)] border border-[var(--border)] rounded-xl p-5 shadow-sm">
<div className="flex items-center justify-between mb-2">
<label
htmlFor="quality-control"
className="text-sm font-heading font-semibold uppercase tracking-wider text-[var(--muted)] flex items-center gap-2"
className="text-sm font-heading font-semibold text-[var(--text)] flex items-center gap-2"
>
<SlidersHorizontal size={10} />
<SlidersHorizontal size={14} className="text-film-600" />
Quality

<span
Expand Down Expand Up @@ -92,7 +92,7 @@ export default function ExportSettings({

<div
id="quality-description"
className="mt-1 space-y-3"
className="mt-3 space-y-4"
>
<div className="flex justify-between">
<span className="text-sm text-[var(--muted)]">
Expand All @@ -104,7 +104,7 @@ export default function ExportSettings({
</span>
</div>

<p className="text-xs text-[var(--muted)]">
<p className="text-sm text-[var(--muted)] leading-relaxed">
Estimated size:{" "}
<span className="font-semibold text-[var(--text)]">
{estimatedSize}
Expand Down Expand Up @@ -141,13 +141,13 @@ export default function ExportSettings({
)}
</div>

<div>
<div className="bg-[var(--surface)] border border-[var(--border)] rounded-xl p-4 shadow-sm space-y-2">
<div className="flex items-center justify-between mb-1">
<label
htmlFor="stabilization-toggle"
className="text-sm font-heading font-semibold uppercase tracking-wider text-[var(--muted)] flex items-center gap-2"
className="text-sm font-heading font-semibold text-[var(--text)] flex items-center gap-2"
>
<SlidersHorizontal size={10} />
<SlidersHorizontal size={14} className="text-film-600" />
Stabilization
</label>

Expand All @@ -162,19 +162,19 @@ export default function ExportSettings({
})
}
aria-label="Enable video stabilization"
className="w-full accent-film-600 cursor-pointer"
className="w-4 h-4 accent-film-600 cursor-pointer"
/>
</span>
</div>

<p className="text-xs text-[var(--muted)] mb-1">
<p className="text-sm text-[var(--muted)] leading-relaxed">
Reduce camera shake
</p>

<div className="flex justify-end">
<span
className={cn(
"text-xs",
"text-sm",
recipe.stabilization
? "text-[var(--error)] font-medium"
: "text-[var(--muted)]"
Expand All @@ -184,13 +184,13 @@ export default function ExportSettings({
</span>
</div>
</div>
<div>
<div className="bg-[var(--surface)] border border-[var(--border)] rounded-xl p-4 shadow-sm space-y-2">
<div className="flex items-center justify-between mb-1">
<label
htmlFor="denoise-toggle"
className="text-sm font-heading font-semibold uppercase tracking-wider text-[var(--muted)] flex items-center gap-2"
className="text-sm font-heading font-semibold text-[var(--text)] flex items-center gap-2"
>
<SlidersHorizontal size={10} />
<SlidersHorizontal size={14} className="text-film-600" />
Reduce noise

<span
Expand All @@ -213,19 +213,19 @@ export default function ExportSettings({
}
aria-label="Enable noise reduction"
aria-checked={recipe.denoise}
className="w-full accent-film-600 cursor-pointer"
className="w-4 h-4 accent-film-600 cursor-pointer"
/>
</span>
</div>

<p className="text-xs text-[var(--muted)] mb-1">
<p className="text-sm text-[var(--muted)] leading-relaxed">
Reduce low-light video grain
</p>

<div className="flex justify-end">
<span
className={cn(
"text-xs",
"text-sm",
recipe.denoise
? "text-red-700 font-medium"
: "text-[var(--muted)]"
Expand Down
167 changes: 92 additions & 75 deletions src/components/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,98 @@ export default function FileUpload({

onFileSelect(file);
}, [onFileSelect]);
// ── Drop zone (inner) handler ─────────────────────────
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragging(false);

const file = e.dataTransfer.files?.[0];

if (file) {
handleFile(file);
}
};
// ── Drop zone (inner) ─────────────────────────────────
const DropZone = () => (
<div
id="upload-zone"
role="button"
tabIndex={0}
aria-label="Video upload area. Drag and drop a video file or press Enter to browse."
onDragOver={(e) => {
e.preventDefault();
setDragging(true);
}}
onDragLeave={() => setDragging(false)}
onDrop={handleDrop}
onClick={() => inputRef.current?.click()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
inputRef.current?.click();
}
}}
className={cn(
"group flex flex-col items-center justify-center gap-5 py-14 px-6",
"border-2 border-dashed rounded-xl cursor-pointer transition-all duration-300 relative overflow-hidden",
dragging
? "border-[var(--accent)] bg-[var(--accent-muted)] scale-[1.02] shadow-[var(--shadow)] ring-4 ring-[var(--accent-muted)]"
: "border-[var(--border)] bg-[var(--surface)] hover:border-film-400 hover:bg-film-50/40 shadow-sm"
)}
>
{dragging && (
<div className="absolute inset-0 -translate-x-full animate-shimmer bg-gradient-to-r from-transparent via-film-500/20 to-transparent pointer-events-none" />
)}

{/* Upload Animation */}
<div className="w-20 h-20 opacity-100 group-hover:scale-110 transition-all duration-200">
<LottiePlayer animationData={uploadAnim} loop autoplay />
</div>

{/* Main Text */}
<div className="text-center">
<p className="font-heading font-semibold text-[var(--text)] text-lg">
{dragging ? "Release to upload" : "Drag & Drop your video here"}
</p>

<p className="text-sm text-[var(--muted)] mt-2 font-medium">
or click to browse
</p>

<p className="text-sm text-[var(--muted)] mt-2 font-heading">
Ctrl+O / Cmd+O
</p>
</div>

// ── Drop zone (inner) handler ─────────────────────────
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragging(false);
const file = e.dataTransfer.files?.[0];
if (file) handleFile(file);
};
{/* File Types */}
<div className="flex items-center gap-2 px-4 py-2.5 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-sm font-heading font-medium text-[var(--text)] shadow-sm">
<FolderOpen size={15} />
MP4 / MOV / AVI / WebM
</div>

{/* Support Text */}
<p className="text-sm text-[var(--muted)] text-center max-w-md leading-relaxed">
Supports MP4, MOV, AVI, MKV, WebM, and most video formats up to 2GB
</p>

{/* Error */}
{fileError && (
<p className="text-sm text-[var(--error)] text-center font-medium">
{fileError}
</p>
)}

<input
ref={inputRef}
type="file"
accept="video/*"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) handleFile(f);
}}
/>
</div>
);

// ── File info (shown after upload) ───────────────────
const FileInfo = () => (
Expand Down Expand Up @@ -190,75 +274,8 @@ export default function FileUpload({
);

// ── Drop zone (inner) ─────────────────────────────────
const DropZone = () => (
<div
id="upload-zone"
role="button"
tabIndex={0}
aria-label="Video upload area. Drag and drop a video file or press Enter to browse."
onDragOver={(e) => {
e.preventDefault();
setDragging(true);
}}
onDragLeave={() => setDragging(false)}
onDrop={handleDrop}
onClick={() => inputRef.current?.click()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
inputRef.current?.click();
}
}}
className={cn(
"group flex flex-col items-center justify-center gap-4 py-12 px-6",
"border-2 border-dashed rounded-xl cursor-pointer transition-all duration-300 relative overflow-hidden",
dragging
? "border-[var(--accent)] bg-[var(--accent-muted)] scale-[1.02] shadow-[var(--shadow)] ring-4 ring-[var(--accent-muted)]"
: "border-[var(--border)] bg-[var(--bg)] hover:border-film-400 hover:bg-film-50/40"
)}
>
{dragging && (
<div className="absolute inset-0 -translate-x-full animate-shimmer bg-gradient-to-r from-transparent via-film-500/20 to-transparent pointer-events-none" />
)}

<div className="w-20 h-20 opacity-80 group-hover:opacity-100 transition-opacity group-hover:scale-110 duration-200">
<LottiePlayer animationData={uploadAnim} loop autoplay />
</div>

<div className="text-center">
<p className="font-heading font-semibold text-[var(--text)] text-base">
{dragging ? "Release to upload" : "Drag & Drop your video here"}
</p>
<p className="text-sm text-[var(--muted)] mt-1">or click to browse</p>
<p className="text-xs text-[var(--muted)] mt-2 font-heading">
Ctrl+O / Cmd+O
</p>
</div>

<div className="flex items-center gap-2 px-4 py-2 bg-[var(--surface)] border border-[var(--border)] rounded-lg text-sm font-heading font-medium text-[var(--muted)]">
<FolderOpen size={14} />
MP4 / MOV / AVI / WebM
</div>

<p className="text-xs text-[var(--muted)] text-center">
Supports: MP4, MOV, AVI, MKV, WebM, and most video formats up to 2GB
</p>

{fileError && (
<p className="text-sm text-[var(--error)] text-center">{fileError}</p>
)}

<input
ref={inputRef}
type="file"
accept="video/*"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) handleFile(f);
}}
/>
</div>
);


return (
<>
Expand Down
Loading