# 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: 1–5)
- `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 (`` 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 — 1–5 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`**:
- 150–300 floating particle nodes (reduced on mobile: 60–80)
- 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` (1–5)
- 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 `