Hanzo Dev commited on
Commit Β·
cabb811
1
Parent(s): a5493e9
feat: Complete @hanzo /ui integration with proper imports
Browse files- Use @hanzo /ui/primitives for all component imports
- Fix button variant names (primary instead of default)
- Add all missing template properties (rating, featured, installCmd, etc)
- Create local Logo components since not exported from @hanzo /ui
- Update tsconfig for bundler module resolution
- Add react-hook-form and date-fns dependencies
- Set dark mode by default with pure black (#000) background
- Fix template data with proper types
- app/layout.tsx +2 -2
- app/page.tsx +323 -391
- components/hanzo-brand.tsx +3 -12
- components/logo.tsx +30 -0
- components/template-data.ts +86 -48
- components/ui-components.tsx +3 -79
- package-lock.json +28 -0
- package.json +2 -0
- scripts/screenshot-templates.js +91 -0
- templates/devforge.tsx +2 -2
- templates/mobilefirst.tsx +2 -2
- templates/saasify.tsx +2 -2
- tsconfig.json +2 -1
app/layout.tsx
CHANGED
|
@@ -15,8 +15,8 @@ export default function RootLayout({
|
|
| 15 |
children: React.ReactNode
|
| 16 |
}) {
|
| 17 |
return (
|
| 18 |
-
<html lang="en">
|
| 19 |
-
<body className={inter.className}>
|
| 20 |
{children}
|
| 21 |
</body>
|
| 22 |
</html>
|
|
|
|
| 15 |
children: React.ReactNode
|
| 16 |
}) {
|
| 17 |
return (
|
| 18 |
+
<html lang="en" className="dark">
|
| 19 |
+
<body className={`${inter.className} bg-black text-white`}>
|
| 20 |
{children}
|
| 21 |
</body>
|
| 22 |
</html>
|
app/page.tsx
CHANGED
|
@@ -2,12 +2,33 @@
|
|
| 2 |
|
| 3 |
import { useState, useEffect } from 'react'
|
| 4 |
import { templates, categories, type Template } from '@/components/template-data'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import {
|
| 6 |
Search,
|
| 7 |
Grid3x3,
|
| 8 |
List,
|
| 9 |
-
Moon,
|
| 10 |
-
Sun,
|
| 11 |
Sparkles,
|
| 12 |
Code2,
|
| 13 |
Palette,
|
|
@@ -42,465 +63,376 @@ export default function GalleryPage() {
|
|
| 42 |
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
| 43 |
const [searchQuery, setSearchQuery] = useState('')
|
| 44 |
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null)
|
| 45 |
-
const [isDark, setIsDark] = useState(false)
|
| 46 |
const [showFilters, setShowFilters] = useState(false)
|
| 47 |
const [copiedId, setCopiedId] = useState<string | null>(null)
|
| 48 |
const [favorites, setFavorites] = useState<string[]>([])
|
| 49 |
const [showScrollTop, setShowScrollTop] = useState(false)
|
| 50 |
|
| 51 |
// Filter templates based on search and category
|
| 52 |
-
const filteredTemplates = templates.filter(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
const matchesCategory = selectedCategory === 'all' || template.category === selectedCategory
|
| 54 |
-
const matchesSearch = template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
| 55 |
-
template.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
| 56 |
-
template.tagline.toLowerCase().includes(searchQuery.toLowerCase())
|
| 57 |
-
return matchesCategory && matchesSearch
|
| 58 |
-
})
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
if (isDark) {
|
| 63 |
-
document.documentElement.classList.add('dark')
|
| 64 |
-
} else {
|
| 65 |
-
document.documentElement.classList.remove('dark')
|
| 66 |
-
}
|
| 67 |
-
}, [isDark])
|
| 68 |
|
| 69 |
-
//
|
| 70 |
useEffect(() => {
|
| 71 |
const handleScroll = () => {
|
| 72 |
-
setShowScrollTop(window.scrollY >
|
| 73 |
}
|
| 74 |
window.addEventListener('scroll', handleScroll)
|
| 75 |
return () => window.removeEventListener('scroll', handleScroll)
|
| 76 |
}, [])
|
| 77 |
|
| 78 |
-
const
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
setCopiedId(id)
|
| 81 |
setTimeout(() => setCopiedId(null), 2000)
|
| 82 |
}
|
| 83 |
|
| 84 |
const toggleFavorite = (id: string) => {
|
| 85 |
setFavorites(prev =>
|
| 86 |
-
prev.includes(id)
|
|
|
|
|
|
|
| 87 |
)
|
| 88 |
}
|
| 89 |
|
| 90 |
-
const
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
|
|
|
|
|
|
| 99 |
{/* Header */}
|
| 100 |
-
<header className="sticky top-0 z-50
|
| 101 |
-
<div className="
|
| 102 |
<div className="flex items-center justify-between">
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
<div className="relative">
|
| 106 |
-
<
|
| 107 |
-
<
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</div>
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
</div>
|
| 119 |
-
</div>
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
<button
|
| 126 |
onClick={() => setViewMode('grid')}
|
| 127 |
-
className={
|
| 128 |
-
viewMode === 'grid'
|
| 129 |
-
? 'bg-white dark:bg-slate-700 shadow-sm'
|
| 130 |
-
: 'hover:bg-white/50 dark:hover:bg-slate-700/50'
|
| 131 |
-
}`}
|
| 132 |
>
|
| 133 |
-
<Grid3x3 className="
|
| 134 |
-
</
|
| 135 |
-
<
|
|
|
|
|
|
|
| 136 |
onClick={() => setViewMode('list')}
|
| 137 |
-
className={
|
| 138 |
-
viewMode === 'list'
|
| 139 |
-
? 'bg-white dark:bg-slate-700 shadow-sm'
|
| 140 |
-
: 'hover:bg-white/50 dark:hover:bg-slate-700/50'
|
| 141 |
-
}`}
|
| 142 |
>
|
| 143 |
-
<List className="
|
| 144 |
-
</
|
| 145 |
</div>
|
| 146 |
-
|
| 147 |
-
{/* Dark Mode Toggle */}
|
| 148 |
-
<button
|
| 149 |
-
onClick={() => setIsDark(!isDark)}
|
| 150 |
-
className="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
| 151 |
-
>
|
| 152 |
-
{isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
| 153 |
-
</button>
|
| 154 |
-
|
| 155 |
-
{/* Filter Toggle (Mobile) */}
|
| 156 |
-
<button
|
| 157 |
-
onClick={() => setShowFilters(!showFilters)}
|
| 158 |
-
className="sm:hidden p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
| 159 |
-
>
|
| 160 |
-
<Filter className="h-5 w-5" />
|
| 161 |
-
</button>
|
| 162 |
-
</div>
|
| 163 |
-
</div>
|
| 164 |
-
|
| 165 |
-
{/* Search Bar */}
|
| 166 |
-
<div className="mt-4">
|
| 167 |
-
<div className="relative">
|
| 168 |
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
| 169 |
-
<input
|
| 170 |
-
type="text"
|
| 171 |
-
placeholder="Search templates..."
|
| 172 |
-
value={searchQuery}
|
| 173 |
-
onChange={(e) => setSearchQuery(e.target.value)}
|
| 174 |
-
className="w-full pl-10 pr-4 py-2 rounded-lg bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
| 175 |
-
/>
|
| 176 |
</div>
|
| 177 |
</div>
|
| 178 |
</div>
|
| 179 |
-
</
|
| 180 |
-
|
| 181 |
-
{/*
|
| 182 |
-
<
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
<
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
{/* Quick Stats */}
|
| 213 |
-
<div className="mt-8 p-4 rounded-lg bg-gradient-to-r from-blue-500/5 to-purple-500/5 border border-slate-200 dark:border-slate-800">
|
| 214 |
-
<h3 className="text-sm font-semibold mb-3">Quick Stats</h3>
|
| 215 |
-
<div className="space-y-2 text-sm">
|
| 216 |
-
<div className="flex items-center justify-between">
|
| 217 |
-
<span className="text-muted-foreground">Total Templates</span>
|
| 218 |
-
<span className="font-medium">{templates.length}</span>
|
| 219 |
</div>
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
</div>
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
</div>
|
| 228 |
</div>
|
| 229 |
-
</div>
|
| 230 |
-
</div>
|
| 231 |
-
</aside>
|
| 232 |
-
|
| 233 |
-
{/* Template Grid/List */}
|
| 234 |
-
<div className="flex-1">
|
| 235 |
-
{/* Results Header */}
|
| 236 |
-
<div className="flex items-center justify-between mb-6">
|
| 237 |
-
<div>
|
| 238 |
-
<h2 className="text-lg font-semibold">
|
| 239 |
-
{filteredTemplates.length} Templates Found
|
| 240 |
-
</h2>
|
| 241 |
-
<p className="text-sm text-muted-foreground">
|
| 242 |
-
{selectedCategory !== 'all' && `in ${categories.find(c => c.id === selectedCategory)?.name}`}
|
| 243 |
-
</p>
|
| 244 |
-
</div>
|
| 245 |
-
<select className="px-3 py-1.5 rounded-lg bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-sm">
|
| 246 |
-
<option>Most Popular</option>
|
| 247 |
-
<option>Newest First</option>
|
| 248 |
-
<option>Price: Low to High</option>
|
| 249 |
-
<option>Price: High to Low</option>
|
| 250 |
-
</select>
|
| 251 |
-
</div>
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
<div className="absolute top-3 right-3 z-10">
|
| 269 |
-
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-gradient-to-r from-green-400 to-emerald-400 text-white">
|
| 270 |
-
NEW
|
| 271 |
-
</span>
|
| 272 |
-
</div>
|
| 273 |
-
)}
|
| 274 |
-
|
| 275 |
-
{/* Gradient Header */}
|
| 276 |
-
<div className={`relative ${viewMode === 'list' ? 'w-48' : 'h-32'} bg-gradient-to-br ${template.gradient} p-6 flex items-center justify-center`}>
|
| 277 |
-
<span className="text-5xl animate-float">{template.icon}</span>
|
| 278 |
-
{/* Popularity Indicator */}
|
| 279 |
-
<div className="absolute bottom-2 right-2 flex items-center gap-1 bg-black/20 backdrop-blur-sm px-2 py-1 rounded-full">
|
| 280 |
-
<TrendingUp className="h-3 w-3 text-white" />
|
| 281 |
-
<span className="text-xs text-white font-medium">{template.popularity}%</span>
|
| 282 |
</div>
|
| 283 |
</div>
|
| 284 |
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
{/* Tech Stack */}
|
| 305 |
-
<div className="flex flex-wrap gap-1 mb-4">
|
| 306 |
-
{template.tech.slice(0, 3).map((tech) => (
|
| 307 |
-
<span key={tech} className="px-2 py-1 text-xs bg-slate-100 dark:bg-slate-800 rounded">
|
| 308 |
-
{tech}
|
| 309 |
-
</span>
|
| 310 |
))}
|
| 311 |
-
{template.tech.length > 3 && (
|
| 312 |
-
<span className="px-2 py-1 text-xs text-muted-foreground">
|
| 313 |
-
+{template.tech.length - 3} more
|
| 314 |
-
</span>
|
| 315 |
-
)}
|
| 316 |
</div>
|
| 317 |
|
| 318 |
-
{/*
|
| 319 |
-
<div className="
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
</div>
|
| 327 |
|
| 328 |
{/* Actions */}
|
| 329 |
-
<div className="flex
|
| 330 |
-
<
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
</span>
|
| 333 |
-
<div className="flex gap-2">
|
| 334 |
-
<button
|
| 335 |
-
onClick={() => window.location.href = `/template/${template.id}`}
|
| 336 |
-
className="px-3 py-1.5 text-xs font-medium bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg hover:opacity-90 transition-opacity flex items-center gap-1"
|
| 337 |
-
>
|
| 338 |
-
<Eye className="h-3 w-3" />
|
| 339 |
-
Preview
|
| 340 |
-
</button>
|
| 341 |
-
<button
|
| 342 |
-
onClick={() => setSelectedTemplate(template)}
|
| 343 |
-
className="px-3 py-1.5 text-xs font-medium bg-slate-100 dark:bg-slate-800 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors flex items-center gap-1"
|
| 344 |
-
>
|
| 345 |
-
<Code2 className="h-3 w-3" />
|
| 346 |
-
Details
|
| 347 |
-
</button>
|
| 348 |
-
</div>
|
| 349 |
</div>
|
| 350 |
</div>
|
| 351 |
-
</
|
| 352 |
-
|
| 353 |
-
|
| 354 |
</div>
|
| 355 |
-
|
| 356 |
-
</
|
| 357 |
-
|
| 358 |
-
{/*
|
| 359 |
-
|
| 360 |
-
<div className="
|
| 361 |
-
<div className="
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
<
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
>
|
| 368 |
-
<X className="h-5 w-5 text-white" />
|
| 369 |
-
</button>
|
| 370 |
-
<div className="flex items-center gap-4">
|
| 371 |
-
<span className="text-7xl">{selectedTemplate.icon}</span>
|
| 372 |
-
<div className="text-white">
|
| 373 |
-
<h2 className="text-3xl font-bold mb-2">{selectedTemplate.name}</h2>
|
| 374 |
-
<p className="text-lg opacity-90">{selectedTemplate.tagline}</p>
|
| 375 |
-
<div className="flex items-center gap-4 mt-3">
|
| 376 |
-
<span className="px-3 py-1 bg-white/20 backdrop-blur rounded-full text-sm font-medium">
|
| 377 |
-
{selectedTemplate.category}
|
| 378 |
-
</span>
|
| 379 |
-
<span className="px-3 py-1 bg-white/20 backdrop-blur rounded-full text-sm font-medium">
|
| 380 |
-
{selectedTemplate.price}
|
| 381 |
-
</span>
|
| 382 |
-
</div>
|
| 383 |
-
</div>
|
| 384 |
-
</div>
|
| 385 |
</div>
|
| 386 |
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
<div className="grid grid-cols-2 gap-3">
|
| 398 |
-
{selectedTemplate.features.map((feature) => (
|
| 399 |
-
<div key={feature} className="flex items-center gap-3 p-3 rounded-lg bg-slate-50 dark:bg-slate-800">
|
| 400 |
-
<Check className="h-4 w-4 text-green-500 flex-shrink-0" />
|
| 401 |
-
<span className="text-sm">{feature}</span>
|
| 402 |
-
</div>
|
| 403 |
-
))}
|
| 404 |
-
</div>
|
| 405 |
-
</div>
|
| 406 |
-
|
| 407 |
-
{/* Tech Stack */}
|
| 408 |
-
<div className="mb-8">
|
| 409 |
-
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
| 410 |
-
<Code2 className="h-5 w-5 text-blue-500" />
|
| 411 |
-
Technology Stack
|
| 412 |
-
</h3>
|
| 413 |
-
<div className="flex flex-wrap gap-2">
|
| 414 |
-
{selectedTemplate.tech.map((tech) => (
|
| 415 |
-
<span key={tech} className="px-3 py-1.5 bg-gradient-to-r from-blue-500/10 to-purple-500/10 rounded-lg font-medium text-sm">
|
| 416 |
-
{tech}
|
| 417 |
-
</span>
|
| 418 |
-
))}
|
| 419 |
-
</div>
|
| 420 |
-
</div>
|
| 421 |
-
|
| 422 |
-
{/* Quick Start */}
|
| 423 |
-
<div className="mb-8">
|
| 424 |
-
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
| 425 |
-
<Rocket className="h-5 w-5 text-purple-500" />
|
| 426 |
-
Quick Start
|
| 427 |
-
</h3>
|
| 428 |
-
<div className="bg-slate-900 dark:bg-slate-950 rounded-lg p-4 font-mono text-sm">
|
| 429 |
-
<div className="flex items-center justify-between mb-2">
|
| 430 |
-
<span className="text-green-400"># Install and run</span>
|
| 431 |
-
<button
|
| 432 |
-
onClick={() => copyToClipboard(
|
| 433 |
-
`cd /Users/z/work/hanzo/templates/${selectedTemplate.id.replace('forge', 'tool').replace('first', '').replace('ify', '').replace('kit', '').replace('dash', '')}\nnpm install --legacy-peer-deps\nnpm run dev`,
|
| 434 |
-
selectedTemplate.id
|
| 435 |
-
)}
|
| 436 |
-
className="p-1.5 rounded hover:bg-slate-800 transition-colors"
|
| 437 |
-
>
|
| 438 |
-
{copiedId === selectedTemplate.id ? (
|
| 439 |
-
<Check className="h-4 w-4 text-green-500" />
|
| 440 |
-
) : (
|
| 441 |
-
<Copy className="h-4 w-4 text-slate-400" />
|
| 442 |
-
)}
|
| 443 |
-
</button>
|
| 444 |
-
</div>
|
| 445 |
-
<div className="text-slate-300">
|
| 446 |
-
<div>cd /Users/z/work/hanzo/templates/{selectedTemplate.id.replace('forge', 'tool').replace('first', '').replace('ify', '').replace('kit', '').replace('dash', '')}</div>
|
| 447 |
-
<div>npm install --legacy-peer-deps</div>
|
| 448 |
-
<div>npm run dev</div>
|
| 449 |
-
</div>
|
| 450 |
-
</div>
|
| 451 |
-
</div>
|
| 452 |
-
|
| 453 |
-
{/* Actions */}
|
| 454 |
-
<div className="flex gap-3">
|
| 455 |
-
<button
|
| 456 |
-
onClick={() => window.location.href = `/template/${selectedTemplate.id}`}
|
| 457 |
-
className="flex-1 px-6 py-3 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg font-medium hover:opacity-90 transition-opacity flex items-center justify-center gap-2"
|
| 458 |
-
>
|
| 459 |
-
<Play className="h-5 w-5" />
|
| 460 |
-
Launch Preview
|
| 461 |
-
</button>
|
| 462 |
-
<button className="px-6 py-3 bg-slate-100 dark:bg-slate-800 rounded-lg font-medium hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors flex items-center justify-center gap-2">
|
| 463 |
-
<Github className="h-5 w-5" />
|
| 464 |
-
View Code
|
| 465 |
-
</button>
|
| 466 |
-
<button className="px-6 py-3 bg-slate-100 dark:bg-slate-800 rounded-lg font-medium hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors flex items-center justify-center gap-2">
|
| 467 |
-
<Download className="h-5 w-5" />
|
| 468 |
-
Download
|
| 469 |
-
</button>
|
| 470 |
-
</div>
|
| 471 |
</div>
|
| 472 |
</div>
|
| 473 |
</div>
|
| 474 |
-
|
| 475 |
|
| 476 |
-
{/* Scroll to Top
|
| 477 |
{showScrollTop && (
|
| 478 |
-
<
|
|
|
|
|
|
|
| 479 |
onClick={scrollToTop}
|
| 480 |
-
className="fixed bottom-8 right-8
|
| 481 |
>
|
| 482 |
-
<ArrowUp className="
|
| 483 |
-
</
|
| 484 |
)}
|
| 485 |
-
|
| 486 |
-
{/* Floating Action Bar */}
|
| 487 |
-
<div className="fixed bottom-8 left-1/2 -translate-x-1/2 glass-effect px-4 py-2 rounded-full shadow-xl flex items-center gap-4">
|
| 488 |
-
<button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors">
|
| 489 |
-
<Globe className="h-5 w-5" />
|
| 490 |
-
</button>
|
| 491 |
-
<button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors">
|
| 492 |
-
<Users className="h-5 w-5" />
|
| 493 |
-
</button>
|
| 494 |
-
<button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors">
|
| 495 |
-
<BarChart3 className="h-5 w-5" />
|
| 496 |
-
</button>
|
| 497 |
-
<button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors">
|
| 498 |
-
<Shield className="h-5 w-5" />
|
| 499 |
-
</button>
|
| 500 |
-
<button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors">
|
| 501 |
-
<Settings className="h-5 w-5" />
|
| 502 |
-
</button>
|
| 503 |
-
</div>
|
| 504 |
</div>
|
| 505 |
)
|
| 506 |
}
|
|
|
|
| 2 |
|
| 3 |
import { useState, useEffect } from 'react'
|
| 4 |
import { templates, categories, type Template } from '@/components/template-data'
|
| 5 |
+
import {
|
| 6 |
+
Button,
|
| 7 |
+
Card,
|
| 8 |
+
CardContent,
|
| 9 |
+
CardDescription,
|
| 10 |
+
CardHeader,
|
| 11 |
+
CardTitle,
|
| 12 |
+
Badge,
|
| 13 |
+
Input,
|
| 14 |
+
Label,
|
| 15 |
+
Select,
|
| 16 |
+
SelectContent,
|
| 17 |
+
SelectItem,
|
| 18 |
+
SelectTrigger,
|
| 19 |
+
SelectValue,
|
| 20 |
+
Tabs,
|
| 21 |
+
TabsContent,
|
| 22 |
+
TabsList,
|
| 23 |
+
TabsTrigger,
|
| 24 |
+
ScrollArea,
|
| 25 |
+
Separator
|
| 26 |
+
} from '@hanzo/ui/primitives'
|
| 27 |
+
import { Logo, LogoText } from '@/components/logo'
|
| 28 |
import {
|
| 29 |
Search,
|
| 30 |
Grid3x3,
|
| 31 |
List,
|
|
|
|
|
|
|
| 32 |
Sparkles,
|
| 33 |
Code2,
|
| 34 |
Palette,
|
|
|
|
| 63 |
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
| 64 |
const [searchQuery, setSearchQuery] = useState('')
|
| 65 |
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null)
|
|
|
|
| 66 |
const [showFilters, setShowFilters] = useState(false)
|
| 67 |
const [copiedId, setCopiedId] = useState<string | null>(null)
|
| 68 |
const [favorites, setFavorites] = useState<string[]>([])
|
| 69 |
const [showScrollTop, setShowScrollTop] = useState(false)
|
| 70 |
|
| 71 |
// Filter templates based on search and category
|
| 72 |
+
const filteredTemplates = templates.filter(template => {
|
| 73 |
+
const matchesSearch = searchQuery === '' ||
|
| 74 |
+
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
| 75 |
+
template.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
| 76 |
+
template.tech.some(t => t.toLowerCase().includes(searchQuery.toLowerCase()))
|
| 77 |
+
|
| 78 |
const matchesCategory = selectedCategory === 'all' || template.category === selectedCategory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
return matchesSearch && matchesCategory
|
| 81 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
// Handle scroll to top
|
| 84 |
useEffect(() => {
|
| 85 |
const handleScroll = () => {
|
| 86 |
+
setShowScrollTop(window.scrollY > 400)
|
| 87 |
}
|
| 88 |
window.addEventListener('scroll', handleScroll)
|
| 89 |
return () => window.removeEventListener('scroll', handleScroll)
|
| 90 |
}, [])
|
| 91 |
|
| 92 |
+
const scrollToTop = () => {
|
| 93 |
+
window.scrollTo({ top: 0, behavior: 'smooth' })
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
const copyInstallCommand = (command: string, id: string) => {
|
| 97 |
+
navigator.clipboard.writeText(command)
|
| 98 |
setCopiedId(id)
|
| 99 |
setTimeout(() => setCopiedId(null), 2000)
|
| 100 |
}
|
| 101 |
|
| 102 |
const toggleFavorite = (id: string) => {
|
| 103 |
setFavorites(prev =>
|
| 104 |
+
prev.includes(id)
|
| 105 |
+
? prev.filter(fav => fav !== id)
|
| 106 |
+
: [...prev, id]
|
| 107 |
)
|
| 108 |
}
|
| 109 |
|
| 110 |
+
const getCategoryIcon = (category: string) => {
|
| 111 |
+
switch(category) {
|
| 112 |
+
case 'Development': return <Code2 className="w-4 h-4" />
|
| 113 |
+
case 'Business': return <BarChart3 className="w-4 h-4" />
|
| 114 |
+
case 'Creative': return <Palette className="w-4 h-4" />
|
| 115 |
+
default: return <Sparkles className="w-4 h-4" />
|
| 116 |
+
}
|
| 117 |
}
|
| 118 |
|
| 119 |
+
const getRandomGradient = (index: number) => {
|
| 120 |
+
const gradients = [
|
| 121 |
+
'from-blue-600 to-purple-600',
|
| 122 |
+
'from-green-600 to-teal-600',
|
| 123 |
+
'from-orange-600 to-red-600',
|
| 124 |
+
'from-pink-600 to-purple-600',
|
| 125 |
+
'from-yellow-600 to-orange-600',
|
| 126 |
+
'from-indigo-600 to-purple-600',
|
| 127 |
+
'from-teal-600 to-blue-600',
|
| 128 |
+
'from-red-600 to-pink-600'
|
| 129 |
+
]
|
| 130 |
+
return gradients[index % gradients.length]
|
| 131 |
+
}
|
| 132 |
|
| 133 |
+
return (
|
| 134 |
+
<div className="min-h-screen bg-black text-white">
|
| 135 |
{/* Header */}
|
| 136 |
+
<header className="sticky top-0 z-50 bg-black/80 backdrop-blur-lg border-b border-white/10">
|
| 137 |
+
<div className="container mx-auto px-4 py-4">
|
| 138 |
<div className="flex items-center justify-between">
|
| 139 |
+
<div className="flex items-center space-x-4">
|
| 140 |
+
<Logo className="h-8 w-8" />
|
| 141 |
+
<LogoText className="h-6" />
|
| 142 |
+
<Separator orientation="vertical" className="h-6" />
|
| 143 |
+
<span className="text-sm text-muted-foreground">Template Gallery</span>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<nav className="flex items-center space-x-4">
|
| 147 |
+
<Button variant="ghost" size="icon">
|
| 148 |
+
<Github className="w-5 h-5" />
|
| 149 |
+
</Button>
|
| 150 |
+
<Button variant="ghost" size="icon">
|
| 151 |
+
<Settings className="w-5 h-5" />
|
| 152 |
+
</Button>
|
| 153 |
+
</nav>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
</header>
|
| 157 |
+
|
| 158 |
+
{/* Hero Section */}
|
| 159 |
+
<section className="relative overflow-hidden py-20 px-4">
|
| 160 |
+
<div className="absolute inset-0 bg-grid-pattern opacity-10" />
|
| 161 |
+
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-black/50 to-black" />
|
| 162 |
+
|
| 163 |
+
<div className="container mx-auto relative z-10">
|
| 164 |
+
<div className="text-center max-w-4xl mx-auto">
|
| 165 |
+
<Badge variant="outline" className="mb-4 animate-pulse">
|
| 166 |
+
<Sparkles className="w-3 h-3 mr-1" />
|
| 167 |
+
100x Your Development Speed
|
| 168 |
+
</Badge>
|
| 169 |
+
|
| 170 |
+
<h1 className="text-5xl md:text-7xl font-bold mb-6 bg-gradient-to-r from-white to-gray-400 bg-clip-text text-transparent">
|
| 171 |
+
Hanzo AI App Templates
|
| 172 |
+
</h1>
|
| 173 |
+
|
| 174 |
+
<p className="text-xl text-gray-400 mb-12">
|
| 175 |
+
Production-ready templates powered by @hanzo/ui. Start with our battle-tested components,
|
| 176 |
+
ship faster with AI assistance, and scale with confidence.
|
| 177 |
+
</p>
|
| 178 |
+
|
| 179 |
+
{/* Search and Filters */}
|
| 180 |
+
<div className="max-w-2xl mx-auto space-y-4">
|
| 181 |
<div className="relative">
|
| 182 |
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
| 183 |
+
<Input
|
| 184 |
+
type="text"
|
| 185 |
+
placeholder="Search templates, technologies, or features..."
|
| 186 |
+
value={searchQuery}
|
| 187 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 188 |
+
className="pl-10 pr-4 py-3 bg-white/5 border-white/10 text-white placeholder-gray-500"
|
| 189 |
+
/>
|
| 190 |
</div>
|
| 191 |
+
|
| 192 |
+
<div className="flex flex-wrap gap-2 justify-center">
|
| 193 |
+
<Button
|
| 194 |
+
variant={selectedCategory === 'all' ? 'primary' : 'outline'}
|
| 195 |
+
onClick={() => setSelectedCategory('all')}
|
| 196 |
+
className="gap-2"
|
| 197 |
+
>
|
| 198 |
+
<Sparkles className="w-4 h-4" />
|
| 199 |
+
All Templates
|
| 200 |
+
<Badge variant="secondary">{templates.length}</Badge>
|
| 201 |
+
</Button>
|
| 202 |
+
|
| 203 |
+
{categories.map(category => (
|
| 204 |
+
<Button
|
| 205 |
+
key={category.id}
|
| 206 |
+
variant={selectedCategory === category.id ? 'primary' : 'outline'}
|
| 207 |
+
onClick={() => setSelectedCategory(category.id)}
|
| 208 |
+
className="gap-2"
|
| 209 |
+
>
|
| 210 |
+
{getCategoryIcon(category.name)}
|
| 211 |
+
{category.name}
|
| 212 |
+
<Badge variant="secondary">
|
| 213 |
+
{templates.filter(t => t.category === category.id).length}
|
| 214 |
+
</Badge>
|
| 215 |
+
</Button>
|
| 216 |
+
))}
|
| 217 |
</div>
|
|
|
|
| 218 |
|
| 219 |
+
<div className="flex justify-center gap-2">
|
| 220 |
+
<Button
|
| 221 |
+
variant="outline"
|
| 222 |
+
size="icon"
|
|
|
|
| 223 |
onClick={() => setViewMode('grid')}
|
| 224 |
+
className={viewMode === 'grid' ? 'bg-white/10' : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
>
|
| 226 |
+
<Grid3x3 className="w-4 h-4" />
|
| 227 |
+
</Button>
|
| 228 |
+
<Button
|
| 229 |
+
variant="outline"
|
| 230 |
+
size="icon"
|
| 231 |
onClick={() => setViewMode('list')}
|
| 232 |
+
className={viewMode === 'list' ? 'bg-white/10' : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
>
|
| 234 |
+
<List className="w-4 h-4" />
|
| 235 |
+
</Button>
|
| 236 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
</div>
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
+
</section>
|
| 241 |
+
|
| 242 |
+
{/* Templates Grid/List */}
|
| 243 |
+
<section className="container mx-auto px-4 py-12">
|
| 244 |
+
{filteredTemplates.length === 0 ? (
|
| 245 |
+
<div className="text-center py-20">
|
| 246 |
+
<p className="text-gray-400 text-lg">No templates found matching your criteria.</p>
|
| 247 |
+
<Button
|
| 248 |
+
variant="outline"
|
| 249 |
+
onClick={() => {
|
| 250 |
+
setSearchQuery('')
|
| 251 |
+
setSelectedCategory('all')
|
| 252 |
+
}}
|
| 253 |
+
className="mt-4"
|
| 254 |
+
>
|
| 255 |
+
Clear Filters
|
| 256 |
+
</Button>
|
| 257 |
+
</div>
|
| 258 |
+
) : (
|
| 259 |
+
<div className={viewMode === 'grid'
|
| 260 |
+
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6'
|
| 261 |
+
: 'space-y-4 max-w-4xl mx-auto'
|
| 262 |
+
}>
|
| 263 |
+
{filteredTemplates.map((template, index) => (
|
| 264 |
+
<Card
|
| 265 |
+
key={template.id}
|
| 266 |
+
className="group relative bg-white/5 border-white/10 hover:bg-white/10 transition-all duration-300 overflow-hidden"
|
| 267 |
+
>
|
| 268 |
+
{/* Preview Image Placeholder */}
|
| 269 |
+
<div className={`relative h-48 bg-gradient-to-br ${getRandomGradient(index)} opacity-20`}>
|
| 270 |
+
<div className="absolute inset-0 flex items-center justify-center">
|
| 271 |
+
<span className="text-6xl">{template.icon}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
</div>
|
| 273 |
+
|
| 274 |
+
{/* Quick Actions */}
|
| 275 |
+
<div className="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
| 276 |
+
<Button
|
| 277 |
+
variant="ghost"
|
| 278 |
+
size="icon"
|
| 279 |
+
className="bg-black/50 backdrop-blur"
|
| 280 |
+
onClick={() => toggleFavorite(template.id)}
|
| 281 |
+
>
|
| 282 |
+
<Heart className={`w-4 h-4 ${favorites.includes(template.id) ? 'fill-red-500 text-red-500' : ''}`} />
|
| 283 |
+
</Button>
|
| 284 |
+
<Button
|
| 285 |
+
variant="ghost"
|
| 286 |
+
size="icon"
|
| 287 |
+
className="bg-black/50 backdrop-blur"
|
| 288 |
+
>
|
| 289 |
+
<Share2 className="w-4 h-4" />
|
| 290 |
+
</Button>
|
| 291 |
</div>
|
| 292 |
+
|
| 293 |
+
{/* Rating Badge */}
|
| 294 |
+
<div className="absolute top-2 left-2">
|
| 295 |
+
<Badge className="bg-black/50 backdrop-blur">
|
| 296 |
+
<Star className="w-3 h-3 mr-1 fill-yellow-500 text-yellow-500" />
|
| 297 |
+
{template.rating}%
|
| 298 |
+
</Badge>
|
| 299 |
</div>
|
| 300 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
<CardHeader>
|
| 303 |
+
<div className="flex items-start justify-between">
|
| 304 |
+
<div>
|
| 305 |
+
<CardTitle className="text-xl flex items-center gap-2">
|
| 306 |
+
{template.name}
|
| 307 |
+
{template.featured && (
|
| 308 |
+
<Badge variant="secondary" className="animate-pulse">
|
| 309 |
+
<Zap className="w-3 h-3 mr-1" />
|
| 310 |
+
Featured
|
| 311 |
+
</Badge>
|
| 312 |
+
)}
|
| 313 |
+
</CardTitle>
|
| 314 |
+
<CardDescription className="text-gray-400 mt-1">
|
| 315 |
+
{template.description}
|
| 316 |
+
</CardDescription>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
</div>
|
| 318 |
</div>
|
| 319 |
|
| 320 |
+
<div className="flex flex-wrap gap-1 mt-3">
|
| 321 |
+
{template.tech.map((tech) => (
|
| 322 |
+
<Badge key={tech} variant="outline" className="text-xs">
|
| 323 |
+
{tech}
|
| 324 |
+
</Badge>
|
| 325 |
+
))}
|
| 326 |
+
</div>
|
| 327 |
+
</CardHeader>
|
| 328 |
+
|
| 329 |
+
<CardContent>
|
| 330 |
+
<div className="space-y-3">
|
| 331 |
+
{/* Features */}
|
| 332 |
+
<div className="space-y-1">
|
| 333 |
+
{template.features.slice(0, 3).map((feature, idx) => (
|
| 334 |
+
<div key={idx} className="flex items-center gap-2 text-sm text-gray-400">
|
| 335 |
+
<Check className="w-3 h-3 text-green-500" />
|
| 336 |
+
{feature}
|
| 337 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
</div>
|
| 340 |
|
| 341 |
+
{/* Install Command */}
|
| 342 |
+
<div className="relative group/cmd">
|
| 343 |
+
<code className="block p-2 bg-black/50 border border-white/10 rounded text-xs text-gray-400 pr-10">
|
| 344 |
+
{template.installCmd}
|
| 345 |
+
</code>
|
| 346 |
+
<Button
|
| 347 |
+
variant="ghost"
|
| 348 |
+
size="icon"
|
| 349 |
+
className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6"
|
| 350 |
+
onClick={() => copyInstallCommand(template.installCmd, template.id)}
|
| 351 |
+
>
|
| 352 |
+
{copiedId === template.id ? (
|
| 353 |
+
<Check className="w-3 h-3 text-green-500" />
|
| 354 |
+
) : (
|
| 355 |
+
<Copy className="w-3 h-3" />
|
| 356 |
+
)}
|
| 357 |
+
</Button>
|
| 358 |
</div>
|
| 359 |
|
| 360 |
{/* Actions */}
|
| 361 |
+
<div className="flex gap-2 pt-2">
|
| 362 |
+
<Button
|
| 363 |
+
variant="primary"
|
| 364 |
+
className="flex-1 gap-2"
|
| 365 |
+
onClick={() => window.location.href = `/template/${template.id}`}
|
| 366 |
+
>
|
| 367 |
+
<Eye className="w-4 h-4" />
|
| 368 |
+
Preview
|
| 369 |
+
</Button>
|
| 370 |
+
<Button
|
| 371 |
+
variant="outline"
|
| 372 |
+
className="flex-1 gap-2"
|
| 373 |
+
>
|
| 374 |
+
<Download className="w-4 h-4" />
|
| 375 |
+
Use Template
|
| 376 |
+
</Button>
|
| 377 |
+
</div>
|
| 378 |
+
|
| 379 |
+
{/* Stats */}
|
| 380 |
+
<div className="flex items-center justify-between pt-2 text-xs text-gray-500">
|
| 381 |
+
<span className="flex items-center gap-1">
|
| 382 |
+
<Clock className="w-3 h-3" />
|
| 383 |
+
{template.updatedAt}
|
| 384 |
+
</span>
|
| 385 |
+
<span className={`flex items-center gap-1 ${
|
| 386 |
+
template.pricing === 'Free' ? 'text-green-500' : 'text-yellow-500'
|
| 387 |
+
}`}>
|
| 388 |
+
{template.pricing === 'Free' ? 'Free' : template.pricing}
|
| 389 |
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
</div>
|
| 391 |
</div>
|
| 392 |
+
</CardContent>
|
| 393 |
+
</Card>
|
| 394 |
+
))}
|
| 395 |
</div>
|
| 396 |
+
)}
|
| 397 |
+
</section>
|
| 398 |
+
|
| 399 |
+
{/* Footer */}
|
| 400 |
+
<footer className="border-t border-white/10 mt-20">
|
| 401 |
+
<div className="container mx-auto px-4 py-8">
|
| 402 |
+
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
| 403 |
+
<div className="flex items-center gap-4">
|
| 404 |
+
<Logo className="h-6 w-6" />
|
| 405 |
+
<span className="text-sm text-gray-400">
|
| 406 |
+
Β© 2024 Hanzo AI. Built with @hanzo/ui
|
| 407 |
+
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
</div>
|
| 409 |
|
| 410 |
+
<div className="flex items-center gap-6 text-sm text-gray-400">
|
| 411 |
+
<a href="https://hanzo.ai" className="hover:text-white transition-colors">
|
| 412 |
+
hanzo.ai
|
| 413 |
+
</a>
|
| 414 |
+
<a href="https://github.com/hanzoai" className="hover:text-white transition-colors">
|
| 415 |
+
GitHub
|
| 416 |
+
</a>
|
| 417 |
+
<a href="https://docs.hanzo.ai" className="hover:text-white transition-colors">
|
| 418 |
+
Documentation
|
| 419 |
+
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
</div>
|
| 421 |
</div>
|
| 422 |
</div>
|
| 423 |
+
</footer>
|
| 424 |
|
| 425 |
+
{/* Scroll to Top */}
|
| 426 |
{showScrollTop && (
|
| 427 |
+
<Button
|
| 428 |
+
variant="primary"
|
| 429 |
+
size="icon"
|
| 430 |
onClick={scrollToTop}
|
| 431 |
+
className="fixed bottom-8 right-8 z-50 rounded-full shadow-lg animate-fade-in"
|
| 432 |
>
|
| 433 |
+
<ArrowUp className="w-4 h-4" />
|
| 434 |
+
</Button>
|
| 435 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
</div>
|
| 437 |
)
|
| 438 |
}
|
components/hanzo-brand.tsx
CHANGED
|
@@ -1,16 +1,7 @@
|
|
| 1 |
-
// Hanzo
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
export const HanzoLogo = ({ className }: { className?: string }) => (
|
| 5 |
-
<svg
|
| 6 |
-
viewBox="0 0 24 24"
|
| 7 |
-
className={cn("w-6 h-6", className)}
|
| 8 |
-
fill="currentColor"
|
| 9 |
-
>
|
| 10 |
-
<path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/>
|
| 11 |
-
</svg>
|
| 12 |
-
)
|
| 13 |
|
|
|
|
| 14 |
export const hanzoColors = {
|
| 15 |
background: '#000000',
|
| 16 |
foreground: '#ffffff',
|
|
|
|
| 1 |
+
// Re-export Hanzo logo components from local file
|
| 2 |
+
export { Logo, LogoText, LogoIcon, HanzoLogo, HanzoLogoText, HanzoLogoIcon } from './logo'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
+
// Hanzo brand colors (matching @hanzo/ui defaults - pure black background)
|
| 5 |
export const hanzoColors = {
|
| 6 |
background: '#000000',
|
| 7 |
foreground: '#ffffff',
|
components/logo.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Hanzo Logo Components
|
| 2 |
+
import { cn } from '@/lib/utils'
|
| 3 |
+
|
| 4 |
+
export const Logo = ({ className, ...props }: { className?: string } & React.SVGProps<SVGSVGElement>) => (
|
| 5 |
+
<svg
|
| 6 |
+
viewBox="0 0 24 24"
|
| 7 |
+
className={cn("w-6 h-6", className)}
|
| 8 |
+
fill="currentColor"
|
| 9 |
+
{...props}
|
| 10 |
+
>
|
| 11 |
+
<path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/>
|
| 12 |
+
</svg>
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
export const LogoText = ({ className, ...props }: { className?: string } & React.SVGProps<SVGSVGElement>) => (
|
| 16 |
+
<svg
|
| 17 |
+
viewBox="0 0 80 24"
|
| 18 |
+
className={cn("h-6", className)}
|
| 19 |
+
fill="currentColor"
|
| 20 |
+
{...props}
|
| 21 |
+
>
|
| 22 |
+
<text x="0" y="18" fontFamily="system-ui" fontSize="18" fontWeight="bold">HANZO</text>
|
| 23 |
+
</svg>
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
export const LogoIcon = Logo
|
| 27 |
+
|
| 28 |
+
export const HanzoLogo = Logo
|
| 29 |
+
export const HanzoLogoText = LogoText
|
| 30 |
+
export const HanzoLogoIcon = LogoIcon
|
components/template-data.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface Template {
|
|
| 16 |
demoUrl?: string
|
| 17 |
githubUrl?: string
|
| 18 |
price?: string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
|
| 21 |
export const templates: Template[] = [
|
|
@@ -28,11 +33,11 @@ export const templates: Template[] = [
|
|
| 28 |
port: 3001,
|
| 29 |
primaryColor: '#00d4ff',
|
| 30 |
secondaryColor: '#00ff88',
|
| 31 |
-
gradient: 'from-cyan-
|
| 32 |
features: [
|
| 33 |
-
'AI Code Generation',
|
| 34 |
'Real-time Collaboration',
|
| 35 |
-
'
|
| 36 |
'CI/CD Integration',
|
| 37 |
'Code Review Tools',
|
| 38 |
'Performance Monitoring'
|
|
@@ -41,7 +46,12 @@ export const templates: Template[] = [
|
|
| 41 |
category: 'development',
|
| 42 |
popularity: 95,
|
| 43 |
status: 'stable',
|
| 44 |
-
price: 'Free'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
},
|
| 46 |
{
|
| 47 |
id: 'mobilefirst',
|
|
@@ -50,13 +60,12 @@ export const templates: Template[] = [
|
|
| 50 |
description: 'Visual mobile app builder with cross-platform deployment',
|
| 51 |
icon: 'π±',
|
| 52 |
port: 3002,
|
| 53 |
-
primaryColor: '#
|
| 54 |
-
secondaryColor: '#
|
| 55 |
-
gradient: 'from-
|
| 56 |
features: [
|
| 57 |
'Drag & Drop Builder',
|
| 58 |
'Cross-Platform Export',
|
| 59 |
-
'Live Device Preview',
|
| 60 |
'Push Notifications',
|
| 61 |
'App Store Deploy',
|
| 62 |
'Analytics Dashboard'
|
|
@@ -65,7 +74,12 @@ export const templates: Template[] = [
|
|
| 65 |
category: 'development',
|
| 66 |
popularity: 88,
|
| 67 |
status: 'new',
|
| 68 |
-
price: '$29/mo'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
},
|
| 70 |
{
|
| 71 |
id: 'saasify',
|
|
@@ -74,13 +88,12 @@ export const templates: Template[] = [
|
|
| 74 |
description: 'Complete SaaS boilerplate with authentication, billing, and admin',
|
| 75 |
icon: 'βοΈ',
|
| 76 |
port: 3003,
|
| 77 |
-
primaryColor: '#
|
| 78 |
-
secondaryColor: '#
|
| 79 |
-
gradient: 'from-
|
| 80 |
features: [
|
| 81 |
-
'
|
| 82 |
-
'Stripe
|
| 83 |
-
'User Management',
|
| 84 |
'Admin Dashboard',
|
| 85 |
'Email Templates',
|
| 86 |
'API Documentation'
|
|
@@ -89,7 +102,12 @@ export const templates: Template[] = [
|
|
| 89 |
category: 'business',
|
| 90 |
popularity: 92,
|
| 91 |
status: 'stable',
|
| 92 |
-
price: '$99'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
},
|
| 94 |
{
|
| 95 |
id: 'startupkit',
|
|
@@ -98,13 +116,12 @@ export const templates: Template[] = [
|
|
| 98 |
description: 'Comprehensive toolkit for startup founders and teams',
|
| 99 |
icon: 'π',
|
| 100 |
port: 3004,
|
| 101 |
-
primaryColor: '#
|
| 102 |
-
secondaryColor: '#
|
| 103 |
-
gradient: 'from-
|
| 104 |
features: [
|
| 105 |
-
'
|
| 106 |
-
'Investor
|
| 107 |
-
'Financial Modeling',
|
| 108 |
'Team Workspace',
|
| 109 |
'Legal Templates',
|
| 110 |
'Growth Analytics'
|
|
@@ -113,7 +130,12 @@ export const templates: Template[] = [
|
|
| 113 |
category: 'business',
|
| 114 |
popularity: 79,
|
| 115 |
status: 'beta',
|
| 116 |
-
price: '$49/mo'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
},
|
| 118 |
{
|
| 119 |
id: 'analyticsdash',
|
|
@@ -122,12 +144,12 @@ export const templates: Template[] = [
|
|
| 122 |
description: 'Real-time analytics dashboard with custom metrics and reports',
|
| 123 |
icon: 'π',
|
| 124 |
port: 3005,
|
| 125 |
-
primaryColor: '#
|
| 126 |
-
secondaryColor: '#
|
| 127 |
-
gradient: 'from-
|
| 128 |
features: [
|
| 129 |
-
'Real-time
|
| 130 |
-
'Custom
|
| 131 |
'Data Visualization',
|
| 132 |
'Report Builder',
|
| 133 |
'API Access',
|
|
@@ -137,7 +159,12 @@ export const templates: Template[] = [
|
|
| 137 |
category: 'analytics',
|
| 138 |
popularity: 85,
|
| 139 |
status: 'stable',
|
| 140 |
-
price: '$79/mo'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
},
|
| 142 |
{
|
| 143 |
id: 'blog',
|
|
@@ -146,13 +173,12 @@ export const templates: Template[] = [
|
|
| 146 |
description: 'Professional blog with MDX support and content management',
|
| 147 |
icon: 'π',
|
| 148 |
port: 3006,
|
| 149 |
-
primaryColor: '#
|
| 150 |
secondaryColor: '#8b5cf6',
|
| 151 |
-
gradient: 'from-
|
| 152 |
features: [
|
| 153 |
'MDX Support',
|
| 154 |
'SEO Optimized',
|
| 155 |
-
'Newsletter Integration',
|
| 156 |
'Comment System',
|
| 157 |
'RSS Feed',
|
| 158 |
'Dark Mode'
|
|
@@ -161,7 +187,12 @@ export const templates: Template[] = [
|
|
| 161 |
category: 'content',
|
| 162 |
popularity: 90,
|
| 163 |
status: 'stable',
|
| 164 |
-
price: 'Free'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
},
|
| 166 |
{
|
| 167 |
id: 'changelog',
|
|
@@ -170,13 +201,11 @@ export const templates: Template[] = [
|
|
| 170 |
description: 'Keep users informed with beautiful changelogs',
|
| 171 |
icon: 'π',
|
| 172 |
port: 3007,
|
| 173 |
-
primaryColor: '#
|
| 174 |
-
secondaryColor: '#
|
| 175 |
-
gradient: 'from-
|
| 176 |
features: [
|
| 177 |
-
'Version
|
| 178 |
-
'Categories & Tags',
|
| 179 |
-
'Email Notifications',
|
| 180 |
'RSS Feed',
|
| 181 |
'Search & Filter',
|
| 182 |
'Markdown Support'
|
|
@@ -185,7 +214,12 @@ export const templates: Template[] = [
|
|
| 185 |
category: 'content',
|
| 186 |
popularity: 75,
|
| 187 |
status: 'stable',
|
| 188 |
-
price: 'Free'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
},
|
| 190 |
{
|
| 191 |
id: 'portfolio',
|
|
@@ -194,13 +228,12 @@ export const templates: Template[] = [
|
|
| 194 |
description: 'Personal portfolio for developers and designers',
|
| 195 |
icon: 'π€',
|
| 196 |
port: 3008,
|
| 197 |
-
primaryColor: '#
|
| 198 |
-
secondaryColor: '#
|
| 199 |
-
gradient: 'from-
|
| 200 |
features: [
|
| 201 |
-
'Resume Builder',
|
| 202 |
'Project Gallery',
|
| 203 |
-
'
|
| 204 |
'Contact Form',
|
| 205 |
'Social Links',
|
| 206 |
'Analytics'
|
|
@@ -209,7 +242,12 @@ export const templates: Template[] = [
|
|
| 209 |
category: 'creative',
|
| 210 |
popularity: 82,
|
| 211 |
status: 'stable',
|
| 212 |
-
price: 'Free'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
}
|
| 214 |
]
|
| 215 |
|
|
@@ -217,7 +255,7 @@ export const categories = [
|
|
| 217 |
{ id: 'all', name: 'All Templates', icon: 'π―' },
|
| 218 |
{ id: 'development', name: 'Development', icon: 'π»' },
|
| 219 |
{ id: 'business', name: 'Business', icon: 'πΌ' },
|
| 220 |
-
{ id: 'creative', name: 'Creative', icon: 'π¨' },
|
| 221 |
{ id: 'analytics', name: 'Analytics', icon: 'π' },
|
| 222 |
-
{ id: 'content', name: 'Content', icon: '
|
|
|
|
| 223 |
]
|
|
|
|
| 16 |
demoUrl?: string
|
| 17 |
githubUrl?: string
|
| 18 |
price?: string
|
| 19 |
+
rating: number
|
| 20 |
+
featured?: boolean
|
| 21 |
+
installCmd: string
|
| 22 |
+
updatedAt: string
|
| 23 |
+
pricing: 'Free' | 'Pro' | 'Enterprise'
|
| 24 |
}
|
| 25 |
|
| 26 |
export const templates: Template[] = [
|
|
|
|
| 33 |
port: 3001,
|
| 34 |
primaryColor: '#00d4ff',
|
| 35 |
secondaryColor: '#00ff88',
|
| 36 |
+
gradient: 'from-cyan-500 to-green-500',
|
| 37 |
features: [
|
| 38 |
+
'AI-Powered Code Generation',
|
| 39 |
'Real-time Collaboration',
|
| 40 |
+
'Built-in Testing Framework',
|
| 41 |
'CI/CD Integration',
|
| 42 |
'Code Review Tools',
|
| 43 |
'Performance Monitoring'
|
|
|
|
| 46 |
category: 'development',
|
| 47 |
popularity: 95,
|
| 48 |
status: 'stable',
|
| 49 |
+
price: 'Free',
|
| 50 |
+
rating: 100,
|
| 51 |
+
featured: true,
|
| 52 |
+
installCmd: 'npx create-hanzo-app --template devforge',
|
| 53 |
+
updatedAt: '2 days ago',
|
| 54 |
+
pricing: 'Free'
|
| 55 |
},
|
| 56 |
{
|
| 57 |
id: 'mobilefirst',
|
|
|
|
| 60 |
description: 'Visual mobile app builder with cross-platform deployment',
|
| 61 |
icon: 'π±',
|
| 62 |
port: 3002,
|
| 63 |
+
primaryColor: '#ff00d4',
|
| 64 |
+
secondaryColor: '#ffaa00',
|
| 65 |
+
gradient: 'from-pink-500 to-orange-500',
|
| 66 |
features: [
|
| 67 |
'Drag & Drop Builder',
|
| 68 |
'Cross-Platform Export',
|
|
|
|
| 69 |
'Push Notifications',
|
| 70 |
'App Store Deploy',
|
| 71 |
'Analytics Dashboard'
|
|
|
|
| 74 |
category: 'development',
|
| 75 |
popularity: 88,
|
| 76 |
status: 'new',
|
| 77 |
+
price: '$29/mo',
|
| 78 |
+
rating: 95,
|
| 79 |
+
featured: false,
|
| 80 |
+
installCmd: 'npx create-hanzo-app --template mobile',
|
| 81 |
+
updatedAt: 'Today',
|
| 82 |
+
pricing: 'Pro'
|
| 83 |
},
|
| 84 |
{
|
| 85 |
id: 'saasify',
|
|
|
|
| 88 |
description: 'Complete SaaS boilerplate with authentication, billing, and admin',
|
| 89 |
icon: 'βοΈ',
|
| 90 |
port: 3003,
|
| 91 |
+
primaryColor: '#8b5cf6',
|
| 92 |
+
secondaryColor: '#f59e0b',
|
| 93 |
+
gradient: 'from-violet-500 to-amber-500',
|
| 94 |
features: [
|
| 95 |
+
'Authentication & SSO',
|
| 96 |
+
'Stripe Billing',
|
|
|
|
| 97 |
'Admin Dashboard',
|
| 98 |
'Email Templates',
|
| 99 |
'API Documentation'
|
|
|
|
| 102 |
category: 'business',
|
| 103 |
popularity: 92,
|
| 104 |
status: 'stable',
|
| 105 |
+
price: '$99',
|
| 106 |
+
rating: 98,
|
| 107 |
+
featured: true,
|
| 108 |
+
installCmd: 'npx create-hanzo-app --template saas',
|
| 109 |
+
updatedAt: '1 week ago',
|
| 110 |
+
pricing: 'Pro'
|
| 111 |
},
|
| 112 |
{
|
| 113 |
id: 'startupkit',
|
|
|
|
| 116 |
description: 'Comprehensive toolkit for startup founders and teams',
|
| 117 |
icon: 'π',
|
| 118 |
port: 3004,
|
| 119 |
+
primaryColor: '#3b82f6',
|
| 120 |
+
secondaryColor: '#10b981',
|
| 121 |
+
gradient: 'from-blue-500 to-emerald-500',
|
| 122 |
features: [
|
| 123 |
+
'Landing Pages',
|
| 124 |
+
'Investor Dashboards',
|
|
|
|
| 125 |
'Team Workspace',
|
| 126 |
'Legal Templates',
|
| 127 |
'Growth Analytics'
|
|
|
|
| 130 |
category: 'business',
|
| 131 |
popularity: 79,
|
| 132 |
status: 'beta',
|
| 133 |
+
price: '$49/mo',
|
| 134 |
+
rating: 92,
|
| 135 |
+
featured: false,
|
| 136 |
+
installCmd: 'npx create-hanzo-app --template startup',
|
| 137 |
+
updatedAt: '3 days ago',
|
| 138 |
+
pricing: 'Pro'
|
| 139 |
},
|
| 140 |
{
|
| 141 |
id: 'analyticsdash',
|
|
|
|
| 144 |
description: 'Real-time analytics dashboard with custom metrics and reports',
|
| 145 |
icon: 'π',
|
| 146 |
port: 3005,
|
| 147 |
+
primaryColor: '#f97316',
|
| 148 |
+
secondaryColor: '#14b8a6',
|
| 149 |
+
gradient: 'from-orange-500 to-teal-500',
|
| 150 |
features: [
|
| 151 |
+
'Real-time Data',
|
| 152 |
+
'Custom Dashboards',
|
| 153 |
'Data Visualization',
|
| 154 |
'Report Builder',
|
| 155 |
'API Access',
|
|
|
|
| 159 |
category: 'analytics',
|
| 160 |
popularity: 85,
|
| 161 |
status: 'stable',
|
| 162 |
+
price: '$79/mo',
|
| 163 |
+
rating: 96,
|
| 164 |
+
featured: false,
|
| 165 |
+
installCmd: 'npx create-hanzo-app --template analytics',
|
| 166 |
+
updatedAt: '1 day ago',
|
| 167 |
+
pricing: 'Enterprise'
|
| 168 |
},
|
| 169 |
{
|
| 170 |
id: 'blog',
|
|
|
|
| 173 |
description: 'Professional blog with MDX support and content management',
|
| 174 |
icon: 'π',
|
| 175 |
port: 3006,
|
| 176 |
+
primaryColor: '#06b6d4',
|
| 177 |
secondaryColor: '#8b5cf6',
|
| 178 |
+
gradient: 'from-cyan-500 to-violet-500',
|
| 179 |
features: [
|
| 180 |
'MDX Support',
|
| 181 |
'SEO Optimized',
|
|
|
|
| 182 |
'Comment System',
|
| 183 |
'RSS Feed',
|
| 184 |
'Dark Mode'
|
|
|
|
| 187 |
category: 'content',
|
| 188 |
popularity: 90,
|
| 189 |
status: 'stable',
|
| 190 |
+
price: 'Free',
|
| 191 |
+
rating: 94,
|
| 192 |
+
featured: false,
|
| 193 |
+
installCmd: 'npx create-hanzo-app --template blog',
|
| 194 |
+
updatedAt: '5 days ago',
|
| 195 |
+
pricing: 'Free'
|
| 196 |
},
|
| 197 |
{
|
| 198 |
id: 'changelog',
|
|
|
|
| 201 |
description: 'Keep users informed with beautiful changelogs',
|
| 202 |
icon: 'π',
|
| 203 |
port: 3007,
|
| 204 |
+
primaryColor: '#84cc16',
|
| 205 |
+
secondaryColor: '#f43f5e',
|
| 206 |
+
gradient: 'from-lime-500 to-rose-500',
|
| 207 |
features: [
|
| 208 |
+
'Version Control',
|
|
|
|
|
|
|
| 209 |
'RSS Feed',
|
| 210 |
'Search & Filter',
|
| 211 |
'Markdown Support'
|
|
|
|
| 214 |
category: 'content',
|
| 215 |
popularity: 75,
|
| 216 |
status: 'stable',
|
| 217 |
+
price: 'Free',
|
| 218 |
+
rating: 91,
|
| 219 |
+
featured: false,
|
| 220 |
+
installCmd: 'npx create-hanzo-app --template changelog',
|
| 221 |
+
updatedAt: '1 week ago',
|
| 222 |
+
pricing: 'Free'
|
| 223 |
},
|
| 224 |
{
|
| 225 |
id: 'portfolio',
|
|
|
|
| 228 |
description: 'Personal portfolio for developers and designers',
|
| 229 |
icon: 'π€',
|
| 230 |
port: 3008,
|
| 231 |
+
primaryColor: '#ec4899',
|
| 232 |
+
secondaryColor: '#6366f1',
|
| 233 |
+
gradient: 'from-pink-500 to-indigo-500',
|
| 234 |
features: [
|
|
|
|
| 235 |
'Project Gallery',
|
| 236 |
+
'About Section',
|
| 237 |
'Contact Form',
|
| 238 |
'Social Links',
|
| 239 |
'Analytics'
|
|
|
|
| 242 |
category: 'creative',
|
| 243 |
popularity: 82,
|
| 244 |
status: 'stable',
|
| 245 |
+
price: 'Free',
|
| 246 |
+
rating: 97,
|
| 247 |
+
featured: false,
|
| 248 |
+
installCmd: 'npx create-hanzo-app --template portfolio',
|
| 249 |
+
updatedAt: '4 days ago',
|
| 250 |
+
pricing: 'Free'
|
| 251 |
}
|
| 252 |
]
|
| 253 |
|
|
|
|
| 255 |
{ id: 'all', name: 'All Templates', icon: 'π―' },
|
| 256 |
{ id: 'development', name: 'Development', icon: 'π»' },
|
| 257 |
{ id: 'business', name: 'Business', icon: 'πΌ' },
|
|
|
|
| 258 |
{ id: 'analytics', name: 'Analytics', icon: 'π' },
|
| 259 |
+
{ id: 'content', name: 'Content', icon: 'π' },
|
| 260 |
+
{ id: 'creative', name: 'Creative', icon: 'π¨' },
|
| 261 |
]
|
components/ui-components.tsx
CHANGED
|
@@ -1,79 +1,3 @@
|
|
| 1 |
-
//
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
export const Button = ({
|
| 5 |
-
children,
|
| 6 |
-
className,
|
| 7 |
-
variant = 'default',
|
| 8 |
-
size = 'default',
|
| 9 |
-
...props
|
| 10 |
-
}: any) => {
|
| 11 |
-
const variants: any = {
|
| 12 |
-
default: 'bg-white text-black hover:bg-gray-200',
|
| 13 |
-
ghost: 'bg-transparent hover:bg-white/10',
|
| 14 |
-
outline: 'border border-white text-white hover:bg-white hover:text-black'
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
const sizes: any = {
|
| 18 |
-
default: 'px-4 py-2',
|
| 19 |
-
sm: 'px-3 py-1',
|
| 20 |
-
lg: 'px-6 py-3'
|
| 21 |
-
}
|
| 22 |
-
|
| 23 |
-
return (
|
| 24 |
-
<button
|
| 25 |
-
className={cn(
|
| 26 |
-
'rounded-lg font-semibold transition-colors',
|
| 27 |
-
variants[variant],
|
| 28 |
-
sizes[size],
|
| 29 |
-
className
|
| 30 |
-
)}
|
| 31 |
-
{...props}
|
| 32 |
-
>
|
| 33 |
-
{children}
|
| 34 |
-
</button>
|
| 35 |
-
)
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
export const Card = ({ children, className, ...props }: any) => (
|
| 39 |
-
<div className={cn('rounded-xl', className)} {...props}>
|
| 40 |
-
{children}
|
| 41 |
-
</div>
|
| 42 |
-
)
|
| 43 |
-
|
| 44 |
-
export const CardHeader = ({ children, className, ...props }: any) => (
|
| 45 |
-
<div className={cn('p-6 pb-0', className)} {...props}>
|
| 46 |
-
{children}
|
| 47 |
-
</div>
|
| 48 |
-
)
|
| 49 |
-
|
| 50 |
-
export const CardTitle = ({ children, className, ...props }: any) => (
|
| 51 |
-
<h3 className={cn('text-lg font-semibold', className)} {...props}>
|
| 52 |
-
{children}
|
| 53 |
-
</h3>
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
export const CardDescription = ({ children, className, ...props }: any) => (
|
| 57 |
-
<p className={cn('text-sm text-gray-400', className)} {...props}>
|
| 58 |
-
{children}
|
| 59 |
-
</p>
|
| 60 |
-
)
|
| 61 |
-
|
| 62 |
-
export const CardContent = ({ children, className, ...props }: any) => (
|
| 63 |
-
<div className={cn('p-6', className)} {...props}>
|
| 64 |
-
{children}
|
| 65 |
-
</div>
|
| 66 |
-
)
|
| 67 |
-
|
| 68 |
-
export const Badge = ({ children, className, variant = 'default', ...props }: any) => (
|
| 69 |
-
<span
|
| 70 |
-
className={cn(
|
| 71 |
-
'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
|
| 72 |
-
variant === 'secondary' && 'bg-gray-100 text-gray-900',
|
| 73 |
-
className
|
| 74 |
-
)}
|
| 75 |
-
{...props}
|
| 76 |
-
>
|
| 77 |
-
{children}
|
| 78 |
-
</span>
|
| 79 |
-
)
|
|
|
|
| 1 |
+
// Import @hanzo/ui components - these are 1:1 with shadcn/ui
|
| 2 |
+
// @hanzo/ui exports from ./primitives subpath, not from root
|
| 3 |
+
export * from '@hanzo/ui/primitives'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package-lock.json
CHANGED
|
@@ -31,12 +31,14 @@
|
|
| 31 |
"@radix-ui/react-tooltip": "^1.1.0",
|
| 32 |
"class-variance-authority": "^0.7.1",
|
| 33 |
"clsx": "^2.1.1",
|
|
|
|
| 34 |
"framer-motion": "^11.18.2",
|
| 35 |
"lucide-react": "^0.456.0",
|
| 36 |
"next": "15.3.5",
|
| 37 |
"next-themes": "^0.4.6",
|
| 38 |
"react": "^19.0.0",
|
| 39 |
"react-dom": "^19.0.0",
|
|
|
|
| 40 |
"react-hot-toast": "^2.6.0",
|
| 41 |
"tailwind-merge": "^3.3.1",
|
| 42 |
"tailwindcss-animate": "^1.0.7"
|
|
@@ -4346,6 +4348,16 @@
|
|
| 4346 |
"url": "https://github.com/sponsors/ljharb"
|
| 4347 |
}
|
| 4348 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4349 |
"node_modules/debug": {
|
| 4350 |
"version": "4.4.3",
|
| 4351 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
|
@@ -7664,6 +7676,22 @@
|
|
| 7664 |
"react": "^19.1.1"
|
| 7665 |
}
|
| 7666 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7667 |
"node_modules/react-hot-toast": {
|
| 7668 |
"version": "2.6.0",
|
| 7669 |
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
|
|
|
|
| 31 |
"@radix-ui/react-tooltip": "^1.1.0",
|
| 32 |
"class-variance-authority": "^0.7.1",
|
| 33 |
"clsx": "^2.1.1",
|
| 34 |
+
"date-fns": "^4.1.0",
|
| 35 |
"framer-motion": "^11.18.2",
|
| 36 |
"lucide-react": "^0.456.0",
|
| 37 |
"next": "15.3.5",
|
| 38 |
"next-themes": "^0.4.6",
|
| 39 |
"react": "^19.0.0",
|
| 40 |
"react-dom": "^19.0.0",
|
| 41 |
+
"react-hook-form": "^7.62.0",
|
| 42 |
"react-hot-toast": "^2.6.0",
|
| 43 |
"tailwind-merge": "^3.3.1",
|
| 44 |
"tailwindcss-animate": "^1.0.7"
|
|
|
|
| 4348 |
"url": "https://github.com/sponsors/ljharb"
|
| 4349 |
}
|
| 4350 |
},
|
| 4351 |
+
"node_modules/date-fns": {
|
| 4352 |
+
"version": "4.1.0",
|
| 4353 |
+
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
| 4354 |
+
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
| 4355 |
+
"license": "MIT",
|
| 4356 |
+
"funding": {
|
| 4357 |
+
"type": "github",
|
| 4358 |
+
"url": "https://github.com/sponsors/kossnocorp"
|
| 4359 |
+
}
|
| 4360 |
+
},
|
| 4361 |
"node_modules/debug": {
|
| 4362 |
"version": "4.4.3",
|
| 4363 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
|
|
|
| 7676 |
"react": "^19.1.1"
|
| 7677 |
}
|
| 7678 |
},
|
| 7679 |
+
"node_modules/react-hook-form": {
|
| 7680 |
+
"version": "7.62.0",
|
| 7681 |
+
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
| 7682 |
+
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
| 7683 |
+
"license": "MIT",
|
| 7684 |
+
"engines": {
|
| 7685 |
+
"node": ">=18.0.0"
|
| 7686 |
+
},
|
| 7687 |
+
"funding": {
|
| 7688 |
+
"type": "opencollective",
|
| 7689 |
+
"url": "https://opencollective.com/react-hook-form"
|
| 7690 |
+
},
|
| 7691 |
+
"peerDependencies": {
|
| 7692 |
+
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
| 7693 |
+
}
|
| 7694 |
+
},
|
| 7695 |
"node_modules/react-hot-toast": {
|
| 7696 |
"version": "2.6.0",
|
| 7697 |
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
|
package.json
CHANGED
|
@@ -32,12 +32,14 @@
|
|
| 32 |
"@radix-ui/react-tooltip": "^1.1.0",
|
| 33 |
"class-variance-authority": "^0.7.1",
|
| 34 |
"clsx": "^2.1.1",
|
|
|
|
| 35 |
"framer-motion": "^11.18.2",
|
| 36 |
"lucide-react": "^0.456.0",
|
| 37 |
"next": "15.3.5",
|
| 38 |
"next-themes": "^0.4.6",
|
| 39 |
"react": "^19.0.0",
|
| 40 |
"react-dom": "^19.0.0",
|
|
|
|
| 41 |
"react-hot-toast": "^2.6.0",
|
| 42 |
"tailwind-merge": "^3.3.1",
|
| 43 |
"tailwindcss-animate": "^1.0.7"
|
|
|
|
| 32 |
"@radix-ui/react-tooltip": "^1.1.0",
|
| 33 |
"class-variance-authority": "^0.7.1",
|
| 34 |
"clsx": "^2.1.1",
|
| 35 |
+
"date-fns": "^4.1.0",
|
| 36 |
"framer-motion": "^11.18.2",
|
| 37 |
"lucide-react": "^0.456.0",
|
| 38 |
"next": "15.3.5",
|
| 39 |
"next-themes": "^0.4.6",
|
| 40 |
"react": "^19.0.0",
|
| 41 |
"react-dom": "^19.0.0",
|
| 42 |
+
"react-hook-form": "^7.62.0",
|
| 43 |
"react-hot-toast": "^2.6.0",
|
| 44 |
"tailwind-merge": "^3.3.1",
|
| 45 |
"tailwindcss-animate": "^1.0.7"
|
scripts/screenshot-templates.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
const puppeteer = require('puppeteer');
|
| 4 |
+
const fs = require('fs');
|
| 5 |
+
const path = require('path');
|
| 6 |
+
|
| 7 |
+
const templates = [
|
| 8 |
+
'devforge',
|
| 9 |
+
'mobilefirst',
|
| 10 |
+
'saasify',
|
| 11 |
+
'startupkit',
|
| 12 |
+
'analyticsdash',
|
| 13 |
+
'blog',
|
| 14 |
+
'changelog',
|
| 15 |
+
'portfolio'
|
| 16 |
+
];
|
| 17 |
+
|
| 18 |
+
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
|
| 19 |
+
const screenshotDir = path.join(process.cwd(), 'public', 'screenshots');
|
| 20 |
+
|
| 21 |
+
async function captureScreenshots() {
|
| 22 |
+
// Ensure screenshot directory exists
|
| 23 |
+
if (!fs.existsSync(screenshotDir)) {
|
| 24 |
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const browser = await puppeteer.launch({
|
| 28 |
+
headless: 'new',
|
| 29 |
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
console.log('π¨ Starting screenshot capture for all templates...\n');
|
| 33 |
+
|
| 34 |
+
for (const template of templates) {
|
| 35 |
+
try {
|
| 36 |
+
console.log(`πΈ Capturing ${template}...`);
|
| 37 |
+
|
| 38 |
+
const page = await browser.newPage();
|
| 39 |
+
|
| 40 |
+
// Set viewport to standard desktop size
|
| 41 |
+
await page.setViewport({ width: 1920, height: 1080 });
|
| 42 |
+
|
| 43 |
+
// Navigate to template page
|
| 44 |
+
const url = `${baseUrl}/template/${template}`;
|
| 45 |
+
await page.goto(url, { waitUntil: 'networkidle2' });
|
| 46 |
+
|
| 47 |
+
// Wait for content to load
|
| 48 |
+
await page.waitForTimeout(2000);
|
| 49 |
+
|
| 50 |
+
// Capture full page screenshot
|
| 51 |
+
const screenshotPath = path.join(screenshotDir, `${template}.png`);
|
| 52 |
+
await page.screenshot({
|
| 53 |
+
path: screenshotPath,
|
| 54 |
+
fullPage: false, // Just viewport for consistent sizing
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// Also capture a thumbnail version
|
| 58 |
+
await page.setViewport({ width: 1280, height: 720 });
|
| 59 |
+
const thumbnailPath = path.join(screenshotDir, `${template}-thumb.png`);
|
| 60 |
+
await page.screenshot({
|
| 61 |
+
path: thumbnailPath,
|
| 62 |
+
fullPage: false,
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
// Capture mobile version
|
| 66 |
+
await page.setViewport({ width: 375, height: 812 });
|
| 67 |
+
const mobilePath = path.join(screenshotDir, `${template}-mobile.png`);
|
| 68 |
+
await page.screenshot({
|
| 69 |
+
path: mobilePath,
|
| 70 |
+
fullPage: false,
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
await page.close();
|
| 74 |
+
|
| 75 |
+
console.log(` β
Desktop: ${template}.png`);
|
| 76 |
+
console.log(` β
Thumbnail: ${template}-thumb.png`);
|
| 77 |
+
console.log(` β
Mobile: ${template}-mobile.png\n`);
|
| 78 |
+
|
| 79 |
+
} catch (error) {
|
| 80 |
+
console.error(` β Error capturing ${template}:`, error.message);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
await browser.close();
|
| 85 |
+
|
| 86 |
+
console.log('π Screenshot capture complete!');
|
| 87 |
+
console.log(`π Screenshots saved to: ${screenshotDir}`);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Run the screenshot capture
|
| 91 |
+
captureScreenshots().catch(console.error);
|
templates/devforge.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
-
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from '
|
| 3 |
-
import { HanzoLogo } from '
|
| 4 |
import { Code2, Zap, Shield, Globe, Users, Terminal, Cloud, Sparkles } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function DevForgeTemplate() {
|
|
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from '@hanzo/ui/primitives'
|
| 3 |
+
import { HanzoLogo } from '@/components/logo'
|
| 4 |
import { Code2, Zap, Shield, Globe, Users, Terminal, Cloud, Sparkles } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function DevForgeTemplate() {
|
templates/mobilefirst.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
-
import { Button, Card, CardContent, CardHeader, CardTitle, Badge } from '
|
| 3 |
-
import { HanzoLogo } from '
|
| 4 |
import { Smartphone, Palette, Layers, Zap, Download, Share2 } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function MobileFirstTemplate() {
|
|
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
+
import { Button, Card, CardContent, CardHeader, CardTitle, Badge } from '@hanzo/ui/primitives'
|
| 3 |
+
import { HanzoLogo } from '@/components/logo'
|
| 4 |
import { Smartphone, Palette, Layers, Zap, Download, Share2 } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function MobileFirstTemplate() {
|
templates/saasify.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
-
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from '
|
| 3 |
-
import { HanzoLogo } from '
|
| 4 |
import { CreditCard, Users, BarChart3, Shield, Zap, Globe } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function SaaSifyTemplate() {
|
|
|
|
| 1 |
import { motion } from 'framer-motion'
|
| 2 |
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from '@hanzo/ui/primitives'
|
| 3 |
+
import { HanzoLogo } from '@/components/logo'
|
| 4 |
import { CreditCard, Users, BarChart3, Shield, Zap, Globe } from 'lucide-react'
|
| 5 |
|
| 6 |
export default function SaaSifyTemplate() {
|
tsconfig.json
CHANGED
|
@@ -4,12 +4,13 @@
|
|
| 4 |
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
"allowJs": true,
|
| 6 |
"skipLibCheck": true,
|
|
|
|
| 7 |
"strict": true,
|
| 8 |
"forceConsistentCasingInFileNames": true,
|
| 9 |
"noEmit": true,
|
| 10 |
"esModuleInterop": true,
|
| 11 |
"module": "esnext",
|
| 12 |
-
"moduleResolution": "
|
| 13 |
"resolveJsonModule": true,
|
| 14 |
"isolatedModules": true,
|
| 15 |
"jsx": "preserve",
|
|
|
|
| 4 |
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
"allowJs": true,
|
| 6 |
"skipLibCheck": true,
|
| 7 |
+
"typeRoots": ["./node_modules/@types"],
|
| 8 |
"strict": true,
|
| 9 |
"forceConsistentCasingInFileNames": true,
|
| 10 |
"noEmit": true,
|
| 11 |
"esModuleInterop": true,
|
| 12 |
"module": "esnext",
|
| 13 |
+
"moduleResolution": "bundler",
|
| 14 |
"resolveJsonModule": true,
|
| 15 |
"isolatedModules": true,
|
| 16 |
"jsx": "preserve",
|