# 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 `