Axil_Accountants/implementation_plan.md
Vadym Samoilenko 83a8878f4a feat: redesign HeroSection to 3-column MinimalistHero layout
- HeroSection: 3-col layout (copy | concentric circles+dashboard | display headline)
  - 'use client' + framer-motion entrance animations (slide in from sides, scale centre)
  - DashboardPreview inline component (compact portal mockup)
  - Two floating stat mini-cards (Avg Tax Saved, Response Time)
  - Mobile: stacked layout, right headline column hidden, H1 in left column
- ContainerScroll: simplified — removed 72rem scroll container and scroll transforms;
  now plain layout wrapper with CSS fadeInUp entrance
- Header: logo size increased h-10 → h-13 (40px → 52px)
- fix: escape apostrophes in ProcessSection, SolutionSection, TestimonialsSection
- fix: remove unused customSize param from SpotlightCard
- docs: update CONTEXT_HANDOVER.md with session 4 changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 21:20:42 +00:00

932 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Axil Accountants — Implementation Plan
> Based on: `Concept.md` v1.0
> Stack: Next.js 15 · TypeScript · Tailwind CSS v4 · Payload CMS 3 · PostgreSQL (Neon) · Vercel
> Each feature is isolated and can be developed, tested, and reviewed independently.
---
## Legend
- `[ ]` — Not started
- `[x]` — Complete
- `[~]` — In progress
- `[!]` — Blocked / needs decision
---
## Feature 1 — Project Setup & Repository
- [x] Scaffold Next.js 16.1.6 project with App Router and TypeScript (`create-next-app`)
- [x] Configure TypeScript (`tsconfig.json`): strict mode, path aliases (`@/*`)
- [x] Install and configure **Tailwind CSS v4**
- [x] Install and configure **ESLint 9** (Next.js preset)
- [x] Install and configure **Prettier** with Tailwind class sorting plugin
- [x] Set up **Husky 9** + **lint-staged 16** (pre-commit: lint + format)
- [x] Define `.env.local` structure and `.env.example` template
- [x] Create `src/` directory structure (app, components/ui/layout/sections/three/cms, lib, hooks, types, payload)
- [x] Initialise Git repository, create `main` and `develop` branches
- [x] **Docker**: multi-stage Dockerfile (dev/build/production) + docker-compose.yml (app + PostgreSQL 17)
- [x] ~~Create **Vercel** project~~**N/A: self-hosted on Ubuntu**
- [x] ~~Configure Vercel environment variables~~**N/A: `.env.local` on server**
---
## Feature 2 — Database & Infrastructure
- [x] **Docker PostgreSQL 17** — сервис `db` в `docker-compose.yml` (user: axil, db: axil)
- [x] `DATABASE_URI` прописан в `.env.local``postgresql://axil:axil_dev@db:5432/axil`
- [x] Verify database connectivity — healthcheck в docker-compose (`pg_isready`)
- [ ] **Media storage** — Payload local disk storage (`/public/media`) — без внешних сервисов
- [ ] **Email (Resend)** — настроить позже (Feature 19, когда нужны реальные письма)
---
## Feature 3 — Payload CMS — Core Installation ✅
- [x] Install Payload CMS 3 into the Next.js project
- [x] Configure `payload.config.ts` (serverURL, secret, db, editor, admin)
- [x] Mount Payload API handler at `src/app/(payload)/api/[...slug]/route.ts`
- [x] Mount admin panel at `src/app/(payload)/admin/[[...segments]]/page.tsx`
- [x] Add `layout.tsx` with `RootLayout` from `@payloadcms/next/layouts` (provides ConfigProvider)
- [x] Configure **local disk storage** for Payload media (`/public/media`)
- [x] Email adapter — deferred (Feature 19, Resend)
- [x] Verify admin panel loads at `/admin`**confirmed ✅**
- [x] Install **Payload Form Builder plugin** (`@payloadcms/plugin-form-builder`) — installed, re-enabled in Feature 4
- [x] Downgrade Next.js 16.1.6 → 15.4.11 (peer dep compatibility with Payload 3.77)
- [x] Upgrade Docker Node.js 20 → 22 (undici/tsx compatibility)
---
## Feature 4 — CMS Collections Schema ✅
Define all Payload collections and globals. Each collection = one content type the client manages.
### 4.1 — Media Collection
- [x] `Media` collection с локальным хранилищем (`/public/media`)
- Fields: `alt` (text, required), `caption` (text, optional)
- Image sizes: `thumbnail` (400×300), `card` (800×600), `hero` (1920×1080)
### 4.2 — Users Collection
- [x] `Users` collection (extends Payload default)
- Fields: `name`, `email`, `role` (select: admin | editor)
- Role-based access control: editors can manage content, not settings
### 4.3 — Services Collection
- [x] `Services` collection
- `title` (text, required)
- `slug` (slug, auto from title, unique)
- `icon` (select: bookkeeping | tax | payroll | vat)
- `tagline` (text — short subtitle)
- `description` (rich text — full description)
- `whatsIncluded` (array of bullet strings)
- `whoItsFor` (array: `{ audience: select, description: text }`)
- `howItWorks` (array of 3: `{ step: number, title: text, description: text }`)
- `faq` (array: `{ question: text, answer: rich text }`)
- `relatedTestimonials` (relationship → Testimonials, many)
- `seo` group: `{ metaTitle, metaDescription, ogImage → Media }`
- `publishedAt` (date), `status` (select: draft | published)
### 4.4 — Blog Posts Collection
- [x] `Posts` collection
- `title` (text, required)
- `slug` (slug, unique)
- `author` (relationship → Users)
- `category` (relationship → Categories)
- `coverImage` (upload → Media)
- `excerpt` (textarea, 150 chars max)
- `content` (rich text — Lexical with full feature set)
- `readingTime` (number, auto-calculated on save via hook)
- `publishedAt` (date)
- `status` (select: draft | published | scheduled)
- `tags` (array of text)
- `seo` group: `{ metaTitle, metaDescription, ogImage → Media }`
### 4.5 — Categories Collection
- [x] `Categories` collection
- `name` (text, required)
- `slug` (slug)
- `colour` (text — hex, for tag colour)
### 4.6 — Team Members Collection
- [x] `TeamMembers` collection
- `name` (text, required)
- `role` (text)
- `qualifications` (text — e.g. "ACCA, MAAT")
- `bio` (textarea)
- `photo` (upload → Media)
- `linkedIn` (text — URL)
- `order` (number — display order)
### 4.7 — Testimonials Collection
- [x] `Testimonials` collection
- `clientName` (text, required)
- `businessName` (text)
- `businessType` (select: sole-trader | limited-company | startup | other)
- `rating` (select: 15)
- `quote` (textarea, required)
- `photo` (upload → Media, optional)
- `source` (select: google | manual)
- `service` (relationship → Services, optional — for filtering)
- `featured` (checkbox — show in homepage carousel)
- `publishedAt` (date)
### 4.8 — FAQs Collection
- [x] `FAQs` collection (global FAQ bank)
- `question` (text, required)
- `answer` (rich text)
- `service` (relationship → Services, optional)
- `global` (checkbox — show on generic FAQ sections)
- `order` (number)
### 4.9 — Form Builder (Payload Plugin Collections)
- [x] Enable `@payloadcms/plugin-form-builder` — provides:
- `Forms` collection (drag-and-drop field builder in admin)
- `FormSubmissions` collection (stores all submissions)
- [x] Configure supported field types: `text`, `email`, `phone`, `select`, `checkbox`, `textarea`
- [ ] Configure submission handlers:
- Email notification (to: configurable per form)
- Webhook trigger (URL: from webhook registry in Site Settings)
- Redirect URL or confirmation message (per form)
### 4.10 — Navigation Global
- [x] `Navigation` global
- `items` (array):
- `label` (text)
- `href` (text, optional — for simple links)
- `isDropdown` (checkbox)
- `children` (array, conditional): `{ label, href, icon, description }`
### 4.11 — Footer Global
- [x] `Footer` global
- `columns` (array of 4):
- `heading` (text)
- `links` (array: `{ label, href }`)
- `contactInfo` group: `{ address, phone, email }`
- `socialLinks` group: `{ linkedIn, facebook, instagram }`
- `legalLinks` (array: `{ label, href }`)
- `copyrightText` (text)
### 4.12 — Site Settings Global
- [x] `SiteSettings` global
- **Brand**: `siteName`, `tagline`, `logo → Media`, `logoDark → Media`, `favicon → Media`
- **Contact**: `address`, `phone`, `email`, `officeHours`
- **Social**: `linkedIn`, `facebook`, `instagram`
- **Analytics**: `googleAnalyticsId`, `googleTagManagerId`, `facebookPixelId`
- **Booking**: `calendlyUrl`, `calendlyInline` (checkbox)
- **Chat Bot**: `chatBotMode` (select: disabled | ai | livechat | leadcapture), `chatBotCustomCode` (code field — for Tawk.to etc.)
- **Webhooks Registry**: `webhooks` (array: `{ name, url, active }`)
- **Email**: `notificationEmail` (default recipient for form submissions)
- **SEO Defaults**: `defaultMetaTitle`, `defaultMetaDescription`, `defaultOgImage → Media`
- **Scripts**: `headerScripts` (code), `footerScripts` (code) — for third-party embeds
---
## Feature 5 — Design System & Base UI Components ✅
- [x] Configure Tailwind CSS with custom design tokens:
```js
// globals.css (@theme inline)
colors: {
emerald: {
DEFAULT: '#3CC68A', // primary — CTA кнопки, иконки, border
dark: '#27A870', // hover / active
deeper: '#1A8C5B', // тёмные секции
light: '#7DDCB0', // tints
mist: '#E8F8F1', // светлые фоны секций
},
blue: {
DEFAULT: '#1B9AD6', // secondary — заголовки, trust, links
dark: '#1480B8', // hover
light: '#6CC4E8', // tints
mist: '#E8F5FC', // светлые фоны
},
charcoal: '#162520', // основной текст
muted: '#6B7280', // второстепенный текст
bg: '#F5FEFA', // фоновый off-white
}
borderRadius: { card: '16px', hero: '24px', pill: '999px' }
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Satoshi', 'sans-serif'],
mono: ['DM Mono', 'monospace'],
}
```
- [x] Set up fonts:
- `Satoshi` (variable font) — Fontshare CDN (`<link>` in layout.tsx, CSS var reference in @theme)
- `Inter` — `next/font/google` → `--font-inter` → `--font-sans`
- `DM Mono` — `next/font/google` → `--font-dm-mono` → `--font-mono`
- [x] Create CSS variables in `globals.css` for all design tokens (enables runtime theming)
- [x] **Button** component (`src/components/ui/Button.tsx`):
- Variants: `primary` (green fill), `secondary` (outline), `ghost` (text only)
- Sizes: `sm`, `md`, `lg`
- States: hover (scale + colour shift), focus (ring), loading (spinner), disabled
- Optional: trailing arrow icon, leading icon
- [x] **Icon system** (`src/components/ui/icons/`):
- Custom SVG line icons for each service (Bookkeeping, Tax, Payroll, VAT)
- Shared icons: ArrowRight, CheckCircle, Star, Menu, X, ChevronDown
- All exported as React components with `size` and `color` props
- [x] **Heading** component — `h1``h4` with correct size + weight from design system
- [x] **Tag/Badge** component — pill with colour variant (green, grey, blue)
- [x] **StarRating** component — 15 filled star SVGs
- [x] **GlassCard** component — frosted glass base with emerald border, hover glow (light + dark variants)
- [x] **Section** layout wrapper — `max-w-[1440px]`, horizontal padding
- [x] **Divider** component — subtle emerald gradient horizontal rule
- [x] **Spinner** component — loading state for forms and async content
---
## Feature 6 — Animation Infrastructure
- [ ] Install dependencies: `gsap`, `@gsap/react`, `lenis`, `framer-motion`, `@react-three/fiber`, `three`
- [ ] **Lenis smooth scroll** provider (`src/lib/lenis.tsx`):
- `LenisProvider` wraps the app in root layout
- Integrates with GSAP ticker for synchronized animations
- Disabled on mobile if causing performance issues
- [ ] **GSAP context provider** (`src/lib/gsap.ts`):
- Registers ScrollTrigger plugin
- Exports `gsap` instance with plugins pre-registered
- Custom hook `useGSAP` for component-level animation cleanup
- [ ] **`useAnimateOnScroll` hook** (`src/hooks/useAnimateOnScroll.ts`):
- Accepts `ref`, `animation type` (fadeUp, fadeIn, slideLeft, etc.), `delay`, `stagger`
- Returns trigger state
- Uses ScrollTrigger under the hood
- [ ] **`useCountUp` hook** (`src/hooks/useCountUp.ts`):
- Accepts `target` (number), `duration`, `prefix` (`£`, `+`), `suffix` (`%`, `+`)
- Triggers animation when element enters viewport
- Returns current display value (string)
- [ ] **`AnimatedText` component** (`src/components/ui/AnimatedText.tsx`):
- Splits text into words or lines
- Each word/line slides up on a stagger delay
- GSAP-driven, respects `prefers-reduced-motion`
- [ ] **`FadeInSection` component** (`src/components/ui/FadeInSection.tsx`):
- Wraps any content
- Fades + translates up when enters viewport
- Accepts `delay` and `duration` props
- [ ] **Framer Motion page transition** (`src/components/layout/PageTransition.tsx`):
- Wraps each page with `AnimatePresence`
- Default: fade + subtle Y translate
- Used in root layout
- [ ] **`prefers-reduced-motion` utility** — all animation components check and skip if enabled
---
## Feature 7 — Header & Navigation
- [ ] **`Header` component** (`src/components/layout/Header.tsx`):
- Fixed/sticky at top, `z-50`
- On scroll: applies `backdrop-blur` + white/85% opacity background (Framer Motion)
- Transparent when at top of page
- Max-width container, flex layout: logo | nav | CTA
- [ ] **Logo component** — renders logo image from CMS Site Settings, supports light/dark variant
- [ ] **Desktop navigation** (`src/components/layout/DesktopNav.tsx`):
- Renders items from CMS Navigation global
- Simple links: hover underline slide-in animation
- Dropdown trigger: `Services ▾` — renders `ServicesDropdown`
- [ ] **`ServicesDropdown` component**:
- Opens on hover/focus, closes on blur/outside click
- Grid of service cards: icon + name + short description
- Framer Motion: animate-in (scale + fade from top)
- [ ] **Header CTA button** — "Book a Free Consultation" → opens booking modal
- [ ] **`MobileMenu` component** (`src/components/layout/MobileMenu.tsx`):
- Hamburger icon button (animated → X on open)
- Full-screen overlay, `fixed inset-0`, dark green tint background
- Framer Motion: slide-in from right or fade-in
- Staggered nav item reveal
- Large CTA button at bottom
- Closes on link click or backdrop click
- [ ] Fetch navigation data from Payload at build/request time (cached)
---
## Feature 8 — Footer
- [ ] **`Footer` component** (`src/components/layout/Footer.tsx`):
- 4-column grid (collapses to 2×2 on tablet, 1 column on mobile)
- Column 1: Logo, tagline, social icon links
- Column 2: Services links
- Column 3: Company links (About, Blog, Contact)
- Column 4: Contact info (address, phone, email) + ICAEW/ACCA badges
- [ ] Social icon links (LinkedIn, Facebook, Instagram) — SVG icons, hover colour transition
- [ ] ICAEW / ACCA badge images (stored in media, linked from Site Settings)
- [ ] **Bottom bar**: copyright text | Privacy Policy | Cookie Policy
- [ ] Data fetched from CMS Footer global + Site Settings global
---
## Feature 9 — 3D Hero Scene (Three.js / R3F)
- [ ] Install: `@react-three/fiber`, `@react-three/drei`, `three`
- [ ] **`GlobeScene` component** (`src/components/three/GlobeScene.tsx`):
- Canvas with `@react-three/fiber`
- Camera position, fog, ambient + directional lighting (soft green tint)
- [ ] **`Globe` mesh**:
- `SphereGeometry` with high segment count
- Custom `PointsMaterial` shader: dot-matrix surface pattern
- Colour: sage green `#6BAF7D` on near-white background
- Slow auto-rotation around Y axis (`useFrame`)
- [ ] **`ParticleNetwork`**:
- 150300 floating particle nodes (reduced on mobile: 6080)
- Lines connecting nearby particles (`LineSegments`)
- Nodes float with subtle Perlin noise movement
- Colour: soft mint with 40% opacity
- [ ] **Mouse parallax interaction**:
- Track `mousemove` on hero section
- Globe and particle network shift slightly (opposite to mouse direction)
- `useSpring` (React Spring or Framer Motion) for smooth interpolation
- [ ] **Device-adaptive performance**:
- Detect mobile / low-power device
- Reduce particle count, disable post-processing effects
- `React.Suspense` boundary with skeleton loader during WebGL init
- [ ] **Mobile fallback** (`src/components/three/GlobeFallback.tsx`):
- 2D SVG animated globe (CSS animation, no WebGL)
- Shown when WebGL unavailable or `prefers-reduced-motion` is set
- [ ] **`useWebGLSupport` hook** — detects WebGL availability, returns boolean
---
## Feature 10 — Home Page: Hero Section
- [ ] **`HeroSection` component** (`src/components/sections/home/HeroSection.tsx`)
- [ ] Full-viewport height (`min-h-screen`), flex / grid split layout
- [ ] Left column:
- Eyebrow tag (e.g. "Trusted by 500+ UK Businesses") — Badge component, fade-in
- `AnimatedText` H1: "Smart Accounting for Growing British Businesses" — word stagger
- Subheadline paragraph — fade-in with delay
- Button group: Primary CTA + Secondary CTA (with arrow icon)
- Trust strip: star rating + "4.9 Google Rating" · "ICAEW Certified" · "500+ Clients Served"
- [ ] Right column: `GlobeScene` (or `GlobeFallback` on mobile/no WebGL)
- [ ] Mobile layout: stacked, 3D scene below text, reduced height
- [ ] Content driven from CMS Pages collection (hero fields)
---
## Feature 11 — Home Page: Pain Points Section
- [ ] **`PainPointsSection` component**
- [ ] Dark green background (`#1A2E1F`), full-width
- [ ] Headline + subtext — `AnimatedText` reveal
- [ ] 3 `GlassCard` components (frosted glass on dark background):
- Card 1: "Missed deadlines & HMRC penalties"
- Card 2: "Confused by tax rules that keep changing"
- Card 3: "Hours wasted on bookkeeping instead of growing"
- [ ] ScrollTrigger staggered reveal: cards slide up 100ms apart
- [ ] Subtle parallax: background shifts 20px on scroll
---
## Feature 12 — Home Page: Services Overview Section
- [ ] **`ServicesSection` component**
- [ ] Section header: headline + subheadline
- [ ] 2×2 `GlassCard` grid (CSS Grid, responsive)
- [ ] Each card:
- Custom SVG icon (from icon system)
- Service name (heading)
- Short description (from CMS)
- "Learn More →" link to `/services/[slug]`
- Hover: `translateY(-4px)` + sage green border glow (`box-shadow`)
- [ ] ScrollTrigger staggered card reveal
- [ ] CTA block below grid: "Not sure which service you need? → Let's Talk"
- [ ] Data: fetch all Services from Payload
---
## Feature 13 — Home Page: Why Choose Axil Section
- [ ] **`WhyAxilSection` component**
- [ ] Section headline: "Why Businesses Choose Axil"
- [ ] **Stat counter row** (4 stats):
- `500+` Businesses Served
- `98%` Client Retention Rate
- `£2M+` Tax Saved for Our Clients
- `12+` Years of Experience
- Each uses `useCountUp` hook, triggers on ScrollTrigger enter
- Numbers in `DM Mono` font
- [ ] **4 USP blocks** (icon + heading + description):
- ICAEW & ACCA Qualified
- Fixed Monthly Engagement
- Dedicated Account Manager
- Cloud-Based & Paper-Free
- [ ] `FadeInSection` wrapper per block, staggered
---
## Feature 14 — Home Page: Testimonials Section
- [ ] **`TestimonialsSection` component**
- [ ] Auto-scrolling marquee carousel (infinite horizontal scroll, CSS animation)
- Pause on hover
- Two rows scrolling in opposite directions (optional premium touch)
- [ ] **`TestimonialCard`** component:
- Client name + business type
- `StarRating` (15)
- Quote text (truncated at 3 lines, expand on click)
- Optional client avatar (from Media, fallback to initials)
- [ ] Google Reviews badge (static badge image or API widget)
- [ ] ICAEW / ACCA certification badge display
- [ ] CTA below: "Join hundreds of satisfied UK businesses → Book Your Free Consultation"
- [ ] Data: fetch featured testimonials from Payload (where `featured = true`)
---
## Feature 15 — Home Page: Who We Work With Section
- [ ] **`AudienceSection` component**
- [ ] 3 audience cards:
- Sole Traders — illustrated icon, description, "Learn More →"
- Limited Companies — illustrated icon, description, "Learn More →"
- Startups — illustrated icon, description, "Learn More →"
- [ ] Illustrated icons: custom SVG per audience type
- [ ] Cards: `GlassCard`, hover lift effect
- [ ] FadeIn reveal on scroll
---
## Feature 16 — Home Page: How It Works Section
- [ ] **`HowItWorksSection` component**
- [ ] 3-step horizontal layout (flex row on desktop, vertical stack on mobile)
- [ ] Each step: numbered circle + title + description
- [ ] **Animated connecting line**:
- SVG path between steps
- GSAP `DrawSVGPlugin` or stroke `dashoffset` animation
- Draws left-to-right as section scrolls into view
- [ ] Step data driven from CMS (global or hardcoded — TBD with client)
---
## Feature 17 — Home Page: Blog Preview Section
- [ ] **`BlogPreviewSection` component**
- [ ] Section headline + "Visit Our Blog →" link
- [ ] Fetch 3 most recent published posts from Payload
- [ ] **`BlogCard`** component (reused on blog index):
- Cover image (`next/image`, lazy-loaded)
- Category tag badge
- Title (heading)
- Excerpt (truncated)
- Author name + date + read time
- Hover: image subtle zoom, card lift
- [ ] Grid: 3 columns desktop, 1 column mobile
---
## Feature 18 — Home Page: Final CTA Section
- [ ] **`FinalCTASection` component**
- [ ] Full-width, deep forest green background (`#1A2E1F` or `#2E7D52`)
- [ ] Centred content:
- Large headline: "Ready to take the stress out of your finances?"
- Subheadline
- Large primary CTA button (white on green): "Book a Free Consultation"
- Trust reassurance: "★ 4.9/5 on Google · ICAEW Certified · No lock-in contracts"
- [ ] Optional: subtle animated background (particle drift or gradient shift)
---
## Feature 19 — Consultation Booking Modal & Form
This is the primary conversion mechanism. Opens from every CTA across the site.
- [ ] **`BookingModal` component** (`src/components/BookingModal.tsx`):
- Global state: `useBookingModal()` Zustand store or React Context
- Triggered by `openBookingModal()` from any component
- `AnimatePresence` + Framer Motion: fade-in backdrop, slide-up modal
- Close on backdrop click, Escape key, close button
- Accessible: focus trap, `role="dialog"`, `aria-modal`
- [ ] **Two modes** (configurable in CMS Site Settings):
- **Mode A — Calendly**: renders `<iframe>` from `calendlyUrl` setting
- **Mode B — Custom Form**: renders the consultation form
- [ ] **Consultation form fields**:
- Full name (text, required)
- Email address (email, required)
- Phone number (text, optional)
- Business type (select: sole trader / limited company / startup / other)
- What do you need help with? (select: bookkeeping / tax / payroll / VAT / not sure)
- Message (textarea, optional)
- [ ] Form validation: client-side (React Hook Form + Zod schema)
- [ ] Submit handler (API route `POST /api/contact`):
- Save to Payload FormSubmissions collection
- Send notification email via Resend to admin email (from Site Settings)
- Trigger webhook if configured (from webhooks registry)
- Return success/error response
- [ ] Success state: animated checkmark + "We'll be in touch within 1 business day" message
- [ ] Error state: clear error messages per field + general error fallback
---
## Feature 20 — Services Pages
### 20.1 — Services Overview Page (`/services`)
- [ ] Grid of all 4 service cards (large format)
- [ ] Each card links to individual service page
- [ ] Intro section: headline + subheadline
- [ ] CTA at bottom
### 20.2 — Individual Service Pages (`/services/[slug]`)
- [ ] Dynamic route with `generateStaticParams` (ISR)
- [ ] Fetch service data by slug from Payload at build/revalidate time
- [ ] **Hero section**: service name, bold tagline, illustration/icon, CTA button
- [ ] **What's Included section**: bullet list of deliverables (from CMS `whatsIncluded`)
- [ ] **Who It's For section**: audience cards filtered to relevant segments
- [ ] **How It Works section**: 3-step process (service-specific, from CMS)
- [ ] **FAQ section**: accordion component
- Fetch FAQs related to this service from Payload
- Animated expand/collapse (Framer Motion `AnimatePresence` + height animation)
- JSON-LD `FAQPage` schema injected via `generateMetadata`
- [ ] **Testimonials section**: filtered by service (from Payload relationship)
- [ ] **Final CTA section**
- [ ] `generateMetadata`: SEO title, description, OG image from CMS `seo` group
---
## Feature 21 — About Page (`/about`)
- [ ] Fetch team members, site settings from Payload
- [ ] **Hero section**: large headline + team group photo (from Media)
- [ ] **Our Story section**: rich text from CMS (Lexical renderer)
- [ ] **Team grid section**:
- `TeamMemberCard` component: photo, name, role, qualifications, LinkedIn link
- Responsive grid: 3 col desktop, 2 col tablet, 1 col mobile
- Hover: subtle overlay with LinkedIn button
- Data from Payload TeamMembers collection (ordered by `order` field)
- [ ] **Core Values section**: 4 value blocks (icon + title + description)
- [ ] **Certifications section**: ICAEW + ACCA logo badges with descriptions
- [ ] **Final CTA section**
---
## Feature 22 — Blog System
### 22.1 — Blog Index Page (`/blog`)
- [ ] Fetch all published posts (ISR, revalidate: 3600s)
- [ ] **Search input**: client-side filter by title/excerpt
- [ ] **Category filter tabs**: "All" + each category — filters post grid
- [ ] Blog post card grid (3 columns desktop, 1 mobile) — `BlogCard` component
- [ ] Pagination (10 posts per page, Payload `limit`/`page` query)
- [ ] SEO metadata for blog index
### 22.2 — Individual Blog Post Page (`/blog/[slug]`)
- [ ] `generateStaticParams` + ISR (revalidate: 3600s)
- [ ] Fetch post by slug from Payload
- [ ] **Lexical rich text renderer** (`src/lib/lexical-renderer.tsx`):
- Renders all Lexical nodes: headings, paragraphs, lists, blockquotes, images, code blocks
- Custom component for pull quotes (green left border, large text)
- Syntax highlighting for code blocks (`shiki` or `prism`)
- [ ] **Article layout**: hero image (full-width), title, author + date + read time + category tag
- [ ] **Table of Contents** (auto-generated from H2/H3 headings):
- Sticky sidebar on desktop
- Active heading highlight on scroll (Intersection Observer)
- [ ] **Social share buttons**: copy link, Twitter/X, LinkedIn
- [ ] **Related posts section**: 23 posts from same category
- [ ] `generateMetadata`: full SEO + Open Graph from CMS seo group
---
## Feature 23 — Contact Page (`/contact`)
- [ ] Hero: "Get in Touch" + brief description
- [ ] **Contact form** (renders a Payload Form via form builder):
- Fields: name, email, phone, subject, message
- Submission saves to Payload FormSubmissions + email notification
- [ ] **Contact info panel**: address, phone, email, office hours
- [ ] **Google Maps embed** (iframe, optional — lazy-loaded)
- [ ] Office hours block
---
## Feature 24 — Legal Pages (`/privacy-policy`, `/cookie-policy`)
- [ ] Static page routes
- [ ] Content editable via CMS Pages collection (rich text fields)
- [ ] Standard typography layout: table of contents sidebar + main content
- [ ] Last updated date (from CMS)
---
## Feature 25 — Chat Bot Module
- [ ] **`ChatWidget` component** (`src/components/chat/ChatWidget.tsx`):
- Floating button (bottom-right), "Chat with us" label
- Slide-up panel animation (Framer Motion)
- Reads `chatBotMode` from Site Settings global
- [ ] **Mode: disabled** — component renders nothing
- [ ] **Mode: livechat** (Tawk.to or custom):
- Reads `chatBotCustomCode` from Site Settings
- Injects script via `useEffect` (no SSR)
- Custom button hidden; Tawk.to button shown instead
- [ ] **Mode: ai** — AI Chat:
- Pre-chat form: collect visitor name + email
- Chat messages UI: user + bot bubbles, loading dots
- API route `POST /api/chat`:
- Sends message to OpenAI/Claude with system prompt
- System prompt: company info, services, FAQs — fetched from Payload
- Streams response back (SSE or JSON)
- Escalation: if bot confidence low → "Let me connect you with our team" → email notification
- Conversation log saved to Payload (custom `ChatLogs` collection or email)
- [ ] **Mode: leadcapture** — Scripted Bot:
- Step 1: "Hi! What's your name?"
- Step 2: "Great, {name}! What's your email address?"
- Step 3: "Are you a sole trader, limited company, or startup?"
- Step 4: "What's your main concern? (tax / bookkeeping / payroll / other)"
- Step 5: "Thanks! Our team will be in touch shortly."
- On completion: POST lead to `/api/contact` → email + webhook
---
## Feature 26 — SEO & Metadata System
- [ ] **`generateMetadata` function** utility (`src/lib/metadata.ts`):
- Accepts CMS seo object + fallback to Site Settings defaults
- Returns Next.js `Metadata` object
- Handles: title, description, OG image, canonical URL
- [ ] **Open Graph image generation** (`src/app/og/route.tsx`):
- Dynamic OG images for blog posts using `@vercel/og`
- Template: Axil logo + post title + sage green background
- [ ] **Structured data helpers** (`src/lib/structured-data.ts`):
- `LocalBusiness` schema (name, address, phone, opening hours)
- `AccountingService` schema
- `FAQPage` schema (for service pages)
- `BlogPosting` schema (for blog articles)
- Injected via `<script type="application/ld+json">` in page `<head>`
- [ ] **`sitemap.ts`** (`src/app/sitemap.ts`):
- Fetches all published blog posts + service slugs from Payload
- Returns `MetadataRoute.Sitemap` array
- Revalidates on ISR
- [ ] **`robots.ts`** (`src/app/robots.ts`):
- Allow all crawlers
- Point to sitemap
- Block `/admin/*`
- [ ] Verify all pages have correct `<title>` tags in browser devtools
---
## Feature 27 — Analytics, Tracking & Cookie Consent
- [ ] **`AnalyticsProvider` component** (`src/components/AnalyticsProvider.tsx`):
- Reads GA ID, GTM ID, FB Pixel ID from CMS Site Settings
- Conditionally loads scripts only after cookie consent granted
- [ ] **Google Tag Manager** integration:
- `<Script>` in `<head>` and `<noscript>` in `<body>`
- Only loads after consent
- [ ] **Cookie consent banner** (`src/components/CookieBanner.tsx`):
- GDPR / UK GDPR compliant
- Framer Motion: slides up from bottom on first visit
- Buttons: "Accept All" | "Reject Non-Essential" | "Manage Preferences"
- Stores consent in `localStorage`
- Triggers analytics scripts on accept
- [ ] **`useConsent` hook** — reads/writes consent state, exposes `hasConsent(category)`
- [ ] **Custom code injection** from CMS Site Settings:
- `headerScripts` — injected in `<head>` via Next.js root layout
- `footerScripts` — injected before `</body>`
---
## Feature 28 — Form Builder Frontend Renderer
Allows any Payload-built form to be rendered on the frontend.
- [ ] **`FormRenderer` component** (`src/components/cms/FormRenderer.tsx`):
- Accepts `formId` prop (Payload form document ID or slug)
- Fetches form schema from Payload API
- Dynamically renders all field types:
- `text`, `email`, `phone`, `textarea` → `<input>` / `<textarea>`
- `select` → `<select>` or custom dropdown
- `checkbox` → styled checkbox
- React Hook Form + Zod validation from field config (required, type)
- [ ] **`FormField` component** — renders individual field with label, input, error message
- [ ] **Submit handler**: `POST /api/form-submissions`
- Saves to Payload FormSubmissions
- Triggers email + webhook from form config
- [ ] Success / error UI states
- [ ] Usage: embed `<FormRenderer formId="..." />` anywhere on any CMS-managed page
---
## Feature 29 — Performance Optimisation
- [ ] All images use `next/image` with correct `sizes` prop and `priority` for LCP images
- [ ] `next/font` for Inter + DM Mono (eliminates layout shift from font load)
- [ ] **Dynamic imports** for heavy components:
- `GlobeScene` — `dynamic(() => import(...), { ssr: false })`
- `ChatWidget` — `dynamic(() => import(...), { ssr: false })`
- Lexical renderer — `dynamic` import
- [ ] **ISR** (Incremental Static Regeneration):
- Service pages: `revalidate = 86400` (24h)
- Blog posts: `revalidate = 3600` (1h)
- Homepage: `revalidate = 3600`
- [ ] Install `@next/bundle-analyzer`, run bundle analysis, eliminate large dependencies
- [ ] Lazy-load all sections below fold (Intersection Observer or Next.js `loading="lazy"`)
- [ ] Verify no unused CSS (Tailwind PurgeCSS is automatic)
- [ ] Run Lighthouse on all key pages — target: Performance 95+, SEO 100, Accessibility 95+
- [ ] Fix any Lighthouse findings before delivery
---
## Feature 30 — Accessibility Audit & Fixes
- [ ] Add `<a href="#main-content">Skip to content</a>` as first focusable element
- [ ] Verify all interactive elements are keyboard-navigable (Tab, Enter, Escape)
- [ ] All images have meaningful `alt` text (enforced by Payload Media collection)
- [ ] All form fields have associated `<label>` elements
- [ ] All icon-only buttons have `aria-label`
- [ ] Modals: focus trap, `role="dialog"`, `aria-modal`, return focus on close
- [ ] Navigation dropdowns: `aria-expanded`, `aria-haspopup`
- [ ] Colour contrast: check all text/background combos meet WCAG AA (4.5:1 body, 3:1 large text)
- [ ] Test with screen reader (VoiceOver / NVDA)
- [ ] Verify `prefers-reduced-motion` disables all non-essential animations
---
## Feature 31 — Responsive Design QA
- [ ] Test every page at: `320px`, `375px`, `414px`, `768px`, `1024px`, `1280px`, `1440px`, `1920px`
- [ ] Verify 3D scene: WebGL on desktop, SVG fallback on mobile
- [ ] Verify hamburger menu: opens, nav links work, closes correctly
- [ ] Verify booking modal: fills screen on mobile, scrollable content
- [ ] Verify all form fields usable with touch keyboard (no content hidden behind keyboard)
- [ ] Verify testimonials marquee / carousel: touch-swipeable on mobile
- [ ] Verify blog grid: 1-column on mobile, readable font sizes
- [ ] Verify footer: stacks correctly on mobile
---
## Feature 32 — Error Handling & Monitoring
- [ ] **Sentry** setup (`@sentry/nextjs`):
- Install and configure with `SENTRY_DSN`
- Client + server + edge error capture
- Source maps upload on build
- [ ] **Custom 404 page** (`src/app/not-found.tsx`):
- Brand-styled, helpful message
- Links back to home + contact
- [ ] **Custom 500 page** (`src/app/error.tsx`):
- Brand-styled error page
- Reports error to Sentry
- [ ] All API routes return proper HTTP status codes + JSON error bodies
- [ ] Form submission error states: clear per-field errors + general fallback
- [ ] Payload API fetch failures: graceful degradation (show fallback content, not crash)
---
## Feature 33 — Deployment (Ubuntu VPS + Docker)
Stack: Ubuntu server · Docker + Docker Compose · Nginx reverse proxy · Let's Encrypt SSL
### 33.1 — Server preparation
- [ ] Install Docker + Docker Compose on Ubuntu:
```bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
```
- [ ] Install Nginx + Certbot:
```bash
sudo apt install nginx certbot python3-certbot-nginx -y
```
- [ ] Clone repository on server:
```bash
git clone git@github.com:YOUR_ORG/axil-accountants.git /opt/axil
cd /opt/axil && git checkout main
```
- [ ] Create production `.env.local` on server (manually — never commit):
- `DATABASE_URI=postgresql://axil:STRONG_PASS@db:5432/axil`
- `PAYLOAD_SECRET=STRONG_SECRET_32_CHARS`
- `NEXT_PUBLIC_SITE_URL=https://axilaccountants.co.uk`
### 33.2 — Docker Compose production override
- [ ] Create `docker-compose.prod.yml` (production overrides):
- App: `NODE_ENV=production`, `restart: always`, no port 3000 exposed externally (only via Nginx)
- DB: named volume for persistence, `restart: always`
- Remove dev-only volume mounts
### 33.3 — Nginx reverse proxy
- [ ] Create `/etc/nginx/sites-available/axil` config:
- `server_name axilaccountants.co.uk www.axilaccountants.co.uk`
- `proxy_pass http://127.0.0.1:3000`
- Gzip, proxy headers, timeouts
- [ ] Enable site: `sudo ln -s /etc/nginx/sites-available/axil /etc/nginx/sites-enabled/`
- [ ] Obtain SSL certificate: `sudo certbot --nginx -d axilaccountants.co.uk -d www.axilaccountants.co.uk`
- [ ] Verify auto-renewal: `sudo certbot renew --dry-run`
- [ ] Configure `www` → apex redirect in Nginx (301)
### 33.4 — Deploy script
- [ ] Create `scripts/deploy.sh` at repo root:
```bash
#!/bin/bash
set -e
cd /opt/axil
git pull origin main
docker compose -f docker-compose.yml -f docker-compose.prod.yml build app
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app
docker image prune -f
echo "Deploy complete: $(date)"
```
- [ ] Make executable: `chmod +x scripts/deploy.sh`
- [ ] Test deploy: `./scripts/deploy.sh` — verify site responds after restart
### 33.5 — Verification
- [ ] Run production build locally first: `docker compose build && docker compose up` — no errors
- [ ] Verify HTTPS works: `curl -I https://axilaccountants.co.uk`
- [ ] Run Lighthouse on production URL — confirm scores meet targets
- [ ] Verify DB volume survives container restart (data not lost)
---
## Feature 34 — Content Seeding & CMS Setup
- [ ] Create seed script (`src/payload/seed.ts`):
- Site Settings: company name, logo, contact details, placeholder social URLs
- 4 Services: Bookkeeping, Tax Returns, Payroll, VAT Returns (full content per service)
- 3 Sample Testimonials (marked as featured)
- 3 FAQs per service
- Blog Categories: "Tax Tips", "Business Advice", "HMRC Updates", "Payroll Guide"
- Navigation: full nav structure from concept
- Footer: all columns with links
- [ ] Run seed script against production database
- [ ] Verify all seeded content displays correctly on frontend
- [ ] Create CMS editor user account for client
---
## Feature 35 — Client Handover & Documentation
- [ ] **CMS User Guide** (`docs/cms-guide.md`):
- How to log into the admin panel
- How to edit page content (services, hero text, etc.)
- How to write and publish a blog post
- How to add a testimonial
- How to add a team member
- How to create a contact form
- How to set up a webhook on a form
- How to change the chat bot mode
- How to update integration settings (GA, GTM, Calendly URL)
- [ ] **Handover checklist**:
- [ ] Client has admin login credentials
- [ ] Client has SSH access to server (or handover to sysadmin)
- [ ] Client has Resend account access (for email settings)
- [ ] DNS properly pointing to Vercel
- [ ] Google Analytics connected and receiving data
- [ ] Booking form submissions arriving in CMS + email
- [ ] Mobile tested on real device (iPhone + Android)
- [ ] Lighthouse scores confirmed ≥ 95 Performance
---
## Summary: Feature Dependency Order
```
Feature 1 (Project Setup)
└── Feature 2 (Database)
└── Feature 3 (Payload CMS Core)
└── Feature 4 (CMS Collections Schema)
├── Feature 5 (Design System) ← parallel from here
├── Feature 6 (Animation Infrastructure)
└── Feature 7 + 8 (Header + Footer)
└── Feature 9 (3D Scene)
└── Feature 10 (Hero Section)
└── Features 1118 (Home Page Sections)
└── Feature 19 (Booking Modal) ← needed by all CTAs
├── Feature 20 (Services Pages)
├── Feature 21 (About Page)
├── Feature 22 (Blog)
├── Feature 23 (Contact Page)
└── Feature 24 (Legal Pages)
├── Feature 25 (Chat Bot)
├── Feature 26 (SEO)
├── Feature 27 (Analytics)
├── Feature 28 (Form Renderer)
├── Feature 29 (Performance)
├── Feature 30 (Accessibility)
├── Feature 31 (Responsive QA)
├── Feature 32 (Error Handling)
└── Feature 33 (Deployment)
├── Feature 34 (Content Seed)
└── Feature 35 (Handover)
```
---
_Total features: 35 | Estimated tasks: ~220_
_Last updated: February 2026_