Skip to content

Commit 9c4a6a5

Browse files
committed
Add theme feature
1 parent 80db81c commit 9c4a6a5

File tree

16 files changed

+440
-5
lines changed

16 files changed

+440
-5
lines changed

app/(home)/page.tsx

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { ScreenshotCarousel } from './screenshot-carousel';
77
import { screenshotScreens } from './screenshot-screens';
88
import { TabbedImages } from './tabbed-images';
99
import { LightboxImage } from './lightbox-image';
10+
import { ThemeCarousel } from './theme-carousel';
1011
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
11-
import { Star, Sparkles, Zap, Code2, Image as ImageIcon, Music, Calculator, Puzzle, Search, GalleryHorizontal, Sigma, Plug, Wand2, Mic, Monitor } from 'lucide-react';
12+
import { Star, Sparkles, Zap, Code2, Image as ImageIcon, Music, Calculator, Puzzle, Search, GalleryHorizontal, Sigma, Plug, Wand2, Mic, Monitor, Palette } from 'lucide-react';
1213
import { YouTube } from '@/components/youtube';
1314

1415
export default function HomePage() {
@@ -89,20 +90,38 @@ export default function HomePage() {
8990
</div>
9091
</div>
9192

93+
{/* Themes Section */}
94+
<div id="themes" className="w-full my-16 px-4">
95+
<div className="max-w-7xl mx-auto">
96+
<div className="text-center mb-8">
97+
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-4">
98+
Beautiful Themes
99+
</h2>
100+
<p className="text-lg text-slate-600 dark:text-slate-400">
101+
Personalize your experience with a selection of
102+
<a href="/docs/features/themes" className="text-blue-600 dark:text-blue-400 hover:underline mx-1">
103+
handcrafted themes
104+
</a>
105+
</p>
106+
</div>
107+
<ThemeCarousel />
108+
</div>
109+
</div>
110+
92111
{/* Screenshot Carousel Section */}
93112
<div className="w-full my-12 px-4">
94113
<ScreenshotCarousel screens={screenshotScreens} className="max-w-[1200px] mx-auto" />
95114
</div>
96115

97-
{/* What's New in v3 */}
98-
<div className="w-full my-16 px-4 bg-gradient-to-b from-transparent via-blue-50/50 to-transparent dark:via-blue-950/20 py-16">
116+
{/* What's New */}
117+
<div id="whatsnew" className="w-full my-16 px-4 bg-gradient-to-b from-transparent via-blue-50/50 to-transparent dark:via-blue-950/20 py-16">
99118
<div className="max-w-7xl mx-auto">
100119
<div className="text-center mb-12">
101120
<h2 className="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
102-
What's New in v3
121+
What's New
103122
</h2>
104123
<p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
105-
Major release focused on extensibility, expanded provider support, and enhanced user experience
124+
Latest release focused on extensibility, expanded provider support, and enhanced user experience
106125
</p>
107126
</div>
108127

@@ -220,6 +239,22 @@ export default function HomePage() {
220239
</Card>
221240
</Link>
222241

242+
<Link href="/docs/features/themes" className="block">
243+
<Card className="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 hover:border-blue-400 dark:hover:border-blue-500 transition-colors cursor-pointer h-full">
244+
<CardHeader>
245+
<CardTitle className="flex items-center gap-2 text-slate-900 dark:text-slate-100">
246+
<Palette className="w-5 h-5 text-violet-500" />
247+
Themes
248+
</CardTitle>
249+
</CardHeader>
250+
<CardContent>
251+
<p className="text-slate-700 dark:text-slate-300">
252+
Customize the look and feel with built-in themes or create your own
253+
</p>
254+
</CardContent>
255+
</Card>
256+
</Link>
257+
223258
<Link href="/docs/features/calculator-ui" className="block">
224259
<Card className="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 hover:border-blue-400 dark:hover:border-blue-500 transition-colors cursor-pointer h-full">
225260
<CardHeader>

app/(home)/theme-carousel.tsx

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
'use client'
2+
3+
import { FC, useState, useEffect, useCallback } from "react"
4+
import { cn } from "@/lib/utils"
5+
import Image from "next/image"
6+
7+
export interface ThemePreview {
8+
chromeBorder: string
9+
bgBody: string
10+
bgSidebar: string
11+
icon: string
12+
heading: string
13+
}
14+
15+
export interface ThemeInfo {
16+
name: string
17+
label: string
18+
image: string
19+
preview: ThemePreview
20+
}
21+
22+
const PaletteIcon: FC<{ className?: string }> = ({ className }) => (
23+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className={className}>
24+
<path fill="currentColor" d="M17.5 12a1.5 1.5 0 0 1-1.5-1.5A1.5 1.5 0 0 1 17.5 9a1.5 1.5 0 0 1 1.5 1.5a1.5 1.5 0 0 1-1.5 1.5m-3-4A1.5 1.5 0 0 1 13 6.5A1.5 1.5 0 0 1 14.5 5A1.5 1.5 0 0 1 16 6.5A1.5 1.5 0 0 1 14.5 8m-5 0A1.5 1.5 0 0 1 8 6.5A1.5 1.5 0 0 1 9.5 5A1.5 1.5 0 0 1 11 6.5A1.5 1.5 0 0 1 9.5 8m-3 4A1.5 1.5 0 0 1 5 10.5A1.5 1.5 0 0 1 6.5 9A1.5 1.5 0 0 1 8 10.5A1.5 1.5 0 0 1 6.5 12M12 3a9 9 0 0 0-9 9a9 9 0 0 0 9 9a1.5 1.5 0 0 0 1.5-1.5c0-.39-.15-.74-.39-1c-.23-.27-.38-.62-.38-1a1.5 1.5 0 0 1 1.5-1.5H16a5 5 0 0 0 5-5c0-4.42-4.03-8-9-8" />
25+
</svg>
26+
)
27+
28+
const lightThemes = [
29+
{ name: "light_slate", label: "Light Slate", image: "/img/themes/light_slate.webp", preview: {
30+
"chromeBorder": "border-gray-200",
31+
"bgBody": "bg-white",
32+
"bgSidebar": "bg-gray-50",
33+
"icon": "text-gray-500",
34+
"heading": "text-gray-900"
35+
} },
36+
{ name: "soft_pink", label: "Soft Pink", image: "/img/themes/soft_pink.webp", preview: {
37+
"chromeBorder": "border-pink-200",
38+
"bgBody": "bg-pink-50",
39+
"bgSidebar": "bg-pink-100",
40+
"icon": "text-pink-500",
41+
"heading": "text-pink-900"
42+
} },
43+
{ name: "light_sky", label: "Light Sky", image: "/img/themes/light_sky.webp", preview: {
44+
"chromeBorder": "border-sky-200",
45+
"bgBody": "bg-sky-50",
46+
"bgSidebar": "bg-sky-100",
47+
"icon": "text-sky-500",
48+
"heading": "text-sky-900"
49+
} },
50+
{ name: "light", label: "Light", image: "/img/themes/light.webp", preview: {
51+
"chromeBorder": "border-gray-200",
52+
"bgBody": "bg-white",
53+
"bgSidebar": "bg-gray-50",
54+
"icon": "text-gray-500",
55+
"heading": "text-gray-900"
56+
} },
57+
]
58+
59+
const darkThemes = [
60+
{ name: "nord", label: "Nord", image: "/img/themes/nord.webp", preview: {
61+
"chromeBorder": "border-frost-700/50",
62+
"bgBody": "bg-nord-900",
63+
"bgSidebar": "bg-nord-800",
64+
"icon": "text-nord-300",
65+
"heading": "text-nord-200"
66+
} },
67+
{ name: "matrix", label: "Matrix", image: "/img/themes/matrix.webp", preview: {
68+
"chromeBorder": "border-green-500/50",
69+
"bgBody": "bg-black",
70+
"bgSidebar": "bg-green-950/80",
71+
"icon": "text-green-500",
72+
"heading": "text-green-400 font-mono"
73+
} },
74+
{ name: "blue_smoke", label: "Blue Smoke", image: "/img/themes/blue_smoke.webp", preview: {
75+
"chromeBorder": "border-blue-500/60",
76+
"bgBody": "bg-gray-950",
77+
"bgSidebar": "bg-gray-950",
78+
"icon": "text-blue-400",
79+
"heading": "text-blue-100"
80+
} },
81+
{ name: "dark", label: "Dark", image: "/img/themes/dark.webp", preview: {
82+
"chromeBorder": "border-gray-700",
83+
"bgBody": "bg-gray-900",
84+
"bgSidebar": "bg-gray-800/50",
85+
"icon": "text-gray-400",
86+
"heading": "text-indigo-200"
87+
} },
88+
]
89+
90+
const themes: ThemeInfo[] = [
91+
...darkThemes,
92+
...lightThemes,
93+
]
94+
95+
interface ThemeCarouselProps {
96+
autoPlayInterval?: number
97+
compact?: boolean
98+
className?: string
99+
}
100+
101+
export const ThemeCarousel: FC<ThemeCarouselProps> = ({
102+
autoPlayInterval = 10000,
103+
compact,
104+
className,
105+
}) => {
106+
const [currentIndex, setCurrentIndex] = useState(0)
107+
const [isPaused, setIsPaused] = useState(false)
108+
109+
const goToSlide = useCallback((index: number) => {
110+
setCurrentIndex(index)
111+
}, [])
112+
113+
useEffect(() => {
114+
if (isPaused || themes.length <= 1) return
115+
116+
const interval = setInterval(() => {
117+
setCurrentIndex((prev) => (prev + 1) % themes.length)
118+
}, autoPlayInterval)
119+
120+
return () => clearInterval(interval)
121+
}, [isPaused, autoPlayInterval, currentIndex])
122+
123+
const current = themes[currentIndex]
124+
125+
return (
126+
<div
127+
className={cn("not-prose relative w-full", className)}
128+
onMouseEnter={() => setIsPaused(true)}
129+
onMouseLeave={() => setIsPaused(false)}
130+
>
131+
{/* Main image */}
132+
<div className="relative rounded-xl overflow-hidden shadow-2xl border border-slate-200 dark:border-slate-700">
133+
<div className="relative w-full aspect-[2240/2228]">
134+
{themes.map((theme, index) => (
135+
<Image
136+
key={theme.name}
137+
src={theme.image}
138+
alt={`${theme.label} theme`}
139+
fill
140+
className={cn(
141+
"object-cover transition-opacity duration-500",
142+
index === currentIndex ? "opacity-100" : "opacity-0"
143+
)}
144+
priority={index === 0}
145+
sizes="(max-width: 768px) 100vw, (max-width: 1280px) 80vw, 1024px"
146+
/>
147+
))}
148+
</div>
149+
{/* Theme name badge */}
150+
<div className="absolute bottom-4 left-4 px-3 py-1.5 bg-black/60 backdrop-blur-sm rounded-lg text-white text-sm font-medium">
151+
{current.label}
152+
</div>
153+
</div>
154+
155+
{/* Theme selectors */}
156+
<div className={cn("mt-6 flex flex-col gap-3", !compact && "items-center")}>
157+
{[darkThemes, lightThemes].map((group, groupIndex) => (
158+
<div key={groupIndex} className={cn("flex gap-2", !compact && "justify-center")}>
159+
{group.map((theme) => {
160+
const index = themes.indexOf(theme)
161+
const p = theme.preview
162+
return (
163+
<button type="button"
164+
key={theme.name}
165+
onClick={() => goToSlide(index)}
166+
className={cn(
167+
"rounded-lg border overflow-hidden transition-all duration-300 flex items-stretch",
168+
compact ? "w-40" : "w-44",
169+
p.bgBody, p.chromeBorder,
170+
index === currentIndex
171+
? "shadow-lg scale-105"
172+
: "hover:scale-[1.02]"
173+
)}
174+
aria-label={`Select ${theme.label} theme`}
175+
>
176+
<div className={cn("flex items-center justify-center px-2.5", p.bgSidebar)}>
177+
<PaletteIcon className={cn("size-5", p.icon)} />
178+
</div>
179+
<span className={cn("text-sm font-medium truncate flex-1 px-3 py-2", compact && "text-left", p.heading)}>
180+
{theme.label}
181+
</span>
182+
{!compact && (
183+
<div className={cn("flex items-center pr-2.5", p.icon)}>
184+
<svg className={cn(
185+
"size-2 flex-shrink-0 transition-all duration-300",
186+
index === currentIndex ? "scale-100" : "scale-0"
187+
)} viewBox="0 0 8 8">
188+
<circle cx="4" cy="4" r="4" fill="currentColor" />
189+
</svg>
190+
</div>
191+
)}
192+
</button>
193+
)
194+
})}
195+
</div>
196+
))}
197+
</div>
198+
</div>
199+
)
200+
}

app/global.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@
1818
--color-fd-muted-foreground: rgb(148 163 184); /* slate-400 */
1919
}
2020

21+
@theme {
22+
--default-ring-color: hsl(var(--ring));
23+
24+
--color-nord-100: #eceff4;
25+
--color-nord-200: #e5e9f0;
26+
--color-nord-300: #d8dee9;
27+
28+
--color-nord-600: #4c566a;
29+
--color-nord-700: #434c5e;
30+
--color-nord-800: #3b4252;
31+
--color-nord-900: #2e3440;
32+
33+
--color-frost-400: #8fbcbb;
34+
--color-frost-500: #88c0d0;
35+
--color-frost-600: #81a1c1;
36+
--color-frost-700: #5e81ac;
37+
38+
--color-nord-red: #bf616a;
39+
--color-nord-orange: #d08770;
40+
--color-nord-yellow: #ebcb8b;
41+
--color-nord-green: #a3be8c;
42+
--color-nord-purple: #b48ead;
43+
}
44+
45+
2146
/* Code block background color - target all elements */
2247
pre,
2348
pre *,

content/docs/features/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"pages": [
55
"cli",
66
"web-ui",
7+
"themes",
78
"chat-ui",
89
"analytics",
910
"core-tools",

0 commit comments

Comments
 (0)