feat(web): responsive layout for phone and tablet (header stack, padding scale, seed-row wrap)
Browse files
web/src/components/LoadingBanner.tsx
CHANGED
|
@@ -3,7 +3,7 @@ type Props = { visible: boolean; message: string };
|
|
| 3 |
export default function LoadingBanner({ visible, message }: Props) {
|
| 4 |
if (!visible) return null;
|
| 5 |
return (
|
| 6 |
-
<div className="border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/10 px-8 py-2.5">
|
| 7 |
<div className="flex items-center gap-3">
|
| 8 |
<span className="size-1.5 rounded-full bg-[hsl(var(--ember))] animate-pulse-dot" />
|
| 9 |
<span className="label-mono text-[hsl(var(--ember))]">{message}</span>
|
|
|
|
| 3 |
export default function LoadingBanner({ visible, message }: Props) {
|
| 4 |
if (!visible) return null;
|
| 5 |
return (
|
| 6 |
+
<div className="border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/10 px-4 sm:px-8 py-2.5">
|
| 7 |
<div className="flex items-center gap-3">
|
| 8 |
<span className="size-1.5 rounded-full bg-[hsl(var(--ember))] animate-pulse-dot" />
|
| 9 |
<span className="label-mono text-[hsl(var(--ember))]">{message}</span>
|
web/src/components/ParamsPanel.tsx
CHANGED
|
@@ -18,7 +18,7 @@ function renderControl(
|
|
| 18 |
return (
|
| 19 |
<div key={s.name} className="space-y-1.5">
|
| 20 |
<label htmlFor={id} className="label-mono">{s.label}</label>
|
| 21 |
-
<div className="flex items-center gap-3">
|
| 22 |
<input
|
| 23 |
id={id}
|
| 24 |
aria-label={s.label}
|
|
@@ -27,7 +27,7 @@ function renderControl(
|
|
| 27 |
step={s.step ?? 1}
|
| 28 |
value={v}
|
| 29 |
onChange={(e) => set(s.name, Number(e.target.value))}
|
| 30 |
-
className="field-input !w-44 font-mono text-[12px] py-1"
|
| 31 |
/>
|
| 32 |
<button
|
| 33 |
type="button"
|
|
|
|
| 18 |
return (
|
| 19 |
<div key={s.name} className="space-y-1.5">
|
| 20 |
<label htmlFor={id} className="label-mono">{s.label}</label>
|
| 21 |
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
| 22 |
<input
|
| 23 |
id={id}
|
| 24 |
aria-label={s.label}
|
|
|
|
| 27 |
step={s.step ?? 1}
|
| 28 |
value={v}
|
| 29 |
onChange={(e) => set(s.name, Number(e.target.value))}
|
| 30 |
+
className="field-input !w-40 sm:!w-44 font-mono text-[12px] py-1"
|
| 31 |
/>
|
| 32 |
<button
|
| 33 |
type="button"
|
web/src/components/ProgressBar.tsx
CHANGED
|
@@ -12,9 +12,9 @@ export default function ProgressBar() {
|
|
| 12 |
|
| 13 |
if (state.phase === "error") {
|
| 14 |
return (
|
| 15 |
-
<div className="sticky top-0 z-40 border-b border-red-900/40 bg-red-950/80 backdrop-blur-md px-8 py-2.5">
|
| 16 |
<span className="label-mono text-red-400">progress error</span>
|
| 17 |
-
<span className="ml-3 text-sm text-red-300/90">{state.message}</span>
|
| 18 |
</div>
|
| 19 |
);
|
| 20 |
}
|
|
@@ -37,12 +37,12 @@ export default function ProgressBar() {
|
|
| 37 |
: `Generating 路 ${fmt(elapsedS)}`;
|
| 38 |
|
| 39 |
return (
|
| 40 |
-
<div className="sticky top-0 z-40 border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/15 backdrop-blur-md px-8 py-2">
|
| 41 |
-
<div className="flex items-center gap-4">
|
| 42 |
-
<span className="label-mono text-[hsl(var(--ember))] whitespace-nowrap">
|
| 43 |
{label}
|
| 44 |
</span>
|
| 45 |
-
<div className="flex-1 h-1 bg-[hsl(var(--ember))]/20 rounded-sm overflow-hidden">
|
| 46 |
{fill === null ? (
|
| 47 |
<div className="h-full w-1/3 bg-[hsl(var(--ember))] animate-progress-stripe" />
|
| 48 |
) : (
|
|
|
|
| 12 |
|
| 13 |
if (state.phase === "error") {
|
| 14 |
return (
|
| 15 |
+
<div className="sticky top-0 z-40 border-b border-red-900/40 bg-red-950/80 backdrop-blur-md px-4 sm:px-8 py-2.5">
|
| 16 |
<span className="label-mono text-red-400">progress error</span>
|
| 17 |
+
<span className="ml-3 text-sm text-red-300/90 break-words">{state.message}</span>
|
| 18 |
</div>
|
| 19 |
);
|
| 20 |
}
|
|
|
|
| 37 |
: `Generating 路 ${fmt(elapsedS)}`;
|
| 38 |
|
| 39 |
return (
|
| 40 |
+
<div className="sticky top-0 z-40 border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/15 backdrop-blur-md px-4 sm:px-8 py-2">
|
| 41 |
+
<div className="flex items-center gap-3 sm:gap-4">
|
| 42 |
+
<span className="label-mono text-[hsl(var(--ember))] whitespace-nowrap text-[10px] sm:text-[10.5px]">
|
| 43 |
{label}
|
| 44 |
</span>
|
| 45 |
+
<div className="flex-1 min-w-0 h-1 bg-[hsl(var(--ember))]/20 rounded-sm overflow-hidden">
|
| 46 |
{fill === null ? (
|
| 47 |
<div className="h-full w-1/3 bg-[hsl(var(--ember))] animate-progress-stripe" />
|
| 48 |
) : (
|
web/src/pages/Studio.tsx
CHANGED
|
@@ -28,7 +28,7 @@ function SectionHeader({ num, title, hint }: { num: string; title: string; hint?
|
|
| 28 |
<div className="space-y-1">
|
| 29 |
<div className="flex items-baseline gap-3">
|
| 30 |
<span className="marker-num">{num}</span>
|
| 31 |
-
<h2 className="display-serif text-[22px] leading-tight">{title}</h2>
|
| 32 |
</div>
|
| 33 |
{hint && <p className="label-mono">{hint}</p>}
|
| 34 |
<div className="rule-dotted mt-2" />
|
|
@@ -181,12 +181,12 @@ export default function Studio() {
|
|
| 181 |
return (
|
| 182 |
<div className="min-h-screen relative-z animate-fade-up">
|
| 183 |
<header className="border-b border-border">
|
| 184 |
-
<div className="mx-auto max-w-[1280px] px-8 py-5 flex items-end justify-between">
|
| 185 |
-
<div className="flex items-end gap-4">
|
| 186 |
-
<span className="display-serif text-[34px] leading-none">Chatterbox</span>
|
| 187 |
-
<span className="label-mono pb-1">voice studio 路 v0.2</span>
|
| 188 |
</div>
|
| 189 |
-
<div className="flex items-center gap-6">
|
| 190 |
<ModeToggle mode={mode} onChange={setMode} />
|
| 191 |
{mode === "single" && (
|
| 192 |
<ModelPicker
|
|
@@ -207,13 +207,13 @@ export default function Studio() {
|
|
| 207 |
/>
|
| 208 |
<ProgressBar />
|
| 209 |
{err && (
|
| 210 |
-
<div className="border-b border-red-900/40 bg-red-950/30 px-8 py-2.5">
|
| 211 |
<span className="label-mono text-red-400">error</span>
|
| 212 |
-
<span className="ml-3 text-sm text-red-300/90">{err}</span>
|
| 213 |
</div>
|
| 214 |
)}
|
| 215 |
|
| 216 |
-
<main className="mx-auto max-w-[1280px] px-8 py-10 grid lg:grid-cols-[minmax(0,1fr)_400px] gap-12">
|
| 217 |
<section className="space-y-12">
|
| 218 |
{mode === "single" ? (
|
| 219 |
<>
|
|
@@ -355,10 +355,10 @@ export default function Studio() {
|
|
| 355 |
</aside>
|
| 356 |
</main>
|
| 357 |
|
| 358 |
-
<footer className="border-t border-border mt-16">
|
| 359 |
<MadeBy />
|
| 360 |
-
<div className="rule-dotted mx-8" />
|
| 361 |
-
<div className="mx-auto max-w-[1280px] px-8 py-6 flex items-center justify-between">
|
| 362 |
<span className="label-mono">chatterbox 路 resemble ai</span>
|
| 363 |
<span className="label-mono">stateless 路 browser-persisted</span>
|
| 364 |
</div>
|
|
|
|
| 28 |
<div className="space-y-1">
|
| 29 |
<div className="flex items-baseline gap-3">
|
| 30 |
<span className="marker-num">{num}</span>
|
| 31 |
+
<h2 className="display-serif text-[19px] sm:text-[22px] leading-tight">{title}</h2>
|
| 32 |
</div>
|
| 33 |
{hint && <p className="label-mono">{hint}</p>}
|
| 34 |
<div className="rule-dotted mt-2" />
|
|
|
|
| 181 |
return (
|
| 182 |
<div className="min-h-screen relative-z animate-fade-up">
|
| 183 |
<header className="border-b border-border">
|
| 184 |
+
<div className="mx-auto max-w-[1280px] px-4 sm:px-8 py-4 sm:py-5 flex flex-col lg:flex-row lg:items-end lg:justify-between gap-3 lg:gap-6">
|
| 185 |
+
<div className="flex items-end gap-3 sm:gap-4">
|
| 186 |
+
<span className="display-serif text-[26px] sm:text-[34px] leading-none">Chatterbox</span>
|
| 187 |
+
<span className="label-mono pb-0.5 sm:pb-1 whitespace-nowrap">voice studio 路 v0.2</span>
|
| 188 |
</div>
|
| 189 |
+
<div className="flex flex-wrap items-center gap-3 sm:gap-6">
|
| 190 |
<ModeToggle mode={mode} onChange={setMode} />
|
| 191 |
{mode === "single" && (
|
| 192 |
<ModelPicker
|
|
|
|
| 207 |
/>
|
| 208 |
<ProgressBar />
|
| 209 |
{err && (
|
| 210 |
+
<div className="border-b border-red-900/40 bg-red-950/30 px-4 sm:px-8 py-2.5">
|
| 211 |
<span className="label-mono text-red-400">error</span>
|
| 212 |
+
<span className="ml-3 text-sm text-red-300/90 break-words">{err}</span>
|
| 213 |
</div>
|
| 214 |
)}
|
| 215 |
|
| 216 |
+
<main className="mx-auto max-w-[1280px] px-4 sm:px-8 py-6 sm:py-10 grid lg:grid-cols-[minmax(0,1fr)_400px] gap-8 lg:gap-12">
|
| 217 |
<section className="space-y-12">
|
| 218 |
{mode === "single" ? (
|
| 219 |
<>
|
|
|
|
| 355 |
</aside>
|
| 356 |
</main>
|
| 357 |
|
| 358 |
+
<footer className="border-t border-border mt-10 sm:mt-16">
|
| 359 |
<MadeBy />
|
| 360 |
+
<div className="rule-dotted mx-4 sm:mx-8" />
|
| 361 |
+
<div className="mx-auto max-w-[1280px] px-4 sm:px-8 py-5 sm:py-6 flex flex-col sm:flex-row items-center sm:justify-between gap-2 sm:gap-0">
|
| 362 |
<span className="label-mono">chatterbox 路 resemble ai</span>
|
| 363 |
<span className="label-mono">stateless 路 browser-persisted</span>
|
| 364 |
</div>
|