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:
2026-03-30 18:05:22 +11:00
parent ade2f68f07
commit d51752d600
5 changed files with 919 additions and 3 deletions

2
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View 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' },
},
};

View 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 15 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&apos;t have to be overwhelming. Whether a loved one has
just passed, is imminent, or you&apos;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' }}
>
&ldquo;{t.quote}&rdquo;
</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;

View File

@@ -0,0 +1,2 @@
export { HomePage, default } from './HomePage';
export type { HomePageProps, PartnerLogo, FeatureCard, Testimonial, FAQItem } from './HomePage';