@kennethsolomon/shipkit 3.13.2 → 3.15.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.
Files changed (45) hide show
  1. package/README.md +7 -6
  2. package/commands/sk/brainstorm.md +13 -0
  3. package/commands/sk/execute-plan.md +1 -0
  4. package/commands/sk/security-check.md +4 -0
  5. package/commands/sk/website.md +93 -0
  6. package/commands/sk/write-plan.md +38 -0
  7. package/package.json +1 -1
  8. package/skills/sk:autopilot/SKILL.md +0 -1
  9. package/skills/sk:fast-track/SKILL.md +0 -1
  10. package/skills/sk:gates/SKILL.md +4 -1
  11. package/skills/sk:retro/SKILL.md +0 -1
  12. package/skills/sk:reverse-doc/SKILL.md +0 -1
  13. package/skills/sk:review/SKILL.md +24 -6
  14. package/skills/sk:scope-check/SKILL.md +0 -1
  15. package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +13 -0
  16. package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +1 -0
  17. package/skills/sk:setup-claude/templates/commands/security-check.md.template +3 -0
  18. package/skills/sk:setup-claude/templates/commands/write-plan.md.template +37 -0
  19. package/skills/sk:start/SKILL.md +0 -1
  20. package/skills/sk:team/SKILL.md +0 -1
  21. package/skills/sk:website/SKILL.md +471 -0
  22. package/skills/sk:website/references/art-direction.md +210 -0
  23. package/skills/sk:website/references/brief-template.md +121 -0
  24. package/skills/sk:website/references/content-seo.md +143 -0
  25. package/skills/sk:website/references/handoff-template.md +261 -0
  26. package/skills/sk:website/references/launch-checklist.md +99 -0
  27. package/skills/sk:website/references/niche/accountant.md +75 -0
  28. package/skills/sk:website/references/niche/agency.md +75 -0
  29. package/skills/sk:website/references/niche/cafe.md +79 -0
  30. package/skills/sk:website/references/niche/dentist.md +78 -0
  31. package/skills/sk:website/references/niche/ecommerce.md +76 -0
  32. package/skills/sk:website/references/niche/gym.md +75 -0
  33. package/skills/sk:website/references/niche/home-services.md +76 -0
  34. package/skills/sk:website/references/niche/law-firm.md +75 -0
  35. package/skills/sk:website/references/niche/local-business.md +78 -0
  36. package/skills/sk:website/references/niche/med-spa.md +78 -0
  37. package/skills/sk:website/references/niche/portfolio.md +77 -0
  38. package/skills/sk:website/references/niche/real-estate.md +72 -0
  39. package/skills/sk:website/references/niche/restaurant.md +80 -0
  40. package/skills/sk:website/references/niche/saas.md +80 -0
  41. package/skills/sk:website/references/niche/wedding.md +80 -0
  42. package/skills/sk:website/references/stacks/laravel.md +425 -0
  43. package/skills/sk:website/references/stacks/nextjs.md +345 -0
  44. package/skills/sk:website/references/stacks/nuxt.md +374 -0
  45. package/skills/sk:website/references/whatsapp-cta.md +160 -0
@@ -0,0 +1,425 @@
1
+ # Laravel 11 + Blade + Tailwind — Client Website Stack Reference
2
+
3
+ Stack for building multi-page client marketing sites with PHP/Laravel. NOT a prototype — real copy, real SEO, no fake data.
4
+
5
+ ## Scaffold
6
+
7
+ ```bash
8
+ composer create-project laravel/laravel {project-name}
9
+ cd {project-name}
10
+ npm install
11
+ ```
12
+
13
+ Laravel 11 ships with Tailwind CSS configured via Vite out of the box.
14
+
15
+ ## Directory Structure
16
+
17
+ ```
18
+ {project-name}/
19
+ ├── resources/
20
+ │ ├── views/
21
+ │ │ ├── layouts/
22
+ │ │ │ └── site.blade.php ← site layout (head, nav, footer, WhatsApp)
23
+ │ │ ├── components/
24
+ │ │ │ ├── layout/
25
+ │ │ │ │ ├── navbar.blade.php
26
+ │ │ │ │ └── footer.blade.php
27
+ │ │ │ ├── home/
28
+ │ │ │ │ ├── hero.blade.php
29
+ │ │ │ │ ├── services.blade.php
30
+ │ │ │ │ └── testimonials.blade.php
31
+ │ │ │ ├── contact/
32
+ │ │ │ │ └── form.blade.php
33
+ │ │ │ └── whatsapp-button.blade.php ← floating CTA partial
34
+ │ │ ├── home.blade.php ← Home page
35
+ │ │ ├── about.blade.php ← About page
36
+ │ │ ├── services.blade.php ← Services / Menu page
37
+ │ │ └── contact.blade.php ← Contact page
38
+ │ ├── css/
39
+ │ │ └── app.css ← Tailwind directives + CSS custom properties
40
+ │ └── js/
41
+ │ └── app.js ← Vite entry + Alpine.js for interactivity
42
+ ├── routes/
43
+ │ └── web.php ← page routes + contact POST route
44
+ ├── app/
45
+ │ ├── Http/
46
+ │ │ └── Controllers/
47
+ │ │ └── ContactController.php ← contact form handler
48
+ │ └── Data/
49
+ │ └── SiteData.php ← typed site config: copy, pages, metadata
50
+ ├── config/
51
+ │ └── site.php ← site-wide config values
52
+ ├── public/
53
+ │ ├── images/
54
+ │ └── favicon.ico
55
+ ├── tailwind.config.js
56
+ └── vite.config.js
57
+ ```
58
+
59
+ ## Site Config
60
+
61
+ `config/site.php` — single source of truth for all copy and metadata:
62
+
63
+ ```php
64
+ <?php
65
+
66
+ return [
67
+ 'name' => '{Business Name}',
68
+ 'tagline' => '{Tagline}',
69
+ 'description' => '{Meta description — used for SEO}',
70
+ 'url' => env('APP_URL', 'https://{domain}'),
71
+ 'phone' => '{639171234567}', // E.164 without +
72
+ 'email' => '{contact@example.com}',
73
+ 'address' => '{Full address}',
74
+ 'hours' => '{Mon–Fri 9am–6pm}',
75
+ 'social' => [
76
+ 'facebook' => '{https://facebook.com/page}',
77
+ 'instagram' => '{https://instagram.com/handle}',
78
+ ],
79
+ 'pages' => [
80
+ 'home' => [
81
+ 'title' => '{Business Name} — {Primary benefit}',
82
+ 'description' => '{Page-specific meta description}',
83
+ 'hero' => [
84
+ 'headline' => '{Real headline — no Lorem ipsum}',
85
+ 'subheadline' => '{Supporting line}',
86
+ 'cta' => '{Primary CTA text}',
87
+ 'cta_href' => '/contact',
88
+ ],
89
+ ],
90
+ 'about' => [
91
+ 'title' => 'About — {Business Name}',
92
+ 'description' => '{About page meta description}',
93
+ ],
94
+ 'services' => [
95
+ 'title' => 'Services — {Business Name}',
96
+ 'description' => '{Services page meta description}',
97
+ 'items' => [
98
+ ['name' => '{Service 1}', 'description' => '{Real description}', 'price' => '{optional}'],
99
+ ],
100
+ ],
101
+ 'contact' => [
102
+ 'title' => 'Contact — {Business Name}',
103
+ 'description' => '{Contact page meta description}',
104
+ ],
105
+ ],
106
+ ];
107
+ ```
108
+
109
+ ## Site Layout
110
+
111
+ `resources/views/layouts/site.blade.php`:
112
+
113
+ ```blade
114
+ <!DOCTYPE html>
115
+ <html lang="en">
116
+ <head>
117
+ <meta charset="UTF-8">
118
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
119
+ <title>{{ $title ?? config('site.name') }}</title>
120
+ <meta name="description" content="{{ $description ?? config('site.description') }}">
121
+
122
+ {{-- Open Graph --}}
123
+ <meta property="og:title" content="{{ $title ?? config('site.name') }}">
124
+ <meta property="og:description" content="{{ $description ?? config('site.description') }}">
125
+ <meta property="og:url" content="{{ $canonical ?? url()->current() }}">
126
+ <meta property="og:site_name" content="{{ config('site.name') }}">
127
+ <meta property="og:type" content="website">
128
+
129
+ {{-- Canonical --}}
130
+ <link rel="canonical" href="{{ $canonical ?? url()->current() }}">
131
+ <link rel="icon" href="/favicon.ico">
132
+
133
+ {{-- Fonts: replace with art-direction spec fonts --}}
134
+ <link rel="preconnect" href="https://fonts.googleapis.com">
135
+ <link href="https://fonts.googleapis.com/css2?family={DisplayFont}:wght@400;600;700;800&family={BodyFont}:wght@400;500;600&display=swap" rel="stylesheet">
136
+
137
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
138
+ </head>
139
+ <body class="bg-bg text-fg font-body antialiased">
140
+ <x-layout.navbar />
141
+ <main>
142
+ {{ $slot }}
143
+ </main>
144
+ <x-layout.footer />
145
+
146
+ {{-- Remove if not a local PH/SEA business --}}
147
+ <x-whatsapp-button :phone="config('site.phone')" message="Hi! I found you on your website." />
148
+
149
+ {{-- LocalBusiness structured data --}}
150
+ <script type="application/ld+json">
151
+ {
152
+ "@context": "https://schema.org",
153
+ "@type": "LocalBusiness",
154
+ "name": "{{ config('site.name') }}",
155
+ "description": "{{ config('site.description') }}",
156
+ "url": "{{ config('site.url') }}",
157
+ "telephone": "+{{ config('site.phone') }}",
158
+ "address": {
159
+ "@type": "PostalAddress",
160
+ "streetAddress": "{{ config('site.address') }}"
161
+ }
162
+ }
163
+ </script>
164
+ </body>
165
+ </html>
166
+ ```
167
+
168
+ ## Page Views
169
+
170
+ `resources/views/home.blade.php`:
171
+
172
+ ```blade
173
+ <x-layouts.site
174
+ title="{{ config('site.pages.home.title') }}"
175
+ description="{{ config('site.pages.home.description') }}"
176
+ >
177
+ <x-home.hero />
178
+ <x-home.services />
179
+ <x-home.testimonials />
180
+ </x-layouts.site>
181
+ ```
182
+
183
+ `resources/views/contact.blade.php`:
184
+
185
+ ```blade
186
+ <x-layouts.site
187
+ title="{{ config('site.pages.contact.title') }}"
188
+ description="{{ config('site.pages.contact.description') }}"
189
+ >
190
+ <section class="py-24 px-4 max-w-2xl mx-auto">
191
+ <h1 class="font-display text-4xl font-bold mb-8">Contact Us</h1>
192
+ <x-contact.form />
193
+ </section>
194
+ </x-layouts.site>
195
+ ```
196
+
197
+ ## Routes
198
+
199
+ `routes/web.php`:
200
+
201
+ ```php
202
+ <?php
203
+
204
+ use App\Http\Controllers\ContactController;
205
+
206
+ Route::view('/', 'home')->name('home');
207
+ Route::view('/about', 'about')->name('about');
208
+ Route::view('/services', 'services')->name('services');
209
+ Route::view('/contact', 'contact')->name('contact');
210
+
211
+ Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
212
+
213
+ // Sitemap
214
+ Route::get('/sitemap.xml', function () {
215
+ $sitemap = simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>');
216
+ foreach (['/', '/about', '/services', '/contact'] as $path) {
217
+ $url = $sitemap->addChild('url');
218
+ $url->addChild('loc', config('site.url') . $path);
219
+ $url->addChild('changefreq', 'monthly');
220
+ $url->addChild('priority', $path === '/' ? '1.0' : '0.8');
221
+ }
222
+ return response($sitemap->asXML(), 200)->header('Content-Type', 'application/xml');
223
+ })->name('sitemap');
224
+
225
+ // Robots.txt
226
+ Route::get('/robots.txt', function () {
227
+ return response("User-agent: *\nAllow: /\nSitemap: " . config('site.url') . "/sitemap.xml", 200)
228
+ ->header('Content-Type', 'text/plain');
229
+ });
230
+ ```
231
+
232
+ ## Contact Controller
233
+
234
+ `app/Http/Controllers/ContactController.php`:
235
+
236
+ ```php
237
+ <?php
238
+
239
+ namespace App\Http\Controllers;
240
+
241
+ use Illuminate\Http\Request;
242
+
243
+ class ContactController extends Controller
244
+ {
245
+ public function store(Request $request)
246
+ {
247
+ $validated = $request->validate([
248
+ 'name' => 'required|string|max:255',
249
+ 'email' => 'required|email',
250
+ 'phone' => 'nullable|string|max:50',
251
+ 'message' => 'required|string|max:5000',
252
+ ]);
253
+
254
+ // Honeypot check (add a hidden "website" field to the form)
255
+ if ($request->filled('website')) {
256
+ return back()->with('success', "Message received. We'll be in touch soon.");
257
+ }
258
+
259
+ // TODO: wire to mail (Mail::to(...)->send(new ContactMail($validated)))
260
+ // For now: log submission
261
+ logger()->info('Contact form submission', $validated);
262
+
263
+ return back()->with('success', "Message received. We'll be in touch soon.");
264
+ }
265
+ }
266
+ ```
267
+
268
+ ## Contact Form Component
269
+
270
+ `resources/views/components/contact/form.blade.php`:
271
+
272
+ ```blade
273
+ <form
274
+ action="{{ route('contact.store') }}"
275
+ method="POST"
276
+ x-data="{ loading: false }"
277
+ @submit="loading = true"
278
+ class="space-y-4"
279
+ >
280
+ @csrf
281
+
282
+ {{-- Honeypot --}}
283
+ <input type="text" name="website" class="hidden" autocomplete="off" tabindex="-1">
284
+
285
+ @if (session('success'))
286
+ <p class="text-green-600 font-medium">{{ session('success') }}</p>
287
+ @endif
288
+
289
+ <input type="text" name="name" value="{{ old('name') }}" placeholder="Your name" required
290
+ class="w-full px-4 py-3 border rounded-lg @error('name') border-red-500 @enderror">
291
+ @error('name') <p class="text-red-500 text-sm">{{ $message }}</p> @enderror
292
+
293
+ <input type="email" name="email" value="{{ old('email') }}" placeholder="Email address" required
294
+ class="w-full px-4 py-3 border rounded-lg @error('email') border-red-500 @enderror">
295
+ @error('email') <p class="text-red-500 text-sm">{{ $message }}</p> @enderror
296
+
297
+ <input type="tel" name="phone" value="{{ old('phone') }}" placeholder="Phone (optional)"
298
+ class="w-full px-4 py-3 border rounded-lg">
299
+
300
+ <textarea name="message" placeholder="Your message" required rows="4"
301
+ class="w-full px-4 py-3 border rounded-lg resize-none @error('message') border-red-500 @enderror">{{ old('message') }}</textarea>
302
+ @error('message') <p class="text-red-500 text-sm">{{ $message }}</p> @enderror
303
+
304
+ <button type="submit" :disabled="loading"
305
+ class="w-full px-6 py-3 bg-accent text-white rounded-lg font-medium transition hover:opacity-90 disabled:opacity-60">
306
+ <span x-show="!loading">Send Message</span>
307
+ <span x-show="loading">Sending...</span>
308
+ </button>
309
+ </form>
310
+ ```
311
+
312
+ ## WhatsApp Blade Component
313
+
314
+ `resources/views/components/whatsapp-button.blade.php`:
315
+
316
+ ```blade
317
+ @props([
318
+ 'phone', // E.164 without +: e.g., "639171234567"
319
+ 'message' => null,
320
+ ])
321
+
322
+ @php
323
+ $url = $message
324
+ ? 'https://wa.me/' . $phone . '?text=' . urlencode($message)
325
+ : 'https://wa.me/' . $phone;
326
+ @endphp
327
+
328
+ <a
329
+ href="{{ $url }}"
330
+ target="_blank"
331
+ rel="noopener noreferrer"
332
+ aria-label="Chat on WhatsApp"
333
+ class="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-[#25D366] shadow-lg transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-[#25D366] focus:ring-offset-2"
334
+ >
335
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" class="h-7 w-7" aria-hidden="true">
336
+ <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
337
+ </svg>
338
+ </a>
339
+ ```
340
+
341
+ ## Tailwind Config
342
+
343
+ `tailwind.config.js`:
344
+
345
+ ```js
346
+ export default {
347
+ content: [
348
+ './resources/**/*.blade.php',
349
+ './resources/**/*.js',
350
+ ],
351
+ theme: {
352
+ extend: {
353
+ colors: {
354
+ bg: 'var(--color-bg)',
355
+ fg: 'var(--color-fg)',
356
+ accent: 'var(--color-accent)',
357
+ muted: 'var(--color-muted)',
358
+ surface: 'var(--color-surface)',
359
+ },
360
+ fontFamily: {
361
+ display: ['{DisplayFont}', 'serif'],
362
+ body: ['{BodyFont}', 'sans-serif'],
363
+ },
364
+ },
365
+ },
366
+ }
367
+ ```
368
+
369
+ `resources/css/app.css`:
370
+
371
+ ```css
372
+ @tailwind base;
373
+ @tailwind components;
374
+ @tailwind utilities;
375
+
376
+ :root {
377
+ --color-bg: #xxxxxx; /* from art direction spec */
378
+ --color-fg: #xxxxxx;
379
+ --color-accent: #xxxxxx;
380
+ --color-muted: #xxxxxx;
381
+ --color-surface: #xxxxxx;
382
+ }
383
+ ```
384
+
385
+ ## Alpine.js for Interactivity
386
+
387
+ `resources/js/app.js`:
388
+
389
+ ```js
390
+ import Alpine from 'alpinejs'
391
+ window.Alpine = Alpine
392
+ Alpine.start()
393
+ ```
394
+
395
+ Install:
396
+
397
+ ```bash
398
+ npm install alpinejs
399
+ ```
400
+
401
+ ## Dev + Build Commands
402
+
403
+ ```bash
404
+ # Run both in separate terminals
405
+ php artisan serve # http://localhost:8000
406
+ npm run dev # Vite HMR for assets
407
+
408
+ # Or use Laravel Herd (auto-serves at {project-name}.test)
409
+
410
+ npm run build # compile assets for production
411
+ php artisan optimize # cache config, routes, views for production
412
+ ```
413
+
414
+ ## Vercel / Netlify Deploy
415
+
416
+ Laravel requires a PHP host — Vercel and Netlify do not support PHP natively. Options:
417
+
418
+ | Host | Notes |
419
+ |---|---|
420
+ | **Laravel Cloud** | First-party — simplest, scalable |
421
+ | **Forge + DigitalOcean** | Full control, $6–12/mo droplet |
422
+ | **Railway** | Docker-based, easy setup |
423
+ | **Render** | Free tier available for small sites |
424
+
425
+ Add deploy steps to `DEPLOY.md` based on chosen host. The default guide should recommend Laravel Cloud or Forge.