initial commit
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
---
|
||||
name: small-business-website
|
||||
description: Build complete multi-page static business websites from a design blueprint — mobile-first CSS, contact forms, local SEO, photo galleries, emergency landing pages. For local service businesses (HVAC, handyman, contractors, etc.).
|
||||
---
|
||||
|
||||
# Small Business Website Builder
|
||||
|
||||
Build a complete, production-ready static website for a local service business from a design blueprint. All pages are pure HTML/CSS/JS — no frameworks, no build step, no backend.
|
||||
|
||||
## Triggers
|
||||
|
||||
- "build a website for [business]"
|
||||
- "I need a site for [client/service]"
|
||||
- "design and build a website"
|
||||
- User provides a blueprint or design spec for a local business site
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A design blueprint or clear spec for pages, sections, layout, and SEO
|
||||
2. Business details: name, phone, email, service areas, USPs
|
||||
3. Target directory (e.g., `/mnt/seagate8tb/Websites/BusinessName`)
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Blueprint First
|
||||
If no blueprint exists, create one first. See `references/blueprint-template.md` for the format. The blueprint is the source of truth for all implementation decisions — every section, CTA, SEO placement, and color choice should be decided here before any code is written.
|
||||
|
||||
### Step 2: Directory Structure
|
||||
```
|
||||
BusinessName/
|
||||
├── BLUEPRINT.md
|
||||
├── index.html
|
||||
├── css/style.css
|
||||
├── js/main.js
|
||||
├── contact/index.html
|
||||
├── service-pages/...
|
||||
├── images/
|
||||
```
|
||||
|
||||
### Step 3: Build in Priority Order
|
||||
Build phases that each produce a working, deployable increment:
|
||||
|
||||
1. **Global shell** — `css/style.css` + `js/main.js` + one page with header/footer/mobile nav/callbar
|
||||
2. **Highest-conversion page** — usually emergency or contact, the page that makes money
|
||||
3. **Contact form** — form with client validation, loading/success/error states
|
||||
4. **Homepage** — all sections, the main routing page
|
||||
5. **Service pages** — one per service line, built to their specific psychology
|
||||
6. **Polish** — schema markup, meta tags, image optimization
|
||||
|
||||
### Step 4: Global CSS Architecture
|
||||
All styles go in `css/style.css`. Structure:
|
||||
```
|
||||
1. CSS custom properties (colors, typography, spacing, layout vars)
|
||||
2. Reset
|
||||
3. Utility classes
|
||||
4. Buttons (btn, btn-primary, btn-emergency, btn-outline, btn-large, btn-full)
|
||||
5. Phone link (.phone-link)
|
||||
6. Header (.site-header, .logo, .main-nav, .header-right, .hamburger)
|
||||
7. Mobile nav drawer (.mobile-nav)
|
||||
8. Tap-to-call bar (.callbar — mobile only, hidden on desktop)
|
||||
9. Page sections (.page-section, .section-heading, .section-subheading)
|
||||
10. Footer (.site-footer, .footer-grid, .footer-service-areas)
|
||||
11. Page-specific section styles
|
||||
12. Responsive: Tablet (768px) — grid layouts, header changes, hide mobile elements
|
||||
13. Responsive: Desktop (1024px) — larger type, wider grids
|
||||
```
|
||||
|
||||
Key patterns:
|
||||
- **Mobile-first**: single-column by default, grids activate at 768px+
|
||||
- **CSS custom properties** for all colors and spacing — enables easy rebranding
|
||||
- **System font stack** — no Google Fonts, no FOUT, fastest load
|
||||
- **Touch targets**: 44px minimum, full-width buttons on mobile
|
||||
- **Phone numbers**: always wrapped in `<a href="tel:...">` — never plain text
|
||||
|
||||
### Step 5: JavaScript (minimal)
|
||||
`js/main.js` handles:
|
||||
- Hamburger menu toggle (open/close drawer, animate icon, body scroll lock)
|
||||
- Header shrink on scroll
|
||||
- Active nav link highlighting based on current URL path
|
||||
- Smooth scroll for anchor links
|
||||
|
||||
Keep JS minimal. No frameworks. Gallery filters and form handling are page-specific inline scripts.
|
||||
|
||||
### Step 6: Contact Form Pattern
|
||||
Use Web3Forms (free tier, no backend). See `references/contact-form.md` for the full pattern.
|
||||
|
||||
Core requirements:
|
||||
- 5-6 fields max: Name*, Phone*, Email (opt), Service (dropdown)*, Description*, Lead Source (opt)
|
||||
- Client-side validation with red border + error text on required fields
|
||||
- Loading state: button disables, shows spinner, text hides
|
||||
- Success state: form replaced by thank-you card with customer's first name + emergency phone fallback
|
||||
- Error state: red banner, button re-enables for retry
|
||||
- Web3Forms POST: `fetch('https://api.web3forms.com/submit', {method:'POST', body: new FormData(form)})`
|
||||
|
||||
### Step 7: Local SEO Checklist
|
||||
- **H1**: primary keyword + city on every page
|
||||
- **H2**: service areas in at least one H2 per page
|
||||
- **Footer**: NAP (Name, Address, Phone) + all service areas on every page
|
||||
- **Image alt text**: "Service description in City TN" format
|
||||
- **Schema.org**: `LocalBusiness` JSON-LD with `areaServed` array on every page
|
||||
- **Meta descriptions**: unique 155-char description per page, include city
|
||||
- **Page titles**: "Service City TN | Business Name" format
|
||||
|
||||
### Step 8: Emergency Landing Page (if applicable)
|
||||
For service businesses with 24/7 emergency offerings, create a standalone stripped-down page:
|
||||
- Zero external resources — all CSS inline, no JS files, no images, no fonts
|
||||
- ~7KB total page weight, sub-1s cold load
|
||||
- ONE goal: phone call. Phone number is the only prominent interactive element
|
||||
- Red/urgent accent, pulsing emergency badge
|
||||
- No navigation links (people click them and bounce)
|
||||
- "No after-hours fees" prominently addressed
|
||||
|
||||
### Step 9: Before/After Gallery (if applicable)
|
||||
For service businesses where visual proof drives conversions:
|
||||
- 2×2 or 4-col grid of before/after pairs side-by-side
|
||||
- Filter bar by service category (All, Fences, Lawns, etc.)
|
||||
- Filter JS: `data-category` attributes, toggles `display:none`
|
||||
- Place gallery filter script BEFORE main.js in the HTML to ensure it runs independently
|
||||
- "Before"/"After" labels on each image
|
||||
- Lightbox on click (optional — add later)
|
||||
|
||||
### Step 10: Pricing Table (if applicable)
|
||||
- Responsive table: collapses to label-value rows on mobile using `data-label` attributes
|
||||
- Use "Flat Rate" or "Starting at $XX" language — don't lock in exact numbers
|
||||
- Disclaimer: "Final price confirmed before any work begins"
|
||||
- Wrap in a card with border and shadow for visual weight
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **`file://` CORS**: Form submissions and fetch() calls fail from `file://` origins. This is expected. Test forms from a real web server or use browser dev tools locally.
|
||||
- **`/js/main.js` path**: Works on a real web server (absolute path). On `file://`, resolves to filesystem root. Gallery filter scripts should load BEFORE main.js to run independently.
|
||||
- **Don't over-build**: No calendar scheduler, no payment forms, no user accounts. Keep it static.
|
||||
- **Phone numbers everywhere**: Every page needs at least one tappable phone link. The emergency page needs three.
|
||||
- **Blueprint lock-in**: Once the blueprint is written, treat it as spec. Don't redesign mid-build unless the user explicitly asks.
|
||||
|
||||
## After Build
|
||||
|
||||
1. Search all files for `XXX-XXXX` and replace with real phone number
|
||||
2. Replace placeholder email
|
||||
3. Get Web3Forms access key, update contact page
|
||||
4. Replace emoji/placeholder images with real photos
|
||||
5. Fill in actual pricing
|
||||
6. Serve via nginx
|
||||
@@ -0,0 +1,81 @@
|
||||
# Website Blueprint Template
|
||||
|
||||
Use this format when designing a website before building. Write the blueprint first, then build from it.
|
||||
|
||||
---
|
||||
|
||||
## Business Context
|
||||
- **Client:** [name]
|
||||
- **Business:** [type — HVAC, handyman, plumbing, etc.]
|
||||
- **USPs:** [2-4 unique selling propositions — 24/7, family-owned, flat-rate, fast response]
|
||||
- **Service Areas:** [city list]
|
||||
|
||||
## URL Structure
|
||||
```
|
||||
/ → Homepage
|
||||
/service-a → Service page A
|
||||
/service-a/landing → Standalone landing page (for ads)
|
||||
/service-b → Service page B
|
||||
/contact → Contact / Quote Request
|
||||
```
|
||||
|
||||
## Technology
|
||||
- Static HTML/CSS/JS — no backend
|
||||
- Form submissions via Web3Forms (free tier)
|
||||
- No frameworks, no build step
|
||||
- System font stack, CSS custom properties
|
||||
|
||||
---
|
||||
|
||||
## Per-Page Blueprint
|
||||
|
||||
For each page, list sections top-to-bottom with:
|
||||
- Section name
|
||||
- Visual elements (icons, photos, layout)
|
||||
- Copy and headlines (with SEO keywords)
|
||||
- CTAs (buttons, phone links)
|
||||
- Psychology notes (what the visitor feels, what they need to see)
|
||||
|
||||
### Global Header
|
||||
```
|
||||
Layout: Logo | Nav links | Phone | CTA button
|
||||
Mobile: Logo | Hamburger + sticky tap-to-call bar below
|
||||
```
|
||||
|
||||
### Global Footer
|
||||
```
|
||||
Layout: Brand | Service links | Contact | Service areas
|
||||
SEO: All service area cities in footer on every page
|
||||
```
|
||||
|
||||
### Page: [Name]
|
||||
#### Section 1: Hero
|
||||
- Headline: [H1 with primary keyword + city]
|
||||
- Subcopy: [1-2 lines]
|
||||
- CTAs: [primary CTA, secondary CTA]
|
||||
- Psychology: [what the visitor is thinking]
|
||||
|
||||
#### Section 2: [Name]
|
||||
- ...
|
||||
|
||||
[Repeat for all sections]
|
||||
|
||||
---
|
||||
|
||||
## Color Palette
|
||||
|
||||
| Role | Color | Usage |
|
||||
|------|-------|-------|
|
||||
| Primary | hex | primary CTAs, brand elements |
|
||||
| Secondary | hex | secondary CTAs |
|
||||
| Emergency | hex | urgency CTAs if applicable |
|
||||
| Dark | hex | body text, dark sections |
|
||||
| Light | hex | page background |
|
||||
|
||||
## Implementation Priority
|
||||
1. Global shell (header, footer, CSS, JS)
|
||||
2. [Highest-conversion page]
|
||||
3. [Contact form]
|
||||
4. [Homepage]
|
||||
5. [Remaining service pages]
|
||||
6. Polish (schema, meta, images)
|
||||
@@ -0,0 +1,135 @@
|
||||
# Contact Form Pattern (Web3Forms)
|
||||
|
||||
Static-site contact form that emails the business owner directly. No backend, no calendar, no payment.
|
||||
|
||||
## Form Fields
|
||||
|
||||
Keep it SHORT. Every extra field costs conversions.
|
||||
|
||||
| Field | Required | Type | Notes |
|
||||
|-------|----------|------|-------|
|
||||
| Name | Yes | text | `autocomplete="name"` |
|
||||
| Phone | Yes | tel | THE most important field for phone-first businesses |
|
||||
| Email | No | email | Optional — don't require it, it adds friction |
|
||||
| Service Needed | Yes | select | Dropdown with all service types + "Other" |
|
||||
| Project Details | Yes | textarea | 3-line minimum, gives context before callback |
|
||||
| Lead Source | No | select | Google/Facebook/Neighbor/Other — helps measure marketing |
|
||||
|
||||
**Deliberately missing:** address, calendar, file upload, CAPTCHA (add only if spam), budget field.
|
||||
|
||||
## HTML Structure
|
||||
|
||||
```html
|
||||
<div class="form-card" id="quote-form-wrapper">
|
||||
<div class="form-error" id="form-error" role="alert">
|
||||
Something went wrong. Please try again or call us directly.
|
||||
</div>
|
||||
|
||||
<form id="quote-form" novalidate>
|
||||
<!-- fields with .form-group > .form-label + .form-input/.form-select/.form-textarea + .field-error -->
|
||||
<button type="submit" class="btn submit-btn">
|
||||
<span class="btn-text">Get My Free Quote</span>
|
||||
<span class="spinner"></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Success state (hidden by default) -->
|
||||
<div class="form-success" id="form-success">
|
||||
<div class="check-icon">✓</div>
|
||||
<h3>Thanks, <span id="success-name">friend</span>!</h3>
|
||||
<p>We'll reach out within 2 hours.</p>
|
||||
<div class="emergency-note">
|
||||
<strong>🚨 Emergency?</strong> Call now: <a href="tel:...">(XXX) XXX-XXXX</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## CSS States
|
||||
|
||||
```css
|
||||
.form-input.error, .form-select.error, .form-textarea.error {
|
||||
border-color: var(--color-emergency);
|
||||
box-shadow: 0 0 0 3px rgba(220,38,38,0.1);
|
||||
}
|
||||
.field-error { display: none; }
|
||||
.field-error.visible { display: block; color: var(--color-emergency); }
|
||||
.form-error { display: none; background: var(--color-emergency-light); }
|
||||
.form-error.visible { display: block; }
|
||||
.form-success { display: none; }
|
||||
.form-success.visible { display: block; }
|
||||
.submit-btn.loading .spinner { display: inline-block; }
|
||||
.submit-btn.loading .btn-text { display: none; }
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
||||
```javascript
|
||||
var WEB3FORMS_KEY = 'YOUR_ACCESS_KEY'; // Replace with real key
|
||||
var WEB3FORMS_URL = 'https://api.web3forms.com/submit';
|
||||
|
||||
// Validation
|
||||
function validate() {
|
||||
var valid = true;
|
||||
if (!nameField.value.trim()) { showErr('name'); valid = false; }
|
||||
if (!phoneField.value.trim()) { showErr('phone'); valid = false; }
|
||||
if (!detailsField.value.trim()) { showErr('details'); valid = false; }
|
||||
if (!serviceField.value) { showErr('service'); valid = false; }
|
||||
return valid;
|
||||
}
|
||||
|
||||
// Clear errors on input
|
||||
inputs.forEach(function(el) {
|
||||
el.addEventListener('input', function() {
|
||||
el.classList.remove('error');
|
||||
getErrorEl(el.name).classList.remove('visible');
|
||||
});
|
||||
});
|
||||
|
||||
// Submit
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
if (!validate()) return;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.classList.add('loading');
|
||||
|
||||
var payload = new FormData(form);
|
||||
payload.append('access_key', WEB3FORMS_KEY);
|
||||
payload.append('subject', 'New Quote Request — Business Name');
|
||||
payload.append('from_name', 'Business Website');
|
||||
|
||||
fetch(WEB3FORMS_URL, { method: 'POST', body: payload })
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
document.getElementById('success-name').textContent =
|
||||
nameField.value.trim().split(' ')[0] || 'friend';
|
||||
form.style.display = 'none';
|
||||
successDiv.classList.add('visible');
|
||||
} else {
|
||||
throw new Error(data.message || 'Submission failed');
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.classList.remove('loading');
|
||||
errorBanner.classList.add('visible');
|
||||
console.error('Form error:', err);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to https://web3forms.com/ — create free account
|
||||
2. Copy access key
|
||||
3. Replace `YOUR_ACCESS_KEY` in the form script
|
||||
4. Configure destination email in Web3Forms dashboard
|
||||
|
||||
## Testing
|
||||
|
||||
- `file://` CORS blocks fetch() — expected. Test from a real web server.
|
||||
- Click submit with empty fields → validation errors on all required fields
|
||||
- Fill all required fields, submit → loading spinner → success card (if key valid) or error banner (if invalid key)
|
||||
- Success card shows customer's first name + emergency phone fallback
|
||||
Reference in New Issue
Block a user