Resolve merge conflict in pnpm-lock.yaml by combining both versions
This commit is contained in:
268
app/(pages)/about/page.tsx
Normal file
268
app/(pages)/about/page.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Diamond, Users, Trophy, Palette, Heart } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
const stats = [
|
||||
{ number: "500+", label: "Expected Participants" },
|
||||
{ number: "3", label: "Categories" },
|
||||
{ number: "₹50K+", label: "Prize Pool" },
|
||||
{ number: "1", label: "Week of Creativity" },
|
||||
];
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: Palette,
|
||||
title: "Creativity",
|
||||
description: "We celebrate original thinking and artistic expression in all its forms.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Community",
|
||||
description: "Building connections between artists and art enthusiasts across SJEC.",
|
||||
},
|
||||
{
|
||||
icon: Trophy,
|
||||
title: "Excellence",
|
||||
description: "Recognizing and rewarding outstanding artistic talent and dedication.",
|
||||
},
|
||||
{
|
||||
icon: Heart,
|
||||
title: "Inclusivity",
|
||||
description: "Welcoming artists of all skill levels and artistic backgrounds.",
|
||||
},
|
||||
];
|
||||
|
||||
const timeline = [
|
||||
{ date: "Feb 15, 2026", event: "Registration Opens", status: "completed" },
|
||||
{ date: "Feb 21, 2026", event: "Submissions Begin", status: "active" },
|
||||
{ date: "Feb 28, 2026", event: "Submission Deadline", status: "upcoming" },
|
||||
{ date: "Mar 1-5, 2026", event: "Public Voting", status: "upcoming" },
|
||||
{ date: "Mar 7, 2026", event: "Winners Announced", status: "upcoming" },
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
{/* Hero Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
ABOUT US
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-black tracking-tight text-white mb-6">
|
||||
Celebrating Art at
|
||||
<span className="text-lime-400"> SJEC</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-3xl mx-auto leading-relaxed">
|
||||
ArtSplash is St. Joseph Engineering College's premier art competition,
|
||||
brought to you by <span className="text-lime-400 font-semibold">Sceptix</span> —
|
||||
the official technical club. We're on a mission to discover, celebrate,
|
||||
and showcase the incredible artistic talent within our community.
|
||||
</p>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Stats Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-6"
|
||||
>
|
||||
{stats.map((stat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 text-center"
|
||||
>
|
||||
<div className="text-3xl sm:text-4xl font-black text-lime-400 mb-2">
|
||||
{stat.number}
|
||||
</div>
|
||||
<div className="text-zinc-400 text-sm">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Mission Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-20">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="inline-block bg-zinc-800 text-lime-400 px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
OUR MISSION
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-black text-white mb-6">
|
||||
Empowering Artists,
|
||||
<br />
|
||||
<span className="text-lime-400">Inspiring Creativity</span>
|
||||
</h2>
|
||||
<p className="text-zinc-400 leading-relaxed mb-6">
|
||||
ArtSplash was created to provide a platform where students can express
|
||||
their creativity, gain recognition for their work, and connect with
|
||||
fellow artists. Whether you're a digital artist, traditional painter,
|
||||
or poster designer, there's a place for you here.
|
||||
</p>
|
||||
<p className="text-zinc-400 leading-relaxed">
|
||||
Our competition is open to all SJEC students who want to showcase their
|
||||
artistic talents. The public voting system ensures that the community
|
||||
has a voice in celebrating the best artworks.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="grid grid-cols-2 gap-4"
|
||||
>
|
||||
{values.map((value, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 hover:border-lime-400/50 transition-colors"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-xl bg-lime-400/10 flex items-center justify-center mb-4">
|
||||
<value.icon className="w-6 h-6 text-lime-400" />
|
||||
</div>
|
||||
<h3 className="text-white font-bold mb-2">{value.title}</h3>
|
||||
<p className="text-zinc-500 text-sm">{value.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Timeline Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block bg-zinc-800 text-lime-400 px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
EVENT TIMELINE
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-black text-white">
|
||||
Important <span className="text-lime-400">Dates</span>
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="relative">
|
||||
{/* Timeline Line */}
|
||||
<div className="absolute left-1/2 top-0 bottom-0 w-px bg-zinc-800 hidden md:block" />
|
||||
|
||||
<div className="space-y-8">
|
||||
{timeline.map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
className={`flex flex-col md:flex-row items-center gap-4 ${
|
||||
index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse"
|
||||
}`}
|
||||
>
|
||||
<div className={`flex-1 ${index % 2 === 0 ? "md:text-right" : "md:text-left"}`}>
|
||||
<div
|
||||
className={`inline-block bg-zinc-900 border rounded-2xl p-6 ${
|
||||
item.status === "active"
|
||||
? "border-lime-400"
|
||||
: item.status === "completed"
|
||||
? "border-zinc-700"
|
||||
: "border-zinc-800"
|
||||
}`}
|
||||
>
|
||||
<div className="text-lime-400 text-sm font-bold mb-1">{item.date}</div>
|
||||
<div className="text-white font-semibold">{item.event}</div>
|
||||
<div
|
||||
className={`text-xs mt-2 px-2 py-1 rounded-full inline-block ${
|
||||
item.status === "active"
|
||||
? "bg-lime-400/20 text-lime-400"
|
||||
: item.status === "completed"
|
||||
? "bg-zinc-700 text-zinc-400"
|
||||
: "bg-zinc-800 text-zinc-500"
|
||||
}`}
|
||||
>
|
||||
{item.status === "active"
|
||||
? "In Progress"
|
||||
: item.status === "completed"
|
||||
? "Completed"
|
||||
: "Upcoming"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center Dot */}
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-4 ${
|
||||
item.status === "active"
|
||||
? "bg-lime-400 border-lime-400/30"
|
||||
: item.status === "completed"
|
||||
? "bg-zinc-600 border-zinc-700"
|
||||
: "bg-zinc-800 border-zinc-700"
|
||||
}`}
|
||||
/>
|
||||
|
||||
<div className="flex-1 hidden md:block" />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Organized By Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-3xl p-8 md:p-12 text-center"
|
||||
>
|
||||
<span className="inline-block bg-lime-400/10 text-lime-400 px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
ORGANIZED BY
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-3 mb-6">
|
||||
<Diamond className="w-10 h-10 text-lime-400" />
|
||||
<span className="text-3xl font-black text-white">SCEPTIX</span>
|
||||
</div>
|
||||
<p className="text-zinc-400 max-w-2xl mx-auto mb-8">
|
||||
Sceptix is the official technical club of St. Joseph Engineering College,
|
||||
dedicated to fostering innovation, creativity, and technical excellence
|
||||
among students.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/register"
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 text-sm font-bold tracking-wider hover:bg-lime-300 transition-all rounded-full"
|
||||
>
|
||||
JOIN ARTSPLASH
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-8 py-4 text-sm font-bold tracking-wider border border-zinc-700 hover:border-zinc-500 transition-all rounded-full"
|
||||
>
|
||||
CONTACT US
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
360
app/(pages)/contact/page.tsx
Normal file
360
app/(pages)/contact/page.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Mail,
|
||||
Phone,
|
||||
MapPin,
|
||||
Send,
|
||||
Instagram,
|
||||
Linkedin,
|
||||
MessageSquare,
|
||||
Clock,
|
||||
CheckCircle
|
||||
} from "lucide-react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
const contactMethods = [
|
||||
{
|
||||
icon: Mail,
|
||||
label: "Email",
|
||||
value: "artsplash@sjec.ac.in",
|
||||
href: "mailto:artsplash@sjec.ac.in",
|
||||
description: "Best for detailed inquiries",
|
||||
},
|
||||
{
|
||||
icon: Phone,
|
||||
label: "Phone",
|
||||
value: "+91 98765 43210",
|
||||
href: "tel:+919876543210",
|
||||
description: "Mon-Fri, 10AM-5PM",
|
||||
},
|
||||
{
|
||||
icon: MapPin,
|
||||
label: "Location",
|
||||
value: "SJEC, Mangalore",
|
||||
href: "https://maps.google.com/?q=St+Joseph+Engineering+College+Mangalore",
|
||||
description: "Visit us on campus",
|
||||
},
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: Instagram,
|
||||
label: "Instagram",
|
||||
href: "https://instagram.com/sceptix_sjec",
|
||||
username: "@sceptix_sjec",
|
||||
},
|
||||
{
|
||||
icon: Linkedin,
|
||||
label: "LinkedIn",
|
||||
href: "https://linkedin.com/company/sceptix-sjec",
|
||||
username: "Sceptix SJEC",
|
||||
},
|
||||
];
|
||||
|
||||
const departments = [
|
||||
"General Inquiry",
|
||||
"Registration Help",
|
||||
"Technical Support",
|
||||
"Submission Issues",
|
||||
"Voting Questions",
|
||||
"Partnership & Sponsorship",
|
||||
"Other",
|
||||
];
|
||||
|
||||
export default function ContactPage() {
|
||||
const [formState, setFormState] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
department: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormState((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
setIsSubmitting(false);
|
||||
setIsSubmitted(true);
|
||||
};
|
||||
|
||||
if (isSubmitted) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20 flex items-center">
|
||||
<div className="max-w-xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full bg-lime-400 flex items-center justify-center mx-auto mb-6">
|
||||
<CheckCircle className="w-10 h-10 text-black" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl font-black text-white mb-4">
|
||||
Message Sent!
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg mb-8">
|
||||
Thank you for reaching out. We'll get back to you within 24-48 hours.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center bg-zinc-900 border border-zinc-800 text-white px-6 py-3 rounded-xl font-semibold hover:border-zinc-700 transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
CONTACT
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-4">
|
||||
Get in <span className="text-lime-400">Touch</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Have a question about ArtSplash? We're here to help. Reach out to us
|
||||
through any of the channels below.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid lg:grid-cols-5 gap-8 lg:gap-12">
|
||||
{/* Contact Info Column */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="lg:col-span-2 space-y-6"
|
||||
>
|
||||
{/* Contact Methods */}
|
||||
<div className="space-y-4">
|
||||
{contactMethods.map((method, index) => (
|
||||
<a
|
||||
key={method.label}
|
||||
href={method.href}
|
||||
target={method.label === "Location" ? "_blank" : undefined}
|
||||
rel={method.label === "Location" ? "noopener noreferrer" : undefined}
|
||||
className="block bg-zinc-900 border border-zinc-800 rounded-xl p-5 hover:border-zinc-700 transition-colors group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 bg-lime-400/10 rounded-lg group-hover:bg-lime-400/20 transition-colors">
|
||||
<method.icon className="w-6 h-6 text-lime-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-zinc-500 text-sm mb-1">{method.label}</p>
|
||||
<p className="text-white font-semibold">{method.value}</p>
|
||||
<p className="text-zinc-500 text-sm mt-1">{method.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Response Time */}
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 bg-lime-400/10 rounded-lg">
|
||||
<Clock className="w-5 h-5 text-lime-400" />
|
||||
</div>
|
||||
<h3 className="text-white font-semibold">Response Time</h3>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-sm">
|
||||
We typically respond within <span className="text-lime-400 font-semibold">24-48 hours</span> on
|
||||
business days. For urgent issues during the competition, we'll prioritize your query.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
|
||||
<h3 className="text-white font-semibold mb-4">Follow Us</h3>
|
||||
<div className="flex gap-3">
|
||||
{socialLinks.map((social) => (
|
||||
<a
|
||||
key={social.label}
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 bg-zinc-800 text-zinc-300 px-4 py-3 rounded-xl hover:bg-zinc-700 hover:text-white transition-colors"
|
||||
>
|
||||
<social.icon className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">{social.username}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="lg:col-span-3"
|
||||
>
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 sm:p-8">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-lime-400/10 rounded-lg">
|
||||
<MessageSquare className="w-5 h-5 text-lime-400" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-white">Send us a Message</h2>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{/* Name & Email Row */}
|
||||
<div className="grid sm:grid-cols-2 gap-5">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Your Name <span className="text-lime-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formState.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="John Doe"
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Email Address <span className="text-lime-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formState.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="john@example.com"
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Department & Subject Row */}
|
||||
<div className="grid sm:grid-cols-2 gap-5">
|
||||
<div>
|
||||
<label htmlFor="department" className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Topic <span className="text-lime-400">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
id="department"
|
||||
name="department"
|
||||
value={formState.department}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white appearance-none focus:outline-none focus:border-lime-400 transition-colors cursor-pointer"
|
||||
>
|
||||
<option value="">Select a topic</option>
|
||||
{departments.map((dept) => (
|
||||
<option key={dept} value={dept}>{dept}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4">
|
||||
<svg className="w-5 h-5 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Subject <span className="text-lime-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
value={formState.subject}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Brief subject"
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Your Message <span className="text-lime-400">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
value={formState.message}
|
||||
onChange={handleChange}
|
||||
required
|
||||
rows={5}
|
||||
maxLength={1000}
|
||||
placeholder="Describe your question or issue in detail..."
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors resize-none"
|
||||
/>
|
||||
<p className="text-zinc-500 text-sm mt-2">
|
||||
{formState.message.length}/1000 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className={`w-full flex items-center justify-center gap-2 px-6 py-4 rounded-xl font-bold transition-all ${
|
||||
isSubmitting
|
||||
? "bg-zinc-700 text-zinc-400 cursor-not-allowed"
|
||||
: "bg-lime-400 text-black hover:bg-lime-300"
|
||||
}`}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<svg className="animate-spin w-5 h-5" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="w-5 h-5" />
|
||||
Send Message
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
296
app/(pages)/faq/page.tsx
Normal file
296
app/(pages)/faq/page.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ChevronDown,
|
||||
HelpCircle,
|
||||
Vote,
|
||||
Upload,
|
||||
Award,
|
||||
Shield,
|
||||
Clock,
|
||||
Mail
|
||||
} from "lucide-react";
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
category: "General",
|
||||
icon: HelpCircle,
|
||||
questions: [
|
||||
{
|
||||
q: "What is ArtSplash?",
|
||||
a: "ArtSplash is SJEC's premier art competition organized by Sceptix. It's a platform for students to showcase their artistic talents across three categories: Digital Art, Paintings, and Poster Making. The competition features public voting and exciting prizes for winners.",
|
||||
},
|
||||
{
|
||||
q: "When does the competition take place?",
|
||||
a: "The competition runs from February 15 to March 7, 2026. This includes the registration period, submission window, and voting phase. Make sure to check the Rules page for specific deadlines for each phase.",
|
||||
},
|
||||
{
|
||||
q: "Who can participate in ArtSplash?",
|
||||
a: "ArtSplash is open to all students of St Joseph Engineering College (SJEC), Mangalore. You must have a valid SJEC email address (@sjec.ac.in) to register and submit artwork.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Registration & Submission",
|
||||
icon: Upload,
|
||||
questions: [
|
||||
{
|
||||
q: "How do I register for the competition?",
|
||||
a: "Click on the 'Register' button and sign in using your SJEC Google account (@sjec.ac.in). Fill in your details including department, year, and phone number. Once registered, you can submit your artwork through the Submit page.",
|
||||
},
|
||||
{
|
||||
q: "What file formats are accepted?",
|
||||
a: "We accept JPG, PNG, and PDF formats. For Digital Art and Paintings, JPG or PNG is recommended. For Poster Making, PDF format is preferred. Maximum file size is 10MB.",
|
||||
},
|
||||
{
|
||||
q: "Can I submit multiple artworks?",
|
||||
a: "You can submit one artwork per category, meaning you can have up to 3 submissions total (one Digital Art, one Painting, and one Poster). Each submission must be original work.",
|
||||
},
|
||||
{
|
||||
q: "Can I edit my submission after uploading?",
|
||||
a: "You can edit your submission during the submission window. Once the voting phase begins, no changes can be made to ensure fairness. Make sure your submission is final before the deadline.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Voting",
|
||||
icon: Vote,
|
||||
questions: [
|
||||
{
|
||||
q: "Who can vote?",
|
||||
a: "Anyone can vote! Unlike submissions which require an SJEC email, voting is open to everyone. Each person can cast one vote per email address, verified through our CAPTCHA system.",
|
||||
},
|
||||
{
|
||||
q: "Can I see how many votes an artwork has?",
|
||||
a: "To maintain fairness and prevent bandwagon voting, vote counts are hidden during the voting period. Results will be announced after the voting phase ends.",
|
||||
},
|
||||
{
|
||||
q: "How is the winner decided?",
|
||||
a: "Winners are determined by a combination of public votes (70%) and judge scores (30%). Our panel of judges evaluates artworks based on creativity, technique, and adherence to theme.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Rules & Guidelines",
|
||||
icon: Shield,
|
||||
questions: [
|
||||
{
|
||||
q: "What are the content guidelines?",
|
||||
a: "All submissions must be original work created by the participant. AI-generated content is strictly prohibited and will be detected through our verification system. Content must be appropriate and not contain any offensive, violent, or copyrighted material.",
|
||||
},
|
||||
{
|
||||
q: "What happens if I'm caught using AI-generated art?",
|
||||
a: "All submissions go through our AI detection system. If AI-generated content is detected, your submission will be disqualified immediately, and you may be banned from future competitions.",
|
||||
},
|
||||
{
|
||||
q: "Can I use references for my artwork?",
|
||||
a: "Yes, you can use references for inspiration, but your final artwork must be substantially original. Direct copies or heavy traces of existing artwork are not allowed.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Prizes & Awards",
|
||||
icon: Award,
|
||||
questions: [
|
||||
{
|
||||
q: "What are the prizes?",
|
||||
a: "Each category has three prize tiers: 1st Place, 2nd Place, and 3rd Place. Prizes include certificates, merchandise, and exciting rewards. Details will be announced on our social media channels.",
|
||||
},
|
||||
{
|
||||
q: "When will winners be announced?",
|
||||
a: "Winners will be announced within 3 days after the voting phase ends. All participants will be notified via email, and results will be posted on our website and social media.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Technical Issues",
|
||||
icon: Clock,
|
||||
questions: [
|
||||
{
|
||||
q: "My submission failed to upload. What should I do?",
|
||||
a: "First, check if your file meets the requirements (JPG/PNG/PDF, under 10MB). Try using a different browser or clearing your cache. If the issue persists, contact us at artsplash@sjec.ac.in with details of the error.",
|
||||
},
|
||||
{
|
||||
q: "I didn't receive a confirmation email after registering.",
|
||||
a: "Check your spam/junk folder first. If you still can't find it, try logging in to verify your registration status. Contact us if you're still having issues.",
|
||||
},
|
||||
{
|
||||
q: "Can I participate if I'm not currently on campus?",
|
||||
a: "Absolutely! ArtSplash is entirely online. You can register, submit, and vote from anywhere as long as you have internet access and (for submissions) a valid SJEC email.",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function FAQPage() {
|
||||
const [openItems, setOpenItems] = useState<Set<string>>(new Set(["general-0"]));
|
||||
const [activeCategory, setActiveCategory] = useState("General");
|
||||
|
||||
const toggleItem = (id: string) => {
|
||||
setOpenItems((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const filteredFaqs = activeCategory === "All"
|
||||
? faqs
|
||||
: faqs.filter(cat => cat.category === activeCategory);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
FAQ
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-4">
|
||||
Frequently Asked <span className="text-lime-400">Questions</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Got questions? We've got answers. Find everything you need to know about ArtSplash below.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Category Tabs */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="mb-8 overflow-x-auto scrollbar-hide"
|
||||
>
|
||||
<div className="flex gap-2 min-w-max pb-2">
|
||||
<button
|
||||
onClick={() => setActiveCategory("All")}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-all whitespace-nowrap ${
|
||||
activeCategory === "All"
|
||||
? "bg-lime-400 text-black"
|
||||
: "bg-zinc-900 text-zinc-400 hover:text-white border border-zinc-800"
|
||||
}`}
|
||||
>
|
||||
All Topics
|
||||
</button>
|
||||
{faqs.map((cat) => (
|
||||
<button
|
||||
key={cat.category}
|
||||
onClick={() => setActiveCategory(cat.category)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all whitespace-nowrap ${
|
||||
activeCategory === cat.category
|
||||
? "bg-lime-400 text-black"
|
||||
: "bg-zinc-900 text-zinc-400 hover:text-white border border-zinc-800"
|
||||
}`}
|
||||
>
|
||||
<cat.icon className="w-4 h-4" />
|
||||
{cat.category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* FAQ Accordion */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
{filteredFaqs.map((category, catIndex) => (
|
||||
<div key={category.category}>
|
||||
{/* Category Header */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-lime-400/10 rounded-lg">
|
||||
<category.icon className="w-5 h-5 text-lime-400" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-white">{category.category}</h2>
|
||||
</div>
|
||||
|
||||
{/* Questions */}
|
||||
<div className="space-y-3">
|
||||
{category.questions.map((item, qIndex) => {
|
||||
const itemId = `${category.category.toLowerCase().replace(/\s+/g, "-")}-${qIndex}`;
|
||||
const isOpen = openItems.has(itemId);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={qIndex}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: qIndex * 0.05 }}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleItem(itemId)}
|
||||
className="w-full flex items-center justify-between p-5 text-left"
|
||||
>
|
||||
<span className="text-white font-semibold pr-4">{item.q}</span>
|
||||
<ChevronDown
|
||||
className={`w-5 h-5 text-zinc-400 flex-shrink-0 transition-transform duration-300 ${
|
||||
isOpen ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="px-5 pb-5">
|
||||
<div className="h-px bg-zinc-800 mb-4" />
|
||||
<p className="text-zinc-400 leading-relaxed">{item.a}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Contact CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="mt-16 text-center"
|
||||
>
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-8">
|
||||
<div className="w-14 h-14 rounded-full bg-lime-400/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Mail className="w-7 h-7 text-lime-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">Still have questions?</h3>
|
||||
<p className="text-zinc-400 mb-6">
|
||||
Can't find what you're looking for? We're here to help!
|
||||
</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-3 rounded-xl font-bold hover:bg-lime-300 transition-colors"
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
503
app/(pages)/gallery/page.tsx
Normal file
503
app/(pages)/gallery/page.tsx
Normal file
@@ -0,0 +1,503 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo, useEffect, useCallback } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
Search,
|
||||
Filter,
|
||||
Heart,
|
||||
Monitor,
|
||||
Palette,
|
||||
FileImage,
|
||||
Grid,
|
||||
LayoutGrid,
|
||||
X,
|
||||
ChevronDown,
|
||||
Eye
|
||||
} from "lucide-react";
|
||||
|
||||
// Mock data - In production, this would come from the API
|
||||
const mockArtworks = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Digital Dreams",
|
||||
artist: "Rahul K.",
|
||||
category: "digital-art",
|
||||
imageUrl: "https://images.unsplash.com/photo-1549490349-8643362247b5?w=600&h=600&fit=crop",
|
||||
department: "CSE",
|
||||
year: "3rd Year",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Ocean Serenity",
|
||||
artist: "Priya M.",
|
||||
category: "paintings",
|
||||
imageUrl: "https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=600&h=600&fit=crop",
|
||||
department: "ECE",
|
||||
year: "2nd Year",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Climate Action Now",
|
||||
artist: "Arun S.",
|
||||
category: "poster-making",
|
||||
imageUrl: "https://images.unsplash.com/photo-1561214115-f2f134cc4912?w=600&h=600&fit=crop",
|
||||
department: "ISE",
|
||||
year: "4th Year",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Neon Nights",
|
||||
artist: "Sneha R.",
|
||||
category: "digital-art",
|
||||
imageUrl: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=600&h=600&fit=crop",
|
||||
department: "CSE",
|
||||
year: "2nd Year",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "Mountain Sunrise",
|
||||
artist: "Karthik V.",
|
||||
category: "paintings",
|
||||
imageUrl: "https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?w=600&h=600&fit=crop",
|
||||
department: "ME",
|
||||
year: "3rd Year",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
title: "Tech Innovation",
|
||||
artist: "Divya N.",
|
||||
category: "poster-making",
|
||||
imageUrl: "https://images.unsplash.com/photo-1513364776144-60967b0f800f?w=600&h=600&fit=crop",
|
||||
department: "AIML",
|
||||
year: "2nd Year",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
title: "Abstract Emotions",
|
||||
artist: "Vijay K.",
|
||||
category: "digital-art",
|
||||
imageUrl: "https://images.unsplash.com/photo-1547826039-bfc35e0f1ea8?w=600&h=600&fit=crop",
|
||||
department: "CSE",
|
||||
year: "4th Year",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
title: "Sunset Reflections",
|
||||
artist: "Meera S.",
|
||||
category: "paintings",
|
||||
imageUrl: "https://images.unsplash.com/photo-1578926288207-a90a5366759d?w=600&h=600&fit=crop",
|
||||
department: "EEE",
|
||||
year: "3rd Year",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
title: "Save Our Planet",
|
||||
artist: "Nikhil B.",
|
||||
category: "poster-making",
|
||||
imageUrl: "https://images.unsplash.com/photo-1482160549825-59d1b23cb208?w=600&h=600&fit=crop",
|
||||
department: "CE",
|
||||
year: "2nd Year",
|
||||
},
|
||||
];
|
||||
|
||||
const categories = [
|
||||
{ id: "all", label: "All Categories", icon: LayoutGrid },
|
||||
{ id: "digital-art", label: "Digital Art", icon: Monitor },
|
||||
{ id: "paintings", label: "Paintings", icon: Palette },
|
||||
{ id: "poster-making", label: "Poster Making", icon: FileImage },
|
||||
];
|
||||
|
||||
// Seeded random shuffle function
|
||||
function seededShuffle<T>(array: T[], seed: number): T[] {
|
||||
const shuffled = [...array];
|
||||
let currentIndex = shuffled.length;
|
||||
|
||||
// Simple seeded random number generator
|
||||
const seededRandom = () => {
|
||||
seed = (seed * 9301 + 49297) % 233280;
|
||||
return seed / 233280;
|
||||
};
|
||||
|
||||
while (currentIndex > 0) {
|
||||
const randomIndex = Math.floor(seededRandom() * currentIndex);
|
||||
currentIndex--;
|
||||
[shuffled[currentIndex], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[currentIndex]];
|
||||
}
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
export default function GalleryPage() {
|
||||
const [selectedCategory, setSelectedCategory] = useState("all");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [viewMode, setViewMode] = useState<"grid" | "large">("grid");
|
||||
const [selectedArtwork, setSelectedArtwork] = useState<typeof mockArtworks[0] | null>(null);
|
||||
const [votedArtworks, setVotedArtworks] = useState<Set<string>>(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const stored = localStorage.getItem("artsplash-votes");
|
||||
return stored ? new Set(JSON.parse(stored)) : new Set();
|
||||
} catch {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
return new Set();
|
||||
});
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
// Lock body scroll when modal is open
|
||||
useEffect(() => {
|
||||
if (selectedArtwork) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
return () => { document.body.style.overflow = ""; };
|
||||
}, [selectedArtwork]);
|
||||
|
||||
// Close modal on Escape key
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setSelectedArtwork(null);
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
// Use a daily seed for consistent randomization throughout the day
|
||||
const dailySeed = useMemo(() => {
|
||||
const today = new Date();
|
||||
return today.getFullYear() * 10000 + (today.getMonth() + 1) * 100 + today.getDate();
|
||||
}, []);
|
||||
|
||||
// Filter and shuffle artworks
|
||||
const filteredArtworks = useMemo(() => {
|
||||
let result = mockArtworks;
|
||||
|
||||
// Filter by category
|
||||
if (selectedCategory !== "all") {
|
||||
result = result.filter(art => art.category === selectedCategory);
|
||||
}
|
||||
|
||||
// Filter by search query
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
result = result.filter(
|
||||
art =>
|
||||
art.title.toLowerCase().includes(query) ||
|
||||
art.artist.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
// Shuffle with seed
|
||||
return seededShuffle(result, dailySeed);
|
||||
}, [selectedCategory, searchQuery, dailySeed]);
|
||||
|
||||
const handleVote = useCallback((artworkId: string) => {
|
||||
if (votedArtworks.has(artworkId)) return;
|
||||
setVotedArtworks(prev => {
|
||||
const next = new Set([...prev, artworkId]);
|
||||
try {
|
||||
localStorage.setItem("artsplash-votes", JSON.stringify([...next]));
|
||||
} catch { /* storage full - ignore */ }
|
||||
return next;
|
||||
});
|
||||
}, [votedArtworks]);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
GALLERY
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-4">
|
||||
Explore <span className="text-lime-400">Artworks</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Browse through amazing artworks submitted by talented artists from SJEC.
|
||||
Vote for your favorites to help them win!
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Filters Bar */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 items-stretch lg:items-center justify-between">
|
||||
{/* Search */}
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search by title or artist..."
|
||||
className="w-full bg-zinc-900 border border-zinc-800 rounded-xl pl-12 pr-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category Pills - Desktop */}
|
||||
<div className="hidden lg:flex items-center gap-2">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all ${
|
||||
selectedCategory === cat.id
|
||||
? "bg-lime-400 text-black"
|
||||
: "bg-zinc-900 text-zinc-400 hover:text-white border border-zinc-800"
|
||||
}`}
|
||||
>
|
||||
<cat.icon className="w-4 h-4" />
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile Filter Button */}
|
||||
<button
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className="lg:hidden flex items-center justify-center gap-2 bg-zinc-900 border border-zinc-800 text-white px-4 py-3 rounded-xl"
|
||||
>
|
||||
<Filter className="w-5 h-5" />
|
||||
Filters
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
|
||||
{/* View Toggle */}
|
||||
<div className="hidden sm:flex items-center gap-2 bg-zinc-900 border border-zinc-800 rounded-xl p-1">
|
||||
<button
|
||||
onClick={() => setViewMode("grid")}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
viewMode === "grid" ? "bg-lime-400 text-black" : "text-zinc-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<Grid className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("large")}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
viewMode === "large" ? "bg-lime-400 text-black" : "text-zinc-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<LayoutGrid className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Category Filters */}
|
||||
<AnimatePresence>
|
||||
{showFilters && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
className="lg:hidden overflow-hidden"
|
||||
>
|
||||
<div className="flex flex-wrap gap-2 pt-4">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => {
|
||||
setSelectedCategory(cat.id);
|
||||
setShowFilters(false);
|
||||
}}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all ${
|
||||
selectedCategory === cat.id
|
||||
? "bg-lime-400 text-black"
|
||||
: "bg-zinc-900 text-zinc-400 border border-zinc-800"
|
||||
}`}
|
||||
>
|
||||
<cat.icon className="w-4 h-4" />
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
|
||||
{/* Results Count */}
|
||||
<div className="text-zinc-500 text-sm mb-6">
|
||||
Showing {filteredArtworks.length} artwork{filteredArtworks.length !== 1 ? "s" : ""}
|
||||
</div>
|
||||
|
||||
{/* Gallery Grid */}
|
||||
{filteredArtworks.length > 0 ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className={`grid gap-6 ${
|
||||
viewMode === "grid"
|
||||
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
|
||||
: "grid-cols-1 sm:grid-cols-2"
|
||||
}`}
|
||||
>
|
||||
{filteredArtworks.map((artwork, index) => (
|
||||
<motion.div
|
||||
key={artwork.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: index * 0.05 }}
|
||||
className="group bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden hover:border-zinc-700 transition-all"
|
||||
>
|
||||
{/* Image */}
|
||||
<div
|
||||
className="relative aspect-square cursor-pointer overflow-hidden"
|
||||
onClick={() => setSelectedArtwork(artwork)}
|
||||
>
|
||||
<Image
|
||||
src={artwork.imageUrl}
|
||||
alt={artwork.title}
|
||||
fill
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100">
|
||||
<Eye className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
{/* Category Badge */}
|
||||
<div className="absolute top-3 left-3">
|
||||
<span className="inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium bg-black/70 text-white backdrop-blur-sm">
|
||||
{artwork.category === "digital-art" && <Monitor className="w-3 h-3" />}
|
||||
{artwork.category === "paintings" && <Palette className="w-3 h-3" />}
|
||||
{artwork.category === "poster-making" && <FileImage className="w-3 h-3" />}
|
||||
{categories.find(c => c.id === artwork.category)?.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="p-4">
|
||||
<h3 className="text-white font-bold text-lg mb-1 truncate">{artwork.title}</h3>
|
||||
<p className="text-zinc-400 text-sm mb-4">
|
||||
by {artwork.artist} • {artwork.department}
|
||||
</p>
|
||||
|
||||
{/* Vote Button */}
|
||||
<button
|
||||
onClick={() => handleVote(artwork.id)}
|
||||
disabled={votedArtworks.has(artwork.id)}
|
||||
className={`w-full flex items-center justify-center gap-2 px-4 py-3 rounded-xl font-semibold transition-all ${
|
||||
votedArtworks.has(artwork.id)
|
||||
? "bg-lime-400/20 text-lime-400 cursor-default"
|
||||
: "bg-zinc-800 text-white hover:bg-lime-400 hover:text-black"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-5 h-5 ${votedArtworks.has(artwork.id) ? "fill-lime-400" : ""}`} />
|
||||
{votedArtworks.has(artwork.id) ? "Voted!" : "Vote for this"}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<div className="w-16 h-16 rounded-full bg-zinc-900 flex items-center justify-center mx-auto mb-4">
|
||||
<Search className="w-8 h-8 text-zinc-600" />
|
||||
</div>
|
||||
<h3 className="text-white font-bold text-xl mb-2">No artworks found</h3>
|
||||
<p className="text-zinc-500">Try adjusting your search or filter criteria</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Artwork Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedArtwork && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/90 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
onClick={() => setSelectedArtwork(null)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-3xl overflow-hidden max-w-4xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="relative aspect-video">
|
||||
<Image
|
||||
src={selectedArtwork.imageUrl}
|
||||
alt={selectedArtwork.title}
|
||||
fill
|
||||
sizes="(max-width: 1024px) 100vw, 896px"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedArtwork(null)}
|
||||
className="absolute top-4 right-4 p-2 bg-black/50 backdrop-blur-sm rounded-full hover:bg-black/70 transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 md:p-8">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl md:text-3xl font-black text-white mb-2">
|
||||
{selectedArtwork.title}
|
||||
</h2>
|
||||
<p className="text-zinc-400">
|
||||
by <span className="text-lime-400">{selectedArtwork.artist}</span>
|
||||
</p>
|
||||
</div>
|
||||
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium bg-zinc-800 text-white">
|
||||
{selectedArtwork.category === "digital-art" && <Monitor className="w-4 h-4" />}
|
||||
{selectedArtwork.category === "paintings" && <Palette className="w-4 h-4" />}
|
||||
{selectedArtwork.category === "poster-making" && <FileImage className="w-4 h-4" />}
|
||||
{categories.find(c => c.id === selectedArtwork.category)?.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4 mb-6">
|
||||
<div className="bg-zinc-800/50 rounded-xl p-4">
|
||||
<p className="text-zinc-500 text-sm mb-1">Department</p>
|
||||
<p className="text-white font-semibold">{selectedArtwork.department}</p>
|
||||
</div>
|
||||
<div className="bg-zinc-800/50 rounded-xl p-4">
|
||||
<p className="text-zinc-500 text-sm mb-1">Year</p>
|
||||
<p className="text-white font-semibold">{selectedArtwork.year}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleVote(selectedArtwork.id)}
|
||||
disabled={votedArtworks.has(selectedArtwork.id)}
|
||||
className={`w-full flex items-center justify-center gap-2 px-6 py-4 rounded-xl font-bold transition-all ${
|
||||
votedArtworks.has(selectedArtwork.id)
|
||||
? "bg-lime-400/20 text-lime-400 cursor-default"
|
||||
: "bg-lime-400 text-black hover:bg-lime-300"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-5 h-5 ${votedArtworks.has(selectedArtwork.id) ? "fill-lime-400" : ""}`} />
|
||||
{votedArtworks.has(selectedArtwork.id) ? "You voted for this artwork!" : "Vote for this Artwork"}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
16
app/(pages)/layout.tsx
Normal file
16
app/(pages)/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import Navbar from "../components/Navbar";
|
||||
import Footer from "../components/Footer";
|
||||
|
||||
export default function PagesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
395
app/(pages)/register/page.tsx
Normal file
395
app/(pages)/register/page.tsx
Normal file
@@ -0,0 +1,395 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Mail,
|
||||
User,
|
||||
Phone,
|
||||
GraduationCap,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
Chrome
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
const departments = [
|
||||
"Computer Science & Engineering",
|
||||
"Information Science & Engineering",
|
||||
"Electronics & Communication Engineering",
|
||||
"Electrical & Electronics Engineering",
|
||||
"Mechanical Engineering",
|
||||
"Civil Engineering",
|
||||
"Artificial Intelligence & Machine Learning",
|
||||
"Data Science",
|
||||
];
|
||||
|
||||
const years = ["1st Year", "2nd Year", "3rd Year", "4th Year"];
|
||||
|
||||
export default function RegisterPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
department: "",
|
||||
year: "",
|
||||
agreeToRules: false,
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isRegistered, setIsRegistered] = useState(false);
|
||||
const [isEmailValid, setIsEmailValid] = useState<boolean | null>(null);
|
||||
const [isPhoneValid, setIsPhoneValid] = useState<boolean | null>(null);
|
||||
|
||||
const validateEmail = (email: string) => {
|
||||
const sjecEmailRegex = /^[a-zA-Z0-9._%+-]+@sjec\.ac\.in$/;
|
||||
return sjecEmailRegex.test(email);
|
||||
};
|
||||
|
||||
const validatePhone = (phone: string) => {
|
||||
const phoneRegex = /^(\+91[\s-]?)?[6-9]\d{9}$/;
|
||||
return phoneRegex.test(phone.replace(/\s/g, ""));
|
||||
};
|
||||
|
||||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const email = e.target.value;
|
||||
setFormData({ ...formData, email });
|
||||
if (email.length > 0) {
|
||||
setIsEmailValid(validateEmail(email));
|
||||
} else {
|
||||
setIsEmailValid(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const phone = e.target.value;
|
||||
setFormData({ ...formData, phone });
|
||||
if (phone.length > 0) {
|
||||
setIsPhoneValid(validatePhone(phone));
|
||||
} else {
|
||||
setIsPhoneValid(null);
|
||||
}
|
||||
};
|
||||
|
||||
const isFormValid =
|
||||
formData.name.trim().length > 0 &&
|
||||
isEmailValid === true &&
|
||||
isPhoneValid !== false &&
|
||||
formData.phone.trim().length > 0 &&
|
||||
formData.department.length > 0 &&
|
||||
formData.year.length > 0 &&
|
||||
formData.agreeToRules;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
// TODO: Implement actual registration logic
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setIsSubmitting(false);
|
||||
setIsRegistered(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
{isRegistered ? (
|
||||
<div className="max-w-xl mx-auto px-4 sm:px-6 lg:px-8 text-center pt-20">
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full bg-lime-400 flex items-center justify-center mx-auto mb-6">
|
||||
<CheckCircle className="w-10 h-10 text-black" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl font-black text-white mb-4">
|
||||
Registration Successful!
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg mb-8">
|
||||
Welcome to ArtSplash 2026! You can now submit your artwork.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/submit"
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold hover:bg-lime-300 transition-all"
|
||||
>
|
||||
Submit Artwork
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-8 py-4 rounded-full font-bold border border-zinc-700 hover:border-zinc-500 transition-all"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-start">
|
||||
{/* Left Column - Info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
JOIN ARTSPLASH
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-6">
|
||||
Register to
|
||||
<span className="text-lime-400"> Compete</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg mb-8 leading-relaxed">
|
||||
Create your account to participate in ArtSplash 2026.
|
||||
Only SJEC students with valid college email addresses can register to submit artwork.
|
||||
</p>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="space-y-4 mb-8">
|
||||
{[
|
||||
"Submit artwork in up to 3 categories",
|
||||
"Get your art seen by hundreds of voters",
|
||||
"Compete for exciting prizes",
|
||||
"Receive feedback from moderators",
|
||||
].map((benefit, index) => (
|
||||
<div key={index} className="flex items-center gap-3">
|
||||
<CheckCircle className="w-5 h-5 text-lime-400" />
|
||||
<span className="text-zinc-300">{benefit}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Important Notice */}
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-lime-400 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="text-white font-semibold mb-2">Important</h3>
|
||||
<p className="text-zinc-400 text-sm">
|
||||
You must use your official SJEC email address (@sjec.ac.in) to register
|
||||
and submit artwork. Public voters can use any email address.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Column - Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-3xl p-8">
|
||||
{/* OAuth Button */}
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-center gap-3 bg-white text-black px-6 py-4 rounded-xl font-semibold hover:bg-zinc-100 transition-colors mb-6"
|
||||
>
|
||||
<Chrome className="w-5 h-5" />
|
||||
Continue with Google
|
||||
</button>
|
||||
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-zinc-700" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-4 bg-zinc-900 text-zinc-500">or register with email</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label className="block text-white text-sm font-medium mb-2">
|
||||
Full Name
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" />
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl pl-12 pr-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
placeholder="Enter your full name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Field */}
|
||||
<div>
|
||||
<label className="block text-white text-sm font-medium mb-2">
|
||||
SJEC Email Address
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" />
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleEmailChange}
|
||||
className={`w-full bg-zinc-800 border rounded-xl pl-12 pr-12 py-3 text-white placeholder-zinc-500 focus:outline-none transition-colors ${
|
||||
isEmailValid === null
|
||||
? "border-zinc-700 focus:border-lime-400"
|
||||
: isEmailValid
|
||||
? "border-lime-400"
|
||||
: "border-red-500"
|
||||
}`}
|
||||
placeholder="yourname@sjec.ac.in"
|
||||
/>
|
||||
{isEmailValid !== null && (
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2">
|
||||
{isEmailValid ? (
|
||||
<CheckCircle className="w-5 h-5 text-lime-400" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isEmailValid === false && (
|
||||
<p className="text-red-500 text-sm mt-2">
|
||||
Please use your SJEC email address (@sjec.ac.in)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Phone Field */}
|
||||
<div>
|
||||
<label className="block text-white text-sm font-medium mb-2">
|
||||
Phone Number
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" />
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={formData.phone}
|
||||
onChange={handlePhoneChange}
|
||||
className={`w-full bg-zinc-800 border rounded-xl pl-12 pr-12 py-3 text-white placeholder-zinc-500 focus:outline-none transition-colors ${
|
||||
isPhoneValid === null
|
||||
? "border-zinc-700 focus:border-lime-400"
|
||||
: isPhoneValid
|
||||
? "border-lime-400"
|
||||
: "border-red-500"
|
||||
}`}
|
||||
placeholder="+91 XXXXX XXXXX"
|
||||
/>
|
||||
{isPhoneValid !== null && (
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2">
|
||||
{isPhoneValid ? (
|
||||
<CheckCircle className="w-5 h-5 text-lime-400" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isPhoneValid === false && (
|
||||
<p className="text-red-500 text-sm mt-2">
|
||||
Please enter a valid Indian phone number
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Department & Year */}
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-white text-sm font-medium mb-2">
|
||||
Department
|
||||
</label>
|
||||
<div className="relative">
|
||||
<GraduationCap className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" />
|
||||
<select
|
||||
required
|
||||
value={formData.department}
|
||||
onChange={(e) => setFormData({ ...formData, department: e.target.value })}
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl pl-12 pr-4 py-3 text-white appearance-none focus:outline-none focus:border-lime-400 transition-colors"
|
||||
>
|
||||
<option value="">Select</option>
|
||||
{departments.map((dept) => (
|
||||
<option key={dept} value={dept}>
|
||||
{dept}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-white text-sm font-medium mb-2">
|
||||
Year
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
value={formData.year}
|
||||
onChange={(e) => setFormData({ ...formData, year: e.target.value })}
|
||||
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white appearance-none focus:outline-none focus:border-lime-400 transition-colors"
|
||||
>
|
||||
<option value="">Select Year</option>
|
||||
{years.map((year) => (
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terms Checkbox */}
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="agreeToRules"
|
||||
required
|
||||
checked={formData.agreeToRules}
|
||||
onChange={(e) => setFormData({ ...formData, agreeToRules: e.target.checked })}
|
||||
className="mt-1 w-4 h-4 rounded border-zinc-700 bg-zinc-800 text-lime-400 focus:ring-lime-400"
|
||||
/>
|
||||
<label htmlFor="agreeToRules" className="text-zinc-400 text-sm">
|
||||
I have read and agree to the{" "}
|
||||
<Link href="/rules" className="text-lime-400 hover:underline">
|
||||
rules and guidelines
|
||||
</Link>{" "}
|
||||
of ArtSplash 2026
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isFormValid}
|
||||
className="w-full flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-4 rounded-xl font-bold tracking-wider hover:bg-lime-300 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-black/30 border-t-black rounded-full animate-spin" />
|
||||
REGISTERING...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
CREATE ACCOUNT
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-zinc-500 text-sm mt-6">
|
||||
Already have an account?{" "}
|
||||
<Link href="/login" className="text-lime-400 hover:underline">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
312
app/(pages)/rules/page.tsx
Normal file
312
app/(pages)/rules/page.tsx
Normal file
@@ -0,0 +1,312 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
FileImage,
|
||||
Users,
|
||||
Vote,
|
||||
Award,
|
||||
Clock,
|
||||
Mail
|
||||
} from "lucide-react";
|
||||
|
||||
const eligibilityRules = [
|
||||
{ text: "Must be a current student of St. Joseph Engineering College (SJEC)", allowed: true },
|
||||
{ text: "Must have a valid SJEC email address (@sjec.ac.in) to submit artwork", allowed: true },
|
||||
{ text: "One submission per category per participant", allowed: true },
|
||||
{ text: "Participants can enter multiple categories", allowed: true },
|
||||
{ text: "Alumni and faculty are not eligible to participate", allowed: false },
|
||||
{ text: "Group submissions are not allowed", allowed: false },
|
||||
];
|
||||
|
||||
const submissionRules = [
|
||||
{
|
||||
icon: FileImage,
|
||||
title: "File Requirements",
|
||||
rules: [
|
||||
"Accepted formats: JPG, PNG, PDF",
|
||||
"Maximum file size: 10MB",
|
||||
"Minimum resolution: 1920x1080 pixels",
|
||||
"Digital art must be in RGB color mode",
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: CheckCircle,
|
||||
title: "Content Guidelines",
|
||||
rules: [
|
||||
"All artwork must be original and created by the participant",
|
||||
"No AI-generated artwork will be accepted",
|
||||
"Artwork must not contain offensive, inappropriate, or copyrighted content",
|
||||
"Previously published work may be submitted if you own the rights",
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Clock,
|
||||
title: "Submission Details",
|
||||
rules: [
|
||||
"Include a title (max 100 characters)",
|
||||
"Provide a description of your artwork (max 500 characters)",
|
||||
"Select the appropriate category",
|
||||
"Fill in accurate artist details",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const categoryRules = [
|
||||
{
|
||||
title: "Digital Art",
|
||||
description: "Artwork created using digital tools and software",
|
||||
requirements: [
|
||||
"Created using software like Photoshop, Illustrator, Procreate, etc.",
|
||||
"Digital paintings, illustrations, and photo manipulations accepted",
|
||||
"Must be entirely digitally created (no scanned traditional art)",
|
||||
"3D renders and digital sculptures are accepted",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Paintings",
|
||||
description: "Traditional paintings in any medium",
|
||||
requirements: [
|
||||
"Oil, acrylic, watercolor, or any traditional painting medium",
|
||||
"Submit high-quality photographs or scans of your work",
|
||||
"Physical artwork must be completed before submission deadline",
|
||||
"Mixed media paintings are accepted",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Poster Making",
|
||||
description: "Creative posters with impactful messages",
|
||||
requirements: [
|
||||
"Can be digital or traditional",
|
||||
"Must have a clear theme or message",
|
||||
"Text and typography should be legible",
|
||||
"Theme: Open (artist's choice)",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const votingRules = [
|
||||
{ text: "Voting is open to the general public", icon: Users },
|
||||
{ text: "One vote per email address per artwork", icon: Vote },
|
||||
{ text: "CAPTCHA verification required to prevent spam", icon: AlertTriangle },
|
||||
{ text: "Vote counts will be hidden during the voting period", icon: Award },
|
||||
{ text: "Voting period: March 1-5, 2026", icon: Clock },
|
||||
{ text: "Results will be announced via email", icon: Mail },
|
||||
];
|
||||
|
||||
const disqualificationReasons = [
|
||||
"Submission of AI-generated artwork",
|
||||
"Plagiarism or copyright infringement",
|
||||
"Submitting work created by someone else",
|
||||
"Multiple submissions in the same category",
|
||||
"Inappropriate or offensive content",
|
||||
"Vote manipulation or fraudulent voting",
|
||||
"Missing or false participant information",
|
||||
];
|
||||
|
||||
export default function RulesPage() {
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
{/* Hero Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
COMPETITION RULES
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-black tracking-tight text-white mb-6">
|
||||
Rules & <span className="text-lime-400">Guidelines</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Please read all rules carefully before participating. Violation of any
|
||||
rules may result in disqualification.
|
||||
</p>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Eligibility Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl font-black text-white mb-8 flex items-center gap-3">
|
||||
<Users className="w-8 h-8 text-lime-400" />
|
||||
Eligibility
|
||||
</h2>
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 md:p-8">
|
||||
<div className="grid gap-4">
|
||||
{eligibilityRules.map((rule, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 p-3 rounded-xl bg-zinc-800/50"
|
||||
>
|
||||
{rule.allowed ? (
|
||||
<CheckCircle className="w-5 h-5 text-lime-400 flex-shrink-0 mt-0.5" />
|
||||
) : (
|
||||
<XCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
)}
|
||||
<span className="text-zinc-300">{rule.text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Submission Rules Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl font-black text-white mb-8 flex items-center gap-3">
|
||||
<FileImage className="w-8 h-8 text-lime-400" />
|
||||
Submission Rules
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{submissionRules.map((section, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-xl bg-lime-400/10 flex items-center justify-center mb-4">
|
||||
<section.icon className="w-6 h-6 text-lime-400" />
|
||||
</div>
|
||||
<h3 className="text-white font-bold text-lg mb-4">{section.title}</h3>
|
||||
<ul className="space-y-3">
|
||||
{section.rules.map((rule, ruleIndex) => (
|
||||
<li key={ruleIndex} className="flex items-start gap-2 text-zinc-400 text-sm">
|
||||
<span className="text-lime-400 mt-1">•</span>
|
||||
{rule}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Category Rules Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl font-black text-white mb-8 flex items-center gap-3">
|
||||
<Award className="w-8 h-8 text-lime-400" />
|
||||
Category Guidelines
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
{categoryRules.map((category, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 md:p-8"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row md:items-start gap-6">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-lime-400 mb-2">
|
||||
{category.title}
|
||||
</h3>
|
||||
<p className="text-zinc-400 mb-4">{category.description}</p>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-white font-semibold mb-3">Requirements:</h4>
|
||||
<ul className="space-y-2">
|
||||
{category.requirements.map((req, reqIndex) => (
|
||||
<li key={reqIndex} className="flex items-start gap-2 text-zinc-400 text-sm">
|
||||
<CheckCircle className="w-4 h-4 text-lime-400 flex-shrink-0 mt-0.5" />
|
||||
{req}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Voting Rules Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl font-black text-white mb-8 flex items-center gap-3">
|
||||
<Vote className="w-8 h-8 text-lime-400" />
|
||||
Voting Rules
|
||||
</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{votingRules.map((rule, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-xl p-4 flex items-center gap-4"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-lime-400/10 flex items-center justify-center flex-shrink-0">
|
||||
<rule.icon className="w-5 h-5 text-lime-400" />
|
||||
</div>
|
||||
<span className="text-zinc-300 text-sm">{rule.text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Disqualification Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-2xl sm:text-3xl font-black text-white mb-8 flex items-center gap-3">
|
||||
<AlertTriangle className="w-8 h-8 text-red-500" />
|
||||
Disqualification Criteria
|
||||
</h2>
|
||||
<div className="bg-red-500/10 border border-red-500/30 rounded-2xl p-6 md:p-8">
|
||||
<p className="text-zinc-300 mb-6">
|
||||
Participants may be disqualified for any of the following reasons:
|
||||
</p>
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
{disqualificationReasons.map((reason, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 p-3 rounded-xl bg-zinc-900/50"
|
||||
>
|
||||
<XCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-zinc-400 text-sm">{reason}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 p-4 bg-zinc-900 rounded-xl">
|
||||
<p className="text-zinc-400 text-sm">
|
||||
<strong className="text-white">Note:</strong> All submissions will be
|
||||
reviewed by moderators using AI detection tools and manual inspection.
|
||||
Decisions made by the organizing committee are final.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
390
app/(pages)/submit/page.tsx
Normal file
390
app/(pages)/submit/page.tsx
Normal file
@@ -0,0 +1,390 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Upload,
|
||||
FileText,
|
||||
X,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Monitor,
|
||||
Palette,
|
||||
FileImage,
|
||||
ArrowRight,
|
||||
Info
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
const categories = [
|
||||
{
|
||||
id: "digital-art",
|
||||
title: "Digital Art",
|
||||
icon: Monitor,
|
||||
description: "Artwork created using digital tools and software",
|
||||
},
|
||||
{
|
||||
id: "paintings",
|
||||
title: "Paintings",
|
||||
icon: Palette,
|
||||
description: "Traditional paintings in any medium",
|
||||
},
|
||||
{
|
||||
id: "poster-making",
|
||||
title: "Poster Making",
|
||||
icon: FileImage,
|
||||
description: "Creative posters with impactful messages",
|
||||
},
|
||||
];
|
||||
|
||||
export default function SubmitPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
title: "",
|
||||
description: "",
|
||||
category: "",
|
||||
});
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const [fileError, setFileError] = useState<string | null>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileSelect = (selectedFile: File) => {
|
||||
const validTypes = ["image/jpeg", "image/png", "application/pdf"];
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
setFileError(null);
|
||||
|
||||
if (!validTypes.includes(selectedFile.type)) {
|
||||
setFileError("Invalid file type. Please upload a JPG, PNG, or PDF file.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFile.size > maxSize) {
|
||||
setFileError("File too large. Maximum size is 10MB.");
|
||||
return;
|
||||
}
|
||||
|
||||
setFile(selectedFile);
|
||||
|
||||
if (selectedFile.type.startsWith("image/")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => setPreview(e.target?.result as string);
|
||||
reader.readAsDataURL(selectedFile);
|
||||
} else {
|
||||
setPreview(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
const droppedFile = e.dataTransfer.files[0];
|
||||
if (droppedFile) handleFileSelect(droppedFile);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!file || !formData.category) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
// TODO: Implement actual submission logic
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setIsSubmitting(false);
|
||||
setSubmitSuccess(true);
|
||||
};
|
||||
|
||||
const removeFile = () => {
|
||||
setFile(null);
|
||||
setPreview(null);
|
||||
setFileError(null);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
};
|
||||
|
||||
if (submitSuccess) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20 flex items-center justify-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="text-center px-4"
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full bg-lime-400/20 flex items-center justify-center mx-auto mb-6">
|
||||
<CheckCircle className="w-10 h-10 text-lime-400" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl font-black text-white mb-4">
|
||||
Submission Successful!
|
||||
</h1>
|
||||
<p className="text-zinc-400 mb-8 max-w-md mx-auto">
|
||||
Your artwork has been submitted for review. You'll receive an email
|
||||
notification once it's approved by our moderators.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSubmitSuccess(false);
|
||||
setFormData({ title: "", description: "", category: "" });
|
||||
setFile(null);
|
||||
setPreview(null);
|
||||
setFileError(null);
|
||||
}}
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold hover:bg-lime-300 transition-all"
|
||||
>
|
||||
Submit Another
|
||||
</button>
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-8 py-4 rounded-full font-bold border border-zinc-700 hover:border-zinc-500 transition-all"
|
||||
>
|
||||
View Gallery
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black pt-32 pb-20">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-6">
|
||||
SUBMIT ARTWORK
|
||||
</span>
|
||||
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-4">
|
||||
Share Your <span className="text-lime-400">Masterpiece</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Upload your artwork and let the world see your creativity.
|
||||
Make sure to read the rules before submitting.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Info Banner */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-4 mb-8 flex items-start gap-3"
|
||||
>
|
||||
<Info className="w-5 h-5 text-lime-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-sm text-zinc-400">
|
||||
<strong className="text-white">Submission Requirements:</strong> JPG, PNG, or PDF files up to 10MB.
|
||||
Minimum resolution 1920x1080. AI-generated artwork will be detected and rejected.
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
{/* Category Selection */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<label className="block text-white text-lg font-bold mb-4">
|
||||
Select Category
|
||||
</label>
|
||||
<div className="grid sm:grid-cols-3 gap-4">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category.id}
|
||||
type="button"
|
||||
onClick={() => setFormData({ ...formData, category: category.id })}
|
||||
className={`p-6 rounded-2xl border-2 transition-all text-left ${
|
||||
formData.category === category.id
|
||||
? "bg-lime-400/10 border-lime-400"
|
||||
: "bg-zinc-900 border-zinc-800 hover:border-zinc-700"
|
||||
}`}
|
||||
>
|
||||
<category.icon
|
||||
className={`w-8 h-8 mb-3 ${
|
||||
formData.category === category.id ? "text-lime-400" : "text-zinc-500"
|
||||
}`}
|
||||
/>
|
||||
<h3 className={`font-bold mb-1 ${
|
||||
formData.category === category.id ? "text-lime-400" : "text-white"
|
||||
}`}>
|
||||
{category.title}
|
||||
</h3>
|
||||
<p className="text-zinc-500 text-sm">{category.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* File Upload */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
>
|
||||
<label className="block text-white text-lg font-bold mb-4">
|
||||
Upload Artwork
|
||||
</label>
|
||||
<div
|
||||
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
|
||||
onDragLeave={() => setIsDragging(false)}
|
||||
onDrop={handleDrop}
|
||||
className={`relative border-2 border-dashed rounded-2xl transition-all ${
|
||||
isDragging
|
||||
? "border-lime-400 bg-lime-400/5"
|
||||
: file
|
||||
? "border-lime-400/50 bg-zinc-900"
|
||||
: "border-zinc-700 bg-zinc-900 hover:border-zinc-600"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
onChange={(e) => e.target.files?.[0] && handleFileSelect(e.target.files[0])}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
{file ? (
|
||||
<div className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
{preview ? (
|
||||
<img
|
||||
src={preview}
|
||||
alt="Preview"
|
||||
className="w-32 h-32 object-cover rounded-xl"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-32 h-32 bg-zinc-800 rounded-xl flex items-center justify-center">
|
||||
<FileText className="w-12 h-12 text-zinc-600" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<p className="text-white font-semibold mb-1">{file.name}</p>
|
||||
<p className="text-zinc-500 text-sm">
|
||||
{(file.size / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeFile}
|
||||
className="p-2 hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5 text-zinc-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-3 text-lime-400 text-sm">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
File ready for upload
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="w-full p-12 text-center"
|
||||
>
|
||||
<div className="w-16 h-16 rounded-full bg-zinc-800 flex items-center justify-center mx-auto mb-4">
|
||||
<Upload className="w-8 h-8 text-zinc-500" />
|
||||
</div>
|
||||
<p className="text-white font-semibold mb-2">
|
||||
Drop your artwork here or click to browse
|
||||
</p>
|
||||
<p className="text-zinc-500 text-sm">
|
||||
Supports JPG, PNG, PDF up to 10MB
|
||||
</p>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{fileError && (
|
||||
<div className="flex items-center gap-2 mt-3 text-red-500 text-sm">
|
||||
<AlertCircle className="w-4 h-4 flex-shrink-0" />
|
||||
{fileError}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Title & Description */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label className="block text-white text-lg font-bold mb-4">
|
||||
Artwork Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
maxLength={100}
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
className="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-4 py-4 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors"
|
||||
placeholder="Give your artwork a memorable title"
|
||||
/>
|
||||
<p className="text-zinc-500 text-sm mt-2">{formData.title.length}/100 characters</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white text-lg font-bold mb-4">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
required
|
||||
maxLength={500}
|
||||
rows={4}
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-4 py-4 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors resize-none"
|
||||
placeholder="Tell us about your artwork, the inspiration behind it, and the techniques used..."
|
||||
/>
|
||||
<p className="text-zinc-500 text-sm mt-2">{formData.description.length}/500 characters</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="flex flex-col sm:flex-row gap-4"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !file || !formData.category || !formData.title || !formData.description}
|
||||
className="flex-1 flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold tracking-wider hover:bg-lime-300 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-black/30 border-t-black rounded-full animate-spin" />
|
||||
SUBMITTING...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
SUBMIT ARTWORK
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<Link
|
||||
href="/rules"
|
||||
className="flex items-center justify-center gap-2 bg-transparent text-white px-8 py-4 rounded-full font-bold border border-zinc-700 hover:border-zinc-500 transition-all"
|
||||
>
|
||||
View Rules
|
||||
</Link>
|
||||
</motion.div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
69
app/components/CTA.tsx
Normal file
69
app/components/CTA.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, Sparkles } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function CTA() {
|
||||
return (
|
||||
<section className="py-24 bg-black relative overflow-hidden">
|
||||
<div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 bg-lime-400 text-black px-4 py-2 text-xs font-bold tracking-wider rounded-full mb-8">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
LIMITED TIME
|
||||
</div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl lg:text-6xl font-black tracking-tight text-white mb-6 leading-tight">
|
||||
Ready to Showcase
|
||||
<br />
|
||||
<span className="text-lime-400">
|
||||
Your Masterpiece?
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<p className="text-zinc-400 text-lg sm:text-xl max-w-2xl mx-auto mb-10">
|
||||
Join hundreds of talented artists from SJEC in this creative
|
||||
celebration. Submit your artwork and let the world vote for your
|
||||
talent.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/register"
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-10 py-5 text-base font-bold tracking-wide hover:bg-lime-300 transition-all hover:scale-105 rounded-full shadow-xl shadow-lime-400/25"
|
||||
>
|
||||
REGISTER NOW
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/rules"
|
||||
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-10 py-5 text-base font-bold tracking-wide border-2 border-zinc-700 hover:border-lime-400 hover:text-lime-400 transition-all rounded-full"
|
||||
>
|
||||
VIEW RULES
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Deadline Notice */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="mt-12 inline-flex items-center gap-3 bg-zinc-900 border border-zinc-800 px-6 py-3 rounded-full"
|
||||
>
|
||||
<span className="w-3 h-3 bg-red-500 rounded-full animate-pulse" />
|
||||
<span className="text-zinc-400 font-medium">
|
||||
Submissions close on <span className="text-lime-400 font-bold">February 28, 2026</span>
|
||||
</span>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
280
app/components/Categories.tsx
Normal file
280
app/components/Categories.tsx
Normal file
@@ -0,0 +1,280 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Palette, Monitor, FileImage, ArrowRight, LucideIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import gsap from "gsap";
|
||||
|
||||
const categories = [
|
||||
{
|
||||
icon: Monitor,
|
||||
title: "Digital Art",
|
||||
description:
|
||||
"Create stunning digital masterpieces using any software of your choice.",
|
||||
color: "from-violet-500 to-purple-600",
|
||||
bgColor: "bg-violet-500",
|
||||
},
|
||||
{
|
||||
icon: Palette,
|
||||
title: "Paintings",
|
||||
description:
|
||||
"Traditional paintings in any medium - oil, acrylic, watercolor.",
|
||||
color: "from-orange-500 to-red-500",
|
||||
bgColor: "bg-orange-500",
|
||||
},
|
||||
{
|
||||
icon: FileImage,
|
||||
title: "Poster Making",
|
||||
description:
|
||||
"Design impactful posters that communicate powerful messages.",
|
||||
color: "from-lime-500 to-green-500",
|
||||
bgColor: "bg-lime-500",
|
||||
},
|
||||
];
|
||||
|
||||
// BounceCards Component for Category Cards
|
||||
interface CategoryCard {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
interface BounceCardsProps {
|
||||
className?: string;
|
||||
cards: CategoryCard[];
|
||||
containerWidth?: number;
|
||||
containerHeight?: number;
|
||||
animationDelay?: number;
|
||||
animationStagger?: number;
|
||||
easeType?: string;
|
||||
transformStyles?: string[];
|
||||
enableHover?: boolean;
|
||||
}
|
||||
|
||||
function BounceCards({
|
||||
className = "",
|
||||
cards,
|
||||
containerWidth = 500,
|
||||
containerHeight = 400,
|
||||
animationDelay = 0.5,
|
||||
animationStagger = 0.08,
|
||||
easeType = "elastic.out(1, 0.8)",
|
||||
transformStyles = [
|
||||
"rotate(-8deg) translate(-200px)",
|
||||
"rotate(0deg)",
|
||||
"rotate(8deg) translate(200px)",
|
||||
],
|
||||
enableHover = true,
|
||||
}: BounceCardsProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.fromTo(
|
||||
".card",
|
||||
{ scale: 0 },
|
||||
{
|
||||
scale: 1,
|
||||
stagger: animationStagger,
|
||||
ease: easeType,
|
||||
delay: animationDelay,
|
||||
}
|
||||
);
|
||||
}, containerRef);
|
||||
return () => ctx.revert();
|
||||
}, [animationDelay, animationStagger, easeType]);
|
||||
|
||||
const getNoRotationTransform = (transformStr: string): string => {
|
||||
const hasRotate = /rotate\([\s\S]*?\)/.test(transformStr);
|
||||
if (hasRotate) {
|
||||
return transformStr.replace(/rotate\([\s\S]*?\)/, "rotate(0deg)");
|
||||
} else if (transformStr === "none") {
|
||||
return "rotate(0deg)";
|
||||
} else {
|
||||
return `${transformStr} rotate(0deg)`;
|
||||
}
|
||||
};
|
||||
|
||||
const getPushedTransform = (
|
||||
baseTransform: string,
|
||||
offsetX: number
|
||||
): string => {
|
||||
const translateRegex = /translate\(([-0-9.]+)px\)/;
|
||||
const match = baseTransform.match(translateRegex);
|
||||
if (match) {
|
||||
const currentX = parseFloat(match[1]);
|
||||
const newX = currentX + offsetX;
|
||||
return baseTransform.replace(translateRegex, `translate(${newX}px)`);
|
||||
} else {
|
||||
return baseTransform === "none"
|
||||
? `translate(${offsetX}px)`
|
||||
: `${baseTransform} translate(${offsetX}px)`;
|
||||
}
|
||||
};
|
||||
|
||||
const pushSiblings = (hoveredIdx: number) => {
|
||||
const q = gsap.utils.selector(containerRef);
|
||||
if (!enableHover || !containerRef.current) return;
|
||||
|
||||
cards.forEach((_, i) => {
|
||||
const selector = q(`.card-${i}`);
|
||||
gsap.killTweensOf(selector);
|
||||
|
||||
const baseTransform = transformStyles[i] || "none";
|
||||
|
||||
if (i === hoveredIdx) {
|
||||
const noRotation = getNoRotationTransform(baseTransform);
|
||||
gsap.to(selector, {
|
||||
transform: noRotation,
|
||||
zIndex: 10,
|
||||
duration: 0.4,
|
||||
ease: "back.out(1.4)",
|
||||
overwrite: "auto",
|
||||
});
|
||||
} else {
|
||||
const offsetX = i < hoveredIdx ? -80 : 80;
|
||||
const pushedTransform = getPushedTransform(baseTransform, offsetX);
|
||||
|
||||
const distance = Math.abs(hoveredIdx - i);
|
||||
const delay = distance * 0.05;
|
||||
|
||||
gsap.to(selector, {
|
||||
transform: pushedTransform,
|
||||
zIndex: 1,
|
||||
duration: 0.4,
|
||||
ease: "back.out(1.4)",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const resetSiblings = () => {
|
||||
if (!enableHover || !containerRef.current) return;
|
||||
const q = gsap.utils.selector(containerRef);
|
||||
|
||||
cards.forEach((_, i) => {
|
||||
const selector = q(`.card-${i}`);
|
||||
gsap.killTweensOf(selector);
|
||||
|
||||
const baseTransform = transformStyles[i] || "none";
|
||||
gsap.to(selector, {
|
||||
transform: baseTransform,
|
||||
zIndex: cards.length - i,
|
||||
duration: 0.4,
|
||||
ease: "back.out(1.4)",
|
||||
overwrite: "auto",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center justify-center ${className}`}
|
||||
ref={containerRef}
|
||||
style={{
|
||||
width: containerWidth,
|
||||
height: containerHeight,
|
||||
}}
|
||||
>
|
||||
{cards.map((card, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`card card-${idx} absolute w-[220px] h-[280px] border-4 border-zinc-800 rounded-[24px] overflow-hidden bg-zinc-900 p-6 flex flex-col cursor-pointer`}
|
||||
style={{
|
||||
boxShadow: "0 8px 40px rgba(0, 0, 0, 0.5)",
|
||||
transform: transformStyles[idx] || "none",
|
||||
zIndex: cards.length - idx,
|
||||
}}
|
||||
onMouseEnter={() => pushSiblings(idx)}
|
||||
onMouseLeave={resetSiblings}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`w-14 h-14 rounded-xl bg-gradient-to-br ${card.color} flex items-center justify-center mb-4 flex-shrink-0`}
|
||||
>
|
||||
<card.icon className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-bold text-white mb-2">{card.title}</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-zinc-400 text-sm leading-relaxed flex-grow">
|
||||
{card.description}
|
||||
</p>
|
||||
|
||||
{/* Submit Link */}
|
||||
<div className="mt-4 pt-4 border-t border-zinc-800">
|
||||
<Link
|
||||
href="/submit"
|
||||
className="inline-flex items-center gap-2 text-lime-400 text-sm font-semibold hover:text-lime-300 transition-colors"
|
||||
>
|
||||
Submit Now
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Categories() {
|
||||
const transformStyles = [
|
||||
"rotate(-8deg) translate(-220px)",
|
||||
"rotate(0deg)",
|
||||
"rotate(8deg) translate(220px)",
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-black overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-4">
|
||||
CATEGORIES
|
||||
</span>
|
||||
<h2 className="text-4xl sm:text-5xl font-black tracking-tight text-white mb-4">
|
||||
Three Ways to
|
||||
<span className="text-lime-400"> Express</span>
|
||||
</h2>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
Choose your canvas, unleash your creativity. Each category offers a
|
||||
unique way to showcase your artistic vision.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* BounceCards with Category Cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<BounceCards
|
||||
cards={categories}
|
||||
containerWidth={700}
|
||||
containerHeight={380}
|
||||
animationDelay={0.3}
|
||||
animationStagger={0.1}
|
||||
easeType="elastic.out(1, 0.8)"
|
||||
transformStyles={transformStyles}
|
||||
enableHover={true}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
111
app/components/Footer.tsx
Normal file
111
app/components/Footer.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Diamond, Mail, Instagram, Twitter } from "lucide-react";
|
||||
|
||||
const footerLinks = {
|
||||
competition: [
|
||||
{ href: "/register", label: "Register" },
|
||||
{ href: "/submit", label: "Submit Art" },
|
||||
{ href: "/gallery", label: "Gallery" },
|
||||
{ href: "/rules", label: "Rules" },
|
||||
],
|
||||
information: [
|
||||
{ href: "/about", label: "About" },
|
||||
{ href: "/faq", label: "FAQ" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
],
|
||||
};
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-zinc-950 text-white border-t border-zinc-800">
|
||||
{/* Main Footer */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12">
|
||||
{/* Brand */}
|
||||
<div className="lg:col-span-2">
|
||||
<Link href="/" className="flex items-center gap-2 mb-6">
|
||||
<Diamond className="w-8 h-8 text-lime-400" />
|
||||
<span className="text-2xl font-black tracking-tight">ARTSPLASH</span>
|
||||
</Link>
|
||||
<p className="text-zinc-400 max-w-md mb-6 leading-relaxed">
|
||||
ArtSplash is SJEC's premier art competition celebrating creativity
|
||||
across Digital Art, Paintings, and Poster Making. Join us in this
|
||||
vibrant celebration of artistic expression.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a
|
||||
href="#"
|
||||
className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center hover:bg-lime-400 hover:text-black transition-colors"
|
||||
>
|
||||
<Instagram className="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center hover:bg-lime-400 hover:text-black transition-colors"
|
||||
>
|
||||
<Twitter className="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="mailto:artsplash@sjec.ac.in"
|
||||
className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center hover:bg-lime-400 hover:text-black transition-colors"
|
||||
>
|
||||
<Mail className="w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Competition Links */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4">Competition</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.competition.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-zinc-400 hover:text-lime-400 transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Information Links */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4">Information</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.information.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-zinc-400 hover:text-lime-400 transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="border-t border-zinc-800">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-zinc-500 text-sm">
|
||||
© 2026 ArtSplash. All rights reserved.
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-zinc-500 text-sm">
|
||||
<span>Powered by</span>
|
||||
<span className="text-lime-400 font-bold">Sceptix</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
233
app/components/Hero.tsx
Normal file
233
app/components/Hero.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useCallback, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import gsap from "gsap";
|
||||
|
||||
function useGridSize() {
|
||||
const [gridSize, setGridSize] = useState({ cols: 20, rows: 12 });
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const updateGridSize = () => {
|
||||
const width = window.innerWidth;
|
||||
if (width < 640) {
|
||||
setGridSize({ cols: 8, rows: 10 });
|
||||
} else if (width < 1024) {
|
||||
setGridSize({ cols: 12, rows: 10 });
|
||||
} else {
|
||||
setGridSize({ cols: 20, rows: 12 });
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUpdate = () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(updateGridSize, 150);
|
||||
};
|
||||
|
||||
updateGridSize();
|
||||
window.addEventListener("resize", debouncedUpdate);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
window.removeEventListener("resize", debouncedUpdate);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return gridSize;
|
||||
}
|
||||
|
||||
function TileGrid() {
|
||||
const gridRef = useRef<HTMLDivElement>(null);
|
||||
const tilesRef = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const { cols, rows } = useGridSize();
|
||||
|
||||
const handleMouseEnter = useCallback((index: number) => {
|
||||
const tile = tilesRef.current[index];
|
||||
if (tile) {
|
||||
gsap.killTweensOf(tile);
|
||||
gsap.to(tile, {
|
||||
rotateY: 180,
|
||||
duration: 0.06, // Reduced duration for quicker flip
|
||||
ease: "power2.inOut",
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback((index: number) => {
|
||||
const tile = tilesRef.current[index];
|
||||
if (tile) {
|
||||
gsap.killTweensOf(tile);
|
||||
gsap.to(tile, {
|
||||
rotateY: 0,
|
||||
duration: 0.6, // Reduced duration for quicker flip back
|
||||
ease: "power2.inOut",
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Reset refs array when grid size changes
|
||||
useEffect(() => {
|
||||
tilesRef.current = [];
|
||||
}, [cols, rows]);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`grid-${cols}-${rows}`}
|
||||
ref={gridRef}
|
||||
className="absolute inset-0 grid z-0"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${rows}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: cols * rows }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onMouseEnter={() => handleMouseEnter(index)}
|
||||
onMouseLeave={() => handleMouseLeave(index)}
|
||||
className="cursor-pointer"
|
||||
style={{ perspective: "500px" }}
|
||||
>
|
||||
<div
|
||||
ref={(el) => { tilesRef.current[index] = el; }}
|
||||
className="w-full h-full relative"
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
}}
|
||||
>
|
||||
{/* Front - Black */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black border border-zinc-900"
|
||||
style={{ backfaceVisibility: "hidden" }}
|
||||
/>
|
||||
{/* Back - White */}
|
||||
<div
|
||||
className="absolute inset-0 bg-white"
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
transform: "rotateY(180deg)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Hero() {
|
||||
const headingRef = useRef<HTMLHeadingElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Animate heading on mount
|
||||
if (headingRef.current) {
|
||||
gsap.fromTo(
|
||||
headingRef.current,
|
||||
{ opacity: 0, y: 50 },
|
||||
{ opacity: 1, y: 0, duration: 1, ease: "power3.out", delay: 0.3 }
|
||||
);
|
||||
}
|
||||
|
||||
if (contentRef.current) {
|
||||
gsap.fromTo(
|
||||
contentRef.current.children,
|
||||
{ opacity: 0, y: 30 },
|
||||
{ opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: "power3.out", delay: 0.5 }
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="relative min-h-screen bg-black overflow-hidden pt-28">
|
||||
{/* Tile Grid Background */}
|
||||
<TileGrid />
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full pointer-events-none">
|
||||
<div className="flex items-center justify-center min-h-[calc(100vh-12rem)] pb-20">
|
||||
{/* Center Content */}
|
||||
<div className="text-center relative z-10">
|
||||
<div className="inline-flex items-center gap-2 bg-lime-400 text-black px-5 py-2.5 text-xs font-bold tracking-widest rounded-full mb-10 pointer-events-auto">
|
||||
<span className="w-2 h-2 bg-black rounded-full animate-pulse" />
|
||||
FEBRUARY 2026
|
||||
</div>
|
||||
|
||||
<h1
|
||||
ref={headingRef}
|
||||
className="text-6xl sm:text-7xl lg:text-8xl xl:text-9xl font-black tracking-tighter text-white leading-[0.85] mb-8"
|
||||
style={{
|
||||
WebkitTextStroke: "2px black",
|
||||
paintOrder: "stroke fill",
|
||||
}}
|
||||
>
|
||||
<span className="block">UNLEASH</span>
|
||||
<span
|
||||
className="block text-lime-400 drop-shadow-[0_0_30px_rgba(163,230,53,0.5)]"
|
||||
style={{
|
||||
WebkitTextStroke: "2px black",
|
||||
paintOrder: "stroke fill",
|
||||
}}
|
||||
>
|
||||
YOUR ART
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div ref={contentRef}>
|
||||
<p className="text-zinc-300 text-base sm:text-lg max-w-lg mx-auto mb-10 leading-relaxed font-light drop-shadow-[0_2px_4px_rgba(0,0,0,0.8)]">
|
||||
From canvas to screen, showcase your creativity and let your
|
||||
artwork speak to the world.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center pointer-events-auto mb-12">
|
||||
<Link
|
||||
href="/register"
|
||||
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-10 py-4 text-sm font-bold tracking-wider hover:bg-lime-300 transition-all hover:scale-105 rounded-full shadow-[0_0_30px_rgba(163,230,53,0.3)]"
|
||||
>
|
||||
JOIN COMPETITION
|
||||
</Link>
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-10 py-4 text-sm font-bold tracking-wider hover:bg-white/10 transition-all rounded-full border border-zinc-700 hover:border-zinc-500"
|
||||
>
|
||||
EXPLORE GALLERY
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<span className="text-lime-400 text-[10px] tracking-[0.3em] uppercase drop-shadow-[0_0_10px_rgba(163,230,53,0.8)]">Scroll</span>
|
||||
<div className="w-px h-8 bg-gradient-to-b from-lime-400 to-transparent shadow-[0_0_10px_rgba(163,230,53,0.5)]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Marquee */}
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-lime-400 text-black py-3 overflow-hidden z-10">
|
||||
<div className="animate-marquee-slow whitespace-nowrap flex items-center">
|
||||
{Array(8)
|
||||
.fill(null)
|
||||
.map((_, i) => (
|
||||
<div key={i} className="flex items-center">
|
||||
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase">
|
||||
ArtSplash
|
||||
</span>
|
||||
<span className="text-black/40 mx-4">✦</span>
|
||||
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase text-black/60">
|
||||
SJEC
|
||||
</span>
|
||||
<span className="text-black/40 mx-4">✦</span>
|
||||
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase">
|
||||
2026
|
||||
</span>
|
||||
<span className="text-black/40 mx-4">✦</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
101
app/components/HowItWorks.tsx
Normal file
101
app/components/HowItWorks.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { UserPlus, Upload, Shield, Vote, Trophy } from "lucide-react";
|
||||
|
||||
const steps = [
|
||||
{
|
||||
icon: UserPlus,
|
||||
step: "01",
|
||||
title: "Register",
|
||||
description: "Sign up with your SJEC email to participate in the competition.",
|
||||
},
|
||||
{
|
||||
icon: Upload,
|
||||
step: "02",
|
||||
title: "Submit",
|
||||
description: "Upload your artwork with title, description, and category details.",
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
step: "03",
|
||||
title: "Review",
|
||||
description: "Moderators review submissions to ensure guidelines are met.",
|
||||
},
|
||||
{
|
||||
icon: Vote,
|
||||
step: "04",
|
||||
title: "Public Voting",
|
||||
description: "The public votes for their favorite artworks in each category.",
|
||||
},
|
||||
{
|
||||
icon: Trophy,
|
||||
step: "05",
|
||||
title: "Results",
|
||||
description: "Winners are announced and celebrated across all categories.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function HowItWorks() {
|
||||
return (
|
||||
<section className="py-24 bg-black text-white overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<span className="inline-block bg-lime-400 text-black px-4 py-1.5 text-xs font-bold tracking-wider rounded-full mb-4">
|
||||
HOW IT WORKS
|
||||
</span>
|
||||
<h2 className="text-4xl sm:text-5xl font-black tracking-tight mb-4">
|
||||
Your Journey to
|
||||
<span className="text-lime-400"> Victory</span>
|
||||
</h2>
|
||||
<p className="text-zinc-400 text-lg max-w-2xl mx-auto">
|
||||
From registration to winning, here's how ArtSplash works.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Steps Timeline */}
|
||||
<div className="relative">
|
||||
{/* Connection Line */}
|
||||
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-zinc-800 hidden lg:block" />
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-8 lg:gap-4">
|
||||
{steps.map((step, index) => (
|
||||
<motion.div
|
||||
key={step.step}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
className="relative"
|
||||
>
|
||||
{/* Card */}
|
||||
<div className="bg-zinc-900 rounded-2xl p-6 h-full border border-zinc-800 hover:border-lime-400/50 transition-colors group">
|
||||
{/* Step Number */}
|
||||
<div className="relative z-10 w-12 h-12 rounded-full bg-lime-400 text-black font-black text-sm flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
|
||||
{step.step}
|
||||
</div>
|
||||
|
||||
{/* Icon */}
|
||||
<step.icon className="w-8 h-8 text-lime-400 mb-4" />
|
||||
|
||||
{/* Content */}
|
||||
<h3 className="text-xl font-bold mb-2">{step.title}</h3>
|
||||
<p className="text-zinc-400 text-sm leading-relaxed">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
122
app/components/Navbar.tsx
Normal file
122
app/components/Navbar.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Menu, X, Diamond } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/gallery", label: "GALLERY" },
|
||||
{ href: "/submit", label: "SUBMIT ART" },
|
||||
{ href: "/about", label: "ABOUT" },
|
||||
{ href: "/rules", label: "RULES" },
|
||||
{ href: "/faq", label: "FAQ" },
|
||||
{ href: "/contact", label: "CONTACT" },
|
||||
];
|
||||
|
||||
export default function Navbar() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-zinc-950/90 backdrop-blur-md border-b border-zinc-800">
|
||||
{/* Announcement Bar */}
|
||||
<div className="bg-lime-400 text-black overflow-hidden">
|
||||
<div className="whitespace-nowrap py-2 text-xs tracking-widest flex">
|
||||
|
||||
{/* Track */}
|
||||
<div className="animate-marquee flex">
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<span key={`first-${i}`} className="mx-8 shrink-0">
|
||||
✦ OPEN FOR SUBMISSIONS
|
||||
</span>
|
||||
))}
|
||||
|
||||
{/* Duplicate for seamless loop */}
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<span key={`second-${i}`} className="mx-8 shrink-0">
|
||||
✦ OPEN FOR SUBMISSIONS
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Navbar */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center gap-2 group">
|
||||
<Diamond className="w-6 h-6 text-lime-400 group-hover:rotate-12 transition-transform" />
|
||||
<span className="text-xl font-bold tracking-tight text-white">ARTSPLASH</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="text-sm font-medium tracking-wide text-zinc-300 hover:text-white transition-colors relative group"
|
||||
>
|
||||
{link.label}
|
||||
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-lime-400 group-hover:w-full transition-all duration-300" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<div className="hidden lg:block">
|
||||
<Link
|
||||
href="/register"
|
||||
className="bg-lime-400 text-black px-6 py-2.5 text-sm font-bold tracking-wide hover:bg-lime-300 transition-colors rounded-full"
|
||||
>
|
||||
REGISTER NOW
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="lg:hidden p-2 hover:bg-zinc-800 rounded-lg transition-colors text-white"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{isOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="lg:hidden bg-zinc-950 border-t border-zinc-800"
|
||||
>
|
||||
<div className="px-4 py-6 space-y-4">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="block text-lg font-medium text-zinc-300 hover:text-white hover:pl-2 transition-all"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
href="/register"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="block w-full text-center bg-lime-400 text-black px-6 py-3 text-sm font-bold tracking-wide hover:bg-lime-300 transition-colors rounded-full mt-6"
|
||||
>
|
||||
REGISTER NOW
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
38
app/components/Stats.tsx
Normal file
38
app/components/Stats.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const stats = [
|
||||
{ value: "3", label: "Categories" },
|
||||
{ value: "∞", label: "Possibilities" },
|
||||
{ value: "1", label: "Winner Per Category" },
|
||||
{ value: "100%", label: "Free Entry" },
|
||||
];
|
||||
|
||||
export default function Stats() {
|
||||
return (
|
||||
<section className="py-16 bg-lime-400">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{stats.map((stat, index) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="text-center"
|
||||
>
|
||||
<div className="text-5xl sm:text-6xl font-black text-black mb-2">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-black/70 font-medium tracking-wide uppercase text-sm">
|
||||
{stat.label}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,94 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #000000;
|
||||
--foreground: #ffffff;
|
||||
--lime: #c5ff00;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-lime-400: #c5ff00;
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Animations */
|
||||
@keyframes marquee {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-marquee {
|
||||
animation: marquee 60s linear infinite;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.animate-marquee:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.animate-marquee-slow {
|
||||
animation: marquee 30s linear infinite;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #18181b;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c5ff00;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8e000;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background-color: #c5ff00;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
*:focus-visible {
|
||||
outline: 2px solid #c5ff00;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Gradient text utility */
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #c5ff00 0%, #84cc16 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "ArtSplash | SJEC Art Competition 2026",
|
||||
description:
|
||||
"Join SJEC's premier art competition featuring Digital Art, Paintings, and Poster Making. Register now and showcase your creativity!",
|
||||
keywords: ["art competition", "SJEC", "digital art", "paintings", "poster making", "ArtSplash"],
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -24,9 +26,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen bg-black`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
76
app/page.tsx
76
app/page.tsx
@@ -1,65 +1,23 @@
|
||||
import Image from "next/image";
|
||||
import Navbar from "./components/Navbar";
|
||||
import Hero from "./components/Hero";
|
||||
import Categories from "./components/Categories";
|
||||
import HowItWorks from "./components/HowItWorks";
|
||||
import Stats from "./components/Stats";
|
||||
import CTA from "./components/CTA";
|
||||
import Footer from "./components/Footer";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
<>
|
||||
<Navbar />
|
||||
<main>
|
||||
<Hero />
|
||||
<Categories />
|
||||
<Stats />
|
||||
<HowItWorks />
|
||||
<CTA />
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
2910
package-lock.json
generated
Normal file
2910
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,9 @@
|
||||
"check": "biome check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.34.3",
|
||||
"gsap": "^3.14.2",
|
||||
"lucide-react": "^0.575.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"next": "16.1.6",
|
||||
"pg": "^8.18.0",
|
||||
|
||||
952
pnpm-lock.yaml
generated
952
pnpm-lock.yaml
generated
@@ -17,6 +17,15 @@ importers:
|
||||
pg:
|
||||
specifier: ^8.18.0
|
||||
version: 8.18.0
|
||||
framer-motion:
|
||||
specifier: ^12.34.3
|
||||
version: 12.34.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
gsap:
|
||||
specifier: ^3.14.2
|
||||
version: 3.14.2
|
||||
lucide-react:
|
||||
specifier: ^0.575.0
|
||||
version: 0.575.0(react@19.2.3)
|
||||
react:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
@@ -567,6 +576,15 @@ packages:
|
||||
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
|
||||
engines: {node: '>= 0.12'}
|
||||
|
||||
framer-motion@12.34.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
motion-dom: 12.34.3
|
||||
motion-utils: 12.29.2
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
fs-extra@0.26.7:
|
||||
resolution: {integrity: sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==}
|
||||
|
||||
@@ -587,929 +605,7 @@ packages:
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
har-schema@2.0.0:
|
||||
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
har-validator@5.1.5:
|
||||
resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}
|
||||
engines: {node: '>=6'}
|
||||
deprecated: this library is no longer supported
|
||||
|
||||
has-ansi@2.0.0:
|
||||
resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
http-signature@1.2.0:
|
||||
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
|
||||
engines: {node: '>=0.8', npm: '>=1.3.7'}
|
||||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
inquirer-promise@0.0.3:
|
||||
resolution: {integrity: sha512-82CQX586JAV9GAgU9yXZsMDs+NorjA0nLhkfFx9+PReyOnuoHRbHrC1Z90sS95bFJI1Tm1gzMObuE0HabzkJpg==}
|
||||
|
||||
inquirer@0.11.4:
|
||||
resolution: {integrity: sha512-QR+2TW90jnKk9LUUtbcA3yQXKt2rDEKMh6+BAZQIeumtzHexnwVLdPakSslGijXYLJCzFv7GMXbFCn0pA00EUw==}
|
||||
|
||||
is-fullwidth-code-point@1.0.0:
|
||||
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-typedarray@1.0.0:
|
||||
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
|
||||
|
||||
isstream@0.1.2:
|
||||
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
jsbn@0.1.1:
|
||||
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
|
||||
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||
|
||||
json-stringify-safe@5.0.1:
|
||||
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||
|
||||
jsonfile@2.4.0:
|
||||
resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==}
|
||||
|
||||
jsprim@1.4.2:
|
||||
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
|
||||
kaiser@0.0.4:
|
||||
resolution: {integrity: sha512-m8ju+rmBqvclZmyrOXgGGhOYSjKJK6RN1NhqEltemY87UqZOxEkizg9TOy1vQSyJ01Wx6SAPuuN0iO2Mgislvw==}
|
||||
|
||||
klaw@1.3.1:
|
||||
resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.30.2:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lodash@3.10.1:
|
||||
resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==}
|
||||
|
||||
lodash@4.17.23:
|
||||
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
mute-stream@0.0.5:
|
||||
resolution: {integrity: sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg==}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
next@16.1.6:
|
||||
resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
'@playwright/test': ^1.51.1
|
||||
babel-plugin-react-compiler: '*'
|
||||
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@playwright/test':
|
||||
optional: true
|
||||
babel-plugin-react-compiler:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
number-is-nan@1.0.1:
|
||||
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
oauth-sign@0.9.0:
|
||||
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
onetime@1.1.0:
|
||||
resolution: {integrity: sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
os-homedir@1.0.2:
|
||||
resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
performance-now@2.1.0:
|
||||
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
||||
|
||||
pg-cloudflare@1.3.0:
|
||||
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
|
||||
|
||||
pg-connection-string@2.11.0:
|
||||
resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==}
|
||||
|
||||
pg-int8@1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
pg-pool@3.11.0:
|
||||
resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==}
|
||||
peerDependencies:
|
||||
pg: '>=8.0'
|
||||
|
||||
pg-protocol@1.11.0:
|
||||
resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==}
|
||||
|
||||
pg-types@2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
pg@8.18.0:
|
||||
resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
|
||||
pgpass@1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
postcss@8.4.31:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postgres-bytea@1.0.1:
|
||||
resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
postgres-date@1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
postgres-interval@1.2.0:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
psl@1.15.0:
|
||||
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qs@6.5.3:
|
||||
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
react-dom@19.2.3:
|
||||
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
||||
peerDependencies:
|
||||
react: ^19.2.3
|
||||
|
||||
react@19.2.3:
|
||||
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readline2@1.0.1:
|
||||
resolution: {integrity: sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==}
|
||||
|
||||
regenerator-runtime@0.9.6:
|
||||
resolution: {integrity: sha512-D0Y/JJ4VhusyMOd/o25a3jdUqN/bC85EFsaoL9Oqmy/O4efCh+xhp7yj2EEOsj974qvMkcW8AwUzJ1jB/MbxCw==}
|
||||
|
||||
request-promise@3.0.0:
|
||||
resolution: {integrity: sha512-wVGUX+BoKxYsavTA72i6qHcyLbjzM4LR4y/AmDCqlbuMAursZdDWO7PmgbGAUvD2SeEJ5iB99VSq/U51i/DNbw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
deprecated: request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
|
||||
|
||||
request@2.88.2:
|
||||
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
|
||||
engines: {node: '>= 6'}
|
||||
deprecated: request has been deprecated, see https://github.com/request/request/issues/3142
|
||||
|
||||
restore-cursor@1.0.1:
|
||||
resolution: {integrity: sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
rimraf@2.7.1:
|
||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
run-async@0.1.0:
|
||||
resolution: {integrity: sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw==}
|
||||
|
||||
rx-lite@3.1.2:
|
||||
resolution: {integrity: sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
sharp@0.34.5:
|
||||
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
sshpk@1.18.0:
|
||||
resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
string-width@1.0.2:
|
||||
resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-ansi@3.0.1:
|
||||
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
styled-jsx@5.1.6:
|
||||
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '*'
|
||||
babel-plugin-macros: '*'
|
||||
react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
supports-color@2.0.0:
|
||||
resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
tailwindcss@4.1.18:
|
||||
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
|
||||
|
||||
tapable@2.3.0:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
|
||||
tough-cookie@2.5.0:
|
||||
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
ts-node@10.9.2:
|
||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
tweetnacl@0.14.5:
|
||||
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
untildify@3.0.3:
|
||||
resolution: {integrity: sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
user-home@2.0.0:
|
||||
resolution: {integrity: sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
uuid@3.4.0:
|
||||
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
|
||||
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
|
||||
hasBin: true
|
||||
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
verror@1.10.0:
|
||||
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@emnapi/runtime@1.8.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@img/colour@1.0.0':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-riscv64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.8.1
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@next/env@16.1.6': {}
|
||||
|
||||
'@next/swc-darwin-arm64@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.1.6':
|
||||
optional: true
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/node@4.1.18':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.19.0
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.18':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.1.18':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.1.18
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.1.18
|
||||
'@tailwindcss/oxide-darwin-x64': 4.1.18
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.1.18
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.1.18
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.1.18
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.1.18
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.1.18
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
|
||||
|
||||
'@tailwindcss/postcss@4.1.18':
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.1.18
|
||||
'@tailwindcss/oxide': 4.1.18
|
||||
postcss: 8.5.6
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
'@tsconfig/node10@1.0.12': {}
|
||||
|
||||
'@tsconfig/node12@1.0.11': {}
|
||||
|
||||
'@tsconfig/node14@1.0.3': {}
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@types/node@20.19.31':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/pg@8.16.0':
|
||||
dependencies:
|
||||
'@types/node': 20.19.31
|
||||
pg-protocol: 1.11.0
|
||||
pg-types: 2.2.0
|
||||
|
||||
'@types/react-dom@19.2.3(@types/react@19.2.11)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.11
|
||||
|
||||
'@types/react@19.2.11':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
ansi-escapes@1.4.0: {}
|
||||
|
||||
ansi-regex@2.1.1: {}
|
||||
|
||||
ansi-styles@2.2.1: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
asn1@0.2.6:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
assert-plus@1.0.0: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
aws-sign2@0.7.0: {}
|
||||
|
||||
aws4@1.13.2: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
baseline-browser-mapping@2.9.19: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
dependencies:
|
||||
tweetnacl: 0.14.5
|
||||
|
||||
biome@0.3.3:
|
||||
dependencies:
|
||||
bluebird: 3.7.2
|
||||
chalk: 1.1.3
|
||||
commander: 2.20.3
|
||||
editor: 1.0.0
|
||||
fs-promise: 0.5.0
|
||||
inquirer-promise: 0.0.3
|
||||
request-promise: 3.0.0
|
||||
untildify: 3.0.3
|
||||
user-home: 2.0.0
|
||||
|
||||
bluebird@3.7.2: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
caniuse-lite@1.0.30001767: {}
|
||||
|
||||
caseless@0.12.0: {}
|
||||
|
||||
chalk@1.1.3:
|
||||
dependencies:
|
||||
ansi-styles: 2.2.1
|
||||
escape-string-regexp: 1.0.5
|
||||
has-ansi: 2.0.0
|
||||
strip-ansi: 3.0.1
|
||||
supports-color: 2.0.0
|
||||
|
||||
cli-cursor@1.0.2:
|
||||
dependencies:
|
||||
restore-cursor: 1.0.1
|
||||
|
||||
cli-width@1.1.1: {}
|
||||
|
||||
client-only@0.0.1: {}
|
||||
|
||||
code-point-at@1.1.0: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
core-js@2.6.12: {}
|
||||
|
||||
core-util-is@1.0.2: {}
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
dashdash@1.14.1:
|
||||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
diff@4.0.4: {}
|
||||
|
||||
dotenv@17.3.1: {}
|
||||
|
||||
earlgrey-runtime@0.1.2:
|
||||
dependencies:
|
||||
core-js: 2.6.12
|
||||
kaiser: 0.0.4
|
||||
lodash: 4.17.23
|
||||
regenerator-runtime: 0.9.6
|
||||
|
||||
ecc-jsbn@0.1.2:
|
||||
dependencies:
|
||||
jsbn: 0.1.1
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
editor@1.0.0: {}
|
||||
|
||||
enhanced-resolve@5.19.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
escape-string-regexp@1.0.5: {}
|
||||
|
||||
exit-hook@1.1.1: {}
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
extsprintf@1.3.0: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
figures@1.7.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
object-assign: 4.1.1
|
||||
|
||||
forever-agent@0.6.1: {}
|
||||
|
||||
form-data@2.3.3:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
fs-extra@0.26.7:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 2.4.0
|
||||
klaw: 1.3.1
|
||||
path-is-absolute: 1.0.1
|
||||
rimraf: 2.7.1
|
||||
|
||||
fs-promise@0.5.0:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
fs-extra: 0.26.7
|
||||
mz: 2.7.0
|
||||
thenify-all: 1.6.0
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
getpass@0.1.7:
|
||||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
|
||||
glob@7.2.3:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
gsap@3.14.2: {}
|
||||
|
||||
har-schema@2.0.0: {}
|
||||
|
||||
@@ -1646,6 +742,10 @@ snapshots:
|
||||
|
||||
lodash@4.17.23: {}
|
||||
|
||||
lucide-react@0.575.0(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -1662,6 +762,12 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
motion-dom@12.34.3:
|
||||
dependencies:
|
||||
motion-utils: 12.29.2
|
||||
|
||||
motion-utils@12.29.2: {}
|
||||
|
||||
mute-stream@0.0.5: {}
|
||||
|
||||
mz@2.7.0:
|
||||
|
||||
Reference in New Issue
Block a user