HomePage V2: full-bleed hero, stats bar, discover section, editorial testimonials
New layout inspired by reference designs: - Full-bleed hero with parsonshero.png + gradient overlay (heroImageUrl prop) - Trust stats bar: 1,500+ families, 4.9 rating, 300+ directors - "See what you'll discover" section: map placeholder + featured ProviderCards - Editorial testimonials: alternating left/right with quote marks (no cards) - Minimal FAQ: borderless accordion with divider lines - V1 split hero preserved when heroImage prop used instead New types: TrustStat, FeaturedProvider. New props: heroImageUrl, stats, featuredProviders, discoverMapSlot, onSelectFeaturedProvider. V1 stories unchanged, new V2 story added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ 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 type { FeaturedProvider, TrustStat } from './HomePage';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import { Footer } from '../../organisms/Footer';
|
||||
|
||||
@@ -264,3 +265,68 @@ export const Mobile: Story = {
|
||||
viewport: { defaultViewport: 'mobile1' },
|
||||
},
|
||||
};
|
||||
|
||||
// ─── V2 data ────────────────────────────────────────────────────────────────
|
||||
|
||||
const trustStats: TrustStat[] = [
|
||||
{ value: '1,500+', label: 'Families helped' },
|
||||
{ value: '4.9', label: 'Google Rating' },
|
||||
{ value: '300+', label: 'Funeral directors' },
|
||||
];
|
||||
|
||||
const featuredProviders: FeaturedProvider[] = [
|
||||
{
|
||||
id: 'parsons',
|
||||
name: 'H.Parsons Funeral Directors',
|
||||
location: 'Wentworth, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/E8E0D6/8B6F47?text=H.Parsons',
|
||||
logoUrl: 'https://placehold.co/64x64/FEF9F5/BA834E?text=HP',
|
||||
rating: 4.6,
|
||||
reviewCount: 7,
|
||||
startingPrice: 900,
|
||||
},
|
||||
{
|
||||
id: 'rankins',
|
||||
name: 'Rankins Funeral Services',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/D7E1E2/4C5B6B?text=Rankins',
|
||||
logoUrl: 'https://placehold.co/64x64/F2F5F6/4C5B6B?text=R',
|
||||
rating: 4.8,
|
||||
reviewCount: 23,
|
||||
startingPrice: 1200,
|
||||
},
|
||||
{
|
||||
id: 'easy-funerals',
|
||||
name: 'Easy Funerals',
|
||||
location: 'Sydney, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/F0F7F0/3B7A3B?text=Easy+Funerals',
|
||||
logoUrl: 'https://placehold.co/64x64/F0F7F0/3B7A3B?text=EF',
|
||||
rating: 4.5,
|
||||
reviewCount: 42,
|
||||
startingPrice: 850,
|
||||
},
|
||||
];
|
||||
|
||||
// ─── V2 Story ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** V2 layout — full-bleed hero, stats bar, map + provider cards, editorial testimonials */
|
||||
export const V2: Story = {
|
||||
args: {
|
||||
navigation: nav,
|
||||
footer,
|
||||
heroImageUrl: '/brandassets/images/heroes/parsonshero.png',
|
||||
stats: trustStats,
|
||||
featuredProviders,
|
||||
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),
|
||||
features,
|
||||
googleRating: 4.9,
|
||||
googleReviewCount: 2340,
|
||||
testimonials,
|
||||
faqItems,
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
onCtaClick: () => console.log('CTA clicked'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 FormatQuoteIcon from '@mui/icons-material/FormatQuote';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import StarHalfIcon from '@mui/icons-material/StarHalf';
|
||||
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||
@@ -12,6 +13,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
import { Button } from '../../atoms/Button';
|
||||
import { Card } from '../../atoms/Card';
|
||||
import { ProviderCard } from '../../molecules/ProviderCard';
|
||||
import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
@@ -43,6 +45,25 @@ export interface FAQItem {
|
||||
answer: React.ReactNode;
|
||||
}
|
||||
|
||||
/** A trust stat for the stats bar */
|
||||
export interface TrustStat {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/** A featured provider for the discover section */
|
||||
export interface FeaturedProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
verified?: boolean;
|
||||
imageUrl?: string;
|
||||
logoUrl?: string;
|
||||
rating?: number;
|
||||
reviewCount?: number;
|
||||
startingPrice?: number;
|
||||
}
|
||||
|
||||
/** Props for the HomePage component */
|
||||
export interface HomePageProps {
|
||||
/** Navigation bar */
|
||||
@@ -54,19 +75,31 @@ export interface HomePageProps {
|
||||
heroHeading?: string;
|
||||
/** Hero subheading paragraph */
|
||||
heroSubheading?: string;
|
||||
/** Hero image slot */
|
||||
/** Hero image slot (split layout — V1) */
|
||||
heroImage?: React.ReactNode;
|
||||
/** Hero background image URL (full-bleed layout — V2). Takes priority over heroImage. */
|
||||
heroImageUrl?: string;
|
||||
|
||||
/** FuneralFinder search callback */
|
||||
onSearch?: (params: FuneralFinderV3SearchParams) => void;
|
||||
/** FuneralFinder loading state */
|
||||
searchLoading?: boolean;
|
||||
|
||||
/** Trust stats bar (e.g. "1,500+ families helped") */
|
||||
stats?: TrustStat[];
|
||||
|
||||
/** Partner logos for the trust carousel */
|
||||
partnerLogos?: PartnerLogo[];
|
||||
/** Trust line text above the carousel */
|
||||
partnerTrustLine?: string;
|
||||
|
||||
/** Featured providers for the "Discover" section */
|
||||
featuredProviders?: FeaturedProvider[];
|
||||
/** Map slot for the discover section */
|
||||
discoverMapSlot?: React.ReactNode;
|
||||
/** Callback when a featured provider card is clicked */
|
||||
onSelectFeaturedProvider?: (id: string) => void;
|
||||
|
||||
/** Feature cards for "Why Use FA" */
|
||||
features?: FeatureCard[];
|
||||
|
||||
@@ -136,10 +169,15 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
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,
|
||||
heroImageUrl,
|
||||
onSearch,
|
||||
searchLoading,
|
||||
stats = [],
|
||||
partnerLogos = [],
|
||||
partnerTrustLine = 'Providing services from hundreds of trusted funeral homes across Australia',
|
||||
featuredProviders = [],
|
||||
discoverMapSlot,
|
||||
onSelectFeaturedProvider,
|
||||
features = [],
|
||||
googleRating,
|
||||
googleReviewCount,
|
||||
@@ -152,6 +190,8 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const isFullBleedHero = !!heroImageUrl;
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
@@ -167,73 +207,114 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 1: Hero
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="hero-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-warm)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Container
|
||||
maxWidth="lg"
|
||||
{isFullBleedHero ? (
|
||||
/* ── V2: Full-bleed hero with text overlay ── */
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="hero-heading"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
minHeight: { xs: 420, md: 520 },
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', md: 'row' },
|
||||
alignItems: 'center',
|
||||
gap: { xs: 3, md: 0 },
|
||||
py: { xs: 4, md: 0 },
|
||||
justifyContent: 'center',
|
||||
backgroundImage: `url(${heroImageUrl})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
'&::after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0.4) 100%)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Hero text */}
|
||||
<Box
|
||||
sx={{
|
||||
flex: { md: '0 0 50%' },
|
||||
py: { md: 6 },
|
||||
pr: { md: 5 },
|
||||
textAlign: { xs: 'center', md: 'left' },
|
||||
}}
|
||||
<Container
|
||||
maxWidth="md"
|
||||
sx={{ position: 'relative', zIndex: 1, textAlign: 'center', py: 6 }}
|
||||
>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h1"
|
||||
id="hero-heading"
|
||||
tabIndex={-1}
|
||||
sx={{ mb: 2, color: 'text.primary' }}
|
||||
sx={{ mb: 2, color: 'var(--fa-color-white)' }}
|
||||
>
|
||||
{heroHeading}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: 'rgba(255,255,255,0.85)', maxWidth: 560, mx: 'auto' }}
|
||||
>
|
||||
{heroSubheading}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Hero image */}
|
||||
<Box
|
||||
role="img"
|
||||
aria-label="Family planning together"
|
||||
</Container>
|
||||
</Box>
|
||||
) : (
|
||||
/* ── V1: Split hero ── */
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="hero-heading"
|
||||
sx={{ bgcolor: 'var(--fa-color-surface-warm)', overflow: 'hidden' }}
|
||||
>
|
||||
<Container
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
flex: { md: '0 0 50%' },
|
||||
minHeight: { xs: 200, md: 320 },
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
borderRadius: { xs: 'var(--fa-border-radius-lg)', md: 0 },
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', md: 'row' },
|
||||
alignItems: 'center',
|
||||
gap: { xs: 3, md: 0 },
|
||||
py: { xs: 4, 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>
|
||||
<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>
|
||||
<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)
|
||||
@@ -256,6 +337,131 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 2b: Stats Bar (V2 only)
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{stats.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-label="Trust statistics"
|
||||
sx={{ py: { xs: 4, md: 5 }, mt: { xs: 4, md: 5 } }}
|
||||
>
|
||||
<Container maxWidth="md">
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: { xs: 3, md: 6 },
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{stats.map((stat) => (
|
||||
<Box key={stat.label}>
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="p"
|
||||
sx={{ color: 'primary.main', mb: 0.5 }}
|
||||
>
|
||||
{stat.value}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{stat.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 2c: Discover — Map + Featured Providers (V2)
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{featuredProviders.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="discover-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||
py: { xs: 6, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h2"
|
||||
id="discover-heading"
|
||||
sx={{ mb: 1.5, color: 'text.primary' }}
|
||||
>
|
||||
See what you’ll discover
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
sx={{ maxWidth: 520, mx: 'auto' }}
|
||||
>
|
||||
From trusted local providers to personalised options, find the right care near
|
||||
you.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' },
|
||||
gap: 3,
|
||||
alignItems: 'start',
|
||||
}}
|
||||
>
|
||||
{/* Map placeholder */}
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
minHeight: { xs: 240, md: 400 },
|
||||
bgcolor: 'var(--fa-color-surface-cool)',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{discoverMapSlot || (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Map coming soon
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Featured provider cards */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{featuredProviders.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.id}
|
||||
name={provider.name}
|
||||
location={provider.location}
|
||||
verified={provider.verified}
|
||||
imageUrl={provider.imageUrl}
|
||||
logoUrl={provider.logoUrl}
|
||||
rating={provider.rating}
|
||||
reviewCount={provider.reviewCount}
|
||||
startingPrice={provider.startingPrice}
|
||||
onClick={
|
||||
onSelectFeaturedProvider
|
||||
? () => onSelectFeaturedProvider(provider.id)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 3: Partner Logos Carousel
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
@@ -319,12 +525,9 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
'100%': { transform: 'translateX(-50%)' },
|
||||
},
|
||||
'&:hover': { animationPlayState: 'paused' },
|
||||
'@media (prefers-reduced-motion: reduce)': {
|
||||
animation: 'none',
|
||||
},
|
||||
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
|
||||
}}
|
||||
>
|
||||
{/* Duplicate logos for seamless infinite scroll */}
|
||||
{[...partnerLogos, ...partnerLogos].map((logo, i) => (
|
||||
<Box
|
||||
key={`${logo.alt}-${i}`}
|
||||
@@ -339,10 +542,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.5,
|
||||
transition: 'opacity 0.2s, filter 0.2s',
|
||||
'&:hover': {
|
||||
filter: 'grayscale(0%)',
|
||||
opacity: 1,
|
||||
},
|
||||
'&:hover': { filter: 'grayscale(0%)', opacity: 1 },
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
@@ -442,112 +642,100 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════
|
||||
Section 5: Reviews / Testimonials
|
||||
Section 5: Testimonials
|
||||
═══════════════════════════════════════════════════════════════════ */}
|
||||
{(testimonials.length > 0 || googleRating != null) && (
|
||||
{testimonials.length > 0 && (
|
||||
<Box
|
||||
component="section"
|
||||
aria-labelledby="reviews-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-brand-950)',
|
||||
py: { xs: 6, md: 10 },
|
||||
bgcolor: 'var(--fa-color-surface-default)',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Container maxWidth="md">
|
||||
<Typography
|
||||
variant="h2"
|
||||
component="h2"
|
||||
id="reviews-heading"
|
||||
sx={{ textAlign: 'center', mb: 1, color: 'text.primary' }}
|
||||
>
|
||||
Testimonials
|
||||
</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
|
||||
{googleRating != null && (
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
<StarRating rating={googleRating} size={16} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{googleRating} Google Rating
|
||||
{googleReviewCount
|
||||
? ` · ${googleReviewCount.toLocaleString('en-AU')} 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>
|
||||
)}
|
||||
|
||||
{/* Editorial testimonials — alternating alignment */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{testimonials.map((t, i) => {
|
||||
const isRight = i % 2 === 1;
|
||||
return (
|
||||
<Box
|
||||
key={`${t.name}-${i}`}
|
||||
sx={{
|
||||
textAlign: isRight ? 'right' : 'left',
|
||||
maxWidth: '85%',
|
||||
ml: isRight ? 'auto' : 0,
|
||||
mr: isRight ? 0 : 'auto',
|
||||
}}
|
||||
>
|
||||
<FormatQuoteIcon
|
||||
sx={{
|
||||
fontSize: 32,
|
||||
color: 'var(--fa-color-brand-300)',
|
||||
transform: isRight ? 'scaleX(-1)' : 'none',
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="h5"
|
||||
component="blockquote"
|
||||
sx={{ mb: 2, fontWeight: 400 }}
|
||||
>
|
||||
{t.quote}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="label"
|
||||
component="cite"
|
||||
sx={{ fontStyle: 'normal', display: 'block', mb: 0.5 }}
|
||||
>
|
||||
{t.name}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<StarRating rating={t.rating} size={12} />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
· {t.timeAgo}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)}
|
||||
@@ -597,32 +785,34 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
id="faq-heading"
|
||||
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 }, color: 'text.primary' }}
|
||||
>
|
||||
Frequently Asked Questions
|
||||
FAQ
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ maxWidth: 800, mx: 'auto' }}>
|
||||
<Box sx={{ maxWidth: 700, mx: 'auto' }}>
|
||||
{faqItems.map((item, i) => (
|
||||
<Accordion
|
||||
key={i}
|
||||
disableGutters
|
||||
elevation={0}
|
||||
sx={{
|
||||
border: 1,
|
||||
bgcolor: 'transparent',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderRadius: '8px !important',
|
||||
mb: 2,
|
||||
'&:before': { display: 'none' },
|
||||
overflow: 'hidden',
|
||||
'&:first-of-type': {
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 3, py: 1 }}>
|
||||
<Typography variant="h6" component="h3">
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 0, py: 1.5 }}>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{item.question}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ px: 3, pb: 3 }}>
|
||||
<AccordionDetails sx={{ px: 0, pb: 3 }}>
|
||||
{typeof item.answer === 'string' ? (
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{item.answer}
|
||||
</Typography>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user