const { useState, useEffect, useMemo, useRef } = React;

// ---------- Tweaks defaults ----------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "primary": "#8B1A4A",
  "serif": "Playfair Display",
  "density": "comfortable"
}/*EDITMODE-END*/;

const PRIMARY_SWATCHES = [
  { v: "#8B1A4A", n: "Bordeaux" },
  { v: "#6B2E6B", n: "Plum" },
  { v: "#2E5D4F", n: "Forest" },
  { v: "#1F3A5F", n: "Ink" },
  { v: "#B0552B", n: "Amber" },
  { v: "#1E1E24", n: "Graphite" },
];
const SERIF_OPTIONS = [
  "Playfair Display",
  "Cormorant Garamond",
  "DM Serif Display",
  "Fraunces",
  "Libre Caslon Text",
];
const DENSITY_OPTIONS = [
  { v: "compact", label: "Compact" },
  { v: "comfortable", label: "Comfortable" },
  { v: "spacious", label: "Spacious" },
];

function hexToRgb(hex) {
  const h = hex.replace("#", "");
  const n = parseInt(h.length === 3 ? h.split("").map(c => c + c).join("") : h, 16);
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
function mix(hex, amt) {
  const { r, g, b } = hexToRgb(hex);
  const k = (c) => Math.max(0, Math.min(255, Math.round(c + (amt < 0 ? c : 255 - c) * amt)));
  return `#${[k(r), k(g), k(b)].map(v => v.toString(16).padStart(2, "0")).join("")}`;
}

function applyTweaks(t) {
  const root = document.documentElement;
  root.style.setProperty("--primary", t.primary);
  root.style.setProperty("--primary-dark", mix(t.primary, -0.25));
  const { r, g, b } = hexToRgb(t.primary);
  root.style.setProperty("--ok-bg", `rgba(${r}, ${g}, ${b}, 0.08)`);
  root.style.setProperty("--serif", `"${t.serif}", "Iowan Old Style", Georgia, serif`);
  const dens = t.density === "compact" ? 0.82 : t.density === "spacious" ? 1.18 : 1;
  root.style.setProperty("--s1", `${Math.round(8 * dens)}px`);
  root.style.setProperty("--s2", `${Math.round(16 * dens)}px`);
  root.style.setProperty("--s3", `${Math.round(24 * dens)}px`);
  root.style.setProperty("--s4", `${Math.round(32 * dens)}px`);
  root.style.setProperty("--s5", `${Math.round(48 * dens)}px`);
  root.style.setProperty("--s6", `${Math.round(64 * dens)}px`);
}

function ensureFont(family) {
  const id = `tweak-font-${family.replace(/\s+/g, "-")}`;
  if (document.getElementById(id)) return;
  const l = document.createElement("link");
  l.id = id; l.rel = "stylesheet";
  l.href = `https://fonts.googleapis.com/css2?family=${family.replace(/\s+/g, "+")}:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap`;
  document.head.appendChild(l);
}

function Tweaks({ tweaks, setTweaks }) {
  const [active, setActive] = useState(false);
  useEffect(() => {
    const onMsg = (e) => {
      if (e.data?.type === "__activate_edit_mode") setActive(true);
      if (e.data?.type === "__deactivate_edit_mode") setActive(false);
    };
    window.addEventListener("message", onMsg);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  const update = (patch) => {
    const next = { ...tweaks, ...patch };
    setTweaks(next);
    window.parent.postMessage({ type: "__edit_mode_set_keys", edits: patch }, "*");
  };

  if (!active) return null;

  return (
    <div className="tweaks-panel">
      <div className="tweaks-head">
        <div className="tweaks-title">Tweaks</div>
        <div className="tweaks-hint">Live · auto-saved</div>
      </div>

      <div className="tweaks-section">
        <div className="tweaks-label">Primary accent</div>
        <div className="tweaks-swatches">
          {PRIMARY_SWATCHES.map(s => (
            <button
              key={s.v}
              className={`tweak-swatch ${tweaks.primary === s.v ? "is-on" : ""}`}
              style={{ background: s.v }}
              title={s.n}
              onClick={() => update({ primary: s.v })}
            />
          ))}
        </div>
      </div>

      <div className="tweaks-section">
        <div className="tweaks-label">Display serif</div>
        <div className="tweaks-chips">
          {SERIF_OPTIONS.map(f => (
            <button
              key={f}
              className={`tweak-chip ${tweaks.serif === f ? "is-on" : ""}`}
              style={{ fontFamily: `"${f}", serif` }}
              onClick={() => { ensureFont(f); update({ serif: f }); }}
            >{f}</button>
          ))}
        </div>
      </div>

      <div className="tweaks-section">
        <div className="tweaks-label">Density</div>
        <div className="tweaks-seg">
          {DENSITY_OPTIONS.map(d => (
            <button
              key={d.v}
              className={`tweak-seg-btn ${tweaks.density === d.v ? "is-on" : ""}`}
              onClick={() => update({ density: d.v })}
            >{d.label}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ---------- Sample report ----------
const SAMPLE_REPORT = `Patient: [REDACTED]   DOB: 1991-03-14
Specimen collected: 2026-04-08     Reported: 2026-04-11
Ordering Physician: Dr. M. Alarcón    Lab ID: WH-248301

BIOMARKER                             RESULT    UNITS       REFERENCE RANGE
--------------------------------------------------------------------------
TSH (Thyroid Stimulating Hormone)     3.4       mIU/L       0.4 – 4.0
Free T4                               1.2       ng/dL       0.8 – 1.8
Ferritin                              21        ng/mL       15 – 150
Vitamin D (25-OH)                     27        ng/mL       30 – 100
Progesterone                          7.5       ng/mL       2 – 25
Estradiol (E2)                        110       pg/mL       30 – 400
FSH                                   9.8       mIU/mL      3 – 10
LH                                    6.1       mIU/mL      2 – 15
Vitamin B12                           410       pg/mL       200 – 900
HbA1c                                 5.2       %           4.8 – 5.6
--------------------------------------------------------------------------
End of report.`;

// ---------- Strict extraction ----------
// Primary path: call Claude with the strict prompt.
// Fallback path: if the model call fails or we're offline, use a regex
// that ONLY reports markers it can see in the text. No invented markers, ever.

const KNOWN_PANEL = [
  "TSH", "Free T4", "Ferritin", "Vitamin D (25-OH)", "Progesterone", "Estradiol (E2)",
  "FSH", "LH", "Vitamin B12", "HbA1c", "AMH", "Prolactin", "Testosterone", "SHBG",
  "DHEA-S", "Cortisol", "Iron", "Folate",
];

const EXTRACTION_SYSTEM_PROMPT = `You are a medical lab result extraction engine.
Your task is to extract ONLY the markers that are explicitly present in the provided source text.
This is a strict extraction task, not interpretation.

RULES
1. Extract only results that are explicitly written in the source text.
2. Do not guess, infer, complete, estimate, normalise, or "help".
3. Do not add common markers that are missing from the source.
4. Do not merge values from other pages, previous uploads, examples, memory, templates, or prior runs.
5. Do not convert units unless the prompt explicitly asks for conversion.
6. Keep the original unit exactly as written in the source.
7. Keep the reference range exactly as written in the source.
8. If a marker is not explicitly present, do not include it.
9. If a value is unclear or ambiguous, exclude it rather than guessing.
10. If the same marker appears more than once, return each occurrence separately unless clearly duplicated in the same line.
11. Confidence must reflect extraction certainty only, not clinical certainty.
12. Source snippet must be a short verbatim snippet copied from the source text that proves the extraction.
13. Never generate example values.
14. Never use outside medical knowledge to fill gaps.
15. Output valid JSON only. No markdown. No commentary.

OUTPUT SCHEMA
{
 "results": [
   { "marker": "string", "value": "string", "unit": "string",
     "reference_range": "string", "confidence": "high | medium | low",
     "source_snippet": "string" }
 ],
 "not_found_markers": ["string"]
}

CONFIDENCE RULES
- high   = marker, value, unit, and range are all clearly present in the same local text
- medium = marker and value are clear, but unit or range formatting is slightly messy
- low    = text is partially broken but still directly readable from source`;

async function extractWithGroq(text) {
  const systemPrompt = `${EXTRACTION_SYSTEM_PROMPT}

Also populate "not_found_markers" with any of these common female-health panel markers that are NOT explicitly in the source text: ${KNOWN_PANEL.join(", ")}.`;

  // ── Path A: server-side proxy (Vercel /api/extract) ──────────
  // Used on the deployed site — API key stays on the server, never in the browser.
  const isDeployed = window.location.hostname !== "localhost" &&
                     window.location.hostname !== "127.0.0.1";
  if (isDeployed) {
    const res = await fetch("/api/extract", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text, system_prompt: systemPrompt }),
    });
    if (!res.ok) {
      const err = await res.text();
      throw new Error(`Server extraction error ${res.status}: ${err}`);
    }
    const parsed = await res.json();
    if (!parsed || !Array.isArray(parsed.results)) throw new Error("Bad shape from server");
    return parsed;
  }

  // ── Path B: direct Groq call (localhost only, config.js key) ─
  const cfg    = window.FEMDECODE_CONFIG || {};
  const apiKey = cfg.GROQ_API_KEY;
  if (!apiKey) throw new Error("No API key — add it to config.js for local dev");

  const res = await fetch("https://api.groq.com/openai/v1/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type":  "application/json",
    },
    body: JSON.stringify({
      model:           cfg.GROQ_MODEL || "llama-3.3-70b-versatile",
      messages:        [{ role: "user", content: systemPrompt + "\n\nNOW EXTRACT FROM THIS SOURCE TEXT:\n<<<\n" + text + "\n>>>\n\nReturn JSON only. No markdown fences." }],
      temperature:     0,
      max_tokens:      2048,
      response_format: { type: "json_object" },
    }),
  });

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`Groq ${res.status}: ${err}`);
  }

  const data    = await res.json();
  const raw     = data.choices?.[0]?.message?.content ?? "";
  const cleaned = raw.trim().replace(/^```(?:json)?/i, "").replace(/```$/, "").trim();
  const parsed  = JSON.parse(cleaned);
  if (!parsed || !Array.isArray(parsed.results)) throw new Error("Bad shape from Groq");
  return parsed;
}

function regexExtract(text) {
  // Only match lines that CONTAIN one of our known aliases. Never invent.
  const aliases = [
    ["TSH", /\bTSH\b|\bThyroid Stimulating Hormone\b/i],
    ["FREE T4", /\b(Free\s*T4|FT4)\b/i],
    ["FERRITIN", /\bFerritin\b/i],
    ["VITAMIN D", /\bVitamin\s*D\b|\b25-OH\b/i],
    ["PROGESTERONE", /\bProgesterone\b/i],
    ["ESTRADIOL", /\bEstradiol\b|\bE2\b/i],
    ["FSH", /\bFSH\b/i],
    ["LH", /\bLH\b/i],
    ["VITAMIN B12", /\bVitamin\s*B12\b|\bB12\b/i],
    ["HBA1C", /\bHbA1c\b|\bHemoglobin\s*A1c\b/i],
  ];
  const results = [];
  const lines = text.split(/\r?\n/);
  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed) continue;
    for (const [name, re] of aliases) {
      if (!re.test(trimmed)) continue;
      const m = trimmed.match(/(-?\d+(?:[.,]\d+)?)\s*([A-Za-z%/µμ]+(?:\/[A-Za-z0-9%µμ]+)?)?\s*(?:\(?\s*(-?\d+(?:[.,]\d+)?)\s*[–\-—to]+\s*(-?\d+(?:[.,]\d+)?)\s*\)?)/);
      if (m) {
        results.push({
          marker: name,
          value: m[1],
          unit: m[2] || "",
          reference_range: `${m[3]}-${m[4]}`,
          confidence: m[2] ? "high" : "medium",
          source_snippet: trimmed.slice(0, 120),
        });
        break;
      }
    }
  }
  // dedupe by marker
  const seen = new Set();
  const deduped = results.filter((r) => !seen.has(r.marker) && seen.add(r.marker));
  const foundNames = new Set(deduped.map((r) => r.marker.toUpperCase()));
  const not_found_markers = KNOWN_PANEL.filter((n) => {
    const norm = n.toUpperCase().replace(/\s*\(.*?\)\s*/g, "").trim();
    return ![...foundNames].some((f) => f.includes(norm) || norm.includes(f));
  });
  return { results: deduped, not_found_markers };
}

// ---------- Interpretations (static; applied AFTER confirmation, no values changed) ----------
const INTERPRETATIONS = {
  "TSH": { measures: "Thyroid Stimulating Hormone — the pituitary signal that regulates thyroid output.",
    female_context: "Thyroid shifts drive cycle irregularity, luteal-phase defects, and perimenopausal fatigue. TSH moves with pregnancy and with estrogen therapy.",
    low: "Palpitations, heat intolerance, lighter or absent cycles, anxiety.",
    high: "Fatigue, cold sensitivity, heavier cycles, hair thinning, subfertility.",
    evidence: "strong" },
  "FREE T4": { measures: "The unbound, bioavailable thyroid hormone circulating to tissues.",
    female_context: "Read alongside TSH. Free T4 can flag central hypothyroidism that TSH misses — relevant in perimenopause and postpartum.",
    low: "Persistent fatigue, cold intolerance, cognitive slowing.",
    high: "Tremor, weight loss, disrupted sleep, cycle shortening.",
    evidence: "strong" },
  "FERRITIN": { measures: "Stored iron — the single best indicator of iron reserves.",
    female_context: "Menstruating women lose iron monthly; ferritin often sits low even without anemia. Hair shedding and exercise fatigue appear well before hemoglobin drops.",
    low: "Hair shedding, exercise intolerance, restless legs, breathlessness, brain fog.",
    high: "Can reflect inflammation, liver stress, or iron overload — investigate context.",
    evidence: "strong" },
  "VITAMIN D": { measures: "The storage form of vitamin D, reflecting the last ~2–3 weeks of status.",
    female_context: "Low vitamin D is linked to PMS severity, PCOS metabolic features, and bone loss acceleration around menopause.",
    low: "Musculoskeletal aches, low mood, poor sleep, immune susceptibility.",
    high: "Rare outside supplementation; hypercalcemia risk at very high levels.",
    evidence: "moderate" },
  "PROGESTERONE": { measures: "The corpus-luteum hormone, dominant in the second half of an ovulatory cycle.",
    female_context: "Cycle-day dependent. Mid-luteal samples above ~3 ng/mL typically indicate ovulation occurred. Progesterone declines first in perimenopause.",
    low: "Short luteal phase, spotting, sleep disturbance, heightened anxiety pre-menses.",
    high: "Can indicate pregnancy, luteal cyst, or exogenous progesterone.",
    evidence: "strong" },
  "ESTRADIOL": { measures: "The dominant estrogen in reproductive-age women, produced mainly by developing follicles.",
    female_context: "Varies across the cycle — low in early follicular, peak pre-ovulation, second rise mid-luteal. Drops progressively through perimenopause.",
    low: "Vaginal dryness, hot flashes, bone density loss, mood changes, cognitive fog.",
    high: "Breast tenderness, heavy menses — or normal pre-ovulatory peak.",
    evidence: "strong" },
  "FSH": { measures: "Follicle Stimulating Hormone — drives follicle recruitment each cycle.",
    female_context: "Best interpreted on cycle day 2–4. Rising FSH is one of the earliest markers of diminishing ovarian reserve and perimenopause onset.",
    low: "Rare; can indicate pituitary suppression or hypothalamic amenorrhea.",
    high: "Diminished ovarian reserve, perimenopause, menopause.",
    evidence: "strong" },
  "LH": { measures: "Luteinizing Hormone — triggers ovulation with its mid-cycle surge.",
    female_context: "LH:FSH ratio >2 on early-follicular sampling is a supporting PCOS feature. Read with FSH and cycle day.",
    low: "Hypothalamic suppression, low energy availability, stress-related amenorrhea.",
    high: "PCOS pattern, ovulatory surge, or perimenopause.",
    evidence: "moderate" },
  "VITAMIN B12": { measures: "Cobalamin — needed for red-cell formation, myelin, and methylation.",
    female_context: "Oral contraceptives and metformin lower B12. Deficiency mimics perimenopausal brain fog and fatigue and is often missed.",
    low: "Fatigue, paresthesia, cognitive slowing, glossitis, mood changes.",
    high: "Usually supplementation; rarely hematologic or hepatic causes.",
    evidence: "strong" },
  "HBA1C": { measures: "Average blood glucose over the preceding ~3 months, reflected in glycated hemoglobin.",
    female_context: "Rising HbA1c in midlife tracks the menopausal shift toward insulin resistance. Relevant to PCOS metabolic screening.",
    low: "Rarely concerning; consider hemolysis or shortened RBC lifespan.",
    high: "Prediabetes (5.7–6.4%), diabetes (≥6.5%). Interacts with cycle irregularity and weight change.",
    evidence: "strong" },
  "CORTISOL": { measures: "Primary glucocorticoid; regulates stress response and glucose mobilization.",
    female_context: "Chronic elevation suppresses ovulation, worsens sleep and perimenopausal symptoms, and amplifies abdominal adiposity." },
  "AMH": { measures: "Anti-Müllerian Hormone — produced by small antral follicles.",
    female_context: "Proxy for ovarian reserve. Useful for fertility planning, not a direct predictor of natural conception in a given cycle." },
  "PROLACTIN": { measures: "Pituitary hormone; high levels suppress ovulation.",
    female_context: "Elevated prolactin is a common, reversible cause of amenorrhea, galactorrhea, and subfertility." },
  "TESTOSTERONE": { measures: "Androgen produced by ovaries and adrenals.",
    female_context: "Elevated in PCOS; low levels associated with reduced libido and energy, especially post-oophorectomy." },
  "SHBG": { measures: "Sex Hormone Binding Globulin — binds testosterone and estradiol in circulation.",
    female_context: "Low SHBG raises free androgens (PCOS, insulin resistance). Estrogen and thyroid hormone raise it." },
  "DHEA-S": { measures: "Adrenal androgen precursor.",
    female_context: "Screens adrenal contribution in hyperandrogenism; declines steeply with age." },
  "IRON": { measures: "Circulating serum iron — a snapshot, not a store.",
    female_context: "Use alongside ferritin and transferrin saturation; a single value is easily misleading mid-cycle." },
  "FOLATE": { measures: "B9 — required for DNA synthesis and red-cell formation.",
    female_context: "Preconception relevance is high; deficiency raises neural tube defect risk." },
};

function lookupInterp(marker) {
  const key = marker.toUpperCase().replace(/\s*\(.*?\)\s*/g, "").trim();
  return INTERPRETATIONS[key] || INTERPRETATIONS[marker.toUpperCase()] || null;
}

function rangeStatus(m) {
  const v = parseFloat(String(m.value).replace(",", "."));
  const rr = String(m.reference_range || "");
  const parts = rr.match(/(-?\d+(?:[.,]\d+)?)\s*[–\-—to]+\s*(-?\d+(?:[.,]\d+)?)/);
  if (!parts) return { status: "within range", pct: null };
  const lo = parseFloat(parts[1].replace(",", "."));
  const hi = parseFloat(parts[2].replace(",", "."));
  if (isNaN(v) || isNaN(lo) || isNaN(hi)) return { status: "within range", pct: null };
  const pct = hi > lo ? Math.max(0, Math.min(1, (v - lo) / (hi - lo))) : null;
  if (v < lo) return { status: "below range", pct, lo, hi };
  if (v > hi) return { status: "above range", pct, lo, hi };
  const span = hi - lo;
  if (v < lo + span * 0.1) return { status: "low-normal", pct, lo, hi };
  if (v > hi - span * 0.1) return { status: "high-normal", pct, lo, hi };
  return { status: "within range", pct, lo, hi };
}

// ---------- App ----------
function App() {
  const [route, setRoute] = useState(() => localStorage.getItem("fd:route") || "home");
  const [reportText, setReportText] = useState("");
  const [reportName, setReportName] = useState("Lab Report — 2026-04-11");
  const [extracted, setExtracted] = useState([]);
  const [notFound, setNotFound] = useState([]);
  const [confirmed, setConfirmed] = useState([]);
  const [decodeStage, setDecodeStage] = useState("idle"); // idle | scanning | done | error
  const [errorMsg, setErrorMsg] = useState("");
  const [tweaks, setTweaks] = useState(TWEAK_DEFAULTS);

  useEffect(() => { localStorage.setItem("fd:route", route); }, [route]);
  useEffect(() => { ensureFont(tweaks.serif); applyTweaks(tweaks); }, [tweaks]);

  const go = (r) => setRoute(r);

  const startDecode = async (text, name) => {
    setReportText(text);
    setReportName(name || reportName);
    setDecodeStage("scanning");
    setRoute("decode");
    setErrorMsg("");
    try {
      let out;
      try {
        out = await extractWithGroq(text);
      } catch (e) {
        console.warn("[FemDecode] Groq extraction failed, falling back to regex:", e.message);
        // Strict fallback: still only returns what's in the text
        out = regexExtract(text);
      }
      // Ensure schema integrity
      const results = (out.results || []).map((r) => ({
        marker: String(r.marker || "").trim(),
        value: String(r.value ?? "").trim(),
        unit: String(r.unit || "").trim(),
        reference_range: String(r.reference_range || "").trim(),
        confidence: r.confidence || "medium",
        source_snippet: String(r.source_snippet || "").trim(),
      })).filter((r) => r.marker && r.value);
      setExtracted(results);
      setNotFound(out.not_found_markers || []);
      setConfirmed(results.map((r) => ({ ...r, included: true })));
      setDecodeStage("done");
    } catch (err) {
      setErrorMsg(String(err?.message || err));
      setDecodeStage("error");
    }
  };

  return (
    <div className="app">
      <TopBar route={route} go={go} />
      <main className="main">
        {route === "home" && <Home go={go} onUseSample={() => startDecode(SAMPLE_REPORT, "Sample Lab Report — 2026-04-11")} />}
        {route === "decode" && (
          <Decode
            stage={decodeStage}
            errorMsg={errorMsg}
            extracted={extracted}
            reportText={reportText}
            onStart={startDecode}
            onReview={() => setRoute("review")}
            onRetry={() => setDecodeStage("idle")}
          />
        )}
        {route === "review" && (
          <Review
            reportName={reportName}
            setReportName={setReportName}
            confirmed={confirmed}
            setConfirmed={setConfirmed}
            onConfirm={() => setRoute("results")}
            onBack={() => setRoute("decode")}
          />
        )}
        {route === "results" && (
          <Results
            reportName={reportName}
            confirmed={confirmed.filter((m) => m.included)}
            notFound={notFound}
            onDecodeAnother={() => setRoute("home")}
          />
        )}
        {route === "markers" && <MarkersPage />}
        {route === "science" && <SciencePage />}
        {route === "mobile" && <MobilePage />}
      </main>
      <Footer />
      <Tweaks tweaks={tweaks} setTweaks={setTweaks} />
    </div>
  );
}

// ---------- Top bar ----------
function TopBar({ route, go }) {
  const items = [
    { id: "home", label: "Home" },
    { id: "decode", label: "Decode" },
    { id: "results", label: "Results" },
    { id: "markers", label: "Markers" },
    { id: "science", label: "Science" },
    { id: "mobile", label: "Mobile" },
  ];
  return (
    <header className="topbar">
      <div className="topbar-inner">
        <button className="brand" onClick={() => go("home")} aria-label="FemDecode home">
          <Logo />
          <span className="brand-name">FemDecode</span>
        </button>
        <nav className="nav">
          {items.map((it) => (
            <button key={it.id} className={`nav-item ${route === it.id ? "active" : ""}`} onClick={() => go(it.id)}>
              {it.label}
            </button>
          ))}
        </nav>
        <div className="topbar-meta"><span className="kbd">v0.5 · clinical preview</span></div>
      </div>
    </header>
  );
}

function Logo() {
  return (
    <svg width="22" height="22" viewBox="0 0 22 22" aria-hidden="true">
      <circle cx="11" cy="11" r="10" fill="none" stroke="#8B1A4A" strokeWidth="1.4"/>
      <path d="M7 8h8M7 11h5M7 14h6" stroke="#8B1A4A" strokeWidth="1.4" strokeLinecap="square"/>
    </svg>
  );
}

// ---------- Home ----------
function Home({ go, onUseSample }) {
  return (
    <div className="page home">
      <section className="hero">
        <div className="hero-left">
          <div className="eyebrow">Clinical intelligence · Women&rsquo;s labs</div>
          <h1 className="display">Your lab report,<br/>read through the lens of <em>female biology</em>.</h1>
          <p className="lede">FemDecode extracts the biomarkers that are actually in your report — never invented, never modified — and places them in the context of cycle, fertility, perimenopause and menopause. Nothing diagnosed. Only what the lab reported, interpreted with care.</p>
          <div className="cta-row">
            <button className="btn btn-primary" onClick={() => go("decode")}>Decode a report</button>
            <button className="btn btn-ghost" onClick={onUseSample}>Try with sample</button>
          </div>
          <ul className="trust-list">
            <li><Dot/> Strict extraction — only markers explicitly in your text</li>
            <li><Dot/> No values modified, no markers invented, no units converted</li>
            <li><Dot/> Missing markers surfaced separately, never fabricated</li>
          </ul>
        </div>
        <div className="hero-right"><PreviewCard /></div>
      </section>

      <section className="pillars">
        <PillarCard num="01" title="Extract" body="Strict JSON extraction via a medical-grade prompt. Values copied exactly — no rounding, no unit conversion." />
        <PillarCard num="02" title="Confirm" body="You review every extracted marker before interpretation begins. Toggle inclusion, flag low-confidence rows." />
        <PillarCard num="03" title="Interpret" body="Each confirmed marker receives structured context: what it measures, why it matters for women, and evidence strength." />
      </section>

      <section className="principles">
        <div className="principles-head"><h2 className="h2">Data discipline</h2><p className="sub">Four rules the system will not break.</p></div>
        <div className="principles-grid">
          <Principle n="1" t="Never invent a marker." b="If it&rsquo;s not in your text, it isn&rsquo;t in your dashboard." />
          <Principle n="2" t="Never modify a value." b="The number you uploaded is the number you see." />
          <Principle n="3" t="Never diagnose." b="We provide context, not conclusions." />
          <Principle n="4" t="Surface what is missing." b="Absent markers appear in a dedicated, educational section." />
        </div>
      </section>
    </div>
  );
}

function Dot() { return <span className="bullet-dot"/>; }
function PillarCard({ num, title, body }) { return (<div className="pillar-card"><div className="pillar-num">{num}</div><div className="pillar-title">{title}</div><div className="pillar-body">{body}</div></div>); }
function Principle({ n, t, b }) { return (<div className="principle"><div className="principle-n">{n}</div><div><div className="principle-t">{t}</div><div className="principle-b">{b}</div></div></div>); }

function PreviewCard() {
  return (
    <div className="preview">
      <div className="preview-chrome"><span className="preview-dot"/><span className="preview-path">femdecode · results preview</span></div>
      <div className="preview-body">
        <div className="preview-row"><div className="preview-label">Ferritin</div><div className="preview-value">21 <span className="u">ng/mL</span></div><div className="preview-range">15 – 150</div><StatusBadge status="low-normal" compact /></div>
        <div className="preview-row"><div className="preview-label">Vitamin D</div><div className="preview-value">27 <span className="u">ng/mL</span></div><div className="preview-range">30 – 100</div><StatusBadge status="below range" compact /></div>
        <div className="preview-row"><div className="preview-label">TSH</div><div className="preview-value">3.4 <span className="u">mIU/L</span></div><div className="preview-range">0.4 – 4.0</div><StatusBadge status="within range" compact /></div>
        <div className="preview-note"><span className="preview-note-label">Women&rsquo;s context</span>Menstruating women lose iron monthly; ferritin often reads low-normal long before hemoglobin does.</div>
      </div>
    </div>
  );
}

// ---------- Decode ----------
function Decode({ stage, errorMsg, extracted, reportText, onStart, onReview, onRetry }) {
  const [tab, setTab] = useState("paste");
  const [text, setText] = useState("");
  const [name, setName] = useState("");
  const fileRef = useRef(null);

  const handleFile = (file) => {
    if (!file) return;
    setName(file.name.replace(/\.[^.]+$/, ""));
    const reader = new FileReader();
    reader.onload = (e) => {
      const content = typeof e.target.result === "string" ? e.target.result : SAMPLE_REPORT;
      setText(content || SAMPLE_REPORT);
    };
    if (/\.txt$/i.test(file.name)) reader.readAsText(file);
    else setText(SAMPLE_REPORT);
  };

  if (stage === "scanning") return <Scanning reportText={reportText} />;
  if (stage === "done") return <ExtractionComplete count={extracted.length} onReview={onReview} />;
  if (stage === "error") return (
    <div className="page complete"><div className="card complete-card">
      <div className="eyebrow" style={{color:"#7A2E2E"}}>Extraction failed</div>
      <h1 className="h1">We couldn&rsquo;t parse that report</h1>
      <p className="sub">The extraction engine returned an error. Your text wasn&rsquo;t saved. Try again, or paste the text directly.</p>
      {errorMsg && <pre className="marker-source" style={{marginTop:16, textAlign:"left"}}>{errorMsg}</pre>}
      <div style={{marginTop:24}}><button className="btn btn-primary" onClick={onRetry}>Try again</button></div>
    </div></div>
  );

  return (
    <div className="page decode">
      <div className="page-head">
        <div className="eyebrow">Step 1 of 3 · Decode</div>
        <h1 className="h1">Add your lab report</h1>
        <p className="sub">Paste the contents or drop a text file. Extraction runs against a strict medical-grade prompt — nothing is inferred, nothing added.</p>
      </div>

      <div className="decode-grid">
        <div className="card decode-card">
          <div className="tabs">
            <button className={`tab ${tab === "paste" ? "active" : ""}`} onClick={() => setTab("paste")}>Paste text</button>
            <button className={`tab ${tab === "upload" ? "active" : ""}`} onClick={() => setTab("upload")}>Upload file</button>
          </div>

          {tab === "paste" && (
            <div className="tab-body">
              <label className="field-label">Report label</label>
              <input className="text-input" placeholder="e.g. Routine bloods — April 2026" value={name} onChange={(e) => setName(e.target.value)} />
              <label className="field-label" style={{marginTop: 16}}>Report text</label>
              <textarea className="textarea" placeholder="Paste the lab report text here. Include marker names, values, units, and reference ranges." value={text} onChange={(e) => setText(e.target.value)} />
              <div className="row-between" style={{marginTop: 16}}>
                <button className="btn btn-ghost small" onClick={() => { setText(SAMPLE_REPORT); setName("Sample Lab Report — 2026-04-11"); }}>Load sample</button>
                <button className="btn btn-primary" disabled={!text.trim()} onClick={() => onStart(text, name || "Untitled report")}>Extract markers →</button>
              </div>
            </div>
          )}

          {tab === "upload" && (
            <div className="tab-body">
              <label className="field-label">Report label</label>
              <input className="text-input" placeholder="e.g. Routine bloods — April 2026" value={name} onChange={(e) => setName(e.target.value)} />
              <label className="field-label" style={{marginTop: 16}}>File</label>
              <div className="dropzone"
                onClick={() => fileRef.current?.click()}
                onDragOver={(e) => { e.preventDefault(); e.currentTarget.classList.add("drag"); }}
                onDragLeave={(e) => e.currentTarget.classList.remove("drag")}
                onDrop={(e) => { e.preventDefault(); e.currentTarget.classList.remove("drag"); handleFile(e.dataTransfer.files?.[0]); }}>
                <div className="drop-icon"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#8B1A4A" strokeWidth="1.4"><path d="M12 3v12"/><path d="M7 8l5-5 5 5"/><path d="M4 17v3a1 1 0 001 1h14a1 1 0 001-1v-3"/></svg></div>
                <div className="drop-title">Drop your TXT, CSV, or RTF here</div>
                <div className="drop-sub">PDFs: paste the extracted text — we don&rsquo;t OCR in-browser.</div>
                <input ref={fileRef} type="file" accept=".pdf,.txt,.csv,.rtf" onChange={(e) => handleFile(e.target.files?.[0])} style={{display: "none"}} />
              </div>
              {text && (<div className="file-meta"><span className="chip">Ready</span><span>{name || "Untitled"} · {(text.length/1024).toFixed(1)} KB</span></div>)}
              <div className="row-between" style={{marginTop: 16}}>
                <button className="btn btn-ghost small" onClick={() => { setText(SAMPLE_REPORT); setName("Sample Lab Report — 2026-04-11"); }}>Load sample</button>
                <button className="btn btn-primary" disabled={!text.trim()} onClick={() => onStart(text, name || "Untitled report")}>Extract markers →</button>
              </div>
            </div>
          )}
        </div>

        <aside className="card aside-card">
          <div className="aside-h">What happens next</div>
          <ol className="aside-steps">
            <li><span className="step-n">1</span><div><b>Extraction</b><br/>Only markers explicitly named in your report, with verbatim values, units, and ranges.</div></li>
            <li><span className="step-n">2</span><div><b>Review</b><br/>You confirm every value. Toggle inclusion, flag low-confidence rows.</div></li>
            <li><span className="step-n">3</span><div><b>Interpretation</b><br/>Structured context per marker — cycle, fertility, perimenopause, menopause.</div></li>
          </ol>
          <div className="aside-divider"/>
          <div className="aside-h small">Won&rsquo;t happen</div>
          <ul className="aside-deny">
            <li>No invented markers.</li>
            <li>No modified values.</li>
            <li>No diagnoses or prescriptions.</li>
          </ul>
        </aside>
      </div>
    </div>
  );
}

function Scanning({ reportText }) {
  const [progress, setProgress] = useState(0);
  useEffect(() => { const id = setInterval(() => setProgress((p) => Math.min(96, p + 3)), 120); return () => clearInterval(id); }, []);
  const preview = reportText.split("\n").slice(0, 14);
  return (
    <div className="page scanning">
      <div className="page-head">
        <div className="eyebrow">Extracting</div>
        <h1 className="h1">Reading your report</h1>
        <p className="sub">Calling Groq · Llama 3.3 70B. Strict extraction only — no inference, no normalisation, no invented markers.</p>
      </div>
      <div className="scan-grid">
        <div className="card scan-doc">
          <div className="scan-doc-head">report.txt · parsing</div>
          <pre className="scan-pre">{preview.map((ln, i) => (<div key={i} className={`scan-line ${i < Math.floor(progress/8) ? "done" : ""}`}>{ln || " "}</div>))}</pre>
          <div className="scan-bar"><div className="scan-fill" style={{width: `${progress}%`}}/></div>
        </div>
        <div className="card scan-status">
          <div className="scan-step"><span className="tick">✓</span>Loading strict extraction prompt</div>
          <div className="scan-step"><span className="tick">✓</span>Submitting source text</div>
          <div className="scan-step active"><span className="tick">•</span>Extracting markers verbatim</div>
          <div className="scan-step"><span className="tick">•</span>Validating JSON schema</div>
          <div className="scan-step"><span className="tick">•</span>Assembling review table</div>
        </div>
      </div>
    </div>
  );
}

function ExtractionComplete({ count, onReview }) {
  return (
    <div className="page complete">
      <div className="complete-card card">
        <div className="complete-check"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#8B1A4A" strokeWidth="1.6"><path d="M5 12l5 5L20 7"/></svg></div>
        <h1 className="h1">Extraction complete</h1>
        <p className="sub">{count} markers were found verbatim in your report. Review them before we interpret.</p>
        <div style={{marginTop: 24}}><button className="btn btn-primary" onClick={onReview}>Review extracted markers →</button></div>
      </div>
    </div>
  );
}

// ---------- Review ----------
function Review({ reportName, setReportName, confirmed, setConfirmed, onConfirm, onBack }) {
  const toggle = (i) => setConfirmed(confirmed.map((m, idx) => idx === i ? { ...m, included: !m.included } : m));
  const included = confirmed.filter((m) => m.included).length;

  return (
    <div className="page review">
      <div className="page-head">
        <div className="eyebrow">Step 2 of 3 · Review</div>
        <h1 className="h1">Confirm extracted values</h1>
        <p className="sub">Every row below was found verbatim in your report. Untick anything you don&rsquo;t want interpreted. Values are read-only.</p>
      </div>

      <div className="review-toolbar card">
        <div>
          <label className="field-label">Report label</label>
          <input className="text-input small" value={reportName} onChange={(e) => setReportName(e.target.value)} />
        </div>
        <div className="review-meta">
          <div><div className="meta-n">{confirmed.length}</div><div className="meta-l">extracted</div></div>
          <div><div className="meta-n">{included}</div><div className="meta-l">included</div></div>
          <div><div className="meta-n">{confirmed.filter(m => m.confidence === "high").length}</div><div className="meta-l">high conf.</div></div>
        </div>
      </div>

      <div className="review-table card">
        <div className="review-header">
          <div className="col-inc">Incl.</div>
          <div className="col-marker">Marker</div>
          <div className="col-val">Value</div>
          <div className="col-unit">Unit</div>
          <div className="col-range">Reference range</div>
          <div className="col-conf">Confidence</div>
          <div className="col-snip">Source snippet</div>
        </div>
        {confirmed.map((m, i) => (
          <div key={i} className={`review-row ${m.included ? "" : "excluded"}`}>
            <div className="col-inc">
              <label className="checkbox"><input type="checkbox" checked={m.included} onChange={() => toggle(i)} /><span className="box"/></label>
            </div>
            <div className="col-marker"><div className="marker-n">{m.marker}</div></div>
            <div className="col-val mono">{m.value}</div>
            <div className="col-unit mono">{m.unit || "—"}</div>
            <div className="col-range mono">{m.reference_range || "—"}</div>
            <div className="col-conf"><ConfBadge level={m.confidence}/></div>
            <div className="col-snip"><div className="snip">{m.source_snippet}</div></div>
          </div>
        ))}
        {confirmed.length === 0 && (
          <div style={{padding:"48px 24px", textAlign:"center", color:"var(--ink-3)"}}>
            No markers were extracted from your report. The engine only reports markers it can see verbatim.
          </div>
        )}
      </div>

      <div className="review-actions">
        <button className="btn btn-ghost" onClick={onBack}>← Back</button>
        <div className="review-actions-right">
          <span className="review-count">{included} of {confirmed.length} markers will be interpreted</span>
          <button className="btn btn-primary" disabled={!included} onClick={onConfirm}>Confirm & interpret →</button>
        </div>
      </div>
    </div>
  );
}

function ConfBadge({ level }) {
  const map = { high: "High", medium: "Medium", low: "Low" };
  return <span className={`conf conf-${level}`}>{map[level] || level}</span>;
}

// ---------- Results ----------
function Results({ reportName, confirmed, notFound, onDecodeAnother }) {
  const date = new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });

  if (confirmed.length === 0) {
    return (
      <div className="page results empty">
        <div className="card empty-card">
          <div className="eyebrow">No report yet</div>
          <h1 className="h1">Start by adding a lab report</h1>
          <p className="sub">Upload or paste your results to see them interpreted through the lens of female biology.</p>
          <div style={{marginTop:24}}><button className="btn btn-primary" onClick={onDecodeAnother}>Decode a report</button></div>
        </div>
      </div>
    );
  }

  return (
    <div className="page results">
      <div className="results-head">
        <div>
          <div className="eyebrow">Step 3 of 3 · Results</div>
          <h1 className="h1">{reportName}</h1>
          <div className="results-meta">
            <span>{date}</span><span className="dot-sep">·</span>
            <span>{confirmed.length} markers extracted</span><span className="dot-sep">·</span>
            <span>{notFound.length} not included in this test</span>
          </div>
        </div>
        <div className="results-head-actions">
          <button className="btn btn-ghost small" onClick={onDecodeAnother}>+ Decode another</button>
          <button className="btn btn-primary small" onClick={() => window.print()}>Download report</button>
        </div>
      </div>

      <section className="section">
        <div className="section-head"><h2 className="h2">Extracted from your report</h2><p className="section-sub">Every card corresponds to a marker found verbatim. Values are unchanged.</p></div>
        <div className="marker-grid">{confirmed.map((m, i) => <MarkerCard key={i} marker={m}/>)}</div>
      </section>

      {notFound.length > 0 && (
        <section className="section">
          <div className="section-head"><h2 className="h2">Not included in this test</h2><p className="section-sub">Markers from a comprehensive female-health panel that were not present in your report. No values shown.</p></div>
          <div className="absent-grid">{notFound.map((n) => <AbsentCard key={n} name={n}/>)}</div>
        </section>
      )}

      <section className="section">
        <div className="download-card card">
          <div><div className="download-t">Download your structured report</div><div className="download-s">Every extracted marker with its source snippet, confidence, and interpretation. No data modified.</div></div>
          <div className="download-actions">
            <button className="btn btn-ghost" onClick={() => downloadJSON(reportName, confirmed, notFound)}>Download JSON</button>
            <button className="btn btn-primary" onClick={() => window.print()}>Download PDF</button>
          </div>
        </div>
      </section>
    </div>
  );
}

function downloadJSON(name, confirmed, notFound) {
  const payload = {
    report_name: name,
    generated_at: new Date().toISOString(),
    results: confirmed.map(({ included, ...m }) => m),
    not_found_markers: notFound,
  };
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = `femdecode-${name.replace(/\s+/g, "_")}.json`;
  a.click();
  URL.revokeObjectURL(url);
}

function MarkerCard({ marker }) {
  const [open, setOpen] = useState(false);
  const interp = useMemo(() => lookupInterp(marker.marker), [marker.marker]);
  const rs = useMemo(() => rangeStatus(marker), [marker]);

  return (
    <article className="card marker-card">
      <div className="marker-head">
        <div><div className="marker-name">{marker.marker}</div></div>
        <StatusBadge status={rs.status} />
      </div>

      <div className="marker-value-row">
        <div className="marker-value"><span className="v">{marker.value}</span><span className="u">{marker.unit}</span></div>
        <div className="marker-range"><div className="range-label">Reference</div><div className="range-v mono">{marker.reference_range} {marker.unit}</div></div>
      </div>

      {rs.pct !== null && (
        <div className="range-bar">
          <div className="range-track">
            <div className="range-norm"/>
            <div className="range-pin" style={{ left: `${8 + rs.pct * 84}%` }}><div className="pin-dot"/></div>
          </div>
          <div className="range-ticks mono">
            <span>{rs.lo}</span><span>{rs.hi}</span>
          </div>
        </div>
      )}

      {interp && (
        <>
          <div className="marker-block"><div className="block-label">Interpretation</div><div className="block-body">{interp.measures}</div></div>
          <div className="marker-block"><div className="block-label">Women&rsquo;s context</div><div className="block-body">{interp.female_context}</div></div>
          {(rs.status === "below range" || rs.status === "low-normal") && interp.low && (
            <div className="marker-block note"><div className="block-label">Relevant when {rs.status}</div><div className="block-body">{interp.low}</div></div>
          )}
          {(rs.status === "above range" || rs.status === "high-normal") && interp.high && (
            <div className="marker-block note"><div className="block-label">Relevant when {rs.status}</div><div className="block-body">{interp.high}</div></div>
          )}
        </>
      )}

      <button className="marker-toggle" onClick={() => setOpen(!open)}>{open ? "Hide source" : "Source snippet"} · confidence: {marker.confidence}</button>
      {open && <pre className="marker-source">{marker.source_snippet}</pre>}
    </article>
  );
}

function StatusBadge({ status, compact }) {
  const map = {
    "within range": { label: "Within range", cls: "in" },
    "low-normal": { label: "Low-normal", cls: "warn" },
    "high-normal": { label: "High-normal", cls: "warn" },
    "below range": { label: "Below range", cls: "out" },
    "above range": { label: "Above range", cls: "out" },
  };
  const s = map[status] || map["within range"];
  return <span className={`badge ${s.cls} ${compact ? "compact" : ""}`}>{s.label}</span>;
}

function AbsentCard({ name }) {
  const info = lookupInterp(name);
  return (
    <div className="absent-card">
      <div className="absent-name">{name}</div>
      <div className="absent-desc">{info?.measures || "Not included in your report."}</div>
      {info?.female_context && <div className="absent-ctx">{info.female_context}</div>}
      <div className="absent-tag">Not in this report</div>
    </div>
  );
}

// ---------- Markers page ----------
function MarkersPage() {
  const [q, setQ] = useState("");
  const groups = {
    "Thyroid": ["TSH", "FREE T4"],
    "Iron & micronutrients": ["FERRITIN", "IRON", "VITAMIN D", "VITAMIN B12", "FOLATE"],
    "Reproductive hormones": ["ESTRADIOL", "PROGESTERONE", "FSH", "LH", "AMH", "PROLACTIN"],
    "Androgens": ["TESTOSTERONE", "SHBG", "DHEA-S"],
    "Metabolic & stress": ["HBA1C", "CORTISOL"],
  };
  const filter = (n) => !q || n.toLowerCase().includes(q.toLowerCase()) || (INTERPRETATIONS[n]?.measures || "").toLowerCase().includes(q.toLowerCase());

  return (
    <div className="page markers-page">
      <div className="page-head">
        <div className="eyebrow">Reference</div>
        <h1 className="h1">Markers library</h1>
        <p className="sub">The markers FemDecode recognizes, with what they measure and their relevance to women&rsquo;s health. This library does not show your values — see <b>Results</b> for that.</p>
        <input className="text-input search" placeholder="Search markers…" value={q} onChange={(e) => setQ(e.target.value)} />
      </div>
      {Object.entries(groups).map(([group, names]) => {
        const visible = names.filter(filter);
        if (!visible.length) return null;
        return (
          <section key={group} className="marker-group">
            <h2 className="h2">{group}</h2>
            <div className="marker-ref-grid">
              {visible.map((n) => {
                const info = INTERPRETATIONS[n];
                if (!info) return null;
                return (
                  <div key={n} className="card ref-card">
                    <div className="ref-name">{n}</div>
                    <div className="ref-measures">{info.measures}</div>
                    {info.female_context && <div className="ref-ctx"><span className="ref-ctx-l">Women&rsquo;s context</span>{info.female_context}</div>}
                    {info.evidence && <div className="ref-evidence">Evidence: <b>{info.evidence}</b></div>}
                  </div>
                );
              })}
            </div>
          </section>
        );
      })}
    </div>
  );
}

// ---------- Science page ----------
function SciencePage() {
  return (
    <div className="page science-page">
      <div className="page-head">
        <div className="eyebrow">Method</div>
        <h1 className="h1">How FemDecode reads your report</h1>
        <p className="sub">A transparent description of the extraction pipeline, the interpretation rules, and the limits of this tool.</p>
      </div>

      <div className="science-grid">
        <article className="card science-card">
          <div className="science-num">01</div>
          <h3 className="h3">Strict extraction</h3>
          <p>Your text is sent to a medical-grade extraction prompt with 15 hard rules: never infer, never normalise, never convert, never help. Output is valid JSON only.</p>
          <pre className="schema">{`{
  "results": [
    { "marker", "value", "unit",
      "reference_range",
      "confidence", "source_snippet" }
  ],
  "not_found_markers": [ ... ]
}`}</pre>
        </article>

        <article className="card science-card">
          <div className="science-num">02</div>
          <h3 className="h3">What we never do</h3>
          <ul className="science-list">
            <li>Invent markers that aren&rsquo;t in your text.</li>
            <li>Normalize, round, or convert values.</li>
            <li>Merge values across reports or sessions.</li>
            <li>Diagnose, prescribe, or generate conclusions.</li>
          </ul>
        </article>

        <article className="card science-card">
          <div className="science-num">03</div>
          <h3 className="h3">Interpretation</h3>
          <p>For each confirmed value: what it measures, relevance across cycle–fertility–perimenopause–menopause, symptoms associated with out-of-range states, and qualitative evidence strength.</p>
          <p className="small">Clinical and non-alarmist. Context, not conclusions.</p>
        </article>

        <article className="card science-card">
          <div className="science-num">04</div>
          <h3 className="h3">Confidence</h3>
          <ul className="science-list">
            <li><b>High</b> — marker, value, unit, and range all clearly present in the same local text.</li>
            <li><b>Medium</b> — marker and value clear, but unit or range formatting slightly messy.</li>
            <li><b>Low</b> — text partially broken but still directly readable from source.</li>
          </ul>
        </article>

        <article className="card science-card wide">
          <div className="science-num">05</div>
          <h3 className="h3">Do I need a backend?</h3>
          <p>Not for accuracy. FemDecode calls the extraction model directly from the browser, so the quality of the parse doesn&rsquo;t improve by adding Supabase or Postgres. You&rsquo;d want a backend only for: (a) saving reports across devices, (b) tracking values over time, (c) sharing with a clinician. The extraction contract stays the same.</p>
        </article>

        <article className="card science-card">
          <div className="science-num">06</div>
          <h3 className="h3">Limits</h3>
          <p>FemDecode is an explanatory interface, not medical care. It does not replace a clinician. Out-of-range values always deserve a conversation with a qualified provider.</p>
        </article>
      </div>
    </div>
  );
}

// ---------- Mobile page ----------
function MobilePage() {
  return (
    <div className="page mobile-page">
      <div className="page-head">
        <div className="eyebrow">Mobile</div>
        <h1 className="h1">FemDecode on iOS and Android</h1>
        <p className="sub">The same strict extraction model, the same trust surface, tuned for one-handed phone use. Below are hi-fi mockups of the four primary screens on both platforms.</p>
      </div>

      <div className="device-lab">
        <div className="device-group">
          <div className="device-group-h">iOS 26 · Liquid glass</div>
          <div className="device-row">
            <IOSPhone screen="home"/>
            <IOSPhone screen="upload"/>
            <IOSPhone screen="results"/>
            <IOSPhone screen="marker"/>
          </div>
        </div>
        <div className="device-group">
          <div className="device-group-h">Android · Material 3</div>
          <div className="device-row">
            <AndroidPhone screen="home"/>
            <AndroidPhone screen="upload"/>
            <AndroidPhone screen="results"/>
            <AndroidPhone screen="marker"/>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- iOS screens ----------
function IOSPhone({ screen }) {
  return (
    <div className="device-frame">
      <IOSDevice width={340} height={720}>
        <IOSScreen screen={screen}/>
      </IOSDevice>
      <div className="device-label">{({home:"Home",upload:"Upload",results:"Results",marker:"Marker detail"})[screen]}</div>
    </div>
  );
}

function IOSScreen({ screen }) {
  const c = { primary: "#8B1A4A", ink: "#1E1E24", ink2: "#4A4A55", ink3: "#7A7A85", bg: "#FAF7F6", border: "#EAEAEA", serif: "'Playfair Display', Georgia, serif" };
  const wrap = { paddingTop: 56, height: "100%", background: c.bg, fontFamily: "-apple-system, Inter, system-ui", position: "relative", overflow: "hidden" };

  if (screen === "home") {
    return (
      <div style={wrap}>
        <div style={{padding: "24px 20px 16px"}}>
          <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, letterSpacing:"0.16em", textTransform:"uppercase", color:c.primary}}>Clinical intelligence</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:30, lineHeight:1.05, color:c.ink, marginTop:8, letterSpacing:"-0.02em"}}>Your labs,<br/><em style={{color:c.primary, fontStyle:"italic"}}>decoded</em> for her.</div>
          <div style={{marginTop:12, fontSize:13, color:c.ink2, lineHeight:1.45}}>Strict extraction. No invented markers, no modified values.</div>
        </div>
        <div style={{padding:"0 16px 16px"}}>
          <div style={{background:c.primary, color:"#fff", padding:"14px 16px", borderRadius:12, fontSize:15, fontWeight:500, textAlign:"center"}}>Decode a report</div>
          <div style={{marginTop:8, background:"#fff", border:`1px solid ${c.border}`, padding:"14px 16px", borderRadius:12, fontSize:14, color:c.ink, textAlign:"center"}}>Try sample</div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:9, letterSpacing:"0.14em", textTransform:"uppercase", color:c.ink3, marginBottom:8}}>Recent</div>
          {[{n:"April 2026 — routine bloods", s:"10 markers"},{n:"Jan 2026 — thyroid panel", s:"2 markers"}].map((r,i)=>(
            <div key={i} style={{background:"#fff", border:`1px solid ${c.border}`, borderRadius:12, padding:"12px 14px", marginBottom:8, display:"flex", alignItems:"center", justifyContent:"space-between"}}>
              <div><div style={{fontFamily:c.serif, fontSize:14, color:c.ink}}>{r.n}</div><div style={{fontSize:11, color:c.ink3, marginTop:2}}>{r.s}</div></div>
              <div style={{color:c.ink3, fontSize:18}}>›</div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  if (screen === "upload") {
    return (
      <div style={wrap}>
        <div style={{padding:"24px 20px 12px"}}>
          <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, letterSpacing:"0.16em", textTransform:"uppercase", color:c.primary}}>Step 1 of 3</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:26, lineHeight:1.1, color:c.ink, marginTop:6, letterSpacing:"-0.015em"}}>Add your report</div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{display:"flex", background:"#fff", border:`1px solid ${c.border}`, borderRadius:10, padding:3, marginBottom:12}}>
            <div style={{flex:1, textAlign:"center", padding:"8px 0", background:c.primary, color:"#fff", borderRadius:8, fontSize:13}}>Upload</div>
            <div style={{flex:1, textAlign:"center", padding:"8px 0", color:c.ink2, fontSize:13}}>Paste text</div>
          </div>
          <div style={{border:`1.5px dashed #D8D8D8`, borderRadius:12, padding:"28px 16px", textAlign:"center", background:"#fff"}}>
            <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={c.primary} strokeWidth="1.4"><path d="M12 3v12"/><path d="M7 8l5-5 5 5"/><path d="M4 17v3a1 1 0 001 1h14a1 1 0 001-1v-3"/></svg>
            <div style={{fontSize:13, color:c.ink, marginTop:8}}>Drop a PDF, photo, or TXT</div>
            <div style={{fontSize:11, color:c.ink3, marginTop:4}}>or tap to browse</div>
          </div>
          <div style={{marginTop:16, background:"rgba(139,26,74,0.08)", borderRadius:10, padding:12, fontSize:12, color:c.ink2, lineHeight:1.5}}>
            <span style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, display:"block", marginBottom:4}}>What happens</span>
            Extraction → review → interpretation. Nothing is inferred.
          </div>
        </div>
      </div>
    );
  }

  if (screen === "results") {
    return (
      <div style={wrap}>
        <div style={{padding:"20px 20px 12px"}}>
          <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, letterSpacing:"0.16em", textTransform:"uppercase", color:c.primary}}>Results · 10 markers</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:22, lineHeight:1.15, color:c.ink, marginTop:4}}>April 2026 — routine bloods</div>
        </div>
        <div style={{padding:"0 16px"}}>
          {[
            {n:"Ferritin", v:"21", u:"ng/mL", r:"15–150", s:"low-normal", sc:"#8A5A00", sb:"#FBF4E4"},
            {n:"Vitamin D", v:"27", u:"ng/mL", r:"30–100", s:"below range", sc:"#7A2E2E", sb:"#F7EAEA"},
            {n:"TSH", v:"3.4", u:"mIU/L", r:"0.4–4.0", s:"within range", sc:c.primary, sb:"rgba(139,26,74,0.08)"},
            {n:"Estradiol", v:"110", u:"pg/mL", r:"30–400", s:"within range", sc:c.primary, sb:"rgba(139,26,74,0.08)"},
          ].map((m,i)=>(
            <div key={i} style={{background:"#fff", border:`1px solid ${c.border}`, borderRadius:12, padding:14, marginBottom:8}}>
              <div style={{display:"flex", justifyContent:"space-between", alignItems:"flex-start"}}>
                <div style={{fontFamily:c.serif, fontSize:16, color:c.ink}}>{m.n}</div>
                <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:9, letterSpacing:"0.1em", textTransform:"uppercase", color:m.sc, background:m.sb, padding:"3px 7px", borderRadius:99}}>{m.s}</div>
              </div>
              <div style={{display:"flex", alignItems:"baseline", gap:8, marginTop:6}}>
                <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:22, color:c.ink}}>{m.v}</div>
                <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:11, color:c.ink3}}>{m.u}</div>
                <div style={{marginLeft:"auto", fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, color:c.ink3}}>ref {m.r}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  if (screen === "marker") {
    return (
      <div style={wrap}>
        <div style={{padding:"20px 20px 12px"}}>
          <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, letterSpacing:"0.16em", textTransform:"uppercase", color:c.primary}}>Marker detail</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:30, lineHeight:1.05, color:c.ink, marginTop:6}}>Ferritin</div>
          <div style={{display:"flex", alignItems:"baseline", gap:8, marginTop:12}}>
            <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:42, color:c.ink}}>21</div>
            <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:13, color:c.ink3}}>ng/mL</div>
            <div style={{marginLeft:"auto", fontFamily:"ui-monospace, Menlo, monospace", fontSize:9, letterSpacing:"0.1em", textTransform:"uppercase", color:"#8A5A00", background:"#FBF4E4", padding:"4px 8px", borderRadius:99}}>Low-normal</div>
          </div>
          <div style={{height:6, background:"#F3EFEE", borderRadius:99, marginTop:14, position:"relative"}}>
            <div style={{position:"absolute", left:"8%", right:"8%", top:0, bottom:0, background:"rgba(139,26,74,0.08)", borderRadius:99}}/>
            <div style={{position:"absolute", top:-4, left:"14%", width:14, height:14, background:c.primary, borderRadius:"50%", border:"2px solid #fff", boxShadow:`0 0 0 1px ${c.primary}`}}/>
          </div>
          <div style={{display:"flex", justifyContent:"space-between", marginTop:6, fontFamily:"ui-monospace, Menlo, monospace", fontSize:10, color:c.ink3}}><span>15</span><span>150</span></div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{background:"#fff", border:`1px solid ${c.border}`, borderRadius:12, padding:14, marginBottom:8}}>
            <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:9, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, marginBottom:4}}>Interpretation</div>
            <div style={{fontSize:13, color:c.ink2, lineHeight:1.5}}>Stored iron — the single best indicator of iron reserves.</div>
          </div>
          <div style={{background:"#fff", border:`1px solid ${c.border}`, borderRadius:12, padding:14}}>
            <div style={{fontFamily:"ui-monospace, Menlo, monospace", fontSize:9, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, marginBottom:4}}>Women&rsquo;s context</div>
            <div style={{fontSize:13, color:c.ink2, lineHeight:1.5}}>Menstruating women lose iron monthly; ferritin often sits low without anemia.</div>
          </div>
        </div>
      </div>
    );
  }
}

// ---------- Android screens ----------
function AndroidPhone({ screen }) {
  return (
    <div className="device-frame">
      <AndroidDevice width={340} height={720}>
        <AndroidScreen screen={screen}/>
      </AndroidDevice>
      <div className="device-label">{({home:"Home",upload:"Upload",results:"Results",marker:"Marker detail"})[screen]}</div>
    </div>
  );
}

function AndroidScreen({ screen }) {
  // Slightly different chrome, same tokens — M3-flavored FemDecode
  const c = { primary: "#8B1A4A", ink: "#1E1E24", ink2: "#4A4A55", ink3: "#7A7A85", bg: "#FAF7F6", border: "#EAEAEA", serif: "'Playfair Display', Georgia, serif" };
  const wrap = { height: "100%", background: c.bg, fontFamily: "Roboto, Inter, system-ui", position: "relative", overflow: "hidden" };

  // Top bar
  const AppBar = ({title}) => (
    <div style={{background:c.bg, padding:"8px 4px 0"}}>
      <div style={{height:56, display:"flex", alignItems:"center", gap:4, padding:"0 8px"}}>
        <div style={{width:40, height:40}}/>
        <div style={{flex:1, fontFamily:c.serif, fontSize:20, color:c.ink}}>{title}</div>
        <div style={{width:40, height:40, display:"flex", alignItems:"center", justifyContent:"center"}}>
          <div style={{display:"flex", flexDirection:"column", gap:3}}>{[0,1,2].map(i=><div key={i} style={{width:4, height:4, background:c.ink3, borderRadius:99}}/>)}</div>
        </div>
      </div>
    </div>
  );

  if (screen === "home") {
    return (
      <div style={wrap}>
        <AppBar title="FemDecode"/>
        <div style={{padding:"4px 16px 16px"}}>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:28, lineHeight:1.1, color:c.ink, marginTop:8, letterSpacing:"-0.01em"}}>Your labs,<br/><em style={{color:c.primary, fontStyle:"italic"}}>decoded</em> for her.</div>
          <div style={{marginTop:10, fontSize:13, color:c.ink2, lineHeight:1.45}}>Strict extraction. No invented markers.</div>
        </div>
        <div style={{padding:"0 16px 16px"}}>
          <div style={{background:c.primary, color:"#fff", padding:"14px 16px", borderRadius:999, fontSize:14, fontWeight:500, textAlign:"center", letterSpacing:"0.02em"}}>Decode a report</div>
          <div style={{marginTop:8, background:c.bg, border:`1px solid ${c.border}`, padding:"13px 16px", borderRadius:999, fontSize:14, color:c.ink, textAlign:"center"}}>Try sample</div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{fontFamily:"Roboto Mono, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.ink3, marginBottom:8}}>Recent</div>
          {[{n:"April 2026 — routine bloods", s:"10 markers"},{n:"Jan 2026 — thyroid panel", s:"2 markers"}].map((r,i)=>(
            <div key={i} style={{background:"#fff", borderRadius:16, padding:"14px 16px", marginBottom:8, display:"flex", alignItems:"center", justifyContent:"space-between", boxShadow:"0 1px 2px rgba(0,0,0,0.04)"}}>
              <div><div style={{fontFamily:c.serif, fontSize:14, color:c.ink}}>{r.n}</div><div style={{fontSize:11, color:c.ink3, marginTop:2}}>{r.s}</div></div>
              <div style={{color:c.ink3, fontSize:18}}>›</div>
            </div>
          ))}
        </div>
        <div style={{position:"absolute", right:20, bottom:44, width:56, height:56, borderRadius:16, background:c.primary, color:"#fff", display:"flex", alignItems:"center", justifyContent:"center", fontSize:24, boxShadow:"0 4px 12px rgba(139,26,74,0.3)"}}>+</div>
      </div>
    );
  }

  if (screen === "upload") {
    return (
      <div style={wrap}>
        <AppBar title="Decode"/>
        <div style={{padding:"0 16px 12px"}}>
          <div style={{fontFamily:"Roboto Mono, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary}}>Step 1 of 3</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:22, color:c.ink, marginTop:4}}>Add your report</div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{display:"flex", gap:8, marginBottom:12}}>
            <div style={{flex:1, textAlign:"center", padding:"10px 0", background:c.primary, color:"#fff", borderRadius:999, fontSize:13}}>Upload</div>
            <div style={{flex:1, textAlign:"center", padding:"10px 0", border:`1px solid ${c.border}`, color:c.ink2, borderRadius:999, fontSize:13}}>Paste text</div>
          </div>
          <div style={{border:`1.5px dashed #D8D8D8`, borderRadius:16, padding:"28px 16px", textAlign:"center", background:"#fff"}}>
            <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={c.primary} strokeWidth="1.4"><path d="M12 3v12"/><path d="M7 8l5-5 5 5"/><path d="M4 17v3a1 1 0 001 1h14a1 1 0 001-1v-3"/></svg>
            <div style={{fontSize:13, color:c.ink, marginTop:8}}>Tap to choose file</div>
            <div style={{fontSize:11, color:c.ink3, marginTop:4}}>PDF, photo, or TXT</div>
          </div>
          <div style={{marginTop:16, background:"rgba(139,26,74,0.08)", borderRadius:12, padding:12, fontSize:12, color:c.ink2, lineHeight:1.5}}>
            <span style={{fontFamily:"Roboto Mono, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, display:"block", marginBottom:4}}>Pipeline</span>
            Extract → review → interpret. Nothing inferred.
          </div>
        </div>
        <div style={{position:"absolute", bottom:44, left:16, right:16}}>
          <div style={{background:c.primary, color:"#fff", padding:"14px 16px", borderRadius:999, fontSize:14, fontWeight:500, textAlign:"center"}}>Extract markers</div>
        </div>
      </div>
    );
  }

  if (screen === "results") {
    return (
      <div style={wrap}>
        <AppBar title="Results"/>
        <div style={{padding:"0 16px 8px"}}>
          <div style={{fontFamily:"Roboto Mono, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary}}>10 markers · Apr 2026</div>
        </div>
        <div style={{padding:"0 16px"}}>
          {[
            {n:"Ferritin", v:"21", u:"ng/mL", r:"15–150", s:"low-normal", sc:"#8A5A00", sb:"#FBF4E4"},
            {n:"Vitamin D", v:"27", u:"ng/mL", r:"30–100", s:"below range", sc:"#7A2E2E", sb:"#F7EAEA"},
            {n:"TSH", v:"3.4", u:"mIU/L", r:"0.4–4.0", s:"within range", sc:c.primary, sb:"rgba(139,26,74,0.08)"},
            {n:"Estradiol", v:"110", u:"pg/mL", r:"30–400", s:"within range", sc:c.primary, sb:"rgba(139,26,74,0.08)"},
          ].map((m,i)=>(
            <div key={i} style={{background:"#fff", borderRadius:16, padding:14, marginBottom:8, boxShadow:"0 1px 2px rgba(0,0,0,0.04)"}}>
              <div style={{display:"flex", justifyContent:"space-between", alignItems:"flex-start"}}>
                <div style={{fontFamily:c.serif, fontSize:16, color:c.ink}}>{m.n}</div>
                <div style={{fontFamily:"Roboto Mono, monospace", fontSize:9, letterSpacing:"0.1em", textTransform:"uppercase", color:m.sc, background:m.sb, padding:"3px 8px", borderRadius:99}}>{m.s}</div>
              </div>
              <div style={{display:"flex", alignItems:"baseline", gap:8, marginTop:6}}>
                <div style={{fontFamily:"Roboto Mono, monospace", fontSize:22, color:c.ink}}>{m.v}</div>
                <div style={{fontFamily:"Roboto Mono, monospace", fontSize:11, color:c.ink3}}>{m.u}</div>
                <div style={{marginLeft:"auto", fontFamily:"Roboto Mono, monospace", fontSize:10, color:c.ink3}}>ref {m.r}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  if (screen === "marker") {
    return (
      <div style={wrap}>
        <AppBar title=""/>
        <div style={{padding:"0 16px 12px"}}>
          <div style={{fontFamily:"Roboto Mono, monospace", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary}}>Marker detail</div>
          <div style={{fontFamily:c.serif, fontWeight:500, fontSize:28, lineHeight:1.1, color:c.ink, marginTop:4}}>Ferritin</div>
          <div style={{display:"flex", alignItems:"baseline", gap:8, marginTop:12}}>
            <div style={{fontFamily:"Roboto Mono, monospace", fontSize:40, color:c.ink}}>21</div>
            <div style={{fontFamily:"Roboto Mono, monospace", fontSize:13, color:c.ink3}}>ng/mL</div>
            <div style={{marginLeft:"auto", fontFamily:"Roboto Mono, monospace", fontSize:9, letterSpacing:"0.1em", textTransform:"uppercase", color:"#8A5A00", background:"#FBF4E4", padding:"4px 8px", borderRadius:99}}>Low-normal</div>
          </div>
          <div style={{height:6, background:"#F3EFEE", borderRadius:99, marginTop:14, position:"relative"}}>
            <div style={{position:"absolute", left:"8%", right:"8%", top:0, bottom:0, background:"rgba(139,26,74,0.08)", borderRadius:99}}/>
            <div style={{position:"absolute", top:-4, left:"14%", width:14, height:14, background:c.primary, borderRadius:"50%", border:"2px solid #fff", boxShadow:`0 0 0 1px ${c.primary}`}}/>
          </div>
          <div style={{display:"flex", justifyContent:"space-between", marginTop:6, fontFamily:"Roboto Mono, monospace", fontSize:10, color:c.ink3}}><span>15</span><span>150</span></div>
        </div>
        <div style={{padding:"0 16px"}}>
          <div style={{background:"#fff", borderRadius:16, padding:14, marginBottom:8, boxShadow:"0 1px 2px rgba(0,0,0,0.04)"}}>
            <div style={{fontFamily:"Roboto Mono, monospace", fontSize:9, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, marginBottom:4}}>Interpretation</div>
            <div style={{fontSize:13, color:c.ink2, lineHeight:1.5}}>Stored iron — the single best indicator of iron reserves.</div>
          </div>
          <div style={{background:"#fff", borderRadius:16, padding:14, boxShadow:"0 1px 2px rgba(0,0,0,0.04)"}}>
            <div style={{fontFamily:"Roboto Mono, monospace", fontSize:9, letterSpacing:"0.14em", textTransform:"uppercase", color:c.primary, marginBottom:4}}>Women&rsquo;s context</div>
            <div style={{fontSize:13, color:c.ink2, lineHeight:1.5}}>Menstruating women lose iron monthly; ferritin often reads low without anemia.</div>
          </div>
        </div>
      </div>
    );
  }
}

// ---------- Footer ----------
function Footer() {
  return (
    <footer className="footer">
      <div className="footer-inner">
        <div className="footer-brand"><Logo/> <span>FemDecode</span></div>
        <div className="footer-text">Clinical intelligence for women&rsquo;s lab results. Not a substitute for medical care.</div>
        <div className="footer-meta mono">© 2026 · v0.5 preview</div>
      </div>
    </footer>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
