# Giansausa Agent Gian Agent Gian Pitch Site

> Agent Gian — Pitch Site

- Skill: `tomevault-io/giansausa-agent-gian-agent-gian-pitch-site` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/giansausa-agent-gian-agent-gian-pitch-site`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/giansausa-agent-gian-agent-gian-pitch-site/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/giansausa-agent-gian-agent-gian-pitch-site

---


# Agent Gian — Pitch Site

A pitch site is a paid deliverable for a Filipino small business: one Next.js + Tailwind v4 codebase, deployed to Vercel under a free `*.vercel.app` URL or a real domain, that loads under a second on phones and looks like a 7-figure brand. This skill captures every decision, every code template, and every Gemini prompt from the Carnage build so you never re-explain.

The reference build: **carnageautoshop.vercel.app** · source: `C:/Users/gians/OneDrive/Desktop/Carnage Offroad Autoshop/carnage-offroad/`.

## When to invoke

Trigger this skill when the user says any of:
- "Build a pitch site for [business]"
- "Scaffold a marketing website for [business]"
- "I want a Carnage-style site"
- "Make a website I can sell to a small business"
- "Set up a one-page marketing site for my client"
- "Ship a 9-section marketing site"

Skip if the user wants a generic Next.js starter, an admin dashboard, an e-commerce checkout, or anything that needs a backend, auth, or DB writes. This is a pitch / marketing site only — every CTA is a phone call, FB DM, SMS, or email; no booking system, no payment, no user accounts.

## Onboarding interview — one question at a time

Run via `AskUserQuestion`. Single multiple-choice per turn. Recommend one option in the question prose. **Never** combine the visual companion offer with other content.

Before question 1, explore project context: run `pwd`, check for an existing repo, read CLAUDE.md if present.

### Q1 — business name + city

> What's the business name and city? (e.g. "Bayan Coffee Roasters · Quezon City")

Free-text. Save as `BUSINESS_NAME` and `BUSINESS_CITY`.

### Q2 — brand personality (visual companion required)

Offer the visual companion as its own message first: "I'll mock up 3 brand-personality directions side-by-side before we lock the look. One sec."

Then present:

> **A · Clean Pro** — Inter throughout, charcoal + white, accent blue, gentle rounded corners. Reads like a Stripe / Linear pricing page. Best for: clinics, dental, accounting, B2B services.
>
> **B · Edgy Garage (Carnage's pick)** — Anton display + Inter body, white + red accent, square clip-angle CTAs, italic skewed wordmark, Ken Burns hero. Reads like Off-road Magazine. Best for: workshops, autoshops, gyms, barbers, anything blue-collar premium.
>
> **C · Premium Build** — Playfair Display + Inter, ivory + warm-black + brass accent, generous whitespace, pinned hero photography, no clip-angle. Reads like a Michelin-recommended restaurant or a boutique salon. Best for: cafés, salons, restaurants, boutique retail.
>
> **My pick:** Edgy Garage if they sell trucks/tools/builds. Premium Build for everything else (cafés, beauty, food, hospitality). Clean Pro for clinics/B2B.

Save as `BRAND_PERSONALITY`.

### Q3 — brand colors

> The default for [BRAND_PERSONALITY] is [defaults below]. Override?
> - Edgy Garage default: white #ffffff + red accent #ff1f1f, deep #c8102e
> - Premium Build default: ivory #f8f5ee + warm-black #1a1612 + brass #b8893d
> - Clean Pro default: white #ffffff + charcoal #111111 + blue accent #2563eb

Save 4 hex codes: `--color-bg-base`, `--color-bg-dark`, `--color-accent`, `--color-accent-deep`.

### Q4 — 3-line motto

> The hero shows a 3-line motto, all-caps, with the **last line in the accent color**. Carnage's is `BUILD IT. / LIFT IT. / DOMINATE ANY TERRAIN.` What are yours?

Constraint: each line ≤ 4 short words. The last line is the punchline.

### Q5 — tagline

> One sentence: what they sell, for whom. Sub-15 words. Carnage's: "Lifts. Bumpers. Mags. Builds — for Hilux, Ranger, D-Max, Triton, BT-50."

### Q6 — categories (5 to 9)

> List 5–9 product or service categories. For each, give: slug · name · short label · 1-line tagline · 2–3 brand/sub-product samples.
>
> Example (Carnage): `lift-kits · Lift Kits · 2" Suspension · Trail clearance for Hilux, Ranger, D-Max, Triton, BT-50. · 2" · OME · Bilstein`

Save as `CATEGORIES[]`.

### Q7 — 3 trust pillars

> The "Why [Business]" section uses 3 cards with red Anton numbers (01 / 02 / 03). Each card has a 3–6 word title and a 20-word body. What are your 3 pillars? Carnage's are: Free labor installation · Installment terms accepted · Trail-tested brands.

### Q8 — brand-strip partners

> List 6–10 brand-name partners shown in greyscale on the brand strip. These are name-drops — they don't need logos. Carnage's: Profender · ARB · Bilstein · Fox · Fuel Off-Road · Old Man Emu · Panther · Roll-N-Lock.

### Q9 — process steps (4)

> The Process section has 4 steps with icons (chat / key / wrench / truck). Adapt the verbs to the business. Carnage's: Inquire · Drop off · Build · Drive away. Coffee shop: Order · Roast · Brew · Sip. Salon: Consult · Cleanse · Style · Glow up.

### Q10 — shop info

> Need 7 fields:
> - Address line 1 + line 2
> - City + zip + region
> - Mobile / primary phone (with country code, e.g. +639...)
> - Landline (optional)
> - FB page URL + handle (the part after fb.com/)
> - Email (optional — leave blank to hide email button in quote modal)
> - Hours (e.g. "Mon–Sat · 9:30am–5:30pm")
> - Google Maps embed URL (Maps → Share → Embed a map → copy the iframe `src`)
> - Google Maps share URL (Maps → Share → Copy link)

### Q11 — vehicle / customer filter list

> The product detail pages have filter chips (`All · Filter1 · Filter2 · …`). For Carnage these were vehicle fitments: All · Hilux · Ranger · D-Max · Triton · BT-50 · Other. What's the equivalent for your business?
>
> - Salon: All · Long hair · Short hair · Color · Bridal
> - Café: All · Hot · Iced · Pastry · Single-origin
> - Clinic: All · General · Pediatric · Cosmetic · Emergency

If the business doesn't need filters, skip this — the FilterChips component can be omitted from product pages.

### Q12 — selling points

> Three yes/no toggles:
> - Free labor / free consultation? (booleans displayed in trust pillars)
> - Installment plans? (booleans displayed in trust pillars)
> - Best-seller badges on certain products? (red corner-cut badges)

### Q13 — quote modal config

> The Request a Quote modal sends via SMS (always) + Email (only if `SHOP.email` is set). Messenger was tried for Carnage and kept failing — it stays disabled. Confirm:
> - Keep the floating quote button? (default yes)
> - Use SMS as primary channel? (default yes — universal, works on every PH phone)
> - Need email button? (only if owner has a real address)

After Q13, proceed straight to scaffolding. Do not ask for confirmation — the user has answered enough.

## Architecture — file tree to scaffold

Mirror Carnage exactly:

```
<slug>/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx                      # 9-section homepage
│   ├── not-found.tsx                 # site-wide 404
│   └── products/
│       └── [slug]/
│           ├── page.tsx              # generateStaticParams over CATEGORIES
│           └── not-found.tsx
├── components/
│   ├── Nav.tsx
│   ├── Footer.tsx
│   ├── DemoRibbon.tsx                # "Sample demo · created by [you]"
│   ├── RequestQuote.tsx              # client modal
│   ├── primitives/
│   │   ├── PrimaryCTA.tsx
│   │   ├── SecondaryCTA.tsx
│   │   ├── Microlabel.tsx
│   │   ├── SectionHeader.tsx
│   │   └── Reveal.tsx                # client, IntersectionObserver
│   ├── home/
│   │   ├── Hero.tsx                  # client, 3-image rotation + Ken Burns
│   │   ├── ProductsGrid.tsx
│   │   ├── WhyCarnage.tsx            # rename to Why<Business>
│   │   ├── BrandStrip.tsx
│   │   ├── HotPicks.tsx
│   │   ├── Process.tsx
│   │   ├── FacebookFeed.tsx
│   │   ├── Gallery.tsx               # client, lightbox
│   │   └── Location.tsx
│   └── product/
│       ├── CategoryHero.tsx
│       ├── FilterChips.tsx           # client, useState
│       ├── ProductCard.tsx
│       └── EmptyState.tsx
├── data/
│   ├── shop.ts                       # single source of truth
│   ├── copy.ts                       # every user-facing string
│   ├── categories.ts
│   ├── products.ts
│   └── gallery.ts
├── lib/
│   └── cn.ts                         # className joiner
├── public/
│   ├── hero/                         # 3 hero variants (PNG, ~3840×1644)
│   ├── sections/                     # 2 section banners
│   ├── categories/                   # 1 image per category
│   ├── products/
│   │   └── placeholder.svg
│   ├── gallery/                      # 8–12 real photos from FB
│   └── logo/                         # favicon + OG image
├── tests/
│   └── smoke.spec.ts
├── package.json
├── tsconfig.json
├── next.config.ts
├── playwright.config.ts
├── postcss.config.mjs
├── eslint.config.mjs
└── README.md
```

## Tech stack + startup commands

```bash
# Working dir example: C:/Users/gians/Downloads/<slug>/  (mind path-with-space quoting on Windows)
npx create-next-app@latest <slug> --ts --tailwind --eslint --app --no-src-dir --turbopack --import-alias "@/*" --use-npm --no-git
cd <slug>

# init repo
git init
git branch -M main

# install runtime deps
npm install yet-another-react-lightbox @vercel/analytics

# install playwright (dev)
npm install -D @playwright/test
npx playwright install --with-deps chromium

# type-check gate (run after every file edit)
npx tsc --noEmit
```

After every task step: `npx tsc --noEmit && git add <specific files> && git commit -m "feat(<area>): <change>"`. **Never** `git add -A` — stage by name. One logical change per commit (Agent Gian rule §V). Auto-push if user opted in.

## Tailwind v4 brand tokens — full `app/globals.css`

Tailwind v4 uses CSS-only config — no `tailwind.config.js`. All tokens live inside the `@theme` block and become utilities automatically (`bg-bg-base`, `text-accent`, etc.). Replace the four color hexes with the user's picks from Q3.

```css
@import "tailwindcss";

@theme {
  --color-bg-base: #ffffff;        /* page background */
  --color-bg-alt: #f9f9f9;         /* alternating section background */
  --color-bg-dark: #0a0a0a;        /* dark sections (Why / Gallery / EmptyState) */
  --color-accent: #ff1f1f;         /* primary accent — links, CTAs, microlabels */
  --color-accent-deep: #c8102e;    /* CTA hover */
  --color-ink: #111111;            /* body text */
  --color-ink-muted: #666666;      /* secondary text */
  --color-ink-faint: #888888;      /* tertiary text + dark-section sub */
  --color-border-soft: #eeeeee;    /* hairline borders */

  --font-display: var(--font-anton), 'Impact', 'Arial Black', sans-serif;
  --font-body: var(--font-inter), -apple-system, BlinkMacSystemFont, sans-serif;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg-base);
  color: var(--color-ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

/* Hero animation: Ken Burns drift */
@keyframes ken-burns {
  0%   { transform: scale(1) translate(0, 0); }
  100% { transform: scale(1.12) translate(-2%, -1.5%); }
}

.ken-burns {
  animation: ken-burns 8s ease-in-out infinite alternate;
  transform-origin: 60% 70%;
  will-change: transform;
}

/* Angled-cut button (garage feel — drop for Premium Build personality) */
.clip-angle {
  clip-path: polygon(6% 0, 100% 0, 94% 100%, 0 100%);
}

/* Skew helper for the logo wordmark */
.skew-logo {
  transform: skew(-5deg);
}

/* Parallax background — works on desktop, fallback handled per-component on iOS */
.bg-fixed-cover {
  background-attachment: fixed;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Reduced motion — kill animations */
@media (prefers-reduced-motion: reduce) {
  .ken-burns { animation: none; }
  .bg-fixed-cover { background-attachment: scroll; }
  * { transition: none !important; }
}

/* Skip-to-content link (a11y) */
.skip-link {
  position: absolute;
  top: -100px;
  left: 0;
  background: var(--color-accent);
  color: #fff;
  padding: 8px 12px;
  z-index: 100;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-size: 12px;
}
.skip-link:focus { top: 0; }
```

For **Premium Build**, swap `--font-display` to Playfair Display, drop `.clip-angle` and `.skew-logo`, swap `.ken-burns` for a softer 12s zoom-out keyframe. For **Clean Pro**, drop the display font (Inter throughout), drop `.clip-angle`, soften the Ken Burns to a 14s pan.

## Fonts — `next/font/google` setup

```tsx
// app/layout.tsx (top portion)
import { Anton, Inter } from 'next/font/google';

const anton = Anton({
  subsets: ['latin'],
  weight: '400',
  variable: '--font-anton',
  display: 'swap',
});

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',
});

// then on <html>
<html lang="en" className={`${anton.variable} ${inter.variable}`}>
```

For Premium Build, replace `Anton` with `Playfair_Display`. For Clean Pro, drop the display font import — pass only `inter.variable`.

## Root layout — full `app/layout.tsx`

```tsx
import type { Metadata } from 'next';
import { Anton, Inter } from 'next/font/google';
import { Analytics } from '@vercel/analytics/next';
import { RequestQuote } from '@/components/RequestQuote';
import { DemoRibbon } from '@/components/DemoRibbon';
import './globals.css';

const anton = Anton({
  subsets: ['latin'],
  weight: '400',
  variable: '--font-anton',
  display: 'swap',
});

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',
});

export const metadata: Metadata = {
  title: '<BUSINESS_NAME> · <BUSINESS_CITY>',
  description: '<TAGLINE>',
  metadataBase: new URL('https://<DOMAIN-OR-VERCEL-URL>'),
  icons: {
    icon: '/logo/<slug>.jpg',
    apple: '/logo/<slug>.jpg',
  },
  openGraph: {
    title: '<BUSINESS_NAME>',
    description: '<TAGLINE>',
    type: 'website',
    images: [{ url: '/logo/<slug>.jpg', width: 1200, height: 630, alt: '<BUSINESS_NAME>' }],
  },
  twitter: {
    card: 'summary_large_image',
    title: '<BUSINESS_NAME>',
    description: '<TAGLINE>',
    images: ['/logo/<slug>.jpg'],
  },
  robots: { index: true, follow: true },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${anton.variable} ${inter.variable}`}>
      <body>
        <a href="#main" className="skip-link">Skip to content</a>
        <DemoRibbon />
        {children}
        <RequestQuote />
        <Analytics />
      </body>
    </html>
  );
}
```

## Homepage composition — `app/page.tsx`

```tsx
import { Nav } from '@/components/Nav';
import { Footer } from '@/components/Footer';
import { Hero } from '@/components/home/Hero';
import { ProductsGrid } from '@/components/home/ProductsGrid';
import { WhyCarnage } from '@/components/home/WhyCarnage';
import { BrandStrip } from '@/components/home/BrandStrip';
import { HotPicks } from '@/components/home/HotPicks';
import { Process } from '@/components/home/Process';
import { FacebookFeed } from '@/components/home/FacebookFeed';
import { Gallery } from '@/components/home/Gallery';
import { Location } from '@/components/home/Location';

export default function HomePage() {
  return (
    <>
      <Nav />
      <main id="main">
        <Hero />
        <ProductsGrid />
        <WhyCarnage />
        <BrandStrip />
        <HotPicks />
        <Process />
        <FacebookFeed />
        <Gallery />
        <Location />
      </main>
      <Footer />
    </>
  );
}
```

## Section 1 — Hero (`components/home/Hero.tsx`)

Client component. Three images crossfade every 7s with parallax + Ken Burns. 3-line motto in Anton uppercase, last line in accent color. Two CTAs (call + catalog). Indicator dots bottom-right.

```tsx
'use client';

import { useEffect, useState } from 'react';
import { COPY } from '@/data/copy';
import { SHOP } from '@/data/shop';
import { PrimaryCTA } from '@/components/primitives/PrimaryCTA';
import { SecondaryCTA } from '@/components/primitives/SecondaryCTA';
import { cn } from '@/lib/cn';

const HERO_IMAGES: string[] = [
  '/hero/<slug>-golden.png',
  '/hero/<slug>-stormy.png',
  '/hero/<slug>-shop.png',
];

const ROTATION_MS = 7000;
const FADE_MS = 1200;

export function Hero() {
  const [active, setActive] = useState(0);

  useEffect(() => {
    if (HERO_IMAGES.length <= 1) return;
    const id = setInterval(() => {
      setActive((prev) => (prev + 1) % HERO_IMAGES.length);
    }, ROTATION_MS);
    return () => clearInterval(id);
  }, []);

  return (
    <section
      id="hero"
      className="relative h-[80vh] min-h-[540px] overflow-hidden text-white bg-[var(--color-bg-dark)]"
    >
      {HERO_IMAGES.map((src, i) => (
        <div
          key={src}
          aria-hidden="true"
          className={cn(
            'absolute inset-0 bg-fixed-cover transition-opacity ease-in-out',
            i === active ? 'opacity-100' : 'opacity-0',
          )}
          style={{ backgroundImage: `url(${src})`, transitionDuration: `${FADE_MS}ms` }}
        >
          <div
            className="absolute inset-0 ken-burns bg-cover bg-center"
            style={{ backgroundImage: `url(${src})` }}
          />
        </div>
      ))}

      <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-b from-black/30 via-black/10 to-black/80 z-[1]" />

      <div className="relative z-10 max-w-6xl mx-auto h-full px-5 flex flex-col justify-end pb-10">
        <span className="inline-block w-fit bg-[var(--color-accent)] text-white text-[10px] font-bold uppercase tracking-[2px] px-2 py-1 mb-3">
          {COPY.hero.badge}
        </span>
        <h1 className="font-[family-name:var(--font-display)] uppercase tracking-wide text-4xl sm:text-5xl md:text-6xl leading-[0.95] drop-shadow-lg">
          {COPY.hero.h1Line1}
          <br />
          {COPY.hero.h1Line2}
          <br />
          <span className="text-[var(--color-accent)]">{COPY.hero.h1Line3}</span>
        </h1>
        <p className="text-sm md:text-base mt-3 opacity-90 drop-shadow-md">{COPY.hero.tagline}</p>
        <div className="flex gap-3 mt-5">
          <PrimaryCTA href={`tel:${SHOP.phone}`}>{COPY.hero.ctaPrimary}</PrimaryCTA>
          <SecondaryCTA href="#products" variant="light">{COPY.hero.ctaSecondary}</SecondaryCTA>
        </div>
      </div>

      {HERO_IMAGES.length > 1 && (
        <div className="absolute bottom-3 right-5 z-10 flex gap-1.5">
          {HERO_IMAGES.map((src, i) => (
            <span
              key={src}
              aria-hidden="true"
              className={cn(
                'block w-1.5 h-1.5 rounded-full transition-all duration-500',
                i === active ? 'bg-[var(--color-accent)] w-4' : 'bg-white/40',
              )}
            />
          ))}
        </div>
      )}
    </section>
  );
}
```

## Section 2 — Products grid (`components/home/ProductsGrid.tsx`)

Full-bleed banner above + 9 image-backed cards with dark gradient, red left bar, hover zoom. Cards without an image fall back to a label-only tile.

```tsx
import Image from 'next/image';
import Link from 'next/link';
import { CATEGORIES } from '@/data/categories';
import { COPY } from '@/data/copy';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { Reveal } from '@/components/primitives/Reveal';

export function ProductsGrid() {
  return (
    <section id="products" className="py-12">
      <Reveal direction="fade" duration={900}>
        <div className="relative w-full aspect-[21/9] sm:aspect-[21/9] md:aspect-[21/8] max-h-[420px] overflow-hidden">
          <Image
            src="/sections/<N>-categories.jpg"
            alt="<BUSINESS_NAME> interior — all categories in one place"
            fill
            sizes="100vw"
            priority={false}
            className="object-cover"
          />
          <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-white via-white/30 to-transparent" />
        </div>
      </Reveal>

      <div className="px-5 max-w-6xl mx-auto -mt-6 md:-mt-10 relative z-10">
        <Reveal>
          <SectionHeader
            label={COPY.products.label}
            title={<>{COPY.products.h2Line1}<br />{COPY.products.h2Line2}</>}
            sub={COPY.products.sub}
          />
        </Reveal>
        <div className="grid grid-cols-2 md:grid-cols-3 gap-2 md:gap-4">
          {CATEGORIES.map((cat, i) => (
            <Reveal key={cat.slug} delay={i * 60}>
              <Link
                href={`/products/${cat.slug}`}
                className={
                  cat.image
                    ? 'group relative block rounded-md overflow-hidden h-32 md:h-40 hover:-translate-y-0.5 hover:shadow-lg transition-all'
                    : 'relative bg-[#f6f6f6] border border-[var(--color-border-soft)] rounded-md p-3 md:p-4 pl-5 md:pl-6 h-24 md:h-28 hover:-translate-y-0.5 hover:shadow-md transition-all flex flex-col justify-between'
                }
              >
                {cat.image ? (
                  <>
                    <Image
                      src={cat.image}
                      alt={`${cat.name} — <BUSINESS_NAME>`}
                      fill
                      sizes="(max-width: 768px) 50vw, 33vw"
                      className="object-cover transition-transform duration-500 group-hover:scale-105"
                    />
                    <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" />
                    <span aria-hidden="true" className="absolute top-0 left-0 h-full w-1 bg-[var(--color-accent)] z-10" />
                    <div className="absolute bottom-2.5 left-3 right-3 z-10">
                      <div className="text-xs md:text-sm font-bold uppercase tracking-wide text-white drop-shadow-lg">{cat.name}</div>
                      <div className="text-[10px] text-white/80 mt-0.5 drop-shadow">{cat.brandSamples}</div>
                    </div>
                    <div aria-hidden="true" className="absolute bottom-2 right-3 text-[var(--color-accent)] text-base z-10 drop-shadow">→</div>
                  </>
                ) : (
                  <>
                    <span className="absolute top-0 left-0 h-full w-1 bg-[var(--color-accent)]" aria-hidden="true" />
                    <div>
                      <div className="text-xs md:text-sm font-bold uppercase tracking-wide text-[var(--color-ink)]">{cat.name}</div>
                      <div className="text-[10px] text-[var(--color-ink-faint)] mt-0.5">{cat.brandSamples}</div>
                    </div>
                    <div className="text-[var(--color-accent)] text-base self-end" aria-hidden="true">→</div>
                  </>
                )}
              </Link>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}
```

## Section 3 — Why [Business] (`components/home/WhyCarnage.tsx` → rename)

Dark section, 3 cards with red Anton numbers, copy lifted from Q7.

```tsx
import { COPY } from '@/data/copy';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { Reveal } from '@/components/primitives/Reveal';

export function WhyCarnage() {
  return (
    <section className="bg-[var(--color-bg-dark)] text-white px-5 py-12">
      <div className="max-w-6xl mx-auto">
        <Reveal>
          <SectionHeader
            label={COPY.why.label}
            title={<>{COPY.why.h2Line1}<br />{COPY.why.h2Line2}</>}
            light
          />
        </Reveal>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4">
          {COPY.why.cards.map((card, i) => (
            <Reveal key={card.title} delay={i * 100} direction="up">
              <div className="bg-black/40 border border-white/5 rounded-md p-4 h-full">
                <div className="font-[family-name:var(--font-display)] text-[var(--color-accent)] text-2xl leading-none">
                  {String(i + 1).padStart(2, '0')}
                </div>
                <h4 className="text-xs font-bold uppercase tracking-wide mt-2">{card.title}</h4>
                <p className="text-[11px] text-[var(--color-ink-faint)] mt-1.5 leading-snug">{card.body}</p>
              </div>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}
```

If renaming the file/export to `Why<Business>`, update the import in `app/page.tsx` and the `cards` source in `data/copy.ts`. Carnage-style works fine for any business — no rename strictly required.

## Section 4 — Brand strip (`components/home/BrandStrip.tsx`)

Greyscale + opacity-60 by default, restores on hover.

```tsx
import { COPY } from '@/data/copy';
import { Microlabel } from '@/components/primitives/Microlabel';
import { Reveal } from '@/components/primitives/Reveal';

export function BrandStrip() {
  return (
    <section className="bg-[var(--color-bg-alt)] px-5 py-6">
      <div className="max-w-6xl mx-auto">
        <Reveal>
          <Microlabel className="text-center">{COPY.brands.label}</Microlabel>
        </Reveal>
        <div className="mt-4 flex flex-wrap items-center justify-center gap-4 md:gap-8 grayscale opacity-60 hover:opacity-100 hover:grayscale-0 transition-all">
          {COPY.brands.list.map((brand, i) => (
            <Reveal key={brand} delay={i * 60} as="span">
              <span className="text-sm md:text-base font-extrabold uppercase tracking-wider text-[var(--color-ink)] inline-block">
                {brand}
              </span>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}
```

## Section 5 — Hot picks (`components/home/HotPicks.tsx`)

Full-bleed banner + horizontal-scroll carousel of `featured: true` products.

```tsx
import Image from 'next/image';
import { COPY } from '@/data/copy';
import { getFeaturedProducts } from '@/data/products';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { ProductCard } from '@/components/product/ProductCard';
import { Reveal } from '@/components/primitives/Reveal';

export function HotPicks() {
  const items = getFeaturedProducts(8);
  return (
    <section className="bg-[var(--color-bg-alt)] py-12">
      <Reveal direction="fade" duration={900}>
        <div className="relative w-full aspect-[21/9] md:aspect-[21/8] max-h-[420px] overflow-hidden">
          <Image
            src="/sections/moving-fast.jpg"
            alt="<BUSINESS_NAME> hot picks"
            fill
            sizes="100vw"
            className="object-cover"
          />
          <div aria-hidden="true" className="absolute inset-0 bg-gradient-to-t from-[var(--color-bg-alt)] via-[var(--color-bg-alt)]/30 to-transparent" />
        </div>
      </Reveal>

      <div className="px-5 max-w-6xl mx-auto -mt-6 md:-mt-10 relative z-10">
        <Reveal>
          <SectionHeader label={COPY.hotPicks.label} title={COPY.hotPicks.h2} sub={COPY.hotPicks.sub} />
        </Reveal>
        <Reveal delay={120}>
          <div
            className="flex gap-3 overflow-x-auto pb-2 -mx-5 px-5"
            style={{ scrollSnapType: 'x mandatory' }}
          >
            {items.map((p) => (
              <div key={p.id} style={{ scrollSnapAlign: 'start' }}>
                <ProductCard product={p} variant="carousel" />
              </div>
            ))}
          </div>
        </Reveal>
      </div>
    </section>
  );
}
```

## Section 6 — Process (`components/home/Process.tsx`)

4 steps with red Anton numbers + outlined SVG icons (paste exact paths). Icons are `currentColor` so they inherit the accent.

```tsx
import { COPY } from '@/data/copy';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { Reveal } from '@/components/primitives/Reveal';

const STEP_ICONS = [
  // 01 — Inquire (chat bubble)
  (
    <svg key="inquire" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
      <line x1="8" y1="10" x2="16" y2="10" />
      <line x1="8" y1="13" x2="13" y2="13" />
    </svg>
  ),
  // 02 — Drop off (key)
  (
    <svg key="dropoff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="7" cy="15" r="4" />
      <line x1="9.83" y1="12.17" x2="22" y2="0" />
      <line x1="17" y1="5" x2="20" y2="8" />
      <line x1="14" y1="8" x2="17" y2="11" />
    </svg>
  ),
  // 03 — Build (wrench)
  (
    <svg key="build" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.121 2.121 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
    </svg>
  ),
  // 04 — Drive away (truck)
  (
    <svg key="drive" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M1 3h13v13H1z" />
      <path d="M14 8h4l3 3v5h-7V8z" />
      <circle cx="5.5" cy="18.5" r="2.5" />
      <circle cx="18.5" cy="18.5" r="2.5" />
    </svg>
  ),
];

export function Process() {
  return (
    <section className="px-5 py-12 max-w-6xl mx-auto">
      <Reveal>
        <SectionHeader
          label={COPY.process.label}
          title={<>{COPY.process.h2Line1}<br />{COPY.process.h2Line2}</>}
        />
      </Reveal>
      <div className="grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-6">
        {COPY.process.steps.map((step, i) => (
          <Reveal key={step.n} delay={i * 120} direction="up">
            <div className="flex md:flex-col gap-3 md:gap-2">
              <div className="flex md:flex-col items-start gap-2 md:gap-1">
                <div className="text-[var(--color-accent)] w-9 h-9 md:w-10 md:h-10">
                  {STEP_ICONS[i]}
                </div>
                <div className="font-[family-name:var(--font-display)] text-[var(--color-ink-faint)] text-base md:text-sm leading-none mt-1">
                  {step.n}
                </div>
              </div>
              <div className="flex-1">
                <h4 className="text-xs font-bold uppercase tracking-wide text-[var(--color-ink)]">{step.title}</h4>
                <p className="text-[11px] text-[var(--color-ink-muted)] mt-1 leading-snug">{step.body}</p>
              </div>
            </div>
          </Reveal>
        ))}
      </div>
    </section>
  );
}
```

For non-Carnage businesses, swap the icon paths: coffee shop = mug / bag-of-beans / pour-over / cup. Salon = scissors / wash / comb / mirror. Use [feathericons.com](https://feathericons.com) — copy 24×24 viewBox, stroke-width 1.5.

## Section 7 — Facebook feed (`components/home/FacebookFeed.tsx`)

```tsx
import { COPY } from '@/data/copy';
import { SHOP } from '@/data/shop';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { Reveal } from '@/components/primitives/Reveal';

export function FacebookFeed() {
  const fbPluginUrl = `https://www.facebook.com/plugins/page.php?href=${encodeURIComponent(
    SHOP.fbUrl,
  )}&tabs=timeline&width=500&height=500&small_header=true&adapt_container_width=true&hide_cover=false&show_facepile=true`;
  return (
    <section id="updates" className="px-5 py-12 max-w-6xl mx-auto">
      <Reveal>
        <SectionHeader label={COPY.facebook.label} title={COPY.facebook.h2} />
      </Reveal>
      <Reveal delay={120}>
        <div className="flex justify-center">
          <iframe
            src={fbPluginUrl}
            title="<BUSINESS_NAME> Facebook page"
            width={500}
            height={500}
            style={{ border: 'none', overflow: 'hidden', minHeight: 500, maxWidth: '100%' }}
            loading="lazy"
            allow="encrypted-media"
          />
        </div>
        <noscript>
          <p className="text-center text-sm mt-4">
            <a href={SHOP.fbUrl} target="_blank" rel="noopener" className="text-[var(--color-accent)] underline">
              Visit our Facebook page
            </a>
          </p>
        </noscript>
      </Reveal>
    </section>
  );
}
```

If the business has Instagram instead of FB, swap the iframe for [SnapWidget](https://snapwidget.com) free embed and rename labels accordingly.

## Section 8 — Gallery (`components/home/Gallery.tsx`)

Client component. 3-col mobile / 4-col desktop. `wide` items span 2 cols. Video tiles get a red play badge. Lightbox is `yet-another-react-lightbox` with the Video plugin.

```tsx
'use client';

import { useState } from 'react';
import Image from 'next/image';
import Lightbox from 'yet-another-react-lightbox';
import Video from 'yet-another-react-lightbox/plugins/video';
import 'yet-another-react-lightbox/styles.css';

import { COPY } from '@/data/copy';
import { GALLERY } from '@/data/gallery';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { Reveal } from '@/components/primitives/Reveal';
import { cn } from '@/lib/cn';

export function Gallery() {
  const [openIndex, setOpenIndex] = useState<number | null>(null);

  const slides = GALLERY.map((item) => {
    if (item.type === 'video') {
      return {
        type: 'video' as const,
        sources: [{ src: item.src, type: 'video/mp4' }],
        poster: item.thumb,
        autoPlay: true,
      };
    }
    return { src: item.src, alt: item.caption ?? '' };
  });

  return (
    <section id="gallery" className="bg-[var(--color-bg-dark)] text-white px-5 py-12">
      <div className="max-w-6xl mx-auto">
        <Reveal>
          <SectionHeader label={COPY.gallery.label} title={COPY.gallery.h2} sub={COPY.gallery.sub} light />
        </Reveal>
        <div className="grid grid-cols-3 md:grid-cols-4 gap-1 md:gap-2">
          {GALLERY.map((item, i) => (
            <Reveal
              key={`${item.src}-${i}`}
              delay={i * 50}
              className={cn(item.wide && 'col-span-2')}
            >
              <button
                type="button"
                onClick={() => setOpenIndex(i)}
                className={cn(
                  'relative w-full bg-gray-800 rounded-sm overflow-hidden aspect-square transition-transform duration-300 hover:scale-[1.02]',
                  item.wide && 'aspect-[2/1]',
                )}
                aria-label={item.caption ?? `Gallery item ${i + 1}`}
              >
                <Image
                  src={item.thumb ?? item.src}
                  alt={item.caption ?? ''}
                  fill
                  sizes="(max-width: 768px) 33vw, 25vw"
                  className="object-cover"
                />
                {item.type === 'video' && (
                  <span aria-hidden="true" className="absolute inset-0 flex items-center justify-center">
                    <span className="w-8 h-8 rounded-full bg-[var(--color-accent)] flex items-center justify-center">
                      <span className="block w-0 h-0 border-l-[8px] border-l-white border-y-[5px] border-y-transparent ml-0.5" />
                    </span>
                  </span>
                )}
              </button>
            </Reveal>
          ))}
        </div>
        <Lightbox
          open={openIndex !== null}
          index={openIndex ?? 0}
          close={() => setOpenIndex(null)}
          slides={slides}
          plugins={[Video]}
        />
      </div>
    </section>
  );
}
```

## Section 9 — Location (`components/home/Location.tsx`)

```tsx
import { COPY } from '@/data/copy';
import { SHOP, formatAddress } from '@/data/shop';
import { SectionHeader } from '@/components/primitives/SectionHeader';
import { PrimaryCTA } from '@/components/primitives/PrimaryCTA';
import { SecondaryCTA } from '@/components/primitives/SecondaryCTA';
import { Reveal } from '@/components/primitives/Reveal';

export function Location() {
  return (
    <section id="visit" className="px-5 py-12 max-w-6xl mx-auto">
      <Reveal>
        <SectionHeader label={COPY.location.label} title={COPY.location.h2} />
      </Reveal>
      <div className="grid gap-4 md:grid-cols-[3fr_2fr]">
        <Reveal direction="left" delay={80}>
          <div className="rounded-md overflow-hidden border border-[var(--color-border-soft)] aspect-[4/3] md:aspect-auto md:h-[300px]">
            <iframe
              src={SHOP.mapsEmbedUrl}
              title="<BUSINESS_NAME> on Google Maps"
              width="100%"
              height="100%"
              style={{ border: 0 }}
              loading="lazy"
              referrerPolicy="no-referrer-when-downgrade"
              allowFullScreen
            />
          </div>
        </Reveal>
        <Reveal direction="right" delay={160}>
          <div className="text-sm">
            <div className="mb-4">
              <div className="text-[10px] font-bold uppercase tracking-widest text-[var(--color-accent)]">Address</div>
              <p className="mt-1 text-[var(--color-ink)]">{formatAddress()}</p>
            </div>
            <div className="mb-4">
              <div className="text-[10px] font-bold uppercase tracking-widest text-[var(--color-accent)]">Hours</div>
              <p className="mt-1 text-[var(--color-ink)]">{SHOP.hours}</p>
            </div>
            <div className="flex flex-wrap gap-2 mt-4">
              <SecondaryCTA href={SHOP.mapsUrl} variant="dark" external>
                {COPY.location.ctaMaps}
              </SecondaryCTA>
              <PrimaryCTA href={`tel:${SHOP.phone}`}>{COPY.location.ctaCall}</PrimaryCTA>
            </div>
          </div>
        </Reveal>
      </div>
    </section>
  );
}
```

## Product detail pages — `app/products/[slug]/page.tsx`

Async server component. `generateStaticParams` over `CATEGORIES`. `generateMetadata` per category. `notFound()` on unknown slug.

```tsx
import { notFound } from 'next/navigation';
import type { Metadata } from 'next';
import { CATEGORIES, getCategoryBySlug } from '@/data/categories';
import { getProductsByCategory } from '@/data/products';
import { Nav } from '@/components/Nav';
import { Footer } from '@/components/Footer';
import { CategoryHero } from '@/components/product/CategoryHero';
import { FilterChips } from '@/components/product/FilterChips';
import { EmptyState } from '@/components/product/EmptyState';

type Params = { slug: string };

export function generateStaticParams(): Params[] {
  return CATEGORIES.map((c) => ({ slug: c.slug }));
}

export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
  const { slug } = await params;
  const category = getCategoryBySlug(slug);
  if (!category) return { title: 'Category not found · <BUSINESS_NAME>' };
  return {
    title: `${category.name} · <BUSINESS_NAME>`,
    description: category.tagline,
  };
}

export default async function CategoryPage({ params }: { params: Promise<Params> }) {
  const { slug } = await params;
  const category = getCategoryBySlug(slug);
  if (!category) notFound();
  const products = getProductsByCategory(category.slug);

  return (
    <>
      <Nav />
      <main id="main">
        <CategoryHero category={category} />
        <FilterChips products={products} />
        <EmptyState />
      </main>
      <Footer />
    </>
  );
}
```

`app/products/[slug]/not-found.tsx`:

```tsx
import Link from 'next/link';
import { Nav } from '@/components/Nav';
import { Footer } from '@/components/Footer';
import { PrimaryCTA } from '@/components/primitives/PrimaryCTA';

export default function NotFound() {
  return (
    <>
      <Nav />
      <main id="main" className="px-5 py-20 max-w-3xl mx-auto text-center">
        <h1 className="font-[family-name:var(--font-display)] uppercase tracking-wide text-4xl">Category not found</h1>
        <p className="text-sm text-[var(--color-ink-muted)] mt-2">That product line doesn&apos;t exist (yet).</p>
   

…(truncated)
