diff --git a/app/globals.css b/app/globals.css
index 4bc4c9d..9f48bd2 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,125 +1,94 @@
@import "tailwindcss";
@import "tw-animate-css";
-@custom-variant dark (&:is(.dark *));
-
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-poppins);
- --font-mono: var(--font-poppins);
- --color-sidebar-ring: var(--sidebar-ring);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar: var(--sidebar);
- --color-chart-5: var(--chart-5);
- --color-chart-4: var(--chart-4);
- --color-chart-3: var(--chart-3);
- --color-chart-2: var(--chart-2);
- --color-chart-1: var(--chart-1);
- --color-ring: var(--ring);
- --color-input: var(--input);
- --color-border: var(--border);
- --color-destructive: var(--destructive);
- --color-accent-foreground: var(--accent-foreground);
- --color-accent: var(--accent);
- --color-muted-foreground: var(--muted-foreground);
- --color-muted: var(--muted);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-secondary: var(--secondary);
- --color-primary-foreground: var(--primary-foreground);
- --color-primary: var(--primary);
- --color-popover-foreground: var(--popover-foreground);
- --color-popover: var(--popover);
- --color-card-foreground: var(--card-foreground);
- --color-card: var(--card);
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
-}
-
:root {
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
+ --foreground-rgb: 0, 0, 0;
+ --background-start-rgb: 214, 219, 220;
+ --background-end-rgb: 255, 255, 255;
}
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
+@media (prefers-color-scheme: dark) {
+ :root {
+ --foreground-rgb: 255, 255, 255;
+ --background-start-rgb: 0, 0, 0;
+ --background-end-rgb: 0, 0, 0;
+ }
+}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
}
@layer base {
* {
- @apply border-border outline-ring/50;
+ border-color: hsl(var(--border));
}
body {
- @apply bg-background text-foreground;
+ background-color: #ffffff;
+ color: hsl(var(--foreground));
}
- body.menu-open {
- overflow: hidden;
+ html.dark body {
+ background-color: #1a1e26;
+ }
+ html {
+ scroll-behavior: smooth;
+ }
+}
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
}
}
diff --git a/app/layout.tsx b/app/layout.tsx
index affedce..5b3bb9b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,29 +1,28 @@
-import type { Metadata } from "next";
-import { Poppins } from "next/font/google";
-import "./globals.css";
+import './globals.css';
+import type { Metadata } from 'next';
+import { Inter } from 'next/font/google';
+import { Providers } from './providers';
+import { Toaster } from '@/components/ui/toaster';
-const poppins = Poppins({
- variable: "--font-poppins",
- subsets: ["latin"],
- weight: ["300", "400", "500", "600", "700"],
-});
+const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
- title: "Attune Heart Therapy",
- description: "Attune Heart Therapy",
+ title: 'Attune Heart Therapy | Nathalie Mac Guffie, LCSW | Miami, FL',
+ description: 'Compassionate, evidence-based therapy in Miami, FL. Licensed Clinical Social Worker offering anxiety, depression, trauma therapy and more.',
};
export default function RootLayout({
children,
-}: Readonly<{
+}: {
children: React.ReactNode;
-}>) {
+}) {
return (
-
-
- {children}
+
+
+
+ {children}
+
+
);
diff --git a/app/page.tsx b/app/page.tsx
index 295f8fd..fced93b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,65 +1,22 @@
-import Image from "next/image";
+import Section from "../components/Section";
+import { Footer } from "../components/Footer";
+import { HeroSection } from "@/components/Hero";
+import { About } from "@/components/About";
+import { Services } from "@/components/Services";
+import { ContactSection } from "@/components/ContactSection";
export default function Home() {
return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/app/providers.tsx b/app/providers.tsx
new file mode 100644
index 0000000..e759ace
--- /dev/null
+++ b/app/providers.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import { ThemeProvider } from "../components/ThemeProvider";
+import { Navbar } from "../components/Navbar";
+import { type ReactNode } from "react";
+
+export function Providers({ children }: { children: ReactNode }) {
+ return (
+
+
+ {children}
+
+ );
+}
+
diff --git a/components/About.tsx b/components/About.tsx
new file mode 100644
index 0000000..dbc69fe
--- /dev/null
+++ b/components/About.tsx
@@ -0,0 +1,201 @@
+'use client';
+
+import { motion } from "framer-motion";
+import { useInView } from "framer-motion";
+import { useRef, useEffect, useState } from "react";
+import { Award, Heart, Users } from "lucide-react";
+
+export function About() {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, margin: "-100px" });
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const credentials = [
+ {
+ icon: Award,
+ title: "Licensed Mental Health Counselor (LMHC)",
+ description: "Florida licensed with 30 years of experience",
+ },
+ {
+ icon: Heart,
+ title: "Trauma-Focused Specialist",
+ description: "Certified in TF-CBT for trauma recovery",
+ },
+ {
+ icon: Users,
+ title: "Infant Mental Health & Play Therapy",
+ description: "Registered Play Therapist (RPT-S) and IMH Endorsement",
+ },
+ ];
+
+ return (
+
+
+ {!isDark && (
+
+ )}
+ {isDark && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ Meet Nathalie Mac-Guffie
+
+
+ A dedicated mental health professional specializing in helping children
+ under 10 and their families navigate trauma, emotional challenges, and
+ developmental needs.
+
+
+
+
+
+
+
+ My Approach
+
+
+ I provide person-centered guidance, following your child's lead while
+ drawing out their strengths and incorporating effective coping skills.
+ My interventions are relationship-based, creating a warm, non-judgmental
+ space for growth and healing.
+
+
+ Together, we'll set realistic, measurable, and achievable goals with
+ clear objectives tailored to your family's unique needs.
+
+
+
+
+
+ {credentials.map((cred, index) => {
+ const Icon = cred.icon;
+ return (
+
+
+
+
+
+
+
+ {cred.title}
+
+
+ {cred.description}
+
+
+
+
+ );
+ })}
+
+
+
+
+ );
+}
+
diff --git a/components/ContactSection.tsx b/components/ContactSection.tsx
new file mode 100644
index 0000000..088e3d7
--- /dev/null
+++ b/components/ContactSection.tsx
@@ -0,0 +1,181 @@
+"use client";
+
+import { motion, useInView } from "framer-motion";
+import { useEffect, useRef, useState } from "react";
+import { Send } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Card, CardContent } from "@/components/ui/card";
+import { toast } from "sonner";
+
+export function ContactSection() {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, margin: "-100px" });
+ const [isDark, setIsDark] = useState(false);
+ const [formData, setFormData] = useState({
+ name: "",
+ email: "",
+ phone: "",
+ message: "",
+ });
+
+ // Sync with global theme class like Navbar/Hero/About
+ useEffect(() => {
+ const sync = () => setIsDark(document.documentElement.classList.contains("dark"));
+ sync();
+ const observer = new MutationObserver(sync);
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
+ return () => observer.disconnect();
+ }, []);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ toast("Message Received", {
+ description: "Thank you for reaching out. We'll get back to you soon!",
+ });
+ setFormData({ name: "", email: "", phone: "", message: "" });
+ };
+
+ return (
+
+ );
+}
diff --git a/components/Footer.tsx b/components/Footer.tsx
new file mode 100644
index 0000000..56ed92b
--- /dev/null
+++ b/components/Footer.tsx
@@ -0,0 +1,181 @@
+'use client';
+
+import { motion } from "framer-motion";
+import { Heart, Mail, Phone, MapPin } from "lucide-react";
+import { useEffect, useState } from "react";
+
+export function Footer() {
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const scrollToSection = (id: string) => {
+ const element = document.getElementById(id);
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth" });
+ }
+ };
+
+ const quickLinks = [
+ { name: 'Home', href: '#home' },
+ { name: 'About', href: '#about' },
+ { name: 'Services', href: '#services' },
+ { name: 'Contact', href: '#contact' },
+ ];
+
+ return (
+
+ );
+}
diff --git a/components/Hero.tsx b/components/Hero.tsx
new file mode 100644
index 0000000..98b1b08
--- /dev/null
+++ b/components/Hero.tsx
@@ -0,0 +1,163 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { Button } from '@/components/ui/button';
+import { ArrowRight, Calendar } from 'lucide-react';
+import { useEffect, useState } from 'react';
+
+export function HeroSection() {
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+
+ {!isDark && (
+
+ )}
+ {isDark && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to Attune Heart Therapy
+
+
+
+ Nathalie Mac Guffie, LCSW
+
+
+
+ Compassionate, evidence-based therapy to help you heal, grow, and
+ thrive. Creating a safe space for your journey toward emotional
+ wellness.
+
+
+
+
+
+ Book Appointment
+
+
+
+ Learn More
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/Navbar.tsx b/components/Navbar.tsx
new file mode 100644
index 0000000..9d340a5
--- /dev/null
+++ b/components/Navbar.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import { motion } from "framer-motion";
+import { Button } from "@/components/ui/button";
+import { Heart } from "lucide-react";
+import { ThemeToggle } from "@/components/ThemeToggle";
+import { useEffect, useState } from "react";
+
+export function Navbar() {
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const scrollToSection = (id: string) => {
+ const element = document.getElementById(id);
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth" });
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ Attune Heart Therapy
+
+
+
+
+ scrollToSection("about")}
+ className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
+ >
+ About
+
+ scrollToSection("services")}
+ className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
+ >
+ Services
+
+ scrollToSection("contact")}
+ className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
+ >
+ Contact
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/Section.tsx b/components/Section.tsx
new file mode 100644
index 0000000..caef6c9
--- /dev/null
+++ b/components/Section.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { type ReactNode } from "react";
+
+type Props = {
+ id?: string;
+ title: string;
+ children: ReactNode;
+};
+
+export default function Section({ id, title, children }: Props) {
+ return (
+
+
+ {title}
+
+
+ {children}
+
+
+
+ );
+}
+
+
diff --git a/components/Services.tsx b/components/Services.tsx
new file mode 100644
index 0000000..7aaea79
--- /dev/null
+++ b/components/Services.tsx
@@ -0,0 +1,214 @@
+'use client';
+
+import { motion } from "framer-motion";
+import { useInView } from "framer-motion";
+import { useRef, useEffect, useState } from "react";
+import { Baby, Brain, HeartHandshake, Sparkles, Users2, Shield } from "lucide-react";
+
+export function Services() {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, margin: "-100px" });
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const services = [
+ {
+ icon: Brain,
+ title: "Trauma-Focused Therapy",
+ description: "Evidence-based TF-CBT to help children process and heal from traumatic experiences in a safe, supportive environment.",
+ },
+ {
+ icon: Sparkles,
+ title: "Play Therapy",
+ description: "Child-centered play therapy allowing children to express themselves naturally and build emotional regulation skills.",
+ },
+ {
+ icon: Baby,
+ title: "Infant Mental Health",
+ description: "Specialized support for infants and toddlers, focusing on early attachment, developmental milestones, and caregiver relationships.",
+ },
+ {
+ icon: Users2,
+ title: "Dyadic Therapy",
+ description: "Strengthening parent-child relationships through interactive sessions that enhance communication and connection.",
+ },
+ {
+ icon: HeartHandshake,
+ title: "Social-Emotional Support",
+ description: "Building emotional literacy and self-regulation skills to help children navigate relationships and challenges.",
+ },
+ {
+ icon: Shield,
+ title: "Relationship-Based Care",
+ description: "Fostering healing through nurturing therapeutic relationships and caregiver collaboration.",
+ },
+ ];
+
+ return (
+
+
+ {!isDark && (
+
+ )}
+ {isDark && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ Specialized Services
+
+
+ Comprehensive, evidence-based therapeutic support for children and families
+
+
+
+
+ {services.map((service, index) => {
+ const Icon = service.icon;
+ return (
+
+
+
+
+
+ {service.title}
+
+
+ {service.description}
+
+
+ );
+ })}
+
+
+
+
+ Who I Work With
+
+
+
+ I specialize in working with children under the age of 10 who are
+ dealing with trauma, stressors, or social-emotional challenges and need understanding
+ and support.
+
+
+ The goal is to build a healthy foundation through nurturing relationships and
+ emotional literacy, helping children diminish distress and enhance self-regulation.
+
+
+
+
+
+ );
+}
+
+
diff --git a/components/ThemeProvider.tsx b/components/ThemeProvider.tsx
new file mode 100644
index 0000000..8545a13
--- /dev/null
+++ b/components/ThemeProvider.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { createContext, useContext, useEffect, useMemo, useState } from "react";
+
+type Theme = "light" | "dark";
+
+type ThemeContextValue = {
+ theme: Theme;
+ setTheme: (t: Theme) => void;
+ toggleTheme: () => void;
+};
+
+const ThemeContext = createContext(undefined);
+
+export function useAppTheme() {
+ const ctx = useContext(ThemeContext);
+ if (!ctx) throw new Error("useAppTheme must be used within ThemeProvider");
+ return ctx;
+}
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setThemeState] = useState("light");
+ const setTheme = (t: Theme) => setThemeState(t);
+
+ // Initialize from localStorage or system preference on mount
+ useEffect(() => {
+ const stored = typeof window !== "undefined" ? localStorage.getItem("theme") : null;
+ if (stored === "light" || stored === "dark") {
+ setThemeState(stored);
+ return;
+ }
+ const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
+ setThemeState(prefersDark ? "dark" : "light");
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ // Apply class to and persist
+ useEffect(() => {
+ const root = document.documentElement;
+ if (theme === "dark") {
+ root.classList.add("dark");
+ } else {
+ root.classList.remove("dark");
+ }
+ localStorage.setItem("theme", theme);
+ }, [theme]);
+
+ const value = useMemo(() => ({
+ theme,
+ setTheme,
+ toggleTheme: () => setThemeState((t) => (t === "dark" ? "light" : "dark")),
+ }), [theme]);
+
+ return {children} ;
+}
+
+
diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx
new file mode 100644
index 0000000..883c400
--- /dev/null
+++ b/components/ThemeToggle.tsx
@@ -0,0 +1,36 @@
+import { Moon, Sun } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { useEffect, useState } from "react";
+
+export function ThemeToggle() {
+ const [theme, setTheme] = useState<"light" | "dark">("light");
+
+ useEffect(() => {
+ const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null;
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ const initialTheme = savedTheme || (prefersDark ? "dark" : "light");
+
+ setTheme(initialTheme);
+ document.documentElement.classList.toggle("dark", initialTheme === "dark");
+ }, []);
+
+ const toggleTheme = () => {
+ const newTheme = theme === "light" ? "dark" : "light";
+ setTheme(newTheme);
+ localStorage.setItem("theme", newTheme);
+ document.documentElement.classList.toggle("dark", newTheme === "dark");
+ };
+
+ return (
+
+
+
+
+ );
+}
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
new file mode 100644
index 0000000..681ad98
--- /dev/null
+++ b/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx
new file mode 100644
index 0000000..9b20afe
--- /dev/null
+++ b/components/ui/sonner.tsx
@@ -0,0 +1,40 @@
+"use client"
+
+import {
+ CircleCheckIcon,
+ InfoIcon,
+ Loader2Icon,
+ OctagonXIcon,
+ TriangleAlertIcon,
+} from "lucide-react"
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, type ToasterProps } from "sonner"
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+ ,
+ info: ,
+ warning: ,
+ error: ,
+ loading: ,
+ }}
+ style={
+ {
+ "--normal-bg": "var(--popover)",
+ "--normal-text": "var(--popover-foreground)",
+ "--normal-border": "var(--border)",
+ "--border-radius": "var(--radius)",
+ } as React.CSSProperties
+ }
+ {...props}
+ />
+ )
+}
+
+export { Toaster }
diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx
new file mode 100644
index 0000000..7f21b5e
--- /dev/null
+++ b/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx
new file mode 100644
index 0000000..1d7fb87
--- /dev/null
+++ b/components/ui/toaster.tsx
@@ -0,0 +1,8 @@
+"use client";
+
+// Simple toaster component - can be enhanced later with toast notifications
+export function Toaster() {
+ return null;
+}
+
+
diff --git a/package.json b/package.json
index 1dde493..121eae8 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,13 @@
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "framer-motion": "^12.23.24",
"lucide-react": "^0.552.0",
"next": "16.0.1",
+ "next-themes": "^0.4.6",
"react": "19.2.0",
"react-dom": "19.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
@@ -31,6 +34,5 @@
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
- },
- "packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417"
+ }
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 119e817..2a08c41 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,18 +17,27 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ framer-motion:
+ specifier: ^12.23.24
+ version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
lucide-react:
specifier: ^0.552.0
version: 0.552.0(react@19.2.0)
next:
specifier: 16.0.1
version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react:
specifier: 19.2.0
version: 19.2.0
react-dom:
specifier: 19.2.0
version: 19.2.0(react@19.2.0)
+ sonner:
+ specifier: ^2.0.7
+ version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
tailwind-merge:
specifier: ^3.3.1
version: 3.3.1
@@ -1124,6 +1133,20 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
+ framer-motion@12.23.24:
+ resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@@ -1519,6 +1542,12 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ motion-dom@12.23.23:
+ resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
+
+ motion-utils@12.23.6:
+ resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1535,6 +1564,12 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
next@16.0.1:
resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==}
engines: {node: '>=20.9.0'}
@@ -1766,6 +1801,12 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
+ sonner@2.0.7:
+ resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
+ peerDependencies:
+ react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -3155,6 +3196,15 @@ snapshots:
dependencies:
is-callable: 1.2.7
+ framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ motion-dom: 12.23.23
+ motion-utils: 12.23.6
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
function-bind@1.1.2: {}
function.prototype.name@1.1.8:
@@ -3527,6 +3577,12 @@ snapshots:
minimist@1.2.8: {}
+ motion-dom@12.23.23:
+ dependencies:
+ motion-utils: 12.23.6
+
+ motion-utils@12.23.6: {}
+
ms@2.1.3: {}
nanoid@3.3.11: {}
@@ -3535,6 +3591,11 @@ snapshots:
natural-compare@1.4.0: {}
+ next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 16.0.1
@@ -3829,6 +3890,11 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
source-map-js@1.2.1: {}
stable-hash@0.0.5: {}
diff --git a/public/3786819.jpg b/public/3786819.jpg
new file mode 100644
index 0000000..cdb4bcc
Binary files /dev/null and b/public/3786819.jpg differ