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 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((template) => {
 
 
 
 
 
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
- // Dark mode toggle
61
- useEffect(() => {
62
- if (isDark) {
63
- document.documentElement.classList.add('dark')
64
- } else {
65
- document.documentElement.classList.remove('dark')
66
- }
67
- }, [isDark])
68
 
69
- // Scroll to top button
70
  useEffect(() => {
71
  const handleScroll = () => {
72
- setShowScrollTop(window.scrollY > 300)
73
  }
74
  window.addEventListener('scroll', handleScroll)
75
  return () => window.removeEventListener('scroll', handleScroll)
76
  }, [])
77
 
78
- const copyToClipboard = (text: string, id: string) => {
79
- navigator.clipboard.writeText(text)
 
 
 
 
80
  setCopiedId(id)
81
  setTimeout(() => setCopiedId(null), 2000)
82
  }
83
 
84
  const toggleFavorite = (id: string) => {
85
  setFavorites(prev =>
86
- prev.includes(id) ? prev.filter(f => f !== id) : [...prev, id]
 
 
87
  )
88
  }
89
 
90
- const scrollToTop = () => {
91
- window.scrollTo({ top: 0, behavior: 'smooth' })
 
 
 
 
 
92
  }
93
 
94
- return (
95
- <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 transition-colors">
96
- {/* Background Pattern */}
97
- <div className="fixed inset-0 bg-grid opacity-[0.02] dark:opacity-[0.05] pointer-events-none" />
 
 
 
 
 
 
 
 
 
98
 
 
 
99
  {/* Header */}
100
- <header className="sticky top-0 z-50 glass-effect border-b border-slate-200 dark:border-slate-800">
101
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
102
  <div className="flex items-center justify-between">
103
- {/* Logo & Title */}
104
- <div className="flex items-center gap-3">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  <div className="relative">
106
- <div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg blur animate-pulse-slow" />
107
- <div className="relative bg-gradient-to-r from-blue-600 to-purple-600 p-2 rounded-lg">
108
- <Sparkles className="h-6 w-6 text-white" />
109
- </div>
 
 
 
 
110
  </div>
111
- <div>
112
- <h1 className="text-2xl font-bold text-gradient">
113
- Hanzo Templates
114
- </h1>
115
- <p className="text-sm text-muted-foreground">
116
- Production-ready templates for every project
117
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  </div>
119
- </div>
120
 
121
- {/* Actions */}
122
- <div className="flex items-center gap-2">
123
- {/* View Mode Toggle */}
124
- <div className="hidden sm:flex items-center bg-slate-100 dark:bg-slate-800 rounded-lg p-1">
125
- <button
126
  onClick={() => setViewMode('grid')}
127
- className={`px-3 py-1.5 rounded-md transition-colors ${
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="h-4 w-4" />
134
- </button>
135
- <button
 
 
136
  onClick={() => setViewMode('list')}
137
- className={`px-3 py-1.5 rounded-md transition-colors ${
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="h-4 w-4" />
144
- </button>
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
- </header>
180
-
181
- {/* Main Content */}
182
- <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
183
- <div className="flex gap-8">
184
- {/* Sidebar - Categories */}
185
- <aside className={`${showFilters ? 'block' : 'hidden'} sm:block w-64 flex-shrink-0`}>
186
- <div className="sticky top-24">
187
- <h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4">
188
- Categories
189
- </h2>
190
- <nav className="space-y-1">
191
- {categories.map((category) => (
192
- <button
193
- key={category.id}
194
- onClick={() => setSelectedCategory(category.id)}
195
- className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-all ${
196
- selectedCategory === category.id
197
- ? 'bg-gradient-to-r from-blue-500/10 to-purple-500/10 text-blue-600 dark:text-blue-400 font-medium'
198
- : 'hover:bg-slate-100 dark:hover:bg-slate-800'
199
- }`}
200
- >
201
- <span className="text-xl">{category.icon}</span>
202
- <span>{category.name}</span>
203
- <span className="ml-auto text-xs text-muted-foreground">
204
- {category.id === 'all'
205
- ? templates.length
206
- : templates.filter(t => t.category === category.id).length}
207
- </span>
208
- </button>
209
- ))}
210
- </nav>
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
- <div className="flex items-center justify-between">
221
- <span className="text-muted-foreground">Free Templates</span>
222
- <span className="font-medium">{templates.filter(t => t.price === 'Free').length}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  </div>
224
- <div className="flex items-center justify-between">
225
- <span className="text-muted-foreground">New Releases</span>
226
- <span className="font-medium">{templates.filter(t => t.status === 'new').length}</span>
 
 
 
 
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
- {/* Templates */}
254
- <div className={
255
- viewMode === 'grid'
256
- ? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'
257
- : 'space-y-4'
258
- }>
259
- {filteredTemplates.map((template) => (
260
- <article
261
- key={template.id}
262
- className={`group relative bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden transition-all hover:shadow-xl hover:scale-[1.02] ${
263
- viewMode === 'list' ? 'flex' : ''
264
- }`}
265
- >
266
- {/* Status Badge */}
267
- {template.status === 'new' && (
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
- {/* Content */}
286
- <div className={`p-5 ${viewMode === 'list' ? 'flex-1' : ''}`}>
287
- <div className="flex items-start justify-between mb-3">
288
- <div>
289
- <h3 className="text-lg font-semibold mb-1">{template.name}</h3>
290
- <p className="text-sm text-muted-foreground">{template.tagline}</p>
291
- </div>
292
- <button
293
- onClick={() => toggleFavorite(template.id)}
294
- className="p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
295
- >
296
- <Heart className={`h-4 w-4 ${favorites.includes(template.id) ? 'fill-red-500 text-red-500' : ''}`} />
297
- </button>
298
- </div>
299
-
300
- <p className="text-sm text-muted-foreground mb-4 line-clamp-2">
301
- {template.description}
302
- </p>
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
- {/* Features Preview */}
319
- <div className="space-y-1 mb-4">
320
- {template.features.slice(0, viewMode === 'list' ? 4 : 2).map((feature) => (
321
- <div key={feature} className="flex items-center gap-2 text-xs text-muted-foreground">
322
- <Check className="h-3 w-3 text-green-500" />
323
- <span>{feature}</span>
324
- </div>
325
- ))}
 
 
 
 
 
 
 
 
 
326
  </div>
327
 
328
  {/* Actions */}
329
- <div className="flex items-center justify-between pt-4 border-t border-slate-200 dark:border-slate-800">
330
- <span className="text-sm font-semibold text-blue-600 dark:text-blue-400">
331
- {template.price}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- </article>
352
- ))}
353
- </div>
354
  </div>
355
- </div>
356
- </main>
357
-
358
- {/* Template Detail Modal */}
359
- {selectedTemplate && (
360
- <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in">
361
- <div className="relative w-full max-w-4xl max-h-[90vh] bg-white dark:bg-slate-900 rounded-2xl shadow-2xl overflow-hidden animate-scale-in">
362
- {/* Modal Header with Gradient */}
363
- <div className={`h-48 bg-gradient-to-br ${selectedTemplate.gradient} p-8 relative`}>
364
- <button
365
- onClick={() => setSelectedTemplate(null)}
366
- className="absolute top-4 right-4 p-2 rounded-lg bg-white/20 backdrop-blur hover:bg-white/30 transition-colors"
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
- {/* Modal Content */}
388
- <div className="p-8 overflow-y-auto max-h-[calc(90vh-12rem)]">
389
- <p className="text-muted-foreground mb-6">{selectedTemplate.description}</p>
390
-
391
- {/* Features Grid */}
392
- <div className="mb-8">
393
- <h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
394
- <Zap className="h-5 w-5 text-yellow-500" />
395
- Key Features
396
- </h3>
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 Button */}
477
  {showScrollTop && (
478
- <button
 
 
479
  onClick={scrollToTop}
480
- className="fixed bottom-8 right-8 p-3 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full shadow-lg hover:scale-110 transition-transform animate-fade-up"
481
  >
482
- <ArrowUp className="h-5 w-5" />
483
- </button>
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 Brand Components and Constants
2
- import { cn } from '../lib/utils'
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-400 via-blue-500 to-emerald-400',
32
  features: [
33
- 'AI Code Generation',
34
  'Real-time Collaboration',
35
- 'Automated Testing',
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: '#8b5cf6',
54
- secondaryColor: '#ec4899',
55
- gradient: 'from-purple-500 via-pink-500 to-rose-500',
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: '#3b82f6',
78
- secondaryColor: '#6366f1',
79
- gradient: 'from-blue-500 via-indigo-500 to-purple-500',
80
  features: [
81
- 'Multi-tenant Architecture',
82
- 'Stripe Integration',
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: '#f97316',
102
- secondaryColor: '#fbbf24',
103
- gradient: 'from-orange-500 via-amber-500 to-yellow-400',
104
  features: [
105
- 'Pitch Deck Builder',
106
- 'Investor CRM',
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: '#14b8a6',
126
- secondaryColor: '#06b6d4',
127
- gradient: 'from-teal-500 via-cyan-500 to-blue-500',
128
  features: [
129
- 'Real-time Dashboards',
130
- 'Custom Metrics',
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: '#6366f1',
150
  secondaryColor: '#8b5cf6',
151
- gradient: 'from-indigo-500 via-purple-500 to-pink-500',
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: '#10b981',
174
- secondaryColor: '#34d399',
175
- gradient: 'from-emerald-500 via-green-500 to-teal-500',
176
  features: [
177
- 'Version Tracking',
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: '#f59e0b',
198
- secondaryColor: '#fbbf24',
199
- gradient: 'from-amber-500 via-orange-500 to-red-500',
200
  features: [
201
- 'Resume Builder',
202
  'Project Gallery',
203
- 'Blog Section',
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
- // Simple UI component replacements to avoid @hanzo/ui dependency issues
2
- import { cn } from '../lib/utils'
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 '../components/ui-components'
3
- import { HanzoLogo } from '../components/hanzo-brand'
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 '../components/ui-components'
3
- import { HanzoLogo } from '../components/hanzo-brand'
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 '../components/ui-components'
3
- import { HanzoLogo } from '../components/hanzo-brand'
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": "node",
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",