import { useState, useMemo, useRef, useEffect } from "react";

/* ============================================================
   CARMA — Submission Platform Prototype v4
   Knight rate plan v2026-05-13 · Guidelines v3.0
   v4: 48 contiguous program states; CBD-only unlicensed OK;
   pending license = flag but indication proceeds; synthetics =
   PL ineligible only (proceed without PL); all declines route
   to Brokerage/Captive with upload-driven submission; scroll
   fixes; allocation % input fix; limits capped at 2M; full
   construction names + Greenhouse (rated as Frame); justified
   schedule credits max 20% (more = referral); no rates shown.
   ============================================================

   PROJECT LOGIC & DEVELOPER ADVICE — read before changing anything
   ----------------------------------------------------------------
   1. SOURCE OF TRUTH: knight_rate_plan_v2026-05-13.json governs all
      rates, factors, minimums, and rules. The constants below are a
      prototype convenience copied from it; production must import the
      JSON directly. Never invent or alter a rate — if missing, ask.
      All rule changes are recorded as dated adjudications in the JSON.
   2. RETRO TIER RULE (adjudication 11): when mapping a retroactive
      DATE to a pricing tier, ROUND UP to the tier that fully covers
      the period. Example: retro 2.2 years before inception = 3-year
      tier (40%), never the 2-year tier. Partial years always advance.
      The current UI is a tier select with a hint; when the retro DATE
      field is added, derive the tier with Math.ceil(yearsBack).
   3. MINIMUM PREMIUMS (adjudications 6 + 12): per-line MPs apply
      ONLY when that line is monoline. Monoline GL MP is LIMIT-BASED:
      500K/1M = $500, 1M/1M = $800, 1M/2M = $1,000 (2M/2M = $1,200, adj 46
      $1,000 pending confirmation). Monoline PCO = $2,500, Property =
      $400. Multi-line takes raw line premiums and applies only the
      $750 package minimum once, at the end. ALL minimums render in
      the live indication in REAL TIME (rail shows "· MIN" when the
      floor governs; total box notes the package minimum).
   4. CREDITS (adjudication + design): agent side is an additive
      request in 5% steps, justification required per category, 20%
      max applied, excess = referral. The production rater's IRPM is
      MULTIPLICATIVE per category with line-specific category sets
      (property uses Special Hazards / Building Features / Crime
      Score / Other). The UW workbench (Iteration 3) implements the
      full multiplicative table; expect small deltas between agent
      indication and UW-adjudicated premium until then.
   5. RATE VISIBILITY: agents never see class rates, ILFs, or factors
      anywhere in the UI — premiums and verdicts only. Underwriter
      views (future) invert this and show full pricing anatomy.
   6. DECLINES ARE LEADS: every decline must end in a Brokerage or
      Captive submission path with documents attached. Never add a
      dead-end decline.
   7. CBD-ONLY UNLICENSED (adjudication 13): selecting license =
      'Not licensed' with ONLY CBD/Hemp Retail operations triggers a
      follow-up gate: "Does the insured hold a license to sell or
      manufacture hemp?" Yes = validate at binding; No = UW flag,
      indication proceeds.
   8. FEES & TAXES: Online Rater Fees (Lowered), 6/11/2026 — banded
      by indicated premium: <10K = $225 admin + $175 UW; 10K-24,999 =
      $275 + $200; 25K-49,999 = $325 + $250. Premium >= 50K (adj 47,
      6/13/2026) = a single 3% fee on the indicated premium (no flat band) NOT
      provided — top band applied interim, pending ruling. Property at
      multiple locations adds $175 per location beyond the first
      (charged separately) — wire when multi-location ships. SL tax is
      a flat 6% estimate on premium + fees pending SLTF extraction.
      Rail itemizes premium / admin fee / UW fee / taxes and totals as
      ESTIMATED TOTAL COST with a not-bindable disclaimer.
   9. TESTS: headless interaction suite at /home/claude/apptest/run.js
      (98 checks incl. two golden accounts reconciled to the
      production rater and a live LIO quote, both broker submission
      flows, and the adj. 18 greenhouse paths). Any engine change
      must keep the suite green; extend it when adding behavior.
   10. UNIFIED SUBMISSION ACTION (adjudication 21 + EVP revision,
      6/12/2026 — rating screen, pure flow, no new rates): the rail
      shows ONE action button. Default label "Request firm quote →";
      when any referral condition is live (Property TIV >
      $5M/location, schedule credit requested above 20%, greenhouse
      property per adj. 18) the SAME button relabels to "Refer to
      underwriting →" with the accent border and a REFERRAL
      RECOMMENDED badge. Both states open the SAME panel — heading
      adapts ("FIRM QUOTE REQUEST" vs "REFERRAL TO UNDERWRITING") —
      with ONE attachments section: Application REQUIRED (send is
      blocked without it: "Application is required to submit to
      underwriting"); Loss runs, SOV, and Other documents OPTIONAL
      (adj. 21 supersedes the interim new-venture loss-runs rule;
      licenses / supplemental apps / COIs / dec pages fold into the
      Other slot's helper text) — plus the same broker-contact block
      and required-field validation as the Brokerage submission flow
      (note 6 / adjudication 17: insured name + broker name + broker
      email) and an OPTIONAL "Need by date" (included in the summary
      and email body as "Need by: {date}" when present). Send issues
      a REF- reference number for BOTH modes (mirrors the Brokerage
      CB- pattern), composes the email to underwriting@carma365.com
      AND sales@carma365.com — subject "{Referral|Firm quote
      request} — {insured} — Ref {ref}", body = submission summary +
      attachment list — and swaps to a green confirmation: reference
      number, recipients, attached documents, the 24–48 hour
      response commitment, and a collapsed "Email payload (prototype
      preview)" block. Email dispatch is SIMULATED — the payload
      lives in state only; production replaces it with the backend
      mailer (Iteration 1). WFR-2822: attached files are uploaded to
      S3 and relayed onto the C360 submission (see uploadAttachments).
      The underwriting ledger and rail note
      "Referred to UW · [ref]" after send. Rating math, gates, and
      verdicts are untouched.
   11. DOCUMENT DE-DUPLICATION (EVP revision, 6/12/2026): the rating
      screen's generic "SUBMISSION DOCUMENTS" drop zone duplicated
      the panel's typed slots (application/ACORDs, SOV, loss runs,
      supplementals, dec pages). It now renders ONLY when the
      Brokerage/Captive path is forced (submitRequired) — that rail
      flow still requires attached documents (note 6). The quote /
      referral panel's four slots are the single source of document
      collection everywhere else. No document type appears twice in
      the panel or confirmation.
   ============================================================ */

const C = {
  /* Carma brand palette (6/12/2026): accent #FF4F52 / accent-dark #E63840,
     navy #1C2B3A / navy-light #243447, silver #94A3B8 / silver-dark #64748B,
     slate grays. Semantic greens/ambers retained for verdict states. */
  paper: "#F8FAFC", ink: "#1C2B3A", red: "#E63840", accent: "#FF4F52", green: "#1E6B3C",
  amber: "#B07A1E", line: "#E2E8F0", dim: "#64748B", panel: "#FFFFFF",
};
const font = {
  display: "'Playfair Display', serif",
  body: "'Inter', sans-serif",
  mono: "'IBM Plex Mono', monospace",
};
const CONTIGUOUS = ["AL","AZ","AR","CA","CO","CT","DE","DC","FL","GA","ID","IL","IN","IA","KS","KY","LA","ME","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VT","VA","WA","WV","WI","WY"];
const NON_CONTIGUOUS = ["AK","HI"];
const COVERAGES = [
  { id: "gl", label: "General Liability", sub: "Occurrence form · no deductible" },
  { id: "prod", label: "Products / Completed Ops", sub: "Claims-made DITL · monoline OK" },
  { id: "prop", label: "Commercial Property", sub: "Special form · $5M max per location" },
  { id: "im", label: "Inland Marine", sub: "Property in transit limits available via Property Enhancement Endorsement", comingSoon: true },
];
const RISKS = [
  { id: "cult_in", label: "Cultivation — Indoor", group: "Cultivation" },
  { id: "cult_out", label: "Cultivation — Outdoor", group: "Cultivation" },
  { id: "cult_gh", label: "Cultivation — Greenhouse", group: "Cultivation" },
  { id: "extract", label: "Extraction / Manufacturing", group: "Processing" },
  { id: "mfg", label: "Manufacturing — Non-Extraction", group: "Processing" },
  { id: "lab", label: "Lab Testing", group: "Processing" },
  { id: "dist", label: "Wholesale / Distribution", group: "Movement" },
  { id: "trans", label: "Transportation / Delivery", group: "Movement" },
  { id: "ret_c", label: "Retail — Cannabis", group: "Retail" },
  { id: "ret_h", label: "Retail — CBD / Hemp", group: "Retail" },
  /* "Other" classification (EVP revision, 6/12/2026): unclassified risk — skips rating,
     routes directly to the unified panel in referral mode. Description is required and
     travels in the referral email/summary. Encouraging copy: lead capture, not dead end. */
  { id: "other_risk", label: "Other", group: "Other" },
];

/* WFR-1906 (review finding #1): rater RISK ids → cannabis-v1 schema enums.
   The rater identifies risks by its own short ids (cult_in, extract, ret_c, …).
   The c360 cannabis-v1 application expects canonical enum VALUES:
     general_info.operations  ∈ OPERATIONS_OPTIONS  (cannabis-v1.ts)
     locations[].growOperations ∈ {indoor, outdoor, greenhouse} (GROW_OPERATIONS)
   Emitting raw rater ids would make the enriched data un-renderable in the c360
   application UI (unknown checkbox values are preserved-not-rendered). Any rater id
   with no mapping here is OMITTED rather than passed through raw. */
const RISK_TO_OPERATION = {
  cult_in:    "cultivation",
  cult_out:   "cultivation",
  cult_gh:    "cultivation",
  extract:    "extraction",
  mfg:        "manufacturing_non_extraction",
  lab:        "lab_testing",
  dist:       "wholesale_distribution",
  trans:      "delivery_end_user",
  ret_c:      "adult_use_retail",
  ret_h:      "cbd_hemp_retail",
  other_risk: "other",
};
const RISK_TO_GROW_OPERATION = {
  cult_in: "indoor",
  cult_out: "outdoor",
  cult_gh: "greenhouse",
};
/* Map a set/array of rater RISK ids → de-duped cannabis-v1 enum values; unmappable
   ids are dropped (omit, never emit a raw id). Exported for the projection test. */
function mapRiskIdsToOperations(riskIds) {
  const out = [];
  for (const id of Array.from(riskIds || [])) {
    const v = RISK_TO_OPERATION[id];
    if (v && !out.includes(v)) out.push(v);
  }
  return out;
}
function mapRiskIdsToGrowOperations(riskIds) {
  const out = [];
  for (const id of Array.from(riskIds || [])) {
    const v = RISK_TO_GROW_OPERATION[id];
    if (v && !out.includes(v)) out.push(v);
  }
  return out;
}
export { mapRiskIdsToOperations, mapRiskIdsToGrowOperations };

/* WFR-1906 (review finding #3, XSS hardening): HTML-escape any value before it is
   interpolated into indicationHtml — the Lambda emails that string to the agent
   verbatim as a Graph HTML body, so unescaped <>&"' in any interpolated value
   (coverage labels, deductible/limit strings, and any broker-entered field added
   to the template later) could inject markup. Defense-in-depth for an internal tool. */
function escapeHtml(value) {
  return String(value == null ? "" : value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}
export { escapeHtml };

/* Risk-classification label for the email subject / summary. "Other" carries the
   typed description. (adjudication 29, 6/12/2026) */
function riskClassLabel(riskSet, otherDesc) {
  const has = riskSet && riskSet.has ? (id) => riskSet.has(id) : (id) => (riskSet || []).includes(id);
  if (has("other_risk")) return otherDesc && otherDesc.trim() ? `Other — ${otherDesc.trim()}` : "Other (unclassified)";
  const ids = Array.from(riskSet || []);
  const labels = ids.map((id) => (RISKS.find((r) => r.id === id) || {}).label || id);
  return labels.join(", ") || "Classification TBD";
}
/* SINGLE submission email subject — referral and firm-quote requests are one and
   the same, so the subject is identical for every submission (adjudication 29):
   NEW SUBMISSION - {Named Insured}, {Effective date}, {Risk Classification}, {Need-by date} */
function newSubmissionSubject(insured, effDate, riskClass, needBy) {
  return `NEW SUBMISSION - ${insured && insured.trim() ? insured.trim() : "Unnamed insured"}, `
    + `${effDate ? "eff " + effDate : "eff date TBD"}, `
    + `${riskClass}, `
    + `${needBy ? "need by " + needBy : "no need-by date"}`;
}
const CLASS_MAP = {
  ret_c:  { code: 18437, name: "Dispensary / Retail",            gl: 1.50, pl: 0.78 },
  ret_h:  { code: 18437, name: "CBD Retail",                     gl: 1.50, pl: 0.78 },
  cult_in:{ code: 15669, name: "Cultivation — Indoor",           gl: 1.25, pl: 1.74 },
  cult_gh:{ code: 15669, name: "Cultivation — Greenhouse",       gl: 1.25, pl: 1.74 },
  extract:{ code: 52343, name: "Extraction / Manufacturing",     gl: 1.75, pl: 1.94 },
  mfg:    { code: 53377, name: "Manufacturing — Non-Extraction", gl: 0.90, pl: 1.86 },
  lab:    { code: 97002, name: "Lab Testing",                    gl: 0.90, pl: 1.78 },
  dist:   { code: 99338, name: "Wholesale / Distribution",       gl: 0.64, pl: 1.92 },
  trans:  { code: 99338, name: "Transportation / Delivery",      gl: 0.64, pl: 1.92 },
};
const GL_ILF = [
  { k: "500K / 1M", f: 0.90 }, { k: "1M / 1M", f: 0.98 }, { k: "1M / 2M", f: 1.00 }, { k: "2M / 2M", f: 1.32 },
];
/* Monoline GL minimum premium by limit (adjudication 12; 2M/2M set to $1,200 per adjudication 46, 6/13/2026). */
const GL_MONO_MP = { "500K / 1M": 500, "1M / 1M": 800, "1M / 2M": 1000, "2M / 2M": 1200 };
const PL_ILF = [
  { k: "500K / 1M", f: 0.92 }, { k: "1M / 1M", f: 0.98 }, { k: "1M / 2M", f: 1.00 }, { k: "2M / 2M", f: 1.10 },
];
const PROP_RATES = {
  store: { label: "Store / Dispensary", FRAME: 0.45, JM: 0.38, NC: 0.35, MNC: 0.30, FR: 0.24 },
  mfg:   { label: "Manufacturing & Processing", FRAME: 0.64, JM: 0.58, NC: 0.51, MNC: 0.52, FR: 0.43 },
  cult:  { label: "Cultivation — Indoor", FRAME: 0.65, JM: 0.65, NC: 0.61, MNC: 0.57, FR: 0.40 },
};
const STOCK_RATE = { FRAME: 0.95, JM: 0.85, NC: 0.75, MNC: 0.80, FR: 0.75 };
const CONSTRUCTION = [
  { v: "FRAME", l: "Frame" },
  { v: "JM", l: "Joisted Masonry" },
  { v: "NC", l: "Non-Combustible" },
  { v: "MNC", l: "Masonry Non-Combustible" },
  { v: "FR", l: "Fire Resistive" },
  { v: "GH", l: "Greenhouse" },
];
const constrKey = (v) => (v === "GH" ? "FRAME" : v);
/* Adjudication 52: per-location property record. Same defaults the single-location
   screen used (construction NC, all coverage values 0). Roof/sqft/sprinklered and the
   four update years (roof/plumbing/wiring/HVAC) are captured for underwriting context. */
const ROOF_TYPES = [
  { v: "", l: "Select…" },
  { v: "metal", l: "Metal / standing seam" },
  { v: "membrane", l: "Membrane (TPO/EPDM)" },
  { v: "builtup", l: "Built-up / modified bitumen" },
  { v: "shingle", l: "Asphalt shingle" },
  { v: "tile", l: "Tile / concrete" },
];
let __locSeq = 0;
const newLocation = () => ({
  uid: ++__locSeq,
  addr: "", zip: "", constr: "NC", roof: "", sqft: 0, sprinklered: null,
  yearBuilt: "", /* adj. 60: per-location year built drives building-criteria validation */
  upRoof: "", upPlumb: "", upWiring: "", upHvac: "",
  bldg: 0, tib: 0, bpp: 0, bi: 0, stock: 0, cann: 0,
});
const PROP_DED = ["2500", "7500", "10000", "25000"];
const PROP_DED_F = { "2500": 1.00, "7500": 0.90, "10000": 0.85, "25000": 0.80 };
const PL_DED = ["2500", "5000", "7500", "10000"];
const PL_DED_F = { "2500": 1.00, "5000": 0.96, "7500": 0.93, "10000": 0.90 };
const CREDIT_CATS = [
  { id: "mgmt", label: "Management", why: "Ownership experience · safety culture · loss history" },
  { id: "prem", label: "Premises & Operations", why: "Sprinklers, alarms · access & cash controls · fire controls" },
  { id: "prodc", label: "Product & Procedures", why: "Testing & QC · recall procedures · hazmat handling" },
  { id: "emp", label: "Employees & Training", why: "Screening & turnover · MVR review · OSHA training" },
  { id: "spec", label: "Special Hazards & Exposures", why: "High crime · delivery / unique ops · neighboring hazards" },
];
const CREDIT_OPTIONS = ["0", "5", "10"];
const AB_TIERS = [
  /* adj. 19 (6/12/2026): $1,000 A&B minimum premium applies ONLY at
     sublimits of 100/100 and above; 25/25 and 50/50 rate at pure
     percentage of GL premium with no floor. */
  { k: "None", pct: 0, mp: 0 }, { k: "25/25", pct: 0.05, mp: 0 }, { k: "50/50", pct: 0.10, mp: 0 },
  { k: "100/100", pct: 0.15, mp: 1000 }, { k: "250/250 (max)", pct: 0.20, mp: 1000 },
];

/* ============================================================
   GL / CASUALTY OPTIONAL COVERAGES (binding adjudications 53-57, 6/13/2026).
   All gate on covs.has("gl"); none alter premium unless explicitly selected.
   ============================================================ */
/* adj. 53 — Dispensary dual-basis GL. For the dispensary/retail class
   (ret_c / ret_h, class 18437), GL rates as the LOWER of the sales basis
   (allocated sales /1000 x 1.50) and the square-footage basis
   (retail sqft /1000 x 125). Other operations keep normal sales-basis GL. */
const DISPENSARY_RISKS = ["ret_c", "ret_h"];
const DISP_SQFT_RATE = 125;   /* per 1,000 sqft */
/* adj. 54 — Hired & Non-Owned Auto ($1,000,000 CSL). Flat charge by employee
   band; DECLINED where hired-auto exposure exists. */
const HNOA_BANDS = [
  { k: "0-25", label: "0 – 25 employees", charge: 250 },
  { k: "26-50", label: "26 – 50 employees", charge: 750 },
  { k: "51+", label: "51+ employees", charge: 1000 },
];
/* adj. 55 / 63 — Damage to Rented Premises (DTR). $250K included at $0; higher limits add a flat charge. */
const DTR_OPTS = [
  { k: "250", label: "$250,000 (included)", charge: 0 },
  { k: "300", label: "$300,000", charge: 150 },
  { k: "500", label: "$500,000", charge: 200 },
  { k: "750", label: "$750,000", charge: 250 },
  { k: "1000", label: "$1,000,000", charge: 300 },
];
/* adj. 56 — Employers Liability / Stop Gap. Offered ONLY in the four
   monopolistic states (ND, OH, WA, WY). Charge by GL limit. */
const EL_STATES = ["ND", "OH", "WA", "WY"];
const EL_CHARGE = { "500K / 1M": 300, "1M / 1M": 350, "1M / 2M": 350, "2M / 2M": 350 };
/* adj. 57 — Medical Payments. $5,000 standard / $10,000 available; no premium either way. */
const MEDPAY_OPTS = [
  { k: "5000", label: "$5,000 (standard)" },
  { k: "10000", label: "$10,000" },
];
const RETRO = [
  { k: "None", pct: 0, mp: 0 }, { k: "1 yr", pct: 0.15, mp: 500 }, { k: "2 yr", pct: 0.20, mp: 750 },
  { k: "3 yr", pct: 0.40, mp: 1250 }, { k: "4 yr", pct: 0.50, mp: 1500 }, { k: "5 yr", pct: 0.60, mp: 1750 },
];
const ENHANCEMENT = [
  { k: "None", limit: null, p: 0 },
  { k: "Endorsement #1 — $10,000 property in transit limit", limit: 10000, p: 1000 },
  { k: "Endorsement #2 — $50,000 property in transit limit", limit: 50000, p: 1500 },
];
/* Online Rater Fees (Lowered) — Carma fee schedule 6/11/2026. Premium-banded.
   Premium >= $50,000 (adjudication 47, 6/13/2026): a SINGLE fee = 3% of the
   indicated premium replaces the flat admin+UW band. The 3% applies to the
   indicated premium BEFORE fees/taxes (the same base the bands keyed off).
   Multi-location property: +$175 per additional location beyond the first (charged
   separately) — applies when multi-location ships; prototype is single-location. */
const FEE_BANDS = [
  { under: 10000, admin: 225, uw: 175 },
  { under: 25000, admin: 275, uw: 200 },
  { under: 50000, admin: 325, uw: 250 },
];
const FEE_3PCT_THRESHOLD = 50000; /* adj 47: >= $50K indicated premium => 3% fee */
const getFees = (premium) => {
  if (!premium) return { admin: 0, uw: 0 };
  /* adj 47: indicated premium >= $50K — a single 3%-of-premium fee in place of the
     flat admin+UW band. Reported on the admin-fee line; uw fee = 0. */
  if (premium >= FEE_3PCT_THRESHOLD) return { admin: premium * 0.03, uw: 0, pct3: true };
  const band = FEE_BANDS.find((b) => premium < b.under) || FEE_BANDS[FEE_BANDS.length - 1];
  return { admin: band.admin, uw: band.uw };
};
const BROKERAGE_LINE = " Submission to Brokerage or the Carma Talisman Captive Program is required.";
/* Referral email routing (adjudication 21, 6/12/2026): underwriting AND sales. */
const REFERRAL_TO = ["underwriting@carma365.com", "sales@carma365.com"];
/* Mock wind lookup — production reads knight_wind_eligibility.csv (40,874 ZIPs) */
const CURRENT_YEAR = 2026; /* adj. 60: building-age basis year */
const TIER1 = ["330","331","332","333","334","339","294","295","284","285","775","776","777","778","779","785","700","704","708","395","365","366"];
const TIER2 = ["335","336","337","338","346","347","328","329","773","774","783","784","286","283","296","705","706","707","394","364","299"];
/* Adjudication 58 (6/13/2026): per-ZIP Wind table — WIND TIER (ROS / Tier 1 / Tier 2),
   WIND/HAIL ALLOWED ("YES" | "NO, REFER TO MGMT"), and the verbatim MINIMUM W/H DEDUCTIBLE
   string from the rater. The prototype does not embed the full 40k-row CSV; the sample
   mapping below carries REPRESENTATIVE ZIPs covering every deductible outcome so the
   per-location display logic is exercised and demoable. Full CSV wiring is Iteration 1.
   Distinct deductible outcomes supported: "AOP IS ACCEPTABLE";
   "1%/2%/3% SUBJECT TO $2,500 MINIMUM"; "5% SUBJECT TO $5,000 MINIMUM";
   "5% SUBJECT TO $25,000 MINIMUM"; "BLDG: 2% OR 3% IF >15,000 SQ FT. BPP ONLY 1%"; and "N/A". */
const WIND_SAMPLE = {
  /* AOP / ROS — wind/hail allowed, AOP acceptable */
  "load1aop": { tier: "ROS", allowed: "YES", deductible: "AOP IS ACCEPTABLE" },
  /* Tier 2 — % deductible w/ $2,500 minimum */
  "load2pct": { tier: "Tier 2", allowed: "YES", deductible: "1%/2%/3% SUBJECT TO $2,500 MINIMUM" },
  /* Tier 1 — 5% w/ $5,000 minimum */
  "load5pct": { tier: "Tier 1", allowed: "YES", deductible: "5% SUBJECT TO $5,000 MINIMUM" },
  /* Tier 1 — 5% w/ $25,000 minimum */
  "load25k": { tier: "Tier 1", allowed: "YES", deductible: "5% SUBJECT TO $25,000 MINIMUM" },
  /* Tier 1 — split BLDG/BPP deductible */
  "loadsplit": { tier: "Tier 1", allowed: "YES", deductible: "BLDG: 2% OR 3% IF >15,000 SQ FT. BPP ONLY 1%" },
  /* Tier 1 — NOT allowed, refer to management */
  "loadrefer": { tier: "Tier 1", allowed: "NO, REFER TO MGMT", deductible: "N/A" },
};
/* Representative ZIP 3-digit prefixes mapped to each outcome (demo data; Iteration 1 = full CSV). */
const WIND_AOP = ["100","900","load1aop"];          /* AOP ROS sample ZIPs (e.g. 10001, 90001) */
const WIND_2PCT = ["335","336","load2pct"];          /* 2%/$2,500 sample (FL Tier 2) */
const WIND_5PCT = ["330","331","load5pct"];          /* 5%/$5,000 sample (FL Tier 1) */
const WIND_25K = ["334","load25k"];                  /* 5%/$25,000 sample */
const WIND_SPLIT = ["775","loadsplit"];              /* split BLDG/BPP sample */
const WIND_NOREFER = ["339","loadrefer"];            /* NO, REFER TO MGMT sample */
const windLookup = (z) => {
  if (!/^[0-9]{5}$/.test(z)) return null;
  const p = z.slice(0, 3);
  const mk = (tier, status, allowed, deductible) => {
    /* legacy `text` retained for backward compatibility with existing display/tests */
    const text = allowed === "NO, REFER TO MGMT"
      ? "Wind/Hail not available at this ZIP — refer to underwriting"
      : deductible === "AOP IS ACCEPTABLE"
        ? "Wind/Hail eligible — AOP deductible is acceptable at this ZIP"
        : "Wind/Hail allowed — " + deductible + " applies at this ZIP";
    return { tier, status, allowed, deductible, text };
  };
  if (WIND_NOREFER.includes(p)) return mk("Tier 1", "refer", "NO, REFER TO MGMT", "N/A");
  if (WIND_25K.includes(p)) return mk("Tier 1", "restricted", "YES", "5% SUBJECT TO $25,000 MINIMUM");
  if (WIND_SPLIT.includes(p)) return mk("Tier 1", "restricted", "YES", "BLDG: 2% OR 3% IF >15,000 SQ FT. BPP ONLY 1%");
  if (WIND_5PCT.includes(p) || TIER1.includes(p)) return mk("Tier 1", "restricted", "YES", "5% SUBJECT TO $5,000 MINIMUM");
  if (WIND_2PCT.includes(p) || TIER2.includes(p)) return mk("Tier 2", "restricted", "YES", "1%/2%/3% SUBJECT TO $2,500 MINIMUM");
  return mk("ROS", "clear", "YES", "AOP IS ACCEPTABLE");
};
const GATES = [
  { id: "state", scope: "Program", q: "State of operation", type: "state",
    appliesWhen: () => true,
    evaluate: (a) => (a == null || a === "" ? null : CONTIGUOUS.includes(a) ? "pass" : "refer"),
    passNote: "Program state — surplus lines documents will generate automatically.",
    referNote: "The program covers the 48 contiguous states. Routed to Carma Brokerage Division — submission continues, market changes." },
  { id: "license", scope: "Program", q: "License status in the state of operation", type: "choice",
    options: [{ v: "active", t: "Active & valid" }, { v: "pending", t: "Application pending" }, { v: "none", t: "Not licensed" }],
    appliesWhen: () => true,
    evaluate: (a, s) => {
      if (a == null) return null;
      if (a === "active") return "pass";
      if (a === "pending") return "warn";
      const cbdOnly = s.risks.size > 0 && [...s.risks].every((r) => r === "ret_h");
      return cbdOnly ? "pass" : "decline";
    },
    noteFn: (a, s) => {
      if (a === "active") return "License number will be validated against the state registry and timestamped at quote.";
      if (a === "pending") return "FLAGGED: license pending. The indication will still generate — binding requires an active, validated license.";
      if (a === "none") {
        const cbdOnly = s.risks.size > 0 && [...s.risks].every((r) => r === "ret_h");
        return cbdOnly ? "CBD-only operations may proceed without a cannabis license." : "Unlicensed cannabis operations are not eligible for the program." + BROKERAGE_LINE;
      }
      return null;
    } },
  { id: "hemp", scope: "Program", q: "Does the insured hold a license to sell or manufacture hemp?", type: "yesno",
    appliesWhen: (s, ans) => ans.license === "none" && s.risks.size > 0 && [...s.risks].every((r) => r === "ret_h"),
    /* adj 50 (6/13/2026): a "no" now REFERS to underwriting (was "warn"). The CBD-only /
       unlicensed appliesWhen path is unchanged; only the "no" outcome changed. */
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "refer"),
    noteFn: (a) => a === "yes" ? "Hemp license will be validated at binding."
      : a === "no" ? "No hemp license — provide an explanation; this risk is submitted to underwriting for review."
      : null },
  { id: "kratom_psilo", scope: "Program", q: "Does the applicant sell, distribute, or manufacture kratom or psilocybin (psychedelic mushrooms)?", type: "yesno",
    appliesWhen: () => true,
    evaluate: (a) => (a == null ? null : a === "yes" ? "decline" : "pass"),
    failNote: "Kratom and psilocybin are not eligible for the program." + BROKERAGE_LINE },
  { id: "synthetic", scope: "Program", q: "Any synthetic cannabinoids, Delta-8, Delta-10, THCA, THC-O, HHC, or other intoxicating hemp-derived cannabinoids?", type: "yesno",
    appliesWhen: () => true,
    evaluate: (a) => (a == null ? null : a === "yes" ? "warn" : "pass"),
    noteFn: (a) => a === "yes"
      ? "Intoxicating hemp-derived cannabinoids are not eligible for Products coverage in this program. Submit these risks to Carma Brokerage, which writes this class. The indication will proceed without products coverage; other requested lines are unaffected."
      : "No excluded product categories." },
  { id: "compliance", scope: "Program", q: "In compliance with all state and local cannabis laws, with Track & Trace (METRC/BioTrack) participation?", type: "yesno",
    appliesWhen: () => true,
    evaluate: (a) => (a == null ? null : a === "no" ? "decline" : "pass"),
    failNote: "Program requires compliant operations with active Track & Trace participation." + BROKERAGE_LINE },
  { id: "interstate", scope: "Risk", q: "Is product over 0.3% THC ever transported across state lines?", type: "yesno",
    appliesWhen: (s) => s.risks.has("trans") || s.risks.has("dist"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "decline" : "pass"),
    failNote: "Interstate movement is a federal exposure the program cannot cover." + BROKERAGE_LINE },
  { id: "residential", scope: "Risk", q: "Does anyone live at any insured location?", type: "yesno",
    appliesWhen: (s) => s.covs.has("prop") || s.covs.has("gl"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "refer" : "pass"),
    referNote: "Residential exposure requires underwriter review and proof of separate homeowners coverage." },
  { id: "alarm", scope: "Risk", q: "Central-station burglar alarm covering all openings, with motion detection? (Self-monitored systems do not qualify.)", type: "yesno",
    appliesWhen: (s) => s.covs.has("prop"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "refer"),
    passNote: "Theft coverage eligible at full program terms.",
    referNote: "Eligible to proceed — theft coverage will be restricted or excluded." },
  { id: "video", scope: "Risk", q: "Video surveillance covering all entry/exit points and inventory areas, with minimum 30-day retention?", type: "yesno",
    appliesWhen: (s) => s.covs.has("prop"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "refer"),
    passNote: "Meets program surveillance requirement.",
    referNote: "30-day retention is required for theft coverage. Theft terms restricted until confirmed." },
  { id: "bldage", scope: "Risk", q: "Any building over 50 years old with no electrical, plumbing, HVAC, or roof updates?", type: "yesno",
    appliesWhen: (s) => s.covs.has("prop"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "decline" : "pass"),
    failNote: "Ineligible per property guidelines. Buildings 75+ years also require prior underwriting approval." + BROKERAGE_LINE },
  { id: "panels", scope: "Risk", q: "Knob & tube wiring, non-pigtailed aluminum wiring, or FPE / Zinsco / Murray / Challenger panels?", type: "yesno",
    appliesWhen: (s) => s.covs.has("prop"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "decline" : "pass"),
    failNote: "Ineligible electrical systems per property guidelines." + BROKERAGE_LINE },
  { id: "consumption", scope: "Risk", q: "Is on-site consumption permitted at any retail location?", type: "yesno",
    appliesWhen: (s) => s.risks.has("ret_c"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "decline" : "pass"),
    failNote: "Consumption lounges are ineligible for the program." + BROKERAGE_LINE },
  { id: "led", scope: "Risk", q: "Is grow lighting 100% LED?", type: "yesno",
    appliesWhen: (s) => s.risks.has("cult_in") || s.risks.has("cult_gh"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "decline"),
    passNote: "LED confirmed — no further lighting questions. Inspection verifies at binding.",
    failNote: "This risk still has a strong home with Carma. HID/MH and mixed-lighting grows are placed every day through Carma Brokerage's additional markets — and the Carma Talisman Captive Program was built for risks exactly like this one. Submit below and your answers and documents travel with it." },
  { id: "greenhouse", scope: "Risk", q: "Does the greenhouse have a concrete foundation and non-permeable walls?", type: "yesno",
    appliesWhen: (s) => s.risks.has("cult_gh") && s.covs.has("prop"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "refer" : "decline"),
    noteFn: (a) => a == null ? null : a === "yes"
      ? "Property on greenhouse construction is not available through the online indication — submission to underwriting is required. Wind coverage is not offered on greenhouse structures. (Adjudication 18.)"
      : "Property coverage is not allowed on greenhouse construction without a concrete foundation and non-permeable walls." + BROKERAGE_LINE },
  { id: "electrical", scope: "Risk", q: "All electrical work at grow facilities performed (or to be inspected within 30 days) by a licensed, insured contractor?", type: "yesno",
    appliesWhen: (s) => s.risks.has("cult_in") || s.risks.has("cult_out") || s.risks.has("cult_gh"),
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "refer"),
    referNote: "Electrical warranty cannot attach. Eligible only with an inspection commitment." },
  { id: "hydrocarbon", scope: "Risk", q: "Extraction using hydrocarbon or other flammable solvents?", type: "yesno",
    appliesWhen: (s) => s.risks.has("extract"),
    evaluate: (a) => (a == null ? null : "pass"),
    noteFn: (a) => a === "yes"
      ? "Two follow-up questions appear below: closed-loop system and operator experience."
      : "No further extraction questions are needed." },
  { id: "closedloop", scope: "Risk", q: "Closed-loop system used for all solvent extraction?", type: "yesno",
    appliesWhen: (s, ans) => s.risks.has("extract") && ans.hydrocarbon === "yes",
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "decline"),
    failNote: "Open-loop flammable extraction is prohibited." + BROKERAGE_LINE },
  { id: "extexp", scope: "Risk", q: "Does the volatile extraction operator have at least 3 years related experience?", type: "yesno",
    appliesWhen: (s, ans) => s.risks.has("extract") && ans.hydrocarbon === "yes",
    evaluate: (a) => (a == null ? null : a === "yes" ? "pass" : "decline"),
    failNote: "Minimum 3 years related extraction experience required for volatile operations." + BROKERAGE_LINE },
];
const SUPPRESSED_BASE = 62;
const fmt = (n) => "$" + Math.round(n).toLocaleString();
const num = (s) => Number(String(s).replace(/[^0-9.]/g, "")) || 0;

function Stamp({ status }) {
  const map = {
    pass: { t: "PASS", c: C.green }, warn: { t: "FLAG", c: C.amber },
    refer: { t: "REFER", c: C.amber }, decline: { t: "DECLINE", c: C.red },
  };
  const m = map[status];
  if (!m) return <span style={{ color: C.dim, fontFamily: font.mono, fontSize: 11 }}>—</span>;
  return (
    <span style={{ fontFamily: font.mono, fontSize: 10, fontWeight: 600, letterSpacing: "0.12em", color: m.c,
      border: `1.5px solid ${m.c}`, padding: "2px 7px", borderRadius: 3, transform: "rotate(-2deg)",
      display: "inline-block", background: "#fff" }}>{m.t}</span>
  );
}
function YesNo({ value, onChange }) {
  return (
    <div style={{ display: "flex", gap: 8 }}>
      {["yes", "no"].map((v) => (
        <button key={v} onClick={() => onChange(v)}
          style={{ fontFamily: font.body, fontSize: 13, fontWeight: 600, padding: "7px 22px", borderRadius: 4,
            cursor: "pointer", textTransform: "capitalize",
            border: `1.5px solid ${value === v ? C.ink : C.line}`,
            background: value === v ? C.ink : "#fff", color: value === v ? "#fff" : C.ink }}>{v}</button>
      ))}
    </div>
  );
}
function Field({ label, children, hint }) {
  return (
    <div style={{ marginBottom: 14, marginRight: 20 }}>
      <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 5 }}>{label}</div>
      {children}
      {hint && <div style={{ fontSize: 11, color: C.dim, marginTop: 4, maxWidth: 280 }}>{hint}</div>}
    </div>
  );
}
function Money({ value, onChange, w = 150, disabled = false }) {
  return (
    <input type="text" inputMode="numeric" disabled={disabled} value={value === 0 ? "" : value.toLocaleString()}
      onChange={(e) => onChange(num(e.target.value))} placeholder="$0"
      style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4,
        border: `1.5px solid ${C.line}`, width: w, background: disabled ? "#F1F5F9" : "#fff",
        color: disabled ? "#94A3B8" : C.ink, cursor: disabled ? "not-allowed" : "text" }} />
  );
}
function Sel({ value, onChange, options, w }) {
  const opts = options.map((o) => (typeof o === "string" ? { v: o, l: o } : o));
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}
      style={{ fontFamily: font.body, fontSize: 13, fontWeight: 600, padding: "8px 10px", borderRadius: 4,
        border: `1.5px solid ${C.line}`, background: "#fff", minWidth: w || 150 }}>
      {opts.map((o) => <option key={o.v} value={o.v}>{o.l}</option>)}
    </select>
  );
}
/* WFR-2822: client-side limits mirror the Lambda's. These exist for a fast,
   legible error only — they are bypassable and are NOT the gate. documents.mjs
   revalidates every manifest server-side before any submission is created. */
const MAX_FILES = 10;
const MAX_FILE_BYTES = 25 * 1024 * 1024;
/* WFR-2827 (N-4): mirrors documents.mjs MAX_NAME_LEN. Every server rule the
   client does NOT mirror is a rule the broker discovers at Send, after filling
   the whole form, as a 400 with no submission created — the same failure mode
   F-1 was raised for. Keep this list complete when the server gate changes. */
const MAX_NAME_LEN = 150;
const ALLOWED_EXT = ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "heic", "doc", "docx", "xls", "xlsx", "csv"];

function attachmentError(f, currentCount) {
  if (currentCount >= MAX_FILES) return "You can attach at most " + MAX_FILES + " documents.";
  if (!f.size) return f.name + " is empty.";
  if (f.size > MAX_FILE_BYTES) return f.name + " is larger than 25 MB.";
  if (f.name.length > MAX_NAME_LEN) return f.name.slice(0, 40) + "… has too long a filename (max " + MAX_NAME_LEN + " characters).";
  var dot = f.name.lastIndexOf(".");
  var ext = dot > 0 ? f.name.slice(dot + 1).toLowerCase() : "";
  if (ALLOWED_EXT.indexOf(ext) === -1) return f.name + " is not a supported type (PDF, image, Word, Excel, CSV).";
  return null;
}

/* WFR-2827 (F-1): `totalCount` is the count of documents attached ACROSS the
   whole form — the four referral slots plus this drop zone. The server cap is
   global (documents.mjs MAX_FILES) and fires before createSubmission, so a
   per-widget cap let a routine referral (application + loss runs + SOV + other)
   sail past 10 in the browser and 400 the entire submission at Send. Defaults to
   this widget's own count for the gate-screen render, where no slots exist yet. */
function UploadZone({ files, setFiles, compact, totalCount }) {
  const total = typeof totalCount === "number" ? totalCount : files.length;
  const ref = useRef(null);
  const [err, setErr] = useState(null);
  /* WFR-2822: keep the File itself, not just its name — the bytes are what
     leg 2 POSTs to S3. Validation reads `files` directly rather than the
     functional updater: React defers an updater to render time, so anything
     the updater assigned would still be null by the time setErr ran and the
     error banner would never appear. */
  /* WFR-2835 (M5): no filename dedupe. Two files can legitimately share a name —
     an agent dragging scan.pdf from two different folders is the common case, and
     they are different documents. The old dedupe also sat BEFORE attachmentError,
     so firstErr stayed null and the drop was completely silent. DocSlot never
     deduped; this keeps the two surfaces consistent. Identity is positional from
     here on: chips are keyed by index and removal is by index. */
  const add = (list) => {
    const accepted = [];
    let firstErr = null;
    Array.from(list).forEach((f) => {
      const e = attachmentError(f, total + accepted.length);
      if (e) { if (!firstErr) firstErr = e; return; }
      accepted.push({ name: f.name, size: f.size, type: f.type, file: f });
    });
    if (accepted.length) setFiles(files.concat(accepted));
    setErr(firstErr);
  };
  return (
    <div>
      <div onDragEnter={(e) => { e.preventDefault(); e.stopPropagation(); }}
        onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = "copy"; }}
        onDrop={(e) => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) add(e.dataTransfer.files); }}
        onClick={() => ref.current && ref.current.click()}
        style={{ border: `1.5px dashed ${files.length ? C.green : C.line}`, borderRadius: 6,
          padding: compact ? "12px 14px" : "18px 20px", cursor: "pointer",
          background: files.length ? "#F4F9F5" : "#fff", textAlign: "center" }}>
        <input ref={ref} type="file" multiple style={{ display: "none" }} onChange={(e) => add(e.target.files)} />
        <div style={{ fontSize: 13, fontWeight: 700 }}>
          {files.length ? `${files.length} document${files.length > 1 ? "s" : ""} attached` : "Drop submission documents here or click to browse"}
        </div>
        <div style={{ fontSize: 11, color: C.dim, marginTop: 3 }}>ACORD 125 / 126 / 140 · SOV · loss runs · supplementals · dec pages</div>
      </div>
      {err && <div data-upload-error style={{ fontSize: 11, fontWeight: 600, color: C.red, marginTop: 6 }}>{err}</div>}
      {files.length > 0 && (
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 8 }}>
          {files.map((f, i) => (
            <span key={f.name + i} style={{ fontFamily: font.mono, fontSize: 10, background: "#fff", border: `1px solid ${C.line}`,
              borderRadius: 3, padding: "3px 8px", display: "inline-flex", alignItems: "center", gap: 6 }}>
              {f.name.length > 30 ? f.name.slice(0, 28) + "…" : f.name}
              <button onClick={(e) => { e.stopPropagation(); setFiles(files.filter((x, idx) => idx !== i)); }}
                style={{ border: "none", background: "none", color: C.red, cursor: "pointer", fontWeight: 700, padding: 0 }}>×</button>
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

/* showNeedBy (EVP revision 6/12/2026): optional "Need by date" — rendered only
   in the unified quote/referral panel; included in summary + email when set. */
function BrokerFields({ b, set, showNeedBy }) {
  const I = (k, ph, type) => (
    <input type={type || "text"} value={b[k]} placeholder={ph}
      onChange={(e) => set({ ...b, [k]: e.target.value })}
      style={{ fontFamily: font.body, fontSize: 12.5, padding: "8px 10px", borderRadius: 4,
        border: `1.5px solid ${C.line}`, background: "#fff", width: "100%", color: C.ink }} />
  );
  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6, margin: "8px 0 4px", textAlign: "left" }}>
      <div style={{ gridColumn: "1 / -1" }}>{I("insured", "Named insured *")}</div>
      <div>
        <div style={{ fontSize: 10, fontWeight: 700, color: C.dim, marginBottom: 2 }}>Effective date</div>
        {I("effDate", "", "date")}
      </div>
      <div style={{ alignSelf: "end" }}>{I("agency", "Agency")}</div>
      {/* WFR-1906: this Carma-direct quoter's submitter is the AGENT / PRODUCER —
          name/email/phone/agency capture the agent, not the insured. */}
      <div style={{ gridColumn: "1 / -1", fontSize: 10, fontWeight: 700, letterSpacing: "0.06em", color: C.dim, marginTop: 2 }}>AGENT / PRODUCER</div>
      {I("name", "Agent name *")}
      {I("phone", "Agent phone")}
      <div style={{ gridColumn: "1 / -1" }}>{I("email", "Agent email *")}</div>
      {showNeedBy && (
        <div>
          <div style={{ fontSize: 10, fontWeight: 700, color: C.dim, marginBottom: 2 }}>Need by date</div>
          {I("needBy", "", "date")}
        </div>
      )}
    </div>
  );
}

/* Unified quote/referral document slots (adjudication 21 + EVP revision
   6/12/2026) — flow config only, no rating impact. THE single attachments
   section: Application is the only required document; loss runs, SOV, and
   other documents are optional (adj. 21 supersedes the interim new-venture
   loss-runs rule). Former generic-drop document types fold into hints:
   ACORDs -> Application; licenses, supplemental apps, COIs, dec pages ->
   Other. No document type may appear in two slots. */
const REF_DOC_SLOTS = [
  { id: "app", label: "Application", required: true, hint: "ACORD 125 / 126 / 140" },
  { id: "loss", label: "Loss runs", required: false },
  { id: "sov", label: "Statement of Values (SOV)", required: false },
  { id: "other", label: "Other relevant documents", required: false, hint: "e.g. licenses, supplemental apps, COIs, dec pages" },
];

/* One <input type="file" multiple> per referral slot. WFR-2822: the File itself
   is kept in state and uploaded to the C360 submission (see uploadAttachments). */
function DocSlot({ slot, label, required, hint, files, add, remove, totalCount, children }) {
  const ref = useRef(null);
  const [err, setErr] = useState(null);
  const ok = files.length > 0;
  /* WFR-2827 (F-1): count against the whole form, not just this slot — see the
     note on UploadZone. Rejections were also silent here; now the first one is
     shown, so the broker learns about the ceiling before Send, not after. */
  const total = typeof totalCount === "number" ? totalCount : files.length;
  return (
    <div style={{ marginBottom: 10, paddingBottom: 10, borderBottom: `1px solid ${C.line}` }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
        <span data-doc-label={label} style={{ fontSize: 12.5, fontWeight: 700, width: 230 }}>
          {label}{required ? <span style={{ color: C.red }}> *</span> : <span style={{ color: C.dim, fontWeight: 500 }}> · optional</span>}
        </span>
        <button onClick={() => ref.current && ref.current.click()}
          style={{ padding: "7px 14px", borderRadius: 4, fontSize: 12.5, fontWeight: 600, cursor: "pointer",
            border: `1.5px solid ${ok ? C.green : required ? C.amber : C.line}`,
            background: ok ? "#EAF3EC" : "#fff", color: C.ink }}>
          {ok ? `✓ ${files.length} attached — add more` : "Attach…"}
        </button>
        <input ref={ref} type="file" multiple data-slot={slot} style={{ display: "none" }}
          onChange={(e) => {
            if (!e.target.files || !e.target.files.length) return;
            const accepted = [];
            let firstErr = null;
            Array.from(e.target.files).forEach((f) => {
              const msg = attachmentError(f, total + accepted.length);
              if (msg) { if (!firstErr) firstErr = msg; return; }
              accepted.push({ name: f.name, size: f.size, type: f.type, file: f });
            });
            if (accepted.length) add(accepted);
            setErr(firstErr);
          }} />
        {!ok && required && <span style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.08em", color: C.amber }}>REQUIRED</span>}
      </div>
      {hint && <div style={{ fontSize: 10.5, color: C.dim, marginTop: 4 }}>{hint}</div>}
      {err && <div data-upload-error style={{ fontSize: 11, fontWeight: 600, color: C.red, marginTop: 6 }}>{err}</div>}
      {children}
      {files.length > 0 && (
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 8 }}>
          {files.map((f, i) => (
            <span key={f.name + i} style={{ fontFamily: font.mono, fontSize: 10, background: "#fff", border: `1px solid ${C.line}`,
              borderRadius: 3, padding: "3px 8px", display: "inline-flex", alignItems: "center", gap: 6 }}>
              {(f.name.length > 26 ? f.name.slice(0, 24) + "…" : f.name)} · {(f.size / 1024).toFixed(1)} KB
              <button onClick={() => remove(i)}
                style={{ border: "none", background: "none", color: C.red, cursor: "pointer", fontWeight: 700, padding: 0 }}>×</button>
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

/* ============== RATING ============== */
function RatingScreen({ covs, risks, gateAnswers, files, setFiles, onBack, broker, setBroker, brokerOk, submitted, refNum, doSubmit, onReferralSent, forceReferral, otherDesc, submitToCarma, submitting, submitError, submissionId, submitSuccess }) {
  useEffect(() => { window.scrollTo({ top: 0, behavior: "instant" }); }, []);
  /* WFR-1906 (UI test #1/#3 fix): the real Carma seam is engaged whenever it is
     in flight, has succeeded, or has errored. While seamActive, keep the
     submit-form branch (which hosts the {submitSuccess}/{submitError} panels)
     mounted so the REAL backend result — success submissionId or 4xx c360Message —
     is shown instead of the prototype's simulated confirmation, which the legacy
     synchronous *Sent flag would otherwise unmount. Always false under jsdom (the
     seam no-ops before touching this state), so the regression suite is unchanged. */
  const seamActive = submitting || submitSuccess || !!submitError;
  /* "other_risk" has no CLASS_MAP entry — filter it from rating computations.
     When forceReferral is true, liabRisks is irrelevant (no rating computed),
     but we still need the filter to prevent a crash during render. */
  const liabRisks = [...risks].filter((r) => r !== "cult_out" && r !== "other_risk");
  const synthEx = gateAnswers.synthetic === "yes";
  const plActive = covs.has("prod") && !synthEx;
  /* Every selected line ineligible (monoline Products + excluded product categories):
     stop the indication entirely and direct to Brokerage / Carma Talisman Captive Program. */
  const submitRequired = synthEx && covs.has("prod") && covs.size === 1;

  const [sales, setSales] = useState(0);
  const [alloc, setAlloc] = useState(() => {
    const o = {}; liabRisks.forEach((r, i) => (o[r] = i === 0 ? "100" : "")); return o;
  });
  const [acres, setAcres] = useState(0);
  const [glLimit, setGlLimit] = useState("1M / 2M");
  const [plLimit, setPlLimit] = useState("1M / 2M");
  const [plDed, setPlDed] = useState("2500");
  const [dotl, setDotl] = useState(false);
  const [retro, setRetro] = useState("None");
  const [eb, setEb] = useState(null);
  const [propDed, setPropDed] = useState("2500");
  /* Adjudication 52: property is rated PER LOCATION (up to 3) and summed.
     Each location carries its own construction, ZIP/wind, building characteristics,
     update years, and all six coverage values. Occupancy (occKey) stays shared —
     it derives from the selected operations (the operation class). Location 1 holds
     the same defaults the single-location screen used (constr NC, all values 0). */
  const [locations, setLocations] = useState(() => [newLocation()]);
  const setLoc = (idx, patch) => setLocations((prev) =>
    prev.map((l, j) => (j === idx ? { ...l, ...patch } : l)));
  const addLocation = () => setLocations((prev) => (prev.length >= 3 ? prev : [...prev, newLocation()]));
  const removeLocation = (idx) => setLocations((prev) => (prev.length <= 1 ? prev : prev.filter((_, j) => j !== idx)));
  const [credits, setCredits] = useState({ mgmt: "0", prem: "0", prodc: "0", emp: "0", spec: "0" });
  const [justif, setJustif] = useState({ mgmt: "", prem: "", prodc: "", emp: "", spec: "" });
  const [armed, setArmed] = useState(null);
  const [ab, setAb] = useState("None");
  const [tria, setTria] = useState(false);
  const [enh, setEnh] = useState("None");
  /* GL optional coverages (adj. 53-57). Defaults are deliberately inert:
     no HNOA, DTR included $250K ($0), EL off, Med Pay $5K ($0), and the
     manual retail-sqft GL-basis input at 0 (falls back to sales-basis). */
  const [hnoaOn, setHnoaOn] = useState(false);
  const [hnoaBand, setHnoaBand] = useState("0-25");
  const [hnoaExp, setHnoaExp] = useState(null);   /* hired-auto exposure yes/no */
  const [hnoaDeliv, setHnoaDeliv] = useState(null);   /* adj. 62 — insured performs delivery yes/no */
  const [dtr, setDtr] = useState("250");
  const [elOn, setElOn] = useState(false);
  const [medPay, setMedPay] = useState("5000");
  const [glSqft, setGlSqft] = useState(0);        /* manual retail sqft for the GL basis (adj. 53 fallback) */

  const allocTotal = liabRisks.reduce((a, r) => a + num(alloc[r]), 0);
  const allocOk = liabRisks.length <= 1 || allocTotal === 100;

  /* Schedule credit: justified categories only; >20% requested = referral; 20% max applied */
  const requested = CREDIT_CATS.reduce((a, c) => a + num(credits[c.id]), 0);
  const justified = CREDIT_CATS.reduce((a, c) => a + (num(credits[c.id]) > 0 && justif[c.id].trim() ? num(credits[c.id]) : 0), 0);
  const missingJust = CREDIT_CATS.filter((c) => num(credits[c.id]) > 0 && !justif[c.id].trim());
  const appliedCredit = Math.min(20, justified);
  const creditOver = requested > 20;
  const modFactor = 1 - appliedCredit / 100;

  const mono = covs.size === 1;
  const glILF = GL_ILF.find((x) => x.k === glLimit).f;
  const plILF = PL_ILF.find((x) => x.k === plLimit).f;

  /* adj. 53 — Dispensary dual-basis GL. The dispensary/retail class (ret_c/ret_h)
     rates as the LOWER of the sales basis and the square-footage basis; every
     other operation keeps its normal sales basis. Retail square footage is sourced
     from the Property location cards (sum of each location's sqft) when Property is
     selected; otherwise from the manual "Retail square footage (for GL basis)" input.
     When sqft is 0/unknown the sqft basis is skipped and we fall back to sales-basis
     only (so existing dispensary tests with no sqft are unchanged). */
  const locSqft = locations.reduce((a, l) => a + num(l.sqft), 0);
  const retailSqft = locSqft > 0 ? locSqft : num(glSqft);
  const dualBasisActive = covs.has("gl") && liabRisks.some((r) => DISPENSARY_RISKS.includes(r));
  let dualBasisUsedSqft = false;          /* true when the sqft basis governed any dispensary portion */
  let glBase = 0;
  if (allocOk) {
    glBase = liabRisks.reduce((a, r) => {
      const salesBasis = (sales * num(alloc[r]) / 100 / 1000) * CLASS_MAP[r].gl;
      if (DISPENSARY_RISKS.includes(r) && retailSqft > 0) {
        const sqftBasis = (retailSqft / 1000) * DISP_SQFT_RATE;
        if (sqftBasis < salesBasis) { dualBasisUsedSqft = true; return a + sqftBasis; }
      }
      return a + salesBasis;
    }, 0);
  }
  /* The dispensary square-footage basis cannot be optimized until sqft is entered. */
  const dualBasisNeedsSqft = dualBasisActive && retailSqft <= 0;
  if (risks.has("cult_out")) glBase += acres * 55;
  const glRaw = covs.has("gl") && glBase ? glBase * glILF * modFactor : 0;
  const glMP = GL_MONO_MP[glLimit];
  const glP = covs.has("gl") ? (mono ? Math.max(glMP, glRaw) : glRaw) : 0;
  const glMin = covs.has("gl") && mono && glP > glRaw;

  let rawPl = 0;
  if (plActive && allocOk) {
    const plBase = liabRisks.reduce((a, r) => a + (sales * num(alloc[r]) / 100 / 1000) * CLASS_MAP[r].pl, 0);
    if (plBase) {
      const annual = plBase * plILF * PL_DED_F[plDed] * modFactor;
      const dotlP = dotl ? annual * 0.20 : 0;
      const rt = RETRO.find((x) => x.k === retro);
      const retroP = rt.pct ? Math.max(rt.mp, annual * rt.pct) : 0;
      rawPl = annual + dotlP + retroP;
    }
  }
  const plP = plActive ? (mono ? Math.max(2500, rawPl) : rawPl) : 0;
  const plMin = plActive && mono && plP > rawPl;

  /* Occupancy (occKey) is shared across all locations — it is the operation class,
     derived from the selected operations, not a per-location attribute (adj. 52). */
  const occKey = (risks.has("cult_in") || risks.has("cult_gh")) ? "cult" : (risks.has("extract") || risks.has("mfg")) ? "mfg" : "store";
  const occ = PROP_RATES[occKey];
  /* Adjudication 52: rate each location on its own construction/values, then SUM.
     Adjudication 42 (TIB absorbed into building) and the $5M ceiling + greenhouse
     exclusion (adj. 5 / 18) are evaluated PER LOCATION. The monoline $400 floor and the
     Equipment Breakdown % are applied ONCE to the SUMMED property premium below. */
  const locCalcs = locations.map((loc, idx) => {
    const ck = constrKey(loc.constr);
    const tibEff = loc.bldg > 0 ? 0 : loc.tib;       /* adj. 42, per location */
    const bldgGroup = loc.bldg + tibEff + loc.bpp + loc.bi;
    const stockGroup = loc.stock + loc.cann;
    const tiv = bldgGroup + stockGroup;
    const over = covs.has("prop") && tiv > 5000000;  /* adj. 5: $5M PER LOCATION */
    const gh = covs.has("prop") && loc.constr === "GH"; /* adj. 18: greenhouse refers, per location */
    const wind = windLookup(loc.zip);
    /* adj. 58: a location whose ZIP returns "NO, REFER TO MGMT" (deductible "N/A") REFERS for wind. */
    const windRefer = covs.has("prop") && !!wind && (wind.allowed === "NO, REFER TO MGMT" || wind.deductible === "N/A");
    /* adj. 59: Insurance-to-value advisory (non-blocking). bldg & sqft both > 0 and implied $/sqft < $150. */
    const itvPerSqft = loc.bldg > 0 && loc.sqft > 0 ? loc.bldg / loc.sqft : 0;
    const itvFlag = covs.has("prop") && loc.bldg > 0 && loc.sqft > 0 && itvPerSqft < 150;
    /* adj. 60: building-criteria validation, data-driven per location (mirrors the old bldage gate).
       yearBuilt entered -> age = 2026 - yearBuilt. age>75 => REFER; age>50 w/ no system-update years
       recorded => INELIGIBLE (exclude from online property rating like greenhouse). */
    const yb = parseInt(loc.yearBuilt, 10);
    const ybValid = /^[0-9]{4}$/.test(loc.yearBuilt) && yb > 0;
    const bldgAge = ybValid ? CURRENT_YEAR - yb : null;
    const anyUpdate = !!(loc.upRoof || loc.upPlumb || loc.upWiring || loc.upHvac);
    const bldRefer = covs.has("prop") && bldgAge != null && bldgAge > 75;                 /* over 75 -> refer */
    const bldIneligible = covs.has("prop") && bldgAge != null && bldgAge > 75 === false && bldgAge > 50 && !anyUpdate; /* over 50, no updates -> ineligible */
    /* A location is excluded from online property rating if greenhouse, wind-refer, or building-criteria. */
    const excluded = gh || windRefer || bldRefer || bldIneligible;
    const raw = covs.has("prop") && tiv && !over && !excluded
      ? ((bldgGroup / 100) * occ[ck] + (stockGroup / 100) * STOCK_RATE[ck]) * PROP_DED_F[propDed] * modFactor : 0;
    const valueEntered = loc.bldg + loc.bpp + loc.bi + loc.stock + loc.cann + (loc.bldg > 0 ? 0 : loc.tib) > 0;
    return { idx, loc, ck, tibEff, bldgGroup, stockGroup, tiv, over, gh, wind, windRefer,
      itvPerSqft, itvFlag, bldgAge, anyUpdate, bldRefer, bldIneligible, excluded, raw, valueEntered };
  });
  /* A location only contributes property value / a multi-location fee if it carries values. */
  const valuedLocs = locCalcs.filter((c) => c.valueEntered);
  const overLocs = locCalcs.filter((c) => c.over);     /* over $5M -> refer that location */
  const ghLocs = locCalcs.filter((c) => c.gh);         /* greenhouse -> refer that location */
  const windReferLocs = locCalcs.filter((c) => c.windRefer);   /* adj. 58: wind not available -> refer */
  const bldReferLocs = locCalcs.filter((c) => c.bldRefer);     /* adj. 60: over 75 -> refer */
  const bldIneligLocs = locCalcs.filter((c) => c.bldIneligible); /* adj. 60: over 50, no updates -> ineligible */
  const propOver = overLocs.length > 0;
  const ghProp = ghLocs.length > 0;
  /* Summed raw across rateable locations (over-limit / greenhouse excluded above). */
  const propRaw = covs.has("prop") ? locCalcs.reduce((a, c) => a + c.raw, 0) : 0;
  /* Monoline $400 floor applies ONCE to the summed property premium (adj. 52). */
  const propP = covs.has("prop") && !propOver && !ghProp ? (mono ? Math.max(400, propRaw) : propRaw) : 0;
  const propMin = covs.has("prop") && mono && !propOver && !ghProp && propP > propRaw;
  const ebP = eb === "yes" && propP ? propP * (occKey === "mfg" ? 0.015 : 0.01) : 0;

  const abTier = AB_TIERS.find((x) => x.k === ab);
  const abRaw = covs.has("gl") && armed === "no" && abTier.pct && glP ? glP * abTier.pct : 0;
  const abP = abRaw ? Math.max(abTier.mp, abRaw) : 0; /* adj. 19: $1K floor only at 100/100+ */
  const abMin = abP > abRaw && abRaw > 0;
  const enhP = covs.has("prop") && propP ? ENHANCEMENT.find((x) => x.k === enh).p : 0;
  /* ---- GL optional coverages (adj. 54-57) — only when GL is selected ---- */
  /* adj. 54 — Hired & Non-Owned Auto. Flat charge by employee band; DECLINED where
     hired-auto exposure exists (no premium, refer to Brokerage).
     adj. 62 — also NOT available where the insured performs delivery (commercial-auto
     exposure). HNOA premium applies only when selected AND hired-auto = No AND delivery = No. */
  const hnoaBandObj = HNOA_BANDS.find((b) => b.k === hnoaBand) || HNOA_BANDS[0];
  const hnoaExpDeclined = covs.has("gl") && hnoaOn && hnoaExp === "yes";
  const hnoaDelivDeclined = covs.has("gl") && hnoaOn && hnoaDeliv === "yes";
  const hnoaDeclined = hnoaExpDeclined || hnoaDelivDeclined;
  const hnoaP = covs.has("gl") && hnoaOn && hnoaExp === "no" && hnoaDeliv === "no" ? hnoaBandObj.charge : 0;
  /* adj. 55 / 63 — Damage to Rented Premises (DTR). Flat charge above the included $250K. */
  const dtrObj = DTR_OPTS.find((o) => o.k === dtr) || DTR_OPTS[0];
  const dtrP = covs.has("gl") ? dtrObj.charge : 0;
  /* adj. 56 — Employers Liability / Stop Gap. Monopolistic states only; charge by GL limit. */
  const elEligible = covs.has("gl") && EL_STATES.includes(gateAnswers.state);
  const elP = elEligible && elOn ? (EL_CHARGE[glLimit] || 0) : 0;
  /* adj. 57 — Medical Payments. No premium either way. */

  const triaP = tria ? (glP + plP + abP + propP + ebP + enhP + hnoaP + dtrP + elP) * 0.05 : 0; /* adj. 10: 5% */
  let subtotal = glP + plP + abP + propP + ebP + enhP + hnoaP + dtrP + elP + triaP;
  const preFloor = subtotal;
  if (covs.size > 1) subtotal = Math.max(750, subtotal);
  const pkgMin = covs.size > 1 && preFloor < 750;
  /* Fees: Online Rater schedule, banded by indicated premium. Tax basis = premium + fees
     (interim); 6% flat estimate pending SLTF state table extraction. */
  const { admin: adminFee, uw: uwFee, pct3: fee3pct } = getFees(subtotal);
  /* Adjudication 16/52: multi-location property adds +$175 per additional location
     beyond the first that carries property values (charged as a separate fee). */
  const multiLocCount = covs.has("prop") ? Math.max(0, valuedLocs.length - 1) : 0;
  const multiLocFee = multiLocCount * 175;
  const feesTotal = adminFee + uwFee + multiLocFee;
  const tax = (subtotal + feesTotal) * 0.06;
  const total = subtotal + feesTotal + tax;
  const refers = [
    ...overLocs.map((c) => `Location ${c.idx + 1} property TIV ${fmt(c.tiv)} exceeds the $5,000,000 per-location maximum — referral required`),
    creditOver && `Schedule credit above 20% requested (${requested}%) — underwriter approval required`,
    ...ghLocs.map((c) => `Location ${c.idx + 1} greenhouse construction — property is not available via the online indication; submission to underwriting required. Wind is not offered on greenhouse structures`),
    ...windReferLocs.map((c) => `Location ${c.idx + 1} — wind/hail not available at this ZIP; refer to underwriting`),
    ...bldReferLocs.map((c) => `Location ${c.idx + 1} — building over 75 years old; prior underwriting approval required`),
    ...bldIneligLocs.map((c) => `Location ${c.idx + 1} — building over 50 years old with no recorded system updates; ineligible per property guidelines 5.2, submit to underwriting`),
  ].filter(Boolean);

  /* ---------- Referral-to-Underwriting flow (adjudication 21) — pure flow, no rating impact ---------- */
  const [refOpen, setRefOpen] = useState(() => !!forceReferral);
  const [refSent, setRefSent] = useState(false);
  const [refRefNum, setRefRefNum] = useState("");
  const [refDocs, setRefDocs] = useState({ app: [], loss: [], sov: [], other: [] });
  const [refEmail, setRefEmail] = useState(null); /* composed email payload — prototype preview only */
  const [showEmail, setShowEmail] = useState(false);
  const refPanelRef = useRef(null);
  useEffect(() => { if (refOpen && refPanelRef.current) refPanelRef.current.scrollIntoView({ behavior: "smooth", block: "start" }); }, [refOpen]);
  const addDoc = (slot) => (added) => setRefDocs((p) => ({ ...p, [slot]: [...p[slot], ...added] }));
  const rmDoc = (slot) => (i) => setRefDocs((p) => ({ ...p, [slot]: p[slot].filter((_, j) => j !== i) }));
  /* Adjudication 21: the application is the only required document. */
  const docsOk = refDocs.app.length > 0;
  const refDocCount = REF_DOC_SLOTS.reduce((a, s) => a + refDocs[s.id].length, 0);
  /* WFR-2827 (F-1): the cap the server enforces is GLOBAL, so the client's must
     be too — the drop zone plus every referral slot, counted once. */
  const totalDocCount = files.length + refDocCount;
  /* Same required contact fields as the Brokerage flow (adjudication 17):
     insured name + broker name + broker email. */
  const canSendReferral = docsOk && brokerOk;
  /* Adjudication 43: before a firm-quote / referral SEND from the RATING screen,
     every rating question must be answered. (Rating-screen analog of gate adj. 28.)
     Not applied on the forced-referral path, which shows no rating. */
  const hasLiability = covs.has("gl") || covs.has("prod");
  const propValueEntered = valuedLocs.length > 0; /* adj. 43/52: at least one property value across all locations */
  const ratingOutstanding = [
    hasLiability && sales <= 0 && "Enter estimated sales",
    !allocOk && "Class allocation must total 100%",
    !(armed === "yes" || armed === "no") && "Answer the armed-guards question",
    covs.has("prop") && !propValueEntered && "Enter at least one property value",
  ].filter(Boolean);
  const ratingComplete = ratingOutstanding.length === 0;
  const canSendRatingReferral = canSendReferral && ratingComplete;
  const referredLines = [
    covs.has("prop") && (propOver || ghProp) && "Commercial Property",
    creditOver && "All selected lines — schedule credit is account-level",
  ].filter(Boolean);
  const referralSummary = (rn) => [
    `CARMA UNDERWRITING REFERRAL ${rn}`,
    `Submitted: ${new Date().toLocaleString()}`,
    `Named insured: ${broker.insured}`,
    broker.effDate && `Effective date: ${broker.effDate}`,
    broker.needBy && `Need by: ${broker.needBy}`,
    `Broker: ${broker.name}${broker.agency ? " · " + broker.agency : ""}`,
    `Contact: ${broker.email}${broker.phone ? " · " + broker.phone : ""}`,
    `Lines: ${[...covs].join(", ") || "-"} · Operations: ${[...risks].join(", ") || "-"}`,
    otherDesc && `Risk classification: Other — ${otherDesc}`,
    referredLines.length > 0 && `Lines referred: ${referredLines.join("; ")}`,
    (refers.length > 0 || forceReferral) && `Referral reasons: ${refers.length > 0 ? refers.join("; ") : "Unclassified risk — classification required"}`,
    ...REF_DOC_SLOTS.map((s) => `${s.label} (${refDocs[s.id].length}): ${refDocs[s.id].map((f) => f.name).join("; ") || "none"}`),
    `Indicated premium (not bindable): ${subtotal ? fmt(subtotal) : "—"}`,
  ].filter(Boolean).join("\n");
  const doSendReferral = () => {
    /* WFR-1906 Task 4: real Lambda POST runs IN ADDITION to the simulated dispatch below.
       SIMULATED email dispatch (adjudication 21, prototype): the composed payload
       is stored in state and rendered in the confirmation only — nothing leaves
       the browser. PRODUCTION: replace this block with the backend mailer call
       (Iteration 1). Reference number mirrors the Brokerage flow's
       CB-<base36 timestamp> pattern, prefixed REF- to distinguish referrals. */
    const rn = "REF-" + Date.now().toString(36).toUpperCase();
    const attachments = REF_DOC_SLOTS.flatMap((s) => refDocs[s.id].map((f) => f.name));
    setRefEmail({
      to: REFERRAL_TO.join(", "),
      subject: newSubmissionSubject(broker.insured, broker.effDate, riskClassLabel(risks, otherDesc), broker.needBy),
      body: referralSummary(rn) + `\nAttachments (${attachments.length}): ${attachments.join("; ") || "none"}`,
    });
    setRefRefNum(rn);
    setRefSent(true);
    if (onReferralSent) onReferralSent(rn);
    if (submitToCarma) void submitToCarma("referral", buildCarmaParts(""));
  };
  /* "Back to indication" — clears the referral flow, keeps the indication. */
  const resetReferral = () => {
    setRefSent(false); setRefOpen(false); setShowEmail(false);
    setRefEmail(null); setRefDocs({ app: [], loss: [], sov: [], other: [] });
  };

  /* ====================================================================
     ADJUDICATION 61 (6/13/2026) — END-OF-JOURNEY PRINTABLE + EMAILED
     INDICATION. When the rating is complete AND no referral condition is
     live (no refers, not forced-referral, not a Brokerage-required decline),
     the agent can generate a non-binding INDICATION built from the LIVE
     rated submission. It is simultaneously "emailed" (SIMULATED) to the
     broker/user AND underwriting + sales. Reuses the broker contact fields,
     newSubmissionSubject, fmt, the rail data, and the email-payload preview
     pattern from the referral flow. No rating math changes here.
     ==================================================================== */
  const [indOpen, setIndOpen] = useState(false);
  const [indSent, setIndSent] = useState(false);
  const [indRefNum, setIndRefNum] = useState("");
  const [indEmail, setIndEmail] = useState(null); /* composed payload — prototype preview only */
  const [showIndEmail, setShowIndEmail] = useState(false);
  const indPanelRef = useRef(null);
  useEffect(() => { if (indOpen && indPanelRef.current) indPanelRef.current.scrollIntoView({ behavior: "smooth", block: "start" }); }, [indOpen]);
  /* The clean-indication path is available only when the rating is complete and
     there is NO referral condition (refers), not the forced-referral 'Other'
     path, and not a Brokerage/Captive-required decline (submitRequired). */
  const indicationClean = ratingComplete && refers.length === 0 && !forceReferral && !submitRequired;
  /* An indication needs a recipient: the broker/user email (reused from the
     broker contact block). If missing, the generate button stays disabled and a
     prompt is shown. */
  const indEmailOk = broker.email.trim().length > 0;
  /* The named coverage lines + indicated premiums for the indication body and the
     printable schedule (mirrors the LIVE rail, agent-facing: premiums/limits only,
     NO rate factors/class codes). */
  const indCovLines = [
    covs.has("gl") && ["General Liability", glLimit, glP],
    covs.has("prod") && ["Products / Completed Ops (DITL)", "", synthEx ? null : plP],
    covs.has("gl") && abP > 0 && ["Assault & Battery buyback", ab, abP],
    covs.has("gl") && hnoaP > 0 && ["Hired & Non-Owned Auto", "", hnoaP],
    covs.has("gl") && dtrP > 0 && ["Damage to Rented Premises", "", dtrP],
    covs.has("gl") && elP > 0 && ["Employers Liability / Stop Gap", glLimit, elP],
    covs.has("prop") && ["Commercial Property", "$" + propDed + " AOP ded.", (propOver || ghProp) ? null : propP],
    ebP > 0 && ["Equipment Breakdown", "", ebP],
    enhP > 0 && ["Property Enhancement", "", enhP],
    tria && ["TRIA (terrorism)", "", triaP],
  ].filter(Boolean);
  /* WFR-1906 Task 4: assemble the real-submission payload PARTS from the LIVE
     rating state in this scope (the parent's submitToCarma adds broker + turnstile).
     Only invoked at click time, so it may reference consts declared later in this
     function body. `notes` is the gateNotes value (RatingScreen carries none → ""). */
  const buildCarmaParts = (notes) => ({
    gateNotes: notes || "",
    /* WFR-2822: both call sites share this builder — the referral path and the
       clean-indication path — so an agent who fills the referral slots and then
       lands on a clean indication still has their documents travel. On the pure
       clean path every slot is empty, so this is [] and nothing changes. */
    attachments: REF_DOC_SLOTS.reduce((acc, s) =>
      acc.concat((refDocs[s.id] || []).map((d) => ({ ...d, slot: s.label }))), []),
    rating: {
      carrier: "Knight Specialty Insurance Company",
      schedule: indCovLines.map(([coverage, sub, premium]) => ({ coverage, limit: sub || "", premium: premium == null ? null : premium })),
      premium: subtotal,
      fees: adminFee + uwFee + multiLocFee,
      slTax: tax,
      total,
    },
    sections: {
      generalInfo: {
        applicantName: broker.insured,
        // WFR-1906: the producing AGENT (this Carma-direct quoter's submitter is
        // the agent/producer). Carried on general_info.agent (keyed by email) and
        // persisted via cannabis-v1's passthrough schema. insured stays the named
        // insured (applicantName).
        agent: { name: broker.name, email: broker.email, phone: broker.phone, agency: broker.agency },
        mailingAddress: locations[0] ? locations[0].addr || "" : "",
        // WFR-1906 (review finding #4): the prototype's location model (newLocation())
        // has no structured city field — addr is a single free-text "Street, city, state"
        // input — so there is no reliable value to project here. Heuristic parsing of the
        // combined address string is deferred as a fast-follow (§6d MVP gap). Leave "".
        city: "",
        state: gateAnswers.state || "",
        zipCode: locations[0] ? locations[0].zip || "" : "",
        effectiveDate: broker.effDate || "",
        operations: mapRiskIdsToOperations(risks),
        grossSalesLast12: String(sales || 0),
        coverageWants: { generalLiability: covs.has("gl") ? "yes" : "no", productsLiability: covs.has("prod") ? "yes" : "no" },
      },
      locations: locations.map((l) => ({
        physicalAddress: l.addr || "",
        city: "",
        state: gateAnswers.state || "",
        zipCode: l.zip || "",
        squareFootage: Number(l.sqft) || 0,
        buildingValue: String(l.bldg || 0),
        businessPersonalProperty: String(l.bpp || 0),
        growOperations: mapRiskIdsToGrowOperations(risks),
      })),
    },
    indicationHtml:
      "<table>" +
      indCovLines.map(([coverage, sub, premium]) => "<tr><td>" + escapeHtml(coverage) + (sub ? " (" + escapeHtml(sub) + ")" : "") + "</td><td>" + (premium == null ? "REFER" : escapeHtml(fmt(premium))) + "</td></tr>").join("") +
      "</table>" +
      "<div><div>Indicated premium: " + fmt(subtotal) + "</div>" +
      "<div>Fees: " + fmt(adminFee + uwFee + multiLocFee) + "</div>" +
      "<div>Estimated SL taxes &amp; fees: " + fmt(tax) + "</div>" +
      "<div>Estimated total cost: " + fmt(total) + "</div></div>" +
      "<p>This is a non-binding indication, provided for information purposes only. It is not an offer of coverage and cannot be bound or issued.</p>",
  });
  /* Per-location schedule rows built from the LIVE locCalcs. */
  const constrLabel = (v) => (CONSTRUCTION.find((x) => x.v === v) || {}).l || v || "—";
  const roofLabel = (v) => (ROOF_TYPES.find((x) => x.v === v) || {}).l || "—";
  const indLocRows = locCalcs.map((c) => {
    const windNote = c.windRefer
      ? "Wind/Hail not available at this ZIP — refer to underwriting"
      : c.wind
        ? (c.wind.deductible === "AOP IS ACCEPTABLE"
            ? "Wind/Hail: AOP deductible acceptable (subject to UW review)"
            : "Wind/Hail deductible: " + c.wind.deductible)
        : "";
    const referNote = [
      c.over && "Property TIV exceeds the $5,000,000 per-location maximum — refer to underwriting",
      c.gh && "Greenhouse construction — property not available via the online indication",
      c.bldRefer && "Building over 75 years old — prior underwriting approval required",
      c.bldIneligible && "Building over 50 years old with no recorded system updates — ineligible per property guidelines 5.2",
    ].filter(Boolean).join(" · ");
    return {
      idx: c.idx,
      addr: c.loc.addr || "—",
      zip: c.loc.zip || "—",
      constr: constrLabel(c.loc.constr),
      roof: roofLabel(c.loc.roof),
      sqft: c.loc.sqft ? c.loc.sqft.toLocaleString() + " sq ft" : "—",
      bldg: c.loc.bldg, tib: c.tibEff, bpp: c.loc.bpp, bi: c.loc.bi, stock: c.loc.stock, cann: c.loc.cann,
      tiv: c.tiv,
      premium: c.raw,
      windNote,
      itvNote: c.itvFlag ? ("Building limit implies " + fmt(c.itvPerSqft) + "/sq ft, below the $150/sq ft ITV benchmark — confirm 80% replacement-cost valuation") : "",
      referNote,
    };
  });
  const SUBJECTIVITIES = [
    "Signed and dated application received within 30 days.",
    "Currently valued loss runs (3–5 years, or since inception if newer).",
    "Active, validated state and local cannabis license(s) at binding.",
    "Walk-through video of all insured locations within 30 days.",
    "Satisfactory underwriting inspection (where ordered).",
    "Confirmation of central-station alarm and video surveillance at all entry points.",
    "Buildings insured to at least 80% of replacement cost.",
  ];
  const indicationSummaryLines = (rn) => [
    "CARMA NON-BINDING INDICATION " + rn,
    "Generated: " + new Date().toLocaleString(),
    "Named insured: " + broker.insured,
    broker.effDate && "Effective date: " + broker.effDate,
    broker.needBy && "Need by: " + broker.needBy,
    "Broker: " + broker.name + (broker.agency ? " \u00b7 " + broker.agency : ""),
    "Contact: " + broker.email + (broker.phone ? " \u00b7 " + broker.phone : ""),
    "Risk classification: " + riskClassLabel(risks, otherDesc),
    "Lines: " + [...covs].join(", "),
    "Locations: " + locCalcs.length,
    "",
    "COVERAGES (indicated premium):",
    ...indCovLines.map(([l, sub, p]) => "  " + l + (sub ? " (" + sub + ")" : "") + ": " + (p == null ? "REFER" : fmt(p))),
    "",
    "Indicated premium: " + fmt(subtotal),
    "Admin/UW fees: " + fmt(adminFee + uwFee) + (fee3pct ? " (rater fee 3% of premium)" : ""),
    multiLocFee > 0 && "Multi-location fee: " + fmt(multiLocFee),
    "Estimated SL taxes & fees: " + fmt(tax),
    "ESTIMATED TOTAL COST: " + fmt(total),
    "",
    "This is a non-binding indication, provided for information purposes only. It is not an offer of coverage and cannot be bound or issued.",
  ].filter((x) => x !== false).join("\n");
  /* Recipients: the broker/user email PLUS underwriting@ + sales@. */
  const indRecipients = [broker.email.trim(), ...REFERRAL_TO].filter(Boolean);
  const doSendIndication = () => {
    /* WFR-1906 Task 4: real Lambda POST runs IN ADDITION to the simulated dispatch below.
       SIMULATED email dispatch (adjudication 61, prototype): the composed payload
       is stored in state and rendered in the confirmation/preview only — nothing
       leaves the browser. PRODUCTION: replace this block with the backend mailer
       call (Iteration 1). Reference number prefixed IND- to distinguish the
       non-binding indication from referrals (REF-) and brokerage submissions. */
    const rn = "IND-" + Date.now().toString(36).toUpperCase();
    setIndEmail({
      to: indRecipients.join(", "),
      subject: newSubmissionSubject(broker.insured, broker.effDate, riskClassLabel(risks, otherDesc), broker.needBy),
      body: indicationSummaryLines(rn),
    });
    setIndRefNum(rn);
    setIndSent(true);
    if (submitToCarma) void submitToCarma("clean", buildCarmaParts(""));
  };
  /* Print just the indication. jsdom has no window.print -> guard it so tests
     don't throw. PRODUCTION: this prints the indication-only container that is
     marked with the @media print rules below. */
  const printIndication = () => { if (typeof window !== "undefined" && window.print) window.print(); };
  const resetIndication = () => { setIndSent(false); setIndOpen(false); setShowIndEmail(false); setIndEmail(null); };

  /* EVP revision 6/12/2026: "Other" classification routes DIRECTLY to the
     referral panel — no rating screen, no premium shown. */
  if (forceReferral) {
    return (
      <div style={{ maxWidth: 860, margin: "0 auto", padding: "28px 24px 80px" }}>
        <button onClick={onBack} style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.08em", border: "none",
          background: "none", color: C.dim, cursor: "pointer", padding: 0, marginBottom: 14 }}>← BACK TO ELIGIBILITY</button>
        <div style={{ fontFamily: font.mono, fontSize: 11, color: C.amber, letterSpacing: "0.14em", marginBottom: 6 }}>STEP 4 — UNDERWRITING REFERRAL</div>
        <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>Unclassified risk.</h2>
        <p style={{ color: C.dim, fontSize: 14, margin: "0 0 8px" }}>
          We'll classify this with underwriting and come back with a path. Attach the application and tell us about the operations below —
          that's all we need to get started.
        </p>
        {otherDesc && (
          <div style={{ fontSize: 13, padding: "10px 14px", borderRadius: 5, background: "#F8F1E2", color: "#75500F",
            marginBottom: 18, lineHeight: 1.55 }}>
            <strong>Operations described:</strong> {otherDesc}
          </div>
        )}
        {(!refSent || seamActive) ? (
          <div ref={refPanelRef} style={{ background: C.panel, border: `1px solid ${C.line}`, borderTop: `4px solid ${C.accent}`,
            borderRadius: 6, padding: "18px 20px" }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 8 }}>
              REFERRAL TO UNDERWRITING · SUBMISSION DOCUMENTS</div>
            <p style={{ fontSize: 12.5, color: C.dim, margin: "0 0 12px", lineHeight: 1.5 }}>
              Attach the application — everything else strengthens the file.
            </p>
            {REF_DOC_SLOTS.map((s) => (
              <DocSlot key={s.id} slot={s.id} label={s.label} required={s.required}
                files={refDocs[s.id]} add={addDoc(s.id)} remove={rmDoc(s.id)} totalCount={totalDocCount} />
            ))}
            {/* WFR-2827 (F-1): the ceiling is global and enforced server-side —
                show the running total so it is visible before Send, not after a 400. */}
            <div data-doc-total style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.08em",
              color: totalDocCount >= MAX_FILES ? C.red : C.dim, margin: "2px 0 10px" }}>
              {totalDocCount}/{MAX_FILES} DOCUMENTS ATTACHED{totalDocCount >= MAX_FILES ? " \u00b7 LIMIT REACHED" : ""}
            </div>
            <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: C.dim }}>BROKER & INSURED INFORMATION</div>
            <BrokerFields b={broker} set={setBroker} showNeedBy={true} />
            <button disabled={!canSendReferral || submitting || submitSuccess} onClick={doSendReferral}
              style={{ width: "100%", marginTop: 10, padding: "12px", borderRadius: 5, border: "none",
                background: canSendReferral ? C.accent : "#F7B6B7", color: "#fff", fontWeight: 700, fontSize: 14,
                cursor: canSendReferral ? "pointer" : "not-allowed" }}>
              Send referral to underwriting ({refDocCount} doc{refDocCount === 1 ? "" : "s"}) →
            </button>
            {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
            {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
            {!docsOk && <div style={{ fontSize: 11, fontWeight: 600, color: C.red, marginTop: 6 }}>Application is required to submit to underwriting.</div>}
            {docsOk && !brokerOk && <div style={{ fontSize: 10.5, color: C.dim, marginTop: 6 }}>Insured name and broker name & email are required to send.</div>}
          </div>
        ) : (
          <div style={{ background: C.panel, border: `1.5px solid ${C.green}`, borderRadius: 6, padding: "20px 22px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14 }}>
              <span style={{ width: 36, height: 36, borderRadius: "50%", background: C.green, color: "#fff", flex: "0 0 auto",
                display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 19, fontWeight: 700 }}>✓</span>
              <div>
                <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.14em", color: C.green }}>REFERRAL SENT TO UNDERWRITING</div>
                <div style={{ fontFamily: font.mono, fontSize: 24, fontWeight: 600, letterSpacing: "0.06em" }}>{refRefNum}</div>
              </div>
            </div>
            <div style={{ fontSize: 13, padding: "10px 12px", borderRadius: 4, background: "#EAF3EC", color: "#174D2B", lineHeight: 1.6, marginBottom: 12 }}>
              Your submission has been emailed to underwriting — {REFERRAL_TO.join(" and ")}.{" "}
              <strong>Expect a response from your underwriter within 24–48 hours.</strong>
            </div>
            <div style={{ fontSize: 13, lineHeight: 1.7, marginBottom: 12 }}>
              <div><strong>Named insured:</strong> {broker.insured}</div>
              {broker.effDate && <div><strong>Effective date:</strong> {broker.effDate}</div>}
              {broker.needBy && <div><strong>Need by:</strong> {broker.needBy}</div>}
              {otherDesc && <div><strong>Risk classification:</strong> Other — {otherDesc}</div>}
              <div><strong>Referral reasons:</strong> Unclassified risk — classification required</div>
            </div>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 6 }}>DOCUMENTS ATTACHED</div>
            <div style={{ fontSize: 12.5, lineHeight: 1.7, marginBottom: 12 }}>
              {REF_DOC_SLOTS.map((s) => (
                <div key={s.id}><strong>{s.label}:</strong>{" "}
                  {refDocs[s.id].length ? refDocs[s.id].map((f) => f.name).join("; ") : "none"}
                </div>
              ))}
            </div>
            <button onClick={() => setShowEmail(!showEmail)}
              style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.06em", border: "none", background: "none",
                color: C.dim, cursor: "pointer", padding: 0, marginBottom: 8 }}>
              {showEmail ? "▾" : "▸"} Email payload (prototype preview)
            </button>
            {showEmail && refEmail && (
              <textarea readOnly value={`To: ${refEmail.to}\nSubject: ${refEmail.subject}\n\n${refEmail.body}`}
                style={{ width: "100%", height: 150, padding: 8, borderRadius: 4, border: `1px solid ${C.line}`, display: "block",
                  background: "#fff", color: C.ink, fontFamily: font.mono, fontSize: 10, lineHeight: 1.5, resize: "vertical", marginBottom: 8 }} />
            )}
            <div style={{ display: "flex", gap: 8, marginTop: 4, flexWrap: "wrap" }}>
              <button onClick={resetReferral}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.green}`, background: "#fff", color: C.green,
                  fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Back to indication →
              </button>
              <button onClick={onBack}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.line}`, background: "#fff", color: C.ink,
                  fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Start a new submission →
              </button>
            </div>
          </div>
        )}
      </div>
    );
  }

  /* ====================================================================
     ADJUDICATION 61 — INDICATION OVERLAY. Reached when the agent clicks
     "Email & print indication →" on the rating screen. Renders the non-binding
     indication (built from the LIVE rated submission), the simulated-email
     confirmation, the expandable email-payload preview, and the print control.
     The indication-only printable container is wrapped so window.print() (guarded)
     yields a clean print of JUST the indication.
     ==================================================================== */
  if (indOpen) {
    const Money = ({ label, v }) => (v ? (
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, padding: "2px 0", color: C.ink }}>
        <span style={{ color: C.dim }}>{label}</span><span style={{ fontFamily: font.mono }}>{fmt(v)}</span>
      </div>
    ) : null);
    const indication = (
      <div data-ind-printable style={{ background: "#fff", border: `1px solid ${C.line}`, borderRadius: 8, overflow: "hidden" }}>
        {/* Carma masthead — generic, no 'Knight'. */}
        <div style={{ background: C.ink, color: "#fff", padding: "20px 26px", display: "flex", justifyContent: "space-between", alignItems: "flex-end" }}>
          <div>
            <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 26, letterSpacing: "0.02em" }}>Carma</div>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.18em", color: "#94A3B8", marginTop: 2 }}>CANNABIS PROGRAM · INDICATION</div>
          </div>
          <div style={{ fontFamily: font.mono, fontSize: 10, color: "#94A3B8", textAlign: "right", lineHeight: 1.6 }}>
            {indSent && <div>{indRefNum}</div>}
            <div>{new Date().toLocaleDateString()}</div>
          </div>
        </div>
        {/* NON-BINDING banner */}
        <div style={{ background: "#FBE9EA", color: "#8A1F23", padding: "12px 26px", fontSize: 12.5, lineHeight: 1.55, borderBottom: `1px solid ${C.line}` }}>
          <strong style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.12em" }}>NON-BINDING INDICATION</strong><br />
          This is a non-binding indication, provided for information purposes only. It is not an offer of coverage and cannot be bound or issued.
        </div>
        <div style={{ padding: "20px 26px" }}>
          {/* Insured + dates */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "6px 24px", fontSize: 13, marginBottom: 18 }}>
            <div><span style={{ color: C.dim }}>Named insured: </span><strong>{broker.insured || "—"}</strong></div>
            <div><span style={{ color: C.dim }}>Risk class: </span>{riskClassLabel(risks, otherDesc)}</div>
            <div><span style={{ color: C.dim }}>Effective date: </span>{broker.effDate || "—"}</div>
            <div><span style={{ color: C.dim }}>Need by: </span>{broker.needBy || "—"}</div>
            <div><span style={{ color: C.dim }}>Broker: </span>{broker.name || "—"}{broker.agency ? " · " + broker.agency : ""}</div>
            <div><span style={{ color: C.dim }}>Contact: </span>{broker.email || "—"}</div>
          </div>
          {/* Per-location schedule */}
          {covs.has("prop") && (
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 8 }}>LOCATION SCHEDULE</div>
              {indLocRows.map((r) => (
                <div key={r.idx} style={{ border: `1px solid ${C.line}`, borderRadius: 6, padding: "12px 14px", marginBottom: 8 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, fontWeight: 700, marginBottom: 6 }}>
                    <span>Location {r.idx + 1} — {r.addr}{r.zip !== "—" ? ", " + r.zip : ""}</span>
                    <span style={{ fontFamily: font.mono, color: r.premium ? C.ink : C.amber }}>{r.referNote ? "REFER" : (r.premium ? fmt(r.premium) : "—")}</span>
                  </div>
                  <div style={{ fontSize: 12, color: C.dim, marginBottom: 6 }}>
                    {r.constr} · Roof: {r.roof} · {r.sqft} · TIV {fmt(r.tiv)}
                  </div>
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 24px" }}>
                    <Money label="Building" v={r.bldg} />
                    <Money label="Tenant improvements" v={r.tib} />
                    <Money label="Business personal property" v={r.bpp} />
                    <Money label="Business income" v={r.bi} />
                    <Money label="Stock" v={r.stock} />
                    <Money label="Cannabis stock" v={r.cann} />
                  </div>
                  {r.windNote && <div style={{ fontSize: 11.5, color: C.dim, marginTop: 6 }}>{r.windNote}</div>}
                  {r.itvNote && <div style={{ fontSize: 11.5, color: C.amber, marginTop: 4 }}>{r.itvNote}</div>}
                  {r.referNote && <div style={{ fontSize: 11.5, color: "#8A1F23", marginTop: 4 }}>{r.referNote}</div>}
                </div>
              ))}
            </div>
          )}
          {/* Coverage lines + indicated premiums */}
          <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 8 }}>COVERAGES · INDICATED PREMIUM</div>
          <div style={{ marginBottom: 16 }}>
            {indCovLines.map(([l, sub, p], i) => (
              <div key={i} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, padding: "5px 0", borderBottom: `1px solid ${C.line}` }}>
                <span>{l}{sub ? <span style={{ color: C.dim }}> · {sub}</span> : null}</span>
                <span style={{ fontFamily: font.mono, fontWeight: 600, color: p == null ? C.amber : C.ink }}>{p == null ? "REFER" : fmt(p)}</span>
              </div>
            ))}
          </div>
          {/* Premium / fees / taxes / total */}
          <div style={{ background: C.paper, borderRadius: 6, padding: "12px 16px", marginBottom: 18 }}>
            <Money label="Indicated premium" v={subtotal} />
            <Money label={fee3pct ? "Rater fee (3% of premium)" : "Admin fee"} v={adminFee} />
            <Money label="UW fee" v={uwFee} />
            {multiLocFee > 0 && <Money label={"Multi-location fee (" + multiLocCount + " add'l)"} v={multiLocFee} />}
            <Money label="Estimated SL taxes & fees" v={tax} />
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 16, fontWeight: 800, paddingTop: 8, marginTop: 4, borderTop: `2px solid ${C.ink}` }}>
              <span style={{ fontFamily: font.display }}>ESTIMATED TOTAL COST</span>
              <span style={{ fontFamily: font.mono }}>{fmt(total)}</span>
            </div>
            <div style={{ fontFamily: font.mono, fontSize: 9.5, color: C.dim, marginTop: 4 }}>25% MINIMUM EARNED AT BINDING · SUBJECT TO AUDIT</div>
          </div>
          {/* Subjectivities */}
          <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 6 }}>SUBJECTIVITIES</div>
          <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: C.ink, lineHeight: 1.7 }}>
            {SUBJECTIVITIES.map((s, i) => <li key={i}>{s}</li>)}
          </ul>
        </div>
      </div>
    );
    return (
      <div style={{ maxWidth: 860, margin: "0 auto", padding: "28px 24px 80px" }}>
        {/* Print rules: when printing, show ONLY the indication printable container. */}
        <style>{`@media print { body * { visibility: hidden !important; } [data-ind-printable], [data-ind-printable] * { visibility: visible !important; } [data-ind-printable] { position: absolute; left: 0; top: 0; width: 100%; } }`}</style>
        <button data-no-print onClick={resetIndication} style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.08em", border: "none",
          background: "none", color: C.dim, cursor: "pointer", padding: 0, marginBottom: 14 }}>← BACK TO INDICATION</button>
        {(!indSent || seamActive) ? (<>
          <div data-no-print style={{ background: C.panel, border: `1px solid ${C.line}`, borderTop: `4px solid ${C.accent}`, borderRadius: 6, padding: "16px 20px", marginBottom: 18 }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 8 }}>EMAIL & PRINT THIS INDICATION</div>
            <p style={{ fontSize: 12.5, color: C.dim, margin: "0 0 10px", lineHeight: 1.5 }}>
              This non-binding indication will be emailed to you and to Carma underwriting. Confirm the broker/insured contact below.
            </p>
            <BrokerFields b={broker} set={setBroker} showNeedBy={true} />
            {!indEmailOk && <div style={{ fontSize: 11, color: C.accent, marginTop: 6 }}>A broker/user email is required so the indication has a recipient.</div>}
            <button data-ind-send onClick={doSendIndication} disabled={!indEmailOk || submitting || submitSuccess}
              style={{ width: "100%", marginTop: 10, padding: "12px", borderRadius: 5, border: "none",
                background: indEmailOk ? C.red : "#E2E8F0", color: indEmailOk ? "#fff" : "#94A3B8",
                fontWeight: 700, fontSize: 14, cursor: indEmailOk ? "pointer" : "not-allowed" }}>
              Email & print indication →
            </button>
            {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
            {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
          </div>
          {indication}
        </>) : (<>
          <div data-no-print style={{ background: C.panel, border: `1.5px solid ${C.green}`, borderRadius: 6, padding: "16px 20px", marginBottom: 18 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
              <span style={{ width: 32, height: 32, borderRadius: "50%", background: C.green, color: "#fff", flex: "0 0 auto",
                display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 17, fontWeight: 700 }}>✓</span>
              <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.12em", color: C.green }}>INDICATION EMAILED · {indRefNum}</div>
            </div>
            <div style={{ fontSize: 13, padding: "10px 12px", borderRadius: 4, background: "#EAF3EC", color: "#174D2B", lineHeight: 1.6, marginBottom: 12 }}>
              Indication emailed to {broker.email} and underwriting (underwriting@carma365.com, sales@carma365.com).
            </div>
            <button data-no-print onClick={() => setShowIndEmail(!showIndEmail)}
              style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.06em", border: "none", background: "none", color: C.dim, cursor: "pointer", padding: 0, marginBottom: 8 }}>
              {showIndEmail ? "▾" : "▸"} Email payload (prototype preview)
            </button>
            {showIndEmail && indEmail && (
              <textarea readOnly value={`To: ${indEmail.to}\nSubject: ${indEmail.subject}\n\n${indEmail.body}`}
                style={{ width: "100%", height: 180, padding: 8, borderRadius: 4, border: `1px solid ${C.line}`, display: "block",
                  background: "#fff", color: C.ink, fontFamily: font.mono, fontSize: 10, lineHeight: 1.5, resize: "vertical", marginBottom: 10 }} />
            )}
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <button data-ind-print onClick={printIndication}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.ink}`, background: C.ink, color: "#fff", fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Print indication
              </button>
              <button onClick={resetIndication}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.green}`, background: "#fff", color: C.green, fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Back to indication →
              </button>
            </div>
          </div>
          {indication}
        </>)}
      </div>
    );
  }

  return (
    <div style={{ maxWidth: 1180, margin: "0 auto", padding: "28px 24px 80px", display: "flex", gap: 28, flexWrap: "wrap" }}>
      <main style={{ flex: "1 1 600px", minWidth: 320 }}>
        <button onClick={onBack} style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.08em", border: "none",
          background: "none", color: C.dim, cursor: "pointer", padding: 0, marginBottom: 14 }}>← BACK TO ELIGIBILITY</button>
        <div style={{ fontFamily: font.mono, fontSize: 11, color: C.red, letterSpacing: "0.14em", marginBottom: 6 }}>STEP 4 — RATING</div>
        <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>Watch the number build.</h2>
        <p style={{ color: C.dim, fontSize: 14, margin: "0 0 22px" }}>
          Every field moves the indication on the right. GL carries no deductible. Limits above $2M require underwriter referral. Property maximum is $5M per location.
        </p>
        {synthEx && covs.has("prod") && (
          <div style={{ fontSize: 13, padding: "10px 14px", borderRadius: 6, background: "#F8F1E2", color: "#75500F", marginBottom: 14, lineHeight: 1.5 }}>
            Products liability is ineligible with the program for this risk (excluded product categories) and is not included in this indication.
            Other options are available within Carma Brokerage.
          </div>
        )}

        {/* Exposure + allocation */}
        <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
          <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>EXPOSURE & REVENUE ALLOCATION</div>
          <div style={{ display: "flex", flexWrap: "wrap" }}>
            <Field label="Projected 12-month gross sales"><Money value={sales} onChange={setSales} /></Field>
            {risks.has("cult_out") && <Field label="Outdoor cultivation — acres" hint="Outdoor crop is not eligible for property coverage."><Money value={acres} onChange={setAcres} w={100} /></Field>}
          </div>
          {liabRisks.length > 1 && (
            <div>
              <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 6 }}>
                Revenue allocation by operation {!allocOk && <span style={{ color: C.red }}>— must total 100% (now {allocTotal}%)</span>}
              </div>
              {liabRisks.map((r) => (
                <div key={r} style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
                  <span style={{ fontSize: 12.5, fontWeight: 600, width: 240 }}>{CLASS_MAP[r].name}</span>
                  <input type="text" inputMode="numeric" value={alloc[r]}
                    onChange={(e) => {
                      const v = e.target.value.replace(/[^0-9]/g, "").slice(0, 3);
                      setAlloc((prev) => ({ ...prev, [r]: v }));
                    }}
                    placeholder="0"
                    style={{ fontFamily: font.mono, width: 64, padding: "6px 8px", borderRadius: 4, border: `1.5px solid ${C.line}`, textAlign: "right" }} />
                  <span style={{ fontFamily: font.mono, fontSize: 12, color: C.dim }}>%</span>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Limits */}
        <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
          <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>LIMITS & STRUCTURE</div>
          <div style={{ display: "flex", flexWrap: "wrap" }}>
            {covs.has("gl") && <Field label="GL limit (occurrence)" hint="No deductible offered on GL. Limits above $2M require referral.">
              <Sel value={glLimit} onChange={setGlLimit} options={GL_ILF.map((x) => x.k)} /></Field>}
            {plActive && <>
              <Field label="Products limit (claims-made, DITL)" hint="Defense inside the limit. Limits above $2M require referral.">
                <Sel value={plLimit} onChange={setPlLimit} options={PL_ILF.map((x) => x.k)} /></Field>
              <Field label="Products deductible"><Sel value={plDed} onChange={setPlDed} options={PL_DED} w={110} /></Field>
              <Field label="Retroactive date" hint="Pick the tier that fully covers the retro period — always round up (a 2.2-year retro uses the 3 yr tier)."><Sel value={retro} onChange={setRetro} options={RETRO.map((x) => x.k)} w={110} /></Field>
            </>}
          </div>
          {plActive && (
            <button onClick={() => setDotl(!dotl)}
              style={{ padding: "9px 14px", borderRadius: 5, fontSize: 13, fontWeight: 600, cursor: "pointer",
                border: `1.5px solid ${dotl ? C.green : C.line}`, background: dotl ? "#EAF3EC" : "#fff", color: C.ink }}>
              {dotl ? "✓ " : "+ "}Defense Outside the Limit buyback (C 05 GL 402)
            </button>
          )}
        </div>

        {/* Property — per-location (adj. 52). Location cards render in order; Location 1
            carries the same fields/order the single-location screen used. Shared policy-level
            controls (Equipment Breakdown, deductible, enhancement) follow the location cards. */}
        {covs.has("prop") && (<>
          {locCalcs.map((c) => {
            const loc = c.loc; const idx = c.idx;
            return (
            <div key={loc.uid} style={{ background: C.panel, border: `1px solid ${C.line}`,
              borderTop: `4px solid ${C.accent}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 12, flexWrap: "wrap", gap: 8 }}>
                <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim }}>
                  LOCATION {idx + 1} · OCCUPANCY: {occ.label.toUpperCase()}
                </div>
                {locations.length > 1 && (
                  <button onClick={() => removeLocation(idx)}
                    style={{ fontFamily: font.mono, fontSize: 10.5, letterSpacing: "0.06em", border: `1px solid ${C.line}`,
                      background: "#fff", color: C.red, borderRadius: 4, padding: "4px 10px", cursor: "pointer" }}>
                    REMOVE LOCATION {idx + 1}
                  </button>
                )}
              </div>
              <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
                <Field label="Location address">
                  <input type="text" value={loc.addr} placeholder="Street, city, state"
                    onChange={(e) => setLoc(idx, { addr: e.target.value })}
                    style={{ fontFamily: font.body, fontSize: 13, padding: "8px 10px", borderRadius: 4,
                      border: `1.5px solid ${C.line}`, width: 280, background: "#fff" }} />
                </Field>
                <Field label="Location ZIP code" hint="Wind/hail eligibility and minimum deductible derive automatically — no wind questions to answer.">
                  <input type="text" inputMode="numeric" value={loc.zip} placeholder="00000"
                    onChange={(e) => setLoc(idx, { zip: e.target.value.replace(/[^0-9]/g, "").slice(0, 5) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4,
                      border: `1.5px solid ${C.line}`, width: 110, background: "#fff", letterSpacing: "0.08em" }} />
                </Field>
                {c.wind && (
                  <div style={{ marginBottom: 14, marginRight: 20, fontSize: 12.5, fontWeight: 600, padding: "9px 12px", borderRadius: 4,
                    background: c.windRefer ? "#FBE9EA" : c.wind.status === "clear" ? "#EAF3EC" : "#F8F1E2",
                    color: c.windRefer ? "#8A1F23" : c.wind.status === "clear" ? "#174D2B" : "#75500F", maxWidth: 380 }}>
                    {/* adj. 58: surface the minimum wind/hail deductible from the rater Wind table */}
                    {c.wind.tier} · {c.windRefer
                      ? "Wind/Hail not available at this ZIP — refer to underwriting."
                      : c.wind.deductible === "AOP IS ACCEPTABLE"
                        ? "Wind/Hail: AOP deductible acceptable — subject to change upon underwriting review."
                        : "Wind/Hail deductible: " + c.wind.deductible}
                  </div>
                )}
              </div>
              <div style={{ display: "flex", flexWrap: "wrap" }}>
                <Field label="Construction" hint={loc.constr === "GH" ? "Property is not available on greenhouse construction through the online indication — underwriting submission required. Wind is not offered on greenhouse structures." : undefined}>
                  <Sel value={loc.constr} onChange={(v) => setLoc(idx, { constr: v })} options={CONSTRUCTION} w={210} /></Field>
                <Field label="Roof type">
                  <Sel value={loc.roof} onChange={(v) => setLoc(idx, { roof: v })} options={ROOF_TYPES} w={210} /></Field>
                <Field label="Building square footage">
                  <input type="text" inputMode="numeric" value={loc.sqft === 0 ? "" : loc.sqft.toLocaleString()} placeholder="0"
                    onChange={(e) => setLoc(idx, { sqft: num(e.target.value) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4,
                      border: `1.5px solid ${C.line}`, width: 130, background: "#fff" }} />
                </Field>
                <Field label="Sprinklered?"><YesNo value={loc.sprinklered} onChange={(v) => setLoc(idx, { sprinklered: v })} /></Field>
              </div>
              <div style={{ display: "flex", flexWrap: "wrap" }}>
                <Field label="Year built" hint="Buildings over 75 years require underwriting approval; over 50 require recorded system updates (guideline 5.2).">
                  <input type="text" inputMode="numeric" value={loc.yearBuilt} placeholder="YYYY"
                    onChange={(e) => setLoc(idx, { yearBuilt: e.target.value.replace(/[^0-9]/g, "").slice(0, 4) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4, border: `1.5px solid ${C.line}`, width: 90, background: "#fff" }} />
                </Field>
                <Field label="Roof updated (year)">
                  <input type="text" inputMode="numeric" value={loc.upRoof} placeholder="YYYY"
                    onChange={(e) => setLoc(idx, { upRoof: e.target.value.replace(/[^0-9]/g, "").slice(0, 4) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4, border: `1.5px solid ${C.line}`, width: 90, background: "#fff" }} />
                </Field>
                <Field label="Plumbing updated (year)">
                  <input type="text" inputMode="numeric" value={loc.upPlumb} placeholder="YYYY"
                    onChange={(e) => setLoc(idx, { upPlumb: e.target.value.replace(/[^0-9]/g, "").slice(0, 4) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4, border: `1.5px solid ${C.line}`, width: 90, background: "#fff" }} />
                </Field>
                <Field label="Wiring updated (year)">
                  <input type="text" inputMode="numeric" value={loc.upWiring} placeholder="YYYY"
                    onChange={(e) => setLoc(idx, { upWiring: e.target.value.replace(/[^0-9]/g, "").slice(0, 4) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4, border: `1.5px solid ${C.line}`, width: 90, background: "#fff" }} />
                </Field>
                <Field label="HVAC updated (year)">
                  <input type="text" inputMode="numeric" value={loc.upHvac} placeholder="YYYY"
                    onChange={(e) => setLoc(idx, { upHvac: e.target.value.replace(/[^0-9]/g, "").slice(0, 4) })}
                    style={{ fontFamily: font.mono, fontSize: 14, padding: "8px 10px", borderRadius: 4, border: `1.5px solid ${C.line}`, width: 90, background: "#fff" }} />
                </Field>
              </div>
              <div style={{ display: "flex", flexWrap: "wrap" }}>
                <Field label="Building"><Money value={loc.bldg} onChange={(v) => setLoc(idx, { bldg: v })} /></Field>
                <Field label="Tenant's Improvements & Betterments">
                  {loc.bldg > 0 ? (
                    <div style={{ width: 230 }}>
                      <Money value={0} onChange={() => {}} disabled={true} />
                      <div style={{ fontSize: 11, padding: "8px 10px", borderRadius: 4, background: "#F1F5F9",
                        color: "#243447", lineHeight: 1.45, marginTop: 6, maxWidth: 230 }}>
                        Tenant's Improvements &amp; Betterments are included in the building limit.
                      </div>
                    </div>
                  ) : (
                    <Money value={loc.tib} onChange={(v) => setLoc(idx, { tib: v })} />
                  )}
                </Field>
                <Field label="Business Personal Property / Contents"><Money value={loc.bpp} onChange={(v) => setLoc(idx, { bpp: v })} /></Field>
                <Field label="Business Income / Extra Expense"><Money value={loc.bi} onChange={(v) => setLoc(idx, { bi: v })} /></Field>
                <Field label="Finished Stock, ready for sale"><Money value={loc.stock} onChange={(v) => setLoc(idx, { stock: v })} /></Field>
                <Field label="Cannabis Inventory"><Money value={loc.cann} onChange={(v) => setLoc(idx, { cann: v })} /></Field>
              </div>
              <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: c.over || c.gh ? 8 : 0 }}>
                Location {idx + 1} TIV: {fmt(c.tiv)}
              </div>
              {c.over && <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F" }}>
                Location {idx + 1} TIV {fmt(c.tiv)} exceeds the $5,000,000 per-location program maximum — this location is referred to underwriting and is not rated online.</div>}
              {c.gh && <div style={{ fontSize: 12, marginTop: 8, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F" }}>
                Location {idx + 1} greenhouse construction — property is excluded from the online indication and is referred to underwriting. Wind is not offered on greenhouse structures.</div>}
              {/* adj. 59: insurance-to-value advisory (non-blocking) */}
              {c.itvFlag && <div style={{ fontSize: 12, marginTop: 8, padding: "8px 12px", borderRadius: 4, background: "#F1F5F9", color: "#243447" }}>
                Building limit implies {fmt(c.itvPerSqft)}/sq ft, below the $150/sq ft insurance-to-value benchmark. Confirm the building is insured to at least 80% of replacement cost; subject to underwriting valuation review.</div>}
              {/* adj. 60: building-criteria validation */}
              {c.bldRefer && <div style={{ fontSize: 12, marginTop: 8, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F" }}>
                Location {idx + 1} — building over 75 years old; prior underwriting approval required. This location is excluded from the online indication and referred to underwriting.</div>}
              {c.bldIneligible && <div style={{ fontSize: 12, marginTop: 8, padding: "8px 12px", borderRadius: 4, background: "#FBE9EA", color: "#8A1F23" }}>
                Location {idx + 1} — building over 50 years old with no recorded system updates; ineligible per property guidelines 5.2. This location is excluded from the online indication and submitted to underwriting.</div>}
            </div>
            );
          })}

          {/* Add-location control + SOV indicator (adj. 52) */}
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14, flexWrap: "wrap" }}>
            {locations.length < 3 && (
              <button onClick={addLocation}
                style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.06em", border: `1.5px solid ${C.accent}`,
                  background: "#fff", color: C.accent, borderRadius: 4, padding: "9px 16px", cursor: "pointer", fontWeight: 700 }}>
                + ADD LOCATION
              </button>
            )}
            <span style={{ fontSize: 11.5, color: C.dim }}>Up to 3 locations may be rated online.</span>
          </div>
          {locations.length >= 3 && (
            <div style={{ fontSize: 12.5, padding: "11px 14px", borderRadius: 5, background: "#F1F5F9", color: "#243447",
              lineHeight: 1.55, marginBottom: 14, borderLeft: `4px solid ${C.accent}` }}>
              <strong>Insuring more than three locations?</strong> Attach a Statement of Values (SOV) — additional locations are reviewed by underwriting.
            </div>
          )}

          {/* Shared policy-level property controls */}
          <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>PROPERTY — POLICY-LEVEL TERMS</div>
            <div style={{ fontSize: 12, padding: "10px 12px", borderRadius: 4, background: "#F1F5F9", color: "#243447", lineHeight: 1.5, marginBottom: 14 }}>
              Business Personal Property / Contents, Finished Stock, and Cannabis Inventory are all included under the same property
              coverage form. They are entered as separate limits per location so each is valued correctly and the right terms apply to each category.
            </div>
            <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
              <Field label="Include Equipment Breakdown coverage?"><YesNo value={eb} onChange={setEb} /></Field>
              <Field label="Property deductible"><Sel value={propDed} onChange={setPropDed} options={PROP_DED} w={110} /></Field>
            </div>
            <Field label="Would you like a Property Enhancement Endorsement? Includes property in transit limits."
              hint="Endorsement #1 provides a $10,000 property in transit limit; Endorsement #2 provides $50,000.">
              <Sel value={enh} onChange={setEnh} options={ENHANCEMENT.map((x) => x.k)} w={320} />
            </Field>
            {(gateAnswers.alarm === "no" || gateAnswers.video === "no") && (
              <div style={{ fontSize: 12, marginTop: 8, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F" }}>
                Protective safeguard shortfall from eligibility (central station alarm / 30-day video): theft coverage restricted.</div>
            )}
          </div>
        </>)}

        {/* Schedule credits */}
        <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 4, flexWrap: "wrap" }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim }}>SCHEDULE CREDIT REQUEST</div>
            <div style={{ fontFamily: font.mono, fontSize: 12, fontWeight: 600, color: appliedCredit > 0 ? C.green : C.dim }}>
              {appliedCredit}% applied{creditOver ? ` · ${requested}% requested` : ""}</div>
          </div>
          <p style={{ fontSize: 12, color: C.dim, margin: "0 0 12px", lineHeight: 1.5 }}>
            All credits and debits are subject to underwriting review and approval. Up to 20% total credit may be requested here —
            anything above 20% triggers an underwriter referral. A justification is required for each credit requested.
          </p>
          {CREDIT_CATS.map((c) => (
            <div key={c.id} style={{ marginBottom: 10, paddingBottom: 10, borderBottom: `1px solid ${C.line}` }}>
              <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                <span style={{ fontSize: 12.5, fontWeight: 700, width: 220 }}>{c.label}</span>
                <Sel value={credits[c.id]} onChange={(v) => setCredits({ ...credits, [c.id]: v })}
                  options={CREDIT_OPTIONS.map((o) => ({ v: o, l: o + "% credit" }))} w={110} />
                <span style={{ fontSize: 10.5, color: C.dim, flex: 1, minWidth: 180 }}>{c.why}</span>
              </div>
              {num(credits[c.id]) > 0 && (
                <input type="text" value={justif[c.id]} onChange={(e) => setJustif({ ...justif, [c.id]: e.target.value })}
                  placeholder="Justification required — what supports this credit?"
                  style={{ marginTop: 8, width: "100%", fontFamily: font.body, fontSize: 12.5, padding: "8px 10px",
                    borderRadius: 4, border: `1.5px solid ${justif[c.id].trim() ? C.line : C.amber}`, background: "#fff" }} />
              )}
            </div>
          ))}
          {missingJust.length > 0 && (
            <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F" }}>
              Credits without justification are not applied: {missingJust.map((c) => c.label).join(", ")}.
            </div>
          )}
        </div>

        {/* A&B */}
        {covs.has("gl") && (
          <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>ASSAULT & BATTERY BUYBACK · MAX 250/250</div>
            <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
              <Field label="Are armed guards present at any location?"><YesNo value={armed} onChange={setArmed} /></Field>
              {armed === "no" && <Field label="A&B sublimit"><Sel value={ab} onChange={setAb} options={AB_TIERS.map((x) => x.k)} w={150} /></Field>}
            </div>
            {armed === "yes" && <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#FFEBEB", color: "#A8262B" }}>
              A&B coverage is not offered where armed guards are present.</div>}
          </div>
        )}

        {/* ============ GENERAL LIABILITY — OPTIONS (adj. 53-57) ============ */}
        {covs.has("gl") && (
          <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>GENERAL LIABILITY — OPTIONS</div>

            {/* adj. 53 — dispensary dual-basis GL: surface a manual sqft input only when no
                Property location sqft is available, so the sqft basis can be computed. */}
            {dualBasisActive && locSqft <= 0 && (
              <Field label="Retail square footage (for GL basis)"
                hint={dualBasisNeedsSqft ? "Enter square footage to optimize the GL basis." : "Used to rate the dispensary GL on the lower of the sales and square-footage bases."}>
                <Money value={glSqft} onChange={setGlSqft} w={150} />
              </Field>
            )}

            <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
              {/* adj. 54 — Hired & Non-Owned Auto */}
              <Field label="Hired & Non-Owned Auto ($1,000,000 CSL)">
                <button onClick={() => setHnoaOn(!hnoaOn)}
                  style={{ padding: "9px 16px", borderRadius: 5, fontSize: 13, fontWeight: 600, cursor: "pointer",
                    border: `1.5px solid ${hnoaOn ? C.green : C.line}`, background: hnoaOn ? "#EAF3EC" : "#fff" }}>
                  {hnoaOn ? "✓ Selected" : "Add coverage"}</button>
              </Field>
              {hnoaOn && (
                <Field label="Employee count">
                  <Sel value={hnoaBand} onChange={setHnoaBand}
                    options={HNOA_BANDS.map((b) => ({ v: b.k, l: b.label }))} w={170} />
                </Field>
              )}
              {hnoaOn && (
                <Field label="Does the insured have any hired-auto costs/exposure?">
                  <YesNo value={hnoaExp} onChange={setHnoaExp} />
                </Field>
              )}
              {hnoaOn && (
                <Field label="Does the insured perform delivery?">
                  <YesNo value={hnoaDeliv} onChange={setHnoaDeliv} />
                </Field>
              )}
            </div>
            {hnoaExpDeclined && (
              <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#FFEBEB", color: "#A8262B", marginBottom: 12 }}>
                Hired & Non-Owned Auto is declined where hired-auto exposure exists — refer to Brokerage.</div>
            )}
            {hnoaDelivDeclined && (
              <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#FFEBEB", color: "#A8262B", marginBottom: 12 }}>
                Hired & Non-Owned Auto is not available where the insured performs delivery — delivery is a commercial-auto exposure; refer to Brokerage.</div>
            )}

            <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
              {/* adj. 55 / 63 — Damage to Rented Premises (DTR) */}
              <Field label="Damage to Rented Premises (DTR)">
                <Sel value={dtr} onChange={setDtr}
                  options={DTR_OPTS.map((o) => ({ v: o.k, l: o.label }))} w={190} />
              </Field>

              {/* adj. 57 — Medical Payments (no premium) */}
              <Field label="Medical Payments" hint="No additional premium.">
                <Sel value={medPay} onChange={setMedPay}
                  options={MEDPAY_OPTS.map((o) => ({ v: o.k, l: o.label }))} w={150} />
              </Field>

              {/* adj. 56 — Employers Liability / Stop Gap (monopolistic states only) */}
              {elEligible && (
                <Field label="Employers Liability / Stop Gap" hint="Available in monopolistic states (ND / OH / WA / WY).">
                  <button onClick={() => setElOn(!elOn)}
                    style={{ padding: "9px 16px", borderRadius: 5, fontSize: 13, fontWeight: 600, cursor: "pointer",
                      border: `1.5px solid ${elOn ? C.green : C.line}`, background: elOn ? "#EAF3EC" : "#fff" }}>
                    {elOn ? "✓ Selected" : "Add coverage"}</button>
                </Field>
              )}
            </div>
          </div>
        )}

        {/* Options */}
        <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px", marginBottom: 14 }}>
          <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 12 }}>OPTIONS</div>
          <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end" }}>
            <Field label="TRIA">
              <button onClick={() => setTria(!tria)}
                style={{ padding: "9px 16px", borderRadius: 5, fontSize: 13, fontWeight: 600, cursor: "pointer",
                  border: `1.5px solid ${tria ? C.green : C.line}`, background: tria ? "#EAF3EC" : "#fff" }}>
                {tria ? "✓ Accepted" : "Declined"}</button>
            </Field>
          </div>
          <div style={{ fontSize: 11.5, color: C.dim }}>Excess Liability is not included in the program — available by formal submission through Carma Brokerage.</div>
        </div>

        {/* SUBMISSION DOCUMENTS drop zone: only rendered when brokerage/captive submit is
            required (submitRequired path). The unified referral panel's four typed slots
            are the single document collection point everywhere else. (EVP revision 6/12/2026,
            adjudication 21: no duplicate document sections.) */}
        {submitRequired && (
          <div style={{ background: C.panel, border: `1px solid ${C.line}`, borderRadius: 6, padding: "18px 20px" }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 10 }}>SUBMISSION DOCUMENTS</div>
            <UploadZone files={files} setFiles={setFiles} compact totalCount={totalDocCount} />
          </div>
        )}

        {/* ============ REFERRAL TO UNDERWRITING — document panel (adjudication 21) ============ */}
        {refOpen && (!refSent || seamActive) && (
          <div ref={refPanelRef} style={{ background: C.panel, border: `1px solid ${C.line}`, borderTop: `4px solid ${C.accent}`,
            borderRadius: 6, padding: "18px 20px", marginTop: 14 }}>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 8 }}>
              {refers.length > 0 ? "REFERRAL TO UNDERWRITING · SUBMISSION DOCUMENTS" : "FIRM QUOTE REQUEST · SUBMISSION DOCUMENTS"}</div>
            <p style={{ fontSize: 12.5, color: C.dim, margin: "0 0 12px", lineHeight: 1.5 }}>
              Attach all submission documents. The application is required — everything else strengthens the file.
            </p>
            {refers.length > 0 && (
              <div style={{ fontSize: 12, padding: "8px 12px", borderRadius: 4, background: "#F8F1E2", color: "#75500F", marginBottom: 14, lineHeight: 1.5 }}>
                Referral items: {refers.join("; ")}.
              </div>
            )}
            {REF_DOC_SLOTS.map((s) => (
              <DocSlot key={s.id} slot={s.id} label={s.label} required={s.required}
                files={refDocs[s.id]} add={addDoc(s.id)} remove={rmDoc(s.id)} totalCount={totalDocCount} />
            ))}
            {/* WFR-2827 (F-1): the ceiling is global and enforced server-side —
                show the running total so it is visible before Send, not after a 400. */}
            <div data-doc-total style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.08em",
              color: totalDocCount >= MAX_FILES ? C.red : C.dim, margin: "2px 0 10px" }}>
              {totalDocCount}/{MAX_FILES} DOCUMENTS ATTACHED{totalDocCount >= MAX_FILES ? " \u00b7 LIMIT REACHED" : ""}
            </div>
            <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: C.dim }}>BROKER & INSURED INFORMATION</div>
            <BrokerFields b={broker} set={setBroker} showNeedBy={true} />
            <button disabled={!canSendRatingReferral || submitting || submitSuccess} onClick={doSendReferral}
              style={{ width: "100%", marginTop: 10, padding: "12px", borderRadius: 5, border: "none",
                background: canSendRatingReferral ? C.accent : "#F7B6B7", color: "#fff", fontWeight: 700, fontSize: 14,
                cursor: canSendRatingReferral ? "pointer" : "not-allowed" }}>
              {refers.length > 0 ? "Send referral to underwriting" : "Send firm quote request"} ({refDocCount} doc{refDocCount === 1 ? "" : "s"}) →
            </button>
            {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
            {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
            {!docsOk && (
              <div style={{ fontSize: 11, fontWeight: 600, color: C.red, marginTop: 6 }}>
                Application is required to submit to underwriting.
              </div>
            )}
            {docsOk && !brokerOk && (
              <div style={{ fontSize: 10.5, color: C.dim, marginTop: 6 }}>
                Insured name and broker name & email are required to send.
              </div>
            )}
            {docsOk && brokerOk && !ratingComplete && (
              <div style={{ fontSize: 11, color: C.red, marginTop: 6, lineHeight: 1.5 }}>
                Complete the rating before sending: {ratingOutstanding.join("; ")}.
              </div>
            )}
          </div>
        )}

        {/* ============ REFERRAL CONFIRMATION (adjudication 21) ============ */}
        {refSent && !seamActive && (
          <div style={{ background: C.panel, border: `1.5px solid ${C.green}`, borderRadius: 6, padding: "20px 22px", marginTop: 14 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14 }}>
              <span style={{ width: 36, height: 36, borderRadius: "50%", background: C.green, color: "#fff", flex: "0 0 auto",
                display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 19, fontWeight: 700 }}>✓</span>
              <div>
                <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.14em", color: C.green }}>{refers.length > 0 ? "REFERRAL SENT TO UNDERWRITING" : "FIRM QUOTE REQUEST SENT"}</div>
                <div style={{ fontFamily: font.mono, fontSize: 24, fontWeight: 600, letterSpacing: "0.06em" }}>{refRefNum}</div>
              </div>
            </div>
            <div style={{ fontSize: 13, padding: "10px 12px", borderRadius: 4, background: "#EAF3EC", color: "#174D2B", lineHeight: 1.6, marginBottom: 12 }}>
              Your submission has been emailed to underwriting — {REFERRAL_TO.join(" and ")}.{" "}
              <strong>Expect a response from your underwriter within 24–48 hours.</strong>
            </div>
            <div style={{ fontSize: 13, lineHeight: 1.7, marginBottom: 12 }}>
              <div><strong>Named insured:</strong> {broker.insured}</div>
              {broker.effDate && <div><strong>Effective date:</strong> {broker.effDate}</div>}
              {referredLines.length > 0 && <div><strong>Lines referred:</strong> {referredLines.join("; ")}</div>}
              {otherDesc && <div><strong>Risk classification:</strong> Other — {otherDesc}</div>}
              {(refers.length > 0 || forceReferral) && <div><strong>Referral reasons:</strong> {refers.length > 0 ? refers.join("; ") : "Unclassified risk — classification required"}</div>}
            </div>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 6 }}>DOCUMENTS ATTACHED</div>
            <div style={{ fontSize: 12.5, lineHeight: 1.7, marginBottom: 12 }}>
              {REF_DOC_SLOTS.map((s) => (
                <div key={s.id}><strong>{s.label}:</strong>{" "}
                  {refDocs[s.id].length ? refDocs[s.id].map((f) => f.name).join("; ") : "none"}
                </div>
              ))}
            </div>
            {/* Email payload (prototype preview) — collapsed by default. PRODUCTION:
                this block disappears; the payload is handed to the backend mailer
                (Iteration 1) instead of being stored in state. */}
            <button onClick={() => setShowEmail(!showEmail)}
              style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.06em", border: "none", background: "none",
                color: C.dim, cursor: "pointer", padding: 0, marginBottom: 8 }}>
              {showEmail ? "▾" : "▸"} Email payload (prototype preview)
            </button>
            {showEmail && refEmail && (
              <textarea readOnly value={`To: ${refEmail.to}\nSubject: ${refEmail.subject}\n\n${refEmail.body}`}
                style={{ width: "100%", height: 150, padding: 8, borderRadius: 4, border: `1px solid ${C.line}`, display: "block",
                  background: "#fff", color: C.ink, fontFamily: font.mono, fontSize: 10, lineHeight: 1.5, resize: "vertical", marginBottom: 8 }} />
            )}
            <div style={{ display: "flex", gap: 8, marginTop: 4, flexWrap: "wrap" }}>
              <button onClick={resetReferral}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.green}`, background: "#fff", color: C.green,
                  fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Back to indication →
              </button>
              <button onClick={onBack}
                style={{ padding: "8px 14px", borderRadius: 4, border: `1.5px solid ${C.line}`, background: "#fff", color: C.ink,
                  fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                Start a new submission →
              </button>
            </div>
          </div>
        )}
      </main>

      {/* INDICATION RAIL */}
      <aside style={{ flex: "0 1 320px", minWidth: 280 }}>
        <div style={{ position: "sticky", top: 20, background: C.ink, color: "#fff", borderRadius: 6, padding: "20px 22px" }}>
          <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.14em", color: "#94A3B8", marginBottom: 16 }}>LIVE INDICATION</div>
          {[
            covs.has("gl") && ["General Liability", glP, false, glMin],
            covs.has("prod") && ["Products (DITL)", plP, synthEx, plMin],
            covs.has("gl") && abP > 0 && ["A&B buyback", abP, false, abMin],
            covs.has("gl") && hnoaP > 0 && ["Hired & Non-Owned Auto", hnoaP, false, false],
            covs.has("gl") && dtrP > 0 && ["Damage to Rented Premises", dtrP, false, false],
            covs.has("gl") && elP > 0 && ["Employers Liability", elP, false, false],
            covs.has("prop") && ["Property", propOver || ghProp ? null : propP, false, propMin],
            ebP > 0 && ["Equipment Breakdown", ebP, false, false],
            enhP > 0 && ["Property Enhancement", enhP, false, false],
            tria && ["TRIA", triaP, false, false],
          ].filter(Boolean).map(([l, p, inel, isMin]) => (
            <div key={l} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 8 }}>
              <span style={{ color: "#94A3B8" }}>{l}</span>
              <span style={{ fontFamily: font.mono, fontWeight: 600, fontSize: inel || p == null ? 10.5 : 13, color: inel ? C.amber : p == null ? C.amber : "#fff" }}>
                {inel ? "INELIGIBLE · BROKERAGE" : p == null ? "REFER" : p ? fmt(p) + (isMin ? " · MIN" : "") : "—"}</span>
            </div>
          ))}
          <div style={{ height: 1, background: "#243447", margin: "10px 0" }} />
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, marginBottom: 6, color: "#94A3B8", fontWeight: 600 }}>
            <span>Indicated premium</span><span style={{ fontFamily: font.mono }}>{subtotal ? fmt(subtotal) : "—"}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 6, color: "#94A3B8" }}>
            <span>{fee3pct ? "Rater fee (3% of premium)" : "Admin fee"}</span><span style={{ fontFamily: font.mono }}>{adminFee ? fmt(adminFee) : "—"}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 6, color: "#94A3B8" }}>
            <span>UW fee</span><span style={{ fontFamily: font.mono }}>{uwFee ? fmt(uwFee) : "—"}</span>
          </div>
          {multiLocFee > 0 && (
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 6, color: "#94A3B8" }}>
              <span>Multi-location fee ({multiLocCount} add'l)</span><span style={{ fontFamily: font.mono }}>{fmt(multiLocFee)}</span>
            </div>
          )}
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 12, color: "#94A3B8" }}>
            <span>Est. SL taxes & fees</span><span style={{ fontFamily: font.mono }}>{tax ? fmt(tax) : "—"}</span>
          </div>
          <div style={{ background: "#fff", color: C.ink, borderRadius: 6, padding: "14px 16px", textAlign: "center" }}>
            {submitRequired ? (<>
              <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.red }}>PROGRAM INDICATION UNAVAILABLE</div>
              <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 17, lineHeight: 1.3, margin: "6px 0", color: C.red }}>
                SUBMISSION TO BROKERAGE OR THE CARMA TALISMAN CAPTIVE PROGRAM IS REQUIRED</div>
              <div style={{ fontFamily: font.mono, fontSize: 9.5, color: C.dim }}>YOUR ANSWERS AND DOCUMENTS TRANSFER AUTOMATICALLY</div>
            </>) : (<>
            <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim }}>ESTIMATED TOTAL COST · PREMIUM + TAXES + FEES</div>
            <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 34, lineHeight: 1.1, margin: "4px 0" }}>{total ? fmt(total) : "$ —"}</div>
            {appliedCredit > 0 && <div style={{ fontFamily: font.mono, fontSize: 10, color: C.green }}>{appliedCredit}% SCHEDULE CREDIT · SUBJECT TO UW APPROVAL</div>}
            {pkgMin && <div style={{ fontFamily: font.mono, fontSize: 10, color: C.amber }}>$750 PACKAGE MINIMUM PREMIUM APPLIED</div>}
            <div style={{ fontFamily: font.mono, fontSize: 9.5, color: C.dim, marginTop: 4 }}>25% MINIMUM EARNED AT BINDING · SUBJECT TO AUDIT</div>
            </>)}
          </div>
          <div style={{ marginTop: 10, fontSize: 10.5, lineHeight: 1.55, padding: "9px 11px", borderRadius: 4, background: "#243447", color: "#94A3B8" }}>
            <strong style={{ color: "#94A3B8" }}>INDICATION ONLY — NOT BINDABLE.</strong> This indication is subject to further review and
            approval by the underwriter and is provided solely on the basis of the information provided. Final premium, taxes, and fees may differ.
          </div>
          {gateAnswers.license === "pending" && (
            <div style={{ marginTop: 10, fontSize: 11.5, padding: "8px 10px", borderRadius: 4, background: "#243447", color: "#E8C97A" }}>
              FLAG: license pending — binding requires active, validated license.
            </div>
          )}
          {ghProp && (
            <div style={{ marginTop: 10, fontSize: 11.5, padding: "8px 10px", borderRadius: 4, background: "#243447", color: "#E8C97A" }}>
              GREENHOUSE: property requires submission to underwriting · wind not available on greenhouse structures.
            </div>
          )}
          {refers.length > 0 && (
            <div style={{ marginTop: 10, fontSize: 11.5, padding: "8px 10px", borderRadius: 4, background: "#243447", color: "#E8C97A" }}>
              Referral items: {refers.join("; ")}
            </div>
          )}
          {submitRequired ? (
            (submitted && !seamActive) ? (
              <div style={{ marginTop: 14, padding: "12px", borderRadius: 5, background: C.green, color: "#fff",
                fontWeight: 700, fontSize: 13, textAlign: "center" }}>
                SUBMITTED · {refNum}
              </div>
            ) : (<>
              <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: "#94A3B8", marginTop: 14 }}>BROKER & INSURED INFORMATION</div>
              <BrokerFields b={broker} set={setBroker} />
              <button disabled={!brokerOk || files.length === 0 || submitting || submitSuccess} onClick={doSubmit}
                style={{ width: "100%", marginTop: 8, padding: "12px", borderRadius: 5, border: "none",
                  background: brokerOk && files.length ? C.red : "#243447", color: brokerOk && files.length ? "#fff" : "#64748B",
                  fontWeight: 700, fontSize: 14, cursor: brokerOk && files.length ? "pointer" : "not-allowed" }}>
                Submit to Brokerage / Carma Talisman Captive Program ({files.length} doc{files.length === 1 ? "" : "s"}) →
              </button>
              {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
              {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
              {(!brokerOk || files.length === 0) && (
                <div style={{ fontSize: 10.5, color: "#94A3B8", marginTop: 6 }}>
                  Insured name, broker name & email, and attached documents are required to submit.</div>
              )}
            </>)
          ) : refSent ? (
            <div style={{ marginTop: 14, padding: "12px", borderRadius: 5, background: C.green, color: "#fff",
              fontWeight: 700, fontSize: 13, textAlign: "center", fontFamily: font.mono }}>
              Referred to UW · {refRefNum}
            </div>
          ) : (<>
            {/* EVP revision 6/12/2026: ONE unified action button.
               Default label "Request firm quote →"; when any referral condition is live
               the label becomes "Refer to underwriting →" with accent border + badge. */}
            {refers.length > 0 && (
              <div style={{ textAlign: "center", marginTop: 14 }}>
                <span data-testid="referral-badge" style={{ fontFamily: font.mono, fontSize: 9.5, fontWeight: 600, letterSpacing: "0.12em", color: C.accent,
                  border: `1.5px solid ${C.accent}`, borderRadius: 3, padding: "3px 8px", background: "#fff",
                  display: "inline-block" }}>REFERRAL RECOMMENDED</span>
              </div>
            )}
            <button data-action-btn onClick={() => setRefOpen(true)} disabled={!total && !refers.length}
              style={refers.length > 0
                ? { width: "100%", marginTop: 8, padding: "12px", borderRadius: 5, border: `2px solid ${C.accent}`,
                    background: C.accent, color: "#fff", fontWeight: 700, fontSize: 14, letterSpacing: "0.04em", cursor: "pointer" }
                : { width: "100%", marginTop: 14, padding: "12px", borderRadius: 5, border: "none",
                    background: total ? C.red : "#243447", color: total ? "#fff" : "#64748B",
                    fontWeight: 700, fontSize: 14, cursor: total ? "pointer" : "not-allowed" }}>
              {refers.length > 0 ? "Refer to underwriting →" : "Request firm quote →"}
            </button>
            {/* Adjudication 61 — end-of-journey INDICATION generate action. Shown only when
               the rating is complete AND there is NO referral condition (clean path). When
               referral conditions are live the referral flow above stays the primary path and
               this control is rendered DISABLED with a note that the submission routes to UW. */}
            {refers.length === 0 ? (
              <button data-ind-generate onClick={() => setIndOpen(true)} disabled={!ratingComplete || !total}
                style={{ width: "100%", marginTop: 8, padding: "11px", borderRadius: 5, border: `1.5px solid ${ratingComplete && total ? C.accent : "#243447"}`,
                  background: ratingComplete && total ? "#fff" : "transparent", color: ratingComplete && total ? C.accent : "#64748B",
                  fontWeight: 700, fontSize: 13, cursor: ratingComplete && total ? "pointer" : "not-allowed" }}>
                Email & print indication →
              </button>
            ) : (
              <button data-ind-generate disabled
                style={{ width: "100%", marginTop: 8, padding: "11px", borderRadius: 5, border: "1.5px solid #243447",
                  background: "transparent", color: "#64748B", fontWeight: 700, fontSize: 13, cursor: "not-allowed" }}>
                Email & print indication →
              </button>
            )}
            {refers.length > 0 && (
              <div style={{ fontSize: 10, color: "#94A3B8", marginTop: 6, lineHeight: 1.5 }}>
                A clean indication is unavailable while a referral condition is live — this submission routes to underwriting.
              </div>
            )}
            {refers.length === 0 && !ratingComplete && (
              <div style={{ fontSize: 10, color: "#94A3B8", marginTop: 6, lineHeight: 1.5 }}>
                Complete the rating to email &amp; print an indication.
              </div>
            )}
          </>)}
          <div style={{ fontFamily: font.mono, fontSize: 10, color: "#64748B", marginTop: 12, lineHeight: 1.5 }}>
            Subject to underwriting & subjectivities · wind lookup mocked (production: 40,874-ZIP table) · {files.length} doc{files.length === 1 ? "" : "s"} attached
          </div>
        </div>
      </aside>
    </div>
  );
}

/* ===================== APP ===================== */
export default function CarmaSubmissionPlatform() {
  const [screen, setScreenRaw] = useState("gate");
  const setScreen = (s) => { setScreenRaw(s); window.scrollTo({ top: 0, behavior: "instant" }); };
  const [covs, setCovs] = useState(new Set());
  const [risks, setRisks] = useState(new Set());
  const [answers, setAnswers] = useState({});
  const [files, setFiles] = useState([]);
  const [captive, setCaptive] = useState(false);
  const [submitted, setSubmitted] = useState(false);
  /* WFR-1906 Task 5: real Carma-submission seam state (separate from the simulated
     dispatch above — drives the success/error panels + button disable while in flight). */
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState(null);
  const [submissionId, setSubmissionId] = useState(null);
  const [submitSuccess, setSubmitSuccess] = useState(false);
  /* WFR-2831 (A-3): leg 2 is deliberately NOT awaited — the submission genuinely is
     complete and blocking the success panel on uploads would be worse. But that
     left the UI saying "done" while bytes still had minutes to travel, so an agent
     who closed the tab lost their documents silently: this Story's own bug class,
     re-entering through the front door. `{done, total}` while in flight, else null. */
  const [uploading, setUploading] = useState(null);
  /* WFR-1906 (UI test #1/#3 fix): mirror of RatingScreen's seamActive — keeps the
     gate-screen refer/decline submit forms (which host the seam success/error panels)
     mounted while the real Carma seam is in flight / succeeded / errored, so the real
     submissionId or c360Message shows instead of the simulated SENT/SUBMITTED card.
     Always false under jsdom (the seam no-ops), so the regression suite is unchanged. */
  const seamActive = submitting || submitSuccess || !!submitError;
  /* WFR-2831 (A-3): turn a silent document loss into a prompt the agent can act on.
     Registered only while uploads are actually in flight, so it never nags anyone
     during normal use. Modern browsers show their own generic wording. */
  useEffect(() => {
    if (!uploading || typeof window === "undefined") return;
    const warn = (e) => { e.preventDefault(); e.returnValue = ""; return ""; };
    window.addEventListener("beforeunload", warn);
    return () => window.removeEventListener("beforeunload", warn);
  }, [uploading]);
  const [broker, setBroker] = useState({ insured: "", effDate: "", needBy: "", name: "", agency: "", email: "", phone: "" });
  const [refNum, setRefNum] = useState("");
  /* Referral-to-Underwriting ledger note (note 10): REF- number recorded after send. */
  const [uwReferral, setUwReferral] = useState("");
  const [otherDesc, setOtherDesc] = useState("");
  /* Gate-level "Send to underwriter with notes" (refer verdict): free-text notes + submit kind. */
  const [uwNotes, setUwNotes] = useState("");
  const [submitKind, setSubmitKind] = useState("brokerage");
  const uploadRef = useRef(null);
  const brokerOk = broker.insured.trim() && broker.name.trim() && broker.email.trim();
  /* WFR-1906 Task 4: the real Carma-submission seam. Lives in the parent so the
     `broker` and `uwNotes` state are in scope; the rating/sections/indicationHtml
     payload pieces are assembled by the caller and passed in via `parts`.
     Guarded to no-op under jsdom (regression harness) where shared.js globals are
     absent, so the existing simulated dispatch still runs and the suite stays green. */
  /* WFR-2822 leg 2 + leg 3. Runs only after leg 1 returned a submissionId, so a
     failure here costs the documents, never the submission. Sequential so a slow
     network cannot open ten concurrent uploads from a broker's laptop. */
  const uploadAttachments = async (data, attachments) => {
    /* WFR-2827 (F-2): match POSITIONALLY, not by display name. mintUploads walks
       the manifest in order, so uploads[i] is attachments[i]. Matching by
       `slot — name` collided whenever two attachments produced the same display
       name: one Map entry for two keys meant the same bytes uploaded twice and
       the intended second file silently lost. */
    /* WFR-2831 (A-6): unreachable today — mintUploads walks the manifest 1:1 — so
       this is an assertion, not a guard. But if it ever fired it would abandon the
       whole batch in total silence, which is the most confusing shape this bug
       could take. Say so. */
    if (data.uploads.length !== attachments.length) {
      console.warn("Carma: upload manifest mismatch (" + data.uploads.length + " presigns for " + attachments.length + " attachments) — documents not sent.");
      return;
    }
    const done = [];
    try {
      /* WFR-2831 (A-3): drives the "still uploading" banner and the beforeunload
         guard. Set before the first byte moves and cleared in the finally below, so
         there is no path (including a throw) that leaves the warning armed. */
      setUploading({ done: 0, total: data.uploads.length });
      for (let i = 0; i < data.uploads.length; i++) {
        const u = data.uploads[i];
        const file = attachments[i].file;
        if (!file) continue;
        const form = new FormData();
        Object.keys(u.post.fields).forEach((k) => form.append(k, u.post.fields[k]));
        form.append("file", file);
        try {
          const r = await fetch(u.post.url, { method: "POST", body: form });
          if (r.ok) done.push(u.key); /* WFR-2827 (F-3): Response.ok already covers 204 */
        } catch (e) { /* per-file; the team email lists every name so a gap is visible */ }
        setUploading({ done: i + 1, total: data.uploads.length });
      }
      if (!done.length) {
        console.warn("Carma: no documents reached S3 — none were attached to the submission.");
        return;
      }
      try {
        const r = await fetch(LAMBDA_URL, {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ action: "attach", submissionId: data.submissionId, ticket: data.ticket, keys: done }),
        });
        /* WFR-2831 (A-7): the response used to be awaited and thrown away. A 403 here
           is the A-1 failure — the ticket expired mid-upload — and it returns BEFORE
           relayDocuments, so no server-side alert fires either. This console line was
           the last place anything could notice, and it was blank. */
        if (!r.ok) console.warn("Carma: documents uploaded but could not be attached (HTTP " + r.status + "). Please email them to submissions@carma365.com.");
      } catch (e) { console.warn("Carma: documents uploaded but the attach call failed — " + (e && e.message)); }
    } finally { setUploading(null); }
  };
  const submitToCarma = async (outcome, parts) => {
    if (typeof getTurnstileToken !== "function" || typeof LAMBDA_URL === "undefined") return;
    const token = getTurnstileToken();
    if (!token) { resetTurnstile(); setSubmitError("Please complete the verification challenge."); return; }
    /* WFR-2822: the rating-screen UploadZone files plus any referral-slot
       documents carried in via parts. Only entries holding a real File. */
    const attachments = files.concat((parts && parts.attachments) || []).filter((a) => a && a.file);
    const payload = {
      formType: "cannabis-indication",
      outcome,
      broker: { insured: broker.insured, name: broker.name, email: broker.email, phone: broker.phone, agency: broker.agency, effDate: broker.effDate, needBy: broker.needBy, gateNotes: (parts && parts.gateNotes) || uwNotes || "" },
      rating: parts.rating,
      sections: parts.sections,
      indicationHtml: parts.indicationHtml || "",
      turnstileToken: token,
      website: "",
      attachments: attachments.map((a) => ({ name: a.name, size: a.size, type: a.type, slot: a.slot || null })),
    };
    setSubmitError(null); /* clear any prior-attempt error so a successful retry doesn't show a stale 4xx message */
    setSubmitting(true);
    try {
      const resp = await fetch(LAMBDA_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
      const data = await resp.json().catch(() => null);
      if (!resp.ok) { setSubmitError((data && (data.c360Message || data.error)) || "Submission failed."); resetTurnstile(); return; }
      setSubmissionId(data && data.submissionId);
      setSubmitSuccess(true);
      if (data && data.uploads && data.uploads.length) void uploadAttachments(data, attachments);
    } catch (e) { setSubmitError("Network error — please retry."); resetTurnstile(); } finally { setSubmitting(false); }
  };
  /* onClick handlers pass an event as the first arg; guard so kind is always a string. */
  const doSubmit = (k) => {
    // WFR-1906 Task 4: real Lambda POST runs IN ADDITION to the simulated dispatch below.
    const kind = typeof k === "string" ? k : "brokerage";
    const ref = (kind === "uw" ? "REF-" : "CB-") + Date.now().toString(36).toUpperCase();
    setRefNum(ref); setSubmitKind(kind); setSubmitted(true);
    if (kind === "uw") setUwReferral(ref);
    /* Captive/decline + gate-referral path: no rating in scope here, so send a
       minimal parts object (empty schedule / zero totals) built from parent state. */
    const captiveParts = {
      gateNotes: uwNotes || "",
      rating: { carrier: "Knight Specialty Insurance Company", schedule: [], premium: 0, fees: 0, slTax: 0, total: 0 },
      sections: {
        generalInfo: {
          applicantName: broker.insured, mailingAddress: "", city: "", state: answers.state || "", zipCode: "",
          // WFR-1906: producing AGENT details (see buildCarmaParts) — carried on the
          // referral/captive paths too so the agent is captured on every submit.
          agent: { name: broker.name, email: broker.email, phone: broker.phone, agency: broker.agency },
          effectiveDate: broker.effDate || "", operations: mapRiskIdsToOperations(risks), grossSalesLast12: "0",
          coverageWants: { generalLiability: covs.has("gl") ? "yes" : "no", productsLiability: covs.has("prod") ? "yes" : "no" },
        },
        locations: [],
      },
      indicationHtml: "",
    };
    void submitToCarma(kind === "uw" ? "referral" : "captive", captiveParts);
  };
  const submissionSummary = () => [
    submitKind === "uw" ? `Subject: ${newSubmissionSubject(broker.insured, broker.effDate, riskClassLabel(risks, otherDesc), broker.needBy)}` : `CARMA BROKERAGE SUBMISSION ${refNum}`,
    submitKind === "uw" && `To: ${REFERRAL_TO.join(", ")}`,
    submitKind === "uw" && `Ref: ${refNum}`,
    `Submitted: ${new Date().toLocaleString()}`,
    `Named insured: ${broker.insured}`,
    broker.effDate && `Effective date: ${broker.effDate}`,
    broker.needBy && `Need by: ${broker.needBy}`,
    `Broker: ${broker.name}${broker.agency ? " · " + broker.agency : ""}`,
    `Contact: ${broker.email}${broker.phone ? " · " + broker.phone : ""}`,
    `Lines: ${[...covs].join(", ") || "-"} · Operations: ${[...risks].join(", ") || "-"}`,
    `State: ${answers.state || "-"}`,
    submitKind === "uw" && refersG.length > 0 && `Referral reasons: ${refersG.map((r) => r.g.q).join("; ")}`,
    uwNotes.trim() && `Underwriter notes: ${uwNotes.trim()}`,
    captive && "Carma Talisman Captive Program quote requested",
    `Documents (${files.length}): ${files.map((f) => f.name).join("; ") || "none"}`,
    `Eligibility answers: ${JSON.stringify(answers)}`,
  ].filter(Boolean).join("\n");
  /* Prevent the browser from navigating away when a file is dropped outside the zone */
  useEffect(() => {
    const prevent = (e) => e.preventDefault();
    window.addEventListener("dragover", prevent);
    window.addEventListener("drop", prevent);
    return () => { window.removeEventListener("dragover", prevent); window.removeEventListener("drop", prevent); };
  }, []);

  const sel = { covs, risks };
  const toggle = (set, setter, id) => { const n = new Set(set); n.has(id) ? n.delete(id) : n.add(id); setter(n); };
  const vertical = () => setRisks(new Set(["cult_in", "extract", "dist", "ret_c"]));

  const ready = covs.size > 0 && risks.size > 0;
  const activeGates = useMemo(() => (ready ? GATES.filter((g) => g.appliesWhen(sel, answers)) : []), [covs, risks, answers, ready]);
  const results = activeGates.map((g) => ({ g, status: g.evaluate(answers[g.id], sel) }));
  const answered = results.filter((r) => r.status !== null).length;
  const declines = results.filter((r) => r.status === "decline");
  const refersG = results.filter((r) => r.status === "refer");
  const warns = results.filter((r) => r.status === "warn");
  const complete = ready && activeGates.length > 0 && answered === activeGates.length;
  /* adj. 27 (6/12/2026): lock a decline or refer the moment it appears, even
     before every gate is answered — the verdict box (and its submission CTA)
     opens immediately. "Eligible" still requires the full gate set, since you
     cannot clear as eligible with questions outstanding. Decline outranks refer. */
  let verdict = null;
  if (ready && declines.length) verdict = "decline";
  else if (ready && refersG.length) verdict = "refer";
  else if (complete) verdict = "eligible";
  const suppressed = ready ? SUPPRESSED_BASE - activeGates.length : 0;
  const scrollToUpload = () => uploadRef.current && uploadRef.current.scrollIntoView({ behavior: "smooth", block: "center" });

  return (
    <div style={{ minHeight: "100vh", background: C.paper, color: C.ink, fontFamily: font.body }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
        * { box-sizing: border-box; }
        button:focus-visible, select:focus-visible, input:focus-visible { outline: 2px solid ${C.red}; outline-offset: 2px; }
        @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
      `}</style>
      <header style={{ borderBottom: `1px solid ${C.line}`, background: "#fff" }}>
        <div style={{ maxWidth: 1180, margin: "0 auto", padding: "16px 24px", display: "flex", alignItems: "baseline", gap: 14, flexWrap: "wrap" }}>
          <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 26 }}>CARMA<span style={{ color: C.red }}>/</span></div>
          <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, letterSpacing: "0.08em" }}>SUBMISSION GATE · KNIGHT CANNABIS PROGRAM · PROTOTYPE v4</div>
          <div style={{ marginLeft: "auto", fontFamily: font.mono, fontSize: 11, color: C.dim }}>
            {screen === "gate" ? "1 · ELIGIBILITY" : "2 · RATING"} {files.length > 0 && `· ${files.length} DOCS`}</div>
        </div>
      </header>

      {/* WFR-2831 (A-3): rendered ONCE here rather than beside each of the six
          success panels — it is fixed-position, so it covers every screen and
          survives scrolling, and one render site cannot drift out of sync with
          five others. Deliberately not a progress bar (out of scope): a count is
          enough to tell the agent the tab still has work to do. */}
      {uploading && (
        <div className="upload-pending" role="status" aria-live="polite"
          style={{ position: "fixed", left: 0, right: 0, bottom: 0, zIndex: 60, background: "#0F172A", color: "#fff",
            padding: "12px 24px", fontFamily: font.body, fontSize: 13, textAlign: "center" }}>
          Uploading documents… {uploading.done}/{uploading.total} — please keep this tab open until it finishes.
        </div>
      )}

      {screen === "rating" ? (
        <RatingScreen covs={covs} risks={risks} gateAnswers={answers} files={files} setFiles={setFiles} onBack={() => setScreen("gate")} broker={broker} setBroker={setBroker} brokerOk={brokerOk} submitted={submitted} refNum={refNum} doSubmit={doSubmit} onReferralSent={setUwReferral} forceReferral={risks.has("other_risk")} otherDesc={otherDesc} submitToCarma={submitToCarma} submitting={submitting} submitError={submitError} submissionId={submissionId} submitSuccess={submitSuccess} />
      ) : (
      <div style={{ maxWidth: 1180, margin: "0 auto", padding: "28px 24px 80px", display: "flex", gap: 28, flexWrap: "wrap" }}>
        <main style={{ flex: "1 1 600px", minWidth: 320 }}>
          <section style={{ marginBottom: 32 }} ref={uploadRef}>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.red, letterSpacing: "0.14em", marginBottom: 6 }}>START HERE — OR SKIP STRAIGHT TO STEP 1</div>
            <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>Already have the submission?</h2>
            <p style={{ color: C.dim, fontSize: 14, margin: "0 0 14px" }}>
              Drop the ACORDs, SOV, and supplementals. They travel with this submission everywhere it goes — program quote, Brokerage referral, or Captive submission — and in production they pre-fill the rating screen.
            </p>
            <UploadZone files={files} setFiles={setFiles} />
          </section>

          <section style={{ marginBottom: 36 }}>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.red, letterSpacing: "0.14em", marginBottom: 6 }}>STEP 1 — COVERAGE SELECTION</div>
            <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>Which lines is this risk shopping?</h2>
            <p style={{ color: C.dim, fontSize: 14, margin: "0 0 16px" }}>Any combination works — including monoline Products. Only selected lines generate questions.</p>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 10 }}>
              {COVERAGES.map((c) => {
                const on = covs.has(c.id);
                if (c.comingSoon) return (
                  <div key={c.id} style={{ textAlign: "left", padding: "14px 16px", borderRadius: 6,
                    border: `1.5px dashed ${C.line}`, background: "#F1F5F9", color: "#94A3B8", position: "relative" }}>
                    <div style={{ fontWeight: 700, fontSize: 15, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
                      {c.label}
                      <span style={{ fontFamily: font.mono, fontSize: 9, fontWeight: 600, letterSpacing: "0.1em",
                        border: `1px solid #E2E8F0`, borderRadius: 3, padding: "2px 6px", color: "#64748B" }}>COMING SOON</span>
                    </div>
                    <div style={{ fontSize: 12, marginTop: 2 }}>{c.sub}</div>
                  </div>
                );
                return (
                  <button key={c.id} onClick={() => toggle(covs, setCovs, c.id)}
                    style={{ textAlign: "left", padding: "14px 16px", borderRadius: 6, cursor: "pointer",
                      border: `1.5px solid ${on ? C.ink : C.line}`, background: on ? C.ink : C.panel,
                      color: on ? "#fff" : C.ink, transition: "all .12s" }}>
                    <div style={{ fontWeight: 700, fontSize: 15 }}>{c.label}</div>
                    <div style={{ fontSize: 12, color: on ? "#94A3B8" : C.dim, marginTop: 2 }}>{c.sub}</div>
                  </button>
                );
              })}
            </div>
            <div style={{ marginTop: 10, fontSize: 12, color: C.dim, fontStyle: "italic" }}>
              Excess Liability is not included in the program — available by formal submission through Carma Brokerage.
            </div>
          </section>

          <section style={{ marginBottom: 36, opacity: covs.size ? 1 : 0.35, pointerEvents: covs.size ? "auto" : "none", transition: "opacity .2s" }}>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.red, letterSpacing: "0.14em", marginBottom: 6 }}>STEP 2 — RISK TYPE</div>
            <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>What does the insured actually do?</h2>
            <p style={{ color: C.dim, fontSize: 14, margin: "0 0 16px" }}>
              Select every operation. The agent never sees a class code — these selections map to Knight classes internally.
              <button onClick={vertical} style={{ marginLeft: 10, fontFamily: font.mono, fontSize: 11, letterSpacing: "0.06em", border: "none", background: "none", color: C.red, cursor: "pointer", textDecoration: "underline", padding: 0 }}>vertically integrated →</button>
            </p>
            {["Cultivation", "Processing", "Movement", "Retail", "Other"].map((grp) => (
              <div key={grp} style={{ marginBottom: 12 }}>
                <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.dim, marginBottom: 6 }}>{grp.toUpperCase()}</div>
                <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                  {RISKS.filter((r) => r.group === grp).map((r) => {
                    const on = risks.has(r.id);
                    return (
                      <button key={r.id} onClick={() => {
                          if (r.id === "other_risk") {
                            setRisks(risks.has("other_risk") ? new Set() : new Set(["other_risk"]));
                          } else {
                            const n = new Set(risks); n.delete("other_risk");
                            n.has(r.id) ? n.delete(r.id) : n.add(r.id); setRisks(n);
                          }
                        }}
                        style={{ padding: "8px 14px", borderRadius: 100, fontSize: 13, fontWeight: 600, cursor: "pointer",
                          border: `1.5px solid ${on ? C.green : C.line}`,
                          background: on ? C.green : "#fff", color: on ? "#fff" : C.ink, transition: "all .12s" }}>{r.label}</button>
                    );
                  })}
                </div>
              </div>
            ))}
            {risks.has("other_risk") && (
              <div style={{ marginTop: 8, padding: "14px 16px", borderRadius: 6,
                border: `1.5px solid ${otherDesc.trim() ? C.green : C.amber}`, background: "#FFFBF2" }}>
                <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.amber, marginBottom: 6 }}>
                  UNCLASSIFIED RISK — DESCRIBE OPERATIONS <span style={{ color: C.red }}>*</span>
                </div>
                <textarea
                  data-other-desc
                  value={otherDesc}
                  onChange={(e) => setOtherDesc(e.target.value)}
                  placeholder="Describe the operations in detail — e.g. products sold, processes used, distribution channels, any licensed activities…"
                  style={{ width: "100%", fontFamily: font.body, fontSize: 13, padding: "10px 12px", borderRadius: 4,
                    border: `1.5px solid ${otherDesc.trim() ? C.green : C.line}`, background: "#fff", color: C.ink,
                    resize: "vertical", minHeight: 80, lineHeight: 1.5 }} />
                <div style={{ fontSize: 12, color: "#75500F", marginTop: 8, lineHeight: 1.55 }}>
                  <strong>This risk will go directly to underwriting</strong> — we'll classify it together and come back with a path.
                  Skip the rating screen; attach your application and we'll handle the rest.
                </div>
              </div>
            )}
            {risks.has("ret_h") && (
              <div data-cbd-advisory style={{ marginTop: 12, padding: "12px 16px", borderRadius: 6,
                border: `1px solid ${C.amber}`, background: "#FFFBF2" }}>
                <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.12em", color: C.amber, marginBottom: 6 }}>
                  ADVISORY
                </div>
                <div style={{ fontSize: 12, color: "#75500F", lineHeight: 1.55 }}>
                  CBD, hemp-derived, and nutraceutical or dietary-supplement risks may be better placed through Carma Brokerage. Contact your Carma Brokerage representative to discuss options.
                </div>
              </div>
            )}
          </section>

          <section style={{ opacity: ready ? 1 : 0.3, pointerEvents: ready ? "auto" : "none", transition: "opacity .2s" }}>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.red, letterSpacing: "0.14em", marginBottom: 6 }}>STEP 3 — ELIGIBILITY GATES</div>
            <h2 style={{ fontFamily: font.display, fontWeight: 800, fontSize: 30, margin: "0 0 4px" }}>
              {ready ? `${activeGates.length} questions. That's all.` : "Eligibility gates"}</h2>
            {ready && (
              <p style={{ fontSize: 14, color: C.dim, margin: "0 0 18px" }}>
                Your selections suppressed <strong style={{ color: C.ink }}>{suppressed} questions</strong> other markets would have asked. Sixty seconds to a verdict.
              </p>
            )}
            {ready && activeGates.map((g, i) => {
              const status = g.evaluate(answers[g.id], sel);
              const note = g.noteFn ? g.noteFn(answers[g.id], sel)
                : status === "pass" ? g.passNote : status === "refer" ? g.referNote : status === "decline" ? g.failNote : null;
              const noteShown = status !== null ? note : null;
              return (
                <div key={g.id} style={{ background: C.panel, border: `1px solid ${C.line}`,
                  borderLeft: `4px solid ${status === "decline" ? C.red : status === "refer" || status === "warn" ? C.amber : status === "pass" ? C.green : C.line}`,
                  borderRadius: 6, padding: "16px 18px", marginBottom: 10 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "flex-start" }}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontFamily: font.mono, fontSize: 10, letterSpacing: "0.1em", color: C.dim, marginBottom: 4 }}>
                        {String(i + 1).padStart(2, "0")} · {g.scope.toUpperCase()} ELIGIBILITY</div>
                      <div style={{ fontSize: 15, fontWeight: 600, lineHeight: 1.45, marginBottom: 12 }}>{g.q}</div>
                      {g.type === "yesno" && <YesNo value={answers[g.id]} onChange={(v) => setAnswers({ ...answers, [g.id]: v })} />}
                      {g.type === "state" && (
                        <select value={answers.state || ""} onChange={(e) => setAnswers({ ...answers, state: e.target.value || null })}
                          style={{ fontFamily: font.body, fontSize: 14, padding: "8px 12px", borderRadius: 4, border: `1.5px solid ${C.line}`, background: "#fff", minWidth: 240 }}>
                          <option value="">Select state…</option>
                          {CONTIGUOUS.map((s) => <option key={s} value={s}>{s}</option>)}
                          {NON_CONTIGUOUS.map((s) => <option key={s} value={s}>{s} — outside program</option>)}
                        </select>
                      )}
                      {g.type === "choice" && (
                        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                          {g.options.map((o) => (
                            <button key={o.v} onClick={() => setAnswers({ ...answers, [g.id]: o.v })}
                              style={{ fontSize: 13, fontWeight: 600, padding: "7px 16px", borderRadius: 4, cursor: "pointer",
                                border: `1.5px solid ${answers[g.id] === o.v ? C.ink : C.line}`,
                                background: answers[g.id] === o.v ? C.ink : "#fff",
                                color: answers[g.id] === o.v ? "#fff" : C.ink }}>{o.t}</button>
                          ))}
                        </div>
                      )}
                      {noteShown && (
                        <div style={{ marginTop: 12, fontSize: 13, lineHeight: 1.5, padding: "10px 12px", borderRadius: 4,
                          background: status === "decline" ? "#FFEBEB" : status === "refer" || status === "warn" ? "#F8F1E2" : "#EAF3EC",
                          color: status === "decline" ? "#A8262B" : status === "refer" || status === "warn" ? "#75500F" : "#174D2B" }}>{noteShown}</div>
                      )}
                    </div>
                    <Stamp status={status} />
                  </div>
                </div>
              );
            })}
          </section>
        </main>

        <aside style={{ flex: "0 1 320px", minWidth: 280 }}>
          <div style={{ position: "sticky", top: 20, background: C.panel, border: `1px solid ${C.line}`, borderTop: `4px solid ${C.ink}`, borderRadius: 6, padding: "18px 20px" }}>
            <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.14em", color: C.dim, marginBottom: 14 }}>UNDERWRITING LEDGER</div>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 4 }}>LINES SELECTED</div>
            <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 14, lineHeight: 1.6 }}>
              {covs.size ? COVERAGES.filter((c) => covs.has(c.id)).map((c) => c.label).join(" · ") : <span style={{ color: "#94A3B8" }}>None yet</span>}</div>
            <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 4 }}>OPERATIONS</div>
            <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 14, lineHeight: 1.6 }}>
              {risks.size ? RISKS.filter((r) => risks.has(r.id)).map((r) => r.label.replace("Cultivation — ", "Cult-").replace(" / ", "/")).join(" · ") : <span style={{ color: "#94A3B8" }}>None yet</span>}</div>
            {files.length > 0 && (<>
              <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 4 }}>DOCUMENTS</div>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 14 }}>{files.length} attached — travel with the submission</div></>)}
            {uwReferral && (<>
              <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 4 }}>UNDERWRITING</div>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 14, color: C.green, fontFamily: font.mono }}>Referred to UW · {uwReferral}</div></>)}
            {ready && (<>
              <div style={{ height: 1, background: C.line, margin: "4px 0 14px" }} />
              <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 8 }}>GATES · {answered}/{activeGates.length} ANSWERED</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 16 }}>
                {results.map(({ g, status }) => (
                  <div key={g.id} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 12 }}>
                    <span style={{ color: status ? C.ink : "#94A3B8", fontWeight: 600, flex: 1, paddingRight: 8 }}>
                      {g.q.length > 38 ? g.q.slice(0, 38) + "…" : g.q}</span>
                    <Stamp status={status} />
                  </div>
                ))}
              </div>
              <div style={{ fontFamily: font.mono, fontSize: 11, color: C.dim, marginBottom: 14 }}>{suppressed} QUESTIONS SUPPRESSED BY SELECTION</div></>)}
            <div style={{ borderRadius: 6, padding: "16px 16px", textAlign: "center",
              border: `2px solid ${verdict === "eligible" ? C.green : verdict === "refer" ? C.amber : verdict === "decline" ? C.red : C.line}`,
              background: verdict === "eligible" ? "#EAF3EC" : verdict === "refer" ? "#F8F1E2" : verdict === "decline" ? "#FFEBEB" : "#F8FAFC" }}>
              {!verdict && <div style={{ fontFamily: font.mono, fontSize: 11, letterSpacing: "0.1em", color: C.dim }}>VERDICT FORMS AS YOU ANSWER</div>}
              {verdict === "eligible" && (<>
                {risks.has("other_risk") ? (<>
                  <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 22, color: C.amber, transform: "rotate(-1.5deg)" }}>REFER TO UNDERWRITING</div>
                  <div style={{ fontSize: 12, color: "#75500F", margin: "6px 0 12px", lineHeight: 1.55, textAlign: "left" }}>
                    <strong>Unclassified risk.</strong> We'll classify this with underwriting and come back with a path.
                    {!otherDesc.trim() && <span style={{ color: C.red }}> A description is required before continuing.</span>}
                  </div>
                  <button onClick={() => setScreen("rating")} disabled={!otherDesc.trim()}
                    style={{ width: "100%", padding: "11px", borderRadius: 5, border: "none",
                      background: otherDesc.trim() ? C.amber : "#F5E4A0", color: otherDesc.trim() ? "#fff" : "#9A7A20",
                      fontWeight: 700, fontSize: 14, cursor: otherDesc.trim() ? "pointer" : "not-allowed" }}>
                    Continue to underwriting →</button>
                </>) : (<>
                  <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 24, color: C.green, transform: "rotate(-1.5deg)" }}>ELIGIBLE</div>
                  <div style={{ fontSize: 12, color: "#174D2B", margin: "6px 0 12px" }}>
                    All gates passed{warns.length ? ` with ${warns.length} flag${warns.length > 1 ? "s" : ""} noted` : ""}. Rating unlocks with live indication.</div>
                  <button onClick={() => setScreen("rating")}
                    style={{ width: "100%", padding: "11px", borderRadius: 5, border: "none", background: C.green, color: "#fff", fontWeight: 700, fontSize: 14, cursor: "pointer" }}>
                    Continue to rating →</button>
                </>)}
              </>)}
              {verdict === "refer" && (!submitted || seamActive) && (<>
                <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 24, color: C.amber, transform: "rotate(-1.5deg)" }}>REFER</div>
                <div style={{ fontSize: 12, color: "#75500F", margin: "6px 0 10px", textAlign: "left", lineHeight: 1.5 }}>
                  {refersG.length} item{refersG.length > 1 ? "s" : ""} need{refersG.length > 1 ? "" : "s"} underwriter review. Submission and documents go together — nothing is lost.</div>
                <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: "#75500F", margin: "0 0 4px", textAlign: "left" }}>NOTES FOR THE UNDERWRITER</div>
                <textarea value={uwNotes} onChange={(e) => setUwNotes(e.target.value)}
                  placeholder="Add context for the underwriter — what's driving the referral, account story, anything that helps the review…"
                  style={{ width: "100%", minHeight: 70, padding: "9px 11px", borderRadius: 4, border: `1.5px solid ${C.line}`,
                    background: "#fff", color: C.ink, fontFamily: font.body, fontSize: 12.5, lineHeight: 1.5, resize: "vertical", marginBottom: 10 }} />
                <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: "#75500F", margin: "0 0 0", textAlign: "left" }}>BROKER & INSURED INFORMATION</div>
                <BrokerFields b={broker} set={setBroker} />
                {files.length === 0 ? (
                  <button onClick={scrollToUpload}
                    style={{ width: "100%", marginTop: 8, padding: "10px", borderRadius: 5, fontSize: 13, fontWeight: 700, cursor: "pointer",
                      border: `1.5px solid ${C.amber}`, background: "#fff", color: "#75500F" }}>
                    ↑ Attach submission documents to enable referral</button>
                ) : null}
                <button disabled={files.length === 0 || !brokerOk || !complete || submitting || submitSuccess} onClick={() => doSubmit("uw")}
                  style={{ width: "100%", marginTop: 10, padding: "11px", borderRadius: 5, border: "none",
                    background: files.length && brokerOk && complete ? C.amber : "#F5E4A0", color: files.length && brokerOk && complete ? "#fff" : "#9A7A20", fontWeight: 700, fontSize: 14,
                    cursor: files.length && brokerOk && complete ? "pointer" : "not-allowed" }}>
                  Send to underwriter with notes ({files.length} doc{files.length === 1 ? "" : "s"}) →</button>
                {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
                {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
                {(!brokerOk || files.length === 0 || !complete) && (
                  <div style={{ fontSize: 10.5, color: "#75500F", marginTop: 6, textAlign: "left" }}>
                    {!complete && <span><strong>Answer all {activeGates.length} eligibility questions to send</strong> ({answered}/{activeGates.length} answered).<br /></span>}
                    Insured name, broker name &amp; email, and attached documents are required to send.</div>
                )}</>)}
              {verdict === "refer" && submitted && !seamActive && (<>
                <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 22, color: C.green, transform: "rotate(-1.5deg)" }}>SENT TO UNDERWRITING</div>
                <div style={{ fontFamily: font.mono, fontSize: 11, color: "#174D2B", marginTop: 4 }}>REF {refNum}</div>
                <div style={{ fontSize: 12.5, color: "#174D2B", margin: "8px 0 0", lineHeight: 1.55 }}>
                  Emailed to underwriting — {files.length} document{files.length === 1 ? "" : "s"} and your notes attached.
                  Expect a response from your underwriter within 24–48 hours.
                </div>
                <textarea readOnly value={submissionSummary()}
                  style={{ width: "100%", height: 110, marginTop: 10, padding: 8, borderRadius: 4, border: `1px solid ${C.line}`,
                    background: "#fff", color: C.ink, fontFamily: font.mono, fontSize: 10, lineHeight: 1.5, resize: "vertical" }} />
                <button onClick={() => { try { navigator.clipboard && navigator.clipboard.writeText(submissionSummary()); } catch (err) {} }}
                  style={{ marginTop: 6, padding: "7px 12px", borderRadius: 4, border: `1.5px solid ${C.green}`,
                    background: "#fff", color: C.green, fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                  Copy submission summary</button></>)}
              {verdict === "decline" && (!submitted || seamActive) && (<>
                <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 24, color: C.red, transform: "rotate(-1.5deg)" }}>PROGRAM DECLINE</div>
                <div style={{ fontSize: 12.5, color: "#A8262B", margin: "8px 0 4px", lineHeight: 1.55, textAlign: "left" }}>
                  <strong>Submission to Brokerage or the Carma Talisman Captive Program is required — and this is not the end of the road.</strong> Carma's
                  Brokerage Division maintains additional markets for risks outside program appetite, and the Carma Talisman Captive Program offers an
                  alternative structure for the right risks. Your eligibility answers and attached documents transfer automatically — no
                  re-keying, no second application.
                </div>
                <label style={{ display: "flex", gap: 8, alignItems: "flex-start", fontSize: 12, color: "#A8262B", margin: "10px 0 4px", textAlign: "left", cursor: "pointer" }}>
                  <input type="checkbox" checked={captive} onChange={(e) => setCaptive(e.target.checked)} style={{ marginTop: 2, accentColor: C.red }} />
                  <span><strong>Carma Talisman Captive Program:</strong> the insured is interested in a Captive quote.</span>
                </label>
                <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.08em", color: "#A8262B", margin: "10px 0 0", textAlign: "left" }}>BROKER & INSURED INFORMATION</div>
                <BrokerFields b={broker} set={setBroker} />
                {files.length === 0 ? (
                  <button onClick={scrollToUpload}
                    style={{ width: "100%", marginTop: 8, padding: "10px", borderRadius: 5, fontSize: 13, fontWeight: 700, cursor: "pointer",
                      border: `1.5px solid ${C.red}`, background: "#fff", color: C.red }}>
                    ↑ Attach submission documents to enable referral</button>
                ) : null}
                <button disabled={files.length === 0 || !brokerOk || submitting || submitSuccess} onClick={doSubmit}
                  style={{ width: "100%", marginTop: 10, padding: "11px", borderRadius: 5, border: "none",
                    background: files.length && brokerOk ? C.red : "#F7B6B7", color: "#fff", fontWeight: 700, fontSize: 14,
                    cursor: files.length && brokerOk ? "pointer" : "not-allowed" }}>
                  Submit to Brokerage{captive ? " + Captive" : ""} ({files.length} doc{files.length === 1 ? "" : "s"}) →</button>
                {submitting && (<div className="submit-pending" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#EEF2F7", color: "#334155", fontSize: 13 }}>Submitting to Carma…</div>)}
            {submitSuccess && (<div className="submit-success" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#E8F5E9", color: "#174D2B", fontSize: 13 }}>Submission received • ID: {submissionId || "(pending — operators notified)"}</div>)}
                {submitError && (<div className="submit-error" style={{ marginTop: 10, padding: "10px 12px", borderRadius: 4, background: "#FDECEA", color: "#A8262B", fontSize: 13 }}>{submitError}</div>)}
                {(!brokerOk || files.length === 0) && (
                  <div style={{ fontSize: 10.5, color: "#A8262B", marginTop: 6, textAlign: "left" }}>
                    Insured name, broker name & email, and attached documents are required to submit.</div>
                )}</>)}
              {verdict === "decline" && submitted && !seamActive && (<>
                <div style={{ fontFamily: font.display, fontWeight: 800, fontSize: 22, color: C.green, transform: "rotate(-1.5deg)" }}>SUBMITTED</div>
                <div style={{ fontFamily: font.mono, fontSize: 11, color: "#174D2B", marginTop: 4 }}>REF {refNum}</div>
                <div style={{ fontSize: 12.5, color: "#174D2B", margin: "8px 0 0", lineHeight: 1.55 }}>
                  Routed to Carma Brokerage{captive ? " with Captive interest noted" : ""} — {files.length} document{files.length === 1 ? "" : "s"} attached.
                  A broker picks it up from exactly where you stopped.
                </div>
                <textarea readOnly value={submissionSummary()}
                  style={{ width: "100%", height: 110, marginTop: 10, padding: 8, borderRadius: 4, border: `1px solid ${C.line}`,
                    background: "#fff", color: C.ink, fontFamily: font.mono, fontSize: 10, lineHeight: 1.5, resize: "vertical" }} />
                <button onClick={() => { try { navigator.clipboard && navigator.clipboard.writeText(submissionSummary()); } catch (err) {} }}
                  style={{ marginTop: 6, padding: "7px 12px", borderRadius: 4, border: `1.5px solid ${C.green}`,
                    background: "#fff", color: C.green, fontWeight: 700, fontSize: 12, cursor: "pointer" }}>
                  Copy submission summary</button></>)}
            </div>
            <div style={{ fontFamily: font.mono, fontSize: 10, color: "#94A3B8", marginTop: 12, lineHeight: 1.5 }}>
              Program: 48 contiguous states · license validation mocked in prototype.</div>
          </div>
        </aside>
      </div>
      )}
    </div>
  );
}
/* EOF */
