Initialized repository for chat Podcast website
Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
|
|
@ -0,0 +1,27 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Podcast website
|
||||
|
||||
*Automatically synced with your [v0.app](https://v0.app) deployments*
|
||||
|
||||
[](https://vercel.com/jeff-emmetts-projects/v0-podcast-website)
|
||||
[](https://v0.app/chat/ruIpGcNLead)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository will stay in sync with your deployed chats on [v0.app](https://v0.app).
|
||||
Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app).
|
||||
|
||||
## Deployment
|
||||
|
||||
Your project is live at:
|
||||
|
||||
**[https://vercel.com/jeff-emmetts-projects/v0-podcast-website](https://vercel.com/jeff-emmetts-projects/v0-podcast-website)**
|
||||
|
||||
## Build your app
|
||||
|
||||
Continue building your app on:
|
||||
|
||||
**[https://v0.app/chat/ruIpGcNLead](https://v0.app/chat/ruIpGcNLead)**
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Create and modify your project using [v0.app](https://v0.app)
|
||||
2. Deploy your chats from the v0 interface
|
||||
3. Changes are automatically pushed to this repository
|
||||
4. Vercel deploys the latest version from this repository
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
import { Navigation } from "@/components/navigation"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Music, Instagram, Sprout, BookOpen, Mic, Heart } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export const metadata = {
|
||||
title: "About | Soul Speaks Soil",
|
||||
description: "Learn about Sammy Davis and the Soul Speaks Soil podcast",
|
||||
}
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Navigation />
|
||||
|
||||
<main className="flex-1 pt-24 pb-16">
|
||||
{/* About Sammy Section */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-16 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<div className="aspect-square rounded-xl overflow-hidden bg-primary/5 shadow-lg">
|
||||
<img
|
||||
src="/sammy-davis-in-garden-with-plants-and-soil.jpg"
|
||||
alt="Sammy Davis in her garden"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-foreground mb-6 text-balance">Meet Sammy Davis</h1>
|
||||
<div className="space-y-4 text-muted-foreground leading-relaxed">
|
||||
<p>
|
||||
Sammy Davis is a passionate advocate for sustainable living, regenerative agriculture, and the
|
||||
profound connection between human consciousness and the natural world.
|
||||
</p>
|
||||
<p>
|
||||
With a background in environmental science and a deep love for storytelling, Sammy created Soul
|
||||
Speaks Soil to bridge the gap between ancient wisdom and modern sustainability practices.
|
||||
</p>
|
||||
<p>
|
||||
Through authentic conversations with farmers, activists, artists, and thought leaders, Sammy
|
||||
explores how we can cultivate a more harmonious relationship with the earth and each other.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-4 mt-8">
|
||||
<Button asChild variant="outline">
|
||||
<a href="https://instagram.com/soulspeakssoil" target="_blank" rel="noopener noreferrer">
|
||||
<Instagram className="mr-2 h-4 w-4" />
|
||||
Follow Sammy
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link href="/contact">Get in Touch</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About the Podcast Section */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-16 bg-muted/30 organic-texture">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-4">
|
||||
<Sprout className="h-4 w-4" />
|
||||
<span>The Podcast</span>
|
||||
</div>
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-foreground mb-4 text-balance">
|
||||
About Soul Speaks Soil
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed text-pretty">
|
||||
A podcast exploring the intersection of consciousness, sustainability, and our relationship with the
|
||||
earth.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-12">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Mic className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Deep Conversations</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Hour-long episodes diving deep into topics that matter for our planet and our souls.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<BookOpen className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Diverse Topics</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
From regenerative farming to mindfulness, permaculture to social justice.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Heart className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Community Focused</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Building a community of listeners committed to positive change.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="bg-primary text-primary-foreground shadow-lg">
|
||||
<CardContent className="p-8">
|
||||
<h3 className="text-2xl font-bold mb-4 text-balance">Our Mission</h3>
|
||||
<p className="text-lg leading-relaxed opacity-90 mb-6 text-pretty">
|
||||
Soul Speaks Soil exists to inspire and empower individuals to reconnect with the earth, cultivate
|
||||
sustainable practices, and foster a deeper understanding of our interconnectedness with all living
|
||||
things. Through storytelling and authentic dialogue, we aim to plant seeds of change that grow into
|
||||
a more regenerative future.
|
||||
</p>
|
||||
<Button asChild variant="secondary" size="lg">
|
||||
<a href="https://open.spotify.com/show/soul-speaks-soil" target="_blank" rel="noopener noreferrer">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Listen Now
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Consulting Section */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-16 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-foreground mb-4 text-balance">Work with Sammy</h2>
|
||||
<p className="text-lg text-muted-foreground mb-8 leading-relaxed text-pretty">
|
||||
Sammy is available for consulting, speaking engagements, and podcast collaborations. Whether you're
|
||||
looking to start your own sustainability initiative or want to feature Sammy on your podcast, let's
|
||||
connect.
|
||||
</p>
|
||||
<Button asChild size="lg">
|
||||
<Link href="/contact">Get in Touch</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import { Navigation } from "@/components/navigation"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { ContactForm } from "@/components/contact-form"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Mail, Instagram, Music, MessageSquare } from "lucide-react"
|
||||
|
||||
export const metadata = {
|
||||
title: "Contact | Soul Speaks Soil",
|
||||
description: "Get in touch with Sammy Davis for consulting, podcast collaborations, and more",
|
||||
}
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Navigation />
|
||||
|
||||
<main className="flex-1 pt-24 pb-16">
|
||||
{/* Header Section with organic texture */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-12 bg-muted/30 organic-texture">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-foreground mb-4 text-balance">Get in Touch</h1>
|
||||
<p className="text-lg text-muted-foreground leading-relaxed text-pretty">
|
||||
Whether you're interested in consulting, podcast collaborations, speaking engagements, or just want to
|
||||
say hello, I'd love to hear from you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact Options with mycelial pattern */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-12 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-12">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<MessageSquare className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Consulting</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Sustainability strategy, regenerative practices, and organizational transformation
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Music className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Podcast Guest</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Invite Sammy to be a guest on your podcast or collaborate on content
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Mail className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Speaking</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Book Sammy for conferences, workshops, and speaking engagements
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact Form Section */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<Card className="shadow-lg">
|
||||
<CardContent className="pt-6">
|
||||
<ContactForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Social Links with organic texture background */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-12 bg-muted/30 organic-texture">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<h2 className="text-2xl font-bold text-foreground mb-4">Connect on Social Media</h2>
|
||||
<p className="text-muted-foreground mb-6 leading-relaxed">
|
||||
Follow Soul Speaks Soil for updates, behind-the-scenes content, and community discussions.
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<a
|
||||
href="https://instagram.com/soulspeakssoil"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors shadow-md"
|
||||
>
|
||||
<Instagram className="h-5 w-5" />
|
||||
<span className="font-medium">Instagram</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://open.spotify.com/show/soul-speaks-soil"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/90 transition-colors shadow-md"
|
||||
>
|
||||
<Music className="h-5 w-5" />
|
||||
<span className="font-medium">Spotify</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import { Navigation } from "@/components/navigation"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Music, Calendar, Clock } from "lucide-react"
|
||||
|
||||
export const metadata = {
|
||||
title: "Episodes | Soul Speaks Soil",
|
||||
description: "Browse all episodes of the Soul Speaks Soil podcast",
|
||||
}
|
||||
|
||||
// Sample episode data - this would typically come from a CMS or API
|
||||
const episodes = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Regenerative Agriculture with Maria Santos",
|
||||
guest: "Maria Santos",
|
||||
description:
|
||||
"Maria Santos, a pioneer in regenerative farming, shares her journey from conventional agriculture to building a thriving regenerative farm. We discuss soil health, biodiversity, and how farming can heal both the land and our communities.",
|
||||
date: "January 15, 2025",
|
||||
duration: "58 min",
|
||||
season: 1,
|
||||
episode: 12,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Mindfulness and Nature Connection with Dr. James Chen",
|
||||
guest: "Dr. James Chen",
|
||||
description:
|
||||
"Dr. James Chen, a psychologist and nature therapy practitioner, explores the profound mental health benefits of connecting with nature. We dive into forest bathing, ecotherapy, and how spending time in nature can transform our wellbeing.",
|
||||
date: "January 8, 2025",
|
||||
duration: "62 min",
|
||||
season: 1,
|
||||
episode: 11,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Indigenous Wisdom and Land Stewardship with Sarah Blackfeather",
|
||||
guest: "Sarah Blackfeather",
|
||||
description:
|
||||
"Sarah Blackfeather shares indigenous perspectives on land stewardship and the sacred relationship between people and place. We discuss traditional ecological knowledge and what modern society can learn from indigenous practices.",
|
||||
date: "January 1, 2025",
|
||||
duration: "55 min",
|
||||
season: 1,
|
||||
episode: 10,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Urban Farming and Food Justice with Marcus Johnson",
|
||||
guest: "Marcus Johnson",
|
||||
description:
|
||||
"Marcus Johnson, founder of an urban farming collective, talks about bringing fresh food to food deserts and building community through agriculture. We explore the intersection of food justice, urban planning, and grassroots activism.",
|
||||
date: "December 25, 2024",
|
||||
duration: "51 min",
|
||||
season: 1,
|
||||
episode: 9,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Permaculture Design Principles with Elena Rodriguez",
|
||||
guest: "Elena Rodriguez",
|
||||
description:
|
||||
"Elena Rodriguez, a permaculture designer and educator, breaks down the core principles of permaculture and how they can be applied to gardens, farms, and even our daily lives. We discuss working with nature rather than against it.",
|
||||
date: "December 18, 2024",
|
||||
duration: "64 min",
|
||||
season: 1,
|
||||
episode: 8,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "Climate Action and Hope with Dr. Aisha Patel",
|
||||
guest: "Dr. Aisha Patel",
|
||||
description:
|
||||
"Climate scientist Dr. Aisha Patel shares the latest research on climate change while maintaining a message of hope and actionable solutions. We discuss individual and collective actions that can make a real difference.",
|
||||
date: "December 11, 2024",
|
||||
duration: "59 min",
|
||||
season: 1,
|
||||
episode: 7,
|
||||
},
|
||||
]
|
||||
|
||||
export default function EpisodesPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Navigation />
|
||||
|
||||
<main className="flex-1 pt-24 pb-16">
|
||||
{/* Header Section with organic texture */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-12 bg-muted/30 organic-texture">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-foreground mb-4 text-balance">All Episodes</h1>
|
||||
<p className="text-lg text-muted-foreground mb-8 leading-relaxed text-pretty">
|
||||
Explore our collection of conversations with farmers, activists, artists, and thought leaders who are
|
||||
working to create a more sustainable and connected world.
|
||||
</p>
|
||||
<Button asChild size="lg">
|
||||
<a href="https://open.spotify.com/show/soul-speaks-soil" target="_blank" rel="noopener noreferrer">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Listen on Spotify
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Episodes List with mycelial pattern */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-16 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{episodes.map((episode) => (
|
||||
<Card key={episode.id} className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-2">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge variant="secondary">
|
||||
S{episode.season}E{episode.episode}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-2xl mb-2 text-balance">{episode.title}</CardTitle>
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>{episode.date}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>{episode.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground leading-relaxed mb-4">{episode.description}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground">Guest:</span>
|
||||
<span className="text-sm text-primary font-medium">{episode.guest}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section with mycelial pattern overlay */}
|
||||
<section className="px-4 sm:px-6 lg:px-8 py-16 bg-primary text-primary-foreground relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle at 30% 50%, currentColor 1px, transparent 1px),
|
||||
radial-gradient(circle at 70% 30%, currentColor 1px, transparent 1px),
|
||||
radial-gradient(circle at 50% 80%, currentColor 1px, transparent 1px)`,
|
||||
backgroundSize: "100px 100px, 150px 150px, 120px 120px",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="container mx-auto relative z-10">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-balance">Never Miss an Episode</h2>
|
||||
<p className="text-lg mb-8 opacity-90 leading-relaxed text-pretty">
|
||||
Subscribe on Spotify to get notified when new episodes drop. Join our growing community of listeners
|
||||
committed to positive change.
|
||||
</p>
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<a href="https://open.spotify.com/show/soul-speaks-soil" target="_blank" rel="noopener noreferrer">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Subscribe on Spotify
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
/* Deep earthy palette with rich browns, forest greens, and organic tones */
|
||||
--background: oklch(0.97 0.015 75);
|
||||
--foreground: oklch(0.22 0.025 65);
|
||||
--card: oklch(0.98 0.01 80);
|
||||
--card-foreground: oklch(0.22 0.025 65);
|
||||
--popover: oklch(0.98 0.01 80);
|
||||
--popover-foreground: oklch(0.22 0.025 65);
|
||||
--primary: oklch(0.38 0.06 135);
|
||||
--primary-foreground: oklch(0.98 0.01 80);
|
||||
--secondary: oklch(0.42 0.08 55);
|
||||
--secondary-foreground: oklch(0.98 0.01 80);
|
||||
--muted: oklch(0.88 0.02 75);
|
||||
--muted-foreground: oklch(0.48 0.03 65);
|
||||
--accent: oklch(0.52 0.1 145);
|
||||
--accent-foreground: oklch(0.98 0.01 80);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.98 0.01 80);
|
||||
--border: oklch(0.82 0.02 75);
|
||||
--input: oklch(0.82 0.02 75);
|
||||
--ring: oklch(0.38 0.06 135);
|
||||
--chart-1: oklch(0.38 0.06 135);
|
||||
--chart-2: oklch(0.42 0.08 55);
|
||||
--chart-3: oklch(0.52 0.1 145);
|
||||
--chart-4: oklch(0.32 0.05 125);
|
||||
--chart-5: oklch(0.58 0.08 150);
|
||||
--radius: 0.75rem;
|
||||
--sidebar: oklch(0.98 0.01 80);
|
||||
--sidebar-foreground: oklch(0.22 0.025 65);
|
||||
--sidebar-primary: oklch(0.38 0.06 135);
|
||||
--sidebar-primary-foreground: oklch(0.98 0.01 80);
|
||||
--sidebar-accent: oklch(0.88 0.02 75);
|
||||
--sidebar-accent-foreground: oklch(0.22 0.025 65);
|
||||
--sidebar-border: oklch(0.82 0.02 75);
|
||||
--sidebar-ring: oklch(0.38 0.06 135);
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark mode with deep soil and forest tones */
|
||||
--background: oklch(0.15 0.02 65);
|
||||
--foreground: oklch(0.94 0.01 80);
|
||||
--card: oklch(0.18 0.025 65);
|
||||
--card-foreground: oklch(0.94 0.01 80);
|
||||
--popover: oklch(0.15 0.02 65);
|
||||
--popover-foreground: oklch(0.94 0.01 80);
|
||||
--primary: oklch(0.52 0.1 135);
|
||||
--primary-foreground: oklch(0.15 0.02 65);
|
||||
--secondary: oklch(0.48 0.09 55);
|
||||
--secondary-foreground: oklch(0.15 0.02 65);
|
||||
--muted: oklch(0.25 0.025 65);
|
||||
--muted-foreground: oklch(0.62 0.03 75);
|
||||
--accent: oklch(0.58 0.12 145);
|
||||
--accent-foreground: oklch(0.15 0.02 65);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.94 0.01 80);
|
||||
--border: oklch(0.25 0.025 65);
|
||||
--input: oklch(0.25 0.025 65);
|
||||
--ring: oklch(0.52 0.1 135);
|
||||
--chart-1: oklch(0.52 0.1 135);
|
||||
--chart-2: oklch(0.48 0.09 55);
|
||||
--chart-3: oklch(0.58 0.12 145);
|
||||
--chart-4: oklch(0.42 0.07 125);
|
||||
--chart-5: oklch(0.62 0.1 150);
|
||||
--sidebar: oklch(0.18 0.025 65);
|
||||
--sidebar-foreground: oklch(0.94 0.01 80);
|
||||
--sidebar-primary: oklch(0.52 0.1 135);
|
||||
--sidebar-primary-foreground: oklch(0.15 0.02 65);
|
||||
--sidebar-accent: oklch(0.25 0.025 65);
|
||||
--sidebar-accent-foreground: oklch(0.94 0.01 80);
|
||||
--sidebar-border: oklch(0.25 0.025 65);
|
||||
--sidebar-ring: oklch(0.52 0.1 135);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: "Geist", "Geist Fallback";
|
||||
--font-mono: "Geist Mono", "Geist Mono Fallback";
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adding mycelial network pattern overlay */
|
||||
@layer utilities {
|
||||
.mycelial-pattern {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mycelial-pattern::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: radial-gradient(circle at 20% 30%, oklch(0.38 0.06 135 / 0.03) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, oklch(0.42 0.08 55 / 0.03) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 80%, oklch(0.52 0.1 145 / 0.02) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 20%, oklch(0.38 0.06 135 / 0.02) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.organic-texture {
|
||||
background-image: repeating-linear-gradient(
|
||||
90deg,
|
||||
oklch(0.38 0.06 135 / 0.02) 0px,
|
||||
transparent 1px,
|
||||
transparent 40px
|
||||
), repeating-linear-gradient(0deg, oklch(0.42 0.08 55 / 0.02) 0px, transparent 1px, transparent 40px);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { Geist, Geist_Mono } from "next/font/google"
|
||||
import { Analytics } from "@vercel/analytics/next"
|
||||
import "./globals.css"
|
||||
|
||||
const _geist = Geist({ subsets: ["latin"] })
|
||||
const _geistMono = Geist_Mono({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Soul Speaks Soil | A Podcast by Sammy Davis",
|
||||
description: "Exploring the connection between soul and soil through meaningful conversations",
|
||||
generator: "v0.app",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`font-sans antialiased`}>
|
||||
{children}
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Navigation } from "@/components/navigation"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { Music, Instagram, Sprout, Heart, Users } from "lucide-react"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Navigation />
|
||||
|
||||
{/* Hero Section with mycelial pattern overlay */}
|
||||
<section className="pt-24 pb-16 px-4 sm:px-6 lg:px-8 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
|
||||
<Sprout className="h-4 w-4" />
|
||||
<span>Where Soul Meets Soil</span>
|
||||
</div>
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-foreground mb-6 text-balance">
|
||||
Soul Speaks Soil
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground mb-8 leading-relaxed text-pretty">
|
||||
Join host Sammy Davis for deep conversations exploring the profound connection between our inner world and
|
||||
the earth beneath our feet.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button asChild size="lg" className="text-lg">
|
||||
<a href="https://open.spotify.com/show/soul-speaks-soil" target="_blank" rel="noopener noreferrer">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Listen on Spotify
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="text-lg bg-transparent">
|
||||
<Link href="/episodes">Browse Episodes</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Image Section with soil and moss imagery */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-muted/30 organic-texture">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="aspect-video rounded-xl overflow-hidden bg-primary/5 shadow-lg">
|
||||
<img
|
||||
src="/close-up-of-rich-dark-soil-with-green-moss-and-f.jpg"
|
||||
alt="Rich soil with moss and ferns - Soul Speaks Soil Podcast"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About Preview Section with organic texture background */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 mycelial-pattern">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<Card className="border-2">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Heart className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Authentic Conversations</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Real, unfiltered discussions about sustainability, growth, and connection.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-2">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Users className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Diverse Guests</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Featuring farmers, activists, artists, and thought leaders.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-2">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Sprout className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Rooted in Nature</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Exploring our relationship with the earth and each other.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section with deeper earth tones */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-primary text-primary-foreground relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle at 30% 50%, currentColor 1px, transparent 1px),
|
||||
radial-gradient(circle at 70% 30%, currentColor 1px, transparent 1px),
|
||||
radial-gradient(circle at 50% 80%, currentColor 1px, transparent 1px)`,
|
||||
backgroundSize: "100px 100px, 150px 150px, 120px 120px",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="container mx-auto relative z-10">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-balance">Join the Conversation</h2>
|
||||
<p className="text-lg mb-8 opacity-90 leading-relaxed text-pretty">
|
||||
Follow us on Instagram for behind-the-scenes content, episode updates, and community discussions.
|
||||
</p>
|
||||
<Button asChild size="lg" variant="secondary" className="text-lg">
|
||||
<a href="https://instagram.com/soulspeakssoil" target="_blank" rel="noopener noreferrer">
|
||||
<Instagram className="mr-2 h-5 w-5" />
|
||||
Follow on Instagram
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Send } from "lucide-react"
|
||||
|
||||
export function ContactForm() {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [isSubmitted, setIsSubmitted] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate form submission
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||||
|
||||
setIsSubmitting(false)
|
||||
setIsSubmitted(true)
|
||||
|
||||
// Reset form after 3 seconds
|
||||
setTimeout(() => {
|
||||
setIsSubmitted(false)
|
||||
;(e.target as HTMLFormElement).reset()
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid sm:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name</Label>
|
||||
<Input id="firstName" name="firstName" placeholder="John" required disabled={isSubmitting} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Last Name</Label>
|
||||
<Input id="lastName" name="lastName" placeholder="Doe" required disabled={isSubmitting} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" name="email" type="email" placeholder="john@example.com" required disabled={isSubmitting} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="inquiryType">Inquiry Type</Label>
|
||||
<Select name="inquiryType" required disabled={isSubmitting}>
|
||||
<SelectTrigger id="inquiryType">
|
||||
<SelectValue placeholder="Select an option" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="consulting">Consulting</SelectItem>
|
||||
<SelectItem value="podcast-guest">Podcast Guest Appearance</SelectItem>
|
||||
<SelectItem value="speaking">Speaking Engagement</SelectItem>
|
||||
<SelectItem value="collaboration">Collaboration</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="message">Message</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
name="message"
|
||||
placeholder="Tell me about your inquiry..."
|
||||
rows={6}
|
||||
required
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" size="lg" className="w-full" disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
"Sending..."
|
||||
) : isSubmitted ? (
|
||||
"Message Sent!"
|
||||
) : (
|
||||
<>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Send Message
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{isSubmitted && (
|
||||
<p className="text-center text-sm text-primary">Thank you for reaching out! I'll get back to you soon.</p>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import Link from "next/link"
|
||||
import { Instagram, Music } from "lucide-react"
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="bg-muted/50 border-t border-border organic-texture">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-4">Soul Speaks Soil</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Exploring the connection between soul and soil through meaningful conversations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-4">Quick Links</h4>
|
||||
<div className="space-y-2">
|
||||
<Link href="/about" className="block text-muted-foreground hover:text-primary transition-colors">
|
||||
About
|
||||
</Link>
|
||||
<Link href="/episodes" className="block text-muted-foreground hover:text-primary transition-colors">
|
||||
Episodes
|
||||
</Link>
|
||||
<Link href="/contact" className="block text-muted-foreground hover:text-primary transition-colors">
|
||||
Contact
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-4">Connect</h4>
|
||||
<div className="flex gap-4">
|
||||
<a
|
||||
href="https://open.spotify.com/show/soul-speaks-soil"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary transition-colors"
|
||||
aria-label="Listen on Spotify"
|
||||
>
|
||||
<Music className="h-6 w-6" />
|
||||
</a>
|
||||
<a
|
||||
href="https://instagram.com/soulspeakssoil"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary transition-colors"
|
||||
aria-label="Follow on Instagram"
|
||||
>
|
||||
<Instagram className="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-border text-center text-sm text-muted-foreground">
|
||||
<p>© {new Date().getFullYear()} Soul Speaks Soil. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Menu, X } from "lucide-react"
|
||||
|
||||
export function Navigation() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-background/85 backdrop-blur-lg border-b border-border">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<Link href="/" className="text-xl font-bold text-foreground">
|
||||
Soul Speaks Soil
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
<Link href="/" className="text-foreground hover:text-primary transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<Link href="/about" className="text-foreground hover:text-primary transition-colors">
|
||||
About
|
||||
</Link>
|
||||
<Link href="/episodes" className="text-foreground hover:text-primary transition-colors">
|
||||
Episodes
|
||||
</Link>
|
||||
<Link href="/contact" className="text-foreground hover:text-primary transition-colors">
|
||||
Contact
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<Button variant="ghost" size="icon" className="md:hidden" onClick={() => setIsOpen(!isOpen)}>
|
||||
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{isOpen && (
|
||||
<div className="md:hidden py-4 space-y-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="block text-foreground hover:text-primary transition-colors"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="block text-foreground hover:text-primary transition-colors"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
<Link
|
||||
href="/episodes"
|
||||
className="block text-foreground hover:text-primary transition-colors"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Episodes
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="block text-foreground hover:text-primary transition-colors"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
ThemeProvider as NextThemesProvider,
|
||||
type ThemeProviderProps,
|
||||
} from 'next-themes'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'span'> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : 'span'
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost:
|
||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn('leading-none font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn('px-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Input }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = 'default',
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: 'sm' | 'default'
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position = 'popper',
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
data-slot="select-label"
|
||||
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Textarea }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"name": "my-v0-project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"lint": "eslint .",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@radix-ui/react-accordion": "1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||
"@radix-ui/react-aspect-ratio": "1.1.1",
|
||||
"@radix-ui/react-avatar": "1.1.2",
|
||||
"@radix-ui/react-checkbox": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-context-menu": "2.2.4",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||
"@radix-ui/react-hover-card": "1.1.4",
|
||||
"@radix-ui/react-label": "2.1.1",
|
||||
"@radix-ui/react-menubar": "1.1.4",
|
||||
"@radix-ui/react-navigation-menu": "1.2.3",
|
||||
"@radix-ui/react-popover": "1.1.4",
|
||||
"@radix-ui/react-progress": "1.1.1",
|
||||
"@radix-ui/react-radio-group": "1.2.2",
|
||||
"@radix-ui/react-scroll-area": "1.2.2",
|
||||
"@radix-ui/react-select": "2.1.4",
|
||||
"@radix-ui/react-separator": "1.1.1",
|
||||
"@radix-ui/react-slider": "1.2.2",
|
||||
"@radix-ui/react-slot": "1.1.1",
|
||||
"@radix-ui/react-switch": "1.1.2",
|
||||
"@radix-ui/react-tabs": "1.1.2",
|
||||
"@radix-ui/react-toast": "1.2.4",
|
||||
"@radix-ui/react-toggle": "1.1.1",
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@vercel/analytics": "latest",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"date-fns": "4.1.0",
|
||||
"embla-carousel-react": "8.5.1",
|
||||
"input-otp": "1.4.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "16.0.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "9.8.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^0.9.9",
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"tw-animate-css": "1.3.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 568 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
|
@ -0,0 +1,125 @@
|
|||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.439 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: 'Geist', 'Geist Fallback';
|
||||
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"target": "ES6",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||