@kennethsolomon/shipkit 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/commands/sk/security-check.md +16 -15
- package/package.json +1 -1
- package/skills/sk:accessibility/SKILL.md +13 -8
- package/skills/sk:e2e/SKILL.md +161 -10
- package/skills/sk:mvp/SKILL.md +266 -0
- package/skills/sk:mvp/references/design-system.md +136 -0
- package/skills/sk:mvp/references/landing-page.md +236 -0
- package/skills/sk:mvp/references/stacks/laravel.md +321 -0
- package/skills/sk:mvp/references/stacks/nextjs.md +189 -0
- package/skills/sk:mvp/references/stacks/nuxt.md +250 -0
- package/skills/sk:mvp/references/stacks/react-vite.md +287 -0
- package/skills/sk:perf/SKILL.md +12 -11
- package/skills/sk:seo-audit/SKILL.md +283 -0
- package/skills/sk:write-tests/SKILL.md +42 -3
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Design System — MVP Aesthetic Guidelines
|
|
2
|
+
|
|
3
|
+
Guidelines for generating visually distinctive MVPs that don't look like generic AI output.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
Every MVP must look **intentionally designed**, not template-generated. The goal is to make visitors think "this looks legit" — good enough to trust with their email and time.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Typography
|
|
14
|
+
|
|
15
|
+
### Rules
|
|
16
|
+
- Always use **two fonts**: one display/heading font + one body font.
|
|
17
|
+
- Source from Google Fonts. Never use system fonts, Inter, Roboto, or Arial as the primary choice.
|
|
18
|
+
- Vary choices between projects — do not converge on the same fonts repeatedly.
|
|
19
|
+
|
|
20
|
+
### Suggested Pairings (rotate, don't default to #1)
|
|
21
|
+
1. **DM Serif Display** + **DM Sans** — editorial, trustworthy
|
|
22
|
+
2. **Playfair Display** + **Source Sans 3** — luxury, refined
|
|
23
|
+
3. **Space Grotesk** + **Inter** — tech, modern (use sparingly, very common)
|
|
24
|
+
4. **Sora** + **Nunito Sans** — friendly, approachable SaaS
|
|
25
|
+
5. **Clash Display** + **Satoshi** — bold, contemporary
|
|
26
|
+
6. **Fraunces** + **Commissioner** — warm, distinctive
|
|
27
|
+
7. **Cabinet Grotesk** + **General Sans** — clean, startup
|
|
28
|
+
8. **Bricolage Grotesque** + **Geist** — editorial tech
|
|
29
|
+
|
|
30
|
+
### Scale
|
|
31
|
+
Use a consistent type scale. Recommended base: `16px`.
|
|
32
|
+
|
|
33
|
+
| Element | Size | Weight | Tracking |
|
|
34
|
+
|---------|------|--------|----------|
|
|
35
|
+
| H1 (hero) | 48-72px | 700-800 | -0.02em |
|
|
36
|
+
| H2 (section) | 32-40px | 600-700 | -0.01em |
|
|
37
|
+
| H3 (card title) | 20-24px | 600 | normal |
|
|
38
|
+
| Body | 16-18px | 400 | normal |
|
|
39
|
+
| Small/caption | 13-14px | 400-500 | 0.01em |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Color
|
|
44
|
+
|
|
45
|
+
### Rules
|
|
46
|
+
- Never use default Tailwind color names without customization (`blue-500`, `gray-100` raw).
|
|
47
|
+
- Define a custom palette in `tailwind.config` under `extend.colors`.
|
|
48
|
+
- Every palette needs: background, foreground, primary accent, secondary accent, muted, border, success, error.
|
|
49
|
+
- Commit to a mood — don't mix warm and cool randomly.
|
|
50
|
+
|
|
51
|
+
### Palette Strategies (pick one per project)
|
|
52
|
+
1. **Dark + neon accent** — dark bg (#0a0a0a), light text (#fafafa), vibrant accent (#6366f1 or #22d3ee)
|
|
53
|
+
2. **Warm neutral + earth accent** — warm white (#faf9f6), dark text (#1a1a1a), terracotta/amber accent
|
|
54
|
+
3. **Cool minimal** — pure white (#ffffff), slate text (#334155), single accent color
|
|
55
|
+
4. **Bold saturated** — deep colored bg (#1e1b4b), contrasting text, bright accent
|
|
56
|
+
5. **Soft pastel** — light tinted bg (#f0fdf4), dark text, pastel accent palette
|
|
57
|
+
|
|
58
|
+
### Contrast
|
|
59
|
+
- Text on background must meet WCAG AA (4.5:1 for body text, 3:1 for large text).
|
|
60
|
+
- Test accent colors against both light and dark surfaces.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Spacing
|
|
65
|
+
|
|
66
|
+
Use a **4px base unit** with a consistent scale:
|
|
67
|
+
|
|
68
|
+
| Token | Value | Use for |
|
|
69
|
+
|-------|-------|---------|
|
|
70
|
+
| `xs` | 4px | Icon gaps, tight padding |
|
|
71
|
+
| `sm` | 8px | Inline spacing, compact cards |
|
|
72
|
+
| `md` | 16px | Default padding, form gaps |
|
|
73
|
+
| `lg` | 24px | Card padding, section content |
|
|
74
|
+
| `xl` | 32px | Section gaps |
|
|
75
|
+
| `2xl` | 48px | Between major sections |
|
|
76
|
+
| `3xl` | 64px | Hero padding, page-level spacing |
|
|
77
|
+
| `4xl` | 96px | Landing page section separation |
|
|
78
|
+
|
|
79
|
+
### Layout Rhythm
|
|
80
|
+
- Sections on the landing page should have `py-20` to `py-28` (80-112px) vertical padding.
|
|
81
|
+
- Cards should have consistent `p-6` to `p-8` padding.
|
|
82
|
+
- The max content width should be `max-w-6xl` or `max-w-7xl`, centered.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Components
|
|
87
|
+
|
|
88
|
+
### Buttons
|
|
89
|
+
- Primary: filled with accent color, white text, `rounded-lg` or `rounded-xl`, `px-6 py-3`.
|
|
90
|
+
- Secondary: outlined or ghost, accent color border/text.
|
|
91
|
+
- Hover: subtle scale (`hover:scale-105`) or color shift. Add `transition-all duration-200`.
|
|
92
|
+
- Never use default browser button styles.
|
|
93
|
+
|
|
94
|
+
### Cards
|
|
95
|
+
- Background slightly offset from page bg (e.g., white card on gray bg, or lighter card on dark bg).
|
|
96
|
+
- Consistent `rounded-xl` or `rounded-2xl`.
|
|
97
|
+
- Subtle shadow: `shadow-sm` or `shadow-md`. On dark themes use border instead.
|
|
98
|
+
- Hover state for interactive cards: lift (`hover:-translate-y-1 hover:shadow-lg`).
|
|
99
|
+
|
|
100
|
+
### Forms / Inputs
|
|
101
|
+
- Inputs: `rounded-lg`, visible border, generous padding (`px-4 py-3`).
|
|
102
|
+
- Focus state: accent-colored ring (`focus:ring-2 focus:ring-accent`).
|
|
103
|
+
- Labels above inputs, not floating.
|
|
104
|
+
- Error states: red border + error message below.
|
|
105
|
+
|
|
106
|
+
### Navigation
|
|
107
|
+
- Sticky/fixed navbar with blur backdrop (`backdrop-blur-md bg-white/80`).
|
|
108
|
+
- Logo/name on left, links center or right, CTA button far right.
|
|
109
|
+
- Mobile: hamburger menu with slide-in drawer or dropdown.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Anti-Patterns — NEVER Do These
|
|
114
|
+
|
|
115
|
+
1. **Default Tailwind colors** — `bg-blue-500 text-gray-700` without custom palette
|
|
116
|
+
2. **System fonts** — `-apple-system, BlinkMacSystemFont` or `font-sans` without override
|
|
117
|
+
3. **Flat layouts** — sections that all look the same width/padding/structure
|
|
118
|
+
4. **Missing hover states** — buttons/links that don't respond to hover
|
|
119
|
+
5. **Placeholder.com images** — use gradient boxes, SVG illustrations, or emoji as placeholders instead
|
|
120
|
+
6. **Lorem ipsum** — generate realistic fake content based on the product context
|
|
121
|
+
7. **Inconsistent spacing** — mixing random padding/margin values
|
|
122
|
+
8. **Tiny click targets** — buttons and links must be minimum 44x44px touch targets
|
|
123
|
+
9. **No visual hierarchy** — everything same size/weight, no emphasis
|
|
124
|
+
10. **Generic hero** — "Welcome to [Product]" with no visual interest
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Responsive Design
|
|
129
|
+
|
|
130
|
+
- **Mobile-first** approach — design for 375px width, enhance for desktop.
|
|
131
|
+
- Breakpoints: `sm` (640px), `md` (768px), `lg` (1024px), `xl` (1280px).
|
|
132
|
+
- Hero text scales down: 48px desktop → 32px mobile.
|
|
133
|
+
- Feature grids: 3 columns desktop → 1 column mobile.
|
|
134
|
+
- Navigation: full links desktop → hamburger mobile.
|
|
135
|
+
- Cards: horizontal layout desktop → stacked mobile.
|
|
136
|
+
- Always test that nothing overflows horizontally on mobile.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Landing Page — SaaS Section Patterns
|
|
2
|
+
|
|
3
|
+
Structure and patterns for the mandatory MVP landing page.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Required Sections (in order)
|
|
8
|
+
|
|
9
|
+
Every landing page must include ALL of these sections. Do not skip any.
|
|
10
|
+
|
|
11
|
+
### 1. Navbar
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
[Logo/Name] [Features] [Pricing] [Waitlist] [Join Waitlist →]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Sticky at top with backdrop blur.
|
|
18
|
+
- Logo or product name (text logo is fine — use display font).
|
|
19
|
+
- 2-4 nav links that scroll to sections (anchor links).
|
|
20
|
+
- CTA button on the right that scrolls to waitlist section.
|
|
21
|
+
- Mobile: collapse to hamburger.
|
|
22
|
+
|
|
23
|
+
### 2. Hero Section
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────┐
|
|
27
|
+
│ │
|
|
28
|
+
│ [small eyebrow badge or label] │
|
|
29
|
+
│ │
|
|
30
|
+
│ Big Bold Headline That Sells │
|
|
31
|
+
│ the Benefit, Not the Feature │
|
|
32
|
+
│ │
|
|
33
|
+
│ A subheadline that elaborates in 1-2 │
|
|
34
|
+
│ sentences. Specific, not vague. │
|
|
35
|
+
│ │
|
|
36
|
+
│ [Primary CTA Button] [Secondary Link] │
|
|
37
|
+
│ │
|
|
38
|
+
│ ┌─────────────────────────────────┐ │
|
|
39
|
+
│ │ Hero visual / app preview / │ │
|
|
40
|
+
│ │ illustration / gradient box │ │
|
|
41
|
+
│ └─────────────────────────────────┘ │
|
|
42
|
+
│ │
|
|
43
|
+
└─────────────────────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Headline**: Benefit-driven ("Ship faster" not "Project management tool"). 5-10 words max.
|
|
47
|
+
- **Subheadline**: Explain what it does and for whom. 1-2 sentences.
|
|
48
|
+
- **CTA**: Action verb + outcome ("Join the waitlist", "Get early access", "Start free").
|
|
49
|
+
- **Visual**: App screenshot mockup, abstract gradient, or SVG illustration. Never leave empty.
|
|
50
|
+
- **Eyebrow**: Optional small badge above headline ("Now in beta", "For developers", "AI-powered").
|
|
51
|
+
|
|
52
|
+
### 3. Social Proof Bar
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Trusted by 500+ early adopters
|
|
56
|
+
[Logo] [Logo] [Logo] [Logo] [Logo]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Single line below hero.
|
|
60
|
+
- Use placeholder company names/logos (styled as gray text or simple SVG shapes).
|
|
61
|
+
- Alternatively: "Join 500+ people on the waitlist" with a count (fake but plausible).
|
|
62
|
+
- Keep subtle — muted colors, small text.
|
|
63
|
+
|
|
64
|
+
### 4. Features Grid
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
68
|
+
│ 🎯 Icon │ │ ⚡ Icon │ │ 🔒 Icon │
|
|
69
|
+
│ Title │ │ Title │ │ Title │
|
|
70
|
+
│ 2-line │ │ 2-line │ │ 2-line │
|
|
71
|
+
│ desc │ │ desc │ │ desc │
|
|
72
|
+
└──────────┘ └──────────┘ └──────────┘
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- 3-6 features in a grid (3 columns desktop, 1 mobile).
|
|
76
|
+
- Each card: icon/emoji + title (3-5 words) + description (1-2 sentences).
|
|
77
|
+
- Icons: use emoji or simple SVG. Heroicons or Lucide if the stack supports it.
|
|
78
|
+
- Feature text must match the key features from Step 1.
|
|
79
|
+
|
|
80
|
+
### 5. How It Works
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Step 1 Step 2 Step 3
|
|
84
|
+
① ② ③
|
|
85
|
+
Sign up Connect your See results
|
|
86
|
+
and set up data source in minutes
|
|
87
|
+
your account in one click
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- 3 steps (rarely 4). Numbered or with icons.
|
|
91
|
+
- Each step: number/icon + title + 1-sentence description.
|
|
92
|
+
- Optional: connecting line or arrow between steps.
|
|
93
|
+
- Explains the user journey from signup to value.
|
|
94
|
+
|
|
95
|
+
### 6. Pricing
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
┌──────────┐ ┌──────────────┐ ┌──────────┐
|
|
99
|
+
│ Free │ │ Pro ⭐ │ │Enterprise│
|
|
100
|
+
│ $0/mo │ │ $29/mo │ │ Custom │
|
|
101
|
+
│ │ │ │ │ │
|
|
102
|
+
│ • 3 feat│ │ • All free │ │ • All │
|
|
103
|
+
│ • Basic │ │ • 5 more │ │ • Custom│
|
|
104
|
+
│ │ │ • Priority │ │ • SLA │
|
|
105
|
+
│ [Start] │ │ [Get Pro] │ │ [Contact]│
|
|
106
|
+
└──────────┘ └──────────────┘ └──────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- 2-3 tiers. Middle tier highlighted (border, scale, badge).
|
|
110
|
+
- Prices should be fake but realistic for the product type.
|
|
111
|
+
- Each tier: name, price, feature list (5-7 items), CTA button.
|
|
112
|
+
- Free tier CTA → waitlist. Paid tier CTAs → waitlist (it's an MVP).
|
|
113
|
+
- All buttons route to the waitlist since nothing is real yet.
|
|
114
|
+
|
|
115
|
+
### 7. Testimonials
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
┌──────────────────────────────────┐
|
|
119
|
+
│ "This changed how I work..." │
|
|
120
|
+
│ │
|
|
121
|
+
│ [Avatar] Jane Smith │
|
|
122
|
+
│ CTO, TechCo │
|
|
123
|
+
└──────────────────────────────────┘
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- 2-3 testimonial cards. Carousel or grid.
|
|
127
|
+
- Each: quote (1-3 sentences), name, role/company, avatar placeholder.
|
|
128
|
+
- Generate realistic-sounding quotes that align with the product's value prop.
|
|
129
|
+
- Avatars: use gradient circles with initials, or `ui-avatars.com` service.
|
|
130
|
+
- Mark clearly in code comments that these are placeholder testimonials.
|
|
131
|
+
|
|
132
|
+
### 8. Waitlist / CTA Section
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
┌─────────────────────────────────────────────────┐
|
|
136
|
+
│ │
|
|
137
|
+
│ Ready to try {Product}? │
|
|
138
|
+
│ Join the waitlist for early access. │
|
|
139
|
+
│ │
|
|
140
|
+
│ [email@example.com ] [Join →] │
|
|
141
|
+
│ │
|
|
142
|
+
│ ✓ No spam. We'll only email you when │
|
|
143
|
+
│ we launch. │
|
|
144
|
+
│ │
|
|
145
|
+
└─────────────────────────────────────────────────┘
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- Prominent section near bottom (but before footer).
|
|
149
|
+
- Headline: compelling CTA ("Ready to X?", "Be the first to try").
|
|
150
|
+
- Email input + submit button on one line (desktop), stacked (mobile).
|
|
151
|
+
- Trust line below: "No spam" or "Join X others".
|
|
152
|
+
- States:
|
|
153
|
+
- **Default**: input + button enabled.
|
|
154
|
+
- **Loading**: button shows spinner, input disabled.
|
|
155
|
+
- **Success**: replace form with "You're on the list! We'll notify you at {email}."
|
|
156
|
+
- **Error**: show error message below input (invalid email, server error).
|
|
157
|
+
|
|
158
|
+
### 9. Footer
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
{Product Name} Features | Pricing | Waitlist
|
|
162
|
+
Built with ♥ © 2026 {Product}. All rights reserved.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- Simple. Product name, nav links (repeat from navbar), copyright.
|
|
166
|
+
- Optional: social links (use # placeholders).
|
|
167
|
+
- Dark or muted background to separate from content.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Waitlist Backend Patterns
|
|
172
|
+
|
|
173
|
+
### Backend Stacks (Next.js, Nuxt, Laravel)
|
|
174
|
+
|
|
175
|
+
**API Route Pattern:**
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
POST /api/waitlist
|
|
179
|
+
Body: { "email": "user@example.com" }
|
|
180
|
+
|
|
181
|
+
→ Validate email format (regex or built-in validator)
|
|
182
|
+
→ Read waitlist.json from disk (create if doesn't exist)
|
|
183
|
+
→ Check for duplicate email
|
|
184
|
+
→ Append { email, timestamp, source: "landing-page" }
|
|
185
|
+
→ Write back to waitlist.json
|
|
186
|
+
→ Return { success: true, message: "You're on the list!" }
|
|
187
|
+
|
|
188
|
+
Errors:
|
|
189
|
+
→ Invalid email: 400 { success: false, message: "Please enter a valid email." }
|
|
190
|
+
→ Duplicate: 200 { success: true, message: "You're already on the list!" }
|
|
191
|
+
→ Server error: 500 { success: false, message: "Something went wrong. Try again." }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**waitlist.json format:**
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"entries": [
|
|
198
|
+
{
|
|
199
|
+
"email": "user@example.com",
|
|
200
|
+
"timestamp": "2026-03-18T10:30:00Z",
|
|
201
|
+
"source": "landing-page"
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The waitlist.json file should be in a non-public location:
|
|
208
|
+
- Next.js: project root `./waitlist.json` (outside `public/`)
|
|
209
|
+
- Nuxt: project root `./waitlist.json`
|
|
210
|
+
- Laravel: `storage/app/waitlist.json`
|
|
211
|
+
|
|
212
|
+
### Static Stacks (React + Vite)
|
|
213
|
+
|
|
214
|
+
**Formspree Pattern:**
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<form action="https://formspree.io/f/{your-form-id}" method="POST">
|
|
218
|
+
<input type="email" name="email" required />
|
|
219
|
+
<button type="submit">Join Waitlist</button>
|
|
220
|
+
</form>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- Handle submission via JavaScript fetch for better UX (show loading/success states).
|
|
224
|
+
- Add a code comment: `// Replace {your-form-id} with your Formspree form ID — create one free at formspree.io`
|
|
225
|
+
- Handle Formspree's response format for success/error states.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Copywriting Guidelines
|
|
230
|
+
|
|
231
|
+
- **Headlines**: Lead with the benefit, not the feature. "Save 10 hours a week" > "Task management tool".
|
|
232
|
+
- **Subheadlines**: Be specific about who and what. "For freelancers who juggle too many clients" > "For everyone".
|
|
233
|
+
- **CTAs**: Action verb + outcome. "Get early access" > "Submit". "Join the waitlist" > "Sign up".
|
|
234
|
+
- **Feature descriptions**: Problem → solution format. "Stop losing track of invoices. Auto-track every payment in real time."
|
|
235
|
+
- **Tone**: Match the product. B2B SaaS = professional but warm. Dev tools = casual and direct. Consumer = friendly and energetic.
|
|
236
|
+
- **Never use**: "Revolutionize", "leverage", "synergy", "disrupt", "cutting-edge" (overused startup jargon).
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Laravel + Blade + Tailwind — Stack Reference
|
|
2
|
+
|
|
3
|
+
## Scaffold
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
composer create-project laravel/laravel {project-name}
|
|
7
|
+
cd {project-name}
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Laravel ships with Tailwind and Vite out of the box (Laravel 11+).
|
|
12
|
+
|
|
13
|
+
## Directory Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
{project-name}/
|
|
17
|
+
├── resources/
|
|
18
|
+
│ ├── views/
|
|
19
|
+
│ │ ├── layouts/
|
|
20
|
+
│ │ │ ├── landing.blade.php ← landing page layout
|
|
21
|
+
│ │ │ └── app.blade.php ← app layout (sidebar)
|
|
22
|
+
│ │ ├── components/
|
|
23
|
+
│ │ │ ├── landing/
|
|
24
|
+
│ │ │ │ ├── navbar.blade.php
|
|
25
|
+
│ │ │ │ ├── hero.blade.php
|
|
26
|
+
│ │ │ │ ├── features.blade.php
|
|
27
|
+
│ │ │ │ ├── how-it-works.blade.php
|
|
28
|
+
│ │ │ │ ├── pricing.blade.php
|
|
29
|
+
│ │ │ │ ├── testimonials.blade.php
|
|
30
|
+
│ │ │ │ ├── waitlist-form.blade.php
|
|
31
|
+
│ │ │ │ └── footer.blade.php
|
|
32
|
+
│ │ │ ├── app/
|
|
33
|
+
│ │ │ │ ├── sidebar.blade.php
|
|
34
|
+
│ │ │ │ └── {feature components}
|
|
35
|
+
│ │ │ └── ui/
|
|
36
|
+
│ │ │ ├── button.blade.php
|
|
37
|
+
│ │ │ ├── input.blade.php
|
|
38
|
+
│ │ │ ├── card.blade.php
|
|
39
|
+
│ │ │ └── modal.blade.php
|
|
40
|
+
│ │ ├── landing.blade.php ← landing page
|
|
41
|
+
│ │ ├── dashboard.blade.php ← dashboard
|
|
42
|
+
│ │ ├── {feature-1}.blade.php
|
|
43
|
+
│ │ ├── {feature-2}.blade.php
|
|
44
|
+
│ │ └── settings.blade.php
|
|
45
|
+
│ ├── css/
|
|
46
|
+
│ │ └── app.css ← Tailwind directives + custom vars
|
|
47
|
+
│ └── js/
|
|
48
|
+
│ └── app.js ← Vite entry + Alpine.js for interactivity
|
|
49
|
+
├── routes/
|
|
50
|
+
│ └── web.php ← all routes
|
|
51
|
+
├── app/
|
|
52
|
+
│ └── Http/
|
|
53
|
+
│ └── Controllers/
|
|
54
|
+
│ └── WaitlistController.php
|
|
55
|
+
├── storage/
|
|
56
|
+
│ └── app/
|
|
57
|
+
│ └── waitlist.json ← email storage (auto-created)
|
|
58
|
+
├── public/
|
|
59
|
+
│ └── {compiled assets}
|
|
60
|
+
├── tailwind.config.js
|
|
61
|
+
├── vite.config.js
|
|
62
|
+
└── package.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Routes
|
|
66
|
+
|
|
67
|
+
`routes/web.php`:
|
|
68
|
+
|
|
69
|
+
```php
|
|
70
|
+
use App\Http\Controllers\WaitlistController;
|
|
71
|
+
|
|
72
|
+
// Landing page
|
|
73
|
+
Route::view('/', 'landing');
|
|
74
|
+
|
|
75
|
+
// App pages
|
|
76
|
+
Route::view('/dashboard', 'dashboard');
|
|
77
|
+
Route::view('/{feature-1}', '{feature-1}');
|
|
78
|
+
Route::view('/{feature-2}', '{feature-2}');
|
|
79
|
+
Route::view('/settings', 'settings');
|
|
80
|
+
|
|
81
|
+
// Waitlist API
|
|
82
|
+
Route::post('/api/waitlist', [WaitlistController::class, 'store']);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Layouts
|
|
86
|
+
|
|
87
|
+
### Landing Layout (`resources/views/layouts/landing.blade.php`)
|
|
88
|
+
|
|
89
|
+
```blade
|
|
90
|
+
<!DOCTYPE html>
|
|
91
|
+
<html lang="en">
|
|
92
|
+
<head>
|
|
93
|
+
<meta charset="UTF-8">
|
|
94
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
95
|
+
<title>{{ $title ?? '{Product Name}' }}</title>
|
|
96
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
97
|
+
<link href="https://fonts.googleapis.com/css2?family={DisplayFont}:wght@400;600;700;800&family={BodyFont}:wght@400;500;600&display=swap" rel="stylesheet">
|
|
98
|
+
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
99
|
+
</head>
|
|
100
|
+
<body class="bg-bg text-fg font-body antialiased">
|
|
101
|
+
{{ $slot }}
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### App Layout (`resources/views/layouts/app.blade.php`)
|
|
107
|
+
|
|
108
|
+
```blade
|
|
109
|
+
<!DOCTYPE html>
|
|
110
|
+
<html lang="en">
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="UTF-8">
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
114
|
+
<title>{{ $title ?? '{Product Name}' }}</title>
|
|
115
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
116
|
+
<link href="https://fonts.googleapis.com/css2?family={DisplayFont}:wght@400;600;700;800&family={BodyFont}:wght@400;500;600&display=swap" rel="stylesheet">
|
|
117
|
+
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
118
|
+
</head>
|
|
119
|
+
<body class="bg-bg text-fg font-body antialiased">
|
|
120
|
+
<div class="flex min-h-screen">
|
|
121
|
+
<x-app.sidebar />
|
|
122
|
+
<main class="flex-1 p-6 lg:p-8">
|
|
123
|
+
{{ $slot }}
|
|
124
|
+
</main>
|
|
125
|
+
</div>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Tailwind Config
|
|
131
|
+
|
|
132
|
+
`tailwind.config.js`:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
export default {
|
|
136
|
+
content: [
|
|
137
|
+
'./resources/**/*.blade.php',
|
|
138
|
+
'./resources/**/*.js',
|
|
139
|
+
],
|
|
140
|
+
theme: {
|
|
141
|
+
extend: {
|
|
142
|
+
colors: {
|
|
143
|
+
bg: 'var(--color-bg)',
|
|
144
|
+
fg: 'var(--color-fg)',
|
|
145
|
+
accent: 'var(--color-accent)',
|
|
146
|
+
muted: 'var(--color-muted)',
|
|
147
|
+
},
|
|
148
|
+
fontFamily: {
|
|
149
|
+
display: ['{DisplayFont}', 'serif'],
|
|
150
|
+
body: ['{BodyFont}', 'sans-serif'],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
CSS variables in `resources/css/app.css`:
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
@tailwind base;
|
|
161
|
+
@tailwind components;
|
|
162
|
+
@tailwind utilities;
|
|
163
|
+
|
|
164
|
+
:root {
|
|
165
|
+
--color-bg: #xxxxxx;
|
|
166
|
+
--color-fg: #xxxxxx;
|
|
167
|
+
--color-accent: #xxxxxx;
|
|
168
|
+
--color-muted: #xxxxxx;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Interactivity with Alpine.js
|
|
173
|
+
|
|
174
|
+
Install Alpine.js for lightweight interactivity (modals, toasts, form states):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm install alpinejs
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`resources/js/app.js`:
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
import Alpine from 'alpinejs'
|
|
184
|
+
window.Alpine = Alpine
|
|
185
|
+
Alpine.start()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Waitlist Controller
|
|
189
|
+
|
|
190
|
+
`app/Http/Controllers/WaitlistController.php`:
|
|
191
|
+
|
|
192
|
+
```php
|
|
193
|
+
<?php
|
|
194
|
+
|
|
195
|
+
namespace App\Http\Controllers;
|
|
196
|
+
|
|
197
|
+
use Illuminate\Http\Request;
|
|
198
|
+
use Illuminate\Support\Facades\Storage;
|
|
199
|
+
|
|
200
|
+
class WaitlistController extends Controller
|
|
201
|
+
{
|
|
202
|
+
public function store(Request $request)
|
|
203
|
+
{
|
|
204
|
+
$request->validate([
|
|
205
|
+
'email' => 'required|email',
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
$email = $request->input('email');
|
|
209
|
+
$path = 'waitlist.json';
|
|
210
|
+
|
|
211
|
+
// Read or create
|
|
212
|
+
$data = Storage::exists($path)
|
|
213
|
+
? json_decode(Storage::get($path), true)
|
|
214
|
+
: ['entries' => []];
|
|
215
|
+
|
|
216
|
+
// Check duplicate
|
|
217
|
+
$exists = collect($data['entries'])->contains('email', $email);
|
|
218
|
+
if ($exists) {
|
|
219
|
+
return response()->json(['success' => true, 'message' => "You're already on the list!"]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Append
|
|
223
|
+
$data['entries'][] = [
|
|
224
|
+
'email' => $email,
|
|
225
|
+
'timestamp' => now()->toISOString(),
|
|
226
|
+
'source' => 'landing-page',
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
Storage::put($path, json_encode($data, JSON_PRETTY_PRINT));
|
|
230
|
+
|
|
231
|
+
return response()->json(['success' => true, 'message' => "You're on the list!"]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Blade Component Patterns
|
|
237
|
+
|
|
238
|
+
### Anonymous Components (preferred for UI)
|
|
239
|
+
|
|
240
|
+
`resources/views/components/ui/button.blade.php`:
|
|
241
|
+
|
|
242
|
+
```blade
|
|
243
|
+
@props(['variant' => 'primary', 'size' => 'md'])
|
|
244
|
+
|
|
245
|
+
@php
|
|
246
|
+
$classes = match($variant) {
|
|
247
|
+
'primary' => 'bg-accent text-white hover:opacity-90',
|
|
248
|
+
'secondary' => 'border border-accent text-accent hover:bg-accent/10',
|
|
249
|
+
'ghost' => 'text-fg hover:bg-muted/20',
|
|
250
|
+
};
|
|
251
|
+
$sizes = match($size) {
|
|
252
|
+
'sm' => 'px-4 py-2 text-sm',
|
|
253
|
+
'md' => 'px-6 py-3 text-base',
|
|
254
|
+
'lg' => 'px-8 py-4 text-lg',
|
|
255
|
+
};
|
|
256
|
+
@endphp
|
|
257
|
+
|
|
258
|
+
<button {{ $attributes->merge(['class' => "$classes $sizes rounded-xl font-medium transition-all duration-200"]) }}>
|
|
259
|
+
{{ $slot }}
|
|
260
|
+
</button>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Usage: `<x-ui.button variant="primary">Join Waitlist</x-ui.button>`
|
|
264
|
+
|
|
265
|
+
### Landing Page
|
|
266
|
+
|
|
267
|
+
`resources/views/landing.blade.php`:
|
|
268
|
+
|
|
269
|
+
```blade
|
|
270
|
+
<x-layouts.landing>
|
|
271
|
+
<x-landing.navbar />
|
|
272
|
+
<x-landing.hero />
|
|
273
|
+
<x-landing.features />
|
|
274
|
+
<x-landing.how-it-works />
|
|
275
|
+
<x-landing.pricing />
|
|
276
|
+
<x-landing.testimonials />
|
|
277
|
+
<x-landing.waitlist-form />
|
|
278
|
+
<x-landing.footer />
|
|
279
|
+
</x-layouts.landing>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Waitlist Form with Alpine.js
|
|
283
|
+
|
|
284
|
+
```blade
|
|
285
|
+
<div x-data="{ email: '', status: 'idle', message: '' }">
|
|
286
|
+
<form @submit.prevent="
|
|
287
|
+
status = 'loading';
|
|
288
|
+
fetch('/api/waitlist', {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
|
291
|
+
body: JSON.stringify({ email })
|
|
292
|
+
})
|
|
293
|
+
.then(r => r.json())
|
|
294
|
+
.then(d => { status = 'success'; message = d.message; })
|
|
295
|
+
.catch(() => { status = 'error'; message = 'Something went wrong.'; })
|
|
296
|
+
">
|
|
297
|
+
<template x-if="status !== 'success'">
|
|
298
|
+
<div class="flex gap-3">
|
|
299
|
+
<input x-model="email" type="email" placeholder="you@example.com" required
|
|
300
|
+
class="flex-1 px-4 py-3 rounded-lg border focus:ring-2 focus:ring-accent" />
|
|
301
|
+
<x-ui.button type="submit" x-bind:disabled="status === 'loading'">
|
|
302
|
+
<span x-show="status !== 'loading'">Join Waitlist</span>
|
|
303
|
+
<span x-show="status === 'loading'">Joining...</span>
|
|
304
|
+
</x-ui.button>
|
|
305
|
+
</div>
|
|
306
|
+
</template>
|
|
307
|
+
<p x-show="status === 'success'" x-text="message" class="text-green-600 font-medium"></p>
|
|
308
|
+
<p x-show="status === 'error'" x-text="message" class="text-red-500 text-sm mt-2"></p>
|
|
309
|
+
</form>
|
|
310
|
+
</div>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Dev Server
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Run both in separate terminals (or use Concurrently)
|
|
317
|
+
php artisan serve # http://localhost:8000
|
|
318
|
+
npm run dev # Vite dev server for assets
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Or use Laravel Herd if available (auto-serves at `{project-name}.test`).
|