@usecross/docs 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,239 @@
1
+ import { useState, useRef, useEffect } from 'react'
2
+ import { router } from '@inertiajs/react'
3
+ import { cn } from '../lib/utils'
4
+ import type { DocSetMeta } from '../types'
5
+
6
+ interface DocSetSelectorProps {
7
+ docSets: DocSetMeta[]
8
+ currentDocSet: string
9
+ className?: string
10
+ }
11
+
12
+ // Chevron icon with up/down indicators like Fumadocs
13
+ const ChevronUpDownIcon = ({ className }: { className?: string }) => (
14
+ <svg
15
+ className={className}
16
+ viewBox="0 0 16 16"
17
+ fill="none"
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ >
20
+ <path
21
+ d="M5 6l3-3 3 3M5 10l3 3 3-3"
22
+ stroke="currentColor"
23
+ strokeWidth="1.5"
24
+ strokeLinecap="round"
25
+ strokeLinejoin="round"
26
+ />
27
+ </svg>
28
+ )
29
+
30
+ // Checkmark for selected state
31
+ const CheckIcon = ({ className }: { className?: string }) => (
32
+ <svg
33
+ className={className}
34
+ viewBox="0 0 16 16"
35
+ fill="none"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ >
38
+ <path
39
+ d="M3.5 8.5l3 3 6-6.5"
40
+ stroke="currentColor"
41
+ strokeWidth="1.75"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ />
45
+ </svg>
46
+ )
47
+
48
+ // Default package/docs icon when no iconUrl is provided
49
+ const PackageIcon = ({ className }: { className?: string }) => (
50
+ <svg
51
+ className={className}
52
+ viewBox="0 0 20 20"
53
+ fill="none"
54
+ xmlns="http://www.w3.org/2000/svg"
55
+ >
56
+ <path
57
+ d="M10 2L17 6v8l-7 4-7-4V6l7-4z"
58
+ stroke="currentColor"
59
+ strokeWidth="1.5"
60
+ strokeLinejoin="round"
61
+ />
62
+ <path
63
+ d="M10 10v8M10 10l7-4M10 10L3 6"
64
+ stroke="currentColor"
65
+ strokeWidth="1.5"
66
+ strokeLinecap="round"
67
+ strokeLinejoin="round"
68
+ />
69
+ </svg>
70
+ )
71
+
72
+ /**
73
+ * Dropdown selector for switching between documentation sets.
74
+ * Inspired by Fumadocs design - clean and minimal.
75
+ */
76
+ export function DocSetSelector({ docSets, currentDocSet, className }: DocSetSelectorProps) {
77
+ const [isOpen, setIsOpen] = useState(false)
78
+ const dropdownRef = useRef<HTMLDivElement>(null)
79
+
80
+ const current = docSets.find((ds) => ds.slug === currentDocSet) || docSets[0]
81
+
82
+ // Close dropdown when clicking outside
83
+ useEffect(() => {
84
+ const handleClickOutside = (event: MouseEvent) => {
85
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
86
+ setIsOpen(false)
87
+ }
88
+ }
89
+
90
+ if (isOpen) {
91
+ document.addEventListener('mousedown', handleClickOutside)
92
+ return () => document.removeEventListener('mousedown', handleClickOutside)
93
+ }
94
+ }, [isOpen])
95
+
96
+ // Close on escape key
97
+ useEffect(() => {
98
+ const handleEscape = (event: KeyboardEvent) => {
99
+ if (event.key === 'Escape') setIsOpen(false)
100
+ }
101
+
102
+ if (isOpen) {
103
+ document.addEventListener('keydown', handleEscape)
104
+ return () => document.removeEventListener('keydown', handleEscape)
105
+ }
106
+ }, [isOpen])
107
+
108
+ const handleSelect = (docSet: DocSetMeta) => {
109
+ setIsOpen(false)
110
+ if (docSet.slug !== currentDocSet) {
111
+ router.visit(`${docSet.prefix}/`)
112
+ }
113
+ }
114
+
115
+ return (
116
+ <div className={cn('relative', className)} ref={dropdownRef}>
117
+ {/* Trigger Button - Clean, flat design like Fumadocs */}
118
+ <button
119
+ onClick={() => setIsOpen(!isOpen)}
120
+ className={cn(
121
+ 'w-full flex items-center gap-2.5 px-3 py-2',
122
+ 'bg-gray-100/80 dark:bg-white/5',
123
+ 'border border-gray-200 dark:border-white/10',
124
+ 'rounded-lg',
125
+ 'hover:bg-gray-200/80 dark:hover:bg-white/10',
126
+ 'transition-colors duration-150',
127
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50'
128
+ )}
129
+ aria-label="Select documentation"
130
+ aria-expanded={isOpen}
131
+ aria-haspopup="listbox"
132
+ >
133
+ {/* Icon */}
134
+ <div className="flex-shrink-0 w-5 h-5 flex items-center justify-center text-gray-600 dark:text-gray-400">
135
+ {current.icon ? (
136
+ <span className="text-base leading-none">{current.icon}</span>
137
+ ) : current.iconUrl ? (
138
+ <img src={current.iconUrl} alt="" className="w-5 h-5" />
139
+ ) : (
140
+ <PackageIcon className="w-5 h-5" />
141
+ )}
142
+ </div>
143
+
144
+ {/* Text */}
145
+ <span className="flex-1 text-left text-sm font-medium text-gray-900 dark:text-white truncate">
146
+ {current.name}
147
+ </span>
148
+
149
+ {/* Chevron */}
150
+ <ChevronUpDownIcon className="flex-shrink-0 w-4 h-4 text-gray-400 dark:text-gray-500" />
151
+ </button>
152
+
153
+ {/* Dropdown Menu */}
154
+ <div
155
+ className={cn(
156
+ 'absolute left-0 right-0 mt-1.5',
157
+ 'py-1',
158
+ 'bg-white dark:bg-[#1a1a1a]',
159
+ 'border border-gray-200 dark:border-white/10',
160
+ 'rounded-lg',
161
+ 'shadow-lg shadow-black/5 dark:shadow-black/30',
162
+ 'z-50',
163
+ 'transition-all duration-150 ease-out origin-top',
164
+ isOpen
165
+ ? 'opacity-100 scale-100'
166
+ : 'opacity-0 scale-95 pointer-events-none'
167
+ )}
168
+ role="listbox"
169
+ aria-label="Select documentation set"
170
+ >
171
+ {docSets.map((docSet) => {
172
+ const isSelected = docSet.slug === currentDocSet
173
+
174
+ return (
175
+ <button
176
+ key={docSet.slug || '_root'}
177
+ onClick={() => handleSelect(docSet)}
178
+ className={cn(
179
+ 'w-full flex items-center gap-2.5 px-3 py-2',
180
+ 'transition-colors duration-100',
181
+ 'focus:outline-none',
182
+ isSelected
183
+ ? 'bg-primary-50 dark:bg-primary-500/10'
184
+ : 'hover:bg-gray-50 dark:hover:bg-white/5'
185
+ )}
186
+ role="option"
187
+ aria-selected={isSelected}
188
+ >
189
+ {/* Icon */}
190
+ <div className={cn(
191
+ 'flex-shrink-0 w-5 h-5 flex items-center justify-center',
192
+ isSelected
193
+ ? 'text-primary-600 dark:text-primary-400'
194
+ : 'text-gray-500 dark:text-gray-400'
195
+ )}>
196
+ {docSet.icon ? (
197
+ <span className="text-base leading-none">{docSet.icon}</span>
198
+ ) : docSet.iconUrl ? (
199
+ <img src={docSet.iconUrl} alt="" className="w-5 h-5" />
200
+ ) : (
201
+ <PackageIcon className="w-5 h-5" />
202
+ )}
203
+ </div>
204
+
205
+ {/* Text Content */}
206
+ <div className="flex-1 text-left min-w-0">
207
+ <div
208
+ className={cn(
209
+ 'text-sm font-medium truncate',
210
+ isSelected
211
+ ? 'text-primary-700 dark:text-primary-300'
212
+ : 'text-gray-900 dark:text-white'
213
+ )}
214
+ >
215
+ {docSet.name}
216
+ </div>
217
+ {docSet.description && (
218
+ <div className={cn(
219
+ 'text-xs truncate',
220
+ isSelected
221
+ ? 'text-primary-600/70 dark:text-primary-400/70'
222
+ : 'text-gray-500 dark:text-gray-400'
223
+ )}>
224
+ {docSet.description}
225
+ </div>
226
+ )}
227
+ </div>
228
+
229
+ {/* Checkmark indicator */}
230
+ {isSelected && (
231
+ <CheckIcon className="flex-shrink-0 w-4 h-4 text-primary-600 dark:text-primary-400" />
232
+ )}
233
+ </button>
234
+ )
235
+ })}
236
+ </div>
237
+ </div>
238
+ )
239
+ }
@@ -1,13 +1,15 @@
1
1
  import { Head, Link, usePage } from '@inertiajs/react'
2
2
  import { useState } from 'react'
3
3
  import { Sidebar } from './Sidebar'
4
+ import { ThemeToggle } from './ThemeToggle'
5
+ import { useTheme } from './ThemeProvider'
4
6
  import type { DocsLayoutProps, SharedProps } from '../types'
5
7
 
6
8
  function MobileMenuButton({ onClick, isOpen }: { onClick: () => void; isOpen: boolean }) {
7
9
  return (
8
10
  <button
9
11
  onClick={onClick}
10
- className="inline-flex items-center justify-center p-2 -ml-2 text-black hover:text-primary-500 lg:hidden"
12
+ className="inline-flex items-center justify-center p-2 -ml-2 text-gray-700 hover:text-primary-500 dark:text-gray-300 dark:hover:text-primary-400 lg:hidden transition-colors"
11
13
  aria-expanded={isOpen}
12
14
  >
13
15
  <span className="sr-only">{isOpen ? 'Close menu' : 'Open menu'}</span>
@@ -52,8 +54,9 @@ export function DocsLayout({
52
54
  footer,
53
55
  }: DocsLayoutProps) {
54
56
  const sharedProps = usePage<{ props: SharedProps }>().props as unknown as SharedProps
55
- const { nav, currentPath } = sharedProps
57
+ const { nav, currentPath, docSets, currentDocSet } = sharedProps
56
58
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
59
+ const { resolvedTheme } = useTheme()
57
60
 
58
61
  // Merge props - component props take precedence over shared props from Python
59
62
  const logoUrl = propLogoUrl ?? sharedProps.logoUrl
@@ -68,18 +71,20 @@ export function DocsLayout({
68
71
  <img src={logoUrl} alt="Logo" className="h-8" />
69
72
  ) : null)
70
73
 
71
- // Determine which logo to display in footer (prefer footer-specific logo)
74
+ // Determine which logo to display in footer (theme-aware)
72
75
  const footerLogoUrl = sharedProps.footerLogoUrl || logoUrl
73
- const footerLogo = logo || (footerLogoUrl ? (
74
- <img src={footerLogoUrl} alt="Logo" className="h-6" />
76
+ const footerLogoInvertedUrl = sharedProps.footerLogoInvertedUrl || logoInvertedUrl
77
+ const currentFooterLogoUrl = resolvedTheme === 'dark' ? (footerLogoInvertedUrl || footerLogoUrl) : footerLogoUrl
78
+ const footerLogo = logo || (currentFooterLogoUrl ? (
79
+ <img src={currentFooterLogoUrl} alt="Logo" className="h-6" />
75
80
  ) : null)
76
81
 
77
82
  return (
78
- <div className="min-h-screen bg-white flex flex-col">
83
+ <div className="min-h-screen bg-white dark:bg-[#0f0f0f] flex flex-col transition-colors duration-200">
79
84
  <Head title={title} />
80
85
 
81
86
  {/* Fixed navigation */}
82
- <nav className="fixed w-full z-50 bg-white border-b border-gray-200">
87
+ <nav className="fixed w-full z-50 bg-white/95 dark:bg-[#0f0f0f]/95 backdrop-blur-sm border-b border-gray-200 dark:border-gray-800 transition-colors">
83
88
  <div className="px-4 lg:px-10">
84
89
  <div className="flex justify-between h-16 items-center">
85
90
  <div className="flex items-center gap-2">
@@ -89,17 +94,21 @@ export function DocsLayout({
89
94
  {headerLogo}
90
95
  </Link>
91
96
  ) : (
92
- <Link href="/" className="font-bold text-lg">
97
+ <Link href="/" className="font-bold text-lg text-gray-900 dark:text-white">
93
98
  Docs
94
99
  </Link>
95
100
  )}
96
101
  </div>
97
- <div className="flex items-center space-x-8">
102
+ <div className="flex items-center gap-6">
103
+ <div className="-mr-2">
104
+ <ThemeToggle size="sm" />
105
+ </div>
106
+
98
107
  {navLinks.map((link) => (
99
108
  <Link
100
109
  key={link.href}
101
110
  href={link.href}
102
- className="text-black font-medium hover:text-primary-500 transition-colors"
111
+ className="hidden sm:block text-gray-700 dark:text-gray-300 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
103
112
  >
104
113
  {link.label}
105
114
  </Link>
@@ -109,7 +118,7 @@ export function DocsLayout({
109
118
  href={githubUrl}
110
119
  target="_blank"
111
120
  rel="noopener noreferrer"
112
- className="text-black hover:text-primary-500 transition-colors"
121
+ className="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
113
122
  >
114
123
  <GitHubIcon />
115
124
  </a>
@@ -122,26 +131,26 @@ export function DocsLayout({
122
131
  {/* Mobile sidebar */}
123
132
  {mobileMenuOpen && (
124
133
  <div className="fixed inset-0 z-40 lg:hidden">
125
- <div className="fixed inset-0 bg-black/50" onClick={() => setMobileMenuOpen(false)} />
126
- <div className="fixed inset-y-0 left-0 w-72 overflow-y-auto bg-white px-4 lg:px-10 py-6 pt-20 border-r border-gray-200">
127
- <Sidebar nav={nav} currentPath={currentPath} />
134
+ <div className="fixed inset-0 bg-black/50 dark:bg-black/70" onClick={() => setMobileMenuOpen(false)} />
135
+ <div className="fixed inset-y-0 left-0 w-64 overflow-y-auto bg-white dark:bg-[#0f0f0f] px-4 py-6 pt-20 border-r border-gray-200 dark:border-gray-800 transition-colors">
136
+ <Sidebar nav={nav} currentPath={currentPath} docSets={docSets} currentDocSet={currentDocSet} />
128
137
  </div>
129
138
  </div>
130
139
  )}
131
140
 
132
141
  {/* Main content area */}
133
- <div className="bg-white pt-16 w-full flex-1">
134
- <div className="grid grid-cols-12">
135
- {/* Desktop sidebar */}
136
- <aside className="hidden lg:block lg:col-span-3 xl:col-span-2 border-r border-gray-200 min-h-[calc(100vh-4rem)]">
137
- <nav className="sticky top-16 px-4 lg:px-10 py-6 max-h-[calc(100vh-4rem)] overflow-y-auto">
138
- <Sidebar nav={nav} currentPath={currentPath} />
142
+ <div className="bg-white dark:bg-[#0f0f0f] pt-16 w-full flex-1 transition-colors">
143
+ <div className="flex">
144
+ {/* Desktop sidebar - fixed width */}
145
+ <aside className="hidden lg:block w-72 flex-shrink-0 border-r border-gray-200 dark:border-gray-800 min-h-[calc(100vh-4rem)] transition-colors">
146
+ <nav className="sticky top-16 px-4 py-6 max-h-[calc(100vh-4rem)] overflow-y-auto">
147
+ <Sidebar nav={nav} currentPath={currentPath} docSets={docSets} currentDocSet={currentDocSet} />
139
148
  </nav>
140
149
  </aside>
141
150
 
142
151
  {/* Main content */}
143
- <main className="col-span-12 lg:col-span-9 xl:col-span-10 p-4 lg:px-10 lg:py-6">
144
- <article className="prose prose-lg max-w-3xl prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-3xl prose-h1:mb-4 prose-h2:text-2xl prose-h2:mt-10 first:prose-h2:mt-0 prose-h3:text-xl prose-a:text-primary-600 prose-a:no-underline hover:prose-a:underline prose-code:bg-gray-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none">
152
+ <main className="flex-1 min-w-0 p-4 lg:px-10 lg:py-6">
153
+ <article className="prose prose-lg max-w-3xl prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-3xl prose-h1:mb-4 prose-h2:text-2xl prose-h2:mt-10 first:prose-h2:mt-0 prose-h3:text-xl prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-code:bg-gray-100 dark:prose-code:bg-gray-800 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none dark:prose-headings:text-white dark:prose-strong:text-white dark:text-gray-300">
145
154
  {children}
146
155
  </article>
147
156
  </main>
@@ -150,12 +159,12 @@ export function DocsLayout({
150
159
 
151
160
  {/* Footer */}
152
161
  {footer || (
153
- <footer className="border-t border-gray-200 py-8">
162
+ <footer className="border-t border-gray-200 dark:border-gray-800 py-8 bg-white dark:bg-[#0f0f0f] transition-colors">
154
163
  <div className="px-4 lg:px-10 flex flex-col md:flex-row justify-between items-center gap-6">
155
164
  {footerLogo && <Link href="/">{footerLogo}</Link>}
156
- <div className="flex gap-8 text-sm text-gray-600">
165
+ <div className="flex gap-8 text-sm text-gray-600 dark:text-gray-400">
157
166
  {navLinks.map((link) => (
158
- <Link key={link.href} href={link.href} className="hover:text-black transition-colors">
167
+ <Link key={link.href} href={link.href} className="hover:text-black dark:hover:text-white transition-colors">
159
168
  {link.label}
160
169
  </Link>
161
170
  ))}
@@ -164,7 +173,7 @@ export function DocsLayout({
164
173
  href={githubUrl}
165
174
  target="_blank"
166
175
  rel="noopener noreferrer"
167
- className="hover:text-black transition-colors"
176
+ className="hover:text-black dark:hover:text-white transition-colors"
168
177
  >
169
178
  GitHub
170
179
  </a>
@@ -1,5 +1,7 @@
1
1
  import { Head, Link } from '@inertiajs/react'
2
2
  import { createContext, useContext, useState, type ReactNode } from 'react'
3
+ import { ThemeToggle } from './ThemeToggle'
4
+ import { useTheme } from './ThemeProvider'
3
5
 
4
6
  // ============================================================================
5
7
  // Types
@@ -21,6 +23,7 @@ export interface HomePageContextValue {
21
23
  logoUrl?: string
22
24
  heroLogoUrl?: string
23
25
  footerLogoUrl?: string
26
+ footerLogoInvertedUrl?: string
24
27
  githubUrl?: string
25
28
  navLinks: Array<{ label: string; href: string }>
26
29
  }
@@ -78,9 +81,9 @@ function InstallCommand({ command }: { command: string }) {
78
81
  return (
79
82
  <button
80
83
  onClick={copyToClipboard}
81
- className="group relative flex items-center bg-black border border-black px-4 h-14 font-mono text-sm text-white hover:bg-white hover:text-black transition-colors cursor-pointer"
84
+ className="group relative flex items-center bg-gray-900 dark:bg-white border border-gray-900 dark:border-white px-4 h-14 font-mono text-sm text-white dark:text-gray-900 hover:bg-white dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
82
85
  >
83
- <span className="text-primary-500 mr-2">$</span>
86
+ <span className="text-primary-500 dark:text-primary-600 mr-2">$</span>
84
87
  <span>{command}</span>
85
88
  <svg
86
89
  className={`ml-4 w-4 h-4 transition ${copied ? 'text-green-400' : 'opacity-50 group-hover:opacity-100'}`}
@@ -96,7 +99,7 @@ function InstallCommand({ command }: { command: string }) {
96
99
  />
97
100
  </svg>
98
101
  <span
99
- className={`absolute -top-8 left-1/2 -translate-x-1/2 bg-black text-white text-xs py-1 px-2 rounded transition-opacity duration-300 whitespace-nowrap ${
102
+ className={`absolute -top-8 left-1/2 -translate-x-1/2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 text-xs py-1 px-2 rounded transition-opacity duration-300 whitespace-nowrap ${
100
103
  copied ? 'opacity-100' : 'opacity-0'
101
104
  }`}
102
105
  >
@@ -137,7 +140,7 @@ function DefaultLogo() {
137
140
  }
138
141
 
139
142
  return (
140
- <Link href="/" className="font-bold text-lg">
143
+ <Link href="/" className="font-bold text-lg text-gray-900 dark:text-white">
141
144
  {title}
142
145
  </Link>
143
146
  )
@@ -151,16 +154,20 @@ export function HomeHeader({ renderLogo }: HomeHeaderProps = {}) {
151
154
  const { navLinks, githubUrl } = useHomePage()
152
155
 
153
156
  return (
154
- <nav className="fixed w-full z-50 bg-white border-b border-gray-200">
157
+ <nav className="fixed w-full z-50 bg-white/95 dark:bg-[#0f0f0f]/95 backdrop-blur-sm border-b border-gray-200 dark:border-gray-800 transition-colors">
155
158
  <div className="px-4 lg:px-10">
156
159
  <div className="flex justify-between h-16 items-center">
157
160
  {renderLogo ? renderLogo() : <DefaultLogo />}
158
- <div className="flex items-center space-x-8">
161
+ <div className="flex items-center gap-6">
162
+ <div className="-mr-2">
163
+ <ThemeToggle size="sm" />
164
+ </div>
165
+
159
166
  {navLinks.map((link) => (
160
167
  <Link
161
168
  key={link.href}
162
169
  href={link.href}
163
- className="text-black font-medium hover:text-primary-500 transition-colors"
170
+ className="hidden sm:block text-gray-700 dark:text-gray-300 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
164
171
  >
165
172
  {link.label}
166
173
  </Link>
@@ -170,7 +177,7 @@ export function HomeHeader({ renderLogo }: HomeHeaderProps = {}) {
170
177
  href={githubUrl}
171
178
  target="_blank"
172
179
  rel="noopener noreferrer"
173
- className="text-black hover:text-primary-500 transition-colors"
180
+ className="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
174
181
  >
175
182
  <GitHubIcon />
176
183
  </a>
@@ -193,7 +200,7 @@ export function HomeHero() {
193
200
  <section className="pt-16">
194
201
  <div className="px-4 lg:px-10 py-16 lg:py-24">
195
202
  <div className="max-w-4xl">
196
- <div className="mb-4 text-sm font-mono uppercase tracking-widest text-gray-500">
203
+ <div className="mb-4 text-sm font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400">
197
204
  {tagline}
198
205
  </div>
199
206
  {heroLogoUrl ? (
@@ -205,18 +212,18 @@ export function HomeHero() {
205
212
  />
206
213
  </h1>
207
214
  ) : (
208
- <h1 className="text-5xl lg:text-7xl font-bold tracking-tight mb-6">
215
+ <h1 className="text-5xl lg:text-7xl font-bold tracking-tight mb-6 text-gray-900 dark:text-white">
209
216
  {title}
210
217
  </h1>
211
218
  )}
212
- <p className="text-xl lg:text-2xl text-gray-700 max-w-2xl leading-relaxed mb-8">
219
+ <p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 max-w-2xl leading-relaxed mb-8">
213
220
  {description}
214
221
  </p>
215
222
 
216
223
  <div className="flex flex-col sm:flex-row gap-3">
217
224
  <Link
218
225
  href={ctaHref}
219
- className="inline-flex items-center justify-center px-8 h-14 bg-black text-white font-bold text-lg hover:bg-primary-500 transition-colors border border-black"
226
+ className="inline-flex items-center justify-center px-8 h-14 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-bold text-lg hover:bg-primary-500 dark:hover:bg-primary-500 dark:hover:text-white transition-colors border border-gray-900 dark:border-white hover:border-primary-500 dark:hover:border-primary-500"
220
227
  >
221
228
  {ctaText}
222
229
  </Link>
@@ -234,17 +241,17 @@ export function HomeHero() {
234
241
  export function HomeFeatureItem({ feature, index, totalFeatures }: HomeFeatureItemProps) {
235
242
  return (
236
243
  <div
237
- className={`p-4 lg:p-10 border-b sm:border-b border-gray-200 ${
244
+ className={`p-4 lg:p-10 border-b sm:border-b border-gray-200 dark:border-gray-800 ${
238
245
  index % 2 === 0 ? 'sm:border-r' : ''
239
246
  } ${index >= totalFeatures - 2 ? 'sm:border-b-0' : ''} ${
240
247
  index === totalFeatures - 1 && totalFeatures % 2 === 1 ? 'border-b-0' : ''
241
- }`}
248
+ } transition-colors`}
242
249
  >
243
- <div className="text-5xl font-bold text-primary-500 mb-4">
250
+ <div className="text-5xl font-bold text-primary-500 dark:text-primary-400 mb-4">
244
251
  {String(index + 1).padStart(2, '0')}
245
252
  </div>
246
- <h3 className="text-xl font-bold mb-2">{feature.title}</h3>
247
- <p className="text-gray-600">{feature.description}</p>
253
+ <h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">{feature.title}</h3>
254
+ <p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
248
255
  </div>
249
256
  )
250
257
  }
@@ -260,13 +267,13 @@ export function HomeFeatures({ renderFeature }: HomeFeaturesProps = {}) {
260
267
  }
261
268
 
262
269
  return (
263
- <section className="border-t border-gray-200">
270
+ <section className="border-t border-gray-200 dark:border-gray-800 transition-colors">
264
271
  <div className="grid grid-cols-12">
265
- <div className="col-span-12 lg:col-span-4 p-4 lg:p-10 border-b lg:border-b-0 lg:border-r border-gray-200">
266
- <div className="text-sm font-mono uppercase tracking-widest text-gray-500 mb-4">
272
+ <div className="col-span-12 lg:col-span-4 p-4 lg:p-10 border-b lg:border-b-0 lg:border-r border-gray-200 dark:border-gray-800 transition-colors">
273
+ <div className="text-sm font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400 mb-4">
267
274
  Features
268
275
  </div>
269
- <h2 className="text-4xl lg:text-5xl font-bold tracking-tight">
276
+ <h2 className="text-4xl lg:text-5xl font-bold tracking-tight text-gray-900 dark:text-white">
270
277
  Why {title}?
271
278
  </h2>
272
279
  </div>
@@ -299,27 +306,27 @@ export function HomeCTA() {
299
306
  const { ctaHref } = useHomePage()
300
307
 
301
308
  return (
302
- <section className="border-t border-gray-200">
309
+ <section className="border-t border-gray-200 dark:border-gray-800 transition-colors">
303
310
  <div className="grid grid-cols-12 items-center">
304
311
  <div className="col-span-12 lg:col-span-8 p-4 lg:p-10">
305
- <h2 className="text-4xl lg:text-6xl font-bold tracking-tight mb-4">
312
+ <h2 className="text-4xl lg:text-6xl font-bold tracking-tight mb-4 text-gray-900 dark:text-white">
306
313
  Ready to start?
307
314
  </h2>
308
- <p className="text-xl text-gray-600 mb-8 max-w-2xl">
315
+ <p className="text-xl text-gray-600 dark:text-gray-300 mb-8 max-w-2xl">
309
316
  Get up and running in minutes. Check out our documentation to learn more.
310
317
  </p>
311
318
  <Link
312
319
  href={ctaHref}
313
- className="inline-flex items-center justify-center px-8 py-4 bg-primary-500 text-white font-bold text-lg hover:bg-black transition-colors border border-primary-500 hover:border-black"
320
+ className="inline-flex items-center justify-center px-8 py-4 bg-primary-500 text-white font-bold text-lg hover:bg-gray-900 dark:hover:bg-white dark:hover:text-gray-900 transition-colors border border-primary-500 hover:border-gray-900 dark:hover:border-white"
314
321
  >
315
322
  Read the Docs
316
323
  </Link>
317
324
  </div>
318
325
  <Link
319
326
  href={ctaHref}
320
- className="col-span-12 lg:col-span-4 h-full bg-primary-500 hidden lg:flex items-center justify-center p-4 lg:p-10 hover:bg-black transition-colors min-h-[200px]"
327
+ className="col-span-12 lg:col-span-4 h-full bg-primary-500 hidden lg:flex items-center justify-center p-4 lg:p-10 hover:bg-gray-900 dark:hover:bg-white transition-colors min-h-[200px] group"
321
328
  >
322
- <div className="text-white text-8xl font-bold">&rarr;</div>
329
+ <div className="text-white group-hover:text-white dark:group-hover:text-gray-900 text-8xl font-bold transition-colors">&rarr;</div>
323
330
  </Link>
324
331
  </div>
325
332
  </section>
@@ -330,19 +337,25 @@ export function HomeCTA() {
330
337
  * Footer section.
331
338
  */
332
339
  export function HomeFooter() {
333
- const { title, logoUrl, footerLogoUrl, navLinks, githubUrl } = useHomePage()
340
+ const { title, logoUrl, footerLogoUrl, footerLogoInvertedUrl, navLinks, githubUrl } = useHomePage()
341
+ const { resolvedTheme } = useTheme()
342
+
343
+ // Select appropriate logo based on theme
344
+ const currentLogoUrl = resolvedTheme === 'dark'
345
+ ? (footerLogoInvertedUrl || footerLogoUrl || logoUrl)
346
+ : (footerLogoUrl || logoUrl)
334
347
 
335
348
  return (
336
- <footer className="border-t border-gray-200 py-8">
349
+ <footer className="border-t border-gray-200 dark:border-gray-800 py-8 bg-white dark:bg-[#0f0f0f] transition-colors">
337
350
  <div className="px-4 lg:px-10 flex flex-col md:flex-row justify-between items-center gap-6">
338
- {(footerLogoUrl || logoUrl) && (
351
+ {currentLogoUrl && (
339
352
  <Link href="/">
340
- <img src={footerLogoUrl || logoUrl} alt={title} className="h-6" />
353
+ <img src={currentLogoUrl} alt={title} className="h-6" />
341
354
  </Link>
342
355
  )}
343
- <div className="flex gap-8 text-sm text-gray-600">
356
+ <div className="flex gap-8 text-sm text-gray-600 dark:text-gray-300">
344
357
  {navLinks.map((link) => (
345
- <Link key={link.href} href={link.href} className="hover:text-black transition-colors">
358
+ <Link key={link.href} href={link.href} className="hover:text-black dark:hover:text-white transition-colors">
346
359
  {link.label}
347
360
  </Link>
348
361
  ))}
@@ -351,7 +364,7 @@ export function HomeFooter() {
351
364
  href={githubUrl}
352
365
  target="_blank"
353
366
  rel="noopener noreferrer"
354
- className="hover:text-black transition-colors"
367
+ className="hover:text-black dark:hover:text-white transition-colors"
355
368
  >
356
369
  GitHub
357
370
  </a>
@@ -369,12 +382,21 @@ export function HomeFooter() {
369
382
  function HomeSpacer() {
370
383
  return (
371
384
  <div
372
- className="grow"
385
+ className="grow dark:hidden"
373
386
  style={{ backgroundImage: 'linear-gradient(rgb(229, 231, 235) 1px, transparent 1px)' }}
374
387
  />
375
388
  )
376
389
  }
377
390
 
391
+ function HomeSpacerDark() {
392
+ return (
393
+ <div
394
+ className="grow hidden dark:block"
395
+ style={{ backgroundImage: 'linear-gradient(rgb(38, 38, 38) 1px, transparent 1px)' }}
396
+ />
397
+ )
398
+ }
399
+
378
400
  function DefaultHomeLayout() {
379
401
  return (
380
402
  <>
@@ -383,6 +405,7 @@ function DefaultHomeLayout() {
383
405
  <HomeFeatures />
384
406
  <HomeCTA />
385
407
  <HomeSpacer />
408
+ <HomeSpacerDark />
386
409
  <HomeFooter />
387
410
  </>
388
411
  )
@@ -428,7 +451,7 @@ export function HomePage({
428
451
 
429
452
  return (
430
453
  <HomePageContext.Provider value={contextValue}>
431
- <div className="min-h-screen bg-white flex flex-col">
454
+ <div className="min-h-screen bg-white dark:bg-[#0f0f0f] flex flex-col transition-colors duration-200">
432
455
  <Head title={props.title} />
433
456
  {children || <DefaultHomeLayout />}
434
457
  </div>