HomePage: initial build with hero, FuneralFinder, features, reviews, FAQ
- Hero section with display3 serif heading, warm bg, 50/50 split with image slot
- FuneralFinderV3 widget integrated as overlapping card (negative margin pattern)
- Partner logos carousel with CSS-only infinite scroll, prefers-reduced-motion
- 4 feature cards (outlined, compact padding, warm circular icon backgrounds)
- Reviews section (dark bg, Google aggregate rating, 3 testimonial cards)
- CTA banner with displaySm serif heading ("We Are Here When You Need Us")
- FAQ accordion (reuses SummaryStep pattern)
- Full a11y: section landmarks, aria-labelledby, heading hierarchy h1→h2→h3
- Grief-sensitive copy throughout (no urgency language, warm tone)
- Chromatic added as devDependency, build script updated
- 4 stories: Default, WithoutReviews, Minimal, Mobile
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
266
src/components/pages/HomePage/HomePage.stories.tsx
Normal file
266
src/components/pages/HomePage/HomePage.stories.tsx
Normal file
@@ -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 = () => (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-full.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'none', md: 'block' } }}
|
||||
/>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-short.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'block', md: 'none' } }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const FALogoInverse = () => (
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-full.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 24, filter: 'brightness(0) invert(1)', opacity: 0.9 }}
|
||||
/>
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Log in', href: '/login' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<Footer
|
||||
logo={<FALogoInverse />}
|
||||
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 = (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: 'inherit',
|
||||
background: 'linear-gradient(160deg, #d4bfa8 0%, #c4a882 40%, #8b7355 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
fontSize: 14,
|
||||
fontFamily: 'var(--fa-font-family-body)',
|
||||
textAlign: 'center',
|
||||
px: 4,
|
||||
}}
|
||||
>
|
||||
Hero image placeholder
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
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: <VerifiedOutlinedIcon />,
|
||||
heading: 'Transparent, verified pricing',
|
||||
description:
|
||||
'All costs are itemised for verified partners. No surprise fees. See pricing and options before you commit.',
|
||||
},
|
||||
{
|
||||
icon: <AccessTimeIcon />,
|
||||
heading: 'Available 24 hours a day',
|
||||
description:
|
||||
'Compare, plan and arrange at your own pace, day or night. No pressure to commit online.',
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
heading: 'Find local & compare',
|
||||
description: 'Search and compare local funeral directors to find the right choice for you.',
|
||||
},
|
||||
{
|
||||
icon: <SupportAgentOutlinedIcon />,
|
||||
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<typeof HomePage> = {
|
||||
title: 'Pages/HomePage',
|
||||
component: HomePage,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof HomePage>;
|
||||
|
||||
// ─── 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' },
|
||||
},
|
||||
};
|
||||
648
src/components/pages/HomePage/HomePage.tsx
Normal file
648
src/components/pages/HomePage/HomePage.tsx
Normal file
@@ -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<Theme>;
|
||||
}
|
||||
|
||||
// ─── 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(<StarIcon key={i} sx={sx} />);
|
||||
} else if (rating >= i - 0.5) {
|
||||
stars.push(<StarHalfIcon key={i} sx={sx} />);
|
||||
} else {
|
||||
stars.push(<StarBorderIcon key={i} sx={sx} />);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'inline-flex', gap: 0.25 }}
|
||||
aria-label={`${rating} out of 5 stars`}
|
||||
role="img"
|
||||
>
|
||||
{stars}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
(
|
||||
{
|
||||
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 (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={[
|
||||
{ minHeight: '100vh', display: 'flex', flexDirection: 'column' },
|
||||
...(Array.isArray(sx) ? sx : [sx]),
|
||||
]}
|
||||
>
|
||||
{/* ─── Navigation ─── */}
|
||||
{navigation}
|
||||
|
||||
<Box component="main" id="main-content" sx={{ flex: 1 }}>
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 1: Hero
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="hero-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-warm)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Container
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', md: 'row' },
|
||||
alignItems: 'center',
|
||||
gap: { xs: 3, md: 0 },
|
||||
py: { xs: 4, md: 0 },
|
||||
}}
|
||||
>
|
||||
{/* Hero text */}
|
||||
<Box
|
||||
sx={{
|
||||
flex: { md: '0 0 50%' },
|
||||
py: { md: 6 },
|
||||
pr: { md: 5 },
|
||||
textAlign: { xs: 'center', md: 'left' },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h1"
|
||||
id="hero-heading"
|
||||
tabIndex={-1}
|
||||
sx={{ mb: 2, color: 'text.primary' }}
|
||||
>
|
||||
{heroHeading}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{heroSubheading}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Hero image */}
|
||||
<Box
|
||||
role="img"
|
||||
aria-label="Family planning together"
|
||||
sx={{
|
||||
flex: { md: '0 0 50%' },
|
||||
minHeight: { xs: 200, md: 320 },
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
borderRadius: { xs: 'var(--fa-border-radius-lg)', md: 0 },
|
||||
}}
|
||||
>
|
||||
{heroImage || (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: 'inherit',
|
||||
background:
|
||||
'linear-gradient(160deg, var(--fa-color-brand-100) 0%, var(--fa-color-brand-200) 50%, var(--fa-color-brand-300) 100%)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 2: FuneralFinder Widget (overlapping card)
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
mt: { xs: -4, md: -6 },
|
||||
px: 2,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ maxWidth: 620, mx: 'auto' }}>
|
||||
<FuneralFinderV3
|
||||
heading="Find funeral providers near you"
|
||||
subheading="Tell us a little about what you're looking for and we'll show you options in your area."
|
||||
onSearch={onSearch}
|
||||
loading={searchLoading}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 3: Partner Logos Carousel
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{partnerLogos.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-label="Trusted partners"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-cool)',
|
||||
pt: { xs: 8, md: 10 },
|
||||
pb: { xs: 6, md: 8 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
sx={{ textAlign: 'center', mb: { xs: 3, md: 4 } }}
|
||||
>
|
||||
{partnerTrustLine}
|
||||
</Typography>
|
||||
</Container>
|
||||
|
||||
{/* Carousel track */}
|
||||
<Box
|
||||
role="presentation"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
'&::before, &::after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 80,
|
||||
zIndex: 1,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
'&::before': {
|
||||
left: 0,
|
||||
background:
|
||||
'linear-gradient(to right, var(--fa-color-surface-cool), transparent)',
|
||||
},
|
||||
'&::after': {
|
||||
right: 0,
|
||||
background:
|
||||
'linear-gradient(to left, var(--fa-color-surface-cool), transparent)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
aria-label="Partner funeral directors"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 6, md: 8 },
|
||||
alignItems: 'center',
|
||||
width: 'max-content',
|
||||
animation: 'logoScroll 35s linear infinite',
|
||||
'@keyframes logoScroll': {
|
||||
'0%': { transform: 'translateX(0)' },
|
||||
'100%': { transform: 'translateX(-50%)' },
|
||||
},
|
||||
'&:hover': { animationPlayState: 'paused' },
|
||||
'@media (prefers-reduced-motion: reduce)': {
|
||||
animation: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Duplicate logos for seamless infinite scroll */}
|
||||
{[...partnerLogos, ...partnerLogos].map((logo, i) => (
|
||||
<Box
|
||||
key={`${logo.alt}-${i}`}
|
||||
component="img"
|
||||
src={logo.src}
|
||||
alt={i < partnerLogos.length ? logo.alt : ''}
|
||||
aria-hidden={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,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 4: Why Use Funeral Arranger (Features)
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{features.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="features-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-default)',
|
||||
py: { xs: 6, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
sx={{ color: 'var(--fa-color-text-brand)', mb: 1.5, display: 'block' }}
|
||||
>
|
||||
Why use Funeral Arranger
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h2"
|
||||
component="h2"
|
||||
id="features-heading"
|
||||
sx={{ mb: 2, color: 'text.primary' }}
|
||||
>
|
||||
Making an impossible time a little easier
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ maxWidth: 560, mx: 'auto' }}
|
||||
>
|
||||
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.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: {
|
||||
xs: '1fr',
|
||||
sm: 'repeat(2, 1fr)',
|
||||
md: 'repeat(4, 1fr)',
|
||||
},
|
||||
gap: { xs: 3, md: 3 },
|
||||
}}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<Card key={feature.heading} variant="outlined" padding="compact">
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 1.5,
|
||||
mx: 'auto',
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
bgcolor: 'var(--fa-color-brand-50)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'primary.main',
|
||||
'& svg': { fontSize: 28 },
|
||||
}}
|
||||
>
|
||||
{feature.icon}
|
||||
</Box>
|
||||
<Typography
|
||||
variant="h5"
|
||||
component="h3"
|
||||
sx={{ mb: 1, color: 'text.primary' }}
|
||||
>
|
||||
{feature.heading}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{feature.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 5: Reviews / Testimonials
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{(testimonials.length > 0 || googleRating != null) && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="reviews-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-brand-950)',
|
||||
py: { xs: 6, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 3, md: 5 } }}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
sx={{ color: 'var(--fa-color-brand-400)', mb: 1.5, display: 'block' }}
|
||||
>
|
||||
What our customers say
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h2"
|
||||
component="h2"
|
||||
id="reviews-heading"
|
||||
sx={{ color: 'var(--fa-color-white)', mb: 3 }}
|
||||
>
|
||||
How we have helped families like yours
|
||||
</Typography>
|
||||
|
||||
{/* Google aggregate */}
|
||||
{googleRating != null && (
|
||||
<Box sx={{ mb: { xs: 3, md: 5 } }}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
sx={{
|
||||
color: 'var(--fa-color-neutral-400)',
|
||||
mb: 0.5,
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
Google Reviews
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h1"
|
||||
component="p"
|
||||
sx={{ color: 'var(--fa-color-white)', mb: 0.5 }}
|
||||
>
|
||||
{googleRating}
|
||||
</Typography>
|
||||
<StarRating
|
||||
rating={googleRating}
|
||||
size={20}
|
||||
color="var(--fa-color-brand-400)"
|
||||
/>
|
||||
{googleReviewCount != null && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ color: 'var(--fa-color-neutral-400)', mt: 0.5, display: 'block' }}
|
||||
>
|
||||
{googleReviewCount.toLocaleString('en-AU')} reviews
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Testimonial cards */}
|
||||
{testimonials.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: {
|
||||
xs: '1fr',
|
||||
md: 'repeat(3, 1fr)',
|
||||
},
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{testimonials.map((t, i) => (
|
||||
<Card key={`${t.name}-${i}`} variant="elevated" padding="default">
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" component="p">
|
||||
{t.name}
|
||||
</Typography>
|
||||
<StarRating rating={t.rating} size={14} />
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 2, fontStyle: 'italic' }}
|
||||
>
|
||||
“{t.quote}”
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.tertiary">
|
||||
{t.timeAgo}
|
||||
</Typography>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 6: CTA Banner
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="cta-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-warm)',
|
||||
py: { xs: 6, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg" sx={{ textAlign: 'center' }}>
|
||||
<Typography
|
||||
variant="displaySm"
|
||||
component="h2"
|
||||
id="cta-heading"
|
||||
sx={{ mb: 3, color: 'text.primary', maxWidth: 500, mx: 'auto' }}
|
||||
>
|
||||
{ctaHeading}
|
||||
</Typography>
|
||||
<Button variant="contained" size="large" onClick={onCtaClick}>
|
||||
{ctaButtonLabel}
|
||||
</Button>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 7: FAQ
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{faqItems.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="faq-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-default)',
|
||||
py: { xs: 6, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Typography
|
||||
variant="h2"
|
||||
component="h2"
|
||||
id="faq-heading"
|
||||
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 }, color: 'text.primary' }}
|
||||
>
|
||||
Frequently Asked Questions
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ maxWidth: 800, mx: 'auto' }}>
|
||||
{faqItems.map((item, i) => (
|
||||
<Accordion
|
||||
key={i}
|
||||
disableGutters
|
||||
elevation={0}
|
||||
sx={{
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
borderRadius: '8px !important',
|
||||
mb: 2,
|
||||
'&:before': { display: 'none' },
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 3, py: 1 }}>
|
||||
<Typography variant="h6" component="h3">
|
||||
{item.question}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ px: 3, pb: 3 }}>
|
||||
{typeof item.answer === 'string' ? (
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{item.answer}
|
||||
</Typography>
|
||||
) : (
|
||||
item.answer
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* ─── Footer ─── */}
|
||||
{footer}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
HomePage.displayName = 'HomePage';
|
||||
export default HomePage;
|
||||
2
src/components/pages/HomePage/index.ts
Normal file
2
src/components/pages/HomePage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { HomePage, default } from './HomePage';
|
||||
export type { HomePageProps, PartnerLogo, FeatureCard, Testimonial, FAQItem } from './HomePage';
|
||||
Reference in New Issue
Block a user