/* AllLaw v3. Maria Vargas demo arc.
   Pro-se only · Utah + NY · UPL-discipline patterns · Atticus framing · Tier 1/2/3 architecture.
   Single self-contained module: data + screens + chrome.
*/

/* ================================ DATA ================================ */

const V3_MATTERS = {
  maria: {
    id: 'maria', tier: 1, jurisdiction: 'utah',
    title: 'Vargas v. Westhaven Properties',
    short: 'Vargas v. Westhaven',
    client: { name: 'Maria Vargas', initials: 'MV', city: 'Salt Lake City, UT', occupation: 'Restaurant server' },
    opposing: { name: 'Westhaven Properties LLC', initials: 'WP' },
    type: 'Security deposit recovery',
    statute: 'Utah § 57-17-3',
    statuteNote: '2× wrongful retention available',
    court: 'Salt Lake County Justice Court',
    amount: 1800, amountLabel: '$1,800',
    potentialAmount: 3600, potentialAmountLabel: '$3,600 (with 2× damages)',
    stage: 'Preparation', stageIdx: 1,
    readiness: 87,
    readinessBreakdown: {
      elements: { score: 36, max: 40, label: 'Element completeness', missing: 'All 4 statutory elements have at least one affirmed evidence item. Slight deduction because Element 4 (wrongful retention) is supported by only one document. Courts prefer redundancy on the headline element.' },
      mapping: { score: 28, max: 30, label: 'Evidence-element mapping', missing: '5 of 7 evidence items are affirmed against an element. The 30-day timeline event and the second walkthrough-photo set have not yet been confirmed in the affirmation gate.' },
      statutory: { score: 13, max: 15, label: 'Statutory accuracy', missing: 'Utah § 57-17-3 cite verified current as of 2026-04-01 by Atticus. Pending semi-annual outside-counsel re-verification (next: 2026-10-01).' },
      procedural: { score: 10, max: 15, label: 'Procedural prep', missing: 'Service-of-process method not yet selected. Fee-waiver eligibility not yet determined. Both can finalize in about 5 minutes from the Filing screen.' },
    },
    summary: 'Maria moved out of her apartment on March 15. She gave proper notice, returned keys, and left the unit clean. Westhaven did not return her $1,800 deposit or send an itemized statement within 30 days as Utah § 57-17-3 requires.',

    /* ── Case caption: court-form header used at top of every filing-packet page ── */
    caption: {
      caseNumber: 'UT-SLC-2026-04772',
      court: 'Salt Lake County Justice Court',
      filingDate: 'April 28, 2026',
      plaintiff: 'Maria Vargas',
      plaintiffPhone: '(801) 555-0142',
      plaintiffEmail: 'maria.vargas.case@example.com',
      defendant: 'Westhaven Properties, LLC',
    },

    /* ── Case Snapshot: at-a-glance overview, modeled on James Frank's packet ── */
    snapshot: {
      natureOfClaim: 'Wrongful retention of security deposit and failure to provide itemized statement under Utah Code § 57-17-3.',
      keyIssues: [
        'Westhaven kept Maria\'s $1,800 deposit after she vacated and returned keys.',
        'No itemized written statement of deductions was sent within the 30-day statutory window.',
        'Move-out walkthrough photos with EXIF dates show the unit was clean and undamaged.',
        'Certified-mail demand was sent April 14; no response received.',
      ],
      evidenceHighlights: [
        { label: 'Signed lease with $1,800 deposit clause', exhibit: 1 },
        { label: 'Move-out walkthrough photos (12, EXIF March 15 11:42)', exhibit: 3 },
        { label: 'Certified-mail receipt for demand letter (April 14)', exhibit: 5 },
        { label: '30-day timeline calculation (move-out → demand)', exhibit: 6 },
      ],
      reliefSought: '$3,860 — $1,800 deposit + $1,800 statutory 2× penalty + $200 documented filing time + $60 court costs.',
      maxBalanceOwed: 'None. Maria owed no balance at move-out (rent paid in full through term).',
    },

    /* ── Statement of Claim sections, generated from her affirmed facts ── */
    statementSections: [
      { id: 'background', n: 'I', title: 'Background and Tenancy', body: 'On May 1, 2024, Plaintiff entered into a written lease with Defendant for the residential unit located at 1247 Sugar House Ave, Apt 3B, Salt Lake City, Utah. Lease term: 12 months. Monthly rent: $1,450. Security deposit: $1,800, paid in full at signing.' },
      { id: 'moveout', n: 'II', title: 'Move-Out and Return of Possession', body: 'Plaintiff provided 30-day written notice on February 13, 2026. Move-out completed on March 15, 2026. Keys returned to Defendant\'s on-site office at 12:14 PM. Plaintiff and a witness conducted a self-walkthrough; 12 timestamped photographs were taken, EXIF metadata preserved. Unit was left clean and undamaged. (Exhibit 3)' },
      { id: 'thirty-day', n: 'III', title: 'Statutory 30-Day Window', body: 'Under Utah Code § 57-17-3, the landlord must, within 30 days of the tenant vacating and returning possession, either return the security deposit in full or provide a written itemized statement of deductions and return any remaining balance. As of April 14, 2026 (30 days after move-out), Plaintiff had received neither.' },
      { id: 'demand', n: 'IV', title: 'Written Demand and Refusal', body: 'On April 14, 2026, Plaintiff sent a written demand for return of the deposit via certified mail (Exhibit 5). Tracking confirms delivery on April 16, 2026. As of the filing date of this action, Defendant has provided no response, no return of the deposit, and no itemized statement.' },
      { id: 'wrongful', n: 'V', title: 'Wrongful Retention', body: 'Defendant\'s failure to return the deposit or provide an itemized statement within 30 days constitutes wrongful retention under Utah Code § 57-17-3. The statute provides for recovery of the wrongfully withheld deposit plus an additional sum equal to twice the wrongfully withheld amount. (Utah Code § 57-17-3(5))' },
      { id: 'damages', n: 'VI', title: 'Damages Requested', body: 'Plaintiff seeks judgment in the amount of $3,860, calculated as set forth in the Damages Summary. Plaintiff has incurred documented lost paid time off addressing this matter and seeks reimbursement of court costs.' },
    ],

    /* ── Key Admissions and Undisputed Facts: distilled strongest facts ── */
    keyAdmissions: [
      { fact: 'The lease established a $1,800 security deposit, paid at signing.', source: 'Signed lease (Exhibit 1)' },
      { fact: 'Plaintiff provided 30-day written notice to vacate.', source: 'Notice with delivery confirmation (Exhibit 2)' },
      { fact: 'Plaintiff returned possession of the unit on March 15, 2026.', source: 'Key-return receipt signed by property manager (Exhibit 4)' },
      { fact: 'Defendant received Plaintiff\'s certified-mail demand on April 16, 2026.', source: 'USPS tracking confirmation (Exhibit 5)' },
      { fact: 'No itemized statement of deductions has been provided to Plaintiff.', source: 'Plaintiff testimony; no document in the record reflects one' },
      { fact: 'No portion of the $1,800 deposit has been returned.', source: 'Bank records; no incoming transfer from Defendant' },
    ],

    /* ── Multi-category damages with offset (mirrors James Frank's packet) ── */
    damages: {
      lineItems: [
        { id: 'deposit', label: 'Security deposit (wrongfully retained)', calc: 'Per signed lease', amount: 1800 },
        { id: 'statutory', label: 'Statutory 2× penalty', calc: 'Utah § 57-17-3(5): 2 × $1,800 deposit', amount: 1800 },
        { id: 'lostTime', label: 'Documented lost paid time off', calc: '8 hrs × $25/hr (employer records)', amount: 200 },
        { id: 'courtCosts', label: 'Estimated court costs', calc: 'Filing + service fees', amount: 60 },
      ],
      subtotal: 3860,
      offset: { label: 'Reasonable balance owed by Plaintiff', amount: 0, note: 'Rent paid in full through end of term. No outstanding obligations.' },
      net: 3860,
      netLabel: '$3,860',
    },

    /* ── Reference Packet (confidential, plaintiff use only) ── */
    referencePacket: {
      caseNumber: 'UT-SLC-2026-04772',
      figures: { claimed: '$3,860', alreadyPaid: '$1,800 deposit (held by defendant)', maxBalance: 'None', net: '$3,860' },
      sections: [
        { n: 1, title: 'Opening Statement', use: 'Read aloud word-for-word' },
        { n: 2, title: 'Quick Oral Outline', use: 'One-page reference during testimony' },
        { n: 3, title: 'Exhibit Reference Guide', use: 'What to say for each exhibit' },
        { n: 4, title: 'Handling Their Arguments', use: 'Scripted rebuttals to common defenses' },
        { n: 5, title: 'Cross-Examination Prep', use: 'Questions from defense + your answers' },
        { n: 6, title: 'Questions From the Judge', use: 'Anticipate and answer confidently' },
        { n: 7, title: 'Legal Rules & Key Points', use: 'Utah law reminders, what to say if challenged' },
        { n: 8, title: 'Math & Damages Summary', use: 'All figures verified, hand to judge if asked' },
        { n: 9, title: 'What to Expect in Court', use: 'Step-by-step courtroom procedure' },
      ],
      opening: {
        deliveryNote: 'Estimated delivery: 2–3 minutes. Speak slowly. Pause after each section. Look up at the judge.',
        sections: [
          { label: 'Introduction', body: '"Your Honor, my name is Maria Vargas. I am the plaintiff in this matter, and I am representing myself. I am asking this Court for judgment in the amount of $3,860 based on Westhaven Properties\' wrongful retention of my security deposit under Utah Code § 57-17-3."' },
          { label: 'What happened', body: '"On May 1, 2024, I signed a lease with Westhaven and paid an $1,800 security deposit. On February 13, 2026, I gave proper 30-day written notice. On March 15, 2026, I moved out. I returned the keys, took photographs of the unit\'s condition, and left it clean and undamaged. I have those photographs with their original timestamps preserved."' },
          { label: 'The 30-day window', body: '"Utah Code § 57-17-3 gives a landlord exactly 30 days from when a tenant vacates and returns possession. Within those 30 days the landlord must either return the deposit in full or send a written itemized statement of any deductions. Westhaven did neither."' },
          { label: 'My demand', body: '"On April 14 — 30 days after I moved out — I sent a written demand by certified mail. USPS tracking confirms it was delivered on April 16. To this day, I have received no response, no statement, and no return of any portion of my deposit."', stageNote: '[Hold up Exhibit 5 — certified-mail receipt. Pause.]' },
          { label: 'What the statute provides', body: '"Utah Code § 57-17-3 subsection 5 provides that when a landlord wrongfully retains a deposit, the tenant may recover the deposit plus an additional sum equal to twice the wrongfully retained amount."' },
          { label: 'Closing', body: '"I paid the deposit in good faith, lived in the unit for nearly two years, and left it in good condition. The statute is clear, my evidence is documented, and Westhaven has had every opportunity to comply. I respectfully ask this Court to award $3,860 — my deposit, the statutory penalty, my documented filing time, and court costs. Thank you."', stageNote: '[Look up. Nod. You\'re done with opening.]' },
        ],
      },
      oralOutline: [
        { n: 1, title: 'OPENING', bullets: ['Lease started May 2024, $1,800 deposit', 'Moved out March 15, 2026 with proper notice', 'Westhaven kept the deposit, sent no itemized statement', 'Requesting judgment of $3,860'] },
        { n: 2, title: 'THE LEASE IS THE CONTRACT', bullets: ['Signed May 1, 2024', '$1,800 deposit clause, paid at signing', '12-month term, renewed; no balance owed at move-out'], exhibitRef: 'Exhibit 1' },
        { n: 3, title: 'NOTICE & MOVE-OUT', bullets: ['30-day written notice Feb 13', 'Move-out March 15, 12:14 PM', 'Keys returned to property manager', '12 photographs with EXIF metadata'], exhibitRef: 'Exhibits 2, 3, 4' },
        { n: 4, title: 'THE 30-DAY WINDOW', bullets: ['Utah § 57-17-3: 30 days to return deposit OR send itemized statement', 'Window expired April 14', 'Neither was sent'] },
        { n: 5, title: 'CERTIFIED-MAIL DEMAND', bullets: ['Sent April 14 via certified mail', 'USPS tracking confirms delivery April 16', 'No response received as of filing'], exhibitRef: 'Exhibit 5' },
        { n: 6, title: 'STATUTORY PENALTY', bullets: ['§ 57-17-3(5): wrongful retention triggers 2× damages', '$1,800 deposit + $1,800 statutory = $3,600 base', 'Plus filing time and court costs = $3,860'] },
        { n: 7, title: 'DAMAGES — WHAT I AM REQUESTING', bullets: ['Deposit: $1,800', 'Statutory 2× penalty: $1,800', 'Documented lost time (8 hrs @ $25/hr): $200', 'Court costs: $60', 'Total: $3,860'] },
        { n: 8, title: 'CLOSING', bullets: ['I paid the deposit in good faith.', 'I left the unit clean and on time.', 'Westhaven had 30 days. They did nothing.', 'The statute is clear. I respectfully request $3,860.'] },
      ],
      exhibitGuide: [
        { exhibit: 1, title: 'Signed lease', proves: '$1,800 deposit clause, 12-month term, both parties\' signatures', say: '"This is the operative agreement, Your Honor — the deposit clause is on page 4."' },
        { exhibit: 2, title: '30-day notice with delivery confirmation', proves: 'Plaintiff provided proper notice to vacate on February 13, 2026', say: '"This is my written notice, sent and confirmed delivered."' },
        { exhibit: 3, title: 'Move-out photographs (12 images, EXIF preserved)', proves: 'Unit was clean and undamaged at the time of move-out, March 15, 11:42 AM', say: '"These photos are timestamped on the move-out date — original EXIF preserved."' },
        { exhibit: 4, title: 'Key-return receipt', proves: 'Keys were returned to Westhaven\'s on-site property manager at 12:14 PM on move-out day', say: '"Property manager signed for keys at the time of return."' },
        { exhibit: 5, title: 'Certified-mail demand letter + USPS tracking', proves: 'Plaintiff sent written demand on April 14; tracking shows delivered April 16', say: '"This is my demand letter and the USPS proof of delivery."' },
        { exhibit: 6, title: '30-day timeline calculation', proves: 'Move-out (March 15) plus 30 days falls on April 14 — date the demand was sent', say: '"This shows the statutory window calculation, Your Honor."' },
      ],
      rebuttals: [
        { theySay: '"The unit had damages — we kept the deposit to cover them."', youSay: '"Your Honor, Utah Code § 57-17-3 requires a written itemized statement of any deductions, sent within 30 days. No such statement was ever sent. Without it, the law treats the deposit as wrongfully retained regardless of any later-claimed damages. Exhibit 3 also shows the unit\'s condition at move-out."' },
        { theySay: '"We never received the demand letter."', youSay: '"Exhibit 5 includes USPS certified-mail tracking confirming delivery on April 16, 2026. Delivery is documented."' },
        { theySay: '"Plaintiff didn\'t give proper notice."', youSay: '"Exhibit 2 contains my 30-day written notice dated February 13, 2026, with delivery confirmation. The notice complies with the lease and exceeds the statutory minimum."' },
        { theySay: '"We sent an itemized statement, the plaintiff didn\'t receive it."', youSay: '"Your Honor, the statute requires the landlord to deliver the statement within 30 days. The burden is on the landlord to produce it. I respectfully ask the Court to require Defendant to produce a copy with proof of mailing dated within the statutory window."' },
      ],
      crossExam: [
        { group: 'Lease & Notice', items: [
          { q: '"You agreed to the deposit terms when you signed the lease, correct?"', a: '"Yes — the lease set a $1,800 deposit. Utah law also governs how that deposit must be returned, regardless of the lease language."' },
          { q: '"Did you give exactly 30 days\' notice?"', a: '"I gave more than 30 days. My notice was dated February 13, 2026; I moved out March 15, 2026. That\'s 30 days, exactly as required."' },
        ]},
        { group: 'Move-Out Condition', items: [
          { q: '"You took those photos yourself — how do we know they\'re accurate?"', a: '"The photos retain their original EXIF metadata, including timestamp and GPS coordinates. Exhibit 3 shows them taken at 11:42 AM on March 15 at the unit\'s address. The metadata is verifiable."' },
          { q: '"Was a representative of Westhaven present at your walkthrough?"', a: '"I returned keys to your on-site property manager at 12:14 PM. No formal walkthrough was offered or scheduled."' },
        ]},
        { group: 'The Demand Letter', items: [
          { q: '"Did you send the demand letter to the correct address?"', a: '"Exhibit 5 is addressed to Westhaven Properties at the same address listed on the lease. USPS confirms delivery to that address on April 16."' },
        ]},
      ],
      judgeQuestions: [
        { q: '"What exactly are you asking me to award you today?"', a: '"$3,860, Your Honor — $1,800 for the deposit, $1,800 statutory penalty under § 57-17-3, $200 in documented lost time, and $60 in court costs."' },
        { q: '"Did you receive any written itemized statement at any point?"', a: '"No, Your Honor. None — before, during, or after the 30-day statutory window."' },
        { q: '"Was the unit returned in the same condition you received it?"', a: '"Yes, Your Honor. Exhibit 3 contains 12 photographs with EXIF metadata showing the unit\'s condition at the time of move-out."' },
        { q: '"Did you have any outstanding rent balance at move-out?"', a: '"No, Your Honor. Rent was paid in full through the end of the term."' },
        { q: '"Why is the demand letter dated April 14?"', a: '"Move-out was March 15. The 30-day statutory window expired on April 14. I sent the demand on the day the window expired."' },
        { q: '"Have you had any communication with Defendant since the move-out?"', a: '"No substantive response, Your Honor. No itemized statement, no return of the deposit, no acknowledgment of the demand letter."' },
      ],
      legalRules: {
        dos: [
          'Stay calm. Composure helps the judge focus on the law.',
          'Address the judge as "Your Honor" every single time.',
          'Refer to exhibits by number: "As shown in Exhibit [#], Your Honor..."',
          'If you don\'t know the answer, say: "I don\'t have that in front of me, Your Honor."',
        ],
        donts: [
          'Don\'t interrupt the defendant or the judge.',
          'Don\'t argue with the judge. If overruled, say "Yes, Your Honor" and move on.',
          'Don\'t bring up anything outside your filed claim.',
          'Don\'t react visibly if Defendant says something inaccurate. Note it; address it on your turn.',
        ],
        keyPoints: [
          { title: 'Utah § 57-17-3 — The 30-day rule', body: 'Within 30 days after the tenant vacates and returns possession, the landlord must (a) return the security deposit, or (b) deliver a written itemized statement of deductions and return any remaining balance. Failing both triggers § 57-17-3(5).' },
          { title: '§ 57-17-3(5) — 2× recovery', body: 'When a landlord fails to comply, the court may award the wrongfully retained amount plus an additional sum equal to twice the wrongfully retained amount, plus court costs.' },
          { title: 'Burden of proof on the landlord', body: 'Once the tenant establishes the deposit was paid and not returned, and that no itemized statement was sent within 30 days, the burden shifts to the landlord to produce the statement and any underlying documentation.' },
        ],
      },
      whatToExpect: [
        { n: 1, title: 'Arrive Early', body: 'Get to the courthouse 30 minutes early. Tell the clerk you are the plaintiff in Vargas v. Westhaven Properties. Confirm the judge has your filed documents.' },
        { n: 2, title: 'Case Is Called', body: 'Both sides approach. The judge runs the proceeding — there is no jury. Address the judge as "Your Honor" at all times.' },
        { n: 3, title: 'You Speak First', body: 'As plaintiff, you present first. Deliver your opening statement from Section 1. Then walk the judge through your key exhibits in order. Pause after key points.' },
        { n: 4, title: 'Photo Evidence', body: 'Have your move-out photo binder ready. Hand the judge the printed copies if asked. Keep originals as backup.' },
        { n: 5, title: 'Defendant Responds', body: 'They will present their side. Do NOT interrupt. Take notes on anything inaccurate so you can address it calmly when it is your turn.' },
        { n: 6, title: 'Judge\'s Questions', body: 'The judge may ask either party questions at any time. Answer directly and briefly. "Yes, Your Honor" / "No, Your Honor." See Section 6.' },
        { n: 7, title: 'Judgment', body: 'The judge may rule immediately or mail you the decision within a few days. Either is normal. Stay calm and professional regardless of the outcome.' },
      ],
    },
  },
  daniel: {
    id: 'daniel', tier: 2, jurisdiction: 'utah',
    title: 'Romero v. Mountain View Homeowners',
    short: 'Romero v. Mountain View',
    client: { name: 'Daniel Romero', initials: 'DR', city: 'Provo, UT', occupation: 'Independent contractor' },
    opposing: { name: 'Mountain View Homeowners Assoc.', initials: 'MV' },
    type: 'Contract dispute (above small claims cap)',
    statute: 'Utah contract law',
    court: 'Utah District Court · 4th District',
    amount: 24000, amountLabel: '$24,000',
    stage: 'Triage', stageIdx: 0,
    readiness: 41,
    summary: 'Daniel completed a $24,000 kitchen remodel for an HOA-managed property. Final payment withheld; HOA threatening fraud counterclaim. Above $15K small-claims cap → routes to Tier 2 lawyer-handoff packet.',
  },
  patel: {
    id: 'patel', tier: 1, jurisdiction: 'ny',
    title: 'Patel v. East Side Auto Repair',
    short: 'Patel v. East Side Auto',
    client: { name: 'Sarah Patel', initials: 'SP', city: 'Brooklyn, NY', occupation: 'Graphic designer' },
    opposing: { name: 'East Side Auto Repair Inc.', initials: 'EA' },
    type: 'Auto repair · Consumer protection',
    statute: 'NY GBL § 349 + § 198-a',
    statuteNote: 'Actual + statutory; treble if willful',
    court: 'NYC Civil Court · Kings County (small claims)',
    amount: 2440, amountLabel: '$2,440 (unauthorized)',
    potentialAmount: 7370, potentialAmountLabel: '$7,370 (with treble + statutory)',
    stage: 'Preparation', stageIdx: 1,
    readiness: 79,
    readinessBreakdown: {
      elements: { score: 34, max: 40, label: 'Element completeness', missing: 'All 4 elements have evidence. Element 3 (no written authorization) has the weakest evidence chain. Texts only, not a refusal letter from the shop. Adding the demand-letter response (or lack thereof) would lift this.' },
      mapping: { score: 24, max: 30, label: 'Evidence-element mapping', missing: '5 of 6 evidence items affirmed. The bank statement is uploaded but not yet affirmed against Element 4 (you paid for the unauthorized work).' },
      statutory: { score: 13, max: 15, label: 'Statutory accuracy', missing: 'NY GBL § 198-a + § 349 cites are current as of 2026-04-01 by Atticus. Pending licensed NY counsel-of-record review pre-launch (Phase 0).' },
      procedural: { score: 8, max: 15, label: 'Procedural prep', missing: 'Service method not chosen yet. For NYC Civil Court small-claims under $5K, court-mailed service is fastest ($10) and least error-prone. Pick from the Filing screen.' },
    },
    summary: 'Sarah authorized a $400 brake job. Shop performed and charged $2,840. Adding $2,440 of additional work without written authorization. NY GBL § 198-a requires written estimate AND written authorization for auto repairs above the estimate.',
  },
  /* === Tier 1 case-type variety stubs (UT). Appear in case selector;
        clicking renders the V3ComingSoon stub instead of full screens ===  */
  wage: {
    id: 'wage', tier: 1, jurisdiction: 'utah', comingSoon: true,
    title: 'Lopez v. Pinnacle Construction',
    short: 'Lopez v. Pinnacle',
    client: { name: 'Andres Lopez', initials: 'AL', city: 'Ogden, UT', occupation: 'Drywall installer' },
    opposing: { name: 'Pinnacle Construction LLC', initials: 'PC' },
    type: 'Wage theft · Unpaid overtime',
    statute: 'Utah § 34-28 (Wage Payment Act)',
    statuteNote: 'Treble damages + reasonable attorney\'s fees available',
    court: 'Utah Labor Commission → Salt Lake Justice Court',
    amount: 4280, amountLabel: '$4,280',
    stage: 'Intake', stageIdx: 0,
    readiness: 12,
    summary: '6 weeks of unpaid overtime ($4,280 at time-and-a-half). Employer paid straight-time only; refuses to issue corrected paystubs. Utah Wage Payment Act allows treble damages where wages are wrongfully withheld.',
  },
  fraud: {
    id: 'fraud', tier: 1, jurisdiction: 'utah', comingSoon: true,
    title: 'Tate v. Brightline Solar',
    short: 'Tate v. Brightline',
    client: { name: 'Reagan Tate', initials: 'RT', city: 'Provo, UT', occupation: 'High-school teacher' },
    opposing: { name: 'Brightline Solar Sales LLC', initials: 'BS' },
    type: 'Consumer fraud · Door-to-door misrepresentation',
    statute: 'Utah § 13-11 (Consumer Sales Practices Act)',
    statuteNote: 'Actual damages + statutory; up to $2,000 per violation',
    court: 'Salt Lake County Justice Court',
    amount: 6800, amountLabel: '$6,800',
    stage: 'Intake', stageIdx: 0,
    readiness: 8,
    summary: 'Door-to-door salesman promised 0% APR financing and 30-day cancellation. Contract had 24% APR and a 3-day cancellation window buried on page 7. Utah CSPA prohibits material misrepresentations in consumer sales.',
  },
  breach: {
    id: 'breach', tier: 1, jurisdiction: 'utah', comingSoon: true,
    title: 'Kim v. PetShack Boarding',
    short: 'Kim v. PetShack',
    client: { name: 'Joon Kim', initials: 'JK', city: 'Salt Lake City, UT', occupation: 'Software engineer' },
    opposing: { name: 'PetShack Boarding & Grooming', initials: 'PS' },
    type: 'Small breach of contract · Service refund',
    statute: 'Utah § 78A-8 (small-claims)',
    statuteNote: '$15K small-claims cap',
    court: 'Salt Lake County Justice Court',
    amount: 1240, amountLabel: '$1,240',
    stage: 'Intake', stageIdx: 0,
    readiness: 6,
    summary: 'Boarding facility lost dog\'s collar and harness, returned dog with skin infection from cage rash. Contract guarantees safe boarding. Refund of $1,240 (boarding fee + vet bill) refused.',
  },
};

/* Tier 1 statutory anchors per state */
const V3_STATUTES = {
  utah: [
    { area: 'Security-deposit recovery', cite: '§ 57-17-3', note: '2× wrongful retention' },
    { area: 'Unpaid wages', cite: '§ 34-28-1 et seq.', note: '' },
    { area: 'Consumer fraud', cite: '§ 13-11 et seq.', note: '' },
    { area: 'Auto repair / lemon', cite: '§ 13-44', note: '' },
    { area: 'Small breach of contract', cite: '§ 78A-8', note: '$15K small-claims cap' },
  ],
  ny: [
    { area: 'Security-deposit recovery', cite: 'GOL § 7-108', note: 'Post-2019 HSTPA' },
    { area: 'Unpaid wages', cite: 'Labor Law §§ 190 et seq. + § 198', note: '' },
    { area: 'Consumer fraud', cite: 'GBL § 349', note: '' },
    { area: 'Auto repair', cite: 'GBL § 198-a + § 396-p', note: '' },
    { area: 'Small breach of contract', cite: 'Civ. Court Act § 1801', note: '$10K NYC / $5K else' },
  ],
};

/* Tier 1 elements for security deposit */
const V3_ELEMENTS_DEPOSIT_UT = [
  { id: 'tenancy', label: 'A landlord-tenant relationship existed', explain: 'You rented the unit. We need a signed lease (or proof of rent payment if no written lease).', complete: true, evidence: 2 },
  { id: 'deposit-paid', label: 'You paid a security deposit', explain: 'The amount you paid the landlord at move-in to hold against future damages.', complete: true, evidence: 2 },
  { id: 'tenancy-ended', label: 'Your tenancy ended properly', explain: 'You gave notice (or your lease ended), returned the keys, and moved out by the agreed date.', complete: true, evidence: 3 },
  { id: 'no-return', label: 'Landlord did not return the deposit or itemize within 30 days', explain: 'Utah § 57-17-3 gives the landlord 30 days from the end of tenancy to return the deposit OR send a written explanation of any deductions.', complete: true, evidence: 1 },
];

/* Maria's evidence */
const V3_EVIDENCE_MARIA = [
  { id: 'lease', name: 'Lease agreement (signed 4/1/2024)', type: 'PDF', maps: ['tenancy', 'deposit-paid'] },
  { id: 'deposit-receipt', name: 'Deposit receipt. $1,800 · 4/1/2024', type: 'Image', exif: 'Photo · 3/27/2026', maps: ['deposit-paid'] },
  { id: 'notice', name: '30-day notice to vacate (texted 2/13/2026)', type: 'Screenshot', maps: ['tenancy-ended'] },
  { id: 'keys', name: 'Key return confirmation email · 3/15/2026', type: 'Email', maps: ['tenancy-ended'] },
  { id: 'walkthrough', name: 'Move-out walkthrough photos (12)', type: 'Image set', exif: 'EXIF dates: 3/15/2026', maps: ['tenancy-ended'] },
  { id: 'demand', name: 'Demand letter sent (certified) · 4/16/2026', type: 'PDF + USPS', maps: ['no-return'] },
  { id: 'silence', name: 'No response. 30 days elapsed', type: 'Timeline event', maps: ['no-return'] },
];

/* Damages decomposition for Maria */
const V3_DAMAGES_MARIA = [
  { label: 'Security deposit withheld', amount: 1800, basis: 'Original deposit paid 4/1/2024 (receipt provided)', user: true },
  { label: 'Statutory 2× damages (wrongful retention)', amount: 1800, basis: 'Utah § 57-17-3. Available when landlord neither returns deposit nor itemizes within 30 days', user: false, atticus: true },
  { label: 'Filing fee (recoverable if you prevail)', amount: 60, basis: 'Salt Lake County Justice Court small claims filing fee', user: false },
];

/* ============================================================
   Patel. NY auto-repair · Tier 1 NY parity demo arc
   ============================================================ */

/* Cause-of-action options for V3Theory (Maria) */
const V3_CAUSES_MARIA = [
  { id: 'wrongful-retention', label: 'Wrongful retention of security deposit', cite: 'Utah § 57-17-3', remedy: '2× damages possible', applies: 'The landlord did not return your deposit OR send a written itemization within 30 days after you give the landlord your forwarding address (or after your lease ends, whichever is later).', prove: ['A landlord-tenant relationship existed', 'You paid a deposit', 'Your tenancy ended properly', '30+ days passed after you gave the landlord your forwarding address (or after your lease ended, whichever is later) with no return and no itemization'], match: 'strong' },
  { id: 'breach-lease', label: 'Breach of lease agreement', cite: 'Utah contract law', remedy: 'Actual damages', applies: 'The landlord did something the lease prohibited, or did not do something the lease required.', prove: ['A valid lease', 'A specific lease term that was broken', 'You followed your obligations', 'You suffered measurable damages'], match: 'partial' },
];

/* Cause-of-action options for V3Theory (Patel) */
const V3_CAUSES_PATEL = [
  { id: 'gbl-349', label: 'Deceptive business practices', cite: 'NY GBL § 349', remedy: 'Actual + $50 statutory; treble if willful', applies: 'A business engaged in materially misleading consumer-oriented conduct that caused you injury. Like charging for unauthorized work after a written estimate.', prove: ['The conduct was consumer-oriented', 'The act or omission was materially misleading', 'You were injured as a result'], match: 'strong' },
  { id: 'gbl-198a', label: 'Auto-repair shop regulation violation', cite: 'NY GBL § 198-a', remedy: 'Actual damages', applies: 'An auto-repair shop performed work without a written estimate AND without your written authorization for any work over the estimate.', prove: ['The shop is a registered auto-repair facility', 'A written estimate was given', 'Additional work was performed beyond the estimate', 'You did not authorize the additional work in writing'], match: 'strong' },
];

/* Tier 1 elements for NY auto-repair (Patel) */
const V3_ELEMENTS_AUTOREPAIR_NY = [
  { id: 'estimate', label: 'You received a written estimate for specific repair work', explain: 'NY GBL § 198-a requires the shop to give a written estimate before starting work. We need the original estimate document with the dollar amount and scope.', complete: true, evidence: 1 },
  { id: 'unauth-work', label: 'The shop performed work beyond the estimate', explain: 'Work that was added to the final invoice but was not in the original written estimate.', complete: true, evidence: 2 },
  { id: 'no-auth', label: 'You did not authorize the additional work in writing', explain: 'NY GBL § 198-a requires written authorization for any work over the estimate. Verbal "okay" by phone or text without acknowledging the new dollar amount is not enough.', complete: true, evidence: 2 },
  { id: 'damages', label: 'You paid for the unauthorized work', explain: 'Bank statement, credit card receipt, or paid invoice showing the amount you actually paid above the original estimate.', complete: true, evidence: 1 },
];

/* Patel's evidence */
const V3_EVIDENCE_PATEL = [
  { id: 'estimate', name: 'Written estimate · $400 brake job · 2/22/2026', type: 'PDF', maps: ['estimate'] },
  { id: 'invoice', name: 'Final invoice · $2,840 · 2/24/2026', type: 'PDF', maps: ['unauth-work', 'damages'] },
  { id: 'photos', name: 'Photos of work performed (8)', type: 'Image set', exif: 'EXIF dates: 2/24–2/26/2026', maps: ['unauth-work'] },
  { id: 'texts', name: 'Text messages with shop · no $ confirmation', type: 'Screenshot', maps: ['no-auth'] },
  { id: 'bank', name: 'Bank statement · $2,840 ACH · 2/26/2026', type: 'PDF', maps: ['damages'] },
  { id: 'demand', name: 'Demand letter sent (certified) · 3/15/2026', type: 'PDF + USPS', maps: ['no-auth'] },
];

/* Damages decomposition for Patel (data only. V3Damages reads from getMatterDamagesConfig) */
const V3_DAMAGES_PATEL = [
  { label: 'Unauthorized charges', amount: 2440, basis: '$2,840 final invoice − $400 authorized estimate = $2,440 over estimate', user: true },
  { label: 'GBL § 349 statutory damages', amount: 50, basis: 'NY GBL § 349(h). $50 statutory OR actual, whichever is greater', user: false, atticus: true },
  { label: 'Treble (if willful)', amount: 4880, basis: 'NY GBL § 349 court discretion to treble actual where conduct is willful or knowing', user: false, atticus: true, conditional: true },
  { label: 'Filing fee', amount: 25, basis: 'NYC Civil Court · small-claims filing fee', user: false },
];

/* Per-evidence Atticus reads for Patel */
const V3_EVIDENCE_READS_PATEL = {
  'estimate': {
    elements: ['estimate'],
    why: "This is the foundation document. The written $400 estimate dated 2/22/2026 establishes Element 1 (you received a written estimate). It also defines the boundary. Anything above this dollar amount required your separate written authorization under GBL § 198-a.",
  },
  'invoice': {
    elements: ['unauth-work', 'damages'],
    why: "The final invoice supports two elements. It shows work was performed above the estimate (Element 2) and the $2,440 differential between authorized $400 and final $2,840 is the unauthorized-work damages amount (Element 4).",
  },
  'photos': {
    elements: ['unauth-work'],
    why: "The 8 photos with EXIF dates 2/24–2/26 show the actual work performed beyond the brake job. Useful corroboration for Element 2. They prove the additional work was real, not just a billing error.",
  },
  'texts': {
    elements: ['no-auth'],
    why: "The text thread shows the shop never sent you a written authorization request for the additional work, and you never confirmed a new dollar amount. GBL § 198-a is strict. Verbal okays don't satisfy the written-authorization requirement. This supports Element 3.",
  },
  'bank': {
    elements: ['damages'],
    why: "The ACH on 2/26 shows you actually paid the $2,840. Without proof of payment, even a winning case has no recoverable damages. This locks in Element 4.",
  },
  'demand': {
    elements: ['no-auth'],
    why: "The certified-mail demand letter establishes your good-faith pre-litigation effort and dates the dispute. It also captures your written position that you did not authorize the additional work. Useful for Element 3 and as procedural posture.",
  },
};

/* Atticus messages for Patel (NY GBL framing. No sandbox cover language) */
const V3_ATTICUS_PATEL = {
  intake: {
    topic: 'Plain & Kind orientation',
    userLabel: 'Reading your case',
    body: "This sounds like a NY auto-repair case. Under NY GBL § 198-a, a repair shop must give you a written estimate AND obtain your written authorization for any work above that estimate. You said the original estimate was $400 and the final bill was $2,840. That's $2,440 in additional work. If you didn't sign off on that in writing, the shop's conduct may not satisfy the statute's authorization requirement.",
  },
  cause: {
    topic: 'Procedural research',
    userLabel: 'Working out the court paperwork',
    body: "Atticus's read: GBL § 349 (deceptive business practices) and GBL § 198-a (auto-repair regulation) are both potentially applicable here. GBL § 349 carries broader remedies: actual damages plus $50 statutory, plus the court can choose to triple the damages where conduct is found willful. GBL § 198-a covers actual damages. Both can be pled in the same complaint when both apply. The choice of which to pursue is yours.",
  },
  damages: {
    topic: 'Damages framework',
    userLabel: 'Working out the damages',
    body: "Atticus's read: the starting amount you can ask for is the $2,440 in unauthorized charges. NY GBL § 349 includes $50 statutory damages (awarded automatically when liability is established). The court can choose to triple the damages where conduct is found willful. The third damages line is conditioned on a willfulness finding. You affirm what you want to pursue.",
  },
  readiness: {
    topic: 'Case-readiness check',
    body: "Your case sits at 79 of 100. Element completeness is strong (4 of 4 elements have evidence). Procedural prep is the lightest area. You still need to choose service-of-process method for NYC Civil Court. That's the last thing standing between you and a fileable claim.",
  },
  evidence: {
    topic: 'Evidence-element mapping',
    userLabel: 'Connecting evidence to the law',
    body: "Each piece of evidence supports one or more 'elements'. The things you need to prove. We'll suggest matches based on what we see, but you confirm each one. You're the one making the legal argument; we're just helping you organize it.",
  },
  filing: {
    topic: 'Where and how to file',
    body: "You'll file at NYC Civil Court small-claims part. Kings County (Brooklyn). Filing fee is $25 for claims up to $5,000. After filing, you'll need to serve East Side Auto Repair. Certified mail is acceptable for amounts under $5,000 in NYC small-claims. The court will set a hearing 4–8 weeks out.",
  },
  courtroom: {
    topic: 'Courtroom prep',
    body: "Your hearing is in 4–6 weeks. NYC Civil Court small-claims hearings run 15–20 minutes. The judge will have read your packet. Per NY UPL discipline, we don't write scripted lines for you to recite. Instead, you'll see procedural framing: what judges typically ask in auto-repair cases, how to present your evidence, and courtroom etiquette.",
  },
  documents: {
    topic: 'Final document review',
    body: "Read each document carefully. We've drafted everything from your affirmed claims and evidence. But you're the one signing. If any fact reads wrong, edit it before you affirm. Once you affirm, the packet is locked and ready to file.",
  },
};

/* Theory cards for V3Damages (per matter) */
const V3_DAMAGES_THEORY_CARDS_MARIA = [
  {
    id: 'wrongful', recommended: true, eligible: true,
    icon: 'check',
    title: '2× wrongful retention',
    cite: 'Utah § 57-17-3(c)',
    headline: '$3,600',
    body: "If a court finds the landlord wrongfully retained your deposit, Utah law lets you recover double the wrongfully-retained amount. For a $1,800 deposit, that's $3,600 total.",
    trigger: "Trigger: court finds wrongful retention occurred. Default: yes for cases like yours.",
  },
  {
    id: 'costs', recommended: false, eligible: true,
    icon: 'warn',
    title: 'Court costs',
    cite: 'Utah § 78A-8 (small-claims)',
    headline: '~$90',
    body: 'Filing fees and service-of-process costs are recoverable as part of the judgment. Estimated total: $75 filing fee + $15 certified-mail service = $90.',
    trigger: 'Trigger: you prevail on the underlying claim. Costs are then included in the judgment.',
  },
  {
    id: 'attyfees', recommended: false, eligible: false,
    icon: 'info',
    title: "Attorney's fees",
    cite: 'Not authorized for this case-type',
    headline: 'N/A',
    body: "Utah § 57-17-3 doesn't authorize attorney's fees for security-deposit recovery in small-claims court. We're not including this. It would be unsupported.",
    trigger: null,
    readWhy: "Why this is grayed: Utah's fee-shifting statutes (§ 78B-5-826) don't extend to § 57-17-3 small-claims actions. Without statutory authorization or a fee-shifting clause in your lease, both parties bear their own attorney's fees by default.",
  },
];

const V3_DAMAGES_THEORY_CARDS_PATEL = [
  {
    id: 'deceptive', recommended: true, eligible: true,
    icon: 'check',
    title: 'Actual damages + statutory $50',
    cite: 'NY GBL § 349',
    headline: '$2,490',
    body: "GBL § 349 lets you recover actual damages PLUS $50 statutory, OR statutory alone if actuals are smaller. Your $2,440 in unauthorized charges + $50 statutory = $2,490 baseline.",
    trigger: 'Trigger: court finds deceptive consumer-oriented conduct. Default: yes for repair-shop overcharges without written authorization.',
  },
  {
    id: 'treble', recommended: false, eligible: true,
    icon: 'warn',
    title: 'Treble damages (if willful)',
    cite: 'NY GBL § 349. Court discretion',
    headline: '+$4,880',
    body: 'If the court finds the deception was willful or knowing, it can treble actual damages. That would add $4,880 (the additional 2× of $2,440) on top of baseline. Discretionary. Not automatic.',
    trigger: 'Trigger: court finds the conduct was knowing or willful. Atticus suggests this is plausible given the shop never sent a written authorization request.',
  },
  {
    id: 'attyfees-ny', recommended: false, eligible: false,
    icon: 'info',
    title: "Reasonable attorney's fees",
    cite: 'NY GBL § 349(h)',
    headline: 'N/A (pro-se)',
    body: "GBL § 349(h) authorizes reasonable attorney's fees for prevailing plaintiffs. Pro-se claims don't generate attorney's fees because there's no attorney. We're not including this.",
    trigger: null,
    readWhy: "Why this is grayed: GBL § 349(h) requires a prevailing plaintiff with reasonable attorney's fees actually incurred. Pro-se litigants by definition have no attorney's fees to recover. If you later retain counsel, this category becomes available.",
  },
];

const V3_DAMAGES_ITEMS_MARIA = (theories) => [
  { label: 'Wrongfully retained deposit', calc: '(user-affirmed)', amount: 1800, include: true },
  { label: 'Statutory 2× multiplier', calc: '$1,800 × 2 (per § 57-17-3(c))', amount: 1800, include: theories.wrongful, theory: 'wrongful' },
  { label: 'Filing fee', calc: 'Salt Lake County Justice Court · small-claims', amount: 75, include: theories.costs, theory: 'costs' },
  { label: 'Service of process', calc: 'Certified mail · estimate', amount: 15, include: theories.costs, theory: 'costs' },
];

const V3_DAMAGES_ITEMS_PATEL = (theories) => [
  { label: 'Unauthorized charges', calc: '$2,840 invoice − $400 estimate', amount: 2440, include: true },
  { label: 'GBL § 349 statutory damages', calc: '$50 (per § 349(h))', amount: 50, include: theories.deceptive, theory: 'deceptive' },
  { label: 'Treble (if willful, court discretion)', calc: '$2,440 × 2 incremental ($7,320 total)', amount: 4880, include: theories.treble, theory: 'treble' },
  { label: 'Filing fee', calc: 'NYC Civil Court · small-claims', amount: 25, include: true },
];

const V3_DAMAGES_INITIAL_MARIA = { wrongful: true, costs: true };
const V3_DAMAGES_INITIAL_PATEL = { deceptive: true, treble: false };

/* Filing + service config per matter */
const V3_FILING_MARIA = {
  courtName: 'Salt Lake County Justice Court',
  courtAddress: '450 S State St · Salt Lake City, UT',
  fee: '$60',
  feeNote: 'Fee waiver available. Household income < $35K/yr',
  hearingTimeline: '4–6 weeks',
  hearingNote: 'Justice Court calendar typically',
  serviceIntro: 'Utah requires one of these methods:',
  serviceOptions: [
    { id: 'cert-mail', label: 'Certified mail with return receipt', cost: '$8.45', note: 'You mail it. Cheaper. Works if landlord accepts mail at the office address.', recommend: true },
    { id: 'process-server', label: 'Hire a process server', cost: '$65–$95', note: 'Professional delivers the papers in person. More reliable if you suspect avoidance.' },
    { id: 'sheriff', label: 'County sheriff service', cost: '$30', note: 'Sheriff delivers the papers. Slower (10–20 days) but very official.' },
  ],
};

const V3_FILING_PATEL = {
  courtName: 'NYC Civil Court · Kings County',
  courtAddress: '141 Livingston St · Brooklyn, NY',
  fee: '$25',
  feeNote: 'Poor-person\'s relief available. Household income < $32K/yr (NYC)',
  hearingTimeline: '4–8 weeks',
  hearingNote: 'NYC Civil Court small-claims part',
  serviceIntro: 'NY small-claims under $5K accepts these methods:',
  serviceOptions: [
    { id: 'court-mail-ny', label: 'Court-mailed service (clerk handles it)', cost: '$10', note: 'NYC Civil Court clerk sends certified mail for you. Slowest path but cheapest.', recommend: true },
    { id: 'cert-mail-ny', label: 'Certified mail with return receipt', cost: '$8.45', note: 'You mail it directly. Acceptable for NY small-claims under $5,000.' },
    { id: 'personal-ny', label: 'Personal service (process server)', cost: '$50–$80', note: 'Server hands papers to a person at the business. Most reliable; required if mail bounces.' },
  ],
};

/* Audit-trail events per matter. Every legally-significant decision logged */
const V3_AUDIT_EVENTS_MARIA = [
  { ts: 'Apr 18, 11:12 AM', kind: 'atticus', label: 'Opened Atticus on intake summary', sys: 'Atticus offered Plain & Kind orientation', user: 'You read the orientation; no decision logged' },
  { ts: 'Apr 18, 11:42 AM', kind: 'cause-of-action', label: 'Selected wrongful retention of security deposit', sys: 'System presented 2 options · Atticus recommended Card 1', user: 'You chose: wrongful retention (Utah § 57-17-3)' },
  { ts: 'Apr 18, 12:08 PM', kind: 'evidence', label: 'Affirmed: lease agreement supports Elements 1 + 2', sys: 'Atticus suggested elements 1, 2 with explanation', user: 'You affirmed: lease supports tenancy + deposit-paid' },
  { ts: 'Apr 18, 12:14 PM', kind: 'evidence', label: 'Affirmed: deposit receipt supports Element 2', sys: 'Atticus read: receipt shows $1,800 ACH on 4/1/2024', user: 'You affirmed the mapping' },
  { ts: 'Apr 18, 12:21 PM', kind: 'evidence', label: 'Affirmed: walkthrough photos support Element 3', sys: 'Atticus read: 12 photos with 3/15/2026 EXIF', user: 'You affirmed the mapping' },
  { ts: 'Apr 18, 12:31 PM', kind: 'damages', label: 'Affirmed statutory 2× damages theory', sys: 'Atticus suggested under § 57-17-3(c)', user: 'You affirmed: ask for 2× damages ($3,600)' },
  { ts: 'Apr 18, 12:32 PM', kind: 'damages', label: 'Affirmed court costs (filing + service)', sys: 'Atticus suggested per Utah § 78A-8', user: 'You affirmed: include $90 in costs' },
  { ts: 'Apr 18, 12:33 PM', kind: 'damages', label: "Declined attorney's fees (statute doesn't authorize)", sys: 'Atticus marked grayed; not eligible', user: 'No affirmation needed. System-locked' },
  { ts: 'Apr 19, 9:04 AM', kind: 'atticus', label: 'Voice mode: Plain & Kind 12th-grade', sys: 'Default at MVP', user: 'No change' },
  { ts: 'Apr 19, 9:08 AM', kind: 'filing', label: 'Selected service-of-process method', sys: 'Atticus recommended certified mail for this case', user: 'You chose: certified mail with return receipt ($15)' },
  { ts: 'Apr 19, 9:12 AM', kind: 'filing', label: 'Affirmed claims for packet generation', sys: 'Affirmation gate shown · 3 claims', user: 'You ticked all 3 affirmations' },
];

const V3_AUDIT_EVENTS_PATEL = [
  { ts: 'Mar 16, 7:44 PM', kind: 'atticus', label: 'Opened Atticus on intake summary', sys: 'Atticus offered Plain & Kind orientation', user: 'You read the orientation; no decision logged' },
  { ts: 'Mar 16, 8:22 PM', kind: 'cause-of-action', label: 'Selected GBL § 349 + § 198-a (auto-repair regulation)', sys: 'System presented 2 NY GBL options · Atticus recommended Card 1 (deceptive practices)', user: 'You chose: deceptive business practices + auto-repair regulation' },
  { ts: 'Mar 16, 8:48 PM', kind: 'evidence', label: 'Affirmed: written estimate supports Element 1', sys: 'Atticus read: $400 estimate dated 2/22/2026 establishes the authorized scope', user: 'You affirmed the mapping' },
  { ts: 'Mar 16, 8:51 PM', kind: 'evidence', label: 'Affirmed: final invoice supports Elements 2 + 4', sys: 'Atticus read: $2,840 invoice − $400 estimate = $2,440 unauthorized', user: 'You affirmed the mapping' },
  { ts: 'Mar 16, 8:55 PM', kind: 'evidence', label: 'Affirmed: text screenshots support Element 3', sys: 'Atticus read: no written authorization request from shop', user: 'You affirmed: texts show no authorization confirmation' },
  { ts: 'Mar 16, 9:02 PM', kind: 'damages', label: 'Affirmed deceptive-practices baseline ($2,490)', sys: 'Atticus suggested: actual ($2,440) + GBL § 349 statutory ($50)', user: 'You affirmed: pursue the starting damages amount' },
  { ts: 'Mar 16, 9:03 PM', kind: 'damages', label: 'Considered treble damages (deferred)', sys: 'Atticus flagged Card 2 as conditional on willfulness finding', user: 'You declined for now. Court will decide if willful' },
  { ts: 'Mar 16, 9:04 PM', kind: 'damages', label: "Attorney's fees: not applicable (pro-se)", sys: 'Atticus marked Card 3 grayed (pro-se = $0 fees)', user: 'No affirmation needed. System-locked' },
  { ts: 'Mar 17, 8:14 AM', kind: 'atticus', label: 'Voice mode: Plain & Kind 12th-grade', sys: 'Default at MVP · NY-reviewed Atticus content', user: 'No change' },
  { ts: 'Mar 17, 8:18 AM', kind: 'filing', label: 'Selected court-mailed service', sys: 'Atticus recommended NYC Civ. Ct. clerk-mailing for under-$5K claims', user: 'You chose: court-mailed certified ($10)' },
  { ts: 'Mar 17, 8:22 AM', kind: 'filing', label: 'Affirmed claims for packet generation', sys: 'Affirmation gate shown · 3 claims (NY-specific)', user: 'You ticked all 3 affirmations' },
];

/* Document + affirmation config per matter */
const V3_DOCS_MARIA = {
  formName: 'Statement of Claim. Form 600S',
  courtHeader: 'Salt Lake County Justice Court',
  plaintiff: 'Maria Vargas, 1834 W. Indiana Ave, Salt Lake City, UT 84104',
  defendant: 'Westhaven Properties LLC, 552 S State St, Salt Lake City, UT 84111',
  amountLine: '$3,660 (deposit + statutory 2× damages + filing fee)',
  statuteLine: 'Utah Code § 57-17-3. Wrongful retention of security deposit',
  bodyText: 'Plaintiff Maria Vargas rented Apt. 4B at 1834 W. Indiana Ave from Defendant from April 1, 2024 through March 15, 2026. Plaintiff paid a $1,800 security deposit at move-in (Exhibit 2). Plaintiff vacated on March 15, 2026, returned the keys (Exhibit 4), and left the unit clean (Exhibit 5). As of April 16, 2026. 32 days after end of tenancy. Defendant has neither returned the deposit nor sent a written itemization, in violation of Utah Code § 57-17-3…',
  affirmations: [
    { k: 'a', label: 'I am claiming wrongful retention of my $1,800 security deposit under Utah § 57-17-3.' },
    { k: 'b', label: 'I am asking for 2× statutory damages ($1,800 additional). Atticus suggested this; I am affirming it as my own claim.' },
    { k: 'c', label: 'The facts in the Statement of Claim are true to the best of my knowledge.' },
  ],
};

const V3_DOCS_PATEL = {
  formName: 'Statement of Claim. NYC Civ. Ct. Form CIV-SC-1A',
  courtHeader: 'NYC Civil Court · Kings County (Small-Claims Part)',
  plaintiff: 'Sarah Patel, 482 8th Ave, Apt 3R, Brooklyn, NY 11215',
  defendant: 'East Side Auto Repair Inc., 2114 Coney Island Ave, Brooklyn, NY 11223',
  amountLine: '$2,490 (unauthorized charges + statutory $50; treble may apply)',
  statuteLine: 'NY GBL § 349 + § 198-a. Deceptive practices / auto-repair regulation',
  bodyText: 'Plaintiff Sarah Patel authorized a $400 brake-pad replacement at Defendant\'s shop on February 22, 2026 (Exhibit 1. Written estimate). On February 24, 2026, Defendant invoiced Plaintiff $2,840, having performed $2,440 of additional repair work without obtaining Plaintiff\'s written authorization as required by NY GBL § 198-a (Exhibit 2. Final invoice; Exhibit 4. Text exchange showing no written authorization request). Plaintiff paid the $2,840 under duress to retrieve her vehicle (Exhibit 5. Bank statement). Defendant has refused to refund the unauthorized $2,440 despite a certified-mail demand on March 15, 2026 (Exhibit 6).',
  affirmations: [
    { k: 'a', label: 'I am claiming deceptive business practices under NY GBL § 349 and an auto-repair regulation violation under NY GBL § 198-a.' },
    { k: 'b', label: 'I did not give written authorization for the work above the $400 estimate; the $2,440 differential is unauthorized.' },
    { k: 'c', label: 'The facts in the Statement of Claim are true to the best of my knowledge.' },
  ],
};

/* Per-matter dataset router. Pick the right bundle by matter id */
function getMatterDataset(matterId) {
  if (matterId === 'patel') return {
    causes: V3_CAUSES_PATEL,
    elements: V3_ELEMENTS_AUTOREPAIR_NY,
    evidence: V3_EVIDENCE_PATEL,
    damages: V3_DAMAGES_PATEL,
    damagesTheoryCards: V3_DAMAGES_THEORY_CARDS_PATEL,
    damagesItems: V3_DAMAGES_ITEMS_PATEL,
    damagesInitial: V3_DAMAGES_INITIAL_PATEL,
    damagesIntroLine: 'You\'ve described an auto-repair overcharge case. Under NY GBL, you have actual damages, statutory damages, and possible treble. Pick what to claim. The math runs from what you affirm.',
    evidenceReads: V3_EVIDENCE_READS_PATEL,
    atticus: V3_ATTICUS_PATEL,
    filing: V3_FILING_PATEL,
    docs: V3_DOCS_PATEL,
    auditEvents: V3_AUDIT_EVENTS_PATEL,
    caseNoun: 'auto-repair overcharge',
    initialAffirmed: { 'estimate': 'affirmed', 'invoice': 'affirmed' },
  };
  // Maria default
  return {
    causes: V3_CAUSES_MARIA,
    elements: V3_ELEMENTS_DEPOSIT_UT,
    evidence: V3_EVIDENCE_MARIA,
    damages: V3_DAMAGES_MARIA,
    damagesTheoryCards: V3_DAMAGES_THEORY_CARDS_MARIA,
    damagesItems: V3_DAMAGES_ITEMS_MARIA,
    damagesInitial: V3_DAMAGES_INITIAL_MARIA,
    damagesIntroLine: 'The law lets you ask for several different kinds of damages. Some are mechanical (you put in numbers, math happens). Others depend on whether you can prove certain things. We\'ll show you what\'s available. You choose what to pursue.',
    evidenceReads: V3_EVIDENCE_READS,
    atticus: V3_ATTICUS,
    filing: V3_FILING_MARIA,
    docs: V3_DOCS_MARIA,
    auditEvents: V3_AUDIT_EVENTS_MARIA,
    caseNoun: 'security deposit',
    initialAffirmed: { 'lease': 'affirmed', 'deposit-receipt': 'affirmed' },
  };
}

/* Atticus messages catalog. Maria-specific copy from 01-content-pack.md, Plain & Kind 12th-grade */
const V3_ATTICUS = {
  intake: {
    topic: 'Plain & Kind orientation',
    userLabel: 'Reading your case',
    body: "This sounds like a security-deposit case. Utah law gives a landlord 30 days from your forwarding address to either return your deposit or send a written explanation of any deductions. You said they did neither. That's the kind of fact pattern Utah § 57-17-3 was written for.",
  },
  cause: {
    topic: 'Procedural research',
    userLabel: 'Working out the court paperwork',
    body: "Atticus's read: under Utah § 57-17-3, a landlord has 30 days from your forwarding address. By that point they must either return your deposit or send a written explanation of any deductions. You said they did neither. That fact pattern is consistent with what § 57-17-3 calls wrongful retention. The wrongful-retention option is the strongest match against the framework. The choice of which cause to pursue is yours.",
  },
  damages: {
    topic: 'Damages framework',
    userLabel: 'Working out the damages',
    body: "Atticus's read: based on your facts, the wrongful-retention damages line (2× the retained deposit under Utah § 57-17-3) and the court-costs line are both potentially available. Attorney's fees are not authorized in this case type. You affirm what you want to pursue.",
  },
  readiness: {
    topic: 'Case-readiness read',
    body: "Your case sits at 87 of 100. Element completeness is strong. 4 of 4 elements have supporting evidence. Procedural prep is the lightest area; you still need to choose a service-of-process method. That's the last thing standing between you and a fileable packet.",
  },
  evidence: {
    topic: 'Evidence-element mapping',
    userLabel: 'Connecting evidence to the law',
    body: "Each piece of evidence supports one or more 'elements'. The things you need to prove. We'll suggest matches based on what we see, but you confirm each one. You're the one making the legal argument; we're just helping you organize it.",
  },
  filing: {
    topic: 'Where and how to file',
    body: "You'll file at Salt Lake County Justice Court small-claims department, 450 South State Street. The filing fee is $75 (waivable based on income). After filing, you'll need to serve Westhaven. Certified mail with return receipt is usually the simplest, around $15. The court will set a hearing 4–8 weeks out.",
  },
  courtroom: {
    topic: 'Courtroom prep',
    body: "Your hearing is in 6 weeks. Salt Lake County Justice Court small-claims hearings run 15–30 minutes. The judge will have read your packet. You'll present your case in your own words, then answer questions. We've prepared a script, anticipated questions, and tips on how to present yourself in court specifically for your case.",
  },
  documents: {
    topic: 'Final document review',
    body: "Read each document carefully. We've drafted everything from your affirmed claims and evidence. But you're the one signing. If any fact reads wrong, edit it before you affirm. Once you affirm, the packet is locked and ready to file.",
  },
};

/* Per-evidence Atticus reads. Used in the affirmation gate */
const V3_EVIDENCE_READS = {
  'lease': {
    elements: ['tenancy', 'deposit-paid'],
    why: "This document supports two elements: the landlord-tenant relationship (Element 1) and that you paid a $1,800 deposit on April 1, 2024 (Element 2). The lease lists you as tenant and shows the deposit clause on page 3.",
  },
  'deposit-receipt': {
    elements: ['deposit-paid'],
    why: "This receipt shows the $1,800 you paid Westhaven on the move-in date. It's the cleanest proof of Element 2. That you actually paid a deposit.",
  },
  'notice': {
    elements: ['tenancy-ended'],
    why: "Your text on February 13, 2026 gave the landlord written 30-day notice you were moving out. The standard trigger that ends a month-to-month tenancy in Utah. Supports Element 3.",
  },
  'keys': {
    elements: ['tenancy-ended'],
    why: "The email confirms you returned the keys on March 15, 2026. Your last day in possession. This is what 'tenancy ended' means procedurally. Supports Element 3.",
  },
  'walkthrough': {
    elements: ['tenancy-ended'],
    why: "The 12 walkthrough photos with March 15, 2026 EXIF timestamps show the unit was clean and undamaged when you left. Supports Element 3 and pre-empts a counterclaim that you owed for damages.",
  },
  'demand': {
    elements: ['no-return'],
    why: "Your certified-mail demand on April 16, 2026. 32 days after the end of tenancy. Documents the moment you formally asked for the deposit back and got nothing. Supports Element 4 (the wrongful-retention element itself).",
  },
  'silence': {
    elements: ['no-return'],
    why: "30+ days have passed with no deposit and no written itemization from Westhaven. This is the heart of the § 57-17-3 claim. Element 4.",
  },
};

/* ================================ HEADER / NAV CHROME ================================ */

const V3_NAV = [
  { id: 'home', label: 'Home', icon: Icons.dashboard },
  { id: 'matter', label: 'My case', icon: Icons.folder },
  { id: 'theory', label: 'Legal theory', icon: Icons.scale },
  { id: 'evidence', label: 'Evidence', icon: Icons.paperclip },
  { id: 'timeline', label: 'Timeline', icon: Icons.clock },
  { id: 'damages', label: 'Damages', icon: Icons.dashboard },
  { id: 'readiness', label: 'Case readiness', icon: Icons.check },
  { id: 'documents', label: 'Documents', icon: Icons.doc },
  { id: 'filing', label: 'Filing & service', icon: Icons.send },
  { id: 'courtroom', label: 'Courtroom prep', icon: Icons.book },
  { id: 'audit', label: 'Decision log', icon: Icons.shield },
];

const V3_NAV_TIER2 = [
  { id: 'home', label: 'Home', icon: Icons.dashboard },
  { id: 'matter', label: 'My case', icon: Icons.folder },
  { id: 'evidence', label: 'Evidence', icon: Icons.paperclip },
  { id: 'timeline', label: 'Timeline', icon: Icons.clock },
  { id: 'pitch', label: 'Lawyer-pitch packet', icon: Icons.doc },
  { id: 'audit', label: 'Decision log', icon: Icons.shield },
];

const V3Sidebar = ({ matter, screen, onScreen, onSwitchMatter, onTriggerCrisis, mode, onModeChange }) => {
  const m = V3_MATTERS[matter];
  const items = m.tier === 2 ? V3_NAV_TIER2 : V3_NAV;
  const [collapsed, setCollapsed] = React.useState(false);
  const [hoverItem, setHoverItem] = React.useState(null);
  const [caseMenuOpen, setCaseMenuOpen] = React.useState(false);
  React.useEffect(() => { setCaseMenuOpen(false); }, [matter, collapsed]);

  const isDark = THEME.mode === 'dark';
  const W_EXPANDED = 264;
  const W_COLLAPSED = 64;
  // Light-mode sidebar pulled less white on 2026-05-02. Opacity drop lets the
  // warmer (slightly darker) paper bg show through, matching dark-mode rhythm.
  const sidebarBg = isDark ? 'rgba(28,28,32,0.55)' : 'rgba(252,248,238,0.42)';
  const sidebarBorder = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(20,20,30,0.10)';
  const sidebarShadow = `inset 0 1px 0 rgba(255,255,255,${isDark ? 0.08 : 0.6}), 0 16px 40px -16px rgba(0,0,0,${isDark ? 0.55 : 0.18})`;

  const tierTone = (mm) => mm.tier === 1 ? THEME.success : mm.tier === 2 ? THEME.warn : THEME.blue;
  const jxTone = (mm) => mm.jurisdiction === 'utah' ? THEME.success : THEME.blue;

  return (
    <div style={{
      // Outer 12px gutter. Sidebar floats inside the iPad chrome
      paddingTop: 12, paddingLeft: 12, paddingBottom: 12, paddingRight: 0,
      height: '100%', display: 'flex',
      flexShrink: 0,
    }}>
      <div style={{
        width: collapsed ? W_COLLAPSED : W_EXPANDED,
        transition: 'width 0.28s cubic-bezier(0.22, 1, 0.36, 1)',
        height: '100%',
        background: sidebarBg,
        WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
        backdropFilter: 'blur(28px) saturate(1.6)',
        border: `1px solid ${sidebarBorder}`,
        borderRadius: 16,
        boxShadow: sidebarShadow,
        display: 'flex', flexDirection: 'column',
        overflow: 'hidden',
        position: 'relative',
      }}>
        {/* Header. Logo + collapse toggle */}
        <div style={{
          padding: collapsed ? '20px 0 12px' : '24px 16px 12px',
          display: 'flex', alignItems: 'center',
          justifyContent: collapsed ? 'center' : 'space-between',
          gap: 8, position: 'relative',
        }}>
          {collapsed ? (
            <AllLawLockupSymbol size={28}/>
          ) : (
            <AllLawLockup scale={0.95}/>
          )}
          <div onClick={() => setCollapsed(c => !c)} style={{
            position: collapsed ? 'absolute' : 'static',
            top: collapsed ? 6 : undefined, right: collapsed ? 6 : undefined,
            width: 22, height: 22, borderRadius: 11,
            background: isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
            border: `1px solid ${sidebarBorder}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            cursor: 'pointer', flexShrink: 0,
            transition: 'background 0.15s ease',
          }}
          onMouseEnter={(e) => e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.14)' : 'rgba(0,0,0,0.10)'}
          onMouseLeave={(e) => e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'}
          >
            <div style={{ transform: collapsed ? 'rotate(0deg)' : 'rotate(180deg)', transition: 'transform 0.28s cubic-bezier(0.22, 1, 0.36, 1)', display: 'flex' }}>
              <Icon d={Icons.chevronRight} size={11} stroke={THEME.textDim} sw={2.4}/>
            </div>
          </div>
        </div>


        {/* Nav */}
        <div style={{ padding: collapsed ? '4px 8px' : '4px 10px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
          {/* Persistent "Demo home" back link. Exits the iPad app and returns
             to the partner-demo marketing landing page (index.html). */}
          {screen !== 'welcome' && (
            <div onClick={() => { window.location.href = '../index.html'; }}
              onMouseEnter={(e) => { e.currentTarget.style.background = THEME.blueSoft; e.currentTarget.style.color = THEME.blue; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = THEME.textDim; }}
              style={{
                cursor: 'pointer',
                display: 'flex', alignItems: 'center', gap: collapsed ? 0 : 8,
                justifyContent: collapsed ? 'center' : 'flex-start',
                padding: collapsed ? '8px 0' : '8px 10px',
                margin: collapsed ? '0 auto 8px' : '0 0 10px',
                width: collapsed ? 40 : 'auto', height: collapsed ? 40 : 'auto',
                borderRadius: 8, background: 'transparent',
                border: `1px dashed ${THEME.line}`,
                fontSize: 11.5, color: THEME.textDim, fontWeight: 600,
                letterSpacing: 0.4,
                transition: 'background 0.15s ease, color 0.15s ease',
              }}
              title="Back to demo home"
            >
              <span style={{ fontSize: 14, lineHeight: 1 }}>←</span>
              {!collapsed && <span>Demo home</span>}
            </div>
          )}
          {!collapsed && (
            <div style={{ fontSize: 9.5, color: THEME.textMute, letterSpacing: 1.2, textTransform: 'uppercase', fontWeight: 600, marginBottom: 6, paddingLeft: 4 }}>This case</div>
          )}
          {items.map(item => {
            const active = screen === item.id;
            if (collapsed) {
              return (
                <div key={item.id} onClick={() => onScreen(item.id)}
                  data-coach={`sidebar-${item.id}`}
                  onMouseEnter={() => setHoverItem('nav:' + item.id)}
                  onMouseLeave={() => setHoverItem(null)}
                  style={{
                    position: 'relative',
                    width: 40, height: 40, margin: '0 auto 4px', borderRadius: 10,
                    cursor: 'pointer',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    background: active ? THEME.blueSoft : 'transparent',
                    transition: 'background 0.15s ease',
                  }}>
                  {active && (
                    <div style={{
                      position: 'absolute', right: -8, top: 8, bottom: 8,
                      width: 4, borderRadius: '2px 0 0 2px', background: THEME.blue,
                    }}/>
                  )}
                  <Icon d={item.icon} size={16} stroke={active ? THEME.blue : THEME.textDim} sw={2}/>
                  {hoverItem === 'nav:' + item.id && (
                    <div style={{
                      position: 'absolute', left: '100%', top: '50%', transform: 'translate(10px, -50%)',
                      whiteSpace: 'nowrap', padding: '6px 10px', borderRadius: 7,
                      background: isDark ? 'rgba(20,20,28,0.95)' : 'rgba(40,40,50,0.95)', color: '#fff',
                      fontSize: 12, fontWeight: 500, letterSpacing: 0.1, zIndex: 60,
                      boxShadow: '0 8px 24px rgba(0,0,0,0.3)',
                      pointerEvents: 'none',
                    }}>
                      {item.label}
                    </div>
                  )}
                </div>
              );
            }
            return (
              <div key={item.id} onClick={() => onScreen(item.id)}
                data-coach={`sidebar-${item.id}`}
                style={{
                display: 'flex', alignItems: 'center', gap: 9,
                padding: '8px 10px', borderRadius: 7, cursor: 'pointer',
                background: active ? THEME.blueSoft : 'transparent',
                color: active ? THEME.text : THEME.textDim,
                fontSize: 12.5, fontWeight: active ? 600 : 450,
                marginBottom: 1, borderLeft: active ? `2px solid ${THEME.blue}` : '2px solid transparent', paddingLeft: 9,
              }}>
                <Icon d={item.icon} size={14} stroke={active ? THEME.blue : THEME.textDim}/>
                <span>{item.label}</span>
              </div>
            );
          })}
        </div>

        {/* Theme switcher. Above Phase 3 preview, both states */}
        <div style={{ padding: collapsed ? '10px 8px 8px' : '10px 14px 8px', borderTop: `1px solid ${sidebarBorder}` }}>
          {collapsed ? (
            (() => {
              const willSwitchTo = mode === 'dark' ? 'light' : 'dark';
              return (
                <div onClick={() => onModeChange && onModeChange(willSwitchTo)}
                  onMouseEnter={() => setHoverItem('theme')}
                  onMouseLeave={() => setHoverItem(null)}
                  style={{
                    position: 'relative',
                    width: 40, height: 32, margin: '0 auto', borderRadius: 8,
                    cursor: 'pointer',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    background: isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
                    border: `1px solid ${sidebarBorder}`,
                    transition: 'background 0.15s ease',
                  }}
                  onMouseDown={(e) => e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)'}
                  onMouseUp={(e) => e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}
                >
                  <Icon d={isDark ? Icons.sun : Icons.moon} size={14} stroke={THEME.textDim} sw={2}/>
                  {hoverItem === 'theme' && (
                    <div style={{
                      position: 'absolute', left: '100%', top: '50%', transform: 'translate(10px, -50%)',
                      whiteSpace: 'nowrap', padding: '6px 10px', borderRadius: 7,
                      background: isDark ? 'rgba(20,20,28,0.95)' : 'rgba(40,40,50,0.95)', color: '#fff',
                      fontSize: 12, fontWeight: 500, zIndex: 60,
                      boxShadow: '0 8px 24px rgba(0,0,0,0.3)',
                      pointerEvents: 'none',
                    }}>
                      Switch to {willSwitchTo} mode
                    </div>
                  )}
                </div>
              );
            })()
          ) : (
            <div style={{
              display: 'flex', gap: 4, padding: 3, borderRadius: 8,
              background: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)',
              border: `1px solid ${sidebarBorder}`,
            }}>
              {[
                { value: 'light', label: 'Light', icon: Icons.sun },
                { value: 'dark', label: 'Dark', icon: Icons.moon },
              ].map(opt => {
                const active = mode === opt.value;
                return (
                  <div key={opt.value} onClick={() => onModeChange && onModeChange(opt.value)} style={{
                    flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                    padding: '6px 8px', borderRadius: 6, cursor: 'pointer',
                    background: active ? THEME.blue : 'transparent',
                    color: active ? '#fff' : THEME.textDim,
                    fontSize: 11, fontWeight: active ? 600 : 500, letterSpacing: 0.2,
                    transition: 'background 0.15s ease, color 0.15s ease',
                  }}>
                    <Icon d={opt.icon} size={12} stroke={active ? '#fff' : THEME.textDim} sw={2}/>
                    <span>{opt.label}</span>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {/* Phase 3 marketplace preview. Hidden when collapsed */}
        {!collapsed && (
          <div style={{ padding: '4px 14px 14px' }}>
            <div style={{
              padding: '8px 10px', borderRadius: 7, background: 'transparent',
              border: `1px dashed ${THEME.line}`, fontSize: 10.5, color: THEME.textMute, lineHeight: 1.4,
            }}>
              <div style={{ fontWeight: 600, color: THEME.textDim, marginBottom: 2, fontSize: 10 }}>Phase 3 · Coming Q2 2027</div>
              Attorney marketplace · Reviewing & operating attorneys · Partner firm portal
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

/* Floating active-case switcher. Sits in the top-right of the iPad inner screen.
   Single pill (active case) opens a dropdown listing all matters. */
const V3MatterSwitcher = ({ matter, onSwitchMatter }) => {
  const [open, setOpen] = React.useState(false);
  React.useEffect(() => { setOpen(false); }, [matter]);
  const m = V3_MATTERS[matter];
  if (!m) return null;
  const isDark = THEME.mode === 'dark';
  const tone = m.tier === 1 ? THEME.success : m.tier === 2 ? THEME.warn : THEME.blue;
  const jTone = m.jurisdiction === 'utah' ? THEME.success : THEME.blue;
  const border = isDark ? 'rgba(255,255,255,0.10)' : 'rgba(20,20,30,0.10)';

  return (
    <div style={{
      position: 'absolute', top: 36, right: 18, zIndex: 40,
    }}>
      {/* Trigger pill */}
      <div onClick={() => setOpen(o => !o)} style={{
        padding: '8px 12px', borderRadius: 999, cursor: 'pointer',
        background: isDark ? 'rgba(28,28,32,0.55)' : 'rgba(255,255,255,0.55)',
        backdropFilter: 'blur(28px) saturate(1.6)',
        WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
        border: `1px solid ${open ? THEME.blue + '60' : border}`,
        boxShadow: isDark
          ? '0 8px 24px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.08)'
          : '0 8px 24px rgba(20,30,50,0.14), inset 0 1px 0 rgba(255,255,255,0.6)',
        display: 'flex', alignItems: 'center', gap: 8,
        transition: 'border-color 0.15s ease',
      }}>
        <span style={{ fontSize: 9, fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: tone + '22', color: tone, letterSpacing: 0.5 }}>
          TIER {m.tier}
        </span>
        <span style={{ fontSize: 9, fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: m.jurisdiction === 'utah' ? 'rgba(91,167,115,0.18)' : THEME.blueSoft, color: jTone, letterSpacing: 0.4 }}>
          {m.jurisdiction === 'utah' ? 'UT' : 'NY'}
        </span>
        <span style={{ fontSize: 12, fontWeight: 600, color: THEME.text, letterSpacing: -0.1, maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          {m.short}
        </span>
        <div style={{ transform: open ? 'rotate(270deg)' : 'rotate(90deg)', transition: 'transform 0.18s cubic-bezier(0.22, 1, 0.36, 1)', display: 'flex' }}>
          <Icon d={Icons.chevronRight} size={11} stroke={THEME.textDim} sw={2.4}/>
        </div>
      </div>

      {/* Dropdown menu */}
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{
            position: 'fixed', inset: 0, zIndex: 39,
          }}/>
          <div style={{
            position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 41,
            minWidth: 280,
            padding: 6, borderRadius: 12,
            background: isDark ? 'rgba(22,28,36,0.96)' : 'rgba(255,255,255,0.98)',
            backdropFilter: 'blur(28px) saturate(1.6)',
            WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
            border: `1px solid ${border}`,
            boxShadow: isDark
              ? '0 16px 40px rgba(0,0,0,0.55), 0 4px 12px rgba(0,0,0,0.3)'
              : '0 16px 40px rgba(20,30,50,0.18), 0 4px 12px rgba(20,30,50,0.08)',
          }}>
            <div style={{ fontSize: 9.5, color: THEME.textMute, letterSpacing: 1.2, textTransform: 'uppercase', fontWeight: 600, padding: '6px 8px 4px' }}>
              Switch case · {Object.keys(V3_MATTERS).length} matters
            </div>
            {Object.values(V3_MATTERS).map(mm => {
              const a = mm.id === matter;
              const t = mm.tier === 1 ? THEME.success : mm.tier === 2 ? THEME.warn : THEME.blue;
              const jt = mm.jurisdiction === 'utah' ? THEME.success : THEME.blue;
              return (
                <div key={mm.id} onClick={() => { onSwitchMatter(mm.id); setOpen(false); }} style={{
                  padding: '8px 10px', borderRadius: 8, cursor: 'pointer', marginBottom: 1,
                  background: a ? THEME.blueSoft : 'transparent',
                  border: `1px solid ${a ? THEME.blue + '40' : 'transparent'}`,
                  transition: 'background 0.12s ease',
                }}
                  onMouseEnter={(e) => !a && (e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)')}
                  onMouseLeave={(e) => !a && (e.currentTarget.style.background = 'transparent')}
                >
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                    <span style={{ fontSize: 9, fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: t + '22', color: t, letterSpacing: 0.5 }}>
                      TIER {mm.tier}
                    </span>
                    <span style={{ fontSize: 9, fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: mm.jurisdiction === 'utah' ? 'rgba(91,167,115,0.18)' : THEME.blueSoft, color: jt, letterSpacing: 0.4 }}>
                      {mm.jurisdiction === 'utah' ? 'UT' : 'NY'}
                    </span>
                    {a && <span style={{ fontSize: 8.5, fontWeight: 700, color: THEME.blue, letterSpacing: 0.6, marginLeft: 'auto' }}>● ACTIVE</span>}
                  </div>
                  <div style={{ fontSize: 12, color: a ? THEME.text : THEME.textDim, fontWeight: a ? 600 : 500, lineHeight: 1.3, marginBottom: 2 }}>
                    {mm.short}
                  </div>
                  <div style={{ fontSize: 10.5, color: THEME.textMute, lineHeight: 1.35 }}>
                    {mm.type}
                  </div>
                </div>
              );
            })}
          </div>
        </>
      )}
    </div>
  );
};

/* Disclosure footer. Non-attorney disclosure surface, persistent */
const V3Disclosure = ({ jx, compact, named }) => (
  <div style={{
    padding: compact ? '8px 12px' : '12px 14px', borderRadius: 10,
    background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
    border: `1px dashed ${THEME.line}`,
    fontSize: compact ? 10.5 : 11.5, color: THEME.textDim, lineHeight: 1.5,
    display: 'flex', gap: 10, alignItems: 'flex-start',
  }}>
    <Icon d={Icons.shield} size={compact ? 12 : 14} stroke={THEME.textMute}/>
    <div style={{ flex: 1 }}>
      <span style={{ color: THEME.text, fontWeight: 600 }}>AllLaw Guide is not a law firm.</span>{' '}
      We help you organize your case and generate documents you can file yourself. Atticus is an AI-backed legal SME, not a licensed attorney.
      {jx === 'ny' && <> {' '}NY-facing legal content is reviewed by attorneys licensed in New York.</>}
      {jx === 'utah' && <> {' '}Operating under Utah Office of Legal Innovation Standing Order 15.</>}
    </div>
  </div>
);

/* Atticus card. Every Atticus output uses this */
/* V3Atticus. Clean message card. Liquid-glass surface, left accent rail,
   compact header with sparkle + identity + jurisdiction pill, body, disclosure footer. */
const V3Atticus = ({ topic, body, jx, dense }) => {
  const isDark = THEME.mode === 'dark';
  return (
    <div style={{
      position: 'relative',
      padding: dense ? '12px 16px 12px 18px' : '14px 18px 12px 20px',
      borderRadius: 12,
      background: isDark ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)',
      backdropFilter: 'blur(28px) saturate(1.6)',
      WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
      border: `1px solid ${THEME.line}`,
      overflow: 'hidden',
    }}>
      {/* Left accent rail. Periwinkle gradient, marks "this is Atticus speaking" */}
      <div style={{
        position: 'absolute', left: 0, top: 0, bottom: 0, width: 3,
        background: `linear-gradient(180deg, ${THEME.blue} 0%, ${THEME.accent} 100%)`,
      }}/>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
        <Icon d={Icons.sparkle} size={13} stroke={THEME.blue} fill={THEME.blue}/>
        <span style={{ fontSize: 11.5, color: THEME.text, fontWeight: 600, letterSpacing: -0.1 }}>Atticus</span>
        <span style={{ width: 3, height: 3, borderRadius: 2, background: THEME.textMute, opacity: 0.5 }}/>
        <span style={{ fontSize: 11, color: THEME.textDim, fontWeight: 500 }}>{topic}</span>
        <div style={{ flex: 1 }}/>
        <div style={{
          fontSize: 8.5, fontWeight: 700, padding: '2px 7px', borderRadius: 999, letterSpacing: 0.5,
          background: jx === 'utah' ? 'rgba(91,167,115,0.18)' : THEME.blueSoft,
          color: jx === 'utah' ? THEME.success : THEME.blue,
        }}>
          {jx === 'utah' ? 'SANDBOX-AUTH · UT' : 'NY COUNSEL-REVIEWED'}
        </div>
      </div>
      {/* Body */}
      <div style={{ fontSize: 12.75, color: THEME.text, lineHeight: 1.55, marginBottom: 8, fontWeight: 400 }}>
        {body}
      </div>
      {/* Footer disclosure */}
      <div style={{
        fontSize: 9.5, color: THEME.textMute, fontStyle: 'italic',
        paddingTop: 7, borderTop: `1px solid ${THEME.lineSoft}`,
        letterSpacing: 0.1,
      }}>
        Atticus output · not legal advice.
      </div>
    </div>
  );
};

/* Tier badge */
const V3TierBadge = ({ tier }) => {
  const map = {
    1: { label: 'Tier 1 · Full pro-se support', tone: THEME.success },
    2: { label: 'Tier 2 · Lawyer handoff', tone: THEME.warn },
    3: { label: 'Tier 3 · Orientation + lawyer match', tone: THEME.blue },
  };
  const { label, tone } = map[tier];
  return (
    <span style={{
      fontSize: 10.5, fontWeight: 700, padding: '3px 9px', borderRadius: 5, letterSpacing: 0.4,
      background: tone + '22', color: tone, border: `1px solid ${tone}40`,
      display: 'inline-flex', alignItems: 'center', gap: 5,
    }}>
      <span style={{ width: 5, height: 5, borderRadius: 3, background: tone }}/>
      {label}
    </span>
  );
};

/* Stage rail */
const V3StageRail = ({ stageIdx }) => {
  const stages = ['Intake', 'Preparation', 'Filing', 'Hearing', 'Resolution'];
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
      {stages.map((s, i) => {
        const done = i < stageIdx, active = i === stageIdx;
        const tone = done || active ? THEME.blue : THEME.lineStrong;
        return (
          <React.Fragment key={s}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <div style={{
                width: 18, height: 18, borderRadius: 9,
                background: done ? THEME.blue : active ? THEME.blue + '30' : 'transparent',
                border: `1.5px solid ${tone}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}>
                {done && <Icon d={Icons.check} size={10} stroke="#fff" sw={3}/>}
                {active && <div style={{ width: 6, height: 6, borderRadius: 3, background: THEME.blue }}/>}
              </div>
              <span style={{ fontSize: 11, color: active ? THEME.text : done ? THEME.textDim : THEME.textMute, fontWeight: active ? 600 : 500 }}>{s}</span>
            </div>
            {i < stages.length - 1 && <div style={{ width: 24, height: 1, background: i < stageIdx ? THEME.blue : THEME.line, margin: '0 8px' }}/>}
          </React.Fragment>
        );
      })}
    </div>
  );
};

/* ================================ SCREENS ================================ */

const V3Home = ({ matter, onScreen }) => {
  const ds = getMatterDataset(matter);
  const m = V3_MATTERS[matter];
  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%' }}>
      {/* Top: matter banner */}
      <div data-coach="home-hero" style={{ marginBottom: 18 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
          <V3TierBadge tier={m.tier}/>
          <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>{m.statute}</span>
        </div>
        <div style={{ fontFamily: 'Fraunces', fontSize: 30, color: THEME.text, fontWeight: 500, letterSpacing: -0.5, lineHeight: 1.1, marginBottom: 6 }}>
          {m.title}
        </div>
        <div style={{ fontSize: 12.5, color: THEME.textDim }}>
          {m.client.name} · {m.client.city} · {m.type}
        </div>
      </div>

      <div data-coach="home-stage" style={{ marginBottom: 20 }}>
        <V3StageRail stageIdx={m.stageIdx}/>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: 18 }}>
        {/* Readiness ring */}
        <V3ReadinessRing matter={matter} onScreen={onScreen}/>

        {/* What's next + Atticus */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <V3Atticus topic={ds.atticus.readiness.topic} body={ds.atticus.readiness.body} jx={m.jurisdiction}/>

          <div style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
            <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1.1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>What's next</div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 18, color: THEME.text, fontWeight: 500, letterSpacing: -0.2, marginBottom: 12 }}>
              Two small things, then your packet is ready to file.
            </div>
            {[
              { label: 'Choose how you want to serve the landlord', sub: 'Certified mail or process server', screen: 'filing' },
              { label: 'Read and affirm your final document set', sub: 'Statement of Claim · Exhibit Index · Damages Worksheet', screen: 'documents' },
            ].map((x, i) => (
              <div key={i} onClick={() => onScreen(x.screen)} style={{
                display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 8, cursor: 'pointer',
                background: THEME.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.6)',
                border: `1px solid ${THEME.lineSoft}`, marginBottom: 6,
              }}>
                <div style={{ width: 6, height: 6, borderRadius: 3, background: THEME.warn }}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 500 }}>{x.label}</div>
                  <div style={{ fontSize: 11, color: THEME.textMute, marginTop: 1 }}>{x.sub}</div>
                </div>
                <Icon d={Icons.arrowRight} size={14} stroke={THEME.textDim}/>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div style={{ marginTop: 18 }}>
        <V3Disclosure jx={m.jurisdiction}/>
      </div>
    </div>
  );
};

/* Universal metadata per readiness factor. Same across all matters */
const READINESS_FACTOR_META = {
  elements: {
    description: 'Each part of the law your case has to prove. And whether you have evidence backing each one up.',
    howCalculated: 'For every part of the law (we call these "elements"), we check: do you have a piece of evidence for it? Have you confirmed that piece of evidence is real? If yes. That part counts. If you uploaded something but haven\'t confirmed it yet, you get partial credit. If there\'s nothing for it, that part doesn\'t count.',
    weight: '40%',
    cta: { label: 'Add or confirm evidence', screen: 'evidence' },
  },
  mapping: {
    description: 'How well each piece of evidence you have lines up with the parts of the law your case needs to prove.',
    howCalculated: "We suggest which piece of evidence supports which part of the law. You confirm or change those suggestions. Only confirmed connections count.",
    weight: '30%',
    cta: { label: 'Review evidence connections', screen: 'evidence' },
  },
  statutory: {
    description: "Whether the law we're using actually matches your state and case type. And whether the version of that law is current.",
    howCalculated: 'Atticus (our legal AI) checks the current law and its last update date against the official state court and state bar feeds. Outside lawyers re-check this twice a year.',
    weight: '15%',
    cta: { label: 'Open the law check', screen: 'theory' },
  },
  procedural: {
    description: "The court-paperwork steps for your case: how to serve papers on the other side, whether you qualify for a fee waiver, hearing prep, etc.",
    howCalculated: "We have a checklist for your specific court and case type. Each step you've finished counts. Each missing step lowers the score.",
    weight: '15%',
    cta: { label: 'Open filing checklist', screen: 'filing' },
  },
};

/* Jargon glossary. Single source of truth for "click to define" terms.
   Atticus + Quentin will expand this as we audit more screens. Keep entries short
   and plain (avoid using legal terms inside legal definitions). */
const JARGON = {
  element: {
    title: 'Element',
    body: 'A specific thing the law says you have to prove for your case to win. Different cases have different elements. For a security-deposit case, one element is "the landlord kept your deposit." For a small-claims fraud case, one element is "the other party knew the statement was false." We list yours out, one by one, so you know exactly what to prove.',
  },
  statute: {
    title: 'Statute',
    body: 'The actual written law passed by the state legislature. The rule book your case is decided against. Statutes have numbers (like "Utah § 57-17-3") so courts can point to the exact section.',
  },
  jurisdiction: {
    title: 'Jurisdiction',
    body: 'The state (and sometimes the specific court) whose rules apply to your case. Your jurisdiction depends on where the events happened, where you live, and what the case is about.',
  },
  proSe: {
    title: 'Pro se',
    body: 'A Latin phrase meaning "representing yourself." When you file a case without hiring a lawyer, you\'re pro se. Courts allow it, and we\'re built to help you do it well.',
  },
  service: {
    title: 'Service of process',
    body: "Officially notifying the other side that you've filed a case against them. The court won't move forward until they're properly served. There are specific rules about how it has to be done.",
  },
  affirmation: {
    title: 'Affirm',
    body: 'When you click "Affirm" on a piece of evidence, you\'re telling us. And the court audit log. That the fact is true and is yours. You\'re the one signing off, not the AI.',
  },
  legalTheory: {
    title: 'Legal theory',
    body: 'The specific legal argument you\'re making. It names the law you say was broken and the harm that resulted. Your case sits on top of one or more legal theories.',
  },
  causeOfAction: {
    title: 'Cause of action',
    body: 'A formal name for a legal claim. Each cause of action has elements you have to prove. "Breach of contract" and "wrongful retention of security deposit" are examples.',
  },
  trebleDamages: {
    title: 'Treble damages',
    body: 'Three times the actual damages. Some statutes allow the court to triple what you\'re owed when the other side\'s conduct was willful or particularly bad. The court chooses whether to award it.',
  },
  statutoryDamages: {
    title: 'Statutory damages',
    body: 'A fixed amount the law itself sets, separate from your actual losses. Often a small dollar figure that\'s automatic if you prove the violation. NY GBL § 349 has a $50 statutory damages line.',
  },
  consumerOriented: {
    title: 'Consumer-oriented',
    body: 'Conduct aimed at the general public, not a private dispute between two specific parties. NY GBL § 349 requires the conduct to be consumer-oriented before it counts as a deceptive practice.',
  },
  substantialPerformance: {
    title: 'Substantial performance',
    body: 'When you\'ve done most of what a contract required, even if not perfectly. Courts often credit substantial performance toward what you\'re owed under the contract.',
  },
  statementOfClaim: {
    title: 'Statement of claim',
    body: 'The court form that opens a small-claims case. It says who you\'re suing, what you want, and why you\'re entitled to it. The court uses it to schedule the hearing.',
  },
  exhibitIndex: {
    title: 'Exhibit index',
    body: 'A list of every piece of evidence you\'re bringing to court, numbered and described. Helps the judge follow your case and helps you stay organized at the hearing.',
  },
  damagesWorksheet: {
    title: 'Damages worksheet',
    body: 'A document that shows the math behind what you\'re asking for. Each dollar amount traces back to a fact you affirmed. Courts find it persuasive when damages are easy to verify.',
  },
};

/* Term. Wraps a jargon word with a click-to-define popover. Use anywhere in
   user-facing copy. <Term k="element">elements</Term> renders the word with a
   subtle dotted underline; clicking opens a small definition card. */
const Term = ({ k, children }) => {
  const [open, setOpen] = React.useState(false);
  const def = JARGON[k];
  if (!def) return <>{children}</>;
  return (
    <span style={{ position: 'relative', display: 'inline' }}>
      <span
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        style={{
          cursor: 'help', borderBottom: `1px dotted ${THEME.blue}`,
          color: open ? THEME.blue : 'inherit',
        }}
      >{children}</span>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 79 }}/>
          <div style={{
            position: 'absolute', top: '1.4em', left: 0, zIndex: 80,
            width: 280, padding: '12px 14px', borderRadius: 10,
            background: THEME.mode === 'dark' ? 'rgba(22,28,36,0.98)' : 'rgba(255,255,255,0.99)',
            border: `1px solid ${THEME.blue}40`,
            boxShadow: THEME.mode === 'dark' ? '0 12px 32px rgba(0,0,0,0.55)' : '0 12px 32px rgba(20,30,50,0.18)',
            fontSize: 12, lineHeight: 1.5, color: THEME.text, fontWeight: 400,
            textTransform: 'none', letterSpacing: 0,
          }}>
            <div style={{ fontFamily: 'Fraunces', fontSize: 14, fontWeight: 500, color: THEME.blue, marginBottom: 4 }}>{def.title}</div>
            <div style={{ color: THEME.textDim }}>{def.body}</div>
          </div>
        </>
      )}
    </span>
  );
};

const V3ReadinessRing = ({ matter, onScreen }) => {
  const m = V3_MATTERS[matter];
  const score = m.readiness;
  const pct = score / 100;
  const r = 56, c = 2 * Math.PI * r;
  const tone = score >= 75 ? THEME.success : score >= 50 ? THEME.warn : THEME.danger;
  const [popover, setPopover] = React.useState(null); // factor key or null

  return (
    <>
    {/* Spotlight scrim. Dims everything when a factor popover is open. The card
        below has higher z-index, so it stays bright above the scrim. */}
    {popover && (
      <div onClick={() => setPopover(null)} style={{
        position: 'fixed', inset: 0, zIndex: 49,
        background: 'rgba(0,0,0,0.55)',
        backdropFilter: 'blur(2px)',
        WebkitBackdropFilter: 'blur(2px)',
        animation: 'none',
        cursor: 'pointer',
      }}/>
    )}
    <div data-coach="home-readiness" style={{
      padding: 20, borderRadius: 14, position: 'relative',
      background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
      border: `1px solid ${popover ? THEME.blue + '60' : THEME.line}`,
      /* When popover is open: lift the whole card above the scrim with a glow */
      ...(popover ? {
        zIndex: 50,
        boxShadow: THEME.mode === 'dark'
          ? `0 0 0 1px ${THEME.blue}40, 0 24px 60px rgba(0,0,0,0.6), 0 0 80px ${THEME.blue}20`
          : `0 0 0 1px ${THEME.blue}40, 0 24px 60px rgba(20,30,50,0.25), 0 0 80px ${THEME.blue}20`,
        transition: 'box-shadow 0.22s ease, border-color 0.22s ease',
      } : {
        transition: 'box-shadow 0.22s ease, border-color 0.22s ease',
      }),
    }}>
      <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1.1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>Case readiness</div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', height: 140 }}>
        <svg width={140} height={140} style={{ transform: 'rotate(-90deg)' }}>
          <circle cx={70} cy={70} r={r} fill="none" stroke={THEME.line} strokeWidth={9}/>
          <circle cx={70} cy={70} r={r} fill="none" stroke={tone} strokeWidth={9}
            strokeDasharray={c} strokeDashoffset={c * (1 - pct)} strokeLinecap="round"
            style={{ transition: 'stroke-dashoffset 0.6s ease' }}/>
        </svg>
        <div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
          <div style={{ fontFamily: 'Fraunces', fontSize: 38, color: THEME.text, fontWeight: 500, letterSpacing: -1, lineHeight: 1 }}>{score}</div>
          <div style={{ fontSize: 9.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginTop: 2 }}>of 100</div>
        </div>
      </div>
      <div style={{ textAlign: 'center', fontSize: 12, color: tone, fontWeight: 600, marginTop: 4, marginBottom: 12 }}>
        {score >= 75 ? '✓ Above file-ready threshold' : 'Below 75 file-ready threshold'}
      </div>

      {/* Factors. Always visible. Click any row to open popover with details. */}
      {m.readinessBreakdown && (
        <div style={{ borderTop: `1px solid ${THEME.lineSoft}`, paddingTop: 12 }}>
          {Object.entries(m.readinessBreakdown).map(([key, b]) => {
            const pctFactor = (b.score / b.max) * 100;
            const isOpen = popover === key;
            return (
              <div key={key}
                onClick={(e) => { e.stopPropagation(); setPopover(isOpen ? null : key); }}
                style={{
                  marginBottom: 8, padding: '6px 8px', borderRadius: 7, cursor: 'pointer',
                  // Selected row LIGHTENS to match the popover info box, so the
                  // visual connection between the row and its popover is obvious.
                  background: isOpen
                    ? (THEME.mode === 'dark' ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.95)')
                    : 'transparent',
                  border: `1px solid ${isOpen ? THEME.blue + '70' : 'transparent'}`,
                  boxShadow: isOpen
                    ? (THEME.mode === 'dark'
                        ? `0 0 0 3px ${THEME.blue}18, 0 4px 14px rgba(0,0,0,0.35)`
                        : `0 0 0 3px ${THEME.blue}22, 0 4px 14px rgba(20,30,50,0.10)`)
                    : 'none',
                  transition: 'background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease',
                }}
                onMouseEnter={(e) => !isOpen && (e.currentTarget.style.background = THEME.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.55)')}
                onMouseLeave={(e) => !isOpen && (e.currentTarget.style.background = 'transparent')}
              >
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 11, marginBottom: 4 }}>
                  <span style={{ color: THEME.textDim, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                    {b.label}
                    <span style={{ fontSize: 9, color: THEME.textMute, fontWeight: 600 }}>· {READINESS_FACTOR_META[key]?.weight}</span>
                  </span>
                  <span style={{ color: THEME.text, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>
                    {b.score}<span style={{ color: THEME.textMute, fontWeight: 500 }}>/{b.max}</span>
                  </span>
                </div>
                <div style={{ height: 4, background: THEME.line, borderRadius: 2, overflow: 'hidden' }}>
                  <div style={{
                    width: `${pctFactor}%`, height: '100%',
                    background: pctFactor >= 80 ? THEME.success : pctFactor >= 60 ? THEME.blue : THEME.warn,
                    transition: 'width 0.6s ease',
                  }}/>
                </div>
              </div>
            );
          })}
          <div style={{ fontSize: 10, color: THEME.textMute, textAlign: 'center', marginTop: 4 }}>
            Tap any factor for details
          </div>
        </div>
      )}

      {/* Popover for the active factor */}
      {popover && m.readinessBreakdown && m.readinessBreakdown[popover] && (
        <>
          <div onClick={() => setPopover(null)} style={{
            position: 'fixed', inset: 0, zIndex: 39,
          }}/>
          <div style={{
            position: 'absolute', top: 8, left: 'calc(100% + 12px)', zIndex: 40,
            width: 360, padding: 18, borderRadius: 14,
            background: THEME.mode === 'dark' ? 'rgba(22,28,36,0.96)' : 'rgba(255,255,255,0.98)',
            backdropFilter: 'blur(28px) saturate(1.6)',
            WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
            border: `1px solid ${THEME.line}`,
            boxShadow: THEME.mode === 'dark'
              ? '0 16px 40px rgba(0,0,0,0.55), 0 4px 12px rgba(0,0,0,0.3)'
              : '0 16px 40px rgba(20,30,50,0.18), 0 4px 12px rgba(20,30,50,0.08)',
          }}>
            {(() => {
              const b = m.readinessBreakdown[popover];
              const meta = READINESS_FACTOR_META[popover] || {};
              const pctFactor = (b.score / b.max) * 100;
              return (
                <>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
                    <span style={{ fontSize: 10, color: THEME.textMute, letterSpacing: 1.2, textTransform: 'uppercase', fontWeight: 600 }}>Readiness factor · weight {meta.weight}</span>
                    <span style={{ fontFamily: 'Fraunces', fontSize: 18, color: THEME.text, fontWeight: 500, fontVariantNumeric: 'tabular-nums' }}>
                      {b.score}<span style={{ color: THEME.textMute, fontWeight: 500 }}>/{b.max}</span>
                    </span>
                  </div>
                  <div style={{ fontFamily: 'Fraunces', fontSize: 18, color: THEME.text, fontWeight: 500, letterSpacing: -0.2, marginBottom: 10 }}>{b.label}</div>
                  <div style={{ height: 5, background: THEME.line, borderRadius: 3, overflow: 'hidden', marginBottom: 14 }}>
                    <div style={{
                      width: `${pctFactor}%`, height: '100%',
                      background: pctFactor >= 80 ? THEME.success : pctFactor >= 60 ? THEME.blue : THEME.warn,
                    }}/>
                  </div>
                  <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>What it is</div>
                  <div style={{ fontSize: 12.5, color: THEME.text, lineHeight: 1.55, marginBottom: 12 }}>{meta.description}</div>
                  <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>How it's calculated</div>
                  <div style={{ fontSize: 12, color: THEME.textDim, lineHeight: 1.55, marginBottom: 12 }}>{meta.howCalculated}</div>
                  {b.missing && (
                    <>
                      <div style={{ fontSize: 11, color: THEME.warnText, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>Why this score</div>
                      <div style={{ fontSize: 12.5, color: THEME.text, lineHeight: 1.55, padding: '10px 12px', borderRadius: 8, background: THEME.warnSoft, border: `1px solid ${THEME.warn}30` }}>{b.missing}</div>
                    </>
                  )}
                  {/* Action row: primary CTA jumps to the relevant screen so the user can fix it. */}
                  <div style={{ marginTop: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
                    <div onClick={() => setPopover(null)} style={{ fontSize: 12, color: THEME.textDim, fontWeight: 500, cursor: 'pointer' }}>
                      Close
                    </div>
                    {meta.cta && onScreen && (
                      <div
                        onClick={() => { setPopover(null); onScreen(meta.cta.screen); }}
                        style={{
                          padding: '9px 14px', borderRadius: 8,
                          background: THEME.blue, color: '#fff',
                          fontSize: 12, fontWeight: 600, letterSpacing: 0.2,
                          cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6,
                          boxShadow: `0 4px 12px ${THEME.blue}40`,
                          transition: 'transform 0.12s ease, box-shadow 0.12s ease',
                        }}
                        onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-1px)'; e.currentTarget.style.boxShadow = `0 6px 16px ${THEME.blue}55`; }}
                        onMouseLeave={(e) => { e.currentTarget.style.transform = 'none'; e.currentTarget.style.boxShadow = `0 4px 12px ${THEME.blue}40`; }}
                      >
                        {meta.cta.label}
                        <Icon d={Icons.arrowRight} size={13} stroke="#fff" sw={2.2}/>
                      </div>
                    )}
                  </div>
                </>
              );
            })()}
          </div>
        </>
      )}
    </div>
    </>
  );
};

/* Theory: cause-of-action selection. UPL B.2 pattern */
const V3Theory = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const causes = ds.causes;
  const [selected, setSelected] = React.useState(causes[0].id);
  const [affirmed, setAffirmed] = React.useState(true);
  React.useEffect(() => { setSelected(ds.causes[0].id); setAffirmed(true); }, [matter]);
  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%', maxWidth: 980 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
        <V3TierBadge tier={m.tier}/>
        <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>Cause of action</span>
      </div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 6 }}>
        Which <Term k="legalTheory">legal theory</Term> describes what happened?
      </div>
      <div style={{ fontSize: 13, color: THEME.textDim, lineHeight: 1.55, marginBottom: 16, maxWidth: 720 }}>
        Cases can sometimes fit more than one theory. We've laid out the options below with what each one applies to and what you'd need to prove. <span style={{ color: THEME.text, fontWeight: 600 }}>You choose which to bring</span>. We don't decide for you.
      </div>

      <V3Atticus topic={ds.atticus.cause.topic} body={ds.atticus.cause.body} jx={m.jurisdiction}/>

      <div data-coach="theory-causes" style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 14 }}>
        {causes.map(c => {
          const sel = selected === c.id;
          return (
            <div key={c.id} onClick={() => { setSelected(c.id); setAffirmed(false); }} style={{
              padding: '16px 18px', borderRadius: 13, cursor: 'pointer',
              background: sel ? (THEME.mode === 'dark' ? 'rgba(105,134,191,0.10)' : 'rgba(105,134,191,0.08)') : THEME.mode === 'dark' ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.55)',
              border: `1.5px solid ${sel ? THEME.blue : THEME.line}`,
            }}>
              <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
                <div style={{
                  width: 22, height: 22, borderRadius: 6, marginTop: 2, flexShrink: 0,
                  border: `1.5px solid ${sel ? THEME.blue : THEME.lineStrong}`,
                  background: sel ? THEME.blue : 'transparent',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>{sel && <Icon d={Icons.check} size={13} stroke="#fff" sw={3}/>}</div>
                <div style={{ flex: 1 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
                    <div style={{ fontFamily: 'Fraunces', fontSize: 17, color: THEME.text, fontWeight: 500 }}>{c.label}</div>
                    <Term k="statute"><span style={{ fontSize: 10.5, padding: '1px 6px', borderRadius: 4, background: THEME.line, color: THEME.textDim, fontWeight: 600, cursor: 'help' }}>{c.cite}</span></Term>
                    {c.remedy && <span style={{ fontSize: 10.5, padding: '1px 6px', borderRadius: 4, background: THEME.success + '22', color: THEME.success, fontWeight: 600 }}>{c.remedy}</span>}
                  </div>
                  <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 8 }}>
                    <span style={{ color: THEME.text, fontWeight: 600 }}>This applies if:</span> {c.applies}
                  </div>
                  {sel && (
                    <div style={{ paddingTop: 8, borderTop: `1px solid ${THEME.lineSoft}` }}>
                      <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 6 }}>You'd need to prove:</div>
                      {c.prove.map((p, i) => (
                        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, color: THEME.text, padding: '3px 0' }}>
                          <div style={{ width: 14, height: 14, borderRadius: 7, background: THEME.success + '22', border: `1px solid ${THEME.success}40`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                            <Icon d={Icons.check} size={8} stroke={THEME.success} sw={3}/>
                          </div>
                          {p}
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            </div>
          );
        })}
      </div>

      {selected && (
        <div style={{ marginTop: 16, padding: '14px 16px', borderRadius: 12, background: affirmed ? THEME.successSoft : THEME.warnSoft, border: `1px solid ${affirmed ? THEME.success + '40' : THEME.warn + '40'}` }}>
          <div onClick={() => setAffirmed(a => !a)} style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
            <div style={{
              width: 20, height: 20, borderRadius: 5, flexShrink: 0,
              border: `1.5px solid ${affirmed ? THEME.success : THEME.warn}`,
              background: affirmed ? THEME.success : 'transparent',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>{affirmed && <Icon d={Icons.check} size={12} stroke="#fff" sw={3}/>}</div>
            <div style={{ flex: 1, fontSize: 13, color: THEME.text, fontWeight: 500 }}>
              I affirm: <span style={{ fontWeight: 600 }}>this is the <Term k="legalTheory">legal theory</Term> I want to bring.</span> The system presented options; I made the choice.
            </div>
          </div>
        </div>
      )}

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Per-evidence Atticus affirmation modal */
const V3EvidenceAffirmModal = ({ ev, jx, evidenceReads, elements, onAffirm, onChooseDifferently, onMarkUnrelated, onClose }) => {
  if (!ev) return null;
  const reads = evidenceReads || V3_EVIDENCE_READS;
  const els = elements || V3_ELEMENTS_DEPOSIT_UT;
  const read = reads[ev.id] || { elements: ev.maps, why: "Atticus suggests this piece supports the elements shown below. Confirm the connection or change it." };
  const elNames = read.elements.map(eId => {
    const el = els.find(x => x.id === eId);
    const idx = els.findIndex(x => x.id === eId) + 1;
    return `Element ${idx} ("${el ? el.label : eId}")`;
  }).join(' and ');
  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 30,
      background: THEME.mode === 'dark' ? 'rgba(18,25,33,0.78)' : 'rgba(40,40,40,0.42)',
      backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 32,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        maxWidth: 560, width: '100%', padding: '24px 26px', borderRadius: 16,
        background: THEME.mode === 'dark' ? '#23303D' : '#FFF',
        border: `1px solid ${THEME.line}`, boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
          <div style={{
            width: 26, height: 26, borderRadius: 13,
            background: `linear-gradient(135deg, ${THEME.blue}, ${THEME.accent})`,
            display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
          }}>
            <Icon d={Icons.sparkle} size={13} stroke="#fff" fill="#fff"/>
          </div>
          <div style={{ fontSize: 12, color: THEME.text, fontWeight: 600 }}>
            Atticus · <span style={{ color: THEME.textDim, fontWeight: 500 }}>Evidence-element mapping</span>
          </div>
          <div style={{ flex: 1 }}/>
          <div style={{
            fontSize: 9, fontWeight: 700, padding: '2px 6px', borderRadius: 4, letterSpacing: 0.5,
            background: jx === 'utah' ? 'rgba(91,167,115,0.18)' : THEME.blueSoft,
            color: jx === 'utah' ? THEME.success : THEME.blue,
          }}>{jx === 'utah' ? 'SANDBOX-AUTHORIZED' : 'NY COUNSEL-REVIEWED'}</div>
        </div>
        <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1.1, textTransform: 'uppercase', fontWeight: 600, marginTop: 12, marginBottom: 4 }}>Atticus's read on this:</div>
        <div style={{ fontFamily: 'Fraunces', fontSize: 16, color: THEME.text, fontWeight: 500, lineHeight: 1.4, marginBottom: 8, letterSpacing: -0.2 }}>
          "{ev.name}"
        </div>
        <div style={{ fontSize: 13, color: THEME.text, lineHeight: 1.6, marginBottom: 14 }}>
          This document supports {elNames}. Here's why: {read.why}
        </div>
        <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.55, marginBottom: 18, fontStyle: 'italic' }}>
          Is that the right connection?
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <div onClick={() => onAffirm(ev.id)} style={{
            padding: '12px 16px', borderRadius: 9, cursor: 'pointer',
            background: THEME.blue, color: '#fff',
            fontSize: 13, fontWeight: 600,
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          }}>
            <span>Affirm this mapping</span>
            <Icon d={Icons.arrowRight} size={14} stroke="#fff"/>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <div onClick={() => onChooseDifferently(ev.id)} style={{
              flex: 1, padding: '10px 14px', borderRadius: 9, cursor: 'pointer',
              background: 'transparent', color: THEME.text,
              border: `1px solid ${THEME.line}`,
              fontSize: 12.5, fontWeight: 600, textAlign: 'center',
            }}>Choose differently →</div>
            <div onClick={() => onMarkUnrelated(ev.id)} style={{
              flex: 1, padding: '10px 14px', borderRadius: 9, cursor: 'pointer',
              background: 'transparent', color: THEME.textDim,
              border: `1px solid ${THEME.line}`,
              fontSize: 12.5, fontWeight: 600, textAlign: 'center',
            }}>Mark as unrelated →</div>
          </div>
        </div>

        <div style={{ marginTop: 14, paddingTop: 12, borderTop: `1px solid ${THEME.lineSoft}`, fontSize: 10.5, color: THEME.textMute, fontStyle: 'italic' }}>
          This is Atticus output, not legal advice.
        </div>
      </div>
    </div>
  );
};

/* Evidence + element mapping with per-element affirmation gate (Fix 1) */
const V3Evidence = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const evidence = ds.evidence;
  const elements = ds.elements;
  const evidenceReads = ds.evidenceReads;
  // affirmStatus: { [evId]: 'affirmed' | 'changed' | 'unrelated' | undefined }
  const [affirmStatus, setAffirmStatus] = React.useState(ds.initialAffirmed);
  const [modalEv, setModalEv] = React.useState(null);
  React.useEffect(() => { setAffirmStatus(ds.initialAffirmed); setModalEv(null); }, [matter]);

  const handleAffirm = (id) => { setAffirmStatus(s => ({ ...s, [id]: 'affirmed' })); setModalEv(null); };
  const handleChange = (id) => { setAffirmStatus(s => ({ ...s, [id]: 'changed' })); setModalEv(null); };
  const handleUnrelated = (id) => { setAffirmStatus(s => ({ ...s, [id]: 'unrelated' })); setModalEv(null); };

  // Element status derived from affirmations
  const elementStatus = (eId) => {
    const supportingEv = evidence.filter(e => e.maps.includes(eId) && affirmStatus[e.id] === 'affirmed');
    return supportingEv.length > 0 ? 'supported' : 'missing';
  };

  const totalAffirmed = Object.values(affirmStatus).filter(v => v === 'affirmed').length;
  const elementsCovered = elements.filter(el => elementStatus(el.id) === 'supported').length;
  const fileReady = elementsCovered === elements.length;

  return (
    <div style={{ padding: '24px 36px', overflow: 'auto', height: '100%', position: 'relative' }}>
      <div style={{ marginBottom: 4, fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>Evidence + element mapping</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 24, color: THEME.text, fontWeight: 500, letterSpacing: -0.3, marginBottom: 6 }}>
        Now let's connect your evidence to what you need to prove.
      </div>
      <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.55, marginBottom: 14, maxWidth: 760 }}>
        Each piece of evidence supports one or more "elements". The things you need to prove. We'll suggest matches based on what we see, but <span style={{ color: THEME.text, fontWeight: 600 }}>you confirm each one</span>. You're the one making the legal argument; we're just helping you organize it.
      </div>

      {/* Progress strip */}
      <div data-coach="evidence-progress" style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '10px 14px', borderRadius: 10, background: fileReady ? THEME.successSoft : THEME.warnSoft, border: `1px solid ${fileReady ? THEME.success + '40' : THEME.warn + '40'}`, marginBottom: 14 }}>
        <Icon d={fileReady ? Icons.check : Icons.alert} size={14} stroke={fileReady ? THEME.success : THEME.warn}/>
        <div style={{ flex: 1, fontSize: 12.5, color: THEME.text }}>
          <span style={{ fontWeight: 600 }}>{totalAffirmed} of {evidence.length}</span> evidence items affirmed · <span style={{ fontWeight: 600 }}>{elementsCovered} of {elements.length}</span> elements supported
        </div>
        <div style={{ fontSize: 11.5, color: fileReady ? THEME.success : THEME.warn, fontWeight: 600 }}>
          {fileReady ? '✓ File-ready threshold met' : 'Each evidence item must support an element before file-ready'}
        </div>
      </div>

      <div style={{ padding: '10px 12px', borderRadius: 9, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, fontSize: 11.5, color: THEME.textDim, marginBottom: 14, display: 'flex', gap: 8, alignItems: 'flex-start' }}>
        <Icon d={Icons.alert} size={13} stroke={THEME.textMute}/>
        <div>
          <span style={{ fontWeight: 600, color: THEME.text }}>EXIF / OCR note:</span> Photos retain their original capture dates and GPS coordinates so you can show the court when and where each was taken. You can review or remove this data before filing.
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 320px', gap: 16 }}>
        {/* Evidence list */}
        <div data-coach="evidence-list">
          <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>Your evidence · {evidence.length} items</div>
          {evidence.map(ev => {
            const status = affirmStatus[ev.id];
            const isAffirmed = status === 'affirmed';
            const isUnrelated = status === 'unrelated';
            const isChanged = status === 'changed';
            const tone = isAffirmed ? THEME.success : isUnrelated ? THEME.textMute : isChanged ? THEME.warn : THEME.warn;
            const statusLabel = isAffirmed ? 'Affirmed' : isUnrelated ? 'Marked unrelated' : isChanged ? 'Re-mapping needed' : 'Awaiting your affirmation';
            return (
              <div key={ev.id} onClick={() => setModalEv(ev)} style={{
                padding: '12px 14px', borderRadius: 10, cursor: 'pointer', marginBottom: 6,
                background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)',
                border: `1px solid ${isAffirmed ? THEME.success + '50' : THEME.line}`,
                borderLeft: `3px solid ${tone}`,
                opacity: isUnrelated ? 0.6 : 1,
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                  <Icon d={Icons.paperclip} size={14} stroke={THEME.textDim}/>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 500 }}>{ev.name}</div>
                    <div style={{ fontSize: 10.5, color: THEME.textMute, marginTop: 2 }}>
                      {ev.type}{ev.exif && ` · ${ev.exif}`}
                    </div>
                  </div>
                  <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
                    {!isUnrelated && ev.maps.map(eId => (
                      <span key={eId} style={{ fontSize: 9, fontWeight: 700, padding: '2px 5px', borderRadius: 4, background: isAffirmed ? THEME.success + '22' : THEME.line, color: isAffirmed ? THEME.success : THEME.textDim, letterSpacing: 0.4 }}>
                        E{elements.findIndex(x => x.id === eId) + 1}
                      </span>
                    ))}
                    <span style={{ fontSize: 9.5, fontWeight: 700, padding: '3px 7px', borderRadius: 4, background: tone + '22', color: tone, letterSpacing: 0.4, textTransform: 'uppercase' }}>
                      {isAffirmed ? '✓ AFFIRMED' : isUnrelated ? 'UNRELATED' : isChanged ? 'CHANGED' : 'GATE'}
                    </span>
                  </div>
                </div>
                <div style={{ marginTop: 6, fontSize: 11, color: tone, fontWeight: 500 }}>
                  {isAffirmed ? '✓ ' : ''}{statusLabel}{!status && '. Tap to review Atticus\'s read'}
                </div>
              </div>
            );
          })}
        </div>

        {/* Elements panel */}
        <div data-coach="evidence-elements">
          <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>Elements you must prove</div>
          {elements.map((el, i) => {
            const isSupported = elementStatus(el.id) === 'supported';
            const evCount = evidence.filter(e => e.maps.includes(el.id) && affirmStatus[e.id] === 'affirmed').length;
            return (
              <div key={el.id} style={{ padding: '12px 13px', borderRadius: 10, marginBottom: 6, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, borderLeft: `3px solid ${isSupported ? THEME.success : THEME.warn}` }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                  <span style={{ fontSize: 9.5, fontWeight: 700, padding: '2px 5px', borderRadius: 3, background: isSupported ? THEME.success : THEME.warn, color: '#fff' }}>{i + 1}</span>
                  <span style={{ fontSize: 11, color: isSupported ? THEME.success : THEME.warn, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase', display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                    <Icon d={isSupported ? Icons.checkCircle : Icons.warnCircle} size={13} stroke={isSupported ? THEME.success : THEME.warn} sw={1.8}/>
                    {isSupported ? `${evCount} affirmed` : 'missing'}
                  </span>
                </div>
                <div style={{ fontSize: 12, color: THEME.text, fontWeight: 500, lineHeight: 1.4, marginBottom: 4 }}>{el.label}</div>
                <div style={{ fontSize: 11, color: THEME.textDim, lineHeight: 1.45 }}>{el.explain}</div>
              </div>
            );
          })}
        </div>
      </div>

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>

      <V3EvidenceAffirmModal
        ev={modalEv}
        jx={m.jurisdiction}
        evidenceReads={evidenceReads}
        elements={elements}
        onAffirm={handleAffirm}
        onChooseDifferently={handleChange}
        onMarkUnrelated={handleUnrelated}
        onClose={() => setModalEv(null)}
      />
    </div>
  );
};

/* Damages. Step 1: theory cards (recommended/costs/grayed) + Step 2: arithmetic table (Fix 5) */
const V3Damages = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const theoryCards = ds.damagesTheoryCards;
  const [theories, setTheories] = React.useState(ds.damagesInitial);
  React.useEffect(() => { setTheories(ds.damagesInitial); }, [matter]);

  // Arithmetic table. Derived from affirmed theories
  const items = ds.damagesItems(theories);
  const total = items.filter(i => i.include).reduce((s, i) => s + i.amount, 0);

  const [showWhy, setShowWhy] = React.useState(false);

  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%', maxWidth: 920 }}>
      <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase', marginBottom: 4 }}>Damages · Step 1 of 2</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 6 }}>
        What damages do you want to seek?
      </div>
      <div style={{ fontSize: 13, color: THEME.textDim, lineHeight: 1.55, marginBottom: 14, maxWidth: 720 }}>
        {ds.damagesIntroLine}
      </div>

      <V3Atticus topic={ds.atticus.damages.topic} body={ds.atticus.damages.body} jx={m.jurisdiction}/>

      {/* Theory cards */}
      <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
        {theoryCards.map(card => {
          const checked = card.eligible && theories[card.id];
          const disabled = !card.eligible;
          const borderColor = disabled ? THEME.line : checked ? THEME.blue : THEME.line;
          return (
            <div key={card.id} onClick={() => !disabled && setTheories(t => ({ ...t, [card.id]: !t[card.id] }))} style={{
              padding: '16px 18px', borderRadius: 13, cursor: disabled ? 'default' : 'pointer',
              background: disabled
                ? (THEME.mode === 'dark' ? 'rgba(255,255,255,0.015)' : 'rgba(0,0,0,0.025)')
                : checked
                  ? (THEME.mode === 'dark' ? 'rgba(105,134,191,0.10)' : 'rgba(105,134,191,0.08)')
                  : THEME.mode === 'dark' ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.55)',
              border: `1.5px solid ${card.recommended ? THEME.blue : borderColor}`,
              opacity: disabled ? 0.55 : 1,
              position: 'relative',
            }}>
              {card.recommended && (
                <div style={{ position: 'absolute', top: -10, left: 16, fontSize: 9.5, fontWeight: 700, padding: '3px 8px', borderRadius: 4, background: THEME.blue, color: '#fff', letterSpacing: 0.5 }}>
                  RECOMMENDED FOR YOUR CASE
                </div>
              )}
              <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
                {/* Affirm checkbox or info marker */}
                <div style={{
                  width: 22, height: 22, borderRadius: 6, marginTop: 2, flexShrink: 0,
                  border: `1.5px solid ${disabled ? THEME.lineStrong : checked ? THEME.blue : THEME.lineStrong}`,
                  background: checked ? THEME.blue : 'transparent',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  {checked && <Icon d={Icons.check} size={13} stroke="#fff" sw={3}/>}
                  {disabled && <span style={{ fontSize: 11, color: THEME.textMute }}>-</span>}
                </div>
                <div style={{ flex: 1 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4, flexWrap: 'wrap' }}>
                    {(() => {
                      const tone = card.icon === 'check' ? THEME.success : card.icon === 'warn' ? THEME.warn : THEME.blue;
                      const glyph = card.icon === 'check' ? Icons.checkCircle : card.icon === 'warn' ? Icons.warnCircle : Icons.infoCircle;
                      return <Icon d={glyph} size={18} stroke={tone} sw={1.8}/>;
                    })()}
                    <div style={{ fontFamily: 'Fraunces', fontSize: 17, color: THEME.text, fontWeight: 500, letterSpacing: -0.2 }}>{card.title}</div>
                    <span style={{ fontSize: 10.5, padding: '1px 6px', borderRadius: 4, background: THEME.line, color: THEME.textDim, fontWeight: 600 }}>{card.cite}</span>
                  </div>
                  <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.55, marginBottom: 6 }}>{card.body}</div>
                  {card.trigger && (
                    <div style={{ fontSize: 11.5, color: THEME.textMute, fontStyle: 'italic', lineHeight: 1.5 }}>{card.trigger}</div>
                  )}
                  {disabled && card.readWhy && (
                    <>
                      <div onClick={(e) => { e.stopPropagation(); setShowWhy(w => !w); }} style={{ fontSize: 11.5, color: THEME.blue, fontWeight: 600, marginTop: 6, cursor: 'pointer' }}>
                        {showWhy ? 'Hide why →' : 'Read why →'}
                      </div>
                      {showWhy && (
                        <div style={{ marginTop: 8, padding: '10px 12px', borderRadius: 8, background: THEME.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)', border: `1px solid ${THEME.lineSoft}`, fontSize: 11.5, color: THEME.textDim, lineHeight: 1.55 }}>
                          {card.readWhy}
                        </div>
                      )}
                    </>
                  )}
                </div>
                <div style={{ fontFamily: 'Fraunces', fontSize: 22, color: disabled ? THEME.textMute : THEME.text, fontWeight: 500, letterSpacing: -0.5, fontVariantNumeric: 'tabular-nums' }}>
                  {card.headline}
                </div>
              </div>
            </div>
          );
        })}
      </div>

      {/* Step 2. Arithmetic table */}
      <div data-coach="damages-math" style={{ marginTop: 24, paddingTop: 20, borderTop: `1px solid ${THEME.line}` }}>
        <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase', marginBottom: 4 }}>Damages · Step 2 of 2</div>
        <div style={{ fontFamily: 'Fraunces', fontSize: 22, color: THEME.text, fontWeight: 500, letterSpacing: -0.3, marginBottom: 8 }}>
          Here's the math.
        </div>
        <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 12, lineHeight: 1.55 }}>
          This calculation is mechanical. We add and multiply numbers you've already affirmed. No legal judgment is made here. The damage theories you affirmed in the previous step are what make these line items eligible.
        </div>

        <div style={{ borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, overflow: 'hidden' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 2fr 1fr', padding: '10px 16px', fontSize: 10.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, background: THEME.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)', borderBottom: `1px solid ${THEME.lineSoft}` }}>
            <span>Item</span><span>Calculation</span><span style={{ textAlign: 'right' }}>Amount</span>
          </div>
          {items.map((it, i) => (
            <div key={i} style={{
              display: 'grid', gridTemplateColumns: '2fr 2fr 1fr',
              padding: '12px 16px', borderBottom: i < items.length - 1 ? `1px solid ${THEME.lineSoft}` : 'none',
              opacity: it.include ? 1 : 0.4,
              fontSize: 12.5, color: THEME.text, alignItems: 'center',
            }}>
              <span style={{ fontWeight: 500 }}>{it.label}</span>
              <span style={{ color: THEME.textDim, fontSize: 11.5 }}>{it.calc}</span>
              <span style={{ textAlign: 'right', fontFamily: 'Fraunces', fontWeight: 500, fontVariantNumeric: 'tabular-nums', fontSize: 14 }}>
                {it.include ? `$${it.amount.toLocaleString()}` : '-'}
              </span>
            </div>
          ))}
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 2fr 1fr', padding: '14px 16px', alignItems: 'center', background: THEME.mode === 'dark' ? 'rgba(105,134,191,0.06)' : 'rgba(105,134,191,0.05)', borderTop: `1px solid ${THEME.line}` }}>
            <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 700 }}>Total recovery sought</span>
            <span/>
            <span style={{ textAlign: 'right', fontFamily: 'Fraunces', fontSize: 24, color: THEME.text, fontWeight: 500, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.5 }}>
              ${total.toLocaleString()}
            </span>
          </div>
        </div>
      </div>

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Documents + final affirmation gate */
const V3Documents = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const d = ds.docs;
  const [opened, setOpened] = React.useState(0);
  const [affirms, setAffirms] = React.useState(() =>
    Object.fromEntries(d.affirmations.map(a => [a.k, a.k !== d.affirmations[d.affirmations.length - 1].k]))
  );
  React.useEffect(() => {
    setOpened(0);
    setAffirms(Object.fromEntries(d.affirmations.map(a => [a.k, a.k !== d.affirmations[d.affirmations.length - 1].k])));
  }, [matter]);
  const allAffirmed = Object.values(affirms).every(Boolean);
  const docs = [
    { name: d.formName, pages: 4, status: 'Drafted' },
    { name: `Exhibit Index (${ds.evidence.length} exhibits)`, pages: 2, status: 'Drafted' },
    { name: 'Damages Worksheet', pages: 1, status: 'Drafted' },
  ];
  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%' }}>
      <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase', marginBottom: 4 }}>Documents · Final review</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 12 }}>
        Read each document. Affirm what it says.
      </div>

      <div data-coach="documents-list" style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: 16 }}>
        <div>
          {docs.map((d, i) => (
            <div key={i} onClick={() => setOpened(i)} style={{
              padding: '12px 14px', borderRadius: 10, marginBottom: 8, cursor: 'pointer',
              background: opened === i ? (THEME.mode === 'dark' ? 'rgba(105,134,191,0.10)' : 'rgba(105,134,191,0.08)') : THEME.mode === 'dark' ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.55)',
              border: `1px solid ${opened === i ? THEME.blue : THEME.line}`,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <Icon d={Icons.doc} size={14} stroke={THEME.blue}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 600 }}>{d.name}</div>
                  <div style={{ fontSize: 10.5, color: THEME.textMute, marginTop: 2 }}>{d.pages} pages · {d.status}</div>
                </div>
                <Icon d={Icons.eye} size={13} stroke={THEME.textDim}/>
              </div>
            </div>
          ))}
        </div>

        {/* Mock document preview */}
        <div style={{ padding: 18, borderRadius: 12, background: THEME.mode === 'dark' ? '#23303D' : '#FBFAF5', border: `1px solid ${THEME.line}`, fontFamily: 'Fraunces', minHeight: 320 }}>
          <div style={{ textAlign: 'center', fontSize: 11, color: THEME.textMute, marginBottom: 8, letterSpacing: 1, textTransform: 'uppercase' }}>{d.courtHeader}</div>
          <div style={{ textAlign: 'center', fontSize: 16, color: THEME.text, fontWeight: 600, marginBottom: 12 }}>{d.formName}</div>
          <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr', gap: 6, fontSize: 12, color: THEME.text, lineHeight: 1.7 }}>
            <span style={{ color: THEME.textMute }}>Plaintiff:</span><span>{d.plaintiff}</span>
            <span style={{ color: THEME.textMute }}>Defendant:</span><span>{d.defendant}</span>
            <span style={{ color: THEME.textMute }}>Amount:</span><span>{d.amountLine}</span>
            <span style={{ color: THEME.textMute }}>Statute:</span><span>{d.statuteLine}</span>
          </div>
          <div style={{ marginTop: 12, padding: '10px 0', borderTop: `1px solid ${THEME.lineSoft}`, fontSize: 11.5, color: THEME.textDim, lineHeight: 1.6 }}>
            {d.bodyText}
          </div>
          <div style={{ marginTop: 12, fontSize: 9.5, color: THEME.textMute, textAlign: 'center', borderTop: `1px solid ${THEME.lineSoft}`, paddingTop: 8 }}>
            AllLaw Guide is not a law firm. This document was prepared by the user with platform tools.
          </div>
        </div>
      </div>

      <div style={{ marginTop: 18, padding: '16px 18px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1.5px solid ${THEME.warn + '40'}` }}>
        <div style={{ fontSize: 11, color: THEME.warnText, letterSpacing: 1.2, fontWeight: 700, textTransform: 'uppercase', marginBottom: 4 }}>Affirmation gate</div>
        <div style={{ fontFamily: 'Fraunces', fontSize: 17, color: THEME.text, fontWeight: 500, marginBottom: 10 }}>
          Affirm each claim before we generate the packet:
        </div>
        {d.affirmations.map(x => (
          <div key={x.k} onClick={() => setAffirms(a => ({ ...a, [x.k]: !a[x.k] }))} style={{
            display: 'flex', alignItems: 'flex-start', gap: 10, padding: '8px 0', cursor: 'pointer',
          }}>
            <div style={{
              width: 20, height: 20, borderRadius: 5, marginTop: 1, flexShrink: 0,
              border: `1.5px solid ${affirms[x.k] ? THEME.blue : THEME.lineStrong}`,
              background: affirms[x.k] ? THEME.blue : 'transparent',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>{affirms[x.k] && <Icon d={Icons.check} size={12} stroke="#fff" sw={3}/>}</div>
            <div style={{ flex: 1, fontSize: 12.5, color: THEME.text }}>{x.label}</div>
          </div>
        ))}

        <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{
            padding: '10px 18px', borderRadius: 8,
            background: allAffirmed ? THEME.blue : THEME.line,
            color: allAffirmed ? '#fff' : THEME.textMute,
            fontSize: 13, fontWeight: 600, cursor: allAffirmed ? 'pointer' : 'not-allowed',
          }}>
            I affirm these are my claims · Generate packet
          </div>
          {!allAffirmed && <span style={{ fontSize: 11, color: THEME.textDim }}>Tick all three to enable</span>}
        </div>
      </div>

      <div style={{ marginTop: 12 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Filing instructions */
const V3Filing = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const f = ds.filing;
  const [service, setService] = React.useState('');
  React.useEffect(() => { setService(''); }, [matter]);
  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%', maxWidth: 880 }}>
      <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase', marginBottom: 4 }}>Filing & service</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 12 }}>
        Where to file, what it costs, how to serve.
      </div>

      <div data-coach="filing-summary" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 18 }}>
        {[
          { l: 'Where to file', v: f.courtName, sub: f.courtAddress },
          { l: 'Filing fee', v: f.fee, sub: f.feeNote },
          { l: 'Estimated hearing', v: f.hearingTimeline, sub: f.hearingNote },
        ].map((s, i) => (
          <div key={i} style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
            <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600 }}>{s.l}</div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 18, color: THEME.text, fontWeight: 500, marginTop: 4 }}>{s.v}</div>
            <div style={{ fontSize: 11, color: THEME.textDim, marginTop: 4, lineHeight: 1.45 }}>{s.sub}</div>
          </div>
        ))}
      </div>

      <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>How will you serve the defendant?</div>
      <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 10, lineHeight: 1.55 }}>
        "Serve" means delivering a copy of your Statement of Claim to the other party so they know about the case. {f.serviceIntro}
      </div>
      {f.serviceOptions.map(opt => (
        <div key={opt.id} onClick={() => setService(opt.id)} style={{
          padding: '12px 14px', borderRadius: 10, marginBottom: 6, cursor: 'pointer',
          background: service === opt.id ? (THEME.mode === 'dark' ? 'rgba(105,134,191,0.10)' : 'rgba(105,134,191,0.08)') : THEME.mode === 'dark' ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.55)',
          border: `1px solid ${service === opt.id ? THEME.blue : THEME.line}`,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{
              width: 18, height: 18, borderRadius: 9, flexShrink: 0,
              border: `1.5px solid ${service === opt.id ? THEME.blue : THEME.lineStrong}`,
              background: service === opt.id ? THEME.blue : 'transparent',
            }}/>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <span style={{ fontSize: 13, color: THEME.text, fontWeight: 600 }}>{opt.label}</span>
                {opt.recommend && <span style={{ fontSize: 9.5, fontWeight: 700, padding: '1px 5px', borderRadius: 3, background: THEME.success + '22', color: THEME.success }}>RECOMMENDED FOR YOUR CASE</span>}
              </div>
              <div style={{ fontSize: 11.5, color: THEME.textDim, marginTop: 3, lineHeight: 1.4 }}>{opt.note}</div>
            </div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 15, color: THEME.text, fontWeight: 500 }}>{opt.cost}</div>
          </div>
        </div>
      ))}
    </div>
  );
};

/* Courtroom prep. Utah track (sandbox-authorized scripts) vs NY track (procedural framing only per UPL discipline) */
const V3Courtroom = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const isUtah = m.jurisdiction === 'utah';
  const [tab, setTab] = React.useState(isUtah ? 'opening' : 'expect');
  React.useEffect(() => { setTab(isUtah ? 'opening' : 'expect'); }, [matter]);
  return (
    <div style={{ padding: '24px 36px', overflow: 'auto', height: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6, flexWrap: 'wrap' }}>
        <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>Courtroom prep</span>
        <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 7px', borderRadius: 4, background: isUtah ? 'rgba(91,167,115,0.18)' : THEME.blueSoft, color: isUtah ? THEME.success : THEME.blue, letterSpacing: 0.4 }}>
          {isUtah ? 'SANDBOX-AUTHORIZED · Utah Office of Legal Innovation' : 'Reviewed by NY counsel-of-record'}
        </span>
      </div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 6 }}>
        {isUtah ? 'Walk into court ready.' : 'What to expect in court.'}
      </div>
      <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 14, maxWidth: 700, lineHeight: 1.55 }}>
        {isUtah
          ? 'Loose script for your hearing. The judge will run the show. You don\'t recite this verbatim. Use it as a backbone for what to cover.'
          : 'No script. NY UPL discipline doesn\'t allow case-specific advocacy coaching from us. What follows is courtroom etiquette and what judges typically ask in auto-repair / consumer-protection cases of this kind, generic-to-case-type.'}
      </div>

      <div style={{ display: 'flex', gap: 4, borderBottom: `1px solid ${THEME.line}`, marginBottom: 14 }}>
        {(isUtah ? [
          { v: 'opening', l: 'Opening' },
          { v: 'evidence', l: 'Evidence walk' },
          { v: 'closing', l: 'Closing' },
          { v: 'qa', l: 'Q&A prep · 12 questions' },
          { v: 'behavior', l: 'Behavioral coaching' },
        ] : [
          { v: 'expect', l: 'What to expect' },
          { v: 'qa-generic', l: 'Typical judge questions · 8' },
          { v: 'behavior', l: 'Behavioral coaching' },
        ]).map(t => {
          const a = tab === t.v;
          return (
            <div key={t.v} onClick={() => setTab(t.v)} style={{
              padding: '10px 14px', cursor: 'pointer', fontSize: 12.5, fontWeight: 600,
              color: a ? THEME.text : THEME.textDim,
              borderBottom: `2px solid ${a ? THEME.blue : 'transparent'}`, marginBottom: -1,
            }}>{t.l}</div>
          );
        })}
      </div>

      {tab === 'opening' && (
        <div data-coach="courtroom-opening">
          <V3Atticus topic="Opening statement script" jx={m.jurisdiction} body="Keep it under 60 seconds. Judge wants to know: who you are, what happened, what you want. Specific dates and the statute carry weight; outrage and adjectives don't."/>
          <div style={{ marginTop: 12, padding: 18, borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, fontFamily: 'Fraunces', fontSize: 14.5, color: THEME.text, lineHeight: 1.7 }}>
            "Your Honor, my name is Maria Vargas. I rented an apartment from Westhaven Properties for almost two years. When I moved out on March 15th, I gave proper notice, returned the keys, and left the unit clean. Utah law gives a landlord 30 days to either return my security deposit or send me a written explanation of why they're keeping any of it. It's now been more than 30 days. They've sent neither. I'm here under Utah Code 57-17-3, asking the court for the return of my $1,800 deposit, the statutory 2× damages the law allows, and the $60 filing fee. Thank you."
          </div>
        </div>
      )}
      {tab === 'qa' && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
          {[
            'Did you give written notice?', 'How did you return the keys?', 'Was there a move-out walkthrough?',
            'Have you tried to contact the landlord since you moved out?', 'Do you have the original deposit receipt?',
            'Did the lease address security-deposit handling?', 'Did the landlord ever inspect the unit while you lived there?',
            'Why are you asking for double damages?', 'What address did you give the landlord for the deposit return?',
            'Are there other tenants with the same complaint?', 'Did you cause any damage to the unit?',
            'Have you spoken with a lawyer?',
          ].map((q, i) => (
            <div key={i} style={{ padding: '12px 14px', borderRadius: 10, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
              <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>Q{i + 1}</div>
              <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 500 }}>{q}</div>
            </div>
          ))}
        </div>
      )}
      {tab === 'behavior' && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
          {[
            { l: 'Address the judge as "Your Honor"', t: 'Always. Even if they say "call me Bob." It signals respect for the process.' },
            { l: 'Stand when the judge enters and exits', t: 'Everyone in the room stands. Don\'t miss this.' },
            { l: 'Speak slowly. Pause between facts.', t: 'Court reporters and judges both need pacing. If you feel rushed, slow down on purpose.' },
            { l: 'When confused, say "May I clarify, Your Honor?"', t: 'Better than guessing. Judges respect litigants who admit uncertainty.' },
            { l: 'Don\'t interrupt the other side', t: 'You\'ll get your turn. Take notes; raise points when the judge asks for your response.' },
            { l: 'When you\'re done, stop talking', t: 'Resist the urge to keep adding. End on your strongest sentence.' },
          ].map((x, i) => (
            <div key={i} style={{ padding: '12px 14px', borderRadius: 10, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, borderLeft: `3px solid ${THEME.blue}` }}>
              <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 600, marginBottom: 3 }}>{x.l}</div>
              <div style={{ fontSize: 11.5, color: THEME.textDim, lineHeight: 1.5 }}>{x.t}</div>
            </div>
          ))}
        </div>
      )}
      {(tab === 'evidence' || tab === 'closing') && (
        <div style={{ padding: 18, borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, fontSize: 13, color: THEME.textDim, lineHeight: 1.6 }}>
          {tab === 'evidence' ? 'Walk the judge through your 7 exhibits in numbered order. State each one, what it shows, and which element it proves. Atticus has prepared per-exhibit narration. Tap any exhibit on the Evidence page to see it.' : 'Restate the legal claim, restate what the law allows, restate what you\'re asking for. Three sentences. Done.'}
        </div>
      )}
      {tab === 'expect' && (
        <div>
          <V3Atticus topic="What to expect (NY procedural framing)" jx={m.jurisdiction} body="Per NY UPL discipline, we don't write you a script. Instead, here's how NYC Civil Court small-claims hearings typically run. Generic-to-case-type, not specific-to-your-case. The judge will guide the order; you respond when asked."/>
          <div style={{ marginTop: 12, padding: 18, borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, fontSize: 13, color: THEME.text, lineHeight: 1.6 }}>
            <div style={{ fontFamily: 'Fraunces', fontSize: 16, color: THEME.text, fontWeight: 500, marginBottom: 8 }}>What happens in the courtroom</div>
            <div style={{ color: THEME.textDim, marginBottom: 10 }}>
              Most NYC Civil Court small-claims hearings run 15–20 minutes. The judge will have your packet (Statement of Claim + exhibits). Sessions are typically held in the evenings (5:30–9 PM) at the courthouse. Be prepared to wait for your case to be called.
            </div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 16, color: THEME.text, fontWeight: 500, marginBottom: 8, marginTop: 14 }}>Order of events (typical)</div>
            <ol style={{ margin: 0, paddingLeft: 18, color: THEME.textDim, fontSize: 12.5, lineHeight: 1.7 }}>
              <li>Court calls your case. You and the defendant come forward.</li>
              <li>Judge asks the plaintiff (you) to summarize the case briefly.</li>
              <li>Judge asks the defendant for their response.</li>
              <li>Judge asks clarifying questions of both sides.</li>
              <li>Judge may request to see specific exhibits.</li>
              <li>Judge announces decision then-and-there OR reserves decision for written ruling.</li>
            </ol>
            <div style={{ fontFamily: 'Fraunces', fontSize: 16, color: THEME.text, fontWeight: 500, marginBottom: 8, marginTop: 14 }}>How to present your evidence</div>
            <div style={{ color: THEME.textDim }}>
              Bring 3 paper copies of every exhibit (one for the judge, one for the defendant, one for you). When the judge asks about an exhibit, hand it to the bailiff or court officer who will pass it up. Don't try to walk it to the bench yourself.
            </div>
          </div>
        </div>
      )}
      {tab === 'qa-generic' && (
        <div>
          <V3Atticus topic="Typical judge questions" jx={m.jurisdiction} body="These are the questions a NYC Civil Court judge typically asks in auto-repair / consumer-protection small-claims cases. Generic-to-case-type. Not specific-to-your-facts. Use them as a thinking aid, not a script."/>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10, marginTop: 12 }}>
            {[
              'Did you receive a written estimate before any work was performed?',
              'What was the total amount on the original estimate?',
              'Did you authorize any additional work in writing?',
              'How did the shop communicate the additional work to you?',
              'When did you first see the final invoice?',
              'Have you tried to resolve this directly with the shop?',
              'Do you have evidence of the disputed work?',
              'What outcome are you seeking from the court?',
            ].map((q, i) => (
              <div key={i} style={{ padding: '12px 14px', borderRadius: 10, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
                <div style={{ fontSize: 10.5, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>Q{i + 1}</div>
                <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 500 }}>{q}</div>
              </div>
            ))}
          </div>
        </div>
      )}

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Lawyer pitch packet. Tier 2 (Daniel) */
const V3LawyerPitch = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const auditId = 'AL-7K3M9P2QX1FJ';
  return (
    <div style={{ padding: '24px 36px', overflow: 'auto', height: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
        <V3TierBadge tier={2}/>
        <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>Lawyer-pitch packet</span>
      </div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 4 }}>
        Your case is bigger than what we self-file.
      </div>
      <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 14, maxWidth: 720, lineHeight: 1.55 }}>
        Above the $15,000 small-claims cap and with a threatened counterclaim, this case needs a lawyer. We've packaged everything you've organized into a structured packet you can send to attorneys, with watermarks and an access log so you know who opened it.
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 16 }}>
        {/* Packet preview with watermark */}
        <div style={{ position: 'relative', padding: 22, borderRadius: 12, background: THEME.mode === 'dark' ? '#23303D' : '#FBFAF5', border: `1px solid ${THEME.line}`, overflow: 'hidden', minHeight: 380 }}>
          {/* Watermark */}
          <div style={{
            position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
            transform: 'rotate(-30deg)', pointerEvents: 'none',
            fontFamily: 'Fraunces', fontSize: 22, color: THEME.blue, opacity: 0.08, letterSpacing: 4, textAlign: 'center', lineHeight: 1.6,
          }}>
            Recipient: Hannah Reed, Esq.<br/>Audit ID: {auditId}<br/>Recipient: Hannah Reed, Esq.<br/>Audit ID: {auditId}
          </div>
          <div style={{ position: 'relative' }}>
            <div style={{ fontSize: 10, color: THEME.textMute, letterSpacing: 1.2, textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 }}>AllLaw Guide Lawyer-Pitch Packet · Watermarked</div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 20, color: THEME.text, fontWeight: 600, marginBottom: 4 }}>Romero v. Mountain View Homeowners Assoc.</div>
            <div style={{ fontSize: 11, color: THEME.textDim, marginBottom: 12 }}>Daniel Romero · Provo, UT · Independent contractor</div>

            <div style={{ padding: '8px 10px', borderRadius: 6, background: THEME.warn + '22', border: `1px solid ${THEME.warn}40`, fontSize: 11, color: THEME.text, marginBottom: 10 }}>
              <span style={{ fontWeight: 700, color: THEME.warnText }}>USER ASK:</span> Take the case (full representation). Contingency considered
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: '4px 12px', fontSize: 12, color: THEME.text, lineHeight: 1.6 }}>
              <span style={{ color: THEME.textMute }}>Amount:</span><span>$24,000 (kitchen remodel · Jan–Mar 2026)</span>
              <span style={{ color: THEME.textMute }}>Posture:</span><span>Plaintiff · contract claim · counterclaim threatened</span>
              <span style={{ color: THEME.textMute }}>Forum:</span><span>Utah District Court (above $15K SCC cap)</span>
              <span style={{ color: THEME.textMute }}>Statute:</span><span>Utah contract law · § 78A-5</span>
              <span style={{ color: THEME.textMute }}>Documents:</span><span>11-item structured bundle (signed contract, change orders, photo evidence, pay history, communications log, counterclaim threat)</span>
              <span style={{ color: THEME.textMute }}>Theory:</span><span>Breach of contract. Full performance, partial payment, no documented basis for withholding</span>
              <span style={{ color: THEME.textMute }}>Defenses noted:</span><span>HOA alleges fraudulent invoicing; user disputes. See Exhibit 7 (text logs)</span>
            </div>

            <div style={{ marginTop: 12, padding: '10px 12px', borderRadius: 6, background: THEME.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)', fontSize: 10.5, color: THEME.textMute, lineHeight: 1.55 }}>
              <div style={{ display: 'flex', gap: 6, alignItems: 'flex-start', marginBottom: 6 }}>
                <Icon d={Icons.alert} size={11} stroke={THEME.warn}/>
                <div>
                  <span style={{ color: THEME.text, fontWeight: 600 }}>Voice mode override:</span> Peer / Professional (locked).{' '}
                  <span style={{ fontStyle: 'italic' }}>
                    This packet is for attorney readers, not for you. Your in-app voice setting (Plain &amp; Kind 12th-grade) doesn't apply here. Attorneys need terms-of-art, statute citations, and procedural posture in shorthand. The packet stays in Peer / Professional regardless of your settings.
                  </span>
                </div>
              </div>
              <span style={{ color: THEME.text, fontWeight: 600 }}>Disclaimer:</span> AllLaw Guide is not a law firm. This packet was prepared by the user with platform tools. The user's case-specific decisions and affirmations are documented in the audit trail.
            </div>
          </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {/* Access log */}
          <div style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
            <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>Access log</div>
            <div style={{ fontSize: 12, color: THEME.text, marginBottom: 8 }}>Sent to <span style={{ fontWeight: 600 }}>3 attorneys</span> · <span style={{ fontWeight: 600 }}>5 views</span> · Last opened <span style={{ fontWeight: 600 }}>2 days ago</span></div>
            {[
              { who: 'Hannah Reed, Esq.', firm: 'Reed Law Group', last: '2d ago', views: 3 },
              { who: 'Marco Vélez, Esq.', firm: 'Vélez & Tran PLLC', last: '4d ago', views: 1 },
              { who: 'Aisha Okonkwo, Esq.', firm: 'Okonkwo Solo', last: '6d ago', views: 1 },
            ].map((r, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0', borderTop: i === 0 ? 'none' : `1px solid ${THEME.lineSoft}` }}>
                <Avatar initials={r.who.split(' ').map(p => p[0]).join('').slice(0, 2)} size={26}/>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 11.5, color: THEME.text, fontWeight: 600 }}>{r.who}</div>
                  <div style={{ fontSize: 10, color: THEME.textMute }}>{r.firm} · {r.views} view{r.views > 1 ? 's' : ''} · {r.last}</div>
                </div>
              </div>
            ))}
            <div style={{ marginTop: 6, fontSize: 11, color: THEME.blue, fontWeight: 600, cursor: 'pointer' }}>See full activity log →</div>
          </div>

          {/* User ask selector */}
          <div style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}` }}>
            <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>What are you asking the attorney for?</div>
            {['Review only', 'Advise', 'Take the case', 'Limited-scope'].map((x, i) => (
              <div key={i} style={{
                padding: '7px 10px', borderRadius: 7, marginBottom: 4,
                background: i === 2 ? THEME.blue + '22' : 'transparent',
                border: `1px solid ${i === 2 ? THEME.blue + '60' : THEME.line}`,
                fontSize: 12, color: i === 2 ? THEME.text : THEME.textDim, fontWeight: i === 2 ? 600 : 500,
                display: 'flex', alignItems: 'center', gap: 8,
              }}>
                {i === 2 && <Icon d={Icons.check} size={11} stroke={THEME.blue} sw={3}/>}
                {x}
              </div>
            ))}
          </div>
        </div>
      </div>

      <Avatar />
      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Decision log / audit trail with filter chips (Fix 3) */
const V3AuditLog = ({ matter }) => {
  const m = V3_MATTERS[matter];
  const ds = getMatterDataset(matter);
  const events = ds.auditEvents;

  const FILTERS = [
    { id: 'all', label: 'All' },
    { id: 'cause-of-action', label: 'Cause-of-action' },
    { id: 'evidence', label: 'Evidence' },
    { id: 'damages', label: 'Damages' },
    { id: 'filing', label: 'Filing' },
    { id: 'atticus', label: 'Atticus interactions' },
  ];

  const [filter, setFilter] = React.useState('all');
  const counts = FILTERS.reduce((acc, f) => {
    acc[f.id] = f.id === 'all' ? events.length : events.filter(e => e.kind === f.id).length;
    return acc;
  }, {});
  const visible = filter === 'all' ? events : events.filter(e => e.kind === filter);

  return (
    <div style={{ padding: '28px 36px', overflow: 'auto', height: '100%' }}>
      <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase', marginBottom: 4 }}>Decision log · Audit trail</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 26, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 6 }}>
        Your decision log · {events.length} decisions
      </div>
      <div style={{ fontSize: 12.5, color: THEME.textDim, marginBottom: 14, maxWidth: 760, lineHeight: 1.55 }}>
        Every legally-significant decision you've made on this case is recorded here. For each: what we suggested, what you chose, and the explanation we showed you at the time. <span style={{ color: THEME.text, fontWeight: 600 }}>Decisions are immutable</span>. If you change your mind, the system creates a new entry; old entries are never overwritten.
      </div>

      {/* Filter chips */}
      <div style={{ display: 'flex', gap: 8, marginBottom: 14, flexWrap: 'wrap' }}>
        {FILTERS.map(f => {
          const active = filter === f.id;
          return (
            <div key={f.id} onClick={() => setFilter(f.id)} style={{
              padding: '7px 12px', borderRadius: 999, cursor: 'pointer',
              background: active ? THEME.blue : 'transparent',
              color: active ? '#fff' : THEME.textDim,
              border: `1px solid ${active ? THEME.blue : THEME.line}`,
              fontSize: 11.5, fontWeight: 600,
              display: 'inline-flex', alignItems: 'center', gap: 6,
              transition: 'all 0.15s ease',
            }}>
              <span>{f.label}</span>
              <span style={{
                fontSize: 10, fontWeight: 700, padding: '0px 6px', borderRadius: 999,
                background: active ? 'rgba(255,255,255,0.22)' : THEME.line,
                color: active ? '#fff' : THEME.textMute,
                minWidth: 16, textAlign: 'center',
              }}>{counts[f.id]}</span>
            </div>
          );
        })}
      </div>

      <div style={{ background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, borderRadius: 12 }}>
        {visible.length === 0 && (
          <div style={{ padding: '32px 16px', textAlign: 'center', color: THEME.textMute, fontSize: 12 }}>
            No events in this category.
          </div>
        )}
        {visible.map((e, i) => (
          <div key={i} style={{ padding: '14px 16px', borderTop: i === 0 ? 'none' : `1px solid ${THEME.lineSoft}`, display: 'grid', gridTemplateColumns: '140px 1fr', gap: 14 }}>
            <div>
              <div style={{ fontSize: 11, color: THEME.textMute, fontWeight: 600 }}>{e.ts}</div>
              <div style={{ fontSize: 9.5, fontWeight: 700, padding: '1px 5px', borderRadius: 3, background: THEME.line, color: THEME.textDim, letterSpacing: 0.4, textTransform: 'uppercase', display: 'inline-block', marginTop: 4 }}>{e.kind}</div>
            </div>
            <div>
              <div style={{ fontSize: 13, color: THEME.text, fontWeight: 600, marginBottom: 4 }}>{e.label}</div>
              <div style={{ fontSize: 11.5, color: THEME.textDim, lineHeight: 1.5 }}>
                <span style={{ color: THEME.blue, fontWeight: 600 }}>System:</span> {e.sys}<br/>
                <span style={{ color: THEME.success, fontWeight: 600 }}>You:</span> {e.user}
              </div>
            </div>
          </div>
        ))}
      </div>

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

/* Crisis routing overlay */
const V3CrisisOverlay = ({ onClose }) => (
  <div style={{
    position: 'absolute', inset: 0, zIndex: 50,
    background: THEME.mode === 'dark' ? 'rgba(18,25,33,0.95)' : 'rgba(246,244,238,0.97)',
    backdropFilter: 'blur(18px)', WebkitBackdropFilter: 'blur(18px)',
    display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40,
  }}>
    <div style={{ maxWidth: 580, padding: '32px 36px', borderRadius: 18, background: THEME.mode === 'dark' ? '#23303D' : '#FFF', border: `1px solid ${THEME.line}`, boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}>
      <div style={{ fontSize: 11, color: THEME.warnText, letterSpacing: 1.4, fontWeight: 700, textTransform: 'uppercase', marginBottom: 6 }}>We need to pause</div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 24, color: THEME.text, fontWeight: 500, letterSpacing: -0.3, lineHeight: 1.2, marginBottom: 12 }}>
        What you've described sounds like it might involve safety. That's outside what AllLaw Guide can help with directly. But here's what can help right now.
      </div>
      <div style={{ fontSize: 13, color: THEME.textDim, lineHeight: 1.6, marginBottom: 16 }}>
        We noticed wording in your last entry that suggested possible domestic violence or immediate physical danger. Before we continue, please consider reaching out to one of the resources below. Your case state is saved. You can come back any time.
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
        {[
          { l: 'National DV Hotline', n: '1-800-799-SAFE (7233)', sub: '24/7 · confidential · multilingual' },
          { l: 'Crisis Text Line', n: 'Text HOME to 741741', sub: 'Text-based crisis support' },
          { l: 'Childhelp', n: '1-800-422-4453', sub: 'Child safety + custody emergencies' },
        ].map((x, i) => (
          <div key={i} style={{ padding: '12px 14px', borderRadius: 10, background: THEME.warn + '14', border: `1px solid ${THEME.warn}40` }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <Icon d={Icons.bell} size={16} stroke={THEME.warn}/>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, color: THEME.text, fontWeight: 600 }}>{x.l}</div>
                <div style={{ fontSize: 11, color: THEME.textDim, marginTop: 1 }}>{x.sub}</div>
              </div>
              <div style={{ fontFamily: 'Fraunces', fontSize: 15, color: THEME.warnText, fontWeight: 600 }}>{x.n}</div>
            </div>
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
        <div onClick={onClose} style={{ padding: '10px 16px', borderRadius: 8, background: 'transparent', color: THEME.text, border: `1px solid ${THEME.line}`, fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}>Continue intake anyway</div>
        <div onClick={onClose} style={{ padding: '10px 16px', borderRadius: 8, background: THEME.warn, color: '#fff', fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}>Pause and call this resource</div>
      </div>
    </div>
  </div>
);

/* ================================ WELCOME ================================ */

/* Demo welcome screen. First thing partners see when they enter the prototype.
   Hero with AllLaw lockup + tagline + theme toggle, three case-card buttons that
   launch each walkthrough, MVP / Phase 1 / Phase 2 roadmap chips. Renders inside
   the iPad frame; switches to a matter screen when a card is clicked. */
const V3Welcome = ({ onChooseMatter, onOpenTriage, mode, onModeChange }) => {
  const isDark = THEME.mode === 'dark';
  const cards = [
    {
      key: 'maria',
      eyebrow: 'WALKTHROUGH 1 · UTAH · TIER 1',
      title: 'Maria Vargas',
      sub: 'Security deposit recovery — Salt Lake County',
      body: 'Walk a Utah pro-se litigant from intake through the file-ready packet. Atticus pressure-tested. Sandbox-authorized courtroom prep included.',
      cta: 'Start Maria walkthrough',
      tone: THEME.blue,
      toneSoft: THEME.blueSoft,
      onClick: () => onChooseMatter && onChooseMatter('maria'),
    },
    {
      key: 'new',
      eyebrow: 'WALKTHROUGH 2 · ANY JURISDICTION',
      title: 'Start a new case',
      sub: 'Universal "what\'s going on?" intake demo',
      body: 'See how the platform routes a fresh case to Tier 1, 2, or 3 based on facts and posture, with crisis-routing precedence baked in.',
      cta: 'Open new-case intake',
      tone: THEME.highlight,
      toneSoft: 'rgba(242,232,87,0.18)',
      onClick: () => onOpenTriage && onOpenTriage(),
    },
    {
      key: 'daniel',
      eyebrow: 'WALKTHROUGH 3 · UTAH · TIER 2 HANDOFF',
      title: 'Daniel Romero',
      sub: 'Contract dispute above small-claims cap',
      body: 'Walk the scope-honest handoff: same intake structure as Tier 1, but routes to a lawyer-pitch packet instead of a self-file packet.',
      cta: 'Start Daniel walkthrough',
      tone: THEME.danger,
      toneSoft: THEME.dangerSoft,
      onClick: () => onChooseMatter && onChooseMatter('daniel'),
    },
  ];
  const roadmap = [
    { label: 'MVP', sub: 'Maria UT + Sarah NY end-to-end', state: 'active' },
    { label: 'Phase 1', sub: 'Sandbox launch + telemetry + sandbox quarterly reporting', state: 'planned' },
    { label: 'Phase 2', sub: 'E-filing, lawyer-scan automation, voice modes beyond Plain & Kind', state: 'planned' },
  ];
  return (
    <div style={{
      padding: '40px 48px 32px',
      overflow: 'auto', height: '100%',
      display: 'flex', flexDirection: 'column', gap: 36,
    }}>
      {/* Hero: lockup + theme toggle */}
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 24 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, flex: '1 1 auto', minWidth: 0 }}>
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 8, alignSelf: 'flex-start',
            padding: '5px 11px', borderRadius: 999, background: THEME.blueSoft,
            border: `1px solid ${THEME.blue}40`,
          }}>
            <div style={{ width: 6, height: 6, borderRadius: 3, background: THEME.blue }}/>
            <span style={{ fontSize: 10.5, color: THEME.blue, letterSpacing: 1.4, fontWeight: 700, textTransform: 'uppercase' }}>
              Partner demo prototype
            </span>
          </div>
          <div style={{
            fontFamily: 'Fraunces', fontSize: 42, color: THEME.text, fontWeight: 500,
            letterSpacing: -0.8, lineHeight: 1.05, marginTop: 4,
          }}>
            AllLaw Guide
          </div>
          <div style={{ fontSize: 16, color: THEME.textDim, lineHeight: 1.55, maxWidth: 620 }}>
            A walkthrough of how a pro-se litigant builds a case end-to-end, with Atticus enforcing UPL discipline at every step. Pick a story below to see the platform in action.
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
          <div onClick={() => onModeChange && onModeChange(isDark ? 'light' : 'dark')} style={{
            cursor: 'pointer', padding: '8px 12px', borderRadius: 8, fontSize: 12, fontWeight: 600,
            color: THEME.text, border: `1px solid ${THEME.line}`, background: THEME.glass,
            display: 'flex', alignItems: 'center', gap: 6,
          }}>
            <span>{isDark ? '☾' : '☀'}</span>
            <span>{isDark ? 'Dark' : 'Light'}</span>
          </div>
        </div>
      </div>

      {/* Three case cards */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
        {cards.map(c => (
          <div key={c.key} onClick={c.onClick} style={{
            cursor: 'pointer', position: 'relative',
            padding: '20px 22px', borderRadius: 16,
            background: isDark ? 'rgba(36,42,52,0.55)' : 'rgba(255,255,255,0.65)',
            border: `1px solid ${THEME.line}`,
            backdropFilter: 'blur(28px) saturate(1.4)', WebkitBackdropFilter: 'blur(28px) saturate(1.4)',
            display: 'flex', flexDirection: 'column', gap: 12,
            transition: 'transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease',
          }}
          onMouseEnter={(e) => { e.currentTarget.style.borderColor = c.tone; e.currentTarget.style.transform = 'translateY(-2px)'; e.currentTarget.style.boxShadow = `0 18px 38px -16px ${c.tone}55`; }}
          onMouseLeave={(e) => { e.currentTarget.style.borderColor = THEME.line; e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'none'; }}
          >
            <div style={{
              fontSize: 9.5, color: c.tone, letterSpacing: 1.4, fontWeight: 700,
              textTransform: 'uppercase',
            }}>{c.eyebrow}</div>
            <div style={{ fontFamily: 'Fraunces', fontSize: 22, color: THEME.text, fontWeight: 500, letterSpacing: -0.3, lineHeight: 1.15 }}>
              {c.title}
            </div>
            <div style={{ fontSize: 13, color: THEME.textDim, fontWeight: 500 }}>
              {c.sub}
            </div>
            <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.55, flex: '1 1 auto' }}>
              {c.body}
            </div>
            <div style={{
              marginTop: 6,
              display: 'inline-flex', alignItems: 'center', gap: 8, alignSelf: 'flex-start',
              padding: '7px 12px', borderRadius: 999,
              background: c.toneSoft, color: c.tone,
              fontSize: 11.5, fontWeight: 700, letterSpacing: 0.4,
              border: `1px solid ${c.tone}40`,
            }}>
              <span>{c.cta}</span>
              <span style={{ fontSize: 14, lineHeight: 1 }}>→</span>
            </div>
          </div>
        ))}
      </div>

      {/* Roadmap chips */}
      <div>
        <div style={{
          fontSize: 10.5, color: THEME.textMute, letterSpacing: 1.4, fontWeight: 700,
          textTransform: 'uppercase', marginBottom: 10,
        }}>Roadmap</div>
        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
          {roadmap.map(r => {
            const active = r.state === 'active';
            return (
              <div key={r.label} style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '10px 14px', borderRadius: 12,
                background: active ? THEME.blueSoft : (isDark ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.55)'),
                border: `1px ${active ? 'solid' : 'dashed'} ${active ? THEME.blue + '60' : THEME.line}`,
                flex: '1 1 240px',
              }}>
                <div style={{
                  width: 10, height: 10, borderRadius: 5,
                  background: active ? THEME.blue : THEME.textMute,
                  opacity: active ? 1 : 0.5,
                  flexShrink: 0,
                }}/>
                <div style={{ flex: '1 1 auto', minWidth: 0 }}>
                  <div style={{
                    fontSize: 11, color: active ? THEME.blue : THEME.textDim,
                    letterSpacing: 1.2, fontWeight: 700, textTransform: 'uppercase',
                  }}>{r.label}</div>
                  <div style={{ fontSize: 12.5, color: THEME.text, fontWeight: 500, marginTop: 2 }}>
                    {r.sub}
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

/* ================================ ROOT BODY ================================ */

/* Coming-soon stub for case-types in the case-selector that don't have full screens
   yet (wage / fraud / breach in V3_MATTERS with comingSoon: true).
   Communicates breadth without fake content. */
const V3ComingSoon = ({ matter }) => {
  const m = V3_MATTERS[matter];
  return (
    <div style={{ padding: '40px 36px', overflow: 'auto', height: '100%', maxWidth: 760 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
        <V3TierBadge tier={m.tier}/>
        <span style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1.2, fontWeight: 600, textTransform: 'uppercase' }}>{m.type}</span>
      </div>
      <div style={{ fontFamily: 'Fraunces', fontSize: 28, color: THEME.text, fontWeight: 500, letterSpacing: -0.4, marginBottom: 8 }}>
        {m.title}
      </div>
      <div style={{ fontSize: 13, color: THEME.textDim, lineHeight: 1.55, marginBottom: 18, maxWidth: 640 }}>
        {m.summary}
      </div>

      <div style={{ padding: '18px 20px', borderRadius: 14, background: THEME.warnSoft, border: `1px solid ${THEME.warn}40`, marginBottom: 18, display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <Icon d={Icons.alert} size={16} stroke={THEME.warn}/>
        <div>
          <div style={{ fontSize: 13.5, color: THEME.text, fontWeight: 600, marginBottom: 4 }}>This case-type ships in Phase 1</div>
          <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.6 }}>
            For the partner demo we've fleshed out two end-to-end Tier 1 flows: <span style={{ color: THEME.text, fontWeight: 600 }}>Maria Vargas</span> (UT security deposit) and <span style={{ color: THEME.text, fontWeight: 600 }}>Sarah Patel</span> (NY auto repair). The case-type roster appears here in full because it's the MVP build envelope. Lopez (UT wage), Tate (UT consumer fraud), and Kim (UT small breach) round out the five Tier 1 anchors. Their element frameworks + intake + Atticus content land in Phase 1 alongside sandbox authorization.
          </div>
        </div>
      </div>

      <div style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, marginBottom: 12 }}>
        <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>Statutory anchor</div>
        <div style={{ fontSize: 14, color: THEME.text, fontWeight: 500, marginBottom: 4 }}>{m.statute}</div>
        <div style={{ fontSize: 12, color: THEME.textDim }}>{m.statuteNote}</div>
      </div>

      <div style={{ padding: '14px 16px', borderRadius: 12, background: THEME.mode === 'dark' ? 'rgba(36,42,52,0.60)' : 'rgba(255,255,255,0.60)', backdropFilter: 'blur(28px) saturate(1.6)', WebkitBackdropFilter: 'blur(28px) saturate(1.6)', border: `1px solid ${THEME.line}`, marginBottom: 18 }}>
        <div style={{ fontSize: 11, color: THEME.textMute, letterSpacing: 1, textTransform: 'uppercase', fontWeight: 600, marginBottom: 8 }}>What's coming in Phase 1</div>
        <div style={{ fontSize: 12.5, color: THEME.textDim, lineHeight: 1.6 }}>
          • Element framework + factual intake authored by Atticus + UT sandbox counsel<br/>
          • Per-evidence Atticus reads and affirmation gates<br/>
          • Damages decomposition matching this statute's remedy framework<br/>
          • Document templates for {m.court}<br/>
          • Sandbox-authorized courtroom prep (UT cases) or NY procedural framing (NY cases)
        </div>
      </div>

      <div style={{ fontSize: 12, color: THEME.textMute, lineHeight: 1.6, padding: '12px 14px', borderRadius: 10, border: `1px dashed ${THEME.lineSoft}` }}>
        <span style={{ color: THEME.text, fontWeight: 600 }}>Try the demo:</span> switch the active case in the sidebar to <span style={{ color: THEME.blue, fontWeight: 600 }}>Vargas v. Westhaven</span> (UT Tier 1) or <span style={{ color: THEME.blue, fontWeight: 600 }}>Patel v. East Side Auto</span> (NY Tier 1) to walk an end-to-end pro-se flow.
      </div>

      <div style={{ marginTop: 14 }}><V3Disclosure jx={m.jurisdiction} compact/></div>
    </div>
  );
};

const V3Body = ({ matter, screen, onChooseMatter, onOpenTriage, mode, onModeChange }) => {
  if (screen === 'welcome') {
    return <V3Welcome onChooseMatter={onChooseMatter} onOpenTriage={onOpenTriage} mode={mode} onModeChange={onModeChange}/>;
  }
  const m = V3_MATTERS[matter];
  if (m.comingSoon) return <V3ComingSoon matter={matter}/>;
  if (m.tier === 2) {
    if (screen === 'pitch') return <V3LawyerPitch matter={matter}/>;
    if (screen === 'audit') return <V3AuditLog matter={matter}/>;
    if (screen === 'evidence') return <V3Evidence matter={matter}/>;
    return <V3Home matter={matter}/>;
  }
  switch (screen) {
    case 'theory': return <V3Theory matter={matter}/>;
    case 'evidence': return <V3Evidence matter={matter}/>;
    case 'damages': return <V3Damages matter={matter}/>;
    case 'documents': return <V3Documents matter={matter}/>;
    case 'filing': return <V3Filing matter={matter}/>;
    case 'courtroom': return <V3Courtroom matter={matter}/>;
    case 'audit': return <V3AuditLog matter={matter}/>;
    case 'readiness':
    case 'matter':
    case 'home':
    default: return <V3Home matter={matter}/>;
  }
};

window.V3_MATTERS = V3_MATTERS;
window.V3_NAV = V3_NAV;
window.V3Sidebar = V3Sidebar;
window.V3MatterSwitcher = V3MatterSwitcher;
window.V3Body = V3Body;
window.V3Welcome = V3Welcome;
window.V3CrisisOverlay = V3CrisisOverlay;
