Initial commit

This commit is contained in:
TheMaddax 2024-12-01 20:08:38 -06:00
commit 0fb8045def
47 changed files with 6679 additions and 0 deletions

9
.env Normal file
View file

@ -0,0 +1,9 @@
GOOGLE_EMAIL=system@sigd.net
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
RECIPIENT_EMAIL=chris@sigd.net
GOOGLE_APP_PASSWORD=fvjivgqewtmtpdez
UPSTASH_REDIS_REST_URL=https://gusc1-resolved-jaybird-30779.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXg7ASQgNDMzMDE0MTYtNWE0Ni00OTc3LThhYjktM2IxMzMxNGMyMzMxNWRiNGMyOGU2MjE0NGUyNTkwMTQyNmUxZTU2NzE4NDI=

9
.env.local Normal file
View file

@ -0,0 +1,9 @@
GOOGLE_EMAIL=system@sigd.net
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
RECIPIENT_EMAIL=chris@sigd.net
GOOGLE_APP_PASSWORD=fvjivgqewtmtpdez
UPSTASH_REDIS_REST_URL=https://gusc1-resolved-jaybird-30779.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXg7ASQgNDMzMDE0MTYtNWE0Ni00OTc3LThhYjktM2IxMzMxNGMyMzMxNWRiNGMyOGU2MjE0NGUyNTkwMTQyNmUxZTU2NzE4NDI=

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

37
Dockerfile Normal file
View file

@ -0,0 +1,37 @@
FROM node:20-alpine AS base
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml* ./
FROM base AS deps
RUN pnpm install --frozen-lockfile
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Explicitly run PostCSS/Tailwind build before Next.js build
RUN pnpm dlx tailwindcss -i ./src/styles/globals.css -o ./src/styles/output.css
RUN pnpm build
FROM base AS runner
ENV NODE_ENV=production
ENV PORT=803
ENV HOSTNAME "0.0.0.0"
# Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/src/styles/output.css ./src/styles/output.css
COPY .env.local .env.local
EXPOSE 803
CMD ["node", "server.js"]

19
Dockerfile.dev Normal file
View file

@ -0,0 +1,19 @@
FROM node:20-alpine
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=development
ENV DEBUG=next:build
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["pnpm", "dev"]

1559
Error Normal file

File diff suppressed because it is too large Load diff

0
ROADMAP Normal file
View file

23
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,23 @@
services:
web-dev:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- web-dev-node-modules:/app/node_modules
- web-dev-next:/app/.next
environment:
- NODE_ENV=development
- WATCHPACK_POLLING=true
- CHOKIDAR_USEPOLLING=true
- NEXT_TELEMETRY_DISABLED=1
- HOSTNAME=0.0.0.0
env_file:
- .env.local
volumes:
web-dev-node-modules:
web-dev-next:

24
docker-compose.yml Normal file
View file

@ -0,0 +1,24 @@
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
pull_policy: build
ports:
- "803:803"
environment:
- NODE_ENV=production
- PORT=803
- GOOGLE_EMAIL=${GOOGLE_EMAIL}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_SECURE=${SMTP_SECURE}
- RECIPIENT_EMAIL=${RECIPIENT_EMAIL}
- GOOGLE_APP_PASSWORD=${GOOGLE_APP_PASSWORD}
- UPSTASH_REDIS_REST_URL=${UPSTASH_REDIS_REST_URL}
- UPSTASH_REDIS_REST_TOKEN=${UPSTASH_REDIS_REST_TOKEN}
env_file:
- .env.local
restart: unless-stopped

5
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

14
next.config.js Normal file
View file

@ -0,0 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
swcMinify: true,
images: {
unoptimized: true,
},
experimental: {
serverActions: true,
}
}
module.exports = nextConfig

40
package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "chris-haulmark-website",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@upstash/redis": "^1.34.3",
"framer-motion": "^11.12.0",
"lucide-react": "^0.462.0",
"next": "^14.1.0",
"nodemailer": "^6.9.16",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-simple-maps": "^3.0.0",
"topojson-client": "^3.1.0",
"xss": "^1.0.14",
"validator": "^13.9.0"
},
"devDependencies": {
"@types/node": "^22.10.1",
"@types/nodemailer": "^6.4.17",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-simple-maps": "^3.0.6",
"@types/topojson-client": "^3.1.5",
"@types/topojson-specification": "^1.0.4",
"@types/validator": "^13.7.17",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15",
"typescript": "^5.7.2"
},
"packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
}

1547
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

10
postcss.config.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = {
plugins: {
'tailwindcss': {},
'autoprefixer': {
flexbox: true,
grid: true,
overrideBrowserslist: ['last 2 versions', '> 2%']
},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

View file

@ -0,0 +1,116 @@
import { NextResponse } from 'next/server'
import nodemailer from 'nodemailer'
import { rateLimit } from '@/lib/rate-limit'
interface FormData {
name: string
email: string
service: string
message: string
}
const validateEmail = (email: string): boolean => {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return pattern.test(email)
}
const validateInput = (data: FormData): { isValid: boolean; error?: string } => {
if (!data.name || data.name.length < 2 || data.name.length > 50) {
return { isValid: false, error: 'Invalid name length' }
}
if (!data.email || !validateEmail(data.email)) {
return { isValid: false, error: 'Invalid email format' }
}
if (!data.service) {
return { isValid: false, error: 'Service selection required' }
}
if (!data.message || data.message.length < 10 || data.message.length > 1000) {
return { isValid: false, error: 'Invalid message length' }
}
return { isValid: true }
}
const sanitizeInput = (input: string): string => {
return input
.trim()
.replace(/[<>]/g, '') // Basic XSS prevention
.slice(0, 1000) // Length limitation
}
export async function POST(req: Request) {
try {
// Rate limiting check
const rateLimitResult = await rateLimit()
if (!rateLimitResult.success) {
return NextResponse.json(
{ success: false, message: 'Too many requests. Please try again later.' },
{ status: 429 }
)
}
const data: FormData = await req.json()
// Input validation
const validation = validateInput(data)
if (!validation.isValid) {
return NextResponse.json(
{ success: false, message: validation.error },
{ status: 400 }
)
}
// Sanitize inputs
const sanitizedData = {
name: sanitizeInput(data.name),
email: sanitizeInput(data.email),
service: sanitizeInput(data.service),
message: sanitizeInput(data.message)
}
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.GOOGLE_EMAIL,
pass: process.env.GOOGLE_APP_PASSWORD,
},
})
const mailOptions = {
from: process.env.GOOGLE_EMAIL,
to: process.env.RECIPIENT_EMAIL,
subject: `New Contact Form Submission - ${sanitizedData.service}`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #2563eb;">New Contact Form Submission</h2>
<div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px;">
<p><strong>Name:</strong> ${sanitizedData.name}</p>
<p><strong>Email:</strong> ${sanitizedData.email}</p>
<p><strong>Service:</strong> ${sanitizedData.service}</p>
<p><strong>Message:</strong></p>
<p style="white-space: pre-wrap;">${sanitizedData.message}</p>
</div>
</div>
`,
}
await transporter.sendMail(mailOptions)
return NextResponse.json({
success: true,
message: 'Email sent successfully'
})
} catch (error) {
console.error('Error sending email:', error)
return NextResponse.json(
{ success: false, message: 'Failed to send email' },
{ status: 500 }
)
}
}

102
src/app/biography/page.tsx Normal file
View file

@ -0,0 +1,102 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import RegionMap from '@/components/biography/RegionMap'
import JourneyVisual from '@/components/biography/JourneyVisual'
export default function Biography() {
const currentYear = new Date().getFullYear()
const yearsOfAdvocacy = currentYear - 2010
return (
<motion.div
className="container mx-auto px-4 py-16"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="max-w-4xl mx-auto">
<motion.h1
className="text-4xl md:text-5xl font-bold mb-8 bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent"
initial={{ y: -20 }}
animate={{ y: 0 }}
>
A Journey of Advocacy and Leadership
</motion.h1>
<div className="prose prose-lg max-w-none">
<motion.div
className="space-y-6"
initial={{ y: 20 }}
animate={{ y: 0 }}
>
<p className="text-xl font-medium text-gray-700 leading-relaxed">
My life's journey has been one of profound discovery and purposeful transformation, shaped by experiences that have forged me into an unwavering advocate for the Deaf community.
</p>
<div className="bg-gray-50 p-6 rounded-lg border-l-4 border-blue-600 my-8">
<p className="italic text-gray-700">
"When you daydream, you envision your authentic self. I've learned to use these visions as blueprints for building the person I want to become."
</p>
</div>
<section className="my-12">
<h2 className="text-2xl font-bold mb-4">Early Foundations</h2>
<p>
Growing up in Arkansas's river valley, I became Deaf at age one. My early years were uniquely shaped by being the only Deaf person in my family, navigating the hearing world while developing my identity. It wasn't until my teenage years that I encountered Deaf adults who would profoundly influence my understanding of Deaf culture and community.
</p>
</section>
<section className="my-12">
<h2 className="text-2xl font-bold mb-4">Professional Evolution</h2>
<p>
My professional journey began in 1998 in the technology sector. Starting as a PC technician, I ascended through the ranks to become an IT manager for a government agency by 2009. This decade-long experience in technology equipped me with valuable skills in problem-solving and systematic thinking that I now apply to advocacy.
</p>
<div className="my-8">
<JourneyVisual />
</div>
</section>
<section className="my-12">
<h2 className="text-2xl font-bold mb-4">Global Awakening</h2>
<p>
In 2015, I embarked on a transformative journey that would reshape my understanding of Deaf culture and identity. Over two years, I traversed 47 countries, living with Deaf families and immersing myself in their unique sign languages and cultures. This wasn't merely travel it was a profound exploration of global Deaf perspectives that transformed my approach to advocacy.
</p>
<p className="mt-4">
It was during this journey, sitting on a cliff in the Himalayas, that I experienced a profound realization: for the first time, I felt truly proud of myself, not for others' approval, but for my own growth and authenticity.
</p>
</section>
<section className="my-12">
<h2 className="text-2xl font-bold mb-4">Leadership in Action</h2>
<p>
My dedication to advocacy has culminated in significant leadership roles that span from state to national levels. Since 2021, I've served the Kansas Association of the Deaf (KAD), first as an appointed board member and now as Vice President since 2023. In July 2024, I embraced a new challenge as Region II Board Representative for the National Association of the Deaf (NAD), where I'm developing strategies to champion the interests of Deaf communities across the Midwest heartland - a vibrant mosaic of eleven states stretching from the Great Lakes to the Great Plains.
</p>
<div className="my-8">
<RegionMap />
</div>
<p className="mt-4">
While I'm a seasoned advocate within KAD, my role with NAD represents an exciting new chapter in my leadership journey, allowing me to expand my impact to a national scale while continuing to serve my state community.
</p>
</section>
<section className="my-12">
<h2 className="text-2xl font-bold mb-4">Breaking Communication Barriers</h2>
<p>
For the past {yearsOfAdvocacy} years, I've dedicated myself to dismantling communication barriers within the Deaf community. My work involves advocating for those who cannot advocate for themselves and educating the broader community about Deaf history, arts, and culture. This mission is deeply personal it's about creating the change I wish I had seen in my early years.
</p>
</section>
<section className="my-12 bg-gradient-to-r from-blue-50 to-purple-50 p-8 rounded-xl">
<h2 className="text-2xl font-bold mb-4">Building Tomorrow's Leaders</h2>
<p>
Today, I focus on nurturing and strengthening leadership within the Deaf community. Through mentorship, workshops, and advocacy training, I'm committed to developing the next generation of Deaf leaders. Like planting a tree whose shade will benefit future generations, I'm building a legacy of empowerment and leadership that will continue to grow and create positive change long into the future.
</p>
</section>
</motion.div>
</div>
</div>
</motion.div>
)
}

54
src/app/layout.tsx Normal file
View file

@ -0,0 +1,54 @@
import '@/styles/globals.css'
import React from 'react'
import type { Metadata } from 'next'
import { NotificationProvider } from '@/components/providers/notification-provider'
import Layout from '@/components/layout/Layout'
export const metadata: Metadata = {
title: 'Chris Haulmark - Deaf Advocacy & Leadership',
description: 'Empowering the Deaf community through advocacy, leadership, and cultural bridge-building.',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'Chris Haulmark',
},
formatDetection: {
telephone: true,
date: true,
address: true,
email: true,
url: true,
},
}
export const viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
userScalable: false,
viewportFit: 'cover',
themeColor: '#ffffff',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="h-full antialiased">
<head>
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
</head>
<body className="flex min-h-full flex-col bg-white">
<NotificationProvider>
<Layout>
{children}
</Layout>
</NotificationProvider>
</body>
</html>
)
}

16
src/app/page.tsx Normal file
View file

@ -0,0 +1,16 @@
'use client'
import React from 'react'
import Hero from '@/components/home/Hero'
import ServiceGrid from '@/components/home/ServiceGrid'
import Timeline from '@/components/home/Timeline'
export default function Home() {
return (
<main className="flex flex-col min-h-screen">
<Hero />
<ServiceGrid />
<Timeline />
</main>
)
}

View file

@ -0,0 +1,216 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'
import { BsTranslate, BsFileText } from 'react-icons/bs'
import { FaSignLanguage, FaClosedCaptioning, FaChalkboardTeacher } from 'react-icons/fa'
import { MdAccessibility, MdOutlineGroups } from 'react-icons/md'
const offerings = [
{
icon: <BsTranslate className="w-8 h-8" />,
title: "Translation Services",
description: "I provide precise translations from ASL to English, capturing the nuances and depth of this visual language, and converting it into comprehensible English text"
},
{
icon: <BsFileText className="w-8 h-8" />,
title: "Content Transformation",
description: "I transform ASL content, whether video-based or in-person, into written English narratives or scripts, bridging the communication gap between hearing and Deaf communities"
},
{
icon: <FaClosedCaptioning className="w-8 h-8" />,
title: "Video Captions",
description: "I create accurate, easy-to-follow captions for ASL videos, enhancing accessibility for viewers who may not understand ASL"
},
{
icon: <FaChalkboardTeacher className="w-8 h-8" />,
title: "Training & Workshops",
description: "I organize workshops emphasizing the intricacies of converting ASL expressions into English valuable for interpreters, educators, and those working with the Deaf community"
}
]
const impactAreas = [
{
icon: <MdAccessibility className="w-12 h-12" />,
title: "Communication Accessibility",
description: "I offer consultations on making information more accessible to Deaf individuals through ASL conversion and enriched English translations",
gradient: "from-blue-400 to-blue-600"
},
{
icon: <MdOutlineGroups className="w-12 h-12" />,
title: "Community Advocacy",
description: "As a Deaf advocate, I highlight the importance of accurate ASL-to-English translation and captioning in fostering understanding between communities",
gradient: "from-blue-600 to-mcdi-maroon"
}
]
const services = [
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
}
]
const ServiceNavigation = () => {
const currentIndex = services.findIndex(s => s.path === '/services/asl-bridging')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<Link href={prevService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.div>
</Link>
<Link href={nextService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.div>
</Link>
</motion.div>
)
}
export default function ASLBridging() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<FaSignLanguage className="w-16 h-16 text-blue-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
ASL-to-English Linguistic Bridging
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
As an experienced Deaf professional, I specialize in accurately translating the richness and
diversity of ASL expressions into English. My personal background and expertise enable me to
ensure seamless communication while preserving the authentic voice of ASL.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
{offerings.map((offering, index) => (
<motion.div
key={offering.title}
className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 + 0.4 }}
whileHover={{ y: -5 }}
>
<div className="text-blue-600 mb-4">
{offering.icon}
</div>
<h3 className="text-xl font-bold mb-2">{offering.title}</h3>
<p className="text-gray-600">{offering.description}</p>
</motion.div>
))}
</div>
<motion.div
className="bg-gradient-to-r from-blue-600 to-mcdi-maroon rounded-2xl p-8 text-white max-w-4xl mx-auto mb-16"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
>
<h2 className="text-2xl font-bold mb-4">My Commitment</h2>
<p className="leading-relaxed">
Drawing from my personal journey as a Deaf individual and my extensive experience in advocacy,
I am dedicated to bridging the linguistic gap between ASL and English. My goal is to foster
genuine understanding and respect between Deaf and hearing communities through accurate,
culturally-sensitive translations.
</p>
</motion.div>
<motion.section
className="max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<div className="grid md:grid-cols-2 gap-8">
{impactAreas.map((area, index) => (
<motion.div
key={area.title}
className="relative overflow-hidden rounded-xl shadow-lg"
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 + index * 0.2 }}
whileHover={{ scale: 1.02 }}
>
<div className={`absolute h-2 w-full bg-gradient-to-r ${area.gradient} top-0`} />
<div className="p-8 bg-white">
<div className={`bg-gradient-to-r ${area.gradient} text-white p-4 rounded-full w-20 h-20 flex items-center justify-center mb-6`}>
{area.icon}
</div>
<h3 className="text-xl font-bold mb-4">{area.title}</h3>
<p className="text-gray-600">{area.description}</p>
</div>
</motion.div>
))}
</div>
<ServiceNavigation />
</motion.section>
</div>
</motion.div>
)
}

View file

@ -0,0 +1,222 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { useRouter } from 'next/navigation'
import { FaChalkboardTeacher, FaUserGraduate, FaHandsHelping } from 'react-icons/fa'
import { GiPublicSpeaker } from 'react-icons/gi'
import { BiHistory, BiWorld } from 'react-icons/bi'
import { RiCommunityLine } from 'react-icons/ri'
const offerings = [
{
icon: <GiPublicSpeaker className="w-8 h-8" />,
title: "Public Speaking Engagements",
description: "I deliver enlightening presentations on Deaf history, culture, and empowerment, drawing from my personal journey and extensive global experience"
},
{
icon: <FaChalkboardTeacher className="w-8 h-8" />,
title: "Deafhood Advocacy Workshops",
description: "I facilitate transformative workshops exploring Deafhood philosophy, cultural pride, and identity development"
},
{
icon: <FaUserGraduate className="w-8 h-8" />,
title: "Role Model Visits",
description: "I visit classrooms and institutions as a Deaf role model, empowering Deaf students and educating hearing students about Deaf culture"
},
{
icon: <RiCommunityLine className="w-8 h-8" />,
title: "Community Building",
description: "I actively work to strengthen connections within Deaf spaces, drawing from my experience visiting Deaf communities across 54 countries"
}
]
const impactAreas = [
{
icon: <BiHistory className="w-12 h-12" />,
title: "Cultural Heritage",
description: "I share insights about the rich history of Deaf culture, drawing from my extensive research and personal experiences worldwide",
gradient: "from-mcdi-maroon to-red-600"
},
{
icon: <FaHandsHelping className="w-12 h-12" />,
title: "Advocacy Leadership",
description: "I bring extensive leadership experience from state and national Deaf organizations to strengthen advocacy initiatives",
gradient: "from-mcdi-maroon to-red-600"
},
{
icon: <BiWorld className="w-12 h-12" />,
title: "Global Perspective",
description: "I bring unique insights from my two-year journey across 54 countries, living with Deaf families and experiencing diverse sign languages",
gradient: "from-mcdi-maroon to-red-600"
}
]
const services = [
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
}
]
function ServiceNavigation() {
const router = useRouter()
const currentIndex = services.findIndex(s => s.path === '/services/deafhood')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(prevService.path)}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.button>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(nextService.path)}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.button>
</motion.div>
)
}
export default function Deafhood() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-red-50 via-white to-red-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<RiCommunityLine className="w-16 h-16 text-mcdi-maroon" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-mcdi-maroon to-red-600 bg-clip-text text-transparent">
Deafhood Representation & Education
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
As a dedicated Deaf advocate with global experience, I provide comprehensive Deafhood representation services,
sharing authentic perspectives and fostering understanding through personal experience and expertise.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
{offerings.map((offering, index) => (
<motion.div
key={offering.title}
className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 + 0.4 }}
whileHover={{ y: -5 }}
>
<div className="text-mcdi-maroon mb-4">
{offering.icon}
</div>
<h3 className="text-xl font-bold mb-2">{offering.title}</h3>
<p className="text-gray-600">{offering.description}</p>
</motion.div>
))}
</div>
<motion.div
className="bg-gradient-to-r from-mcdi-maroon to-red-600 rounded-2xl p-8 text-white max-w-4xl mx-auto mb-16"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
>
<h2 className="text-2xl font-bold mb-4">My Commitment to You</h2>
<p className="leading-relaxed">
Drawing from my journey as a Deaf individual and my extensive global experiences,
I am dedicated to sharing authentic Deaf perspectives and fostering understanding.
My role as Vice President of KAD and NAD Region II Board Representative allows me
to bring valuable insights and connections to every engagement.
</p>
</motion.div>
<motion.section
className="max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<div className="grid md:grid-cols-3 gap-8">
{impactAreas.map((area, index) => (
<motion.div
key={area.title}
className="relative overflow-hidden rounded-xl shadow-lg"
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 + index * 0.2 }}
whileHover={{ scale: 1.02 }}
>
<div className={`absolute h-2 w-full bg-gradient-to-r ${area.gradient} top-0`} />
<div className="p-8 bg-white">
<div className={`bg-gradient-to-r ${area.gradient} text-white p-4 rounded-full w-20 h-20 flex items-center justify-center mb-6`}>
{area.icon}
</div>
<h3 className="text-xl font-bold mb-4">{area.title}</h3>
<p className="text-gray-600">{area.description}</p>
</div>
</motion.div>
))}
</div>
<ServiceNavigation />
</motion.section>
</div>
</motion.div>
)
}

View file

@ -0,0 +1,234 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { useRouter } from 'next/navigation'
import { FaUsers, FaLightbulb, FaHandsHelping, FaChalkboardTeacher } from 'react-icons/fa'
import { GiPathDistance, GiMountainRoad } from 'react-icons/gi'
import { RiTeamLine, RiMentalHealthLine } from 'react-icons/ri'
const offerings = [
{
icon: <FaLightbulb className="w-8 h-8" />,
title: "Vision Development",
description: "I guide individuals in discovering and articulating their unique leadership vision within the Deaf community, drawing from my personal journey"
},
{
icon: <FaHandsHelping className="w-8 h-8" />,
title: "One-on-One Mentorship",
description: "I provide personalized guidance drawing from my decades of advocacy experience and leadership roles"
},
{
icon: <FaChalkboardTeacher className="w-8 h-8" />,
title: "Leadership Workshops",
description: "I facilitate interactive sessions focused on developing core leadership competencies, sharing insights from my global Deaf community experiences"
},
{
icon: <RiTeamLine className="w-8 h-8" />,
title: "Community Building",
description: "I share proven strategies for creating and nurturing strong Deaf community networks, based on my experience across 54 countries"
}
]
const impacts = [
{
icon: <GiPathDistance className="w-12 h-12" />,
title: "Personal Growth Journey",
description: "Drawing from my transition from IT professional to Deaf community leader, I personally guide others in discovering their authentic leadership path",
gradient: "from-purple-400 to-purple-600"
},
{
icon: <RiMentalHealthLine className="w-12 h-12" />,
title: "Individual Empowerment",
description: "As your mentor, I help develop your confidence and self-advocacy skills through proven leadership development techniques",
gradient: "from-blue-400 to-blue-600"
},
{
icon: <GiMountainRoad className="w-12 h-12" />,
title: "Strategic Development",
description: "Together, we'll create your personalized roadmap for leadership growth based on your unique strengths and community needs",
gradient: "from-mcdi-maroon to-red-600"
}
]
const services = [
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
},
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
}
]
function ServiceNavigation() {
const router = useRouter()
const currentIndex = services.findIndex(s => s.path === '/services/leadership')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(prevService.path)}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.button>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(nextService.path)}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.button>
</motion.div>
)
}
export default function Leadership() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-purple-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<FaUsers className="w-16 h-16 text-purple-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-purple-600 to-mcdi-maroon bg-clip-text text-transparent">
Transformational Leadership Development
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
As your dedicated leadership mentor, I personally guide and empower the next generation
of Deaf leaders through individualized mentorship, strategic guidance, and proven
leadership development approaches drawn from my extensive experience.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
{offerings.map((offering, index) => (
<motion.div
key={offering.title}
className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 + 0.4 }}
whileHover={{ y: -5 }}
>
<div className="text-purple-600 mb-4">
{offering.icon}
</div>
<h3 className="text-xl font-bold mb-2">{offering.title}</h3>
<p className="text-gray-600">{offering.description}</p>
</motion.div>
))}
</div>
<motion.div
className="bg-gradient-to-r from-purple-600 to-mcdi-maroon rounded-2xl p-8 text-white max-w-4xl mx-auto mb-16"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
>
<h2 className="text-2xl font-bold mb-4">Why Work With Me?</h2>
<ul className="space-y-4">
<li className="flex items-start">
<span className="mr-3"></span>
<p>I bring proven leadership experience spanning state and national levels in prominent Deaf organizations</p>
</li>
<li className="flex items-start">
<span className="mr-3"></span>
<p>I offer unique insights from my two-year journey across 54 countries engaging with global Deaf communities</p>
</li>
<li className="flex items-start">
<span className="mr-3"></span>
<p>I provide personalized mentorship drawing from my transition from IT management to Deaf advocacy</p>
</li>
<li className="flex items-start">
<span className="mr-3"></span>
<p>I focus on individual growth while maintaining a strong connection to community impact</p>
</li>
</ul>
</motion.div>
<motion.section
className="max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<div className="grid md:grid-cols-3 gap-8">
{impacts.map((impact, index) => (
<motion.div
key={impact.title}
className="relative overflow-hidden rounded-xl shadow-lg"
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 + index * 0.2 }}
whileHover={{ scale: 1.02 }}
>
<div className={`absolute h-2 w-full bg-gradient-to-r ${impact.gradient} top-0`} />
<div className="p-8 bg-white">
<div className={`bg-gradient-to-r ${impact.gradient} text-white p-4 rounded-full w-20 h-20 flex items-center justify-center mb-6`}>
{impact.icon}
</div>
<h3 className="text-xl font-bold mb-4">{impact.title}</h3>
<p className="text-gray-600">{impact.description}</p>
</div>
</motion.div>
))}
</div>
<ServiceNavigation />
</motion.section>
</div>
</motion.div>
)
}

View file

@ -0,0 +1,231 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { useRouter } from 'next/navigation'
import { GiTeacher, GiScales } from 'react-icons/gi'
import { FaHandsHelping, FaPencilAlt, FaUserFriends, FaExclamationTriangle } from 'react-icons/fa'
import { MdGavel, MdForum } from 'react-icons/md'
const offerings = [
{
icon: <GiTeacher className="w-8 h-8" />,
title: "Self-Advocacy Training",
description: "I equip individuals with tools to understand and assert their legal rights, drawing from my extensive experience in Deaf advocacy"
},
{
icon: <FaHandsHelping className="w-8 h-8" />,
title: "Peer Advocacy Workshops",
description: "I facilitate workshops that empower Deaf individuals to effectively advocate for others within our community"
},
{
icon: <MdGavel className="w-8 h-8" />,
title: "Rights Education",
description: "I share comprehensive knowledge about legal rights and anti-discrimination laws specifically affecting the Deaf community"
},
{
icon: <FaPencilAlt className="w-8 h-8" />,
title: "Letter Writing Guidance",
description: "I provide personalized assistance in crafting effective communications that assert and defend legal rights"
}
]
const impactAreas = [
{
icon: <FaUserFriends className="w-12 h-12" />,
title: "Community Empowerment",
description: "I foster a network of legally literate Deaf individuals who can confidently navigate their rights and responsibilities",
gradient: "from-yellow-400 to-yellow-600"
},
{
icon: <MdForum className="w-12 h-12" />,
title: "Open Dialogue",
description: "I create spaces for sharing experiences and strategies, building collective knowledge within our community",
gradient: "from-yellow-500 to-amber-600"
}
]
const services = [
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
}
]
function ServiceNavigation() {
const router = useRouter()
const currentIndex = services.findIndex(s => s.path === '/services/legal-literacy')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(prevService.path)}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.button>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(nextService.path)}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.button>
</motion.div>
)
}
export default function LegalLiteracy() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-yellow-50 via-white to-yellow-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-8 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<motion.div
className="bg-gradient-to-r from-amber-50 to-yellow-50 border-l-4 border-amber-500 rounded-lg p-6 mb-12 shadow-lg"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
>
<div className="flex items-center mb-4">
<FaExclamationTriangle className="text-amber-500 w-6 h-6 mr-3" />
<h2 className="text-xl font-bold text-amber-700">Important Notice</h2>
</div>
<p className="text-amber-800 leading-relaxed">
While I bring extensive experience in Deaf advocacy and rights education, I am not a licensed attorney.
The services I provide focus on education and advocacy skills, but do not constitute legal advice or
the practice of law. For specific legal matters, please consult with a qualified attorney.
</p>
</motion.div>
<div className="flex items-center justify-center mb-6">
<GiScales className="w-16 h-16 text-yellow-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-yellow-600 to-amber-600 bg-clip-text text-transparent">
Legal Literacy & Advocacy
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
As an experienced advocate, I provide comprehensive legal education and advocacy training,
empowering the Deaf community to understand and assert their rights effectively.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
{offerings.map((offering, index) => (
<motion.div
key={offering.title}
className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 + 0.4 }}
whileHover={{ y: -5 }}
>
<div className="text-yellow-600 mb-4">
{offering.icon}
</div>
<h3 className="text-xl font-bold mb-2">{offering.title}</h3>
<p className="text-gray-600">{offering.description}</p>
</motion.div>
))}
</div>
<motion.div
className="bg-gradient-to-r from-yellow-600 to-amber-600 rounded-2xl p-8 text-white max-w-4xl mx-auto mb-16"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
>
<h2 className="text-2xl font-bold mb-4">My Approach</h2>
<p className="leading-relaxed">
Drawing from my years of advocacy experience and deep understanding of the Deaf community's
legal challenges, I provide practical, actionable guidance. My goal is to empower you with
the knowledge and confidence to advocate effectively for yourself and others.
</p>
</motion.div>
<motion.section
className="max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<div className="grid md:grid-cols-2 gap-8">
{impactAreas.map((area, index) => (
<motion.div
key={area.title}
className="relative overflow-hidden rounded-xl shadow-lg"
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 + index * 0.2 }}
whileHover={{ scale: 1.02 }}
>
<div className={`absolute h-2 w-full bg-gradient-to-r ${area.gradient} top-0`} />
<div className="p-8 bg-white">
<div className={`bg-gradient-to-r ${area.gradient} text-white p-4 rounded-full w-20 h-20 flex items-center justify-center mb-6`}>
{area.icon}
</div>
<h3 className="text-xl font-bold mb-4">{area.title}</h3>
<p className="text-gray-600">{area.description}</p>
</div>
</motion.div>
))}
</div>
<ServiceNavigation />
</motion.section>
</div>
</motion.div>
)
}

View file

@ -0,0 +1,213 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { useRouter } from 'next/navigation'
import { BiBookHeart, BiMessageDetail, BiMovie } from 'react-icons/bi'
import { FaUserTie, FaChalkboardTeacher, FaBlog } from 'react-icons/fa'
const offerings = [
{
icon: <BiMessageDetail className="w-8 h-8" />,
title: "Personal Storytelling",
description: "I take my own experiences and lessons as a Deaf person and weave them into compelling narratives that inspire and challenge societal perceptions"
},
{
icon: <FaUserTie className="w-8 h-8" />,
title: "Community Story Collection",
description: "I reach into the depths of the Deaf community to bring their experiences to the forefront, creating informative and emotionally resonant content"
},
{
icon: <BiMovie className="w-8 h-8" />,
title: "Video Content Creation",
description: "I produce high-quality video content that distills complex ideas into accessible and relatable narratives"
},
{
icon: <FaChalkboardTeacher className="w-8 h-8" />,
title: "Educational Resources",
description: "I develop comprehensive materials about Deaf culture, ASL, and advocacy strategies"
}
]
const impactAreas = [
{
icon: <BiBookHeart className="w-12 h-12" />,
title: "Inspiring Deaf Role Models",
description: "I create content featuring Deaf individuals from various walks of life, showcasing their accomplishments and journeys",
gradient: "from-green-400 to-green-600"
},
{
icon: <FaBlog className="w-12 h-12" />,
title: "Written Articles",
description: "I provide written narratives covering various aspects of Deaf culture and living, stirring empathy and fostering inclusivity",
gradient: "from-green-400 to-green-600"
}
]
const services = [
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
}
]
function ServiceNavigation() {
const router = useRouter()
const currentIndex = services.findIndex(s => s.path === '/services/narratives')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(prevService.path)}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.button>
<motion.button
className={`flex-1 px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => router.push(nextService.path)}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.button>
</motion.div>
)
}
export default function Narratives() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-green-50 via-white to-green-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<BiBookHeart className="w-16 h-16 text-green-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-green-600 to-green-800 bg-clip-text text-transparent">
Deaf Community Narratives
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
I illuminate the vivid collage of experiences, resilience, triumphs, and challenges
within the Deaf community through compelling storytelling and content creation.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
{offerings.map((offering, index) => (
<motion.div
key={offering.title}
className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 + 0.4 }}
whileHover={{ y: -5 }}
>
<div className="text-green-600 mb-4">
{offering.icon}
</div>
<h3 className="text-xl font-bold mb-2">{offering.title}</h3>
<p className="text-gray-600">{offering.description}</p>
</motion.div>
))}
</div>
<motion.div
className="bg-gradient-to-r from-green-600 to-green-800 rounded-2xl p-8 text-white max-w-4xl mx-auto mb-16"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
>
<h2 className="text-2xl font-bold mb-4">My Commitment</h2>
<p className="leading-relaxed">
Through my Deaf Community Narratives services, I aim to enhance visibility, foster understanding,
and empower the Deaf community at large. Together, let's express the diverse, enriching
perspectives that our community holds.
</p>
</motion.div>
<motion.section
className="max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<div className="grid md:grid-cols-2 gap-8">
{impactAreas.map((area, index) => (
<motion.div
key={area.title}
className="relative overflow-hidden rounded-xl shadow-lg"
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 + index * 0.2 }}
whileHover={{ scale: 1.02 }}
>
<div className={`absolute h-2 w-full bg-gradient-to-r ${area.gradient} top-0`} />
<div className="p-8 bg-white">
<div className={`bg-gradient-to-r ${area.gradient} text-white p-4 rounded-full w-20 h-20 flex items-center justify-center mb-6`}>
{area.icon}
</div>
<h3 className="text-xl font-bold mb-4">{area.title}</h3>
<p className="text-gray-600">{area.description}</p>
</div>
</motion.div>
))}
</div>
<ServiceNavigation />
</motion.section>
</div>
</motion.div>
)
}

151
src/app/services/page.tsx Normal file
View file

@ -0,0 +1,151 @@
'use client'
import { motion, useInView } from 'framer-motion'
import { useRef } from 'react'
import Link from 'next/link'
import { FaSignLanguage, FaUsers } from 'react-icons/fa'
import { RiCommunityLine } from 'react-icons/ri'
import { BiBookHeart } from 'react-icons/bi'
import { GiScales } from 'react-icons/gi'
import { MdVideoLibrary } from 'react-icons/md'
const services = [
{
title: 'ASL-to-English Linguistic Bridging',
description: 'Specializing in accurately translating the richness and diversity of ASL expressions into English, ensuring seamless communication and fostering understanding and inclusivity.',
path: '/services/asl-bridging',
icon: <FaSignLanguage className="w-12 h-12" />,
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
description: 'Offering comprehensive leadership services focusing on inspiring and motivating Deaf individuals to exceed expectations and achieve incredible personal growth.',
path: '/services/leadership',
icon: <FaUsers className="w-12 h-12" />,
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
description: 'Dedicated to amplifying the voices of Deafhood, actively engaging with various audiences, and promoting broad awareness and understanding of our diverse community.',
path: '/services/deafhood',
icon: <RiCommunityLine className="w-12 h-12" />,
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
description: 'Illuminating the vivid collage of experiences, resilience, triumphs, and challenges within the Deaf community through compelling storytelling.',
path: '/services/narratives',
icon: <BiBookHeart className="w-12 h-12" />,
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
description: 'Providing comprehensive legal education to the Deaf community, promoting self-advocacy and peer advocacy through knowledge and empowerment.',
path: '/services/legal-literacy',
icon: <GiScales className="w-12 h-12" />,
color: 'from-yellow-400 to-yellow-600'
},
{
title: 'Testimonials',
description: 'Real stories and experiences from individuals who have been impacted by our services, showcasing the transformative power of our work in the Deaf community.',
path: '/testimonials',
icon: <MdVideoLibrary className="w-12 h-12" />,
color: 'from-indigo-400 to-indigo-600'
}
]
export default function ServicesPage() {
const ref = useRef(null)
const isInView = useInView(ref, { once: false, amount: 0.2 })
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2
}
}
}
const cardVariants = {
hidden: {
opacity: 0,
y: 50
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: "easeOut"
}
}
}
return (
<section className="py-20 bg-gray-50">
<div className="container mx-auto px-4">
<motion.h2
className="text-4xl font-bold mb-12 text-center bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent"
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.6 }}
>
Services
</motion.h2>
<motion.div
ref={ref}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
variants={containerVariants}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
>
{services.map((service) => (
<motion.div
key={service.title}
variants={cardVariants}
>
<Link href={service.path}>
<motion.div
className="bg-white rounded-xl shadow-xl overflow-hidden h-full transform transition-all duration-300 hover:-translate-y-2 flex flex-col"
whileHover={{
scale: 1.02,
transition: { duration: 0.2 }
}}
whileTap={{ scale: 0.98 }}
>
<div className={`h-2 bg-gradient-to-r ${service.color}`} />
<div className="p-6 flex-grow">
<motion.div
className="text-4xl mb-4"
whileHover={{ rotate: 10, scale: 1.1 }}
transition={{ type: "spring", stiffness: 300 }}
>
{service.icon}
</motion.div>
<h3 className="text-xl font-bold mb-3">{service.title}</h3>
<p className="text-gray-600">{service.description}</p>
</div>
<div className={`p-4 bg-gradient-to-r ${service.color} border-t-2 border-white/20`}>
<span className="text-base md:text-lg font-semibold text-white tracking-wider flex items-center justify-between">
Learn More
<motion.span
initial={{ x: 0 }}
whileHover={{ x: 4 }}
className="transform transition-transform"
>
</motion.span>
</span>
</div>
</motion.div>
</Link>
</motion.div>
))}
</motion.div>
</div>
</section>
)
}

View file

@ -0,0 +1,181 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'
import { MdVideoLibrary, MdOutlineRateReview } from 'react-icons/md'
import { BiMessageSquareDetail } from 'react-icons/bi'
import { FaSignLanguage, FaClosedCaptioning } from 'react-icons/fa'
const services = [
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
},
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
}
]
function ServiceNavigation() {
const currentIndex = services.findIndex(s => s.path === '/testimonials')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<Link href={prevService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.div>
</Link>
<Link href={nextService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.div>
</Link>
</motion.div>
)
}
export default function Testimonials() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<MdVideoLibrary className="w-16 h-16 text-blue-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
Client Testimonials
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
Real stories and experiences from individuals who have been impacted by my advocacy
and leadership work in the Deaf community.
</p>
</motion.div>
<motion.div
className="bg-white rounded-xl shadow-lg p-8 mb-16 max-w-4xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<div className="flex items-center justify-center space-x-8 mb-8">
<FaSignLanguage className="w-12 h-12 text-blue-600" />
<FaClosedCaptioning className="w-12 h-12 text-blue-600" />
</div>
<h2 className="text-2xl font-bold text-center mb-6">Coming Soon</h2>
<p className="text-gray-700 text-center mb-8">
I am currently collecting powerful testimonials from individuals who have experienced
transformative changes through my services. Each story will be presented in both ASL
and written English to ensure full accessibility.
</p>
<div className="grid md:grid-cols-3 gap-8 mt-12">
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<MdVideoLibrary className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">ASL Videos</h3>
<p className="text-gray-600 text-sm">Authentic testimonials in American Sign Language</p>
</motion.div>
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<BiMessageSquareDetail className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">English Transcripts</h3>
<p className="text-gray-600 text-sm">Full written translations for complete accessibility</p>
</motion.div>
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<MdOutlineRateReview className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">Impact Stories</h3>
<p className="text-gray-600 text-sm">Real experiences of transformation and growth</p>
</motion.div>
</div>
</motion.div>
<motion.div
className="bg-gradient-to-r from-blue-600 to-mcdi-maroon rounded-2xl p-8 text-white max-w-4xl mx-auto"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.6 }}
>
<h2 className="text-2xl font-bold mb-4">Share Your Story</h2>
<p className="leading-relaxed">
Have you worked with me? Your story can inspire others and contribute to
the growing narrative of empowerment in the Deaf community. Contact me to share your
experience and potentially be featured in my testimonials.
</p>
</motion.div>
<ServiceNavigation />
</div>
</motion.div>
)
}

View file

@ -0,0 +1,181 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'
import { MdVideoLibrary, MdOutlineRateReview } from 'react-icons/md'
import { BiMessageSquareDetail } from 'react-icons/bi'
import { FaSignLanguage, FaClosedCaptioning } from 'react-icons/fa'
const services = [
{
title: 'Legal Literacy',
path: '/services/legal-literacy',
color: 'from-yellow-400 to-yellow-600'
},
{
title: 'Testimonials',
path: '/testimonials',
color: 'from-indigo-400 to-indigo-600'
},
{
title: 'ASL-to-English Linguistic Bridging',
path: '/services/asl-bridging',
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
path: '/services/leadership',
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
path: '/services/deafhood',
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
path: '/services/narratives',
color: 'from-green-400 to-green-600'
}
]
const ServiceNavigation = () => {
const currentIndex = services.findIndex(s => s.path === '/testimonials')
const prevService = services[(currentIndex - 1 + services.length) % services.length]
const nextService = services[(currentIndex + 1) % services.length]
return (
<motion.div
className="mt-16 flex items-center justify-between gap-8 max-w-6xl mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
>
<Link href={prevService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${prevService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span></span>
<span className="font-medium">{prevService.title}</span>
</span>
</motion.div>
</Link>
<Link href={nextService.path} className="flex-1">
<motion.div
className={`px-6 py-4 rounded-xl bg-gradient-to-r ${nextService.color} text-white shadow-lg`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<span className="flex items-center justify-center gap-2">
<span className="font-medium">{nextService.title}</span>
<span></span>
</span>
</motion.div>
</Link>
</motion.div>
)
}
export default function Testimonials() {
return (
<motion.div
className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 py-24"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<motion.div
className="max-w-4xl mx-auto mb-16 px-4"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center justify-center mb-6">
<MdVideoLibrary className="w-16 h-16 text-blue-600" />
</div>
<h1 className="text-4xl md:text-5xl font-bold text-center mb-8 leading-tight pb-2 bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
Client Testimonials
</h1>
<p className="text-xl text-gray-700 text-center leading-relaxed">
Real stories and experiences from individuals who have been impacted by my advocacy
and leadership work in the Deaf community.
</p>
</motion.div>
<motion.div
className="bg-white rounded-xl shadow-lg p-8 mb-16 max-w-4xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<div className="flex items-center justify-center space-x-8 mb-8">
<FaSignLanguage className="w-12 h-12 text-blue-600" />
<FaClosedCaptioning className="w-12 h-12 text-blue-600" />
</div>
<h2 className="text-2xl font-bold text-center mb-6">Coming Soon</h2>
<p className="text-gray-700 text-center mb-8">
I am currently collecting powerful testimonials from individuals who have experienced
transformative changes through my services. Each story will be presented in both ASL
and written English to ensure full accessibility.
</p>
<div className="grid md:grid-cols-3 gap-8 mt-12">
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<MdVideoLibrary className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">ASL Videos</h3>
<p className="text-gray-600 text-sm">Authentic testimonials in American Sign Language</p>
</motion.div>
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<BiMessageSquareDetail className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">English Transcripts</h3>
<p className="text-gray-600 text-sm">Full written translations for complete accessibility</p>
</motion.div>
<motion.div
className="text-center"
whileHover={{ y: -5 }}
>
<div className="bg-blue-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<MdOutlineRateReview className="w-8 h-8 text-blue-600" />
</div>
<h3 className="font-bold mb-2">Impact Stories</h3>
<p className="text-gray-600 text-sm">Real experiences of transformation and growth</p>
</motion.div>
</div>
</motion.div>
<motion.div
className="bg-gradient-to-r from-blue-600 to-mcdi-maroon rounded-2xl p-8 text-white max-w-4xl mx-auto"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.6 }}
>
<h2 className="text-2xl font-bold mb-4">Share Your Story</h2>
<p className="leading-relaxed">
Have you worked with me? Your story can inspire others and contribute to
the growing narrative of empowerment in the Deaf community. Contact me to share your
experience and potentially be featured in my testimonials.
</p>
</motion.div>
<ServiceNavigation />
</div>
</motion.div>
)
}

View file

@ -0,0 +1,106 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
const timelineData = [
{
year: 1998,
title: 'IT Career Start',
description: 'Began as PC technician',
details: 'Started career in technology sector'
},
{
year: 2009,
title: 'IT Manager',
description: 'Government agency IT manager',
details: 'Reached senior technology leadership position'
},
{
year: 2015,
title: 'Global Journey',
description: '47 countries, Deaf communities',
details: 'Immersive experience with Deaf communities worldwide'
},
{
year: 2021,
title: 'KAD Board Member',
description: 'Kansas Association of the Deaf',
details: 'Appointed to KAD board, beginning state-level leadership'
},
{
year: 2023,
title: 'KAD Vice President',
description: 'Leadership advancement',
details: 'Elected as Vice President of Kansas Association of the Deaf'
},
{
year: 2024,
title: 'NAD Region II Representative',
description: 'National Association of the Deaf',
details: 'Elected to serve Midwest region on national board'
}
]
export default function JourneyVisual() {
return (
<motion.div
className="w-full py-8"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<div className="relative">
<div className="relative grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-8 z-20">
{timelineData.map((item, index) => (
<motion.div
key={item.year}
className="flex flex-col items-center"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.2 }}
>
<motion.div
className="w-8 h-8 rounded-full bg-white border-4 border-blue-600 relative z-30 mb-4"
whileHover={{ scale: 1.2 }}
>
<motion.div
className="absolute inset-0 bg-blue-600 rounded-full opacity-20"
animate={{
scale: [1, 1.5, 1],
opacity: [0.2, 0.1, 0.2],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
</motion.div>
<motion.div
className="text-center"
whileHover={{ scale: 1.05 }}
>
<h3 className="font-bold text-xl text-blue-600">{item.year}</h3>
<p className="font-medium text-gray-800">{item.title}</p>
<p className="text-sm text-gray-600">{item.details}</p>
</motion.div>
</motion.div>
))}
</div>
<div className="absolute left-0 w-full h-1 bg-gray-200 top-[19%] transform -translate-y-1/2 z-10 hidden lg:block">
<motion.div
className="h-full bg-gradient-to-r from-blue-600 to-mcdi-maroon"
initial={{ width: 0 }}
whileInView={{ width: '100%' }}
viewport={{ once: true }}
transition={{ duration: 1.5, ease: "easeOut" }}
/>
</div>
</div>
</motion.div>
)
}

View file

@ -0,0 +1,148 @@
'use client'
import React, { useState, useEffect } from "react"
import { ComposableMap, Geographies, Geography } from "react-simple-maps"
import { motion } from "framer-motion"
import { feature } from "topojson-client"
import { GeometryObject, Topology } from "topojson-specification"
import usaStates from "./usa-states.json"
interface StateNameToPostal {
[key: string]: string
}
interface StateProperties {
name: string
}
export default function RegionMap() {
const [hoveredState, setHoveredState] = useState<string | null>(null)
const [dimensions, setDimensions] = useState({ width: 800, height: 400 })
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth
if (width < 640) { // sm breakpoint
setDimensions({ width: 300, height: 200 })
} else if (width < 768) { // md breakpoint
setDimensions({ width: 500, height: 300 })
} else {
setDimensions({ width: 800, height: 400 })
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const regionTwoStates = [
"IL", "IN", "IA", "KS", "KY",
"MI", "MN", "MO", "NE", "ND", "SD", "WI"
]
const stateNameToPostal: StateNameToPostal = {
"Illinois": "IL",
"Indiana": "IN",
"Iowa": "IA",
"Kansas": "KS",
"Kentucky": "KY",
"Michigan": "MI",
"Minnesota": "MN",
"Missouri": "MO",
"Nebraska": "NE",
"North Dakota": "ND",
"South Dakota": "SD",
"Wisconsin": "WI"
}
const geographyData = feature(
usaStates as unknown as Topology<{ states: GeometryObject & { properties: StateProperties } }>,
usaStates.objects.states as any
)
return (
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="w-full bg-white rounded-lg shadow-lg p-2 sm:p-4"
>
<div className="text-center mb-2 sm:mb-4">
<h3 className="text-lg sm:text-xl font-bold bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
NAD Region II: Midwest Heartland
</h3>
</div>
<div className="flex flex-wrap gap-1 sm:gap-2 justify-center mb-2 sm:mb-4">
{regionTwoStates.map((state) => (
<motion.span
key={state}
className={`px-1.5 sm:px-2 py-0.5 sm:py-1 text-xs font-semibold rounded-full transition-all duration-300 ${
hoveredState === state
? "bg-gradient-to-r from-blue-600 to-mcdi-maroon text-white"
: "bg-gray-200 text-gray-600"
}`}
whileHover={{ scale: 1.1 }}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
onMouseEnter={() => setHoveredState(state)}
onMouseLeave={() => setHoveredState(null)}
>
{state}
</motion.span>
))}
</div>
<div className="overflow-hidden">
<ComposableMap
projection="geoAlbersUsa"
projectionConfig={{
scale: dimensions.width * 0.8,
center: [-96, 38]
}}
width={dimensions.width}
height={dimensions.height}
>
<Geographies geography={geographyData}>
{({ geographies }) =>
geographies.map(geo => {
const stateName = geo.properties.name
const statePostal = stateNameToPostal[stateName] || ""
const isRegionTwo = regionTwoStates.includes(statePostal)
const isHovered = hoveredState === statePostal
return (
<Geography
key={geo.rsmKey}
geography={geo}
fill={isRegionTwo ? (isHovered ? "#1E40AF" : "#2563EB") : "#E5E7EB"}
stroke="#FFFFFF"
strokeWidth={0.5}
onMouseEnter={() => setHoveredState(statePostal)}
onMouseLeave={() => setHoveredState(null)}
style={{
default: {
outline: "none",
transition: "all 250ms"
},
hover: {
fill: isRegionTwo ? "#1E40AF" : "#D1D5DB",
outline: "none",
cursor: "pointer"
},
pressed: {
outline: "none"
}
}}
/>
)
})
}
</Geographies>
</ComposableMap>
</div>
</motion.div>
)
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,101 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'
import Image from 'next/image'
export default function Hero() {
return (
<section className="relative min-h-[calc(100dvh-80px)] flex items-center bg-gradient-to-br from-blue-50 via-white to-blue-50">
<div className="container mx-auto px-4 grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center py-12 lg:py-20">
<motion.div
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
className="z-10 text-center lg:text-left"
>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="mb-4 lg:mb-6"
>
<h2 className="text-lg lg:text-xl text-blue-600 font-semibold mb-2">Welcome to a World of Deaf Advocacy</h2>
<h1 className="text-4xl lg:text-5xl font-bold mb-3 lg:mb-4 bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-mcdi-maroon">
Chris Haulmark
</h1>
<h3 className="text-xl lg:text-2xl text-gray-700">Transforming Lives Through Leadership</h3>
</motion.div>
<motion.p
className="text-lg lg:text-xl leading-relaxed text-gray-700 mb-6 lg:mb-8 max-w-lg mx-auto lg:mx-0 font-medium"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
Empowering the Deaf community through advocacy, leadership, and cultural bridge-building. With experience spanning 54 countries and leadership roles in prominent state and national Deaf organizations.
</motion.p>
<motion.div
className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
>
<Link href="/services" className="w-full sm:w-auto">
<motion.button
className="w-full sm:w-auto bg-gradient-to-r from-blue-600 to-mcdi-maroon text-white px-6 lg:px-8 py-3 rounded-lg shadow-lg text-base lg:text-lg"
whileHover={{ scale: 1.05, boxShadow: "0 20px 25px -5px rgb(0 0 0 / 0.1)" }}
whileTap={{ scale: 0.95 }}
>
Explore Services
</motion.button>
</Link>
<Link href="/biography" className="w-full sm:w-auto">
<motion.button
className="w-full sm:w-auto border-2 border-blue-600 text-blue-600 px-6 lg:px-8 py-3 rounded-lg hover:bg-blue-50 transition-colors text-base lg:text-lg"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Learn More
</motion.button>
</Link>
</motion.div>
</motion.div>
<motion.div
className="relative z-10 mt-8 lg:mt-0"
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<div className="w-full h-[300px] sm:h-[400px] lg:h-[600px] relative group">
<motion.div
className="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent rounded-2xl"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
/>
<Image
src="/images/hero-image.jpg"
alt="Chris Haulmark standing by a Dodge Charger near a serene lake"
fill
className="absolute inset-0 rounded-2xl shadow-2xl object-cover grayscale hover:grayscale-0 transition-all duration-500"
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
<motion.div
className="absolute bottom-0 left-0 right-0 p-4 lg:p-8 text-white opacity-100 lg:opacity-0 group-hover:opacity-100 transition-opacity duration-300"
initial={{ y: 20 }}
whileHover={{ y: 0 }}
>
<p className="text-xl lg:text-2xl font-semibold mb-2 drop-shadow-lg">Global Deaf Advocate</p>
<p className="text-base lg:text-lg drop-shadow-lg">Vice President of KAD & NAD Region II Board Representative</p>
</motion.div>
</div>
</motion.div>
</div>
</section>
)
}

View file

@ -0,0 +1,73 @@
'use client'
import Link from 'next/link'
import { FaSignLanguage } from 'react-icons/fa'
import { Users, Users2, BookHeart, Scale, Video } from 'lucide-react'
import React from 'react'
const services = [
{
title: 'ASL-to-English Linguistic Bridging',
description: 'Precise translations capturing ASL nuances',
path: '/services/asl-bridging',
icon: <FaSignLanguage className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-blue-400 to-blue-600'
},
{
title: 'Transformational Leadership',
description: 'Inspiring and motivating Deaf individuals',
path: '/services/leadership',
icon: <Users className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-purple-400 to-purple-600'
},
{
title: 'Deafhood Representation',
description: 'Amplifying voices of Deafhood',
path: '/services/deafhood',
icon: <Users2 className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-mcdi-maroon to-red-600'
},
{
title: 'Deaf Community Narratives',
description: 'Sharing stories that matter',
path: '/services/narratives',
icon: <BookHeart className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-green-400 to-green-600'
},
{
title: 'Legal Literacy',
description: 'Empowering through legal knowledge',
path: '/services/legal-literacy',
icon: <Scale className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-yellow-400 to-yellow-600'
},
{
title: 'Testimonials',
description: 'Real stories and experiences from individuals who have been impacted by our services.',
path: '/testimonials',
icon: <Video className="w-8 h-8 sm:w-12 sm:h-12" />,
color: 'from-indigo-400 to-indigo-600'
}
]
export default function ServiceGrid() {
return (
<section className="py-12 sm:py-16 lg:py-20 bg-gray-50 w-full">
<div className="container mx-auto px-4 sm:px-6 w-full">
<h2 className="text-3xl sm:text-4xl font-bold mb-8 sm:mb-12 text-center bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
Services
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 w-full">
{services.map((service) => (
<div
key={service.title}
className="w-full h-full opacity-100" // Added opacity-100 to ensure visibility
>
{/* Rest of your card content */}
</div>
))}
</div>
</div>
</section>
)
}

View file

@ -0,0 +1,95 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
const timelineData = [
{
year: 1998,
title: 'IT Career Start',
description: 'Began as PC technician',
details: 'Started career in technology sector'
},
{
year: 2009,
title: 'IT Manager',
description: 'Government agency IT manager',
details: 'Reached senior technology leadership position'
},
{
year: 2015,
title: 'Global Journey',
description: '47 countries, Deaf communities',
details: 'Immersive experience with Deaf communities worldwide'
},
{
year: 2021,
title: 'KAD Board Member',
description: 'Kansas Association of the Deaf',
details: 'Appointed to KAD board, beginning state-level leadership'
},
{
year: 2023,
title: 'KAD Vice President',
description: 'Leadership advancement',
details: 'Elected as Vice President of Kansas Association of the Deaf'
},
{
year: 2024,
title: 'NAD Region II Representative',
description: 'National Association of the Deaf',
details: 'Elected to serve Midwest region on national board'
}
]
export default function Timeline() {
return (
<div className="py-20">
<div className="container mx-auto px-4">
<motion.h2
className="text-3xl font-bold mb-12 text-center"
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.6 }}
>
Career Timeline
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-8 justify-items-center">
{timelineData.map((item, index) => (
<motion.div
key={item.year}
className="relative text-center w-full max-w-[250px]"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{
duration: 0.6,
delay: index * 0.2,
ease: "easeOut"
}}
>
<motion.div
className="w-4 h-4 bg-blue-600 rounded-full mx-auto"
whileHover={{ scale: 1.5 }}
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: false }}
transition={{
delay: index * 0.2,
type: "spring",
stiffness: 200
}}
/>
<div className="mt-4">
<h3 className="font-bold">{item.year}</h3>
<p className="text-sm">{item.title}</p>
<p className="text-xs text-gray-600">{item.description}</p>
</div>
</motion.div>
))}
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,113 @@
'use client'
import Link from 'next/link'
import { useState } from 'react'
import { motion } from 'framer-motion'
import ContactModal from '../shared/ContactModal'
export default function Footer() {
const [isContactModalOpen, setIsContactModalOpen] = useState(false)
const services = [
{ path: '/services/asl-bridging', label: 'ASL-to-English Linguistic Bridging' },
{ path: '/services/leadership', label: 'Transformational Leadership' },
{ path: '/services/deafhood', label: 'Deafhood Representation' },
{ path: '/services/narratives', label: 'Deaf Community Narratives' },
{ path: '/services/legal-literacy', label: 'Legal Literacy' },
{ path: '/testimonials', label: 'Testimonials' }
]
return (
<footer className="bg-gradient-to-br from-gray-900 to-gray-800 text-white py-12">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
{/* Quick Links section */}
<div>
<h3 className="text-xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-mcdi-maroon bg-clip-text text-transparent">
Quick Links
</h3>
<ul className="space-y-2">
<li>
<Link href="/" className="hover:text-blue-400 transition-colors">
Home
</Link>
</li>
<li>
<Link href="/biography" className="hover:text-blue-400 transition-colors">
Biography
</Link>
</li>
<li>
<Link href="/services" className="hover:text-blue-400 transition-colors">
Services
</Link>
</li>
</ul>
</div>
{/* Services section */}
<div>
<h3 className="text-xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-mcdi-maroon bg-clip-text text-transparent">
Services
</h3>
<ul className="space-y-2">
{services.map((service) => (
<li key={service.path}>
<Link
href={service.path}
className="hover:text-blue-400 transition-colors"
>
{service.label}
</Link>
</li>
))}
</ul>
</div>
{/* Contact section */}
<div>
<h3 className="text-xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-mcdi-maroon bg-clip-text text-transparent">
Contact
</h3>
<p className="text-gray-300 mb-4">
Get in touch for collaborations and services
</p>
<motion.button
onClick={() => setIsContactModalOpen(true)}
className="px-6 py-3 bg-gradient-to-r from-blue-400 to-mcdi-maroon text-white rounded-lg shadow-lg hover:shadow-xl transition-all"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Contact Now
</motion.button>
<ContactModal
isOpen={isContactModalOpen}
onClose={() => setIsContactModalOpen(false)}
/>
</div>
</div>
{/* Copyright section */}
<div className="mt-12 pt-8 border-t border-gray-700">
<div className="flex flex-col items-center space-y-2">
<div className="text-center flex flex-col">
<span className="text-gray-400 whitespace-nowrap">© {new Date().getFullYear()} Chris Haulmark.</span>
<span className="text-gray-400 whitespace-nowrap">All rights reserved.</span>
</div>
<div className="text-center">
<span className="text-gray-400">Designed and Maintained by </span>
<a
href="http://deafgain.org"
target="_blank"
rel="noopener noreferrer"
className="text-yellow-400 hover:text-yellow-300 transition-colors whitespace-nowrap"
>
DeafGain, LLC
</a>
</div>
</div>
</div>
</div>
</footer>
)
}

View file

@ -0,0 +1,121 @@
'use client'
import { motion } from 'framer-motion'
import Link from 'next/link'
import { useState } from 'react'
import { usePathname } from 'next/navigation'
import { Menu, X } from 'lucide-react'
export default function Header() {
const pathname = usePathname()
const [isOpen, setIsOpen] = useState(false)
const isActive = (path: string) => {
return pathname === path
}
const navItems = [
{ path: '/', label: 'Home' },
{ path: '/biography', label: 'Biography' },
{ path: '/services', label: 'Services' },
{ path: '/testimonials', label: 'Testimonials' }
]
const menuVariants = {
open: {
opacity: 1,
x: 0,
transition: { type: "spring", stiffness: 300, damping: 30 }
},
closed: {
opacity: 0,
x: "100%",
transition: { type: "spring", stiffness: 300, damping: 30 }
}
}
return (
<motion.header
className="fixed w-full bg-white/90 backdrop-blur-sm z-50 shadow-lg"
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4 py-6 flex justify-between items-center">
<motion.div
className="text-4xl font-bold"
whileHover={{ scale: 1.05 }}
>
<Link href="/" className="relative group bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
Chris Haulmark
<motion.div
className="absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-blue-600 to-mcdi-maroon group-hover:w-full transition-all duration-300"
/>
</Link>
</motion.div>
{/* Desktop Navigation */}
<nav className="hidden md:block text-lg">
<ul className="flex space-x-12">
{navItems.map((item) => (
<li key={item.path}>
<Link href={item.path} className="relative group">
<span className={`transition-colors ${
isActive(item.path)
? 'text-blue-600'
: 'hover:text-blue-600'
}`}>
{item.label}
</span>
<motion.div
className={`absolute -bottom-1 left-0 h-0.5 bg-gradient-to-r from-blue-600 to-mcdi-maroon transition-all duration-300 ${
isActive(item.path) ? 'w-full' : 'w-0 group-hover:w-full'
}`}
/>
</Link>
</li>
))}
</ul>
</nav>
{/* Mobile Menu Button */}
<button
className="md:hidden text-2xl"
onClick={() => setIsOpen(!isOpen)}
>
{isOpen ? <X /> : <Menu />}
</button>
{/* Mobile Navigation */}
<motion.nav
className="fixed top-[88px] right-0 h-screen w-64 bg-white shadow-xl md:hidden"
initial="closed"
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
>
<ul className="flex flex-col p-4 space-y-4">
{navItems.map((item) => (
<motion.li
key={item.path}
whileHover={{ x: 10 }}
whileTap={{ scale: 0.95 }}
>
<Link
href={item.path}
className={`block p-2 rounded-lg ${
isActive(item.path)
? 'bg-blue-50 text-blue-600'
: 'hover:bg-gray-50'
}`}
onClick={() => setIsOpen(false)}
>
{item.label}
</Link>
</motion.li>
))}
</ul>
</motion.nav>
</div>
</motion.header>
)
}

View file

@ -0,0 +1,17 @@
'use client'
import Header from './Header'
import Footer from './Footer'
import { NotificationProvider } from '@/components/providers/notification-provider'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen flex flex-col bg-white">
<Header />
<main className="flex-grow pt-20">
{children}
</main>
<Footer />
</div>
)
}

View file

@ -0,0 +1,36 @@
'use client'
import { createContext, useContext } from 'react'
import Toast from '../shared/Toast'
interface NotificationContextType {
showNotification: (message: string, type: 'success' | 'error') => void
}
const NotificationContext = createContext<NotificationContextType>({
showNotification: () => {},
})
export const useNotification = () => useContext(NotificationContext)
import React, { useState } from 'react'
export function NotificationProvider({ children }: { children: React.ReactNode }) {
const [message, setMessage] = useState('')
const [type, setType] = useState<'success' | 'error'>('success')
const [isVisible, setIsVisible] = useState(false)
const showNotification = (message: string, type: 'success' | 'error') => {
setMessage(message)
setType(type)
setIsVisible(true)
setTimeout(() => setIsVisible(false), 3000)
}
return (
<NotificationContext.Provider value={{ showNotification }}>
{children}
<Toast message={message} type={type} isVisible={isVisible} />
</NotificationContext.Provider>
)
}

View file

@ -0,0 +1,49 @@
'use client'
import { motion } from 'framer-motion'
interface AnimatedDotProps {
active?: boolean
onClick?: () => void
size?: 'sm' | 'md' | 'lg'
}
export default function AnimatedDot({
active = false,
onClick,
size = 'md'
}: AnimatedDotProps) {
const sizes = {
sm: 'w-3 h-3',
md: 'w-4 h-4',
lg: 'w-5 h-5'
}
return (
<motion.div
className={`${sizes[size]} rounded-full cursor-pointer relative`}
initial={{ backgroundColor: '#E5E7EB' }}
animate={{
backgroundColor: active ? '#2563EB' : '#E5E7EB',
scale: active ? 1.2 : 1
}}
whileHover={{ scale: 1.3 }}
onClick={onClick}
>
{active && (
<motion.div
className="absolute inset-0 rounded-full bg-blue-500"
initial={{ opacity: 0, scale: 0.5 }}
animate={{
opacity: [0, 0.5, 0],
scale: [1, 1.5, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
}}
/>
)}
</motion.div>
)
}

View file

@ -0,0 +1,28 @@
'use client'
import { motion } from 'framer-motion'
interface CardProps {
title: string
description: string
icon?: React.ReactNode
onClick?: () => void
}
export default function Card({ title, description, icon, onClick }: CardProps) {
return (
<div
className="bg-white rounded-lg shadow-lg p-6 cursor-pointer hover:-translate-y-1 hover:scale-102"
onClick={onClick}
>
{icon && (
<div className="mb-4 text-blue-600">
{icon}
</div>
)}
<h3 className="text-xl font-bold mb-2">{title}</h3>
<p className="text-gray-600">{description}</p>
</div>
)
}

View file

@ -0,0 +1,251 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { useState } from 'react'
import { X } from 'lucide-react'
import { useNotification } from '@/components/providers/notification-provider'
interface ContactModalProps {
isOpen: boolean
onClose: () => void
}
const services = [
{ value: 'asl-bridging', label: 'ASL-to-English Linguistic Bridging' },
{ value: 'leadership', label: 'Transformational Leadership' },
{ value: 'deafhood', label: 'Deafhood Representation' },
{ value: 'narratives', label: 'Deaf Community Narratives' },
{ value: 'legal-literacy', label: 'Legal Literacy' },
{ value: 'general-inquiry', label: 'General Inquiry or Question' }
]
const validateEmail = (email: string) => {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return pattern.test(email)
}
export default function ContactModal({ isOpen, onClose }: ContactModalProps) {
const { showNotification } = useNotification()
const [formData, setFormData] = useState({
name: '',
email: '',
service: '',
message: ''
})
const [errors, setErrors] = useState({
name: '',
email: '',
service: '',
message: ''
})
const validateField = (name: string, value: string) => {
switch(name) {
case 'name':
return value.length < 2 ? 'Name must be at least 2 characters' :
value.length > 50 ? 'Name must be less than 50 characters' : ''
case 'email':
return !validateEmail(value) ? 'Please enter a valid email address' : ''
case 'service':
return !value ? 'Please select a service' : ''
case 'message':
return value.length < 10 ? 'Message must be at least 10 characters' :
value.length > 1000 ? 'Message must be less than 1000 characters' : ''
default:
return ''
}
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
setErrors(prev => ({ ...prev, [name]: validateField(name, value) }))
}
const isFormValid = () => {
const newErrors = {
name: validateField('name', formData.name),
email: validateField('email', formData.email),
service: validateField('service', formData.service),
message: validateField('message', formData.message)
}
setErrors(newErrors)
return !Object.values(newErrors).some(error => error !== '')
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!isFormValid()) {
showNotification('Please correct the errors in the form.', 'error')
return
}
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
if (response.ok) {
setFormData({ name: '', email: '', service: '', message: '' })
onClose()
setTimeout(() => {
showNotification('Message sent successfully! We will get back to you soon.', 'success')
}, 300)
} else {
showNotification('Failed to send message. Please try again.', 'error')
}
} catch (error) {
console.error('Error sending message:', error)
showNotification('Error sending message. Please try again.', 'error')
}
}
return (
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-2 sm:p-4 bg-black/50 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="relative w-full max-w-4xl bg-white rounded-xl sm:rounded-2xl shadow-xl sm:shadow-2xl"
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
>
<button
onClick={onClose}
className="absolute top-2 right-2 sm:top-4 sm:right-4 text-gray-500 hover:text-gray-700"
>
<X className="w-5 h-5 sm:w-6 sm:h-6" />
</button>
<div className="p-4 sm:p-6 md:p-8">
<h2 className="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6 bg-gradient-to-r from-blue-600 to-mcdi-maroon bg-clip-text text-transparent">
Let's Connect
</h2>
<form onSubmit={handleSubmit} className="space-y-4 sm:space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.1 }}
>
<label className="block text-sm font-medium text-gray-700 mb-1 sm:mb-2">
Name
</label>
<input
type="text"
name="name"
required
className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-lg border ${
errors.name ? 'border-red-500' : 'border-gray-300'
} focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900 text-sm sm:text-base`}
value={formData.name}
onChange={handleChange}
/>
{errors.name && (
<p className="mt-1 text-xs sm:text-sm text-red-500">{errors.name}</p>
)}
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}
>
<label className="block text-sm font-medium text-gray-700 mb-1 sm:mb-2">
Email
</label>
<input
type="email"
name="email"
required
className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-lg border ${
errors.email ? 'border-red-500' : 'border-gray-300'
} focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900 text-sm sm:text-base`}
value={formData.email}
onChange={handleChange}
/>
{errors.email && (
<p className="mt-1 text-xs sm:text-sm text-red-500">{errors.email}</p>
)}
</motion.div>
</div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
>
<label className="block text-sm font-medium text-gray-700 mb-1 sm:mb-2">
Area of Interest
</label>
<select
name="service"
required
className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-lg border ${
errors.service ? 'border-red-500' : 'border-gray-300'
} focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900 text-sm sm:text-base`}
value={formData.service}
onChange={handleChange}
>
<option value="">Select an option</option>
{services.map((service) => (
<option key={service.value} value={service.value}>
{service.label}
</option>
))}
</select>
{errors.service && (
<p className="mt-1 text-xs sm:text-sm text-red-500">{errors.service}</p>
)}
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}
>
<label className="block text-sm font-medium text-gray-700 mb-1 sm:mb-2">
Message
</label>
<textarea
name="message"
required
rows={4}
className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-lg border ${
errors.message ? 'border-red-500' : 'border-gray-300'
} focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900 text-sm sm:text-base`}
value={formData.message}
onChange={handleChange}
/>
{errors.message && (
<p className="mt-1 text-xs sm:text-sm text-red-500">{errors.message}</p>
)}
</motion.div>
<motion.button
type="submit"
className="w-full py-3 sm:py-4 bg-gradient-to-r from-blue-600 to-mcdi-maroon text-white rounded-lg font-medium text-sm sm:text-base shadow-lg hover:shadow-xl transition-all"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
Send Message
</motion.button>
</form>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
)
}

View file

@ -0,0 +1,42 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
interface ToastProps {
message: string
type: 'success' | 'error'
isVisible: boolean
}
export default function Toast({ message, type, isVisible }: ToastProps) {
const bgColor = type === 'success'
? 'from-blue-600 to-mcdi-maroon'
: 'from-red-600 to-red-800'
return (
<AnimatePresence>
{isVisible && (
<>
<motion.div
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="w-full max-w-md bg-white rounded-xl shadow-2xl overflow-hidden mx-4"
>
<div className={`h-2 bg-gradient-to-r ${bgColor}`} />
<div className="p-6">
<p className="text-lg font-medium text-gray-800 text-center">{message}</p>
</div>
</motion.div>
</motion.div>
</>
)}
</AnimatePresence>
)
}

41
src/lib/email.ts Normal file
View file

@ -0,0 +1,41 @@
import nodemailer from 'nodemailer'
interface EmailData {
name: string
email: string
service: string
message: string
}
export async function sendEmail(data: EmailData) {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.GOOGLE_EMAIL,
pass: process.env.GOOGLE_APP_PASSWORD,
},
})
const mailOptions = {
from: process.env.GOOGLE_EMAIL,
to: process.env.RECIPIENT_EMAIL,
subject: `New Contact Form Submission - ${data.service}`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #2563eb;">New Contact Form Submission</h2>
<div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px;">
<p><strong>Name:</strong> ${data.name}</p>
<p><strong>Email:</strong> ${data.email}</p>
<p><strong>Service:</strong> ${data.service}</p>
<p><strong>Message:</strong></p>
<p style="white-space: pre-wrap;">${data.message}</p>
</div>
</div>
`,
}
const info = await transporter.sendMail(mailOptions)
return info
}

72
src/lib/rate-limit.ts Normal file
View file

@ -0,0 +1,72 @@
import { Redis } from '@upstash/redis'
import { headers } from 'next/headers'
export interface RateLimitConfig {
interval: number // in seconds
limit: number
}
export interface RateLimitResult {
success: boolean
limit: number
remaining: number
reset: number // timestamp in seconds
}
export class RateLimit {
private redis: Redis
private prefix: string
constructor() {
this.redis = Redis.fromEnv()
this.prefix = 'ratelimit'
}
private getIP(): string {
const headersList = headers()
const forwardedFor = headersList.get('x-forwarded-for')
const realIP = headersList.get('x-real-ip')
const ip = forwardedFor?.split(',')[0] || realIP || 'unknown'
return ip
}
private getKey(identifier: string): string {
return `${this.prefix}:${identifier}`
}
async check(config: RateLimitConfig): Promise<RateLimitResult> {
const ip = this.getIP()
const key = this.getKey(ip)
const now = Math.floor(Date.now() / 1000)
const windowStart = now - (now % config.interval)
const windowKey = `${key}:${windowStart}`
const pipeline = this.redis.pipeline()
pipeline.incr(windowKey)
pipeline.expire(windowKey, config.interval)
const [count] = await pipeline.exec()
const remaining = Math.max(0, config.limit - (count as number))
return {
success: (count as number) <= config.limit,
limit: config.limit,
remaining,
reset: windowStart + config.interval
}
}
}
// Default configuration
const DEFAULT_CONFIG: RateLimitConfig = {
interval: 60 * 60, // 1 hour
limit: 5 // 5 requests per hour
}
// Helper function for easy usage
export async function rateLimit(
config: RateLimitConfig = DEFAULT_CONFIG
): Promise<RateLimitResult> {
const limiter = new RateLimit()
return limiter.check(config)
}

42
src/middleware.ts Normal file
View file

@ -0,0 +1,42 @@
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Generate nonce using Web Crypto API
const nonce = Buffer.from(
crypto.getRandomValues(new Uint8Array(16))
).toString('base64')
// Simplified CSP Header without SSL requirements
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
`
const response = NextResponse.next()
// Security Headers without SSL-specific directives
response.headers.set('Content-Security-Policy', cspHeader.replace(/\s{2,}/g, ' ').trim())
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('X-DNS-Prefetch-Control', 'on')
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
return response
}
export const config = {
matcher: [
'/api/contact',
'/((?!_next/static|_next/image|favicon.ico).*)',
]
}

60
src/styles/globals.css Normal file
View file

@ -0,0 +1,60 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--mcdi-maroon: #800020;
}
html {
scroll-behavior: smooth;
}
body {
@apply antialiased text-gray-900;
}
.prose {
@apply max-w-none;
}
.prose a {
@apply text-blue-600 no-underline hover:text-blue-700 transition-colors;
}
.prose h1, .prose h2, .prose h3, .prose h4 {
@apply text-gray-900 font-bold;
}
.prose p {
@apply text-gray-700;
}
.prose ul {
@apply list-disc list-inside;
}
.prose ol {
@apply list-decimal list-inside;
}
.prose blockquote {
@apply border-l-4 border-blue-600 pl-4 italic;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
@apply bg-gray-100;
}
::-webkit-scrollbar-thumb {
@apply bg-blue-600 rounded-full;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-blue-700;
}

31
tailwind.config.js Normal file
View file

@ -0,0 +1,31 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
'mcdi-maroon': 'var(--mcdi-maroon)',
},
fontSize: {
'mega': ['120px', {
lineHeight: '1',
letterSpacing: '-0.02em',
fontWeight: '700',
}],
},
screens: {
'xs': '375px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
},
},
},
plugins: [],
}

38
tsconfig.json Normal file
View file

@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}, "include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}