Skip to content
Merged
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
131 changes: 129 additions & 2 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
--error-bg: #fee2e2;
--error-border: #fca5a5;
--error-hover: #fecaca;
--neo-bg: #eceff5;
--neo-hi: rgba(255, 255, 255, 1);
--neo-lo: rgba(150, 165, 190, 0.8);
--skeu-top: #f8fafc;
--skeu-bottom: #cfd6e0;
--skeu-edge: rgba(255, 255, 255, 0.9);
--skeu-cut: rgba(15, 23, 42, 0.25);
}

/* ── Dark mode tokens ── */
Expand All @@ -42,6 +49,13 @@
--error-bg: #7f1d1d;
--error-border: #991b1b;
--error-hover: #991b1b;
--neo-bg: #242836;
--neo-hi: rgba(255, 255, 255, 0.09);
--neo-lo: rgba(0, 0, 0, 0.8);
--skeu-top: #2b3042;
--skeu-bottom: #141722;
--skeu-edge: rgba(255, 255, 255, 0.12);
--skeu-cut: rgba(0, 0, 0, 0.7);
}

/* ── High contrast mode tokens ── */
Expand All @@ -64,6 +78,13 @@

--film-600: #FF6666;
--film-400: #66FF66;
--neo-bg: #000000;
--neo-hi: transparent;
--neo-lo: transparent;
--skeu-top: #000000;
--skeu-bottom: #000000;
--skeu-edge: transparent;
--skeu-cut: transparent;
}

/* Ensure export overlay text is readable in high-contrast by forcing dark text */
Expand Down Expand Up @@ -98,7 +119,7 @@ html {
body {
margin: 0;
min-height: 100vh;
background-color: var(--bg);
background-color: var(--neo-bg);
color: var(--text);
line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
Expand Down Expand Up @@ -140,6 +161,14 @@ textarea {
color: var(--text);
}

input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
select,
textarea {
background: var(--neo-bg);
border-color: transparent;
box-shadow: inset 3px 3px 7px var(--neo-lo), inset -3px -3px 7px var(--neo-hi);
}

input:focus,
select:focus,
textarea:focus {
Expand Down Expand Up @@ -168,4 +197,102 @@ detail > summary {
}
details > summary::-webkit-details-marker {
display: none;
}
}

/* ── Neomorphism + skeuomorphism ── */
.neo-raised {
background: var(--neo-bg);
border: 1px solid transparent;
box-shadow: 12px 12px 26px var(--neo-lo), -12px -12px 26px var(--neo-hi);
}

.neo-inset {
background: var(--neo-bg);
border: 1px solid transparent;
box-shadow: inset 6px 6px 12px var(--neo-lo), inset -6px -6px 12px var(--neo-hi);
}

.neo-pressable {
background: var(--neo-bg);
box-shadow: 6px 6px 13px var(--neo-lo), -6px -6px 13px var(--neo-hi);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.neo-pressable:hover { transform: translateY(-1px); }
.neo-pressable:active {
transform: translateY(0);
box-shadow: inset 4px 4px 9px var(--neo-lo), inset -4px -4px 9px var(--neo-hi);
}

/* Brushed-metal title plate with screws and letterpress text */
.skeu-plate {
position: relative;
background:
repeating-linear-gradient(90deg, rgba(255,255,255,0.06) 0 1px, transparent 1px 3px),
linear-gradient(160deg, var(--skeu-top), var(--skeu-bottom));
border: 1px solid var(--skeu-cut);
border-radius: 18px;
box-shadow:
inset 0 1px 0 var(--skeu-edge),
inset 0 -3px 6px var(--skeu-cut),
0 2px 0 var(--skeu-edge),
12px 14px 28px var(--neo-lo),
-10px -10px 24px var(--neo-hi);
}
.skeu-plate::before,
.skeu-plate::after {
content: "";
position: absolute;
top: 10px;
width: 10px;
height: 10px;
border-radius: 50%;
background:
linear-gradient(45deg, transparent 44%, var(--skeu-cut) 44% 56%, transparent 56%),
radial-gradient(circle at 30% 30%, var(--skeu-top), var(--skeu-bottom));
box-shadow: inset 0 0 0 1px var(--skeu-cut), 0 1px 0 var(--skeu-edge);
}
.skeu-plate::before { left: 10px; }
.skeu-plate::after { right: 10px; }

.skeu-title {
text-shadow: 0 1px 0 var(--skeu-edge), 0 -1px 0 var(--skeu-cut);
}

.skeu-well {
background: var(--neo-bg);
border-radius: 12px;
box-shadow: inset 3px 3px 7px var(--neo-lo), inset -3px -3px 7px var(--neo-hi);
}

[data-theme="high-contrast"] .skeu-plate,
[data-theme="high-contrast"] .neo-raised,
[data-theme="high-contrast"] .neo-inset,
[data-theme="high-contrast"] .neo-pressable {
border: 1px solid var(--border);
box-shadow: none;
}

/* Hanging sign: tilted from a pivot above, with two wires to the screws */
.skeu-hang {
position: relative;
margin-top: 28px;
transform: rotate(-2.5deg);
transform-origin: 50% -28px;
transition: transform 0.6s cubic-bezier(0.3, 1.6, 0.5, 1);
}
.skeu-hang:hover { transform: rotate(1.5deg); }
.skeu-wire {
position: absolute;
bottom: calc(100% - 14px);
width: 2px;
height: 30px;
background: linear-gradient(90deg, var(--skeu-edge), var(--skeu-cut));
transform-origin: 50% 100%;
pointer-events: none;
}
.skeu-wire-l { left: 14px; transform: rotate(14deg); }
.skeu-wire-r { right: 14px; transform: rotate(-14deg); }
@media (prefers-reduced-motion: reduce) {
.skeu-hang, .skeu-hang:hover { transition: none; }
.skeu-hang:hover { transform: rotate(-2.5deg); }
}
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function Home() {
target="_blank"
rel="noopener noreferrer"
aria-label="View Reframe on GitHub"
className="hidden sm:flex fixed top-4 right-4 md:right-16 z-50 items-center gap-1.5 px-3 py-1.5 rounded-lg border border-[var(--border)] bg-[var(--surface)] text-[10px] font-heading font-semibold uppercase tracking-wider transition-all duration-200 ease-in-out hover:scale-105 hover:border-[var(--accent)] hover:bg-[var(--accent-muted)] hover:shadow-[var(--shadow)]"
className="hidden sm:flex fixed top-4 right-4 md:right-16 z-50 items-center gap-1.5 px-3 py-1.5 rounded-lg neo-pressable text-[10px] font-heading font-semibold uppercase tracking-wider"
>
⭐ Star on GitHub
</a>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DownloadResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion, onT
};

return (
<div className="p-5 bg-[var(--surface)] border border-[var(--border)] rounded-xl space-y-4">
<div className="p-5 rounded-xl neo-raised space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="w-12 h-12 shrink-0">
Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {

export default function Footer() {
return (
<footer className="w-full border-t border-[var(--border)] bg-[var(--bg)] text-[var(--text)] px-6 py-16 mt-20 transition-colors duration-300">
<footer className="w-full bg-[var(--neo-bg)] text-[var(--text)] shadow-[0_-8px_18px_var(--neo-lo)] px-6 py-16 mt-20 transition-colors duration-300">
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-12 gap-y-12 md:gap-x-12">
{/* Brand Section */}
<div className="md:col-span-5 space-y-6">
Expand Down
6 changes: 3 additions & 3 deletions src/components/FormatSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ export default function FormatSelector({ recipe, onChange }: Props) {
aria-label={`Select ${option.label} format`}
aria-pressed={recipe.format === option.id}
className={cn(
"relative px-3 py-2.5 rounded-lg border-2 transition-all",
"relative px-3 py-2.5 rounded-lg border-2 border-transparent transition-all",
"text-xs font-heading font-semibold uppercase tracking-wider",
recipe.format === option.id
? "border-film-600 bg-film-50 text-film-600"
: "border-[var(--border)] bg-[var(--surface)] text-[var(--muted)] hover:border-film-400 hover:text-film-600"
? "neo-inset text-film-600"
: "neo-pressable text-[var(--muted)] hover:text-film-600"
)}
>
{option.label}
Expand Down
6 changes: 3 additions & 3 deletions src/components/FramingControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export default function FramingControl({ recipe, onChange }: Props) {
title={mode === "fit" ? "Fit: Adds black bars (letterbox) to fill empty space" : "Fill: Crops the video to fill the entire frame"}
onClick={() => onChange({ framing: mode })}
className={cn(
"flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-2 py-4 rounded-lg border transition-all duration-150 hover:scale-[1.02] active:scale-[0.98]",
"flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-2 py-4 rounded-lg border border-transparent transition-all duration-150",
active
? "border-film-500 bg-film-50 text-film-700"
: "border-[var(--border)] text-[var(--muted)] hover:border-film-300 bg-[var(--surface)]"
? "neo-inset text-film-600"
: "neo-pressable text-[var(--muted)]"
)}
>
<Icon size={18} aria-hidden="true" />
Expand Down
18 changes: 9 additions & 9 deletions src/components/PresetSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ export default function PresetSelector({ recipe, onChange }: Props) {
aria-pressed={isActive}
onClick={() => onChange({ preset })}
className={cn(
"flex flex-col items-center justify-center gap-1 py-2 px-1 rounded-lg border text-center transition-all duration-150 cursor-pointer hover:scale-[1.04] active:scale-[0.97]",
"flex flex-col items-center justify-center gap-1 py-2 px-1 rounded-lg border border-transparent text-center transition-all duration-150 cursor-pointer",
isActive
? "border-film-500 bg-film-50 text-film-600"
: "border-[var(--border)] bg-[var(--surface)] text-[var(--muted)] hover:border-film-300 hover:bg-film-50/30 hover:text-[var(--text)]",
? "neo-inset text-film-600"
: "neo-pressable text-[var(--muted)] hover:text-[var(--text)]",
)}
>
{icon}
Expand Down Expand Up @@ -201,10 +201,10 @@ export default function PresetSelector({ recipe, onChange }: Props) {
aria-label={`${preset.label.replaceAll(":", " is to ")} output ratio`}
aria-pressed={active}
className={cn(
"min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-1.5 p-3 rounded-lg border text-center transition-all duration-150 cursor-pointer hover:scale-[1.02] active:scale-[0.98]",
"min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-1.5 p-3 rounded-lg border border-transparent text-center transition-all duration-150 cursor-pointer",
active
? "border-film-500 bg-film-50"
: "border-[var(--border)] bg-[var(--surface)] hover:border-film-300 hover:bg-film-50/30",
? "neo-inset"
: "neo-pressable",
)}
>
<RatioBox
Expand Down Expand Up @@ -239,10 +239,10 @@ export default function PresetSelector({ recipe, onChange }: Props) {
aria-pressed={recipe.preset === "custom"}
onClick={() => handlePresetSelect("custom")}
className={cn(
"min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-1.5 p-3 rounded-lg border text-center transition-all duration-150 cursor-pointer hover:scale-[1.02] active:scale-[0.98]",
"min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-1.5 p-3 rounded-lg border border-transparent text-center transition-all duration-150 cursor-pointer",
recipe.preset === "custom"
? "border-film-500 bg-film-50"
: "border-[var(--border)] bg-[var(--surface)] hover:border-film-300 hover:bg-film-50/30",
? "neo-inset"
: "neo-pressable",
)}
>
<Settings2
Expand Down
6 changes: 3 additions & 3 deletions src/components/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ function KeyboardShortcutsPanel({ isMac }: { isMac: boolean }) {
];

return (
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface)] animate-fade-in overflow-hidden">
<div className="rounded-xl animate-fade-in overflow-hidden neo-raised">
<button
type="button"
aria-expanded={open}
Expand Down Expand Up @@ -599,7 +599,7 @@ export default function VideoEditor() {
/>
</AccordionSection>
</div>
<div className="bg-[var(--surface)] rounded-xl border border-[var(--border)] p-5 space-y-6">
<div className="rounded-xl p-5 neo-raised space-y-6">
<AccordionSection
id="audio"
icon={<Volume2 size={12} />}
Expand Down Expand Up @@ -810,7 +810,7 @@ export default function VideoEditor() {
)}
>
{!file && (
<div className="bg-[var(--surface)] border border-[var(--border)] rounded-xl p-4 animate-fade-in">
<div className="rounded-xl p-4 neo-raised animate-fade-in">
<p className="text-[10px] font-heading font-bold text-film-600 uppercase tracking-widest">
Getting Started
</p>
Expand Down
Loading