@hustle-together/api-dev-tools 3.12.3 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/adr-requests/.gitkeep +10 -0
- package/.claude/agents/adr-researcher.md +109 -0
- package/.claude/agents/visual-analyzer.md +183 -0
- package/.claude/api-dev-state.json +7 -463
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- package/.claude/workflow-logs/None.json +49 -0
- package/.claude/workflow-logs/session-20251230-143727.json +106 -0
- package/.skills/adr-deep-research/SKILL.md +351 -0
- package/.skills/api-create/SKILL.md +116 -17
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +786 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- package/.skills/parallel-spawn/SKILL.md +212 -0
- package/.skills/ralph-continue/SKILL.md +151 -0
- package/.skills/ralph-loop/SKILL.md +341 -0
- package/.skills/ralph-status/SKILL.md +87 -0
- package/.skills/refactor/SKILL.md +59 -0
- package/.skills/shadcn/SKILL.md +522 -0
- package/.skills/test-all/SKILL.md +210 -0
- package/.skills/test-builds/SKILL.md +208 -0
- package/.skills/test-debug/SKILL.md +212 -0
- package/.skills/test-e2e/SKILL.md +168 -0
- package/.skills/test-review/SKILL.md +707 -0
- package/.skills/test-unit/SKILL.md +143 -0
- package/.skills/test-visual/SKILL.md +301 -0
- package/.skills/token-report/SKILL.md +132 -0
- package/CHANGELOG.md +575 -0
- package/README.md +426 -56
- package/bin/cli.js +1538 -88
- package/commands/hustle-api-create.md +22 -0
- package/commands/hustle-build.md +259 -0
- package/commands/hustle-combine.md +81 -2
- package/commands/hustle-ui-create-page.md +84 -2
- package/commands/hustle-ui-create.md +82 -2
- package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
- package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
- package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
- package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
- package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
- package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
- package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
- package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
- package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
- package/hooks/api-workflow-check.py +34 -0
- package/hooks/auto-answer.py +305 -0
- package/hooks/check-update.py +132 -0
- package/hooks/completion-promise-detector.py +293 -0
- package/hooks/context-capacity-warning.py +171 -0
- package/hooks/docs-update-check.py +120 -0
- package/hooks/enforce-dry-run.py +134 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/hooks/generate-adr-options.py +282 -0
- package/hooks/hook_utils.py +609 -0
- package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/hooks/ntfy-on-question.py +240 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +267 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/parallel-orchestrator.py +451 -0
- package/hooks/periodic-reground.py +270 -67
- package/hooks/project-document-prompt.py +302 -0
- package/hooks/remote-question-proxy.py +284 -0
- package/hooks/remote-question-server.py +1224 -0
- package/hooks/run-code-review.py +176 -29
- package/hooks/run-visual-qa.py +338 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/hooks/update-adr-decision.py +236 -0
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-testing-checklist.py +195 -0
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- package/templates/api-showcase/_components/APIModal.tsx +100 -8
- package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
- package/templates/api-showcase/_components/APITester.tsx +367 -58
- package/templates/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- package/templates/docs/page.tsx +230 -0
- package/templates/eslint-plugin-zod-schema/index.js +446 -0
- package/templates/eslint-plugin-zod-schema/package.json +26 -0
- package/templates/github-workflows/security.yml +274 -0
- package/templates/hustle-build-defaults.json +136 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +155 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- package/templates/ui-showcase/page.tsx +1 -1
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand Page Template
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive showcase of your brand identity including:
|
|
5
|
+
* - Color palette with copy-to-clipboard
|
|
6
|
+
* - Typography scale
|
|
7
|
+
* - Component states (buttons, forms, cards)
|
|
8
|
+
* - Animation examples
|
|
9
|
+
* - Voice and tone guidelines
|
|
10
|
+
* - Custom elements (based on interview)
|
|
11
|
+
*
|
|
12
|
+
* Generated by: /hustle-brand
|
|
13
|
+
* Last Updated: {{LAST_UPDATED}}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use client";
|
|
17
|
+
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
20
|
+
import { Button } from "@/components/ui/button";
|
|
21
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
22
|
+
import { Input } from "@/components/ui/input";
|
|
23
|
+
import { Label } from "@/components/ui/label";
|
|
24
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
25
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
26
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
27
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
28
|
+
import { Badge } from "@/components/ui/badge";
|
|
29
|
+
import { Separator } from "@/components/ui/separator";
|
|
30
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
31
|
+
import { Check, Copy, Loader2, ChevronRight, ExternalLink } from "lucide-react";
|
|
32
|
+
import { cn } from "@/lib/utils";
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// BRAND CONFIGURATION (Generated from interview)
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// HUSTLE TOGETHER BRANDING (Default)
|
|
40
|
+
// Run `/hustle-brand` to customize these values via interview
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
const BRAND = {
|
|
44
|
+
name: "Hustle Together",
|
|
45
|
+
tagline: "Interview-Driven, Research-First API Development",
|
|
46
|
+
|
|
47
|
+
colors: {
|
|
48
|
+
primary: { name: "Hustle Red", hex: "#ba0c2f", hsl: "350 87% 39%" },
|
|
49
|
+
secondary: { name: "Background", hex: "#f8f8f8", hsl: "0 0% 97%" },
|
|
50
|
+
accent: { name: "Accent Red Light", hex: "#ba0c2f1a", hsl: "350 87% 39% / 0.1" },
|
|
51
|
+
background: { name: "White", hex: "#ffffff", hsl: "0 0% 100%" },
|
|
52
|
+
foreground: { name: "Black", hex: "#000000", hsl: "0 0% 0%" },
|
|
53
|
+
muted: { name: "Grey", hex: "#666666", hsl: "0 0% 40%" },
|
|
54
|
+
destructive: { name: "Error Red", hex: "#ef4444", hsl: "0 84% 60%" },
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Dark mode variants
|
|
58
|
+
colorsDark: {
|
|
59
|
+
background: { name: "Dark BG", hex: "#0a0a0a", hsl: "0 0% 4%" },
|
|
60
|
+
secondary: { name: "Dark Secondary", hex: "#111111", hsl: "0 0% 7%" },
|
|
61
|
+
foreground: { name: "Light Text", hex: "#f0f0f0", hsl: "0 0% 94%" },
|
|
62
|
+
muted: { name: "Dark Muted", hex: "#999999", hsl: "0 0% 60%" },
|
|
63
|
+
border: { name: "Dark Border", hex: "#333333", hsl: "0 0% 20%" },
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
gradients: [
|
|
67
|
+
{ name: "Hustle Glow", value: "radial-gradient(circle, rgba(186, 12, 47, 0.3) 0%, transparent 70%)" },
|
|
68
|
+
{ name: "Red Accent", value: "linear-gradient(135deg, #ba0c2f 0%, #8a0922 100%)" },
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
typography: {
|
|
72
|
+
fontSans: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif",
|
|
73
|
+
fontMono: "'SF Mono', 'Fira Code', 'Consolas', monospace",
|
|
74
|
+
fontHeading: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif",
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
motion: {
|
|
78
|
+
style: "snappy", // 90s boxy = snappy animations
|
|
79
|
+
durationFast: "150ms",
|
|
80
|
+
durationNormal: "300ms",
|
|
81
|
+
durationSlow: "500ms",
|
|
82
|
+
easing: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
83
|
+
library: "gsap", // Uses GSAP for animations
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
voice: {
|
|
87
|
+
tone: "technical", // Professional but technical for developers
|
|
88
|
+
terminology: {
|
|
89
|
+
users: "developers",
|
|
90
|
+
cta: "Get Started",
|
|
91
|
+
},
|
|
92
|
+
dos: [
|
|
93
|
+
"Use clear, concise technical language",
|
|
94
|
+
"Provide code examples for every concept",
|
|
95
|
+
"Explain the 'why' not just the 'what'",
|
|
96
|
+
"Reference official documentation",
|
|
97
|
+
],
|
|
98
|
+
donts: [
|
|
99
|
+
"Don't use marketing buzzwords",
|
|
100
|
+
"Don't assume prior context - be explicit",
|
|
101
|
+
"Don't skip error handling examples",
|
|
102
|
+
"Don't use placeholder data without noting it",
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
customElements: ["terminal-animation", "gsap-animations", "typing-effect", "grid-background"],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// COMPONENTS
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
function ColorSwatch({ name, hex, hsl }: { name: string; hex: string; hsl: string }) {
|
|
114
|
+
const [copied, setCopied] = useState(false);
|
|
115
|
+
|
|
116
|
+
const copyToClipboard = (value: string) => {
|
|
117
|
+
navigator.clipboard.writeText(value);
|
|
118
|
+
setCopied(true);
|
|
119
|
+
setTimeout(() => setCopied(false), 2000);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="group relative">
|
|
124
|
+
<div
|
|
125
|
+
className="h-24 rounded-lg shadow-sm border cursor-pointer transition-transform hover:scale-105"
|
|
126
|
+
style={{ backgroundColor: hex }}
|
|
127
|
+
onClick={() => copyToClipboard(hex)}
|
|
128
|
+
/>
|
|
129
|
+
<div className="mt-2 space-y-1">
|
|
130
|
+
<p className="font-medium text-sm">{name}</p>
|
|
131
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
132
|
+
<code className="bg-muted px-1 rounded">{hex}</code>
|
|
133
|
+
<button
|
|
134
|
+
onClick={() => copyToClipboard(hex)}
|
|
135
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
|
136
|
+
>
|
|
137
|
+
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />}
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
<p className="text-xs text-muted-foreground">{hsl}</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function TypographyScale() {
|
|
147
|
+
const scale = [
|
|
148
|
+
{ name: "Display", class: "text-5xl font-bold", size: "48px" },
|
|
149
|
+
{ name: "H1", class: "text-4xl font-bold", size: "36px" },
|
|
150
|
+
{ name: "H2", class: "text-3xl font-semibold", size: "30px" },
|
|
151
|
+
{ name: "H3", class: "text-2xl font-semibold", size: "24px" },
|
|
152
|
+
{ name: "H4", class: "text-xl font-medium", size: "20px" },
|
|
153
|
+
{ name: "Body Large", class: "text-lg", size: "18px" },
|
|
154
|
+
{ name: "Body", class: "text-base", size: "16px" },
|
|
155
|
+
{ name: "Body Small", class: "text-sm", size: "14px" },
|
|
156
|
+
{ name: "Caption", class: "text-xs", size: "12px" },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className="space-y-4">
|
|
161
|
+
{scale.map((item) => (
|
|
162
|
+
<div key={item.name} className="flex items-baseline gap-4">
|
|
163
|
+
<span className="w-24 text-sm text-muted-foreground">{item.name}</span>
|
|
164
|
+
<span className={item.class}>The quick brown fox</span>
|
|
165
|
+
<span className="text-xs text-muted-foreground ml-auto">{item.size}</span>
|
|
166
|
+
</div>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ButtonStates() {
|
|
173
|
+
const [loading, setLoading] = useState(false);
|
|
174
|
+
|
|
175
|
+
const handleLoadingDemo = () => {
|
|
176
|
+
setLoading(true);
|
|
177
|
+
setTimeout(() => setLoading(false), 2000);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div className="space-y-6">
|
|
182
|
+
<div className="flex flex-wrap gap-4">
|
|
183
|
+
<div className="space-y-2">
|
|
184
|
+
<p className="text-sm text-muted-foreground">Default</p>
|
|
185
|
+
<Button>Primary</Button>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="space-y-2">
|
|
188
|
+
<p className="text-sm text-muted-foreground">Secondary</p>
|
|
189
|
+
<Button variant="secondary">Secondary</Button>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="space-y-2">
|
|
192
|
+
<p className="text-sm text-muted-foreground">Outline</p>
|
|
193
|
+
<Button variant="outline">Outline</Button>
|
|
194
|
+
</div>
|
|
195
|
+
<div className="space-y-2">
|
|
196
|
+
<p className="text-sm text-muted-foreground">Ghost</p>
|
|
197
|
+
<Button variant="ghost">Ghost</Button>
|
|
198
|
+
</div>
|
|
199
|
+
<div className="space-y-2">
|
|
200
|
+
<p className="text-sm text-muted-foreground">Destructive</p>
|
|
201
|
+
<Button variant="destructive">Destructive</Button>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<Separator />
|
|
206
|
+
|
|
207
|
+
<div className="flex flex-wrap gap-4">
|
|
208
|
+
<div className="space-y-2">
|
|
209
|
+
<p className="text-sm text-muted-foreground">Hover (interact)</p>
|
|
210
|
+
<Button>Hover me</Button>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="space-y-2">
|
|
213
|
+
<p className="text-sm text-muted-foreground">Disabled</p>
|
|
214
|
+
<Button disabled>Disabled</Button>
|
|
215
|
+
</div>
|
|
216
|
+
<div className="space-y-2">
|
|
217
|
+
<p className="text-sm text-muted-foreground">Loading</p>
|
|
218
|
+
<Button disabled={loading} onClick={handleLoadingDemo}>
|
|
219
|
+
{loading ? (
|
|
220
|
+
<>
|
|
221
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
222
|
+
Loading...
|
|
223
|
+
</>
|
|
224
|
+
) : (
|
|
225
|
+
"Click to load"
|
|
226
|
+
)}
|
|
227
|
+
</Button>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="space-y-2">
|
|
230
|
+
<p className="text-sm text-muted-foreground">With Icon</p>
|
|
231
|
+
<Button>
|
|
232
|
+
Continue <ChevronRight className="ml-2 h-4 w-4" />
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<Separator />
|
|
238
|
+
|
|
239
|
+
<div className="flex flex-wrap gap-4">
|
|
240
|
+
<div className="space-y-2">
|
|
241
|
+
<p className="text-sm text-muted-foreground">Small</p>
|
|
242
|
+
<Button size="sm">Small</Button>
|
|
243
|
+
</div>
|
|
244
|
+
<div className="space-y-2">
|
|
245
|
+
<p className="text-sm text-muted-foreground">Default</p>
|
|
246
|
+
<Button size="default">Default</Button>
|
|
247
|
+
</div>
|
|
248
|
+
<div className="space-y-2">
|
|
249
|
+
<p className="text-sm text-muted-foreground">Large</p>
|
|
250
|
+
<Button size="lg">Large</Button>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function FormElements() {
|
|
258
|
+
return (
|
|
259
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
260
|
+
<div className="space-y-4">
|
|
261
|
+
<div className="space-y-2">
|
|
262
|
+
<Label htmlFor="email">Email</Label>
|
|
263
|
+
<Input id="email" type="email" placeholder="name@example.com" />
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div className="space-y-2">
|
|
267
|
+
<Label htmlFor="email-error">Email (Error State)</Label>
|
|
268
|
+
<Input id="email-error" type="email" placeholder="name@example.com" className="border-destructive" />
|
|
269
|
+
<p className="text-sm text-destructive">Please enter a valid email address.</p>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="space-y-2">
|
|
273
|
+
<Label htmlFor="disabled">Disabled Input</Label>
|
|
274
|
+
<Input id="disabled" disabled placeholder="Disabled input" />
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div className="space-y-4">
|
|
279
|
+
<div className="space-y-2">
|
|
280
|
+
<Label>Select</Label>
|
|
281
|
+
<Select>
|
|
282
|
+
<SelectTrigger>
|
|
283
|
+
<SelectValue placeholder="Select an option" />
|
|
284
|
+
</SelectTrigger>
|
|
285
|
+
<SelectContent>
|
|
286
|
+
<SelectItem value="option1">Option 1</SelectItem>
|
|
287
|
+
<SelectItem value="option2">Option 2</SelectItem>
|
|
288
|
+
<SelectItem value="option3">Option 3</SelectItem>
|
|
289
|
+
</SelectContent>
|
|
290
|
+
</Select>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<div className="flex items-center space-x-2">
|
|
294
|
+
<Checkbox id="terms" />
|
|
295
|
+
<Label htmlFor="terms" className="text-sm">Accept terms and conditions</Label>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<div className="space-y-2">
|
|
299
|
+
<Label>Radio Group</Label>
|
|
300
|
+
<RadioGroup defaultValue="option1">
|
|
301
|
+
<div className="flex items-center space-x-2">
|
|
302
|
+
<RadioGroupItem value="option1" id="r1" />
|
|
303
|
+
<Label htmlFor="r1">Option 1</Label>
|
|
304
|
+
</div>
|
|
305
|
+
<div className="flex items-center space-x-2">
|
|
306
|
+
<RadioGroupItem value="option2" id="r2" />
|
|
307
|
+
<Label htmlFor="r2">Option 2</Label>
|
|
308
|
+
</div>
|
|
309
|
+
</RadioGroup>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function CardVariations() {
|
|
317
|
+
return (
|
|
318
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
319
|
+
<Card>
|
|
320
|
+
<CardHeader>
|
|
321
|
+
<CardTitle>Default Card</CardTitle>
|
|
322
|
+
<CardDescription>Standard card with header and content.</CardDescription>
|
|
323
|
+
</CardHeader>
|
|
324
|
+
<CardContent>
|
|
325
|
+
<p className="text-sm text-muted-foreground">
|
|
326
|
+
This is the default card style used throughout the application.
|
|
327
|
+
</p>
|
|
328
|
+
</CardContent>
|
|
329
|
+
</Card>
|
|
330
|
+
|
|
331
|
+
<Card className="border-primary">
|
|
332
|
+
<CardHeader>
|
|
333
|
+
<Badge className="w-fit">Featured</Badge>
|
|
334
|
+
<CardTitle>Highlighted Card</CardTitle>
|
|
335
|
+
<CardDescription>Card with primary border accent.</CardDescription>
|
|
336
|
+
</CardHeader>
|
|
337
|
+
<CardContent>
|
|
338
|
+
<p className="text-sm text-muted-foreground">
|
|
339
|
+
Used to highlight important content or featured items.
|
|
340
|
+
</p>
|
|
341
|
+
</CardContent>
|
|
342
|
+
</Card>
|
|
343
|
+
|
|
344
|
+
<Card className="bg-muted">
|
|
345
|
+
<CardHeader>
|
|
346
|
+
<CardTitle>Muted Card</CardTitle>
|
|
347
|
+
<CardDescription>Card with muted background.</CardDescription>
|
|
348
|
+
</CardHeader>
|
|
349
|
+
<CardContent>
|
|
350
|
+
<p className="text-sm text-muted-foreground">
|
|
351
|
+
Used for secondary or supporting content.
|
|
352
|
+
</p>
|
|
353
|
+
</CardContent>
|
|
354
|
+
</Card>
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function AnimationExamples() {
|
|
360
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<div className="space-y-8">
|
|
364
|
+
<div className="space-y-4">
|
|
365
|
+
<div className="flex items-center gap-4">
|
|
366
|
+
<p className="text-sm font-medium">Motion Style:</p>
|
|
367
|
+
<Badge variant="outline">{BRAND.motion.style}</Badge>
|
|
368
|
+
<Badge variant="outline">{BRAND.motion.durationNormal}</Badge>
|
|
369
|
+
<Badge variant="outline">{BRAND.motion.easing}</Badge>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
374
|
+
<div className="space-y-4">
|
|
375
|
+
<p className="text-sm font-medium">Fade In/Out</p>
|
|
376
|
+
<Button variant="outline" onClick={() => setIsVisible(!isVisible)}>
|
|
377
|
+
Toggle Animation
|
|
378
|
+
</Button>
|
|
379
|
+
<AnimatePresence>
|
|
380
|
+
{isVisible && (
|
|
381
|
+
<motion.div
|
|
382
|
+
initial={{ opacity: 0, y: 20 }}
|
|
383
|
+
animate={{ opacity: 1, y: 0 }}
|
|
384
|
+
exit={{ opacity: 0, y: -20 }}
|
|
385
|
+
transition={{ duration: 0.3 }}
|
|
386
|
+
className="p-4 bg-muted rounded-lg"
|
|
387
|
+
>
|
|
388
|
+
Animated content
|
|
389
|
+
</motion.div>
|
|
390
|
+
)}
|
|
391
|
+
</AnimatePresence>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div className="space-y-4">
|
|
395
|
+
<p className="text-sm font-medium">Hover Scale</p>
|
|
396
|
+
<motion.div
|
|
397
|
+
whileHover={{ scale: 1.05 }}
|
|
398
|
+
whileTap={{ scale: 0.95 }}
|
|
399
|
+
className="p-4 bg-primary text-primary-foreground rounded-lg cursor-pointer text-center"
|
|
400
|
+
>
|
|
401
|
+
Hover or tap me
|
|
402
|
+
</motion.div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<div className="space-y-4">
|
|
407
|
+
<p className="text-sm font-medium">Loading States</p>
|
|
408
|
+
<div className="flex gap-4">
|
|
409
|
+
<div className="space-y-2">
|
|
410
|
+
<Skeleton className="h-12 w-12 rounded-full" />
|
|
411
|
+
<Skeleton className="h-4 w-24" />
|
|
412
|
+
</div>
|
|
413
|
+
<div className="space-y-2">
|
|
414
|
+
<Skeleton className="h-4 w-48" />
|
|
415
|
+
<Skeleton className="h-4 w-36" />
|
|
416
|
+
<Skeleton className="h-4 w-40" />
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function VoiceExamples() {
|
|
425
|
+
const examples = {
|
|
426
|
+
success: {
|
|
427
|
+
professional: "Your changes have been saved successfully.",
|
|
428
|
+
friendly: "Awesome! Your changes are all saved!",
|
|
429
|
+
technical: "Changes committed. 3 records updated.",
|
|
430
|
+
playful: "Boom! Saved it like a boss!",
|
|
431
|
+
},
|
|
432
|
+
error: {
|
|
433
|
+
professional: "An error occurred. Please try again.",
|
|
434
|
+
friendly: "Oops! Something went wrong. Let's try that again.",
|
|
435
|
+
technical: "Error 500: Internal server error. Retry in 30s.",
|
|
436
|
+
playful: "Uh oh! The hamsters powering our servers took a break.",
|
|
437
|
+
},
|
|
438
|
+
empty: {
|
|
439
|
+
professional: "No results found for your search.",
|
|
440
|
+
friendly: "Hmm, we couldn't find anything. Try different keywords?",
|
|
441
|
+
technical: "Query returned 0 results. Broaden search parameters.",
|
|
442
|
+
playful: "It's a ghost town here! Nothing to see.",
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<div className="space-y-8">
|
|
448
|
+
<div className="space-y-4">
|
|
449
|
+
<div className="flex items-center gap-4">
|
|
450
|
+
<p className="text-sm font-medium">Voice Tone:</p>
|
|
451
|
+
<Badge>{BRAND.voice.tone}</Badge>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div className="grid gap-6">
|
|
456
|
+
{Object.entries(examples).map(([type, messages]) => (
|
|
457
|
+
<Card key={type}>
|
|
458
|
+
<CardHeader>
|
|
459
|
+
<CardTitle className="capitalize">{type} Message</CardTitle>
|
|
460
|
+
</CardHeader>
|
|
461
|
+
<CardContent>
|
|
462
|
+
<p className="text-lg">
|
|
463
|
+
{messages[BRAND.voice.tone as keyof typeof messages]}
|
|
464
|
+
</p>
|
|
465
|
+
</CardContent>
|
|
466
|
+
</Card>
|
|
467
|
+
))}
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<Separator />
|
|
471
|
+
|
|
472
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
473
|
+
<div>
|
|
474
|
+
<h4 className="font-semibold text-green-600 mb-4">Do's</h4>
|
|
475
|
+
<ul className="space-y-2">
|
|
476
|
+
{BRAND.voice.dos.map((item, i) => (
|
|
477
|
+
<li key={i} className="flex items-start gap-2 text-sm">
|
|
478
|
+
<Check className="h-4 w-4 text-green-600 mt-0.5 shrink-0" />
|
|
479
|
+
{item}
|
|
480
|
+
</li>
|
|
481
|
+
))}
|
|
482
|
+
</ul>
|
|
483
|
+
</div>
|
|
484
|
+
<div>
|
|
485
|
+
<h4 className="font-semibold text-red-600 mb-4">Don'ts</h4>
|
|
486
|
+
<ul className="space-y-2">
|
|
487
|
+
{BRAND.voice.donts.map((item, i) => (
|
|
488
|
+
<li key={i} className="flex items-start gap-2 text-sm text-muted-foreground">
|
|
489
|
+
<span className="text-red-600 mt-0.5 shrink-0">✕</span>
|
|
490
|
+
{item}
|
|
491
|
+
</li>
|
|
492
|
+
))}
|
|
493
|
+
</ul>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// MAIN PAGE
|
|
502
|
+
// ============================================================================
|
|
503
|
+
|
|
504
|
+
export default function BrandPage() {
|
|
505
|
+
return (
|
|
506
|
+
<div className="container py-12 space-y-16">
|
|
507
|
+
{/* Header */}
|
|
508
|
+
<div className="space-y-4">
|
|
509
|
+
<h1 className="text-4xl font-bold">{BRAND.name} Brand Guide</h1>
|
|
510
|
+
<p className="text-xl text-muted-foreground">{BRAND.tagline}</p>
|
|
511
|
+
<div className="flex gap-2">
|
|
512
|
+
<Badge variant="outline">Last updated: {{LAST_UPDATED}}</Badge>
|
|
513
|
+
<Badge variant="outline">Version {{VERSION}}</Badge>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
{/* Navigation */}
|
|
518
|
+
<Tabs defaultValue="colors" className="space-y-8">
|
|
519
|
+
<TabsList className="grid grid-cols-3 lg:grid-cols-6 w-full">
|
|
520
|
+
<TabsTrigger value="colors">Colors</TabsTrigger>
|
|
521
|
+
<TabsTrigger value="typography">Typography</TabsTrigger>
|
|
522
|
+
<TabsTrigger value="components">Components</TabsTrigger>
|
|
523
|
+
<TabsTrigger value="forms">Forms</TabsTrigger>
|
|
524
|
+
<TabsTrigger value="animations">Animations</TabsTrigger>
|
|
525
|
+
<TabsTrigger value="voice">Voice</TabsTrigger>
|
|
526
|
+
</TabsList>
|
|
527
|
+
|
|
528
|
+
{/* Colors */}
|
|
529
|
+
<TabsContent value="colors" className="space-y-8">
|
|
530
|
+
<div>
|
|
531
|
+
<h2 className="text-2xl font-semibold mb-6">Color Palette</h2>
|
|
532
|
+
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-6">
|
|
533
|
+
{Object.entries(BRAND.colors).map(([key, color]) => (
|
|
534
|
+
<ColorSwatch key={key} {...color} />
|
|
535
|
+
))}
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
{BRAND.gradients.length > 0 && (
|
|
540
|
+
<div>
|
|
541
|
+
<h3 className="text-xl font-semibold mb-4">Gradients</h3>
|
|
542
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
543
|
+
{BRAND.gradients.map((gradient, i) => (
|
|
544
|
+
<div key={i} className="space-y-2">
|
|
545
|
+
<div
|
|
546
|
+
className="h-24 rounded-lg"
|
|
547
|
+
style={{ background: gradient.value }}
|
|
548
|
+
/>
|
|
549
|
+
<p className="text-sm font-medium">{gradient.name}</p>
|
|
550
|
+
<code className="text-xs bg-muted px-2 py-1 rounded">{gradient.value}</code>
|
|
551
|
+
</div>
|
|
552
|
+
))}
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
)}
|
|
556
|
+
</TabsContent>
|
|
557
|
+
|
|
558
|
+
{/* Typography */}
|
|
559
|
+
<TabsContent value="typography" className="space-y-8">
|
|
560
|
+
<div>
|
|
561
|
+
<h2 className="text-2xl font-semibold mb-6">Typography Scale</h2>
|
|
562
|
+
<div className="space-y-4 mb-8">
|
|
563
|
+
<div className="flex gap-4 text-sm">
|
|
564
|
+
<span className="text-muted-foreground">Sans:</span>
|
|
565
|
+
<code className="bg-muted px-2 rounded">{BRAND.typography.fontSans}</code>
|
|
566
|
+
</div>
|
|
567
|
+
<div className="flex gap-4 text-sm">
|
|
568
|
+
<span className="text-muted-foreground">Mono:</span>
|
|
569
|
+
<code className="bg-muted px-2 rounded">{BRAND.typography.fontMono}</code>
|
|
570
|
+
</div>
|
|
571
|
+
<div className="flex gap-4 text-sm">
|
|
572
|
+
<span className="text-muted-foreground">Heading:</span>
|
|
573
|
+
<code className="bg-muted px-2 rounded">{BRAND.typography.fontHeading}</code>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
<TypographyScale />
|
|
577
|
+
</div>
|
|
578
|
+
</TabsContent>
|
|
579
|
+
|
|
580
|
+
{/* Components */}
|
|
581
|
+
<TabsContent value="components" className="space-y-8">
|
|
582
|
+
<div>
|
|
583
|
+
<h2 className="text-2xl font-semibold mb-6">Button States</h2>
|
|
584
|
+
<ButtonStates />
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<Separator />
|
|
588
|
+
|
|
589
|
+
<div>
|
|
590
|
+
<h2 className="text-2xl font-semibold mb-6">Card Variations</h2>
|
|
591
|
+
<CardVariations />
|
|
592
|
+
</div>
|
|
593
|
+
</TabsContent>
|
|
594
|
+
|
|
595
|
+
{/* Forms */}
|
|
596
|
+
<TabsContent value="forms" className="space-y-8">
|
|
597
|
+
<div>
|
|
598
|
+
<h2 className="text-2xl font-semibold mb-6">Form Elements</h2>
|
|
599
|
+
<FormElements />
|
|
600
|
+
</div>
|
|
601
|
+
</TabsContent>
|
|
602
|
+
|
|
603
|
+
{/* Animations */}
|
|
604
|
+
<TabsContent value="animations" className="space-y-8">
|
|
605
|
+
<div>
|
|
606
|
+
<h2 className="text-2xl font-semibold mb-6">Animation Examples</h2>
|
|
607
|
+
<AnimationExamples />
|
|
608
|
+
</div>
|
|
609
|
+
</TabsContent>
|
|
610
|
+
|
|
611
|
+
{/* Voice */}
|
|
612
|
+
<TabsContent value="voice" className="space-y-8">
|
|
613
|
+
<div>
|
|
614
|
+
<h2 className="text-2xl font-semibold mb-6">Voice & Tone</h2>
|
|
615
|
+
<VoiceExamples />
|
|
616
|
+
</div>
|
|
617
|
+
</TabsContent>
|
|
618
|
+
</Tabs>
|
|
619
|
+
|
|
620
|
+
{/* Custom Elements Section (if any) */}
|
|
621
|
+
{BRAND.customElements.length > 0 && (
|
|
622
|
+
<div className="space-y-6">
|
|
623
|
+
<h2 className="text-2xl font-semibold">Custom Elements</h2>
|
|
624
|
+
<p className="text-muted-foreground">
|
|
625
|
+
Unique brand elements selected during the brand interview.
|
|
626
|
+
</p>
|
|
627
|
+
<div className="flex flex-wrap gap-2">
|
|
628
|
+
{BRAND.customElements.map((element) => (
|
|
629
|
+
<Badge key={element} variant="secondary">{element}</Badge>
|
|
630
|
+
))}
|
|
631
|
+
</div>
|
|
632
|
+
{/* Custom element demos would be rendered here based on BRAND.customElements */}
|
|
633
|
+
</div>
|
|
634
|
+
)}
|
|
635
|
+
|
|
636
|
+
{/* Footer */}
|
|
637
|
+
<div className="border-t pt-8">
|
|
638
|
+
<p className="text-sm text-muted-foreground">
|
|
639
|
+
Generated by <code className="bg-muted px-1 rounded">/hustle-brand</code> •
|
|
640
|
+
Edit with <code className="bg-muted px-1 rounded">/hustle-brand --edit</code>
|
|
641
|
+
</p>
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
);
|
|
645
|
+
}
|