This commit is contained in:
+149
@@ -123,3 +123,152 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Animation keyframes ── */
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-down {
|
||||||
|
from { opacity: 0; transform: translateY(-12px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-left {
|
||||||
|
from { opacity: 0; transform: translateX(-20px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-in {
|
||||||
|
from { opacity: 0; transform: scale(0.95); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-bounce {
|
||||||
|
0% { opacity: 0; transform: scale(0.5); }
|
||||||
|
60% { opacity: 1; transform: scale(1.08); }
|
||||||
|
100% { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-in {
|
||||||
|
0% { opacity: 0; transform: translateY(24px) scale(0.97); }
|
||||||
|
100% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { background-position: -200% 0; }
|
||||||
|
100% { background-position: 200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-soft {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Utility classes ── */
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fade-in 0.5s ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fade-in-up 0.5s ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-down {
|
||||||
|
animation: fade-in-down 0.4s ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-left {
|
||||||
|
animation: slide-in-left 0.5s ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-in {
|
||||||
|
animation: scale-in 0.4s ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-bounce {
|
||||||
|
animation: scale-bounce 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-float-in {
|
||||||
|
animation: float-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-shimmer {
|
||||||
|
background: linear-gradient(90deg, transparent 25%, var(--primary) 50%, transparent 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-soft {
|
||||||
|
animation: pulse-soft 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stagger delays for children */
|
||||||
|
.stagger-1 { animation-delay: 0.05s; }
|
||||||
|
.stagger-2 { animation-delay: 0.1s; }
|
||||||
|
.stagger-3 { animation-delay: 0.15s; }
|
||||||
|
.stagger-4 { animation-delay: 0.2s; }
|
||||||
|
.stagger-5 { animation-delay: 0.25s; }
|
||||||
|
.stagger-6 { animation-delay: 0.3s; }
|
||||||
|
.stagger-7 { animation-delay: 0.35s; }
|
||||||
|
.stagger-8 { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
/* Button press effect */
|
||||||
|
.btn-press {
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.btn-press:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btn-press:active {
|
||||||
|
transform: translateY(0.5px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card hover lift */
|
||||||
|
.card-hover {
|
||||||
|
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||||
|
}
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth loading skeleton */
|
||||||
|
.skeleton {
|
||||||
|
background: linear-gradient(90deg, var(--muted) 25%, var(--accent) 50%, var(--muted) 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s ease-in-out infinite;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduced motion preference */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.animate-fade-in,
|
||||||
|
.animate-fade-in-up,
|
||||||
|
.animate-fade-in-down,
|
||||||
|
.animate-slide-in-left,
|
||||||
|
.animate-scale-in,
|
||||||
|
.animate-scale-bounce,
|
||||||
|
.animate-float-in {
|
||||||
|
animation: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.btn-press:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function CreateFormPage() {
|
|||||||
Back to forms
|
Back to forms
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<h1 className="mb-6 text-2xl font-bold text-foreground">
|
<h1 className="mb-6 text-2xl font-bold text-foreground animate-fade-in-up">
|
||||||
Create New Form
|
Create New Form
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ export default function CreateFormPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-8 rounded-xl border border-border bg-card p-6 shadow-sm">
|
<div className="mb-8 rounded-xl border border-border bg-card p-6 shadow-sm animate-float-in" style={{ animationDelay: '0.1s' }}>
|
||||||
<div className="h-2 -mx-6 -mt-6 mb-6 rounded-t-xl bg-primary" />
|
<div className="h-2 -mx-6 -mt-6 mb-6 rounded-t-xl bg-primary" />
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<FormInput
|
<FormInput
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export default function EditFormPage() {
|
|||||||
Back to form
|
Back to form
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<h1 className="mb-6 text-2xl font-bold text-foreground">
|
<h1 className="mb-6 text-2xl font-bold text-foreground animate-fade-in-up">
|
||||||
Edit Form
|
Edit Form
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ export default function EditFormPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-8 rounded-xl border border-border bg-card p-6 shadow-sm">
|
<div className="mb-8 rounded-xl border border-border bg-card p-6 shadow-sm animate-float-in" style={{ animationDelay: '0.1s' }}>
|
||||||
<div className="h-2 -mx-6 -mt-6 mb-6 rounded-t-xl bg-primary" />
|
<div className="h-2 -mx-6 -mt-6 mb-6 rounded-t-xl bg-primary" />
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<FormInput
|
<FormInput
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default function FormResponsesPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm">
|
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm animate-float-in">
|
||||||
<div className="h-2 rounded-t-xl bg-primary" />
|
<div className="h-2 rounded-t-xl bg-primary" />
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||||
@@ -183,7 +183,7 @@ export default function FormResponsesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="mb-6 flex gap-1 rounded-lg border border-border bg-card p-1">
|
<div className="mb-6 flex gap-1 rounded-lg border border-border bg-card p-1 animate-fade-in-up" style={{ animationDelay: '0.15s' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("summary")}
|
onClick={() => setActiveTab("summary")}
|
||||||
className={`flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors ${
|
className={`flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors ${
|
||||||
|
|||||||
+8
-7
@@ -104,7 +104,7 @@ export default function FormPreviewPage() {
|
|||||||
Back to forms
|
Back to forms
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm">
|
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm animate-float-in">
|
||||||
<div className="h-2 rounded-t-xl bg-primary" />
|
<div className="h-2 rounded-t-xl bg-primary" />
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||||||
@@ -172,15 +172,16 @@ export default function FormPreviewPage() {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{form.questions.map((question, index) => (
|
{form.questions.map((question, index) => (
|
||||||
<QuestionPreview
|
<div key={question.id} className="animate-fade-in-up" style={{ animationDelay: `${0.15 + index * 0.08}s` }}>
|
||||||
key={question.id}
|
<QuestionPreview
|
||||||
question={question}
|
question={question}
|
||||||
index={index}
|
index={index}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flex flex-col items-center gap-3 rounded-xl border border-dashed border-border bg-card/50 p-6">
|
<div className="mt-8 flex flex-col items-center gap-3 rounded-xl border border-dashed border-border bg-card/50 p-6 animate-fade-in" style={{ animationDelay: '0.4s' }}>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
This is a read-only preview. Click below to fill out this form.
|
This is a read-only preview. Click below to fill out this form.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -207,8 +207,10 @@ export default function FormsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : forms.length > 0 ? (
|
) : forms.length > 0 ? (
|
||||||
<div className="grid gap-5 sm:grid-cols-2">
|
<div className="grid gap-5 sm:grid-cols-2">
|
||||||
{forms.map((form) => (
|
{forms.map((form, index) => (
|
||||||
<FormCard key={form.id} form={form} />
|
<div key={form.id} className="animate-float-in" style={{ animationDelay: `${index * 0.08}s` }}>
|
||||||
|
<FormCard form={form} />
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function LoginPage() {
|
|||||||
<div className="flex min-h-screen flex-col bg-background">
|
<div className="flex min-h-screen flex-col bg-background">
|
||||||
<div className="flex flex-1 items-center justify-center px-4 py-12">
|
<div className="flex flex-1 items-center justify-center px-4 py-12">
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="mb-8 flex flex-col items-center gap-3">
|
<div className="mb-8 flex flex-col items-center gap-3 animate-fade-in-down">
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
Welcome back
|
Welcome back
|
||||||
</h1>
|
</h1>
|
||||||
@@ -67,7 +67,7 @@ export default function LoginPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-xl border border-border bg-card p-6 shadow-sm">
|
<div className="rounded-xl border border-border bg-card p-6 shadow-sm animate-float-in" style={{ animationDelay: '0.15s' }}>
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||||
{errors.general && (
|
{errors.general && (
|
||||||
<p className="rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<p className="rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
@@ -127,7 +127,7 @@ export default function LoginPage() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-6 text-center text-sm text-muted-foreground">
|
<p className="mt-6 text-center text-sm text-muted-foreground animate-fade-in" style={{ animationDelay: '0.3s' }}>
|
||||||
{"Don't have an account? "}
|
{"Don't have an account? "}
|
||||||
<Link
|
<Link
|
||||||
to="/register"
|
to="/register"
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ export function meta() {
|
|||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: "center", padding: "4rem" }}>
|
<div className="flex min-h-screen flex-col items-center justify-center bg-background px-4" style={{ textAlign: "center" }}>
|
||||||
<h1>404</h1>
|
<h1 className="text-6xl font-bold text-foreground animate-scale-bounce">404</h1>
|
||||||
<p>Page not found.</p>
|
<p className="mt-4 text-lg text-muted-foreground animate-fade-in-up" style={{ animationDelay: '0.2s' }}>Page not found.</p>
|
||||||
<a href="/">Go home</a>
|
<a href="/" className="mt-6 inline-flex items-center gap-2 rounded-lg bg-primary px-6 py-3 text-sm font-medium text-primary-foreground btn-press animate-fade-in-up" style={{ animationDelay: '0.35s' }}>Go home</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export default function RegisterPage() {
|
|||||||
<div className="flex min-h-screen flex-col bg-background">
|
<div className="flex min-h-screen flex-col bg-background">
|
||||||
<div className="flex flex-1 items-center justify-center px-4 py-12">
|
<div className="flex flex-1 items-center justify-center px-4 py-12">
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="mb-8 flex flex-col items-center gap-3">
|
<div className="mb-8 flex flex-col items-center gap-3 animate-fade-in-down">
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
Create your account
|
Create your account
|
||||||
</h1>
|
</h1>
|
||||||
@@ -73,7 +73,7 @@ export default function RegisterPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-xl border border-border bg-card p-6 shadow-sm">
|
<div className="rounded-xl border border-border bg-card p-6 shadow-sm animate-float-in" style={{ animationDelay: '0.15s' }}>
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<FormInput
|
<FormInput
|
||||||
@@ -142,7 +142,7 @@ export default function RegisterPage() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-6 text-center text-sm text-muted-foreground">
|
<p className="mt-6 text-center text-sm text-muted-foreground animate-fade-in" style={{ animationDelay: '0.3s' }}>
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to="/login"
|
||||||
|
|||||||
+12
-11
@@ -140,8 +140,8 @@ export default function SubmitFormPage() {
|
|||||||
<div className="flex min-h-screen flex-col bg-background">
|
<div className="flex min-h-screen flex-col bg-background">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex flex-1 items-center justify-center">
|
<main className="flex flex-1 items-center justify-center">
|
||||||
<div className="mx-4 flex max-w-md flex-col items-center rounded-xl border border-border bg-card p-8 text-center shadow-sm">
|
<div className="mx-4 flex max-w-md flex-col items-center rounded-xl border border-border bg-card p-8 text-center shadow-sm animate-scale-in">
|
||||||
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30">
|
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30 animate-scale-bounce">
|
||||||
<CheckCircle2 className="h-8 w-8 text-green-600 dark:text-green-400" />
|
<CheckCircle2 className="h-8 w-8 text-green-600 dark:text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-bold text-foreground">
|
<h2 className="text-xl font-bold text-foreground">
|
||||||
@@ -198,7 +198,7 @@ export default function SubmitFormPage() {
|
|||||||
Back to form preview
|
Back to form preview
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm">
|
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm animate-float-in">
|
||||||
<div className="h-2 rounded-t-xl bg-primary" />
|
<div className="h-2 rounded-t-xl bg-primary" />
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h1 className="text-xl font-bold text-foreground text-balance sm:text-2xl">
|
<h1 className="text-xl font-bold text-foreground text-balance sm:text-2xl">
|
||||||
@@ -223,14 +223,15 @@ export default function SubmitFormPage() {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||||
{form.questions.map((question, index) => (
|
{form.questions.map((question, index) => (
|
||||||
<QuestionField
|
<div key={question.id} className="animate-fade-in-up" style={{ animationDelay: `${0.1 + index * 0.08}s` }}>
|
||||||
key={question.id}
|
<QuestionField
|
||||||
question={question}
|
question={question}
|
||||||
index={index}
|
index={index}
|
||||||
value={answers[question.id] ?? ""}
|
value={answers[question.id] ?? ""}
|
||||||
onChange={(val) => updateAnswer(question.id, val)}
|
onChange={(val) => updateAnswer(question.id, val)}
|
||||||
error={validationErrors[question.id]}
|
error={validationErrors[question.id]}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-between">
|
<div className="mt-4 flex items-center justify-between">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface FormCardProps {
|
|||||||
|
|
||||||
export function FormCard({ form }: FormCardProps) {
|
export function FormCard({ form }: FormCardProps) {
|
||||||
return (
|
return (
|
||||||
<div className="group flex flex-col rounded-xl border border-border bg-card shadow-sm transition-shadow hover:shadow-md">
|
<div className="group flex flex-col rounded-xl border border-border bg-card shadow-sm card-hover">
|
||||||
<div className="h-1.5 rounded-t-xl bg-primary" />
|
<div className="h-1.5 rounded-t-xl bg-primary" />
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col p-5">
|
<div className="flex flex-1 flex-col p-5">
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const FormButton = forwardRef<HTMLButtonElement, FormButtonProps>(
|
|||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center gap-2 font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
"inline-flex items-center justify-center gap-2 font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
"disabled:pointer-events-none disabled:opacity-50",
|
"disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
"btn-press",
|
||||||
variantStyles[variant],
|
variantStyles[variant],
|
||||||
sizeStyles[size],
|
sizeStyles[size],
|
||||||
className
|
className
|
||||||
|
|||||||
Reference in New Issue
Block a user