let claude haiku 4.5 generate front page and login module (css crashed & login malfunctioning)

This commit is contained in:
2026-05-19 21:36:31 +08:00
parent 593e1cbaf0
commit ed0e1d0f69
14 changed files with 1879 additions and 118 deletions
+151
View File
@@ -0,0 +1,151 @@
"use client";
import { Check } from "lucide-react";
export default function BillingPage() {
const plans = [
{
name: "Free",
price: "$0",
description: "Perfect for getting started",
features: [
"10 messages/month",
"Access to featured characters",
"Basic support",
],
current: true,
},
{
name: "Pro",
price: "$9.99",
period: "/month",
description: "For serious enthusiasts",
features: [
"Unlimited messages",
"Priority character creation",
"Custom characters",
"Ad-free experience",
"Priority support",
],
current: false,
},
{
name: "Premium",
price: "$29.99",
period: "/month",
description: "Maximum experience",
features: [
"Everything in Pro",
"Advanced AI models",
"Character voice customization",
"Private character storage",
"White-label options",
"24/7 VIP support",
],
current: false,
},
];
return (
<div className="p-6 max-w-5xl mx-auto">
{/* Header */}
<div className="mb-12">
<h1 className="text-3xl font-bold mb-2">Billing & Plans</h1>
<p className="text-gray-600">Manage your subscription</p>
</div>
{/* Current Plan */}
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6 mb-12">
<p className="text-sm text-blue-600 font-semibold">CURRENT PLAN</p>
<h3 className="text-2xl font-bold mt-2">Free Plan</h3>
<p className="text-gray-600 mt-1">You're currently on the free plan</p>
</div>
{/* Plans Grid */}
<div className="grid md:grid-cols-3 gap-8">
{plans.map((plan, index) => (
<div
key={index}
className={`rounded-xl border-2 p-8 transition ${
plan.current
? "border-black bg-white"
: "border-gray-200 hover:border-black"
}`}
>
{/* Badge */}
{plan.current && (
<div className="inline-block bg-black text-white px-3 py-1 rounded-full text-xs font-semibold mb-4">
Current Plan
</div>
)}
{/* Plan Name */}
<h3 className="text-2xl font-bold mb-2">{plan.name}</h3>
<p className="text-gray-600 text-sm mb-6">{plan.description}</p>
{/* Price */}
<div className="mb-6">
<span className="text-4xl font-bold">{plan.price}</span>
{plan.period && <span className="text-gray-600">{plan.period}</span>}
</div>
{/* CTA */}
<button
className={`w-full py-3 rounded-lg font-medium mb-6 transition ${
plan.current
? "bg-gray-100 text-gray-600 cursor-default"
: "bg-black text-white hover:bg-gray-800"
}`}
disabled={plan.current}
>
{plan.current ? "Current Plan" : "Upgrade"}
</button>
{/* Features */}
<ul className="space-y-3">
{plan.features.map((feature, idx) => (
<li key={idx} className="flex items-start gap-3 text-sm">
<Check className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
<span>{feature}</span>
</li>
))}
</ul>
</div>
))}
</div>
{/* Billing History */}
<div className="mt-12">
<h2 className="text-2xl font-bold mb-6">Billing History</h2>
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-6 py-3 text-left text-sm font-semibold">Date</th>
<th className="px-6 py-3 text-left text-sm font-semibold">Description</th>
<th className="px-6 py-3 text-left text-sm font-semibold">Amount</th>
<th className="px-6 py-3 text-left text-sm font-semibold">Status</th>
</tr>
</thead>
<tbody>
{[
{ date: "Dec 1, 2024", desc: "Free Plan", amount: "$0.00", status: "Active" },
].map((row, idx) => (
<tr key={idx} className="border-b border-gray-200 hover:bg-gray-50">
<td className="px-6 py-4 text-sm">{row.date}</td>
<td className="px-6 py-4 text-sm">{row.desc}</td>
<td className="px-6 py-4 text-sm font-medium">{row.amount}</td>
<td className="px-6 py-4 text-sm">
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-semibold">
{row.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
+207
View File
@@ -0,0 +1,207 @@
"use client";
import { Upload, Wand2, Save, X } from "lucide-react";
import { useState } from "react";
export default function CreatePage() {
const [characterData, setCharacterData] = useState({
name: "",
avatar: "",
description: "",
personality: "",
category: "Fantasy",
});
const [step, setStep] = useState(1);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setCharacterData((prev) => ({ ...prev, [name]: value }));
};
const handleNext = () => {
if (step < 4) setStep(step + 1);
};
const handlePrev = () => {
if (step > 1) setStep(step - 1);
};
return (
<div className="p-6 max-w-2xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Create Character</h1>
<p className="text-gray-600">Build your own AI character in 4 simple steps</p>
</div>
{/* Progress */}
<div className="flex gap-2 mb-8">
{[1, 2, 3, 4].map((num) => (
<div
key={num}
className={`flex-1 h-2 rounded-full transition ${
num <= step ? "bg-black" : "bg-gray-200"
}`}
></div>
))}
</div>
{/* Form */}
<div className="bg-white border border-gray-200 rounded-xl p-8 space-y-6">
{/* Step 1: Basic Info */}
{step === 1 && (
<div className="space-y-6">
<div>
<label className="block text-sm font-semibold mb-2">Character Name</label>
<input
type="text"
name="name"
value={characterData.name}
onChange={handleChange}
placeholder="e.g., Luna, Alex, Sage"
className="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:border-black focus:outline-none transition"
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2">Category</label>
<select
name="category"
value={characterData.category}
onChange={handleChange}
className="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:border-black focus:outline-none transition"
>
<option>Fantasy</option>
<option>Sci-Fi</option>
<option>Romance</option>
<option>Mystery</option>
<option>Adventure</option>
</select>
</div>
<p className="text-sm text-gray-600">
Give your character a unique name and choose a category that best fits their personality.
</p>
</div>
)}
{/* Step 2: Avatar */}
{step === 2 && (
<div className="space-y-6">
<div>
<label className="block text-sm font-semibold mb-4">Character Avatar</label>
{/* Preview */}
<div className="w-32 h-32 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 rounded-xl flex items-center justify-center mb-6">
<span className="text-5xl"></span>
</div>
{/* Upload Options */}
<div className="space-y-3">
<button className="w-full border-2 border-dashed border-gray-300 rounded-lg py-8 hover:border-gray-400 transition flex flex-col items-center gap-2">
<Upload className="w-6 h-6 text-gray-600" />
<span className="text-sm font-medium">Upload Image</span>
<span className="text-xs text-gray-500">or drag and drop</span>
</button>
<button className="w-full border-2 border-gray-200 rounded-lg py-3 font-medium hover:bg-gray-50 transition flex items-center justify-center gap-2">
<Wand2 className="w-5 h-5" />
Generate with AI
</button>
</div>
</div>
</div>
)}
{/* Step 3: Description */}
{step === 3 && (
<div className="space-y-6">
<div>
<label className="block text-sm font-semibold mb-2">Character Description</label>
<textarea
name="description"
value={characterData.description}
onChange={handleChange}
placeholder="Describe your character's appearance, background, and key traits..."
rows={4}
className="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:border-black focus:outline-none transition resize-none"
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2">Personality & Traits</label>
<textarea
name="personality"
value={characterData.personality}
onChange={handleChange}
placeholder="Define their personality, interests, quirks, and conversation style..."
rows={4}
className="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:border-black focus:outline-none transition resize-none"
/>
</div>
</div>
)}
{/* Step 4: Review */}
{step === 4 && (
<div className="space-y-6">
<div className="bg-gray-50 rounded-lg p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="w-20 h-20 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 rounded-lg flex items-center justify-center text-3xl">
</div>
<div className="flex-1">
<h3 className="text-xl font-bold">{characterData.name || "Character Name"}</h3>
<p className="text-sm text-gray-600">{characterData.category}</p>
</div>
</div>
<div className="border-t border-gray-200 pt-4">
<p className="text-sm">
<span className="font-semibold">Description:</span>{" "}
{characterData.description || "No description provided"}
</p>
</div>
<div className="border-t border-gray-200 pt-4">
<p className="text-sm">
<span className="font-semibold">Personality:</span>{" "}
{characterData.personality || "No personality traits provided"}
</p>
</div>
</div>
<p className="text-sm text-gray-600">
Review your character and click "Publish" to make them available to the community!
</p>
</div>
)}
{/* Actions */}
<div className="flex gap-3 pt-6 border-t border-gray-200">
<button
onClick={handlePrev}
disabled={step === 1}
className="flex-1 px-4 py-3 border-2 border-gray-200 rounded-lg font-medium hover:bg-gray-50 transition disabled:opacity-50"
>
Previous
</button>
{step < 4 ? (
<button
onClick={handleNext}
className="flex-1 px-4 py-3 bg-black text-white rounded-lg font-medium hover:bg-gray-800 transition"
>
Next
</button>
) : (
<button className="flex-1 px-4 py-3 bg-black text-white rounded-lg font-medium hover:bg-gray-800 transition flex items-center justify-center gap-2">
<Save className="w-5 h-5" />
Publish Character
</button>
)}
</div>
</div>
</div>
);
}
+80
View File
@@ -0,0 +1,80 @@
"use client";
import { Heart, MessageCircle, Share2, Trash2 } from "lucide-react";
function generateFavorites() {
const characters = [
{ id: 1, name: "Luna", avatar: "🌙", category: "Fantasy", messages: 250 },
{ id: 2, name: "Alex", avatar: "⚡", category: "Sci-Fi", messages: 180 },
{ id: 3, name: "Sage", avatar: "🧙", category: "Fantasy", messages: 340 },
];
return characters;
}
export default function FavoritesPage() {
const favorites = generateFavorites();
return (
<div className="p-6 max-w-4xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Favorites</h1>
<p className="text-gray-600">{favorites.length} favorite characters</p>
</div>
{/* Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{favorites.map((char) => (
<div
key={char.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-lg transition"
>
{/* Character Card */}
<div className="h-48 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 flex items-center justify-center relative group">
<span className="text-6xl">{char.avatar}</span>
<button className="absolute top-2 right-2 p-2 bg-white rounded-lg opacity-0 group-hover:opacity-100 transition">
<Heart className="w-5 h-5 fill-red-600 text-red-600" />
</button>
</div>
{/* Content */}
<div className="p-4 space-y-4">
<div>
<h3 className="text-xl font-bold">{char.name}</h3>
<p className="text-sm text-gray-600">{char.category} Character</p>
</div>
{/* Stats */}
<div className="flex gap-4 text-sm text-gray-600 pb-4 border-b border-gray-200">
<div className="flex items-center gap-1">
<MessageCircle className="w-4 h-4" />
<span>{char.messages} messages</span>
</div>
</div>
{/* Actions */}
<div className="flex gap-2">
<button className="flex-1 bg-black text-white py-2 rounded-lg font-medium hover:bg-gray-800 transition text-sm">
Continue Chat
</button>
<button className="p-2 hover:bg-gray-100 rounded-lg transition">
<Share2 className="w-5 h-5" />
</button>
<button className="p-2 hover:bg-red-100 text-red-600 rounded-lg transition">
<Trash2 className="w-5 h-5" />
</button>
</div>
</div>
</div>
))}
</div>
{favorites.length === 0 && (
<div className="text-center py-12">
<Heart className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-600">No favorite characters yet</p>
</div>
)}
</div>
);
}
+183
View File
@@ -0,0 +1,183 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import {
Compass,
MessageCircle,
PlusCircle,
User,
LogOut,
ChevronDown,
Sparkles,
Bell,
Search,
} from "lucide-react";
export default function AppLayout({ children }: { children: React.ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(true);
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [activeTab, setActiveTab] = useState("discover");
const navigationItems = [
{ id: "discover", label: "Discover", icon: Compass, href: "/app" },
{ id: "roleplay", label: "Roleplay", icon: Sparkles, href: "/app/roleplay" },
{ id: "create", label: "Create", icon: PlusCircle, href: "/app/create" },
{ id: "messages", label: "Messages", icon: MessageCircle, href: "/app/messages" },
{ id: "profile", label: "Profile", icon: User, href: "/app/profile" },
];
return (
<div className="h-screen flex flex-col bg-white">
{/* Header */}
<header className="border-b border-gray-200 sticky top-0 z-40 bg-white">
<div className="px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
{/* Logo + Title */}
<Link href="/" className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-br from-pink-600 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white text-sm font-bold">RP</span>
</div>
<span className="font-semibold text-lg hidden sm:inline">RoleplayAI</span>
</Link>
{/* Center - Search Bar */}
<div className="flex-1 max-w-md mx-4 hidden md:block">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Search characters..."
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-full text-sm focus:outline-none focus:border-black transition"
/>
</div>
</div>
{/* Right - Actions */}
<div className="flex items-center gap-4">
{/* Subscribe Button */}
<button className="hidden sm:block px-4 py-2 border-2 border-black rounded-full font-medium hover:bg-black hover:text-white transition text-sm">
Subscribe
</button>
{/* Token Count */}
<div className="flex items-center gap-2 px-4 py-2 bg-gray-50 rounded-full">
<span className="text-sm font-semibold"></span>
<span className="text-sm font-medium">450</span>
</div>
{/* Notifications */}
<button className="relative p-2 hover:bg-gray-100 rounded-full transition">
<Bell className="w-5 h-5" />
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full"></span>
</button>
{/* User Avatar & Dropdown */}
<div className="relative">
<button
onClick={() => setUserMenuOpen(!userMenuOpen)}
className="flex items-center gap-2 p-1 hover:bg-gray-100 rounded-full transition"
>
<img
src="https://api.dicebear.com/7.x/avataaars/svg?seed=user"
alt="User"
className="w-8 h-8 rounded-full"
/>
<ChevronDown className="w-4 h-4 text-gray-600" />
</button>
{/* Dropdown Menu */}
{userMenuOpen && (
<div className="absolute right-0 mt-2 w-56 bg-white border border-gray-200 rounded-xl shadow-lg z-50">
{/* Profile Section */}
<div className="p-4 border-b border-gray-200">
<div className="flex items-center gap-3">
<img
src="https://api.dicebear.com/7.x/avataaars/svg?seed=user"
alt="User"
className="w-10 h-10 rounded-full"
/>
<div>
<p className="font-semibold text-sm">John Doe</p>
<p className="text-xs text-gray-500">@johndoe</p>
</div>
</div>
</div>
{/* Menu Items */}
<div className="py-2">
<Link href="/app/profile" className="block px-4 py-2 text-sm hover:bg-gray-50 transition">
My Profile
</Link>
<Link href="/app/settings" className="block px-4 py-2 text-sm hover:bg-gray-50 transition">
Settings
</Link>
<Link href="/app/billing" className="block px-4 py-2 text-sm hover:bg-gray-50 transition">
Billing & Plans
</Link>
<Link href="/app/favorites" className="block px-4 py-2 text-sm hover:bg-gray-50 transition">
Favorites
</Link>
</div>
{/* Divider */}
<div className="border-t border-gray-200"></div>
{/* Logout */}
<button className="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition flex items-center gap-2">
<LogOut className="w-4 h-4" />
Logout
</button>
</div>
)}
</div>
</div>
</div>
</header>
{/* Main Content */}
<div className="flex flex-1 overflow-hidden">
{/* Sidebar */}
<aside
className={`${
sidebarOpen ? "w-56" : "w-0"
} border-r border-gray-200 bg-gray-50 flex flex-col transition-all duration-300 overflow-hidden`}
>
<nav className="flex-1 space-y-2 p-4">
{navigationItems.map((item) => (
<Link
key={item.id}
href={item.href}
onClick={() => setActiveTab(item.id)}
className={`flex items-center gap-3 px-4 py-3 rounded-lg font-medium text-sm transition ${
activeTab === item.id
? "bg-white text-black shadow-sm"
: "text-gray-600 hover:bg-white/50"
}`}
>
<item.icon className="w-5 h-5" />
<span>{item.label}</span>
</Link>
))}
</nav>
{/* Sidebar Footer */}
<div className="border-t border-gray-200 p-4 space-y-3">
<p className="text-xs text-gray-500 font-semibold">UPGRADE</p>
<div className="bg-gradient-to-br from-pink-50 to-purple-50 rounded-lg p-3 space-y-2">
<p className="text-xs font-semibold">Unlock Premium</p>
<p className="text-xs text-gray-600">Get unlimited characters & faster responses</p>
<button className="w-full bg-black text-white py-2 text-xs font-semibold rounded-lg hover:bg-gray-800 transition">
Upgrade
</button>
</div>
</div>
</aside>
{/* Main View */}
<main className="flex-1 overflow-auto">
{children}
</main>
</div>
</div>
);
}
+120
View File
@@ -0,0 +1,120 @@
"use client";
import { MessageSquare, Trash2, MoreHorizontal } from "lucide-react";
function generateMockMessages() {
const messages = [
{
id: 1,
character: "Luna",
avatar: "🌙",
preview: "That sounds interesting...",
timestamp: "2 hours ago",
unread: true,
},
{
id: 2,
character: "Alex",
avatar: "⚡",
preview: "Hey! How have you been?",
timestamp: "5 hours ago",
unread: false,
},
{
id: 3,
character: "Sage",
avatar: "🧙",
preview: "The ancient prophecy speaks of...",
timestamp: "Yesterday",
unread: false,
},
{
id: 4,
character: "Nova",
avatar: "✨",
preview: "Ready for another space adventure?",
timestamp: "2 days ago",
unread: false,
},
{
id: 5,
character: "Echo",
avatar: "🔮",
preview: "I sensed something in the shadows...",
timestamp: "3 days ago",
unread: false,
},
{
id: 6,
character: "Storm",
avatar: "⛈️",
preview: "The journey awaits us!",
timestamp: "1 week ago",
unread: false,
},
];
return messages;
}
export default function MessagesPage() {
const messages = generateMockMessages();
return (
<div className="p-6 max-w-3xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Messages</h1>
<p className="text-gray-600">Your conversations with characters</p>
</div>
{/* Messages List */}
<div className="space-y-2">
{messages.map((msg) => (
<div
key={msg.id}
className={`border border-gray-200 rounded-xl p-4 hover:bg-gray-50 cursor-pointer transition flex items-center gap-4 group ${
msg.unread ? "bg-blue-50" : "bg-white"
}`}
>
{/* Avatar */}
<div className="w-12 h-12 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 rounded-full flex items-center justify-center text-xl flex-shrink-0">
{msg.avatar}
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold">{msg.character}</h3>
{msg.unread && <div className="w-2 h-2 bg-blue-600 rounded-full"></div>}
</div>
<p className="text-sm text-gray-600 truncate">{msg.preview}</p>
</div>
{/* Meta */}
<div className="text-right flex-shrink-0">
<p className="text-xs text-gray-500 mb-2">{msg.timestamp}</p>
<div className="opacity-0 group-hover:opacity-100 transition flex gap-2">
<button className="p-2 hover:bg-gray-200 rounded-lg transition">
<MoreHorizontal className="w-4 h-4" />
</button>
<button className="p-2 hover:bg-red-100 text-red-600 rounded-lg transition">
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
{/* Empty State */}
{messages.length === 0 && (
<div className="text-center py-16">
<MessageSquare className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">No messages yet</h3>
<p className="text-gray-600">Start a conversation with a character!</p>
</div>
)}
</div>
);
}
+93
View File
@@ -0,0 +1,93 @@
"use client";
import { Heart, MessageCircle, Share2 } from "lucide-react";
// Mock data generator
function generateMockCharacters() {
const baseCharacters = [
{ name: "Luna", avatar: "🌙", description: "Mysterious night elf" },
{ name: "Alex", avatar: "⚡", description: "Energetic hacker" },
{ name: "Sage", avatar: "🧙", description: "Wise wizard" },
{ name: "Nova", avatar: "✨", description: "Cosmic explorer" },
];
return Array.from({ length: 12 }, (_, i) => {
const char = baseCharacters[i % baseCharacters.length];
return {
id: i + 1,
...char,
messages: Math.floor(Math.random() * 500) + 50,
likes: Math.floor(Math.random() * 1000) + 100,
};
});
}
export default function DiscoverPage() {
const characters = generateMockCharacters();
return (
<div className="p-6 max-w-4xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Discover</h1>
<p className="text-gray-600">Explore trending characters and conversations</p>
</div>
{/* Waterfall Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{characters.map((char) => (
<div
key={char.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-lg transition group cursor-pointer"
>
{/* Character Card Header */}
<div className="h-48 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 flex items-center justify-center">
<span className="text-6xl">{char.avatar}</span>
</div>
{/* Card Content */}
<div className="p-4 space-y-4">
{/* Title */}
<div>
<h3 className="text-xl font-bold">{char.name}</h3>
<p className="text-sm text-gray-600">{char.description}</p>
</div>
{/* Stats */}
<div className="flex gap-4 text-sm text-gray-600 pb-4 border-b border-gray-200">
<div className="flex items-center gap-1">
<MessageCircle className="w-4 h-4" />
<span>{char.messages}</span>
</div>
<div className="flex items-center gap-1">
<Heart className="w-4 h-4" />
<span>{char.likes}</span>
</div>
</div>
{/* Actions */}
<div className="flex gap-2">
<button className="flex-1 bg-black text-white py-2 rounded-lg font-medium hover:bg-gray-800 transition text-sm">
Chat Now
</button>
<button className="p-2 hover:bg-gray-100 rounded-lg transition">
<Heart className="w-5 h-5" />
</button>
<button className="p-2 hover:bg-gray-100 rounded-lg transition">
<Share2 className="w-5 h-5" />
</button>
</div>
</div>
</div>
))}
</div>
{/* Load More */}
<div className="mt-12 text-center">
<button className="px-8 py-3 border-2 border-gray-200 rounded-full font-medium hover:bg-gray-50 transition">
Load More
</button>
</div>
</div>
);
}
+125
View File
@@ -0,0 +1,125 @@
"use client";
import { Edit2, Copy, LogOut } from "lucide-react";
import { useState } from "react";
export default function ProfilePage() {
const [isEditing, setIsEditing] = useState(false);
return (
<div className="p-6 max-w-3xl mx-auto">
{/* Header */}
<div className="mb-8 flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold mb-2">My Profile</h1>
<p className="text-gray-600">Manage your account</p>
</div>
<button
onClick={() => setIsEditing(!isEditing)}
className="flex items-center gap-2 px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition"
>
<Edit2 className="w-4 h-4" />
{isEditing ? "Done" : "Edit"}
</button>
</div>
{/* Profile Card */}
<div className="bg-white border border-gray-200 rounded-xl p-8 space-y-8 mb-8">
{/* Avatar & Basic Info */}
<div className="flex items-start gap-6">
<img
src="https://api.dicebear.com/7.x/avataaars/svg?seed=user"
alt="Profile"
className="w-24 h-24 rounded-xl"
/>
<div className="flex-1 space-y-4">
<div>
<label className="text-sm font-semibold text-gray-600">Name</label>
<input
type="text"
defaultValue="John Doe"
disabled={!isEditing}
className="w-full px-4 py-2 border border-gray-200 rounded-lg disabled:bg-gray-50 focus:outline-none focus:border-black transition"
/>
</div>
<div>
<label className="text-sm font-semibold text-gray-600">Handle</label>
<div className="flex gap-2">
<input
type="text"
defaultValue="@johndoe"
disabled={!isEditing}
className="flex-1 px-4 py-2 border border-gray-200 rounded-lg disabled:bg-gray-50 focus:outline-none focus:border-black transition"
/>
{isEditing && (
<button className="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200 transition">
<Copy className="w-4 h-4" />
</button>
)}
</div>
</div>
</div>
</div>
{/* Email */}
<div className="border-t border-gray-200 pt-6">
<label className="text-sm font-semibold text-gray-600">Email</label>
<input
type="email"
defaultValue="john@example.com"
disabled
className="w-full px-4 py-2 border border-gray-200 rounded-lg bg-gray-50 focus:outline-none mt-2"
/>
<p className="text-xs text-gray-500 mt-2">Connected via Google OAuth</p>
</div>
{/* Bio */}
<div className="border-t border-gray-200 pt-6">
<label className="text-sm font-semibold text-gray-600">Bio</label>
<textarea
defaultValue="AI enthusiast and roleplay lover"
disabled={!isEditing}
rows={3}
className="w-full px-4 py-2 border border-gray-200 rounded-lg disabled:bg-gray-50 focus:outline-none focus:border-black transition resize-none mt-2"
/>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-4 mb-8">
<div className="bg-white border border-gray-200 rounded-xl p-6 text-center">
<div className="text-2xl font-bold">12</div>
<p className="text-sm text-gray-600 mt-2">Favorite Characters</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-6 text-center">
<div className="text-2xl font-bold">48</div>
<p className="text-sm text-gray-600 mt-2">Conversations</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-6 text-center">
<div className="text-2xl font-bold">325</div>
<p className="text-sm text-gray-600 mt-2">Messages</p>
</div>
</div>
{/* Account Settings */}
<div className="bg-white border border-gray-200 rounded-xl p-8 space-y-4">
<h3 className="text-lg font-semibold mb-4">Account Settings</h3>
<button className="w-full text-left px-4 py-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
Change Password
</button>
<button className="w-full text-left px-4 py-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
Two-Factor Authentication
</button>
<button className="w-full text-left px-4 py-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
Privacy Settings
</button>
</div>
{/* Logout */}
<button className="mt-8 w-full px-4 py-3 flex items-center justify-center gap-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition">
<LogOut className="w-5 h-5" />
Logout
</button>
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
"use client";
import { Heart, MessageCircle, Share2, Filter } from "lucide-react";
import { useState } from "react";
function generateRoleplayCharacters() {
const categories = ["Fantasy", "Sci-Fi", "Romance", "Mystery", "Adventure"];
const baseCharacters = [
{ name: "Luna", avatar: "🌙", category: "Fantasy" },
{ name: "Alex", avatar: "⚡", category: "Sci-Fi" },
{ name: "Sage", avatar: "🧙", category: "Fantasy" },
{ name: "Nova", avatar: "✨", category: "Sci-Fi" },
{ name: "Echo", avatar: "🔮", category: "Mystery" },
{ name: "Storm", avatar: "⛈️", category: "Adventure" },
];
return Array.from({ length: 12 }, (_, i) => {
const char = baseCharacters[i % baseCharacters.length];
return {
id: i + 1,
...char,
messages: Math.floor(Math.random() * 500) + 50,
likes: Math.floor(Math.random() * 1000) + 100,
};
});
}
export default function RoleplayPage() {
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const characters = generateRoleplayCharacters();
const categories = ["Fantasy", "Sci-Fi", "Romance", "Mystery", "Adventure"];
const filtered = selectedCategory
? characters.filter((c) => c.category === selectedCategory)
: characters;
return (
<div className="p-6 max-w-4xl mx-auto">
{/* Header */}
<div className="mb-8 space-y-4">
<div>
<h1 className="text-3xl font-bold mb-2">Roleplay</h1>
<p className="text-gray-600">Immersive roleplay experiences</p>
</div>
{/* Category Filter */}
<div className="flex items-center gap-3 overflow-x-auto pb-2">
<button
onClick={() => setSelectedCategory(null)}
className={`px-4 py-2 rounded-full font-medium text-sm whitespace-nowrap transition ${
selectedCategory === null
? "bg-black text-white"
: "border-2 border-gray-200 hover:border-black"
}`}
>
All
</button>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`px-4 py-2 rounded-full font-medium text-sm whitespace-nowrap transition ${
selectedCategory === cat
? "bg-black text-white"
: "border-2 border-gray-200 hover:border-black"
}`}
>
{cat}
</button>
))}
</div>
</div>
{/* Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filtered.map((char) => (
<div
key={char.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-lg transition group cursor-pointer"
>
{/* Character Card */}
<div className="h-48 bg-gradient-to-br from-blue-200 via-purple-200 to-pink-200 flex items-center justify-center relative">
<span className="text-6xl">{char.avatar}</span>
<div className="absolute top-2 right-2 bg-white px-3 py-1 rounded-full text-xs font-semibold">
{char.category}
</div>
</div>
{/* Content */}
<div className="p-4 space-y-4">
<div>
<h3 className="text-xl font-bold">{char.name}</h3>
<p className="text-sm text-gray-600">{char.category} Character</p>
</div>
{/* Stats */}
<div className="flex gap-4 text-sm text-gray-600 pb-4 border-b border-gray-200">
<div className="flex items-center gap-1">
<MessageCircle className="w-4 h-4" />
<span>{char.messages}</span>
</div>
<div className="flex items-center gap-1">
<Heart className="w-4 h-4" />
<span>{char.likes}</span>
</div>
</div>
{/* Actions */}
<div className="flex gap-2">
<button className="flex-1 bg-black text-white py-2 rounded-lg font-medium hover:bg-gray-800 transition text-sm">
Start Roleplay
</button>
<button className="p-2 hover:bg-gray-100 rounded-lg transition">
<Heart className="w-5 h-5" />
</button>
</div>
</div>
</div>
))}
</div>
{filtered.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-600">No characters found in this category</p>
</div>
)}
</div>
);
}
+117
View File
@@ -0,0 +1,117 @@
"use client";
import { Save, Bell, Lock } from "lucide-react";
import { useState } from "react";
export default function SettingsPage() {
const [settings, setSettings] = useState({
emailNotifications: true,
pushNotifications: false,
marketingEmails: false,
publicProfile: true,
dataCollection: true,
});
const handleToggle = (key: keyof typeof settings) => {
setSettings((prev) => ({ ...prev, [key]: !prev[key] }));
};
return (
<div className="p-6 max-w-3xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Settings</h1>
<p className="text-gray-600">Manage your preferences</p>
</div>
{/* Notification Settings */}
<div className="bg-white border border-gray-200 rounded-xl p-8 mb-6">
<div className="flex items-center gap-3 mb-6">
<Bell className="w-6 h-6" />
<h3 className="text-lg font-semibold">Notifications</h3>
</div>
<div className="space-y-4">
{[
{
key: "emailNotifications" as const,
label: "Email Notifications",
desc: "Receive updates about new characters and messages",
},
{
key: "pushNotifications" as const,
label: "Push Notifications",
desc: "Get notifications on your device",
},
{
key: "marketingEmails" as const,
label: "Marketing Emails",
desc: "Receive promotional content and special offers",
},
].map((item) => (
<div key={item.key} className="flex items-center justify-between py-3 border-b border-gray-200 last:border-0">
<div>
<p className="font-medium">{item.label}</p>
<p className="text-sm text-gray-600">{item.desc}</p>
</div>
<label className="relative inline-flex cursor-pointer">
<input
type="checkbox"
checked={settings[item.key]}
onChange={() => handleToggle(item.key)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-black rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-black"></div>
</label>
</div>
))}
</div>
</div>
{/* Privacy Settings */}
<div className="bg-white border border-gray-200 rounded-xl p-8 mb-6">
<div className="flex items-center gap-3 mb-6">
<Lock className="w-6 h-6" />
<h3 className="text-lg font-semibold">Privacy</h3>
</div>
<div className="space-y-4">
{[
{
key: "publicProfile" as const,
label: "Public Profile",
desc: "Allow other users to see your profile",
},
{
key: "dataCollection" as const,
label: "Data Collection",
desc: "Allow us to collect usage data for improvements",
},
].map((item) => (
<div key={item.key} className="flex items-center justify-between py-3 border-b border-gray-200 last:border-0">
<div>
<p className="font-medium">{item.label}</p>
<p className="text-sm text-gray-600">{item.desc}</p>
</div>
<label className="relative inline-flex cursor-pointer">
<input
type="checkbox"
checked={settings[item.key]}
onChange={() => handleToggle(item.key)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-black rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-black"></div>
</label>
</div>
))}
</div>
</div>
{/* Save Button */}
<button className="flex items-center justify-center gap-2 w-full px-6 py-3 bg-black text-white rounded-lg font-medium hover:bg-gray-800 transition">
<Save className="w-5 h-5" />
Save Settings
</button>
</div>
);
}
+24 -17
View File
@@ -1,26 +1,33 @@
@import "tailwindcss";
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
--primary: #000;
--secondary: #fff;
--accent: #ff0066;
--muted: #8a8a8a;
--border: #e5e5e5;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif;
background: #fafafa;
color: var(--primary);
}
a {
color: inherit;
text-decoration: none;
}
button {
font-family: inherit;
border: none;
cursor: pointer;
}
+134
View File
@@ -0,0 +1,134 @@
"use client";
import Link from "next/link";
import { useState } from "react";
export default function LoginPage() {
const [isLoading, setIsLoading] = useState(false);
const handleGoogleLogin = () => {
setIsLoading(true);
window.location.href = `${process.env.NEXT_PUBLIC_API_URL}/login/google`;
};
return (
<div className="min-h-screen bg-white flex flex-col">
{/* Header */}
<div className="border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<Link href="/" className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-br from-pink-600 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white text-sm font-bold">RP</span>
</div>
<span className="font-semibold text-lg">RoleplayAI</span>
</Link>
</div>
</div>
{/* Main Content */}
<div className="flex-1 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md">
<div className="space-y-8">
{/* Welcome Section */}
<div className="space-y-2 text-center">
<h1 className="text-4xl font-bold">Welcome Back</h1>
<p className="text-gray-600">Sign in to continue to RoleplayAI</p>
</div>
{/* Google OAuth Button */}
<div className="space-y-4">
<button
onClick={handleGoogleLogin}
disabled={isLoading}
className="w-full border-2 border-gray-200 rounded-xl py-3 px-4 font-medium hover:bg-gray-50 transition flex items-center justify-center gap-3 disabled:opacity-50"
>
<svg className="w-5 h-5" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032 c0-3.331,2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.461,2.268,15.365,1,12.545,1 C6.477,1,1.54,5.952,1.54,12s4.938,11,11.005,11c6.071,0,11.067-4.975,11.067-11c0-0.713-0.092-1.405-0.196-2.067H12.545z"
/>
</svg>
{isLoading ? "Signing in..." : "Continue with Google"}
</button>
</div>
{/* Divider */}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">or</span>
</div>
</div>
{/* Email Form */}
<form className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
Email Address
</label>
<input
type="email"
id="email"
placeholder="you@example.com"
className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:border-black focus:outline-none transition"
disabled
/>
<p className="text-xs text-gray-500 mt-2">Email login coming soon</p>
</div>
</form>
{/* Agreement Text */}
<p className="text-center text-sm text-gray-600">
By signing in, you agree to our{" "}
<a href="#" className="text-black font-medium hover:underline">
Terms of Service
</a>{" "}
and{" "}
<a href="#" className="text-black font-medium hover:underline">
Privacy Policy
</a>
</p>
{/* Footer Links */}
<div className="space-y-3 text-center text-sm">
<p>
Don't have an account?{" "}
<a href="#" className="text-black font-medium hover:underline">
Sign up
</a>
</p>
<p>
<a href="/" className="text-gray-600 hover:text-black">
Back to Home
</a>
</p>
</div>
</div>
{/* Trust Indicators */}
<div className="mt-12 pt-8 border-t border-gray-200 space-y-6">
<p className="text-center text-xs text-gray-500">TRUSTED BY</p>
<div className="flex justify-center items-center gap-6 text-gray-400">
<div className="text-center">
<div className="text-2xl font-bold text-gray-600">10K+</div>
<p className="text-xs">Users</p>
</div>
<div className="w-px h-8 bg-gray-200"></div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-600">1000+</div>
<p className="text-xs">Characters</p>
</div>
<div className="w-px h-8 bg-gray-200"></div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-600">24/7</div>
<p className="text-xs">Support</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
+161 -55
View File
@@ -1,65 +1,171 @@
import Image from "next/image";
"use client";
import Link from "next/link";
import { ArrowRight, Sparkles, Users, Zap } from "lucide-react";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
<div className="min-h-screen bg-white">
{/* Navigation */}
<nav className="border-b border-gray-200 sticky top-0 z-50 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-br from-pink-600 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white text-sm font-bold">RP</span>
</div>
<span className="font-semibold text-lg">RoleplayAI</span>
</div>
<div className="flex items-center gap-4">
<Link href="/login" className="text-gray-600 hover:text-gray-900 font-medium">
Sign In
</Link>
<Link href="/login" className="bg-black text-white px-6 py-2 rounded-full hover:bg-gray-800 font-medium">
Get Started
</Link>
</div>
</div>
</nav>
{/* Hero Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-32">
<div className="text-center space-y-6 mb-12">
<h1 className="text-5xl md:text-7xl font-bold leading-tight">
AI Characters,
<br />
<span className="bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-transparent">
Real Conversations
</span>
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Engage in immersive roleplay conversations with AI characters. Create, discover, and connect.
</p>
<div className="flex gap-4 justify-center pt-4">
<Link href="/login" className="bg-black text-white px-8 py-3 rounded-full hover:bg-gray-800 font-medium inline-flex items-center gap-2">
Start Free <ArrowRight size={18} />
</Link>
<button className="border-2 border-black px-8 py-3 rounded-full hover:bg-gray-50 font-medium">
Learn More
</button>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
{/* Hero Visual */}
<div className="bg-gradient-to-br from-purple-100 via-pink-50 to-blue-100 rounded-3xl h-96 flex items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4"></div>
<p className="text-gray-600">AI-powered conversations await</p>
</div>
</div>
</section>
{/* Features Section */}
<section className="bg-gray-50 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-4xl font-bold text-center mb-16">Why Choose RoleplayAI?</h2>
<div className="grid md:grid-cols-3 gap-8">
{[
{
icon: Sparkles,
title: "Rich Characters",
description: "Explore a diverse library of AI characters with unique personalities and backgrounds",
},
{
icon: Users,
title: "Discover & Share",
description: "Find trending conversations and share your roleplay moments with the community",
},
{
icon: Zap,
title: "Instant Creation",
description: "Create your own AI characters in seconds with our intuitive builder",
},
].map((item, i) => (
<div key={i} className="bg-white p-8 rounded-2xl border border-gray-200 hover:shadow-lg transition">
<item.icon className="w-12 h-12 text-pink-600 mb-4" />
<h3 className="text-xl font-semibold mb-3">{item.title}</h3>
<p className="text-gray-600">{item.description}</p>
</div>
))}
</div>
</div>
</section>
{/* Showcase Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
<h2 className="text-4xl font-bold mb-12">Featured Characters</h2>
<div className="grid md:grid-cols-4 gap-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="bg-gradient-to-br from-gray-100 to-gray-200 rounded-xl aspect-square flex items-end p-4">
<div>
<h3 className="font-semibold text-lg mb-1">Character {i}</h3>
<p className="text-sm text-gray-600">Popular choice</p>
</div>
</div>
))}
</div>
</section>
{/* CTA Section */}
<section className="bg-black text-white py-20 mt-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-6">
<h2 className="text-5xl font-bold">Ready to explore?</h2>
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
Join thousands of users enjoying immersive AI conversations
</p>
<Link href="/login" className="inline-block bg-white text-black px-8 py-3 rounded-full hover:bg-gray-100 font-medium">
Get Started for Free
</Link>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-50 border-t border-gray-200 py-12">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-4 gap-8 mb-8">
<div>
<h4 className="font-semibold mb-4">Product</h4>
<ul className="space-y-2 text-gray-600 text-sm">
<li><a href="#" className="hover:text-black">Features</a></li>
<li><a href="#" className="hover:text-black">Pricing</a></li>
<li><a href="#" className="hover:text-black">Security</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Company</h4>
<ul className="space-y-2 text-gray-600 text-sm">
<li><a href="#" className="hover:text-black">About</a></li>
<li><a href="#" className="hover:text-black">Blog</a></li>
<li><a href="#" className="hover:text-black">Contact</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Legal</h4>
<ul className="space-y-2 text-gray-600 text-sm">
<li><a href="#" className="hover:text-black">Terms</a></li>
<li><a href="#" className="hover:text-black">Privacy</a></li>
<li><a href="#" className="hover:text-black">Cookie</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Follow</h4>
<ul className="space-y-2 text-gray-600 text-sm">
<li><a href="#" className="hover:text-black">Twitter</a></li>
<li><a href="#" className="hover:text-black">Discord</a></li>
<li><a href="#" className="hover:text-black">Instagram</a></li>
</ul>
</div>
</div>
<div className="border-t border-gray-200 pt-8 flex justify-between items-center text-gray-600 text-sm">
<p>&copy; 2024 RoleplayAI. All rights reserved.</p>
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-gradient-to-br from-pink-600 to-purple-600 rounded"></div>
<span className="font-semibold">RoleplayAI</span>
</div>
</div>
</div>
</footer>
</div>
);
}
+352 -45
View File
@@ -8,6 +8,8 @@
"name": "character-roleplay-frontend",
"version": "0.1.0",
"dependencies": {
"@tailwindcss/cli": "^4.3.0",
"lucide-react": "^1.16.0",
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4"
@@ -19,7 +21,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.2.6",
"tailwindcss": "^4",
"tailwindcss": "^4.3.0",
"typescript": "^5"
}
},
@@ -280,7 +282,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -302,7 +303,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -989,7 +989,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1000,7 +999,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1011,7 +1009,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1021,14 +1018,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1039,7 +1034,6 @@
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1240,6 +1234,313 @@
"node": ">=12.4.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1256,11 +1557,28 @@
"tslib": "^2.8.0"
}
},
"node_modules/@tailwindcss/cli": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.3.0.tgz",
"integrity": "sha512-X9kdlqyMopO9fewbgHsEeuy31YzMHbdZ9VsKt004tB+mxSg1CNbyhZYCzvhciN0AM4R4b5lvIprPjtNq7iQxpQ==",
"license": "MIT",
"dependencies": {
"@parcel/watcher": "^2.5.1",
"@tailwindcss/node": "4.3.0",
"@tailwindcss/oxide": "4.3.0",
"enhanced-resolve": "^5.21.0",
"mri": "^1.2.0",
"picocolors": "^1.1.1",
"tailwindcss": "4.3.0"
},
"bin": {
"tailwindcss": "dist/index.mjs"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
"integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
@@ -1276,7 +1594,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz",
"integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
@@ -1303,7 +1620,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1320,7 +1636,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1337,7 +1652,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1354,7 +1668,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1371,7 +1684,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1388,7 +1700,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1405,7 +1716,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1422,7 +1732,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1439,7 +1748,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1464,7 +1772,6 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1486,7 +1793,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1503,7 +1809,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1531,7 +1836,6 @@
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -2801,7 +3105,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -2853,7 +3156,6 @@
"version": "5.21.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz",
"integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -3801,7 +4103,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-bigints": {
@@ -4129,7 +4430,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4175,7 +4475,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -4418,7 +4717,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -4555,7 +4853,6 @@
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -4588,7 +4885,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4609,7 +4905,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4630,7 +4925,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4651,7 +4945,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4672,7 +4965,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4693,7 +4985,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4714,7 +5005,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4735,7 +5025,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4756,7 +5045,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4777,7 +5065,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4798,7 +5085,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4858,11 +5144,19 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz",
"integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -4925,6 +5219,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5054,6 +5357,12 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -6069,14 +6378,12 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
"integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
+3 -1
View File
@@ -9,6 +9,8 @@
"lint": "eslint"
},
"dependencies": {
"@tailwindcss/cli": "^4.3.0",
"lucide-react": "^1.16.0",
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4"
@@ -20,7 +22,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.2.6",
"tailwindcss": "^4",
"tailwindcss": "^4.3.0",
"typescript": "^5"
}
}