Compare commits

..

No commits in common. "af29ec2a48b40a8a28273c23558568c6250c4bec" and "4d7201d03ea861e1c292a5e5d028cbaa410ed7b3" have entirely different histories.

27 changed files with 191 additions and 1523 deletions

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import SideNav from "@/app/(admin)/_components/side-nav"; import SideNav from "@/components/side-nav";
export default function Booking() { export default function Booking() {
return ( return (

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import SideNav from "@/app/(admin)/_components/side-nav"; import SideNav from "@/components/side-nav";
export default function Dashboard() { export default function Dashboard() {
return ( return (

View File

@ -1,8 +1,8 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import SideNav from "@/app/(admin)/_components/side-nav"; import SideNav from "@/components/side-nav";
import { Notifications, Notification } from "@/app/(admin)/_components/notifications"; import { Notifications, Notification } from "@/components/notifications";
export default function NotificationsPage() { export default function NotificationsPage() {
const [notifications, setNotifications] = useState<Notification[]>([ const [notifications, setNotifications] = useState<Notification[]>([

View File

@ -1,7 +0,0 @@
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<div>
{children}
</div>
)
}

View File

@ -1,94 +1,125 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @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 { :root {
--foreground-rgb: 0, 0, 0; --radius: 0.625rem;
--background-start-rgb: 214, 219, 220; --background: oklch(1 0 0);
--background-end-rgb: 255, 255, 255; --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);
} }
@media (prefers-color-scheme: dark) { .dark {
:root { --background: oklch(0.145 0 0);
--foreground-rgb: 255, 255, 255; --foreground: oklch(0.985 0 0);
--background-start-rgb: 0, 0, 0; --card: oklch(0.205 0 0);
--background-end-rgb: 0, 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);
@layer base { --primary-foreground: oklch(0.205 0 0);
:root { --secondary: oklch(0.269 0 0);
--background: 0 0% 100%; --secondary-foreground: oklch(0.985 0 0);
--foreground: 0 0% 3.9%; --muted: oklch(0.269 0 0);
--card: 0 0% 100%; --muted-foreground: oklch(0.708 0 0);
--card-foreground: 0 0% 3.9%; --accent: oklch(0.269 0 0);
--popover: 0 0% 100%; --accent-foreground: oklch(0.985 0 0);
--popover-foreground: 0 0% 3.9%; --destructive: oklch(0.704 0.191 22.216);
--primary: 0 0% 9%; --border: oklch(1 0 0 / 10%);
--primary-foreground: 0 0% 98%; --input: oklch(1 0 0 / 15%);
--secondary: 0 0% 96.1%; --ring: oklch(0.556 0 0);
--secondary-foreground: 0 0% 9%; --chart-1: oklch(0.488 0.243 264.376);
--muted: 0 0% 96.1%; --chart-2: oklch(0.696 0.17 162.48);
--muted-foreground: 0 0% 45.1%; --chart-3: oklch(0.769 0.188 70.08);
--accent: 0 0% 96.1%; --chart-4: oklch(0.627 0.265 303.9);
--accent-foreground: 0 0% 9%; --chart-5: oklch(0.645 0.246 16.439);
--destructive: 0 84.2% 60.2%; --sidebar: oklch(0.205 0 0);
--destructive-foreground: 0 0% 98%; --sidebar-foreground: oklch(0.985 0 0);
--border: 0 0% 89.8%; --sidebar-primary: oklch(0.488 0.243 264.376);
--input: 0 0% 89.8%; --sidebar-primary-foreground: oklch(0.985 0 0);
--ring: 0 0% 3.9%; --sidebar-accent: oklch(0.269 0 0);
--chart-1: 12 76% 61%; --sidebar-accent-foreground: oklch(0.985 0 0);
--chart-2: 173 58% 39%; --sidebar-border: oklch(1 0 0 / 10%);
--chart-3: 197 37% 24%; --sidebar-ring: oklch(0.556 0 0);
--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 { @layer base {
* { * {
border-color: hsl(var(--border)); @apply border-border outline-ring/50;
} }
body { body {
background-color: #ffffff; @apply bg-background text-foreground;
color: hsl(var(--foreground));
} }
html.dark body { body.menu-open {
background-color: #1a1e26; overflow: hidden;
}
html {
scroll-behavior: smooth;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
} }
} }

View File

@ -1,28 +1,29 @@
import './globals.css'; import type { Metadata } from "next";
import type { Metadata } from 'next'; import { Poppins } from "next/font/google";
import { Inter } from 'next/font/google'; import "./globals.css";
import { Providers } from './providers';
import { Toaster } from '@/components/ui/toaster';
const inter = Inter({ subsets: ['latin'] }); const poppins = Poppins({
variable: "--font-poppins",
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Attune Heart Therapy | Nathalie Mac Guffie, LCSW | Miami, FL', title: "Attune Heart Therapy",
description: 'Compassionate, evidence-based therapy in Miami, FL. Licensed Clinical Social Worker offering anxiety, depression, trauma therapy and more.', description: "Attune Heart Therapy",
}; };
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}) { }>) {
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en">
<body className={inter.className}> <body
<Providers> className={`${poppins.variable} font-sans antialiased`}
>
{children} {children}
<Toaster />
</Providers>
</body> </body>
</html> </html>
); );

View File

@ -1,25 +1,65 @@
import Section from "../components/Section"; import Image from "next/image";
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";
import { Navbar } from "../components/Navbar";
export default function Home() { export default function Home() {
return ( return (
<main> <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<Navbar /> <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">
<HeroSection /> <Image
className="dark:invert"
<About /> src="/next.svg"
alt="Next.js logo"
<Services /> width={100}
height={20}
<ContactSection /> priority
/>
<Footer /> <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>
</main> </main>
</div>
); );
} }

View File

@ -1,13 +0,0 @@
"use client";
import { ThemeProvider } from "../components/ThemeProvider";
import { type ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
{children}
</ThemeProvider>
);
}

View File

@ -1,201 +0,0 @@
'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 (
<section
id="about"
ref={ref}
className="relative py-20 px-4 overflow-hidden"
>
<div
className="absolute inset-0 z-0"
style={{
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
}}
/>
{!isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-rose-50/50 via-pink-50/50 to-orange-50/50" />
)}
{isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-gray-900/80 via-gray-800/80 to-gray-900/80" />
)}
<div className="absolute inset-0 overflow-hidden z-[2]">
<motion.div
className="absolute top-10 left-10 w-64 h-64 bg-cyan-100 dark:bg-cyan-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-40 dark:opacity-60"
animate={{
x: [0, 80, 0],
y: [0, 40, 0],
scale: [1, 1.1, 1],
}}
transition={{
duration: 18,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<motion.div
className="absolute bottom-20 right-10 w-80 h-80 bg-emerald-100 dark:bg-emerald-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-40 dark:opacity-60"
animate={{
x: [0, -80, 0],
y: [0, 60, 0],
scale: [1, 1.15, 1],
}}
transition={{
duration: 22,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
<div className="container max-w-6xl mx-auto relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8 }}
className="text-center mb-16"
>
<motion.h2
className="text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2 }}
>
Meet Nathalie Mac-Guffie
</motion.h2>
<motion.p
className="text-xl text-muted-foreground max-w-3xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.4 }}
>
A dedicated mental health professional specializing in helping children
under 10 and their families navigate trauma, emotional challenges, and
developmental needs.
</motion.p>
</motion.div>
<div className="grid md:grid-cols-2 gap-12 items-center mb-16">
<motion.div
initial={{ opacity: 0, x: -50 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2 }}
>
<div className="bg-gradient-to-br from-rose-100/30 via-pink-100/30 to-orange-100/30 dark:from-rose-900/20 dark:via-pink-900/20 dark:to-orange-900/20 rounded-3xl p-8 border border-border/50 backdrop-blur-sm">
<motion.h3
className="text-2xl font-semibold mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.8, delay: 0.3 }}
>
My Approach
</motion.h3>
<p className="text-muted-foreground mb-4 leading-relaxed">
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.
</p>
<p className="text-muted-foreground leading-relaxed">
Together, we'll set realistic, measurable, and achievable goals with
clear objectives tailored to your family's unique needs.
</p>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 50 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, delay: 0.4 }}
className="space-y-6"
>
{credentials.map((cred, index) => {
const Icon = cred.icon;
return (
<motion.div
key={cred.title}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.6 + index * 0.1 }}
className="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 hover:border-rose-500/50 hover:scale-105 transition-all cursor-pointer"
>
<div className="flex items-start gap-4">
<motion.div
className="bg-gradient-to-br from-rose-500/20 via-pink-500/20 to-orange-500/20 dark:from-rose-500/30 dark:via-pink-500/30 dark:to-orange-500/30 p-3 rounded-xl"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.2 }}
>
<Icon className="h-6 w-6 text-rose-600 dark:text-rose-400" />
</motion.div>
<div>
<motion.h4
className="font-semibold mb-2 text-foreground"
initial={{ opacity: 0, x: -10 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.5, delay: 0.7 + index * 0.1 }}
>
{cred.title}
</motion.h4>
<motion.p
className="text-sm text-muted-foreground"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.5, delay: 0.8 + index * 0.1 }}
>
{cred.description}
</motion.p>
</div>
</div>
</motion.div>
);
})}
</motion.div>
</div>
</div>
</section>
);
}

View File

@ -1,181 +0,0 @@
"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 (
<section id="contact" className="relative overflow-hidden py-20" ref={ref}>
{/* Theme sync like Hero/About/Navbar */}
<div className="absolute inset-0 z-0" style={{ backgroundColor: isDark ? "#1a1e26" : "#ffffff" }} />
{!isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-rose-50/40 via-pink-50/40 to-orange-50/40" />
)}
{isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-gray-900/70 via-gray-800/70 to-gray-900/70" />
)}
{/* Subtle animated blobs */}
<div className="absolute inset-0 z-[2] overflow-hidden">
<motion.div
className={`absolute -top-10 left-10 h-64 w-64 rounded-full blur-3xl ${isDark ? "bg-pink-900/30 opacity-60" : "bg-pink-100 opacity-50"}`}
animate={{ x: [0, 60, 0], y: [0, 40, 0], scale: [1, 1.05, 1] }}
transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }}
/>
<motion.div
className={`absolute -bottom-10 right-10 h-72 w-72 rounded-full blur-3xl ${isDark ? "bg-orange-900/30 opacity-60" : "bg-orange-100 opacity-50"}`}
animate={{ x: [0, -60, 0], y: [0, -40, 0], scale: [1, 1.08, 1] }}
transition={{ duration: 22, repeat: Infinity, ease: "easeInOut" }}
/>
</div>
<div className="container mx-auto px-4 relative z-10 text-foreground">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
className="mb-16 text-center"
>
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
Get in Touch
</h2>
<div className="mx-auto mb-6 h-1 w-24 rounded-full bg-gradient-to-r from-rose-500 to-pink-600" />
<p className="mx-auto max-w-2xl text-lg text-muted-foreground">
Ready to start your journey? Reach out to schedule a consultation.
</p>
</motion.div>
<div className="grid gap-12 lg:grid-cols-2">
{/* Left: Illustration replacing cards */}
<motion.div
initial={{ opacity: 0, x: -50 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2 }}
className="relative"
>
<Card className="bg-card/60 backdrop-blur-sm border border-border/50 overflow-hidden">
<CardContent className="p-0">
{/* Use a high-quality, license-friendly illustration */}
<div className="relative">
<img
src="/3786819.jpg"
alt="Contact illustration"
className="mx-auto w-full max-w-xl p-6 select-none rounded-xl object-cover"
loading="lazy"
/>
<motion.div
className="pointer-events-none absolute inset-0"
animate={{ opacity: [0.3, 0.6, 0.3] }}
transition={{ duration: 3, repeat: Infinity }}
style={{ background: "radial-gradient(600px circle at 20% 10%, rgba(244,114,182,0.15), transparent 40%), radial-gradient(600px circle at 80% 80%, rgba(251,146,60,0.12), transparent 40%)" }}
/>
</div>
<div className="px-6 pb-6">
<h3 className="text-2xl font-bold mb-2 text-foreground">Let's Begin Your Healing Journey</h3>
<p className="text-muted-foreground leading-relaxed">
Taking the first step toward therapy can feel daunting, but you're not alone. I'm here to support
you through every stage of your journey toward wellness and growth.
</p>
</div>
</CardContent>
</Card>
</motion.div>
{/* Right: Contact form */}
<motion.div
initial={{ opacity: 0, x: 50 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, delay: 0.4 }}
>
<Card className="border border-border/50 bg-card/70 backdrop-blur-sm">
<CardContent className="p-6 md:p-8">
<h3 className="mb-6 text-2xl font-bold text-foreground">Send a Message</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Input
placeholder="Your Name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500"
/>
</div>
<div>
<Input
type="email"
placeholder="Email Address"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500"
/>
</div>
<div>
<Input
type="tel"
placeholder="Phone Number"
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500"
/>
</div>
<div>
<Textarea
placeholder="Tell me a bit about what brings you to therapy..."
rows={6}
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
required
className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500"
/>
</div>
<Button
type="submit"
className="w-full cursor-pointer bg-gradient-to-r from-rose-500 to-pink-600 text-white transition-all hover:from-rose-600 hover:to-pink-700 hover:scale-[1.02]"
size="lg"
>
<Send className="mr-2 h-5 w-5" />
Send Message
</Button>
</form>
</CardContent>
</Card>
</motion.div>
</div>
</div>
</section>
);
}

View File

@ -1,181 +0,0 @@
'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 (
<footer
className="relative py-12 overflow-hidden border-t border-border/50"
style={{
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
}}
>
{!isDark && (
<div className="absolute inset-0 bg-gradient-to-br from-rose-50/30 via-pink-50/30 to-orange-50/30" />
)}
{isDark && (
<div className="absolute inset-0 bg-gradient-to-br from-gray-900/40 via-gray-800/40 to-gray-900/40" />
)}
<div className="container mx-auto px-4 relative z-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-8">
{/* Brand Section */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="lg:col-span-1"
>
<div className="flex items-center gap-2 mb-4">
<div className="bg-gradient-to-br from-rose-500 to-pink-600 p-2 rounded-xl">
<Heart className="h-5 w-5 text-white fill-white" />
</div>
<span className="font-bold text-lg bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
Attune Heart Therapy
</span>
</div>
<p className="text-sm text-muted-foreground mb-2">
Nathalie Mac-Guffie, LMHC, RPT-S, IMH-E, TF-CBT
</p>
<p className="text-sm text-muted-foreground">
Licensed Mental Health Counselor Florida
</p>
</motion.div>
{/* Quick Links */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1 }}
className="lg:col-span-1"
>
<h3 className="font-semibold mb-4 text-foreground">Quick Links</h3>
<ul className="space-y-2">
{quickLinks.map((link) => (
<li key={link.name}>
<button
onClick={() => scrollToSection(link.href.replace('#', ''))}
className="text-sm text-muted-foreground hover:text-rose-600 dark:hover:text-rose-400 transition-colors cursor-pointer hover:translate-x-1 inline-block transition-transform"
>
{link.name}
</button>
</li>
))}
</ul>
</motion.div>
{/* Contact Info */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
className="lg:col-span-1"
>
<h3 className="font-semibold mb-4 text-foreground">Contact</h3>
<ul className="space-y-3">
<li className="flex items-start gap-3">
<Phone className="h-4 w-4 mt-1 text-rose-600 dark:text-rose-400 flex-shrink-0" />
<a
href="tel:+19548073027"
className="text-sm text-muted-foreground hover:text-rose-600 dark:hover:text-rose-400 transition-colors"
>
(954) 807-3027
</a>
</li>
<li className="flex items-start gap-3">
<Mail className="h-4 w-4 mt-1 text-rose-600 dark:text-rose-400 flex-shrink-0" />
<a
href="mailto:info@attunehearttherapy.com"
className="text-sm text-muted-foreground hover:text-rose-600 dark:hover:text-rose-400 transition-colors"
>
info@attunehearttherapy.com
</a>
</li>
<li className="flex items-start gap-3">
<MapPin className="h-4 w-4 mt-1 text-rose-600 dark:text-rose-400 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
Miami, Florida
</span>
</li>
</ul>
</motion.div>
{/* Services Summary */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
className="lg:col-span-1"
>
<h3 className="font-semibold mb-4 text-foreground">Services</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>Trauma-Focused Therapy</li>
<li>Play Therapy</li>
<li>Infant Mental Health</li>
<li>Dyadic Therapy</li>
<li>Social-Emotional Support</li>
</ul>
</motion.div>
</div>
{/* Bottom Section */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
className="mt-8 pt-8 border-t border-border/50"
>
<div className="grid grid-cols-1 items-center gap-3 text-center md:grid-cols-3 md:text-left">
<p className="text-sm text-muted-foreground md:justify-self-start">
© {new Date().getFullYear()} Attune Heart Therapy. All rights reserved.
</p>
<p className="text-xs text-muted-foreground md:justify-self-center">
This site is for informational purposes only and does not constitute medical advice.
</p>
<p className="text-sm text-muted-foreground md:justify-self-end md:text-right">
Providing compassionate, trauma-informed care for children and families.
</p>
</div>
</motion.div>
</div>
</footer>
);
}

View File

@ -1,163 +0,0 @@
'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 (
<section
id="home"
className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20"
>
<div
className="absolute inset-0 z-0"
style={{
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
}}
/>
{!isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-rose-50/50 via-pink-50/50 to-orange-50/50" />
)}
{isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-gray-900/80 via-gray-800/80 to-gray-900/80" />
)}
<div className="absolute inset-0 overflow-hidden z-[2]">
<motion.div
className="absolute top-20 left-10 w-72 h-72 bg-rose-100 dark:bg-rose-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-50 dark:opacity-70"
animate={{
x: [0, 100, 0],
y: [0, 50, 0],
scale: [1, 1.1, 1],
}}
transition={{
duration: 20,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<motion.div
className="absolute top-40 right-10 w-96 h-96 bg-pink-100 dark:bg-pink-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-50 dark:opacity-70"
animate={{
x: [0, -100, 0],
y: [0, 100, 0],
scale: [1, 1.2, 1],
}}
transition={{
duration: 25,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<motion.div
className="absolute bottom-20 left-1/3 w-80 h-80 bg-orange-100 dark:bg-orange-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-50 dark:opacity-70"
animate={{
x: [0, 50, 0],
y: [0, -50, 0],
scale: [1, 1.15, 1],
}}
transition={{
duration: 22,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
<div className="container mx-auto px-4 relative z-10">
<div className="max-w-4xl mx-auto text-center">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<motion.h1
className="text-5xl md:text-7xl font-bold mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
>
Welcome to Attune Heart Therapy
</motion.h1>
<motion.p
className="text-xl md:text-2xl text-muted-foreground mb-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
>
Nathalie Mac Guffie, LCSW
</motion.p>
<motion.p
className="text-lg md:text-xl text-muted-foreground mb-8 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
>
Compassionate, evidence-based therapy to help you heal, grow, and
thrive. Creating a safe space for your journey toward emotional
wellness.
</motion.p>
<motion.div
className="flex flex-col sm:flex-row gap-4 justify-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
>
<Button
size="lg"
className="bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white group cursor-pointer hover:scale-105 hover:shadow-lg transition-all"
>
<Calendar className="mr-2 h-5 w-5" />
Book Appointment
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
</Button>
<Button
size="lg"
variant="outline"
className="cursor-pointer hover:scale-105 hover:bg-gray-100 dark:hover:bg-gray-800 hover:border-cyan-300 dark:hover:border-gray-300 hover:text-gray-900 dark:hover:text-gray-100 transition-all"
>
Learn More
</Button>
</motion.div>
</motion.div>
</div>
</div>
<motion.div
className="absolute bottom-10 left-1/2 -translate-x-1/2"
animate={{ y: [0, 10, 0] }}
transition={{ duration: 2, repeat: Infinity }}
>
<div className="w-6 h-10 border-2 border-cyan-500/50 dark:border-cyan-400/50 rounded-full flex items-start justify-center p-2">
<motion.div
className="w-1.5 h-1.5 bg-cyan-500 dark:bg-cyan-400 rounded-full"
animate={{ y: [0, 12, 0] }}
transition={{ duration: 2, repeat: Infinity }}
/>
</div>
</motion.div>
</section>
);
}

View File

@ -1,94 +0,0 @@
'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 (
<motion.nav
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 0.5 }}
className="fixed top-0 left-0 right-0 z-50 border-b border-border/50"
style={{
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
}}
>
<div className="container mx-auto px-4">
<div className="flex items-center justify-between h-16">
<motion.a
href="#home"
className="flex items-center gap-2"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<div className="bg-gradient-to-br from-rose-500 to-pink-600 p-2 rounded-xl">
<Heart className="h-5 w-5 text-white fill-white" />
</div>
<span className="font-bold text-lg bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
Attune Heart Therapy
</span>
</motion.a>
<div className="hidden md:flex items-center gap-6">
<button
onClick={() => 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
</button>
<button
onClick={() => 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
</button>
<button
onClick={() => 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
</button>
</div>
<div className="flex items-center gap-2">
<Button size="sm" variant="outline" className="hidden sm:inline-flex hover:opacity-90 hover:scale-105 transition-all dark:hover:bg-cyan-900/30" asChild>
<a href="/login">Sign In</a>
</Button>
<ThemeToggle />
<Button size="sm" className="hidden sm:inline-flex hover:opacity-90 hover:scale-105 transition-all dark:hover:bg-emerald-600" asChild>
<a href="tel:+19548073027">Book Now</a>
</Button>
</div>
</div>
</div>
</motion.nav>
);
}

View File

@ -1,30 +0,0 @@
"use client";
import { type ReactNode } from "react";
type Props = {
id?: string;
title: string;
children: ReactNode;
};
export default function Section({ id, title, children }: Props) {
return (
<section id={id} className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
<h2 className="text-2xl font-semibold tracking-tight text-zinc-900 opacity-0 animate-[fadein_500ms_ease-out_forwards] dark:text-zinc-100 sm:text-3xl">
{title}
</h2>
<div className="mt-5 text-zinc-700 opacity-0 animate-[fadein_500ms_ease-out_forwards] [animation-delay:60ms] dark:text-zinc-300">
{children}
</div>
<style jsx>{`
@keyframes fadein {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</section>
);
}

View File

@ -1,214 +0,0 @@
'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 (
<section
id="services"
ref={ref}
className="relative py-20 px-4 overflow-hidden"
>
<div
className="absolute inset-0 z-0"
style={{
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
}}
/>
{!isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-rose-50/50 via-pink-50/50 to-orange-50/50" />
)}
{isDark && (
<div className="absolute inset-0 z-[1] bg-gradient-to-br from-gray-900/80 via-gray-800/80 to-gray-900/80" />
)}
<div className="absolute inset-0 overflow-hidden z-[2]">
<motion.div
className="absolute top-20 right-20 w-72 h-72 bg-pink-100 dark:bg-pink-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-40 dark:opacity-60"
animate={{
x: [0, -90, 0],
y: [0, 50, 0],
scale: [1, 1.2, 1],
}}
transition={{
duration: 20,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<motion.div
className="absolute bottom-10 left-20 w-96 h-96 bg-orange-100 dark:bg-orange-900/20 rounded-full mix-blend-multiply dark:mix-blend-lighten filter blur-xl opacity-40 dark:opacity-60"
animate={{
x: [0, 70, 0],
y: [0, -60, 0],
scale: [1, 1.15, 1],
}}
transition={{
duration: 24,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
<div className="container max-w-6xl mx-auto relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8 }}
className="text-center mb-16"
>
<motion.h2
className="text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2 }}
>
Specialized Services
</motion.h2>
<motion.p
className="text-xl text-muted-foreground max-w-3xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.4 }}
>
Comprehensive, evidence-based therapeutic support for children and families
</motion.p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{services.map((service, index) => {
const Icon = service.icon;
return (
<motion.div
key={service.title}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="group bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 hover:border-rose-500/50 hover:shadow-lg hover:shadow-rose-500/10 hover:scale-105 transition-all duration-300 cursor-pointer"
>
<motion.div
className="bg-gradient-to-br from-rose-500/20 via-pink-500/20 to-orange-500/20 dark:from-rose-500/30 dark:via-pink-500/30 dark:to-orange-500/30 w-14 h-14 rounded-xl flex items-center justify-center mb-4"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.2 }}
>
<Icon className="h-7 w-7 text-rose-600 dark:text-rose-400" />
</motion.div>
<motion.h3
className="text-xl font-semibold mb-3 text-foreground"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.5, delay: 0.3 + index * 0.1 }}
>
{service.title}
</motion.h3>
<motion.p
className="text-muted-foreground leading-relaxed"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
>
{service.description}
</motion.p>
</motion.div>
);
})}
</div>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.6 }}
className="mt-16 bg-gradient-to-br from-rose-100/30 via-pink-100/30 to-orange-100/30 dark:from-rose-900/20 dark:via-pink-900/20 dark:to-orange-900/20 rounded-3xl p-8 border border-border/50 backdrop-blur-sm"
>
<motion.h3
className="text-2xl font-semibold mb-4 text-center bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.8, delay: 0.7 }}
>
Who I Work With
</motion.h3>
<div className="max-w-3xl mx-auto text-center">
<motion.p
className="text-muted-foreground leading-relaxed mb-4"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.8, delay: 0.8 }}
>
I specialize in working with <strong className="text-foreground">children under the age of 10</strong> who are
dealing with trauma, stressors, or social-emotional challenges and need understanding
and support.
</motion.p>
<motion.p
className="text-muted-foreground leading-relaxed"
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.8, delay: 0.9 }}
>
The goal is to build a healthy foundation through nurturing relationships and
emotional literacy, helping children diminish distress and enhance self-regulation.
</motion.p>
</div>
</motion.div>
</div>
</section>
);
}

View File

@ -1,57 +0,0 @@
"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<ThemeContextValue | undefined>(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<Theme>("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 <html> 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<ThemeContextValue>(() => ({
theme,
setTheme,
toggleTheme: () => setThemeState((t) => (t === "dark" ? "light" : "dark")),
}), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}

View File

@ -1,36 +0,0 @@
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 (
<Button
variant="ghost"
size="icon"
onClick={toggleTheme}
className="relative rounded-full cursor-pointer hover:bg-gray-100 dark:hover:bg-cyan-900/30 transition-colors"
aria-label="Toggle theme"
>
<Sun className={`h-5 w-5 transition-all absolute ${theme === "light" ? "rotate-0 scale-100" : "rotate-90 scale-0"}`} />
<Moon className={`h-5 w-5 transition-all absolute ${theme === "dark" ? "rotate-0 scale-100" : "rotate-90 scale-0"}`} />
</Button>
);
}

View File

@ -1,92 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@ -1,40 +0,0 @@
"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 (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }

View File

@ -1,18 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}
/>
)
}
export { Textarea }

View File

@ -1,8 +0,0 @@
"use client";
// Simple toaster component - can be enhanced later with toast notifications
export function Toaster() {
return null;
}

View File

@ -15,13 +15,10 @@
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"lucide-react": "^0.552.0", "lucide-react": "^0.552.0",
"next": "16.0.1", "next": "16.0.1",
"next-themes": "^0.4.6",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -17,27 +17,18 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 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: lucide-react:
specifier: ^0.552.0 specifier: ^0.552.0
version: 0.552.0(react@19.2.0) version: 0.552.0(react@19.2.0)
next: next:
specifier: 16.0.1 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) 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: react:
specifier: 19.2.0 specifier: 19.2.0
version: 19.2.0 version: 19.2.0
react-dom: react-dom:
specifier: 19.2.0 specifier: 19.2.0
version: 19.2.0(react@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: tailwind-merge:
specifier: ^3.3.1 specifier: ^3.3.1
version: 3.3.1 version: 3.3.1
@ -1133,20 +1124,6 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'} 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: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@ -1542,12 +1519,6 @@ packages:
minimist@1.2.8: minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 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: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -1564,12 +1535,6 @@ packages:
natural-compare@1.4.0: natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 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: next@16.0.1:
resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==} resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==}
engines: {node: '>=20.9.0'} engines: {node: '>=20.9.0'}
@ -1801,12 +1766,6 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'} 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: source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3196,15 +3155,6 @@ snapshots:
dependencies: dependencies:
is-callable: 1.2.7 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-bind@1.1.2: {}
function.prototype.name@1.1.8: function.prototype.name@1.1.8:
@ -3577,12 +3527,6 @@ snapshots:
minimist@1.2.8: {} minimist@1.2.8: {}
motion-dom@12.23.23:
dependencies:
motion-utils: 12.23.6
motion-utils@12.23.6: {}
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}
@ -3591,11 +3535,6 @@ snapshots:
natural-compare@1.4.0: {} 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): next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies: dependencies:
'@next/env': 16.0.1 '@next/env': 16.0.1
@ -3890,11 +3829,6 @@ snapshots:
side-channel-map: 1.0.1 side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2 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: {} source-map-js@1.2.1: {}
stable-hash@0.0.5: {} stable-hash@0.0.5: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB