Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- app/globals.css +132 -0
- app/layout.tsx +27 -0
- app/page.tsx +253 -0
app/globals.css
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
@layer base {
|
6 |
+
:root {
|
7 |
+
--background: 0 0% 100%;
|
8 |
+
--foreground: 240 10% 3.9%;
|
9 |
+
--card: 0 0% 100%;
|
10 |
+
--card-foreground: 240 10% 3.9%;
|
11 |
+
--popover: 0 0% 100%;
|
12 |
+
--popover-foreground: 240 10% 3.9%;
|
13 |
+
--primary: 329 100% 61%;
|
14 |
+
--primary-foreground: 0 0% 98%;
|
15 |
+
--secondary: 240 4.8% 95.9%;
|
16 |
+
--secondary-foreground: 240 5.9% 10%;
|
17 |
+
--muted: 240 4.8% 95.9%;
|
18 |
+
--muted-foreground: 240 3.8% 46.1%;
|
19 |
+
--accent: 240 4.8% 95.9%;
|
20 |
+
--accent-foreground: 240 5.9% 10%;
|
21 |
+
--destructive: 0 84.2% 60.2%;
|
22 |
+
--destructive-foreground: 0 0% 98%;
|
23 |
+
--border: 240 5.9% 90%;
|
24 |
+
--input: 240 5.9% 90%;
|
25 |
+
--ring: 329 100% 61%;
|
26 |
+
--radius: 0.5rem;
|
27 |
+
}
|
28 |
+
|
29 |
+
.dark {
|
30 |
+
--background: 240 10% 3.9%;
|
31 |
+
--foreground: 0 0% 98%;
|
32 |
+
--card: 240 10% 3.9%;
|
33 |
+
--card-foreground: 0 0% 98%;
|
34 |
+
--popover: 240 10% 3.9%;
|
35 |
+
--popover-foreground: 0 0% 98%;
|
36 |
+
--primary: 329 100% 61%;
|
37 |
+
--primary-foreground: 0 0% 98%;
|
38 |
+
--secondary: 240 3.7% 15.9%;
|
39 |
+
--secondary-foreground: 0 0% 98%;
|
40 |
+
--muted: 240 3.7% 15.9%;
|
41 |
+
--muted-foreground: 240 5% 64.9%;
|
42 |
+
--accent: 240 3.7% 15.9%;
|
43 |
+
--accent-foreground: 0 0% 98%;
|
44 |
+
--destructive: 0 62.8% 30.6%;
|
45 |
+
--destructive-foreground: 0 0% 98%;
|
46 |
+
--border: 240 3.7% 15.9%;
|
47 |
+
--input: 240 3.7% 15.9%;
|
48 |
+
--ring: 329 100% 61%;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
@layer base {
|
53 |
+
* {
|
54 |
+
@apply border-border;
|
55 |
+
}
|
56 |
+
body {
|
57 |
+
@apply bg-background text-foreground;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
.text-gradient {
|
62 |
+
-webkit-background-clip: text;
|
63 |
+
-webkit-text-fill-color: transparent;
|
64 |
+
}
|
65 |
+
|
66 |
+
.scrollbar-hide {
|
67 |
+
-ms-overflow-style: none;
|
68 |
+
scrollbar-width: none;
|
69 |
+
}
|
70 |
+
.scrollbar-hide::-webkit-scrollbar {
|
71 |
+
display: none;
|
72 |
+
}
|
73 |
+
|
74 |
+
/* Custom Scrollbar Styles */
|
75 |
+
.scrollbar-thin {
|
76 |
+
scrollbar-width: thin;
|
77 |
+
}
|
78 |
+
|
79 |
+
.scrollbar-thumb-gray-600::-webkit-scrollbar-thumb {
|
80 |
+
background-color: #4b5563;
|
81 |
+
border-radius: 0.375rem;
|
82 |
+
}
|
83 |
+
|
84 |
+
.scrollbar-thumb-gray-500::-webkit-scrollbar-thumb {
|
85 |
+
background-color: #6b7280;
|
86 |
+
border-radius: 0.375rem;
|
87 |
+
}
|
88 |
+
|
89 |
+
.scrollbar-track-gray-800::-webkit-scrollbar-track {
|
90 |
+
background-color: #1f2937;
|
91 |
+
border-radius: 0.375rem;
|
92 |
+
}
|
93 |
+
|
94 |
+
.scrollbar-track-gray-700::-webkit-scrollbar-track {
|
95 |
+
background-color: #374151;
|
96 |
+
border-radius: 0.375rem;
|
97 |
+
}
|
98 |
+
|
99 |
+
.scrollbar-thin::-webkit-scrollbar {
|
100 |
+
width: 6px;
|
101 |
+
height: 6px;
|
102 |
+
}
|
103 |
+
|
104 |
+
.scrollbar-thin::-webkit-scrollbar-thumb {
|
105 |
+
background-color: #4b5563;
|
106 |
+
border-radius: 0.375rem;
|
107 |
+
}
|
108 |
+
|
109 |
+
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
110 |
+
background-color: #6b7280;
|
111 |
+
}
|
112 |
+
|
113 |
+
.scrollbar-thin::-webkit-scrollbar-track {
|
114 |
+
background-color: #1f2937;
|
115 |
+
border-radius: 0.375rem;
|
116 |
+
}
|
117 |
+
|
118 |
+
/* Smooth scrolling */
|
119 |
+
.scrollbar-thin {
|
120 |
+
scroll-behavior: smooth;
|
121 |
+
}
|
122 |
+
|
123 |
+
/* Hide scrollbar for specific elements but keep functionality */
|
124 |
+
.scrollbar-hide-but-functional {
|
125 |
+
-ms-overflow-style: none;
|
126 |
+
scrollbar-width: none;
|
127 |
+
overflow: auto;
|
128 |
+
}
|
129 |
+
|
130 |
+
.scrollbar-hide-but-functional::-webkit-scrollbar {
|
131 |
+
display: none;
|
132 |
+
}
|
app/layout.tsx
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type React from "react"
|
2 |
+
import type { Metadata } from "next"
|
3 |
+
import { Inter } from "next/font/google"
|
4 |
+
import "./globals.css"
|
5 |
+
import AuthGuard from "@/components/auth-guard"
|
6 |
+
|
7 |
+
const inter = Inter({ subsets: ["latin"] })
|
8 |
+
|
9 |
+
export const metadata: Metadata = {
|
10 |
+
title: "MoodVibe - AI-Powered Emotion Detection",
|
11 |
+
description: "Discover your emotions through AI-powered image analysis and get personalized music recommendations",
|
12 |
+
generator: 'v0.dev'
|
13 |
+
}
|
14 |
+
|
15 |
+
export default function RootLayout({
|
16 |
+
children,
|
17 |
+
}: {
|
18 |
+
children: React.ReactNode
|
19 |
+
}) {
|
20 |
+
return (
|
21 |
+
<html lang="en">
|
22 |
+
<body className={inter.className}>
|
23 |
+
<AuthGuard>{children}</AuthGuard>
|
24 |
+
</body>
|
25 |
+
</html>
|
26 |
+
)
|
27 |
+
}
|
app/page.tsx
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState, useEffect } from "react"
|
4 |
+
import StoryInterface from "@/components/story-interface"
|
5 |
+
import UserAccountDropdown from "@/components/user-account-dropdown"
|
6 |
+
import { Button } from "@/components/ui/button"
|
7 |
+
import { LogIn } from "lucide-react"
|
8 |
+
import Link from "next/link"
|
9 |
+
|
10 |
+
interface User {
|
11 |
+
id: string
|
12 |
+
name: string
|
13 |
+
email: string
|
14 |
+
createdAt: string
|
15 |
+
emailNotifications: boolean
|
16 |
+
weeklyRecap: boolean
|
17 |
+
dailyMoodCheck: boolean
|
18 |
+
reminderTime: string
|
19 |
+
profilePicture?: string
|
20 |
+
}
|
21 |
+
|
22 |
+
export default function Home() {
|
23 |
+
const [currentUser, setCurrentUser] = useState<User | null>(null)
|
24 |
+
const [showAccountDropdown, setShowAccountDropdown] = useState(false)
|
25 |
+
|
26 |
+
useEffect(() => {
|
27 |
+
// Check for user data in URL params (from OAuth callback)
|
28 |
+
const urlParams = new URLSearchParams(window.location.search)
|
29 |
+
const userDataParam = urlParams.get("userData")
|
30 |
+
|
31 |
+
if (userDataParam) {
|
32 |
+
try {
|
33 |
+
const userData = JSON.parse(decodeURIComponent(userDataParam))
|
34 |
+
localStorage.setItem("user", JSON.stringify(userData))
|
35 |
+
setCurrentUser(userData)
|
36 |
+
|
37 |
+
// Clean up URL
|
38 |
+
window.history.replaceState({}, document.title, window.location.pathname)
|
39 |
+
} catch (error) {
|
40 |
+
console.error("Error parsing user data from URL:", error)
|
41 |
+
}
|
42 |
+
} else {
|
43 |
+
// Check localStorage for existing user
|
44 |
+
try {
|
45 |
+
const savedUser = localStorage.getItem("user")
|
46 |
+
if (savedUser && savedUser !== "undefined") {
|
47 |
+
const userData = JSON.parse(savedUser)
|
48 |
+
setCurrentUser(userData)
|
49 |
+
}
|
50 |
+
} catch (error) {
|
51 |
+
console.error("Error loading user from localStorage:", error)
|
52 |
+
}
|
53 |
+
}
|
54 |
+
}, [])
|
55 |
+
|
56 |
+
const getInitials = (name: string) => {
|
57 |
+
return name
|
58 |
+
.split(" ")
|
59 |
+
.map((n) => n[0])
|
60 |
+
.join("")
|
61 |
+
.toUpperCase()
|
62 |
+
.slice(0, 2)
|
63 |
+
}
|
64 |
+
|
65 |
+
const getAvatarColor = (email: string) => {
|
66 |
+
const colors = [
|
67 |
+
"bg-red-500",
|
68 |
+
"bg-blue-500",
|
69 |
+
"bg-green-500",
|
70 |
+
"bg-purple-500",
|
71 |
+
"bg-pink-500",
|
72 |
+
"bg-indigo-500",
|
73 |
+
"bg-yellow-500",
|
74 |
+
"bg-teal-500",
|
75 |
+
]
|
76 |
+
const hash = email.split("").reduce((a, b) => {
|
77 |
+
a = (a << 5) - a + b.charCodeAt(0)
|
78 |
+
return a & a
|
79 |
+
}, 0)
|
80 |
+
return colors[Math.abs(hash) % colors.length]
|
81 |
+
}
|
82 |
+
|
83 |
+
const renderUserAvatar = () => {
|
84 |
+
if (!currentUser) return null
|
85 |
+
|
86 |
+
if (currentUser.profilePicture) {
|
87 |
+
return (
|
88 |
+
<img
|
89 |
+
src={currentUser.profilePicture || "/placeholder.svg"}
|
90 |
+
alt={currentUser.name}
|
91 |
+
className="w-8 h-8 rounded-full object-cover"
|
92 |
+
/>
|
93 |
+
)
|
94 |
+
}
|
95 |
+
|
96 |
+
return (
|
97 |
+
<div
|
98 |
+
className={`w-8 h-8 rounded-full ${getAvatarColor(currentUser.email)} flex items-center justify-center text-white text-sm font-bold`}
|
99 |
+
>
|
100 |
+
{getInitials(currentUser.name)}
|
101 |
+
</div>
|
102 |
+
)
|
103 |
+
}
|
104 |
+
|
105 |
+
const handleUserUpdate = (updatedUser: User) => {
|
106 |
+
setCurrentUser(updatedUser)
|
107 |
+
}
|
108 |
+
|
109 |
+
return (
|
110 |
+
<main className="min-h-screen bg-gradient-to-br from-purple-900 via-black to-pink-900 text-white">
|
111 |
+
{/* Animated background elements */}
|
112 |
+
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
113 |
+
<div className="absolute top-20 left-10 w-72 h-72 bg-pink-500/20 rounded-full blur-3xl animate-pulse"></div>
|
114 |
+
<div className="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
115 |
+
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-blue-500/10 rounded-full blur-3xl animate-pulse delay-500"></div>
|
116 |
+
</div>
|
117 |
+
|
118 |
+
{/* Grid pattern overlay */}
|
119 |
+
<div
|
120 |
+
className="fixed inset-0 opacity-30 pointer-events-none"
|
121 |
+
style={{
|
122 |
+
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fillRule='evenodd'%3E%3Cg fill='%23ffffff' fillOpacity='0.03'%3E%3Ccircle cx='30' cy='30' r='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
|
123 |
+
}}
|
124 |
+
></div>
|
125 |
+
|
126 |
+
{/* Main content container */}
|
127 |
+
<div className="relative z-10 min-h-screen flex flex-col">
|
128 |
+
{/* Header with User Account or Sign In/Up buttons */}
|
129 |
+
<div className="absolute top-4 right-4 z-30">
|
130 |
+
{currentUser ? (
|
131 |
+
// User Account Button
|
132 |
+
<button
|
133 |
+
onClick={() => setShowAccountDropdown(true)}
|
134 |
+
className="flex items-center gap-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-full px-4 py-2 hover:bg-white/20 transition-all duration-200"
|
135 |
+
>
|
136 |
+
{renderUserAvatar()}
|
137 |
+
<span className="text-white text-sm font-medium hidden sm:block">{currentUser.name.split(" ")[0]}</span>
|
138 |
+
</button>
|
139 |
+
) : (
|
140 |
+
// Sign In/Up Buttons
|
141 |
+
<div className="flex gap-3">
|
142 |
+
<Link href="/auth/login">
|
143 |
+
<Button
|
144 |
+
variant="outline"
|
145 |
+
className="bg-white/10 backdrop-blur-sm border-white/20 text-white hover:bg-white/20 transition-all duration-200"
|
146 |
+
>
|
147 |
+
<LogIn className="h-4 w-4 mr-2" />
|
148 |
+
Sign In
|
149 |
+
</Button>
|
150 |
+
</Link>
|
151 |
+
<Link href="/auth/signup">
|
152 |
+
<Button className="bg-gradient-to-r from-pink-500 to-purple-500 hover:from-pink-600 hover:to-purple-600 text-white border-0 transition-all duration-200">
|
153 |
+
Sign Up
|
154 |
+
</Button>
|
155 |
+
</Link>
|
156 |
+
</div>
|
157 |
+
)}
|
158 |
+
</div>
|
159 |
+
|
160 |
+
{/* User Account Dropdown */}
|
161 |
+
<UserAccountDropdown
|
162 |
+
isOpen={showAccountDropdown}
|
163 |
+
onClose={() => setShowAccountDropdown(false)}
|
164 |
+
currentUser={currentUser}
|
165 |
+
onUserUpdate={handleUserUpdate}
|
166 |
+
/>
|
167 |
+
|
168 |
+
{/* Hero section */}
|
169 |
+
<div className="flex-1 flex flex-col items-center justify-center p-4 pb-8">
|
170 |
+
<div className="text-center mb-8 max-w-4xl mx-auto">
|
171 |
+
<div className="mb-6">
|
172 |
+
<div className="inline-flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-full px-6 py-3 mb-6">
|
173 |
+
<span className="text-2xl">π</span>
|
174 |
+
<span className="text-sm font-medium text-gray-300">AI-Powered Emotion Detection</span>
|
175 |
+
</div>
|
176 |
+
</div>
|
177 |
+
|
178 |
+
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold mb-6 bg-gradient-to-r from-pink-400 via-purple-400 to-blue-400 bg-clip-text text-transparent leading-tight">
|
179 |
+
Mood & Vibe
|
180 |
+
<br />
|
181 |
+
<span className="text-3xl md:text-5xl lg:text-6xl">Detection</span>
|
182 |
+
</h1>
|
183 |
+
|
184 |
+
<p className="text-lg md:text-xl text-gray-300 mb-8 max-w-3xl mx-auto leading-relaxed">
|
185 |
+
Discover your emotions through AI-powered image analysis and get
|
186 |
+
<span className="text-pink-400 font-semibold"> personalized music recommendations </span>
|
187 |
+
that perfectly match your mood
|
188 |
+
</p>
|
189 |
+
</div>
|
190 |
+
|
191 |
+
{/* App interface - Main Interactive Component */}
|
192 |
+
<div className="relative w-full max-w-lg mx-auto">
|
193 |
+
{/* Glow effect around the app */}
|
194 |
+
<div className="absolute -inset-4 bg-gradient-to-r from-pink-500/30 via-purple-500/30 to-blue-500/30 rounded-3xl blur-2xl"></div>
|
195 |
+
|
196 |
+
{/* App container with proper z-index */}
|
197 |
+
<div className="relative z-20">
|
198 |
+
<StoryInterface />
|
199 |
+
</div>
|
200 |
+
</div>
|
201 |
+
|
202 |
+
{/* Features preview - Moved below the app */}
|
203 |
+
<div className="grid md:grid-cols-3 gap-4 mt-8 max-w-3xl mx-auto">
|
204 |
+
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
|
205 |
+
<div className="text-2xl mb-2">π§ </div>
|
206 |
+
<h3 className="text-sm font-semibold mb-1">AI Detection</h3>
|
207 |
+
<p className="text-xs text-gray-400">Advanced emotion analysis</p>
|
208 |
+
</div>
|
209 |
+
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
|
210 |
+
<div className="text-2xl mb-2">π΅</div>
|
211 |
+
<h3 className="text-sm font-semibold mb-1">Smart Music</h3>
|
212 |
+
<p className="text-xs text-gray-400">Personalized recommendations</p>
|
213 |
+
</div>
|
214 |
+
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
|
215 |
+
<div className="text-2xl mb-2">β</div>
|
216 |
+
<h3 className="text-sm font-semibold mb-1">Story Highlights</h3>
|
217 |
+
<p className="text-xs text-gray-400">Save favorite moments</p>
|
218 |
+
</div>
|
219 |
+
</div>
|
220 |
+
|
221 |
+
{/* Bottom info */}
|
222 |
+
<div className="text-center mt-8 max-w-2xl mx-auto">
|
223 |
+
<p className="text-gray-400 mb-4 text-sm">Join millions discovering their emotions through AI</p>
|
224 |
+
<div className="flex flex-wrap items-center justify-center gap-4 text-xs">
|
225 |
+
<div className="flex items-center gap-2">
|
226 |
+
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
227 |
+
<span className="text-gray-400">Real-time Processing</span>
|
228 |
+
</div>
|
229 |
+
<div className="flex items-center gap-2">
|
230 |
+
<span className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></span>
|
231 |
+
<span className="text-gray-400">Privacy Protected</span>
|
232 |
+
</div>
|
233 |
+
<div className="flex items-center gap-2">
|
234 |
+
<span className="w-2 h-2 bg-purple-500 rounded-full animate-pulse"></span>
|
235 |
+
<span className="text-gray-400">Free to Use</span>
|
236 |
+
</div>
|
237 |
+
</div>
|
238 |
+
</div>
|
239 |
+
</div>
|
240 |
+
</div>
|
241 |
+
|
242 |
+
{/* Floating elements */}
|
243 |
+
<div className="fixed top-20 right-20 text-3xl animate-bounce delay-1000 opacity-20 pointer-events-none">π</div>
|
244 |
+
<div className="fixed bottom-32 left-20 text-2xl animate-bounce delay-2000 opacity-20 pointer-events-none">
|
245 |
+
π΅
|
246 |
+
</div>
|
247 |
+
<div className="fixed top-1/3 right-10 text-xl animate-bounce delay-3000 opacity-20 pointer-events-none">β¨</div>
|
248 |
+
<div className="fixed bottom-20 right-1/3 text-2xl animate-bounce delay-4000 opacity-20 pointer-events-none">
|
249 |
+
π
|
250 |
+
</div>
|
251 |
+
</main>
|
252 |
+
)
|
253 |
+
}
|