// SCN2A Family Map — a community connection map of Australia.
//
// Families add themselves via a short external survey; each becomes a pin on
// the map and a card in the list, and its state's count ticks up. Where a
// family opts in, others can ask our team to introduce
// them. No family ever contacts another directly through this page.
//
// The map starts EMPTY by design: every state shows 0 until families join.
// Reviewed families load from families.json; introduction requests post to
// /api/introduction.js; survey CTAs open FMAP_SURVEY_URL (see the constants
// below). State geometry is baked locally in lib/au-states-geo.js
// (window.AU_STATES) — no runtime CDN or D3 dependency.

// ---- Geometry + projection (baked offline, see lib/au-states-geo.js) ----
const AU = (typeof window !== 'undefined' && window.AU_STATES) || null;
const FMAP_STATES_GEO = AU ? AU.states : [];
const FMAP_ANCHORS = AU ? AU.anchors : {};
const FMAP_PROJ = (AU && AU.proj) || { scale: 1100, tx: 500, ty: 370, lng0: 133, lat0: -27 };
const FMAP_VIEWBOX = (AU && AU.viewBox) || '0 0 1000 780';

const FMAP_D2R = Math.PI / 180;
const fmapMercY = (deg) => Math.log(Math.tan(Math.PI / 4 + (deg * FMAP_D2R) / 2));
const FMAP_MY0 = fmapMercY(FMAP_PROJ.lat0);
// Project [lng,lat] to the same pixel space as the baked polygons/badges.
function fmapProject(lng, lat) {
  return [
    FMAP_PROJ.tx + FMAP_PROJ.scale * ((lng - FMAP_PROJ.lng0) * FMAP_D2R),
    FMAP_PROJ.ty - FMAP_PROJ.scale * (fmapMercY(lat) - FMAP_MY0),
  ];
}

const STATE_NAMES = (AU && AU.names) || {
  NSW: 'New South Wales', VIC: 'Victoria', QLD: 'Queensland', SA: 'South Australia',
  WA: 'Western Australia', TAS: 'Tasmania', NT: 'Northern Territory', ACT: 'Australian Capital Territory',
};
const STATE_ORDER = ['All', 'NSW', 'VIC', 'QLD', 'SA', 'WA', 'TAS', 'NT', 'ACT'];

// ---- Pin marker geometry (a single teardrop path, reused for every pin) ----
// Local coordinate space: the tip (the actual located point) sits at (0,0);
// the rounded head is centred above it. Callers position the pin with an SVG
// `transform="translate(x,y)"` on a wrapping <g>, then layer the drop
// animation and any hover/selected scaling on nested <g>s so those transforms
// never collide with each other (see fmap-pin-drop in styles.css).
const FMAP_PIN_D = 'M0,0C-6.4,-9.8 -11,-16.2 -11,-22 A11,11 0 1,1 11,-22 C11,-16.2 6.4,-9.8 0,0 Z';
const FMAP_PIN_HEAD_Y = -22;

// Deterministic "randomness" (no Math.random) so a given preview submission
// always renders at the same nearby spot on re-render, while a fresh
// submission (different ts) lands at a visibly different nearby spot.
function fmapJitter(seed, spread) {
  let h = 0;
  for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
  const a = ((h % 1000) / 1000) - 0.5;
  const b = (((h >> 10) % 1000) / 1000) - 0.5;
  return [a * 2 * spread, b * 2 * spread];
}

// Age for the card, from the birth year the public file carries.
//
// With only a year, the true age is one of two numbers: someone born in 2002
// is 23 until their birthday in 2026 and 24 after it. We show the lower one,
// so the card never gives a family an age their child has not reached yet.
// The cost is that it lags: it catches up on 1 January rather than on the
// birthday, so it can read a year young for most of a year.
//
// That is the deliberate trade for not publishing a date of birth. The exact
// date is collected and held privately — see docs/family-map-curation.md.
//
// Clamped at 0 so a baby born this year reads "age 0" rather than -1.
function fmapAgeFromBirthYear(birthYear) {
  return Math.max(0, new Date().getFullYear() - birthYear - 1);
}

// The "Add your family" survey is the on-site combined Map + Registry flow at
// /join (src/pages-join.jsx). Set FMAP_SURVEY_URL to an absolute URL only if
// the survey ever moves to an external tool (Google Forms / Jotform / Typeform);
// while null, every survey CTA navigates to the internal join route.
const FMAP_SURVEY_URL = null;
const FMAP_JOIN_ROUTE = 'join';

// Introduction request endpoint (the Vercel serverless function in
// /api/introduction.js). Nothing travels from one family to another here. The
// request is emailed to the team's monitored inbox and stops there; they
// speak to both families and connect them only once both have agreed. Nothing
// about the request is stored. If the POST fails the modal reports the failure
// — it must never show a confirmation screen for a request that reached nobody.
const FMAP_INTRO_ENDPOINT = '/api/introduction';

// Note length cap, mirrored by MAX_NOTE_CHARS in /api/introduction.js. The note
// is optional and lands in one person's inbox, so this is about keeping that
// inbox readable rather than a technical limit. Change both ends together.
const FMAP_NOTE_MAX = 1000;

// The states a requester can pick, matching the map's own state list and the
// values families.json is validated against.
const FMAP_REQUEST_STATES = ['NSW', 'VIC', 'QLD', 'SA', 'WA', 'TAS', 'NT', 'ACT'];

// Reviewed families are served from a static JSON file (curated by the team
// after each survey response, per the "we add your pin" step). Ships as [] —
// add approved records to vercel-deploy/families.json and redeploy. Record:
//   { id, name, region, state, role, birthYear, connect, since, topics, blurb, lat, lng }
//   state ∈ NSW VIC QLD SA WA TAS NT ACT ; connect controls the "Ask us to introduce you" button.
// Kept outside /assets so it dodges the year-long immutable cache header.
//
// NOTHING WRITES TO THIS FILE AUTOMATICALLY. A family completing /join reaches
// the intake webhook (or the email backup) and no further — a human has to add
// the record here for the pin to exist. The full runbook, the join-payload →
// record field mapping and the approved region coordinates are in
// docs/family-map-curation.md; validate with `npm run validate:families`.
const FMAP_FAMILIES_URL = '/families.json';

// A published record only becomes a visible pin if it has a real state anchor
// and finite coordinates. Without this guard a record missing lat/lng still
// counts toward the hero total and the state badge, but its pin renders at
// translate(NaN,NaN) — the family reads "0 families" (or a count with nothing
// to click) and believes they were never added. Drop unusable records instead
// of half-showing them, and say so in the console for whoever is curating.
// build/validate-families.mjs catches the same problems before deploy.
function fmapUsable(records) {
  const kept = [];
  const dropped = [];
  (Array.isArray(records) ? records : []).forEach((f) => {
    const ok = f && typeof f === 'object'
      && typeof f.id === 'string' && f.id
      && typeof f.state === 'string' && Object.prototype.hasOwnProperty.call(STATE_NAMES, f.state)
      && Number.isFinite(f.lat) && Number.isFinite(f.lng);
    (ok ? kept : dropped).push(f);
  });
  if (dropped.length && typeof console !== 'undefined' && console.warn) {
    console.warn(
      `[family-map] ${dropped.length} record(s) in families.json are missing a valid id, state or lat/lng and were not mapped. ` +
      'See docs/family-map-curation.md, then run: npm run validate:families',
      dropped
    );
  }
  return kept;
}

const FMAP_SUPPORT_EMAIL = 'info@scn2aaustralia.org';
// Legacy only. The removed message relay once parked undelivered messages,
// including the sender's email address, on this key, and nothing ever read it
// back. Retained purely so the dialog can clear it from the browser of anyone
// who still carries one.
const FMAP_MSG_KEY = 'scn2a_au_messages_v1';

// Sample families (NOT shown) — copy any into FAMILIES to preview the populated map.
// eslint-disable-next-line no-unused-vars
const SAMPLE_FAMILIES = [
  { id: 'f1', name: 'The Nguyen', region: 'Western Sydney', state: 'NSW', role: 'Parents', birthYear: 2020, connect: true, since: '2026', topics: 'Schooling & NDIS', blurb: "Our son was diagnosed at 14 months. We've learned a lot about navigating the NDIS and would love to share notes with other NSW families.", lat: -33.81, lng: 150.92 },
  { id: 'f4', name: 'The Williams', region: 'Inner Melbourne', state: 'VIC', role: 'Dad', birthYear: 2021, connect: true, since: '2026', topics: 'Therapies & AAC', blurb: "Our daughter uses an AAC device and we're always keen to swap ideas on communication and therapy with other Victorian families.", lat: -37.81, lng: 144.96 },
  { id: 'f12', name: 'The Lee', region: 'Perth', state: 'WA', role: 'Parents', birthYear: 2021, connect: true, since: '2026', topics: 'WA services', blurb: "Connecting WA families feels important, we're so far from the east coast. Reach out, we'd love to build something local.", lat: -31.95, lng: 115.86 },
];

// ---- Per-state count badge (circle + number + abbreviation) ----
function FamilyMapBadge({ abbr, count }) {
  const a = FMAP_ANCHORS[abbr];
  if (!a) return null;
  const has = count > 0;
  return (
    <g aria-hidden="true">
      {a.nudged && <line x1={a.ax} y1={a.ay} x2={a.bx} y2={a.by} stroke="#4A7EC7" strokeWidth="1" />}
      <circle cx={a.bx} cy={a.by} r="19" fill={has ? '#1E3A6E' : '#F2EFE9'} stroke={has ? '#1E3A6E' : '#D5CFBF'} strokeWidth="1.5" />
      <text x={a.bx} y={a.by} textAnchor="middle" dominantBaseline="central" fontSize="17" fontWeight="800" fill={has ? '#FFFFFF' : '#1E3A6E'}>{count}</text>
      <text x={a.bx} y={a.by + 31} textAnchor="middle" fontSize="9.5" fontWeight="700" fill="#6B6659" style={{ letterSpacing: '0.08em' }}>{abbr}</text>
    </g>
  );
}

// ---- Side panel: a selected family's detail card ----
function SelectedFamily({ fam, onIntroduce }) {
  return (
    <div className="fmap-rise" key={fam.id} style={{ background: '#fff', border: '1px solid #D5CFBF', borderRadius: 8, padding: 24, boxShadow: '0 1px 2px rgba(13,27,42,0.06), 0 1px 3px rgba(13,27,42,0.08)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16, flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', padding: '4px 10px', borderRadius: 3, background: '#E8E3DA', fontSize: 11, fontWeight: 600, color: '#0D1B2A' }}>{STATE_NAMES[fam.state]}</span>
        <span style={{ fontSize: 12, color: '#6B6659' }}>{fam.region}</span>
        {fam.connect && (
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, marginLeft: 'auto', fontSize: 11, fontWeight: 600, color: '#4A7EC7' }}>
            <span style={{ width: 6, height: 6, borderRadius: 999, background: '#4A7EC7' }} />Open to connect
          </span>
        )}
      </div>
      <h3 style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-0.01em', margin: '0 0 4px', color: '#0D1B2A' }}>{fam.name}'s family</h3>
      {/* SCN2A does not stop at childhood, and the first family on this map is
          here for a 24-year-old. Never say "child" — the person with SCN2A may
          be an adult, and telling their family otherwise erases them. */}
      <div style={{ fontSize: 13, color: '#6B6659', marginBottom: 16 }}>{fam.role} · {fam.name} lives with SCN2A, age {fmapAgeFromBirthYear(fam.birthYear)}</div>
      <p style={{ fontSize: 15, lineHeight: 1.6, color: '#0D1B2A', margin: '0 0 20px' }}>{fam.blurb}</p>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', borderTop: '1px solid #D5CFBF', borderBottom: '1px solid #D5CFBF', margin: '0 0 20px', padding: '14px 0' }}>
        <div>
          <div className="eyebrow" style={{ color: '#6B6659', marginBottom: 4 }}>On the map since</div>
          <div style={{ fontSize: 15, fontWeight: 600, color: '#0D1B2A' }}>{fam.since}</div>
        </div>
        <div>
          <div className="eyebrow" style={{ color: '#6B6659', marginBottom: 4 }}>Happy to talk about</div>
          <div style={{ fontSize: 14, color: '#0D1B2A' }}>{fam.topics}</div>
        </div>
      </div>
      {fam.connect ? (
        <>
          <Button variant="navy" full onClick={() => onIntroduce(fam)}>Ask us to introduce you to {fam.name}'s family</Button>
          <p style={{ fontSize: 11.5, color: '#6B6659', lineHeight: 1.5, margin: '12px 0 0', textAlign: 'center' }}>Introductions are made by our team, with consent from both families. Your details are never shared until you both say yes.</p>
        </>
      ) : (
        <div style={{ textAlign: 'center', fontSize: 13, color: '#6B6659', padding: 12, background: '#E8E3DA', borderRadius: 5, lineHeight: 1.5 }}>This family is on the map but is not open to introductions right now.</div>
      )}
    </div>
  );
}

// ---- Introduction request dialog (request + confirmation) ----
//
// This asks the coordinator for an introduction; it does not write to the other
// family. Nothing here reaches them, and nothing the requester types is shown
// to them — the coordinator speaks to both families first and connects them
// only once both have agreed. The form therefore collects the minimum needed to
// have that conversation: who they are, where they are, how to reach them, and
// optionally why.
function IntroductionModal({ family, onClose }) {
  const [form, setForm] = React.useState({ name: '', state: '', email: '', note: '', consent: false });
  const [errors, setErrors] = React.useState({});
  const [failed, setFailed] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const [sending, setSending] = React.useState(false);
  const dialogRef = React.useRef(null);
  const restoreRef = React.useRef(null);
  const honeypotRef = React.useRef(null);
  const doneRef = React.useRef(null);
  const fieldRefs = { name: React.useRef(null), state: React.useRef(null), email: React.useRef(null), consent: React.useRef(null) };

  // Purge the legacy undelivered-message store (see FMAP_MSG_KEY).
  React.useEffect(() => {
    try { localStorage.removeItem(FMAP_MSG_KEY); } catch (e) { /* ignore storage errors */ }
  }, []);

  // Focus trap + Escape + restore focus, matching the site's other dialogs.
  // `select` belongs in this list: without it the state dropdown is reachable
  // by mouse but skipped by the Tab cycle, which strands keyboard users on a
  // required field they cannot fill.
  React.useEffect(() => {
    restoreRef.current = document.activeElement;
    const dlg = dialogRef.current;
    const focusable = () => dlg ? Array.from(dlg.querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter(el => !el.disabled && el.offsetParent !== null) : [];
    const els = focusable();
    if (els.length) els[0].focus();
    const onKey = (e) => {
      if (e.key === 'Escape') { onClose(); return; }
      if (e.key === 'Tab') {
        const f = focusable(); if (!f.length) return;
        const first = f[0], last = f[f.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    };
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('keydown', onKey); if (restoreRef.current && restoreRef.current.focus) restoreRef.current.focus(); };
  }, [onClose]);

  // Submitting replaces the form with the confirmation, which unmounts the
  // button that had focus. Without this, focus falls back to the body and a
  // screen-reader user is told nothing about what just happened.
  React.useEffect(() => { if (sent && doneRef.current) doneRef.current.focus(); }, [sent]);

  const set = (patch) => { setForm(f => ({ ...f, ...patch })); setErrors(e => { const next = { ...e }; Object.keys(patch).forEach(k => delete next[k]); return next; }); setFailed(false); };

  const submit = async (e) => {
    if (e && e.preventDefault) e.preventDefault();
    if (sending) return; // the button stays clickable while in flight

    const found = {};
    if (!form.name.trim()) found.name = 'Please tell us your first name.';
    if (!form.state) found.state = 'Please choose your state.';
    if (!form.email.trim()) found.email = 'Please add your email so we can reach you.';
    else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email.trim())) found.email = 'That email address does not look right, please check it.';
    if (!form.consent) found.consent = 'Please tick the box so we know you understand how introductions work.';
    if (Object.keys(found).length) {
      setErrors(found);
      // Send focus to the first field that needs attention, so the error is
      // announced rather than left for someone to hunt for.
      const first = ['name', 'state', 'email', 'consent'].find(k => found[k]);
      const el = first && fieldRefs[first].current;
      if (el && el.focus) el.focus();
      return;
    }

    setSending(true);
    setFailed(false);
    try {
      const res = await fetch(FMAP_INTRO_ENDPOINT, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          to: family.id, toName: family.name,
          name: form.name.trim(), state: form.state, email: form.email.trim(),
          note: form.note.trim(), consent: true,
          // Honeypot: empty for people; the server quietly discards bot fills.
          website: (honeypotRef.current && honeypotRef.current.value) || '',
        }),
      });
      if (!res.ok) {
        // The handler says why in JSON. Keep it: a 500 meaning "not configured"
        // shown as "try again in a moment" reads as a flaky connection, so
        // retrying does the same thing every time and the real cause — a
        // missing environment variable — only turns up in the Vercel logs.
        let reason = '';
        try { const b = await res.json(); reason = (b && b.error) || ''; } catch (err) { /* no JSON body */ }
        throw new Error(reason ? `${reason} (HTTP ${res.status})` : `request-failed (HTTP ${res.status})`);
      }
      // A 2xx is not proof the request reached us. Anything sitting in front of
      // the site can answer instead — a Vercel SSO login wall on a preview
      // deployment does exactly this, returning a 200 HTML page, and the
      // confirmation screen then appears for a request no coordinator will
      // ever see. That is the one failure this whole flow exists to prevent, so
      // require the endpoint's own JSON answer before believing it.
      const type = res.headers.get('content-type') || '';
      if (!type.includes('json')) {
        throw new Error(`intercepted-before-reaching-us: responded ${res.status} as "${type || 'unknown type'}", not JSON`);
      }
      const body = await res.json().catch(() => null);
      if (!body || body.ok !== true) {
        throw new Error('endpoint did not confirm the request was sent');
      }
      setSent(true);
    } catch (err) {
      // Never show the confirmation screen for a request that reached nobody. A
      // family asking to meet another family deserves the truth and a way
      // through, not a comforting lie. The cause is ours, not theirs, so the
      // detail goes to the console and they get a plain apology.
      console.error('[family-map] introduction request did not send:', err);
      setFailed(true);
    } finally {
      setSending(false);
    }
  };

  const inputStyle = { width: '100%', boxSizing: 'border-box', padding: '10px 12px', border: '1px solid #D5CFBF', borderRadius: 5, fontFamily: 'inherit', fontSize: 14, color: '#0D1B2A', background: '#fff' };
  const fieldStyle = (bad) => ({ ...inputStyle, borderColor: bad ? '#8E3B5E' : '#D5CFBF' });
  const labelStyle = { display: 'block', fontSize: 11, fontWeight: 600, color: '#0D1B2A', marginBottom: 6 };
  const errorStyle = { fontSize: 12, lineHeight: 1.45, color: '#8E3B5E', margin: '6px 0 0' };

  return (
    <div className="fmap-overlay" role="presentation" onClick={onClose}
      style={{ position: 'fixed', inset: 0, zIndex: 120, background: 'rgba(13,27,42,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div ref={dialogRef} role="dialog" aria-modal="true" aria-labelledby="fmap-intro-title" onClick={(e) => e.stopPropagation()}
        style={{ background: '#F7F4F0', borderRadius: 8, maxWidth: 520, width: '100%', maxHeight: 'calc(100vh - 48px)', overflowY: 'auto', boxShadow: '0 8px 24px rgba(13,27,42,0.12)' }}>
        <div style={{ background: '#1E3A6E', color: '#F7F4F0', padding: '22px 28px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16 }}>
            <div>
              <div className="eyebrow" style={{ color: '#4A7EC7', marginBottom: 8 }}>Family introductions</div>
              <h3 id="fmap-intro-title" style={{ fontSize: 20, fontWeight: 700, margin: 0, color: '#fff' }}>Ask for an introduction</h3>
              <div style={{ fontSize: 13, color: '#B9C2D6', marginTop: 4 }}>{STATE_NAMES[family.state]} · {family.region}</div>
            </div>
            <button onClick={onClose} aria-label="Close" style={{ background: 'none', border: 'none', color: '#B9C2D6', fontSize: 24, lineHeight: 1, cursor: 'pointer', padding: 0 }}>×</button>
          </div>
        </div>

        {!sent ? (
          <form onSubmit={submit} noValidate style={{ padding: '24px 28px' }}>
            <p style={{ fontSize: 13.5, lineHeight: 1.6, color: '#0D1B2A', margin: '0 0 20px' }}>Tell us who you'd like to meet and a little about your family. Our team will reach out to both families and, if you're both happy, make a warm introduction. We never share your details without your permission.</p>

            <div style={{ marginBottom: 16 }}>
              <label htmlFor="fmap-intro-family" style={labelStyle}>Which family</label>
              {/* Pre-filled and read-only: the family is chosen by the card the
                  request was opened from, and a typed-over value would ask the
                  coordinator to introduce someone nobody selected. */}
              <input id="fmap-intro-family" type="text" readOnly value={`${family.name}'s family`}
                style={{ ...inputStyle, background: '#EFEAE1', color: '#6B6659' }} />
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, marginBottom: 16 }} className="stack-mobile-2">
              <div>
                <label htmlFor="fmap-intro-name" style={labelStyle}>Your first name</label>
                <input id="fmap-intro-name" ref={fieldRefs.name} type="text" value={form.name} required aria-required="true"
                  aria-invalid={errors.name ? 'true' : undefined} aria-describedby={errors.name ? 'fmap-intro-name-error' : undefined}
                  onChange={(e) => set({ name: e.target.value })} placeholder="e.g. Sam" style={fieldStyle(errors.name)} />
                {errors.name && <p id="fmap-intro-name-error" role="alert" style={errorStyle}>{errors.name}</p>}
              </div>
              <div>
                <label htmlFor="fmap-intro-state" style={labelStyle}>Your state</label>
                <select id="fmap-intro-state" ref={fieldRefs.state} value={form.state} required aria-required="true"
                  aria-invalid={errors.state ? 'true' : undefined} aria-describedby={errors.state ? 'fmap-intro-state-error' : undefined}
                  onChange={(e) => set({ state: e.target.value })} style={fieldStyle(errors.state)}>
                  <option value="">Choose…</option>
                  {FMAP_REQUEST_STATES.map(s => <option key={s} value={s}>{s}</option>)}
                </select>
                {errors.state && <p id="fmap-intro-state-error" role="alert" style={errorStyle}>{errors.state}</p>}
              </div>
            </div>

            <div style={{ marginBottom: 16 }}>
              <label htmlFor="fmap-intro-email" style={labelStyle}>Your email <span style={{ fontWeight: 500, color: '#6B6659' }}>(so our coordinator can reach you)</span></label>
              <input id="fmap-intro-email" ref={fieldRefs.email} type="email" value={form.email} required aria-required="true"
                aria-invalid={errors.email ? 'true' : undefined} aria-describedby={errors.email ? 'fmap-intro-email-error' : undefined}
                onChange={(e) => set({ email: e.target.value })} placeholder="you@example.com" style={fieldStyle(errors.email)} />
              {errors.email && <p id="fmap-intro-email-error" role="alert" style={errorStyle}>{errors.email}</p>}
            </div>

            {/* Honeypot: invisible to people, irresistible to bots. */}
            <input ref={honeypotRef} type="text" name="website" tabIndex="-1" autoComplete="off" aria-hidden="true"
              style={{ position: 'absolute', left: -9999, top: 'auto', width: 1, height: 1, overflow: 'hidden' }} />

            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 6 }}>
              <label htmlFor="fmap-intro-note" style={{ fontSize: 11, fontWeight: 600, color: '#0D1B2A' }}>A note for our coordinator <span style={{ fontWeight: 500, color: '#6B6659' }}>(optional)</span></label>
              <span aria-live="polite" style={{ fontSize: 11, color: form.note.length > FMAP_NOTE_MAX - 100 ? '#8E3B5E' : '#6B6659' }}>
                {FMAP_NOTE_MAX - form.note.length} characters left
              </span>
            </div>
            <textarea id="fmap-intro-note" value={form.note} maxLength={FMAP_NOTE_MAX} rows="4"
              onChange={(e) => set({ note: e.target.value.slice(0, FMAP_NOTE_MAX) })}
              placeholder="A little about your family, or what you're hoping to talk about."
              style={{ ...inputStyle, lineHeight: 1.5, resize: 'vertical' }} />

            <label style={{ display: 'flex', alignItems: 'flex-start', gap: 9, margin: '16px 0 0', cursor: 'pointer' }}>
              <input type="checkbox" ref={fieldRefs.consent} checked={form.consent} required aria-required="true"
                aria-invalid={errors.consent ? 'true' : undefined} aria-describedby={errors.consent ? 'fmap-intro-consent-error' : undefined}
                onChange={(e) => set({ consent: e.target.checked })}
                style={{ marginTop: 2, width: 16, height: 16, flexShrink: 0, accentColor: '#1E3A6E' }} />
              <span style={{ fontSize: 12.5, lineHeight: 1.5, color: '#0D1B2A' }}>I understand my details will only be shared with the other family if we both agree to connect.</span>
            </label>
            {errors.consent && <p id="fmap-intro-consent-error" role="alert" style={{ ...errorStyle, marginLeft: 25 }}>{errors.consent}</p>}

            {failed && (
              <div role="alert" style={{ background: '#EFEAE1', border: '1px solid #D5CFBF', borderLeft: '3px solid #8E3B5E', borderRadius: 5, padding: '12px 14px', marginTop: 14 }}>
                <p style={{ fontSize: 12.5, lineHeight: 1.55, color: '#0D1B2A', margin: '0 0 6px' }}>
                  We could not send that just now, so your request has not reached us. Everything you typed is still here, so you can try again in a moment.
                </p>
                <p style={{ fontSize: 12.5, lineHeight: 1.55, color: '#6B6659', margin: 0 }}>
                  If it keeps failing, email <a href={`mailto:${FMAP_SUPPORT_EMAIL}`} style={{ color: '#1E3A6E' }}>{FMAP_SUPPORT_EMAIL}</a> and our team will pick it up from there.
                </p>
              </div>
            )}

            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 22 }}>
              <Button variant="outline" onClick={onClose}>Cancel</Button>
              <Button variant="navy" type="submit" disabled={sending}>{sending ? 'Sending…' : 'Send my request'}</Button>
            </div>
          </form>
        ) : (
          <div ref={doneRef} tabIndex={-1} style={{ padding: '36px 28px', textAlign: 'center', outline: 'none' }}>
            <div style={{ width: 52, height: 52, borderRadius: 999, background: '#E5EDF8', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 18px', color: '#4A7EC7', fontSize: 24, fontWeight: 700 }}>✓</div>
            <h3 style={{ fontSize: 20, fontWeight: 700, margin: '0 0 10px', color: '#0D1B2A' }}>Your request is with us</h3>
            <p style={{ fontSize: 14.5, lineHeight: 1.6, color: '#0D1B2A', margin: '0 auto 22px', maxWidth: '42ch' }}>Thank you — your request is with us. Our team will be in touch soon. We'll reach out to {family.name}'s family too, and only connect you once you're both happy to go ahead. There's no rush and no pressure, this happens at your pace.</p>
            <Button variant="navy" onClick={onClose}>Done</Button>
          </div>
        )}
      </div>
    </div>
  );
}


function FamilyMapPage({ navigate }) {
  const [families, setFamilies] = React.useState([]);
  const [selectedId, setSelectedId] = React.useState(null);
  const [hoverId, setHoverId] = React.useState(null);
  const [focusId, setFocusId] = React.useState(null);
  const [hoverState, setHoverState] = React.useState(null);
  const [filter, setFilter] = React.useState('All');
  const [introFamily, setIntroFamily] = React.useState(null);

  // Load the reviewed families list. Ships empty; the team adds approved
  // records to families.json. Failure (or a missing file) leaves the map empty.
  //
  // An empty map is ambiguous: it means "no families have joined yet" OR "the
  // data never loaded", and the two look identical to a visitor and to us. The
  // second is a live risk here — vercel.json rewrites everything that is not a
  // real file to /_shell.html, so if families.json ever stopped being served
  // statically, this fetch would receive an HTML page, r.json() would throw,
  // and the map would sit at zero for every family with nothing to show for it.
  // Log the difference so it is diagnosable from a browser console.
  React.useEffect(() => {
    let alive = true;
    fetch(FMAP_FAMILIES_URL, { cache: 'no-cache' })
      .then(r => {
        if (!r.ok) throw new Error(`families.json responded ${r.status}`);
        const type = r.headers.get('content-type') || '';
        if (!type.includes('json')) throw new Error(`families.json served as "${type}", not JSON — it is probably being caught by the SPA rewrite in vercel.json`);
        return r.json();
      })
      .then(d => { if (alive) setFamilies(fmapUsable(d)); })
      .catch((err) => {
        if (typeof console !== 'undefined' && console.warn) {
          console.warn(`[family-map] Could not load ${FMAP_FAMILIES_URL}, the map is showing zero families for every state regardless of who has joined. ${err && err.message ? err.message : err}`);
        }
      });
    return () => { alive = false; };
  }, []);

  const startSurvey = (e) => {
    if (e && e.preventDefault) e.preventDefault();
    if (FMAP_SURVEY_URL) window.open(FMAP_SURVEY_URL, '_blank', 'noopener');
    else navigate(FMAP_JOIN_ROUTE); // the on-site combined Map + Registry flow
  };
  const scrollToMap = (e) => {
    if (e && e.preventDefault) e.preventDefault();
    const el = document.getElementById('family-map');
    if (el) el.scrollIntoView({ behavior: 'smooth' });
  };


  // Derived data
  const counts = families.reduce((a, f) => { a[f.state] = (a[f.state] || 0) + 1; return a; }, {});
  const statesWithFamilies = Object.keys(counts).length;
  const visible = families.filter(f => filter === 'All' || f.state === filter);
  const selected = families.find(f => f.id === selectedId) || null;
  const hasFamilies = families.length > 0;

  const stat = (n) => (n === 0 ? '0' : String(n));

  return (
    <div data-screen-label="SCN2A Family Map">

      {/* ============ HERO (navy feature band) ============ */}
      <section style={{ background: '#1E3A6E', color: '#F7F4F0', position: 'relative', overflow: 'hidden' }}>
        <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 6, background: '#8E3B5E', zIndex: 2 }} />
        <NetworkMotif opacity={0.12} stroke="#fff" dense />
        <div className="container" style={{ position: 'relative', zIndex: 2, padding: '72px 32px 64px' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.25fr 1fr', gap: 56, alignItems: 'center' }} className="stack-mobile">
            <div>
              <div className="eyebrow" style={{ color: '#4A7EC7', marginBottom: 20 }}>The SCN2A family map · Australia</div>
              <h1 className="hero-h1-mobile-lg" style={{ fontSize: 'clamp(34px, 4.2vw, 44px)', fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1.12, margin: '0 0 20px', color: '#fff', textWrap: 'balance' }}>You are not the only family. Let's find each other.</h1>
              <p className="hero-sub-mobile" style={{ fontSize: 17, lineHeight: 1.6, color: '#B9C2D6', maxWidth: '52ch', margin: '0 0 28px' }}>SCN2A is rare, and Australian families are spread across a vast country. This map brings us together. Add your family, see how many families are in your state, and reach others near you, privately, on your terms.</p>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center' }}>
                <Button size="lg" variant="mulberry" onClick={startSurvey}>Put your family on the map →</Button>
                <Button size="lg" variant="outlineLight" onClick={scrollToMap}>See the map</Button>
              </div>
              <p style={{ fontSize: 12, color: '#B9C2D6', margin: '18px 0 0', opacity: 0.85 }}>Adding your family takes a short survey. You choose exactly what is shown and who can reach you.</p>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
              <div style={{ background: '#18305B', border: '1px solid #26467F', borderRadius: 8, padding: 22 }}>
                <div style={{ fontSize: 38, fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1, color: '#fff' }}>{stat(families.length)}</div>
                <div style={{ fontSize: 13, color: '#B9C2D6', marginTop: 10, lineHeight: 1.45 }}>Families on the map across Australia.</div>
              </div>
              <div style={{ background: '#18305B', border: '1px solid #26467F', borderRadius: 8, padding: 22 }}>
                <div style={{ fontSize: 38, fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1, color: '#fff' }}>{stat(statesWithFamilies)}</div>
                <div style={{ fontSize: 13, color: '#B9C2D6', marginTop: 10, lineHeight: 1.45 }}>States &amp; territories with families so far.</div>
              </div>
              <div style={{ gridColumn: 'span 2', background: '#18305B', border: '1px solid #26467F', borderRadius: 8, padding: '18px 22px', display: 'flex', alignItems: 'center', gap: 14 }}>
                <span style={{ width: 8, height: 8, borderRadius: 999, background: '#4A7EC7', flexShrink: 0 }} />
                <span style={{ fontSize: 13, color: '#B9C2D6', lineHeight: 1.45 }}>The map starts empty. Every number grows as families add themselves, be one of the first.</span>
              </div>
            </div>
          </div>
        </div>
      </section>

      <Breadcrumbs items={[{ label: 'Support', to: 'support' }, { label: 'Family map' }]} navigate={navigate} />

      {/* ============ MAP + SIDE PANEL ============ */}
      <section id="family-map" style={{ background: '#F7F4F0', padding: '56px 0 32px' }}>
        <div className="container">
          <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 24, marginBottom: 24, flexWrap: 'wrap' }}>
            <div>
              <div className="eyebrow" style={{ color: '#6B6659', marginBottom: 10 }}>Families by state</div>
              <h2 className="h2-mobile" style={{ fontSize: 28, fontWeight: 700, letterSpacing: '-0.01em', margin: 0, color: '#0D1B2A' }}>SCN2A families across Australia</h2>
            </div>
            <div role="group" aria-label="Filter families by state" style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              {STATE_ORDER.map(s => {
                const active = s === filter;
                const count = s === 'All' ? families.length : (counts[s] || 0);
                return (
                  <button key={s} onClick={() => setFilter(s)} aria-pressed={active}
                    style={{ background: active ? '#1E3A6E' : 'transparent', color: active ? '#F7F4F0' : '#0D1B2A', border: `1px solid ${active ? '#1E3A6E' : '#D5CFBF'}`, padding: '7px 13px', borderRadius: 999, fontFamily: 'inherit', fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}>
                    {s === 'All' ? 'All families' : s}<span style={{ opacity: 0.55, fontWeight: 500 }}> · {count}</span>
                  </button>
                );
              })}
            </div>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 380px', gap: 24, alignItems: 'start' }} className="stack-mobile">

            {/* MAP CARD */}
            <div style={{ position: 'relative', background: '#fff', border: '1px solid #D5CFBF', borderRadius: 8, padding: 20, boxShadow: '0 1px 2px rgba(13,27,42,0.06), 0 1px 3px rgba(13,27,42,0.08)' }}>
              <svg viewBox={FMAP_VIEWBOX} style={{ width: '100%', height: 'auto', display: 'block' }} preserveAspectRatio="xMidYMid meet"
                role="img" aria-label={hasFamilies
                  ? `Map of Australia showing ${families.length} SCN2A families across ${statesWithFamilies} states and territories.`
                  : 'Map of Australia. No families are on the map yet, every state shows zero.'}>
                <defs>
                  <linearGradient id="fmapLand" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stopColor="#EDE7DA" />
                    <stop offset="100%" stopColor="#DDD4C2" />
                  </linearGradient>
                  <linearGradient id="fmapLandHover" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stopColor="#EDF1F9" />
                    <stop offset="100%" stopColor="#DBE4F3" />
                  </linearGradient>
                  <radialGradient id="fmapOcean" cx="50%" cy="40%" r="72%">
                    <stop offset="0%" stopColor="#FBFAF7" />
                    <stop offset="100%" stopColor="#F1ECE1" />
                  </radialGradient>
                  <filter id="fmapPinShadow" x="-80%" y="-80%" width="260%" height="260%">
                    <feDropShadow dx="0" dy="2" stdDeviation="1.4" floodColor="#0D1B2A" floodOpacity="0.3" />
                  </filter>
                </defs>
                <rect x="0" y="0" width="1000" height="780" fill="url(#fmapOcean)" aria-hidden="true" />
                {/* state polygons */}
                <g>
                  {FMAP_STATES_GEO.map(st => (
                    <path key={st.abbr} className="fmap-state" d={st.d}
                      fill={hoverState === st.abbr ? 'url(#fmapLandHover)' : 'url(#fmapLand)'}
                      stroke="#1E3A6E" strokeOpacity="0.55" strokeWidth="1.25" strokeLinejoin="round" strokeLinecap="round" vectorEffect="non-scaling-stroke"
                      onMouseEnter={() => setHoverState(st.abbr)} onMouseLeave={() => setHoverState(null)} />
                  ))}
                </g>
                {/* per-state count badges (incl. ACT leader line) */}
                <g>
                  {STATE_ORDER.filter(s => s !== 'All').map(abbr => (
                    <FamilyMapBadge key={abbr} abbr={abbr} count={counts[abbr] || 0} />
                  ))}
                </g>
                {/* family pins: a teardrop marker that drops+bounces in once on mount
                    (new family loaded, or filter reveals nothing new — same DOM node,
                    so it never replays), keyboard-focusable, with hover/selected states. */}
                <g>
                  {families.map((f, i) => {
                    const [x, y] = fmapProject(f.lng, f.lat);
                    const isSel = f.id === selectedId;
                    const isActive = f.id === hoverId || f.id === focusId;
                    const dimmed = filter !== 'All' && f.state !== filter;
                    const color = isSel ? '#8E3B5E' : (isActive ? '#4A7EC7' : '#1E3A6E');
                    const scale = isSel ? 1.16 : (isActive ? 1.06 : 1);
                    const delay = `${Math.min(i, 14) * 40}ms`;
                    return (
                      <g key={f.id} transform={`translate(${x},${y})`} opacity={dimmed ? 0.25 : 1}
                        tabIndex={0} role="button" aria-pressed={isSel} style={{ cursor: 'pointer', outline: 'none' }}
                        aria-label={`${f.name}'s family, ${STATE_NAMES[f.state]}${f.connect ? ', open to introductions' : ''}. Select to view details.`}
                        onClick={() => setSelectedId(f.id)}
                        onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedId(f.id); } }}
                        onMouseEnter={() => setHoverId(f.id)} onMouseLeave={() => setHoverId(null)}
                        onFocus={() => setFocusId(f.id)} onBlur={() => setFocusId(null)}>
                        {isSel && <circle cx="0" cy="0" r="9" fill="#8E3B5E" className="fmap-pulse" aria-hidden="true" />}
                        {isActive && <circle cx="0" cy="-2" r="17" fill="none" stroke="#4A7EC7" strokeWidth="2" strokeDasharray="3 3" aria-hidden="true" />}
                        <g className="fmap-pin-drop" style={{ animationDelay: delay }}>
                          <ellipse className="fmap-pin-shadow" style={{ animationDelay: delay }} cx="0" cy="2.5" rx="7" ry="2.2" fill="#0D1B2A" fillOpacity="0.2" aria-hidden="true" />
                          <g style={{ transform: `scale(${scale})`, transformBox: 'fill-box', transformOrigin: 'center bottom', transition: 'transform 150ms cubic-bezier(0.4,0,0.2,1)' }}>
                            <path d={FMAP_PIN_D} fill={color} stroke="#FFFFFF" strokeWidth="1.5" filter="url(#fmapPinShadow)" />
                            <circle cx="0" cy={FMAP_PIN_HEAD_Y} r="3.6" fill="#FFFFFF" />
                          </g>
                        </g>
                      </g>
                    );
                  })}
                </g>
              </svg>

              {!AU && (
                <div style={{ position: 'absolute', inset: 20, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(247,244,240,0.7)' }}>
                  <span className="eyebrow" style={{ color: '#6B6659' }}>Map unavailable</span>
                </div>
              )}

              <div style={{ marginTop: 8, paddingTop: 12, borderTop: '1px solid #D5CFBF', display: 'flex', flexDirection: 'column', gap: 10 }}>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px 18px', alignItems: 'center' }}>
                  <span className="eyebrow" style={{ color: '#6B6659' }}>Legend</span>
                  {[
                    { color: '#1E3A6E', dashed: false, label: 'Family on the map' },
                    { color: '#8E3B5E', dashed: false, label: 'Selected' },
                  ].map(item => (
                    <span key={item.label} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#0D1B2A' }}>
                      <span aria-hidden="true" style={{ width: 10, height: 10, borderRadius: '10px 10px 10px 0', transform: 'rotate(-45deg)', background: item.dashed ? 'transparent' : item.color, border: item.dashed ? `1.5px dashed ${item.color}` : 'none' }} />
                      {item.label}
                    </span>
                  ))}
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 10 }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 12, color: '#6B6659' }}>
                    <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 18, height: 18, borderRadius: 999, background: '#E8E3DA', border: '1px solid #D5CFBF', fontSize: 9, fontWeight: 800, color: '#1E3A6E' }}>0</span>
                    The number in each state is how many families are on the map there
                  </span>
                  <span style={{ fontSize: 12, color: '#6B6659' }}>{visible.length} {visible.length === 1 ? 'family' : 'families'} shown</span>
                </div>
              </div>
            </div>

            {/* SIDE PANEL */}
            <aside style={{ position: 'sticky', top: 92 }}>
              {selected ? (
                <SelectedFamily fam={selected} onIntroduce={setIntroFamily} />
              ) : (
                <div style={{ background: '#fff', border: '1px solid #D5CFBF', borderRadius: 8, padding: '28px 24px', boxShadow: '0 1px 2px rgba(13,27,42,0.06), 0 1px 3px rgba(13,27,42,0.08)', textAlign: 'center' }}>
                  <div style={{ width: 48, height: 48, borderRadius: 999, background: '#E5EDF8', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px' }}>
                    <i data-lucide="map-pin" style={{ width: 22, height: 22, color: '#1E3A6E' }} />
                  </div>
                  {/* Two different states share this panel, and conflating them
                      told families the map was empty while their own pin was
                      visible beside it. Only claim the map is empty when it
                      actually is; otherwise this is a "nothing selected yet"
                      prompt. */}
                  <h3 style={{ fontSize: 19, fontWeight: 700, letterSpacing: '-0.01em', margin: '0 0 8px', color: '#0D1B2A' }}>
                    {hasFamilies ? 'Choose a family to meet them' : 'No families on the map, yet'}
                  </h3>
                  <p style={{ fontSize: 14, lineHeight: 1.6, color: '#0D1B2A', margin: '0 0 20px' }}>
                    {hasFamilies
                      ? `Select any pin on the map, or a card below, to read that family's story and ask for an introduction if they are open to it.`
                      : 'Be one of the first. Add your family and start the SCN2A Australia community map for everyone who comes after you.'}
                  </p>
                  <Button variant="navy" full onClick={startSurvey}>Add your family</Button>
                  <p style={{ fontSize: 11.5, color: '#6B6659', lineHeight: 1.5, margin: '14px 0 0' }}>
                    {hasFamilies
                      ? 'Your pin is added by our team after review, and shows a first name, state and general area only.'
                      : 'Pins and family details appear here once families begin to join.'}
                  </p>
                  <div style={{ borderTop: '1px solid #D5CFBF', marginTop: 20, paddingTop: 18, textAlign: 'left' }}>
                    <div className="eyebrow" style={{ color: '#6B6659', marginBottom: 8 }}>What you'll share</div>
                    <p style={{ fontSize: 12.5, lineHeight: 1.55, color: '#0D1B2A', margin: '0 0 14px' }}>Here's an example of how your family appears on the map — your name, state, general area, and what you'd like to talk about. Nothing more is ever shown.</p>
                    <div style={{ background: '#E8E3DA', border: '1px solid #D5CFBF', borderRadius: 5, padding: '14px 16px', marginBottom: 14 }}>
                      <div style={{ fontSize: 14, fontWeight: 600, color: '#0D1B2A', marginBottom: 6 }}>Sam's family</div>
                      <div style={{ fontSize: 12.5, color: '#6B6659', marginBottom: 8 }}>Brisbane, Queensland · age 7</div>
                      <p style={{ fontSize: 12.5, lineHeight: 1.55, color: '#0D1B2A', margin: '0 0 8px' }}>We'd love to connect with other families in Queensland who are navigating school and therapy.</p>
                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: 8, borderTop: '1px solid #D5CFBF', fontSize: 11.5 }}>
                        <span style={{ color: '#6B6659' }}>Open to introductions</span>
                        <span style={{ color: '#1E3A6E', fontWeight: 600 }}>View →</span>
                      </div>
                    </div>
                    <Button variant="mulberry" full onClick={startSurvey}>Start the survey →</Button>
                  </div>
                </div>
              )}
            </aside>
          </div>
        </div>
      </section>

      {/* ============ FAMILIES LIST ============ */}
      <section style={{ background: '#F7F4F0', padding: '24px 0 64px' }}>
        <div className="container">
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 16, marginBottom: 22, paddingBottom: 16, borderBottom: '1px solid #D5CFBF', flexWrap: 'wrap' }}>
            <h2 style={{ fontSize: 20, fontWeight: 700, margin: 0, color: '#0D1B2A' }}>{filter === 'All' ? 'Families on the map' : `Families in ${STATE_NAMES[filter]}`}</h2>
            <span style={{ fontSize: 13, color: '#6B6659' }}>Tap a family to see them on the map</span>
          </div>

          {!hasFamilies ? (
            <div style={{ background: '#E8E3DA', border: '1px dashed #D5CFBF', borderRadius: 8, padding: '40px 32px', textAlign: 'center' }}>
              <p style={{ fontSize: 15, lineHeight: 1.6, color: '#0D1B2A', margin: '0 auto 6px', maxWidth: '46ch', fontWeight: 600 }}>Families will appear here as they join.</p>
              <p style={{ fontSize: 14, lineHeight: 1.6, color: '#6B6659', margin: '0 auto 20px', maxWidth: '46ch' }}>Once you complete the survey, your family becomes a card here and a pin on the map above.</p>
              <Button variant="mulberry" onClick={startSurvey}>Add your family →</Button>
            </div>
          ) : (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(270px, 1fr))', gap: 14 }}>
              {visible.map(f => (
                <button key={f.id} className="card-hover" onClick={() => setSelectedId(f.id)}
                  style={{ display: 'flex', flexDirection: 'column', gap: 10, padding: 18, border: '1px solid #D5CFBF', borderRadius: 5, background: '#fff', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left' }}>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                    <span className="eyebrow" style={{ color: '#6B6659' }}>{STATE_NAMES[f.state]}</span>
                    {f.connect && <span style={{ width: 8, height: 8, borderRadius: 999, background: '#4A7EC7' }} />}
                  </div>
                  <div>
                    <div style={{ fontSize: 17, fontWeight: 700, letterSpacing: '-0.01em', color: '#0D1B2A', lineHeight: 1.2 }}>{f.name}'s family</div>
                    <div style={{ fontSize: 13, color: '#6B6659', marginTop: 3 }}>{f.region} · age {fmapAgeFromBirthYear(f.birthYear)}</div>
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: 10, borderTop: '1px solid #D5CFBF', fontSize: 12.5 }}>
                    <span style={{ color: '#6B6659' }}>{f.connect ? 'Open to introductions' : 'Listening only'}</span>
                    <span style={{ color: '#1E3A6E', fontWeight: 600 }}>View →</span>
                  </div>
                </button>
              ))}
            </div>
          )}
        </div>
      </section>

      {/* ============ HOW THE MAP WORKS (sandstone band) ============ */}
      <section style={{ background: '#E8E3DA', padding: '64px 0' }}>
        <div className="container">
          <div className="eyebrow" style={{ color: '#6B6659', marginBottom: 12 }}>How the map works</div>
          <h2 className="h2-mobile" style={{ fontSize: 28, fontWeight: 700, letterSpacing: '-0.01em', margin: '0 0 36px', maxWidth: '24ch', color: '#0D1B2A' }}>Three steps, and you decide every detail.</h2>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }} className="stack-mobile-2">
            {[
              { n: '01', title: 'Fill in the short survey', body: 'Tell us your first name, your state and a little about your family. You choose what appears publicly, your suburb is shown as a general area, never your address.' },
              { n: '02', title: 'We add your pin', body: "Our team reviews each response and places your family on the map in your region. Your state's number ticks up by one. Pins are approximate by design, so no one can be identified by location." },
              { n: '03', title: 'Connect in your state', body: "If you opt in, other families can ask our team for an introduction. We reach out to both families, and connect you only when you're both happy to go ahead. You're always in control, and nothing is shared without your say-so." },
            ].map(s => (
              <div key={s.n} style={{ background: '#fff', border: '1px solid #D5CFBF', borderRadius: 8, padding: 28, borderTop: '3px solid #4A7EC7' }}>
                <div style={{ fontSize: 13, fontWeight: 800, color: '#4A7EC7', marginBottom: 14 }}>{s.n}</div>
                <h3 style={{ fontSize: 17, fontWeight: 700, margin: '0 0 10px', color: '#0D1B2A' }}>{s.title}</h3>
                <p style={{ fontSize: 14.5, lineHeight: 1.6, color: '#0D1B2A', margin: 0 }}>{s.body}</p>
              </div>
            ))}
          </div>
        </div>
      </section>

      {/* ============ BEREAVED FAMILIES (warm canvas) ============ */}
      <section style={{ background: '#F7F4F0', padding: '72px 0' }}>
        <div className="container">
          <div style={{ background: '#E8E3DA', border: '1px solid #D5CFBF', borderTop: '3px solid #4A7EC7', borderRadius: 8, padding: '48px 48px 44px' }} className="pad-mobile">
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 48, alignItems: 'start' }} className="stack-mobile">
              <div>
                <div className="eyebrow" style={{ color: '#4A7EC7', marginBottom: 14 }}>For bereaved families</div>
                <h2 className="h2-mobile" style={{ fontSize: 28, fontWeight: 700, letterSpacing: '-0.01em', lineHeight: 1.2, margin: '0 0 18px', color: '#0D1B2A', textWrap: 'balance' }}>When a family member passes away, you are still part of this community.</h2>
                <p style={{ fontSize: 15.5, lineHeight: 1.7, color: '#0D1B2A', margin: '0 0 16px', maxWidth: '52ch' }}>SCN2A and the DEEs take some of the people we love. If your family has lost a child or another family member, you remain one of us, for as long as you want to be. There is no time limit on grief here, and no expectation on you.</p>
                <p style={{ fontSize: 15.5, lineHeight: 1.7, color: '#0D1B2A', margin: '0 0 24px', maxWidth: '52ch' }}>You can add a tribute on our In Memory page, simply stay in touch, or step back for as long as you need. Whatever feels right for your family is right.</p>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12 }}>
                  <Button variant="navy" onClick={(e) => { if (e && e.preventDefault) e.preventDefault(); navigate('in-memory'); }} icon="heart">Visit the In Memory page</Button>
                  <Button variant="ghost" as="a" href={`mailto:${FMAP_SUPPORT_EMAIL}`} icon="mail">Reach out to us</Button>
                </div>
                <p style={{ fontSize: 12, color: '#6B6659', lineHeight: 1.55, margin: '14px 0 0', maxWidth: '46ch' }}>We are a family-led community rather than a bereavement service, so we will listen and help link you into the right supports. These conversations are gentle and at your pace. Nothing is shared without your say-so.</p>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                {[
                  { icon: 'flower-2', title: 'A place to remember them', body: 'Our In Memory page is separate from the live map. Your family can add a photo and a few words of love for the person you have lost, reviewed gently by our team before anything appears, and changed or removed whenever you ask.' },
                  { icon: 'users', title: 'Meet families who understand', body: "We can introduce you, privately, to other Australian families who have lived this particular loss, only if and when you'd like that." },
                  { icon: 'heart', title: 'Remember their name', body: 'We remember the people of our community. With your permission, we honour their names, at our gatherings and in our work.' },
                ].map(c => (
                  <div key={c.title} style={{ display: 'flex', gap: 16, background: '#fff', border: '1px solid #D5CFBF', borderRadius: 5, padding: '18px 20px' }}>
                    <span style={{ flexShrink: 0, color: '#1E3A6E' }}><i data-lucide={c.icon} style={{ width: 22, height: 22 }} /></span>
                    <div>
                      <h3 style={{ fontSize: 15, fontWeight: 700, margin: '0 0 5px', color: '#0D1B2A' }}>{c.title}</h3>
                      <p style={{ fontSize: 13.5, lineHeight: 1.6, color: '#0D1B2A', margin: 0 }}>{c.body}</p>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </section>

      {/* ============ CTA BAND (navy) ============ */}
      <section style={{ background: '#1E3A6E', color: '#F7F4F0', padding: '64px 0', position: 'relative', overflow: 'hidden' }}>
        <NetworkMotif opacity={0.14} stroke="#E8E3DA" />
        <div className="container" style={{ position: 'relative' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 40, alignItems: 'center' }} className="stack-mobile">
            <div>
              <h2 className="h2-mobile" style={{ fontSize: 'clamp(28px, 3.4vw, 32px)', fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1.1, margin: '0 0 14px', color: '#fff' }}>Are you an SCN2A family in Australia?</h2>
              <p style={{ fontSize: 16, lineHeight: 1.6, color: '#B9C2D6', maxWidth: '50ch', margin: 0 }}>One family's story matters. One hundred families on a map change what's possible, for connection, for research, for advocacy. Add yours.</p>
            </div>
            <div style={{ justifySelf: 'end', display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-start' }}>
              <Button size="lg" variant="mulberry" onClick={startSurvey}>Start the survey →</Button>
              <span style={{ fontSize: 12, color: '#B9C2D6' }}>Private, secure, and reviewed by our team before you appear.</span>
            </div>
          </div>
        </div>
      </section>

      {introFamily && <IntroductionModal family={introFamily} onClose={() => setIntroFamily(null)} />}
    </div>
  );
}

Object.assign(window, { FamilyMapPage });
