diff --git a/package-lock.json b/package-lock.json
index 9d067a1..ca24ffa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,7 +31,7 @@
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
- "chromatic": "^11.0.0",
+ "chromatic": "^11.29.0",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
diff --git a/package.json b/package.json
index c42ba45..6ce879e 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"format:check": "prettier --check 'src/**/*.{ts,tsx}'",
"test": "vitest run --passWithNoTests",
"test:watch": "vitest",
- "chromatic": "chromatic --exit-zero-on-changes",
+ "chromatic": "chromatic --exit-zero-on-changes --build-script-name=build:storybook",
"prepare": "husky"
},
"dependencies": {
@@ -45,7 +45,7 @@
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
- "chromatic": "^11.0.0",
+ "chromatic": "^11.29.0",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
diff --git a/src/components/pages/HomePage/HomePage.stories.tsx b/src/components/pages/HomePage/HomePage.stories.tsx
new file mode 100644
index 0000000..54947ab
--- /dev/null
+++ b/src/components/pages/HomePage/HomePage.stories.tsx
@@ -0,0 +1,266 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import Box from '@mui/material/Box';
+import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
+import AccessTimeIcon from '@mui/icons-material/AccessTime';
+import SearchIcon from '@mui/icons-material/Search';
+import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
+import { HomePage } from './HomePage';
+import { Navigation } from '../../organisms/Navigation';
+import { Footer } from '../../organisms/Footer';
+
+// ─── Shared helpers ──────────────────────────────────────────────────────────
+
+const FALogo = () => (
+
+
+
+
+);
+
+const FALogoInverse = () => (
+
+);
+
+const nav = (
+ }
+ items={[
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ { label: 'Log in', href: '/login' },
+ ]}
+ />
+);
+
+const footer = (
+ }
+ tagline="Helping Australian families plan with confidence"
+ linkGroups={[
+ {
+ heading: 'Services',
+ links: [
+ { label: 'Find a Director', href: '/directors' },
+ { label: 'Compare Venues', href: '/venues' },
+ { label: 'Pricing Guide', href: '/pricing' },
+ { label: 'Start Planning', href: '/arrange' },
+ ],
+ },
+ {
+ heading: 'Support',
+ links: [
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ { label: 'Grief Resources', href: '/resources' },
+ ],
+ },
+ {
+ heading: 'Company',
+ links: [
+ { label: 'About Us', href: '/about' },
+ { label: 'Provider Portal', href: '/provider-portal' },
+ { label: 'Partner With Us', href: '/partners' },
+ ],
+ },
+ ]}
+ phone="1800 987 888"
+ email="support@funeralarranger.com.au"
+ legalLinks={[
+ { label: 'Privacy Policy', href: '/privacy' },
+ { label: 'Terms of Service', href: '/terms' },
+ { label: 'Accessibility', href: '/accessibility' },
+ ]}
+ />
+);
+
+// ─── Default data ────────────────────────────────────────────────────────────
+
+const heroImage = (
+
+
+ Hero image placeholder
+
+
+);
+
+const partnerLogos = [
+ { src: '/partnerlogo/logo-placeholder-1.svg', alt: 'KFF Funerals' },
+ { src: '/partnerlogo/logo-placeholder-2.svg', alt: 'Mannings Funerals' },
+ { src: '/partnerlogo/logo-placeholder-3.svg', alt: 'Lady Anne Funerals' },
+ { src: '/partnerlogo/logo-placeholder-4.svg', alt: 'Rankins Funerals' },
+ { src: '/partnerlogo/logo-placeholder-5.svg', alt: 'City Funerals' },
+ { src: '/partnerlogo/logo-placeholder-6.svg', alt: 'Mackay Funerals' },
+ { src: '/partnerlogo/logo-placeholder-7.svg', alt: 'H. Parsons' },
+ { src: '/partnerlogo/logo-placeholder-8.svg', alt: 'Parsons Ladies' },
+ { src: '/partnerlogo/logo-placeholder-9.svg', alt: 'Botanical Funerals' },
+];
+
+const features = [
+ {
+ icon: ,
+ heading: 'Transparent, verified pricing',
+ description:
+ 'All costs are itemised for verified partners. No surprise fees. See pricing and options before you commit.',
+ },
+ {
+ icon: ,
+ heading: 'Available 24 hours a day',
+ description:
+ 'Compare, plan and arrange at your own pace, day or night. No pressure to commit online.',
+ },
+ {
+ icon: ,
+ heading: 'Find local & compare',
+ description: 'Search and compare local funeral directors to find the right choice for you.',
+ },
+ {
+ icon: ,
+ heading: 'Support when you need it',
+ description:
+ 'Arrange everything online or be guided through the steps by your preferred funeral director.',
+ },
+];
+
+const testimonials = [
+ {
+ name: 'Sarah H.',
+ rating: 5,
+ quote:
+ 'At the most difficult time in our lives, this site made comparing costs so straightforward. We saved over $800.',
+ timeAgo: '3 weeks ago',
+ },
+ {
+ name: 'James M.',
+ rating: 5,
+ quote:
+ 'The itemised quote builder meant we could personalise the service within our budget. Highly recommended.',
+ timeAgo: '1 month ago',
+ },
+ {
+ name: 'Tracy W.',
+ rating: 5,
+ quote:
+ 'I had no idea there was such a price difference between local directors. This saved us from overpaying.',
+ timeAgo: '2 months ago',
+ },
+];
+
+const faqItems = [
+ {
+ question: 'What is Funeral Arranger?',
+ answer:
+ 'Funeral Arranger is an online platform that helps Australian families find, compare and arrange funeral services. We connect you with trusted local funeral directors and provide transparent pricing so you can make informed decisions during a difficult time.',
+ },
+ {
+ question: 'What makes Funeral Arranger different from other funeral service providers?',
+ answer:
+ 'Unlike traditional funeral homes, we are an independent comparison platform. We show you transparent, itemised pricing from multiple verified providers in your area so you can compare options and choose what is right for your family and budget.',
+ },
+ {
+ question: 'Do I need to complete all steps at once?',
+ answer:
+ 'No. You can save your progress at any time and return when you are ready. Whether you are pre-planning or arranging at short notice, the process works at your pace with no time pressure.',
+ },
+ {
+ question: 'How much does a funeral cost in Australia?',
+ answer:
+ 'Funeral costs in Australia typically range from $4,000 for a simple cremation to $15,000 or more for a full traditional service. Costs vary by location, provider, and the options you choose. Our platform helps you compare real prices from local providers.',
+ },
+ {
+ question: 'What is the cheapest funeral option?',
+ answer:
+ 'A direct cremation (no service) is generally the most affordable option, starting from around $2,000\u2013$4,000 depending on your location. Our platform shows you all available options so you can find the right balance of service and cost.',
+ },
+];
+
+// ─── Meta ────────────────────────────────────────────────────────────────────
+
+const meta: Meta = {
+ title: 'Pages/HomePage',
+ component: HomePage,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'fullscreen',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+// ─── Stories ─────────────────────────────────────────────────────────────────
+
+/** Full homepage with all sections populated */
+export const Default: Story = {
+ args: {
+ navigation: nav,
+ footer,
+ heroImage,
+ partnerLogos,
+ features,
+ googleRating: 4.9,
+ googleReviewCount: 2340,
+ testimonials,
+ faqItems,
+ onSearch: (params) => console.log('Search:', params),
+ onCtaClick: () => console.log('CTA clicked'),
+ },
+};
+
+/** Before reviews are gathered — testimonials section hidden */
+export const WithoutReviews: Story = {
+ args: {
+ ...Default.args,
+ googleRating: undefined,
+ googleReviewCount: undefined,
+ testimonials: [],
+ },
+};
+
+/** Minimal — no navigation, footer, or optional sections */
+export const Minimal: Story = {
+ args: {
+ heroImage,
+ onSearch: (params) => console.log('Search:', params),
+ },
+};
+
+/** Mobile viewport */
+export const Mobile: Story = {
+ args: { ...Default.args },
+ parameters: {
+ viewport: { defaultViewport: 'mobile1' },
+ },
+};
diff --git a/src/components/pages/HomePage/HomePage.tsx b/src/components/pages/HomePage/HomePage.tsx
new file mode 100644
index 0000000..fd004fe
--- /dev/null
+++ b/src/components/pages/HomePage/HomePage.tsx
@@ -0,0 +1,648 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+import Container from '@mui/material/Container';
+import Accordion from '@mui/material/Accordion';
+import AccordionSummary from '@mui/material/AccordionSummary';
+import AccordionDetails from '@mui/material/AccordionDetails';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import StarIcon from '@mui/icons-material/Star';
+import StarHalfIcon from '@mui/icons-material/StarHalf';
+import StarBorderIcon from '@mui/icons-material/StarBorder';
+import type { SxProps, Theme } from '@mui/material/styles';
+import { Typography } from '../../atoms/Typography';
+import { Button } from '../../atoms/Button';
+import { Card } from '../../atoms/Card';
+import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+/** A partner logo for the trust carousel */
+export interface PartnerLogo {
+ src: string;
+ alt: string;
+}
+
+/** A feature card in the "Why Use FA" section */
+export interface FeatureCard {
+ icon: React.ReactNode;
+ heading: string;
+ description: string;
+}
+
+/** A customer testimonial */
+export interface Testimonial {
+ name: string;
+ rating: number;
+ quote: string;
+ timeAgo: string;
+}
+
+/** A frequently asked question */
+export interface FAQItem {
+ question: string;
+ answer: React.ReactNode;
+}
+
+/** Props for the HomePage component */
+export interface HomePageProps {
+ /** Navigation bar */
+ navigation?: React.ReactNode;
+ /** Footer */
+ footer?: React.ReactNode;
+
+ /** Hero heading (serif display) */
+ heroHeading?: string;
+ /** Hero subheading paragraph */
+ heroSubheading?: string;
+ /** Hero image slot */
+ heroImage?: React.ReactNode;
+
+ /** FuneralFinder search callback */
+ onSearch?: (params: FuneralFinderV3SearchParams) => void;
+ /** FuneralFinder loading state */
+ searchLoading?: boolean;
+
+ /** Partner logos for the trust carousel */
+ partnerLogos?: PartnerLogo[];
+ /** Trust line text above the carousel */
+ partnerTrustLine?: string;
+
+ /** Feature cards for "Why Use FA" */
+ features?: FeatureCard[];
+
+ /** Aggregate Google rating */
+ googleRating?: number;
+ /** Total Google review count */
+ googleReviewCount?: number;
+ /** Testimonial cards */
+ testimonials?: Testimonial[];
+
+ /** CTA banner heading */
+ ctaHeading?: string;
+ /** CTA button label */
+ ctaButtonLabel?: string;
+ /** CTA button click handler */
+ onCtaClick?: () => void;
+
+ /** FAQ items */
+ faqItems?: FAQItem[];
+
+ sx?: SxProps;
+}
+
+// ─── Helpers ─────────────────────────────────────────────────────────────────
+
+/** Renders 1–5 stars (filled, half, or empty) */
+function StarRating({
+ rating,
+ size = 20,
+ color,
+}: {
+ rating: number;
+ size?: number;
+ color?: string;
+}) {
+ const stars: React.ReactNode[] = [];
+ const sx = { fontSize: size, color: color || 'var(--fa-color-brand-500)' };
+
+ for (let i = 1; i <= 5; i++) {
+ if (rating >= i) {
+ stars.push();
+ } else if (rating >= i - 0.5) {
+ stars.push();
+ } else {
+ stars.push();
+ }
+ }
+
+ return (
+
+ {stars}
+
+ );
+}
+
+// ─── Component ───────────────────────────────────────────────────────────────
+
+export const HomePage = React.forwardRef(
+ (
+ {
+ navigation,
+ footer,
+ heroHeading = 'Compare Funeral Directors Near You and Arrange with Confidence',
+ heroSubheading = "Funeral planning doesn't have to be overwhelming. Whether you're thinking ahead or arranging for a loved one, find trusted local providers with transparent pricing, all at your own pace.",
+ heroImage,
+ onSearch,
+ searchLoading,
+ partnerLogos = [],
+ partnerTrustLine = 'Providing services from hundreds of trusted funeral homes across Australia',
+ features = [],
+ googleRating,
+ googleReviewCount,
+ testimonials = [],
+ ctaHeading = 'We Are Here When You Need Us',
+ ctaButtonLabel = 'Start planning',
+ onCtaClick,
+ faqItems = [],
+ sx,
+ },
+ ref,
+ ) => {
+ return (
+
+ {/* ─── Navigation ─── */}
+ {navigation}
+
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 1: Hero
+ ═══════════════════════════════════════════════════════════════════ */}
+
+
+ {/* Hero text */}
+
+
+ {heroHeading}
+
+
+ {heroSubheading}
+
+
+
+ {/* Hero image */}
+
+ {heroImage || (
+
+ )}
+
+
+
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 2: FuneralFinder Widget (overlapping card)
+ ═══════════════════════════════════════════════════════════════════ */}
+
+
+
+
+
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 3: Partner Logos Carousel
+ ═══════════════════════════════════════════════════════════════════ */}
+ {partnerLogos.length > 0 && (
+
+
+
+ {partnerTrustLine}
+
+
+
+ {/* Carousel track */}
+
+
+ {/* Duplicate logos for seamless infinite scroll */}
+ {[...partnerLogos, ...partnerLogos].map((logo, i) => (
+ = partnerLogos.length ? true : undefined}
+ sx={{
+ height: { xs: 48, md: 64 },
+ width: 'auto',
+ objectFit: 'contain',
+ filter: 'grayscale(100%)',
+ opacity: 0.5,
+ transition: 'opacity 0.2s, filter 0.2s',
+ '&:hover': {
+ filter: 'grayscale(0%)',
+ opacity: 1,
+ },
+ flexShrink: 0,
+ }}
+ />
+ ))}
+
+
+
+ )}
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 4: Why Use Funeral Arranger (Features)
+ ═══════════════════════════════════════════════════════════════════ */}
+ {features.length > 0 && (
+
+
+
+
+ Why use Funeral Arranger
+
+
+ Making an impossible time a little easier
+
+
+ Funeral planning doesn't have to be overwhelming. Whether a loved one has
+ just passed, is imminent, or you're pre-planning for the future. Compare
+ transparent pricing from local funeral directors. Explore the service options,
+ coffins and more to personalise a funeral plan in clear, easy steps.
+
+
+
+
+ {features.map((feature) => (
+
+
+
+ {feature.icon}
+
+
+ {feature.heading}
+
+
+ {feature.description}
+
+
+
+ ))}
+
+
+
+ )}
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 5: Reviews / Testimonials
+ ═══════════════════════════════════════════════════════════════════ */}
+ {(testimonials.length > 0 || googleRating != null) && (
+
+
+
+
+ What our customers say
+
+
+ How we have helped families like yours
+
+
+ {/* Google aggregate */}
+ {googleRating != null && (
+
+
+ Google Reviews
+
+
+ {googleRating}
+
+
+ {googleReviewCount != null && (
+
+ {googleReviewCount.toLocaleString('en-AU')} reviews
+
+ )}
+
+ )}
+
+
+ {/* Testimonial cards */}
+ {testimonials.length > 0 && (
+
+ {testimonials.map((t, i) => (
+
+
+
+ {t.name}
+
+
+
+
+ “{t.quote}”
+
+
+ {t.timeAgo}
+
+
+ ))}
+
+ )}
+
+
+ )}
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 6: CTA Banner
+ ═══════════════════════════════════════════════════════════════════ */}
+
+
+
+ {ctaHeading}
+
+
+
+
+
+ {/* ═══════════════════════════════════════════════════════════════════
+ Section 7: FAQ
+ ═══════════════════════════════════════════════════════════════════ */}
+ {faqItems.length > 0 && (
+
+
+
+ Frequently Asked Questions
+
+
+
+ {faqItems.map((item, i) => (
+
+ } sx={{ px: 3, py: 1 }}>
+
+ {item.question}
+
+
+
+ {typeof item.answer === 'string' ? (
+
+ {item.answer}
+
+ ) : (
+ item.answer
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+
+ {/* ─── Footer ─── */}
+ {footer}
+
+ );
+ },
+);
+
+HomePage.displayName = 'HomePage';
+export default HomePage;
diff --git a/src/components/pages/HomePage/index.ts b/src/components/pages/HomePage/index.ts
new file mode 100644
index 0000000..bfdca37
--- /dev/null
+++ b/src/components/pages/HomePage/index.ts
@@ -0,0 +1,2 @@
+export { HomePage, default } from './HomePage';
+export type { HomePageProps, PartnerLogo, FeatureCard, Testimonial, FAQItem } from './HomePage';