diff --git a/docs/memory/component-registry.md b/docs/memory/component-registry.md
index 9c1a561..b8679b5 100644
--- a/docs/memory/component-registry.md
+++ b/docs/memory/component-registry.md
@@ -54,6 +54,7 @@ duplicates) and MUST update it after completing one.
| ServiceSelector | done | ServiceOption × n + Typography + Button | Single-select service panel for arrangement flow. Heading + subheading + ServiceOption list (radiogroup) + optional continue Button. Manages selection state via selectedId/onSelect. maxDescriptionLines pass-through. |
| PricingTable | planned | PriceCard × n + Typography | Comparative pricing display |
| PackageDetail | done | LineItem × n + Typography + Button + Divider | Right-side package detail panel. Warm header band (surface.warm) with "Package" overline, name, price (brand colour), Make Arrangement + Compare (with loading) buttons. Sections (before total) + total + extras (after total, with subtext). T&C grey footer. Audit: 19/20. Maps to Figma Package Select (5405:181955). |
+| FuneralFinder | review | Typography + Button + Chip + Input + custom OptionCard | Hero search widget. Procedural stepped flow: Intent → Planning For (conditional) → Funeral Type (chips, dynamic) → Location → CTA. Brand checkmarks on completion, edit icon to revert, radiogroup semantics. Audit: 17/20 → fixes applied. |
| ArrangementForm | planned | StepIndicator + ServiceSelector + AddOnOption + Button + Typography | Multi-step arrangement wizard. Deferred — build remaining atoms/molecules first. |
| Navigation | done | AppBar + Link + IconButton + Button + Divider + Drawer | Responsive site header. Desktop: logo left, links right, optional CTA. Mobile: hamburger + drawer with nav items, CTA, help footer. Sticky, grey surface bg (surface.subtle). Real FA logo from brandassets/. Maps to Figma Main Nav (14:108) + Mobile Header (2391:41508). |
| Footer | done | Link × n + Typography + Divider + Container + Grid | Dark espresso (brand.950) site footer. Logo + tagline + contact (phone/email) + link group columns + legal bar. Semantic HTML (footer, nav, ul). Critique: 38/40 (Excellent). |
diff --git a/src/components/organisms/FuneralFinder/FuneralFinder.stories.tsx b/src/components/organisms/FuneralFinder/FuneralFinder.stories.tsx
new file mode 100644
index 0000000..bb40a39
--- /dev/null
+++ b/src/components/organisms/FuneralFinder/FuneralFinder.stories.tsx
@@ -0,0 +1,220 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import Box from '@mui/material/Box';
+import { FuneralFinder } from './FuneralFinder';
+import { Navigation } from '../Navigation';
+import { Typography } from '../../atoms/Typography';
+
+const funeralTypes = [
+ { id: 'cremation', label: 'Cremation' },
+ { id: 'burial', label: 'Burial' },
+ { id: 'memorial', label: 'Memorial' },
+ { id: 'catholic', label: 'Catholic' },
+ { id: 'direct-cremation', label: 'Direct Cremation' },
+ { id: 'natural-burial', label: 'Natural Burial' },
+];
+
+const FALogoNav = () => (
+
+);
+
+const meta: Meta = {
+ title: 'Organisms/FuneralFinder',
+ component: FuneralFinder,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+// --- Default -----------------------------------------------------------------
+
+/** Initial state — step 1 active, all others locked */
+export const Default: Story = {
+ args: {
+ funeralTypes,
+ onSearch: (params) => alert(JSON.stringify(params, null, 2)),
+ },
+};
+
+// --- Fewer Funeral Types -----------------------------------------------------
+
+/** With only 3 funeral types — shows compact chip row */
+export const FewerTypes: Story = {
+ args: {
+ funeralTypes: funeralTypes.slice(0, 3),
+ onSearch: (params) => alert(JSON.stringify(params, null, 2)),
+ },
+};
+
+// --- Custom Heading ----------------------------------------------------------
+
+/** With custom heading and subheading */
+export const CustomHeading: Story = {
+ args: {
+ funeralTypes,
+ heading: 'Compare funeral directors in your area',
+ subheading: 'Transparent pricing · No hidden fees · 24/7',
+ onSearch: (params) => alert(JSON.stringify(params, null, 2)),
+ },
+};
+
+// --- In Hero Context (Desktop) -----------------------------------------------
+
+/** As it appears in the homepage hero — desktop layout */
+export const InHeroDesktop: Story = {
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ render: () => (
+
+ }
+ items={[
+ { label: 'Provider Portal', href: '/provider-portal' },
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ { label: 'Log in', href: '/login' },
+ ]}
+ />
+
+ {/* Hero section */}
+
+ {/* Left: heading + search widget */}
+
+
+
+ Discover, Explore, and Plan Funerals in Minutes
+
+
+ Whether you're thinking ahead or arranging for a loved one, find
+ trusted local providers with transparent pricing.
+
+
+ alert(JSON.stringify(params, null, 2))}
+ />
+
+
+
+ {/* Right: hero image placeholder */}
+
+
+
+ ),
+};
+
+// --- In Hero Context (Mobile) ------------------------------------------------
+
+/** Mobile viewport — stacked layout with image above search */
+export const InHeroMobile: Story = {
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ render: () => (
+
+ }
+ items={[
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ { label: 'Log in', href: '/login' },
+ ]}
+ />
+
+ {/* Hero heading */}
+
+
+ Discover, Explore, and Plan Funerals in Minutes
+
+
+ Find trusted local providers with transparent pricing, at your own pace.
+
+
+
+ {/* Hero image */}
+
+
+ {/* Search widget — overlaps image slightly */}
+
+ alert(JSON.stringify(params, null, 2))}
+ />
+
+
+ ),
+};
diff --git a/src/components/organisms/FuneralFinder/FuneralFinder.tsx b/src/components/organisms/FuneralFinder/FuneralFinder.tsx
new file mode 100644
index 0000000..a55c263
--- /dev/null
+++ b/src/components/organisms/FuneralFinder/FuneralFinder.tsx
@@ -0,0 +1,478 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+import CheckCircleIcon from '@mui/icons-material/CheckCircle';
+import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
+import type { SxProps, Theme } from '@mui/material/styles';
+import { Typography } from '../../atoms/Typography';
+import { Button } from '../../atoms/Button';
+import { Chip } from '../../atoms/Chip';
+import { Input } from '../../atoms/Input';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+/** A funeral type option (dynamic, from API) */
+export interface FuneralTypeOption {
+ /** Unique identifier */
+ id: string;
+ /** Display label */
+ label: string;
+}
+
+/** Search parameters returned when the user submits */
+export interface FuneralSearchParams {
+ /** "arrange" (immediate need) or "preplan" */
+ intent: 'arrange' | 'preplan';
+ /** Only present when intent is "preplan" */
+ planningFor?: 'myself' | 'someone-else';
+ /** Selected funeral type ID */
+ funeralTypeId: string;
+ /** Suburb or postcode entered */
+ location: string;
+}
+
+/** Props for the FA FuneralFinder organism */
+export interface FuneralFinderProps {
+ /** Available funeral types — dynamic list from API */
+ funeralTypes: FuneralTypeOption[];
+ /** Called when the user clicks "Find funeral directors" */
+ onSearch?: (params: FuneralSearchParams) => void;
+ /** Optional heading override */
+ heading?: string;
+ /** Optional subheading override */
+ subheading?: string;
+ /** MUI sx prop for the root card */
+ sx?: SxProps;
+}
+
+// ─── Step state types ────────────────────────────────────────────────────────
+
+type Intent = 'arrange' | 'preplan' | null;
+type PlanningFor = 'myself' | 'someone-else' | null;
+
+type StepStatus = 'active' | 'completed' | 'locked';
+
+// ─── Sub-components ──────────────────────────────────────────────────────────
+
+/** Step number badge or completed checkmark */
+function StepBadge({ step, status }: { step: number; status: StepStatus }) {
+ if (status === 'completed') {
+ return (
+
+ );
+ }
+ return (
+
+ {step}
+
+ );
+}
+
+/** A selectable option card within a step — uses radio semantics */
+function OptionCard({
+ label,
+ selected,
+ onClick,
+ disabled,
+}: {
+ label: string;
+ selected: boolean;
+ onClick: () => void;
+ disabled?: boolean;
+}) {
+ return (
+
+ {label}
+
+ );
+}
+
+// ─── Component ───────────────────────────────────────────────────────────────
+
+/**
+ * Hero search widget for the FA homepage.
+ *
+ * Guides users through a procedural stepped flow to find funeral directors:
+ * 1. "I'm here to" — Arrange a funeral now / Pre-plan a funeral
+ * 2. "I'm planning for" (conditional, only for pre-plan) — Myself / Someone else
+ * 3. "Type of funeral" — dynamic list of funeral types (Cremation, Burial, etc.)
+ * 4. Location — suburb or postcode input
+ * 5. CTA: "Find funeral directors"
+ *
+ * Each step reveals progressively. Completed steps collapse to show the
+ * selected value with a brand checkmark. Click a completed step to re-edit.
+ * Reverting a step only resets dependent steps (e.g., changing intent from
+ * "Pre-plan" to "Arrange now" removes the "I'm planning for" step).
+ *
+ * Composes Typography + Button + Chip + Input.
+ */
+export const FuneralFinder = React.forwardRef(
+ (
+ {
+ funeralTypes,
+ onSearch,
+ heading = 'Find funeral directors near you',
+ subheading = "Tell us a little about what you're looking for and we'll show you options in your area.",
+ sx,
+ },
+ ref,
+ ) => {
+ // ─── State ───────────────────────────────────────────────────────
+ const [intent, setIntent] = React.useState(null);
+ const [planningFor, setPlanningFor] = React.useState(null);
+ const [funeralTypeId, setFuneralTypeId] = React.useState(null);
+ const [location, setLocation] = React.useState('');
+
+ // Track which step the user is currently editing (null = auto-advance)
+ const [editingStep, setEditingStep] = React.useState(null);
+
+ // ─── Derived state ──────────────────────────────────────────────
+ const needsPlanningFor = intent === 'preplan';
+ const funeralTypeLabel = funeralTypes.find((ft) => ft.id === funeralTypeId)?.label;
+
+ // Determine step statuses
+ const getStepStatus = (step: number): StepStatus => {
+ if (editingStep === step) return 'active';
+
+ switch (step) {
+ case 1:
+ return intent ? 'completed' : 'active';
+ case 2: // planning for (conditional)
+ if (!needsPlanningFor) return 'locked';
+ if (planningFor) return 'completed';
+ return intent ? 'active' : 'locked';
+ case 3: // funeral type
+ if (funeralTypeId) return 'completed';
+ if (!intent) return 'locked';
+ if (needsPlanningFor && !planningFor) return 'locked';
+ return 'active';
+ case 4: // location
+ if (!funeralTypeId) return 'locked';
+ return 'active';
+ default:
+ return 'locked';
+ }
+ };
+
+ // Step numbering adjusts when planning-for step is hidden
+ const getVisibleStepNumber = (step: number): number => {
+ if (!needsPlanningFor && step > 2) return step - 1;
+ return step;
+ };
+
+ // Can submit?
+ const canSubmit =
+ intent !== null &&
+ (!needsPlanningFor || planningFor !== null) &&
+ funeralTypeId !== null &&
+ location.trim().length > 0;
+
+ // ─── Handlers ───────────────────────────────────────────────────
+ const handleIntentSelect = (value: Intent) => {
+ setIntent(value);
+ // If switching from preplan to arrange, clear planningFor
+ if (value === 'arrange') {
+ setPlanningFor(null);
+ }
+ setEditingStep(null);
+ };
+
+ const handlePlanningForSelect = (value: PlanningFor) => {
+ setPlanningFor(value);
+ setEditingStep(null);
+ };
+
+ const handleFuneralTypeSelect = (id: string) => {
+ setFuneralTypeId(id);
+ setEditingStep(null);
+ };
+
+ const handleRevert = (step: number) => {
+ setEditingStep(step);
+ };
+
+ const handleSubmit = () => {
+ if (!canSubmit || !intent || !funeralTypeId) return;
+ onSearch?.({
+ intent,
+ planningFor: needsPlanningFor ? (planningFor ?? undefined) : undefined,
+ funeralTypeId,
+ location: location.trim(),
+ });
+ };
+
+ // ─── Render helpers ─────────────────────────────────────────────
+ const renderStep = (
+ stepNum: number,
+ label: string,
+ status: StepStatus,
+ completedValue: string | undefined,
+ content: React.ReactNode,
+ ) => {
+ const visibleNum = getVisibleStepNumber(stepNum);
+ const isCompleted = status === 'completed' && editingStep !== stepNum;
+ const isActive = status === 'active' || editingStep === stepNum;
+ const isLocked = status === 'locked';
+
+ return (
+
+
+
+
+
+
+
+ {label}
+
+
+ {isCompleted && completedValue && (
+ handleRevert(stepNum)}
+ sx={{
+ background: 'none',
+ border: 'none',
+ p: 0,
+ cursor: 'pointer',
+ fontFamily: 'inherit',
+ fontSize: '0.9375rem',
+ fontWeight: 600,
+ color: 'text.primary',
+ textAlign: 'left',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.75,
+ '&:hover': { color: 'primary.main' },
+ '&:focus-visible': {
+ outline: '2px solid var(--fa-color-brand-500)',
+ outlineOffset: 2,
+ borderRadius: '2px',
+ },
+ }}
+ aria-label={`${label}: ${completedValue}. Click to change.`}
+ >
+ {completedValue}
+
+
+ )}
+
+ {isActive && content}
+
+ {isLocked && (
+
+ Complete the step above
+
+ )}
+
+
+ );
+ };
+
+ // ─── Render ─────────────────────────────────────────────────────
+ return (
+
+ {/* Header */}
+
+ {heading}
+
+
+ {subheading}
+
+
+ {/* Steps */}
+
+ {/* Step 1: Intent */}
+ {renderStep(
+ 1,
+ "I'M HERE TO",
+ getStepStatus(1),
+ intent === 'arrange' ? 'Arrange a funeral now' : intent === 'preplan' ? 'Pre-plan a funeral' : undefined,
+
+ handleIntentSelect('arrange')}
+ />
+ handleIntentSelect('preplan')}
+ />
+ ,
+ )}
+
+ {/* Step 2: Planning for (conditional) */}
+ {needsPlanningFor &&
+ renderStep(
+ 2,
+ "I'M PLANNING FOR",
+ getStepStatus(2),
+ planningFor === 'myself' ? 'Myself' : planningFor === 'someone-else' ? 'Someone else' : undefined,
+
+ handlePlanningForSelect('myself')}
+ />
+ handlePlanningForSelect('someone-else')}
+ />
+ ,
+ )}
+
+ {/* Step 3: Funeral type */}
+ {renderStep(
+ 3,
+ 'TYPE OF FUNERAL',
+ getStepStatus(3),
+ funeralTypeLabel,
+
+ {funeralTypes.map((ft) => (
+ handleFuneralTypeSelect(ft.id)}
+ />
+ ))}
+ ,
+ )}
+
+ {/* Step 4: Location */}
+ {renderStep(
+ 4,
+ 'LOCATION',
+ getStepStatus(4),
+ undefined, // Location doesn't collapse — always editable when unlocked
+ setLocation(e.target.value)}
+ size="small"
+ fullWidth
+ inputProps={{ 'aria-label': 'Location — suburb or postcode' }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && canSubmit) handleSubmit();
+ }}
+ />,
+ )}
+
+
+ {/* CTA */}
+
+
+
+
+ {/* Trust signal */}
+
+ Free to use · No obligation
+
+
+ );
+ },
+);
+
+FuneralFinder.displayName = 'FuneralFinder';
+export default FuneralFinder;
diff --git a/src/components/organisms/FuneralFinder/index.ts b/src/components/organisms/FuneralFinder/index.ts
new file mode 100644
index 0000000..d4f9fa4
--- /dev/null
+++ b/src/components/organisms/FuneralFinder/index.ts
@@ -0,0 +1,6 @@
+export {
+ FuneralFinder,
+ type FuneralFinderProps,
+ type FuneralTypeOption,
+ type FuneralSearchParams,
+} from './FuneralFinder';