diff --git a/BizDeedz/Offers/AI-Readiness-Accelerator-OnePager.md b/BizDeedz/Offers/AI-Readiness-Accelerator-OnePager.md
new file mode 100644
index 0000000..6eaf0b2
--- /dev/null
+++ b/BizDeedz/Offers/AI-Readiness-Accelerator-OnePager.md
@@ -0,0 +1,92 @@
+# AI Readiness Accelerator
+
+**Get your firm AI-ready in 14 days. No risk. No guesswork.**
+
+---
+
+## The Problem
+
+Law firms are being sold AI tools they can't actually use.
+
+Your intake data is inconsistent. Your templates live in 6 different places. Your staff workflows exist only in people's heads. And nobody has thought through what happens when AI gives bad advice to a client.
+
+Deploying AI on a broken foundation creates malpractice exposure, not efficiency.
+
+---
+
+## What You Get
+
+In 10-14 days, you receive a complete decision package:
+
+| Deliverable | What It Is |
+|-------------|------------|
+| **AI Readiness Scorecard** | Objective baseline across 6 dimensions: process maturity, data quality, knowledge systems, tooling, governance, and change capacity |
+| **Prioritized Roadmap** | 30/60/90-day action plan sequenced by impact and effort |
+| **Use-Case Portfolio** | 3-5 AI opportunities ranked by feasibility, ROI, and risk |
+| **Governance Starter Kit** | Acceptable use policy, access controls, escalation rules, and audit requirements |
+
+---
+
+## How It Works
+
+| Phase | Days | What Happens |
+|-------|------|--------------|
+| **Intake** | 0-2 | Secure data room setup, artifact collection, checklist completion |
+| **Assessment** | 3-6 | Process walkthroughs, data profiling, systems inventory, staff interviews |
+| **Analysis** | 7-9 | Use-case scoring, ROI modeling, risk mapping |
+| **Delivery** | 10-12 | Scorecard presentation, roadmap walkthrough, governance review |
+| **Decision** | 13-14 | Executive readout, next-step alignment |
+
+---
+
+## Best Fit
+
+**Ideal client:**
+- Bankruptcy, family law, estate planning, or high-volume civil practice
+- 5-50 attorneys with paralegal/support staff
+- Using practice management software (Clio, MyCase, PracticePanther, etc.)
+- Current pain: intake bottlenecks, drafting delays, missed deadlines, or data chaos
+
+**Not a fit:**
+- Solo practitioners without support staff
+- Firms with no existing practice management system
+- Firms under active litigation holds affecting data access
+
+---
+
+## Pricing
+
+| Package | Investment | Includes |
+|---------|------------|----------|
+| **AI Readiness Scorecard** | $4,500 | Full assessment + deliverables (Phases A-E) |
+| **Implementation Sprint** | $7,500/sprint | 2-week buildout sprints with defined scope |
+| **Optimization Retainer** | $2,500/month | Ongoing monitoring, KPI tracking, governance upkeep |
+
+*Fixed-fee. No hourly surprises. Payment: 50% to start, 50% on delivery.*
+
+---
+
+## Why BizDeedz
+
+We're not selling you AI tools. We're building the operational foundation that makes AI actually work.
+
+- **Operational-first**: We fix the plumbing before turning on the water
+- **Governance-built-in**: Compliance and ethics baked in from day one
+- **Measurable outcomes**: Cycle time, rework rates, and conversion tracked
+- **Law-firm specific**: Built for bankruptcy, family, estate, and civil workflows
+
+---
+
+## Next Step
+
+**Book a 30-minute diagnostic call.**
+
+We'll review your current tools, pain points, and goals. If there's a fit, you'll receive a scoped proposal within 48 hours.
+
+No obligation. No sales pressure. Just clarity.
+
+**[Schedule Call]** | hello@bizdeedz.com | (xxx) xxx-xxxx
+
+---
+
+*BizDeedz, Inc. | Operational Automation for Legal*
diff --git a/BizDeedz/Offers/Internal-Delivery-SOP.md b/BizDeedz/Offers/Internal-Delivery-SOP.md
new file mode 100644
index 0000000..4e00f3d
--- /dev/null
+++ b/BizDeedz/Offers/Internal-Delivery-SOP.md
@@ -0,0 +1,681 @@
+# AI Readiness Accelerator: Internal Delivery SOP
+
+**Version:** 1.0
+**Last Updated:** February 2026
+**Owner:** Delivery Lead
+
+---
+
+## Table of Contents
+
+1. [Engagement Checklist](#1-engagement-checklist)
+2. [Phase A: Demand Capture](#2-phase-a-demand-capture)
+3. [Phase B: Intake & Data Room](#3-phase-b-intake--data-room)
+4. [Phase C: Readiness Assessment](#4-phase-c-readiness-assessment)
+5. [Phase D: Use-Case Portfolio](#5-phase-d-use-case-portfolio)
+6. [Phase E: Roadmap & Governance](#6-phase-e-roadmap--governance)
+7. [Templates & Checklists](#7-templates--checklists)
+8. [Automation Runbook](#8-automation-runbook)
+
+---
+
+## 1. Engagement Checklist
+
+### Pre-Kickoff Checklist
+
+- [ ] Discovery call completed
+- [ ] Client qualifies (see fit criteria below)
+- [ ] SOW sent and signed
+- [ ] Initial payment received (50%)
+- [ ] Kickoff meeting scheduled
+- [ ] Client contact info captured in CRM
+- [ ] Pre-call questionnaire sent
+
+### Qualification Criteria (Must Have 4/5)
+
+| Criterion | Indicator |
+|-----------|-----------|
+| Practice area fit | Bankruptcy, family, estate, civil litigation |
+| Firm size | 5-50 attorneys |
+| Has practice management | Clio, MyCase, PracticePanther, etc. |
+| Has support staff | At least 1 paralegal or intake coordinator |
+| Clear pain point | Intake, drafting, deadlines, or data chaos |
+
+### Disqualification Triggers
+
+- Solo practitioner with no staff
+- No practice management system
+- Active litigation hold affecting data access
+- Budget below $4,000
+- No decision-maker access within 14 days
+
+---
+
+## 2. Phase A: Demand Capture
+
+### Objective
+Convert attention into qualified, booked diagnostic call.
+
+### Process Steps
+
+| Step | Action | Owner | Automation |
+|------|--------|-------|------------|
+| A1 | Lead captured (form, LinkedIn, referral) | Marketing | Auto-log to CRM |
+| A2 | Lead scored and routed | - | Auto-score based on form fields |
+| A3 | Booking link sent | - | Auto-email with Calendly link |
+| A4 | Confirmation + reminders | - | Auto-sequence (24hr, 1hr before) |
+| A5 | Pre-call questionnaire sent | - | Auto-send on booking confirmation |
+| A6 | Discovery call conducted | Delivery Lead | Manual |
+| A7 | Scope locked, SOW sent | Delivery Lead | Manual (template-based) |
+| A8 | Payment processed | - | Stripe/ACH |
+| A9 | Kickoff scheduled | - | Auto-calendar on payment |
+
+### Pre-Call Questionnaire Fields
+
+```
+1. Firm name
+2. Primary practice area(s)
+3. Number of attorneys
+4. Number of support staff (paralegals, intake, admin)
+5. Practice management software
+6. Document management system
+7. Biggest operational pain point (select top 2):
+ - Intake bottlenecks
+ - Drafting takes too long
+ - Missed deadlines
+ - Data chaos / can't find things
+ - Client communication gaps
+ - Rework / quality issues
+ - Other: ___
+8. Have you tried AI tools before? (Y/N)
+9. If yes, what happened?
+10. Who will be the decision-maker for this engagement?
+11. What does success look like in 90 days?
+```
+
+---
+
+## 3. Phase B: Intake & Data Room
+
+### Objective
+Collect all artifacts within 48 hours of kickoff.
+
+### Data Room Structure
+
+```
+[CLIENT NAME] - AI Readiness/
+├── 00-Admin/
+│ ├── SOW-Signed.pdf
+│ ├── Contacts.md
+│ └── Timeline.md
+├── 01-Org-and-Roles/
+│ ├── org-chart.pdf
+│ └── role-list.csv
+├── 02-Tool-Stack/
+│ ├── tools-inventory.csv
+│ └── screenshots/
+├── 03-Sample-Matters/
+│ └── [10-20 redacted matter files]
+├── 04-SOPs-Templates/
+│ ├── intake-scripts/
+│ ├── checklists/
+│ ├── pleadings-templates/
+│ └── email-templates/
+├── 05-KPI-Exports/
+│ ├── leads.csv
+│ ├── consults.csv
+│ ├── retainers.csv
+│ └── cycle-times.csv
+└── 06-Deliverables/
+ └── [Output files go here]
+```
+
+### Artifact Checklist (Send to Client)
+
+```markdown
+## Required Artifacts for AI Readiness Assessment
+
+Please upload the following to your secure data room by [DATE]:
+
+### Organization (Folder: 01-Org-and-Roles)
+- [ ] Org chart or team structure diagram
+- [ ] List of roles that touch intake, drafting, filing, and client comms
+
+### Technology (Folder: 02-Tool-Stack)
+- [ ] List of software tools used (practice mgmt, email, DMS, billing, e-sign)
+- [ ] Screenshots of main dashboards (optional but helpful)
+
+### Sample Work (Folder: 03-Sample-Matters)
+- [ ] 10-20 sample matters (redact client PII as needed)
+- [ ] Mix of: new intake, in-progress, and closed matters
+
+### Processes (Folder: 04-SOPs-Templates)
+- [ ] Intake scripts or call guides
+- [ ] Checklists used for case setup or filing
+- [ ] Pleading or document templates
+- [ ] Email templates for client communication
+
+### Metrics (Folder: 05-KPI-Exports)
+- [ ] Lead/intake volume (last 6-12 months)
+- [ ] Consultation-to-retainer conversion data
+- [ ] Filing cycle times (if tracked)
+- [ ] Any rework or rejection tracking
+
+---
+
+Questions? Reply to this email or message [CONTACT].
+
+Upload link: [DATA ROOM URL]
+```
+
+### Auto-Chase Sequence
+
+| Day | Action |
+|-----|--------|
+| Day 0 | Initial artifact request |
+| Day 1 | Status check ("anything blocking you?") |
+| Day 2 | Escalation ("we need X to stay on schedule") |
+| Day 3 | Call if incomplete |
+
+---
+
+## 4. Phase C: Readiness Assessment
+
+### Objective
+Score the firm across 6 pillars that predict AI success.
+
+### Assessment Interview Guide
+
+**Interview 1: Intake Coordinator (45-60 min)**
+
+```
+1. Walk me through what happens when a new lead calls or fills out a form.
+2. How do you determine if someone is a good fit?
+3. What information do you collect at first contact?
+4. Where does that information get recorded?
+5. How do you hand off to the attorney or paralegal?
+6. What are the most common reasons a lead doesn't become a client?
+7. What do you wish was easier or faster?
+```
+
+**Interview 2: Paralegal (60 min)**
+
+```
+1. Walk me through a typical new matter setup.
+2. Where do you get the client information from?
+3. What documents do you typically draft first?
+4. Where do templates live? How current are they?
+5. How do you track deadlines and filings?
+6. What causes the most rework or delays?
+7. How do you communicate status to clients?
+8. What tools do you use daily? What's frustrating about them?
+```
+
+**Interview 3: Attorney (45-60 min)**
+
+```
+1. What's your biggest bottleneck right now?
+2. How do you review paralegal work before filing?
+3. What types of errors do you catch most often?
+4. How confident are you in your current data/reporting?
+5. What's your appetite for AI? Concerns?
+6. Who would champion a new system? Who would resist?
+7. What does success look like in 6 months?
+```
+
+### Assessment Scoring Rubric
+
+**Score each pillar 0-5:**
+
+| Score | Definition |
+|-------|------------|
+| 0 | Non-existent |
+| 1 | Ad-hoc, inconsistent |
+| 2 | Defined but not followed |
+| 3 | Followed with exceptions |
+| 4 | Consistent, measured |
+| 5 | Optimized, continuously improving |
+
+#### Pillar 1: Process Maturity
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Intake flow | No standard process | Written SOP exists | SOP followed, measured |
+| Case setup | Different every time | Template exists | Automated checklist |
+| Drafting | Start from scratch | Templates used | Version-controlled library |
+| Filing | Manual, error-prone | Checklist exists | Tracked with SLAs |
+
+#### Pillar 2: Data Quality
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Field consistency | Free-text chaos | Some dropdowns | Enforced taxonomy |
+| Duplicate rate | >10% | 5-10% | <5% |
+| Null/missing | >20% critical fields | 10-20% | <10% |
+| Naming conventions | None | Informal | Enforced |
+
+#### Pillar 3: Knowledge System
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Template location | Scattered, unknown | Central folder | Versioned library |
+| SOPs | In people's heads | Written, outdated | Current, accessible |
+| FAQ/playbooks | None | Informal | Documented, searchable |
+
+#### Pillar 4: Tooling & Integrations
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Practice mgmt | Spreadsheets/paper | Basic PM tool | Mature PM with integrations |
+| Document mgmt | Local drives | Shared drive | DMS with version control |
+| Integrations | Copy-paste between | Some manual sync | API connections |
+
+#### Pillar 5: Risk & Governance
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Data access | No controls | Role-based folders | Permissioned DMS |
+| AI policy | None | Informal guidance | Written policy |
+| Audit trail | None | Some logging | Complete audit trail |
+| Retention | Ad-hoc | Written policy | Automated enforcement |
+
+#### Pillar 6: Change Capacity
+
+| Indicator | 0-1 | 2-3 | 4-5 |
+|-----------|-----|-----|-----|
+| Training history | No formal training | Occasional | Regular, documented |
+| Adoption track record | Changes fail | Mixed results | Changes stick |
+| Role clarity | Unclear ownership | Defined roles | Accountable owners |
+| Executive support | Skeptical | Interested | Active sponsor |
+
+### Data Profiling Checklist
+
+Run on each CSV export:
+
+```
+- [ ] Row count
+- [ ] Column inventory
+- [ ] Null rate per column
+- [ ] Duplicate detection (by key field)
+- [ ] Date format consistency
+- [ ] Outlier detection (numeric fields)
+- [ ] Taxonomy analysis (categorical fields)
+```
+
+### Assessment Output
+
+Internal workbook with:
+- Pillar scores (0-5)
+- Evidence notes for each score
+- Direct quotes from interviews
+- Data quality findings
+- Key risks identified
+
+---
+
+## 5. Phase D: Use-Case Portfolio
+
+### Objective
+Select 3-5 AI opportunities that are feasible AND worth it.
+
+### Use-Case Scoring Matrix
+
+Score each candidate 1-5:
+
+| Criterion | Weight | Definition |
+|-----------|--------|------------|
+| Volume | 25% | How often does this happen? |
+| Repetitiveness | 20% | Same process each time? |
+| Data availability | 20% | Is input data clean and accessible? |
+| Impact | 20% | Time saved, errors reduced, revenue captured |
+| Risk | 15% | What's the blast radius if AI fails? |
+
+**Scoring:**
+- Volume: 1=monthly, 3=weekly, 5=daily+
+- Repetitive: 1=unique each time, 5=identical process
+- Data: 1=unstructured chaos, 5=clean structured fields
+- Impact: 1=minor convenience, 5=major capacity unlock
+- Risk: 1=high (client-facing legal), 5=low (internal admin)
+
+### Standard Use-Case Library
+
+| Use Case | Typical Volume | Complexity | Risk |
+|----------|----------------|------------|------|
+| Intake triage + routing | High | Low | Medium |
+| Document classification + filing | High | Low | Low |
+| Deadline extraction + task creation | High | Medium | Medium |
+| Template-based drafting assist | Medium | Medium | High |
+| Client status updates | High | Low | Low |
+| Missing document requests | Medium | Low | Low |
+| Conflict check assistance | Medium | Medium | High |
+
+### Use-Case One-Pager Template
+
+```markdown
+## [USE CASE NAME]
+
+### Problem
+[What pain does this solve? Current state.]
+
+### Workflow
+1. Trigger: [What kicks this off?]
+2. Input: [What data/documents are needed?]
+3. Process: [What does the automation do?]
+4. Output: [What gets created/updated?]
+5. Human checkpoint: [Where does a person review?]
+
+### Controls
+- [ ] Confidence threshold for escalation
+- [ ] Human review before client-facing output
+- [ ] Audit logging
+- [ ] Exception handling
+
+### Success Metrics
+- Cycle time: [Before] → [After]
+- Error rate: [Before] → [After]
+- Volume capacity: [Before] → [After]
+
+### Effort Estimate
+- Implementation: [S/M/L]
+- Change management: [S/M/L]
+- Risk: [Low/Medium/High]
+
+### ROI Projection
+| Metric | Assumption | Value |
+|--------|------------|-------|
+| Hours saved/week | X tasks × Y min each | Z hours |
+| Hourly value | Staff cost or billable | $XX |
+| Annual value | (Hours × Rate × 50 weeks) | $XX,XXX |
+| Implementation cost | Sprint(s) required | $XX,XXX |
+| Payback period | Months | X months |
+```
+
+---
+
+## 6. Phase E: Roadmap & Governance
+
+### Objective
+Deliver board-ready plan with clear next steps.
+
+### Deliverables Checklist
+
+- [ ] AI Readiness Scorecard (PDF)
+- [ ] Risk Register (spreadsheet)
+- [ ] Prioritized Roadmap (PDF + task board)
+- [ ] Data Cleanup Plan (document)
+- [ ] Use-Case Portfolio (PDF + one-pagers)
+- [ ] Governance Starter Kit (document bundle)
+
+### Scorecard Template Structure
+
+```
+Page 1: Executive Summary
+- Overall readiness score (average of pillars)
+- Top 3 strengths
+- Top 3 gaps
+- Recommended first move
+
+Page 2: Pillar Scores
+- Spider chart visualization
+- Score breakdown by pillar
+- Benchmark comparison (if available)
+
+Pages 3-8: Pillar Details
+- Score + rationale
+- Key findings
+- Recommendations
+
+Page 9: Use-Case Portfolio Summary
+- Ranked list with effort/impact/risk
+- Recommended sequence
+
+Page 10: Roadmap Overview
+- 30/60/90 day milestones
+- Dependencies
+- Resource requirements
+
+Page 11: Governance Summary
+- Key policies needed
+- Risk controls
+- Next steps
+
+Page 12: Appendix
+- Data quality findings
+- Interview summary
+- Assumptions
+```
+
+### Risk Register Template
+
+| Risk ID | Category | Description | Likelihood | Impact | Controls | Owner | Status |
+|---------|----------|-------------|------------|--------|----------|-------|--------|
+| R001 | Data | Inconsistent matter taxonomy | High | Medium | Standardize fields | Ops | Open |
+| R002 | Compliance | No AI usage policy | High | High | Draft policy | Partner | Open |
+| R003 | Adoption | Staff resistance | Medium | High | Training plan | Mgr | Open |
+
+### Governance Starter Kit Contents
+
+**Document 1: AI Acceptable Use Policy (2-3 pages)**
+
+```markdown
+## Purpose
+This policy governs the use of AI tools at [FIRM NAME].
+
+## Scope
+Applies to all attorneys, paralegals, and staff.
+
+## Approved Uses
+- Document classification and filing
+- Deadline extraction and task creation
+- Drafting assistance using approved templates
+- Internal research and summarization
+- Client communication drafts (with human review)
+
+## Prohibited Uses
+- Legal advice to clients without attorney review
+- Submission of AI output without human verification
+- Use of client data in public AI tools (ChatGPT, etc.)
+- Creating final work product without attorney sign-off
+
+## Human Review Requirements
+- All client-facing output requires attorney review
+- All legal analysis requires attorney verification
+- All filings require human quality check
+
+## Data Protection
+- Client data may only be used with approved, secure AI tools
+- No client PII in prompts to public models
+- All AI interactions must be logged
+
+## Compliance
+Violations subject to disciplinary action.
+```
+
+**Document 2: Access & Permissions Model**
+
+```markdown
+## Role-Based Access
+
+| Role | Practice Mgmt | DMS | AI Tools | Client Data |
+|------|---------------|-----|----------|-------------|
+| Partner | Full | Full | Full | Full |
+| Associate | Full | Full | Full | Assigned |
+| Paralegal | Edit | Edit | Supervised | Assigned |
+| Intake | Create | Read | None | Limited |
+| Admin | Read | Read | None | None |
+```
+
+**Document 3: Human-in-the-Loop Rules**
+
+```markdown
+## Escalation Triggers
+
+AI output requires human review when:
+- Confidence score < 85%
+- New matter type not in training set
+- Client-facing communication
+- Filing or court submission
+- Any fee-related calculation
+- Conflict check results
+
+## Exception Handling
+
+When AI fails or flags uncertainty:
+1. Route to designated human reviewer
+2. Log exception with context
+3. Complete task manually
+4. Flag for model improvement review
+```
+
+---
+
+## 7. Templates & Checklists
+
+### Discovery Call Script
+
+```
+"Thanks for taking the time today. I'd like to understand your
+current situation, your goals around AI, and whether we're a
+good fit to help.
+
+1. Tell me about your firm—practice areas, size, structure.
+
+2. What's driving your interest in AI right now?
+
+3. What's your biggest operational pain point today?
+
+4. What tools are you currently using for practice management,
+ documents, and client communication?
+
+5. Have you tried any AI tools? What happened?
+
+6. If we could wave a magic wand, what would be different in
+ 90 days?
+
+7. Who else would be involved in a decision like this?
+
+8. What questions do you have for me?
+
+---
+
+[If qualified]
+
+Here's what I'm thinking: Our AI Readiness Accelerator is a
+10-14 day engagement where we assess your operations, identify
+the 3-5 AI opportunities that are actually worth pursuing, and
+give you a roadmap with the governance controls to do it safely.
+
+It's $4,500 fixed-fee. If you're interested, I can send over a
+scope of work today and we could kick off as early as [DATE].
+
+What questions do you have?"
+```
+
+### Executive Readout Agenda (90 min)
+
+```
+1. Welcome + context (5 min)
+2. Scorecard walkthrough (20 min)
+3. Risk register highlights (10 min)
+4. Use-case portfolio deep dive (20 min)
+5. Roadmap presentation (15 min)
+6. Governance overview (10 min)
+7. Discussion + Q&A (10 min)
+8. Next steps + close (5 min)
+```
+
+### Post-Engagement Handoff Checklist
+
+- [ ] All deliverables uploaded to client data room
+- [ ] Client execution board created (Notion/Planner)
+- [ ] Final invoice sent
+- [ ] Feedback request sent (NPS + testimonial ask)
+- [ ] CRM updated with outcome
+- [ ] Implementation proposal sent (if requested)
+- [ ] 30-day follow-up scheduled
+
+---
+
+## 8. Automation Runbook
+
+### What to Automate
+
+| Process | Tool | Trigger | Action |
+|---------|------|---------|--------|
+| Lead capture → CRM | Make/Zapier | Form submit | Create CRM record + tag |
+| Lead scoring | Make | CRM record created | Calculate score, route |
+| Booking confirmation | Calendly | Event booked | Send confirmation + pre-call form |
+| Reminder sequence | Make | Event -24h, -1h | Send reminder emails |
+| Data room creation | Make | SOW signed | Create folder structure in Drive |
+| Artifact checklist | Make | Data room created | Send checklist email + create tasks |
+| Chase sequence | Make | Day 1, 2, 3 | Send follow-up if incomplete |
+| Report generation | Google Docs | Assessment complete | Populate template from data |
+| Invoice creation | Stripe/QBO | Milestone reached | Generate and send invoice |
+| Follow-up scheduling | Make | Engagement complete | Create 30-day task |
+
+### Make.com Scenario Outlines
+
+**Scenario 1: Lead → CRM → Booking**
+
+```
+Trigger: Typeform/Webflow form submission
+→ Parse form fields
+→ Create Notion/Airtable record
+→ Calculate lead score (weighted fields)
+→ If score >= threshold:
+ → Send Calendly link email
+ → Tag as "Qualified"
+→ Else:
+ → Send "not a fit" email
+ → Tag as "Disqualified"
+```
+
+**Scenario 2: Booking → Pre-Call Prep**
+
+```
+Trigger: Calendly event created
+→ Create CRM deal/opportunity
+→ Send pre-call questionnaire (Typeform)
+→ Schedule reminder emails (-24h, -1h)
+→ Create prep task for delivery lead
+```
+
+**Scenario 3: Payment → Kickoff**
+
+```
+Trigger: Stripe payment received
+→ Update CRM status
+→ Create Google Drive folder structure
+→ Add client to shared Drive
+→ Send welcome email with data room link
+→ Send artifact checklist
+→ Create intake task with Day 2 deadline
+```
+
+**Scenario 4: Artifact Chase**
+
+```
+Trigger: Daily at 9am
+→ Check data room for required files
+→ For each client with incomplete room:
+ → If Day 1: Send "anything blocking you?"
+ → If Day 2: Send "we need X to stay on schedule"
+ → If Day 3: Create escalation task + Slack alert
+```
+
+---
+
+## Appendix: File Naming Conventions
+
+```
+[CLIENT]-[DOC TYPE]-[VERSION]-[DATE].ext
+
+Examples:
+- AcmeLaw-Scorecard-v1-2026-02-15.pdf
+- AcmeLaw-UseCase-IntakeRouting-v1.md
+- AcmeLaw-Roadmap-v2-2026-02-20.pdf
+```
+
+---
+
+*End of Internal Delivery SOP*
diff --git a/BizDeedz/Offers/SOW-Template.md b/BizDeedz/Offers/SOW-Template.md
new file mode 100644
index 0000000..5e7fd58
--- /dev/null
+++ b/BizDeedz/Offers/SOW-Template.md
@@ -0,0 +1,291 @@
+# Statement of Work
+## AI Readiness Accelerator
+
+**Client:** [FIRM NAME]
+**Prepared by:** BizDeedz, Inc.
+**Date:** [DATE]
+**SOW Version:** 1.0
+
+---
+
+## 1. Engagement Overview
+
+### 1.1 Purpose
+BizDeedz will conduct an AI Readiness Assessment for [FIRM NAME] to evaluate operational readiness for AI deployment, identify high-value automation opportunities, and deliver a prioritized implementation roadmap with governance controls.
+
+### 1.2 Engagement Type
+- [ ] AI Readiness Scorecard (Assessment Only)
+- [ ] AI Readiness Scorecard + Implementation Sprint 1
+- [ ] Full Engagement (Scorecard + Implementation + Retainer)
+
+### 1.3 Timeline
+| Milestone | Target Date |
+|-----------|-------------|
+| Kickoff | [DATE] |
+| Data Room Complete | [DATE + 2 days] |
+| Assessment Complete | [DATE + 6 days] |
+| Deliverables Presentation | [DATE + 12 days] |
+| Executive Readout | [DATE + 14 days] |
+
+---
+
+## 2. Scope of Work
+
+### 2.1 Phase A: Pre-Engagement (Pre-Kickoff)
+
+**BizDeedz Responsibilities:**
+- Conduct discovery call to confirm scope and fit
+- Deliver pre-call snapshot questionnaire
+- Validate engagement prerequisites
+
+**Client Responsibilities:**
+- Complete pre-call questionnaire
+- Confirm decision-maker availability for readout
+- Execute this SOW and submit initial payment
+
+**Deliverable:** Signed SOW, scheduled kickoff
+
+---
+
+### 2.2 Phase B: Intake & Data Room Setup (Days 0-2)
+
+**BizDeedz Responsibilities:**
+- Create secure client data room (Google Workspace)
+- Provide standardized artifact checklist
+- Send automated reminders for missing items
+- Confirm data room completion
+
+**Client Responsibilities:**
+- Provide access to designated contact
+- Submit required artifacts within 48 hours:
+ - [ ] Org chart / role list
+ - [ ] Tool stack inventory
+ - [ ] 10-20 sample matters (redacted as needed)
+ - [ ] SOPs and templates currently in use
+ - [ ] KPI exports (leads, consults, retainers, cycle times)
+
+**Deliverable:** Completed data room with all artifacts logged
+
+---
+
+### 2.3 Phase C: Readiness Assessment (Days 3-6)
+
+**BizDeedz Responsibilities:**
+- Conduct process walkthroughs with designated staff
+- Perform data quality profiling on submitted exports
+- Complete systems inventory and integration mapping
+- Score firm across 6 assessment pillars (0-5 scale):
+ 1. Process Maturity
+ 2. Data Quality
+ 3. Knowledge Systems
+ 4. Tooling & Integrations
+ 5. Risk & Governance
+ 6. Change Capacity
+
+**Client Responsibilities:**
+- Make available for interview (60-90 min each):
+ - [ ] 1 intake coordinator or receptionist
+ - [ ] 1-2 paralegals
+ - [ ] 1 attorney (preferably managing partner or practice lead)
+- Provide screen-share access to demonstrate current workflows
+
+**Deliverable:** Completed assessment workbook (internal)
+
+---
+
+### 2.4 Phase D: Use-Case Portfolio & ROI Model (Days 7-9)
+
+**BizDeedz Responsibilities:**
+- Identify candidate AI use cases from assessment findings
+- Score use cases by feasibility, impact, and risk
+- Build ROI model with assumptions documented
+- Draft use-case one-pagers for top 3-5 opportunities
+
+**Use-Case Selection Criteria:**
+- High volume + repetitive + rules-based
+- Clear success metrics (cycle time, rework, conversion)
+- Low ambiguity or defined escalation rules
+- Acceptable risk footprint with controls
+
+**Client Responsibilities:**
+- Validate volume and frequency assumptions
+- Confirm strategic priorities for use-case ranking
+
+**Deliverable:** Ranked use-case portfolio with ROI projections
+
+---
+
+### 2.5 Phase E: Roadmap & Governance Kit (Days 10-12)
+
+**BizDeedz Responsibilities:**
+- Compile AI Readiness Scorecard (final report)
+- Create Risk Register with controls and owners
+- Build Prioritized Roadmap (30/60/90 days)
+- Draft Data Cleanup Plan (taxonomy, naming, minimum viable data)
+- Assemble Governance Starter Kit:
+ - AI Acceptable Use Policy
+ - Access/Permissions Model
+ - Retention & Logging Requirements
+ - Human-in-the-Loop Rules
+
+**Client Responsibilities:**
+- Review draft materials prior to final presentation
+- Confirm attendees for executive readout
+
+**Deliverable:** Complete deliverables package (PDF + source files)
+
+---
+
+### 2.6 Phase F: Executive Readout (Days 13-14)
+
+**BizDeedz Responsibilities:**
+- Conduct 90-minute executive presentation
+- Walk through scorecard, roadmap, and governance kit
+- Facilitate discussion on next steps
+- Provide implementation proposal if requested
+
+**Client Responsibilities:**
+- Ensure decision-maker attendance
+- Come prepared with questions and priorities
+
+**Deliverable:** Recorded session (optional), decision on next phase
+
+---
+
+## 3. Out of Scope
+
+The following are explicitly excluded from this engagement:
+
+- Legal advice or ethics opinions
+- Implementation of AI tools or automations
+- Data migration or system configuration
+- Staff training or change management programs
+- Ongoing support beyond the engagement period
+- Custom software development
+- Compliance certifications or audits
+
+*Implementation services available as separate sprints.*
+
+---
+
+## 4. Client Responsibilities Summary
+
+| Responsibility | Due |
+|----------------|-----|
+| Execute SOW and submit payment | Before kickoff |
+| Complete pre-call questionnaire | Before kickoff |
+| Submit all data room artifacts | Day 2 |
+| Make staff available for interviews | Days 3-5 |
+| Review draft deliverables | Day 11 |
+| Attend executive readout | Day 13-14 |
+
+**Designated Client Contact:** [NAME, EMAIL, PHONE]
+
+---
+
+## 5. Assumptions & Dependencies
+
+1. Client will provide timely access to requested data and personnel
+2. Artifacts will be delivered in usable formats (PDF, CSV, DOCX)
+3. Sample matters will be appropriately redacted for confidentiality
+4. No material changes to firm operations during assessment period
+5. Decision-maker(s) will be available for final readout
+
+**Schedule Impact:** Delays in artifact submission or interview availability will extend timeline day-for-day.
+
+---
+
+## 6. Confidentiality & Data Handling
+
+- All client data will be stored in a dedicated, access-controlled Google Workspace
+- BizDeedz will not share client data with third parties
+- Sample matters and client information will be deleted within 30 days of engagement completion unless retention is requested
+- BizDeedz may retain anonymized, aggregated findings for benchmarking purposes
+
+---
+
+## 7. Pricing & Payment Terms
+
+### 7.1 Investment
+
+| Item | Fee |
+|------|-----|
+| AI Readiness Scorecard (Phases B-E) | $4,500 |
+| **Total** | **$4,500** |
+
+### 7.2 Payment Schedule
+
+| Milestone | Amount | Due |
+|-----------|--------|-----|
+| Engagement Start | $2,250 (50%) | Upon SOW execution |
+| Deliverables Complete | $2,250 (50%) | Upon executive readout |
+
+### 7.3 Payment Method
+- ACH transfer (preferred)
+- Credit card (+3% processing fee)
+
+### 7.4 Expenses
+No travel or expenses anticipated. If travel is requested, expenses will be billed at cost with prior approval.
+
+---
+
+## 8. Change Management
+
+Any changes to scope, timeline, or deliverables require written agreement from both parties. Changes may result in adjusted fees and timelines.
+
+---
+
+## 9. Limitation of Liability
+
+BizDeedz's liability under this SOW shall not exceed the total fees paid. BizDeedz is not liable for client's implementation decisions, AI tool performance, or outcomes resulting from client actions taken after the engagement.
+
+---
+
+## 10. Acceptance
+
+By signing below, both parties agree to the terms of this Statement of Work.
+
+**BizDeedz, Inc.**
+
+Signature: ________________________
+Name: ________________________
+Title: ________________________
+Date: ________________________
+
+**[CLIENT FIRM NAME]**
+
+Signature: ________________________
+Name: ________________________
+Title: ________________________
+Date: ________________________
+
+---
+
+## Appendix A: Implementation Sprint Scope (If Selected)
+
+*To be scoped separately based on assessment findings.*
+
+| Sprint | Duration | Typical Scope |
+|--------|----------|---------------|
+| Sprint 1 | 2 weeks | Data taxonomy, intake routing, document automation foundation |
+| Sprint 2 | 2 weeks | Drafting assist on templates, QC checkpoints |
+| Sprint 3 | 2 weeks | Dashboards, adoption system, training |
+
+**Sprint Pricing:** $7,500 per 2-week sprint (fixed scope)
+
+---
+
+## Appendix B: Optimization Retainer (If Selected)
+
+**Monthly Investment:** $2,500/month (minimum 3-month commitment)
+
+**Includes:**
+- Monthly KPI performance review
+- Automation health monitoring
+- Backlog grooming and sprint recommendations
+- Quarterly governance refresh
+- Slack/email support (SLA: 24-hour response)
+
+---
+
+*End of Statement of Work*
diff --git a/BizDeedz/Offers/templates/AI-Acceptable-Use-Policy.md b/BizDeedz/Offers/templates/AI-Acceptable-Use-Policy.md
new file mode 100644
index 0000000..41af44a
--- /dev/null
+++ b/BizDeedz/Offers/templates/AI-Acceptable-Use-Policy.md
@@ -0,0 +1,334 @@
+# AI Acceptable Use Policy
+
+**[FIRM NAME]**
+
+**Effective Date:** [DATE]
+**Version:** 1.0
+**Approved By:** [NAME, TITLE]
+
+---
+
+## 1. Purpose
+
+This policy establishes guidelines for the acceptable use of artificial intelligence (AI) tools and services at [FIRM NAME]. It ensures AI is used in ways that enhance our practice while protecting client confidentiality, maintaining professional responsibility, and managing risk.
+
+---
+
+## 2. Scope
+
+This policy applies to:
+- All attorneys, paralegals, and staff
+- All AI tools, including but not limited to:
+ - Generative AI (ChatGPT, Claude, Copilot, etc.)
+ - Practice-specific AI features in our software
+ - Automation tools with AI components
+ - Research and analytics tools using AI
+- All work performed on behalf of the firm, including remote work
+
+---
+
+## 3. Definitions
+
+**AI Tool:** Any software or service that uses machine learning, natural language processing, or similar technologies to generate, analyze, or process information.
+
+**Public AI Tool:** AI services accessible to the general public where input may be used to train models or stored by third parties (e.g., free-tier ChatGPT, Bard).
+
+**Enterprise AI Tool:** AI services with business agreements that include data protection, no training on inputs, and contractual safeguards.
+
+**Approved AI Tool:** An AI tool vetted and authorized by the firm for specific use cases.
+
+---
+
+## 4. Guiding Principles
+
+1. **Human Judgment First:** AI assists human decision-making; it does not replace professional judgment.
+
+2. **Client Protection:** Client confidentiality and privilege must never be compromised.
+
+3. **Accuracy Over Speed:** AI output must be verified before use; speed gains cannot come at the cost of correctness.
+
+4. **Transparency:** When relevant, clients should be informed about AI use in their matters.
+
+5. **Continuous Learning:** We will regularly review and update AI practices as technology and ethics evolve.
+
+---
+
+## 5. Approved Uses
+
+The following uses of AI are **approved** when using vetted, enterprise AI tools:
+
+### 5.1 Internal Productivity
+
+- Summarizing internal documents and research
+- Drafting internal communications
+- Generating first drafts of administrative content
+- Brainstorming and outlining
+
+### 5.2 Legal Work (with Human Review)
+
+| Activity | AI Role | Human Requirement |
+|----------|---------|-------------------|
+| Document classification | Suggest category | Paralegal confirms |
+| Template-based drafting | Generate first draft | Attorney reviews and edits |
+| Deadline extraction | Identify dates from documents | Paralegal verifies |
+| Research summarization | Summarize sources | Attorney validates |
+| Client communication drafts | Generate draft text | Attorney approves before send |
+
+### 5.3 Intake and Client Service
+
+- Summarizing intake form responses
+- Generating follow-up email drafts (human review required)
+- Suggesting matter routing based on criteria
+- Extracting key information from documents
+
+---
+
+## 6. Prohibited Uses
+
+The following uses are **strictly prohibited**:
+
+### 6.1 Public AI Tools with Client Data
+
+**Never** input the following into public AI tools:
+- Client names or identifying information
+- Case details or facts
+- Privileged communications
+- Confidential documents or their contents
+- Opposing party information
+- Financial or billing data
+
+### 6.2 Unsupervised Legal Work
+
+- Generating legal advice sent directly to clients without attorney review
+- Filing documents created by AI without human verification
+- Making strategic decisions based solely on AI recommendations
+- Using AI output as final work product
+
+### 6.3 Misrepresentation
+
+- Representing AI-generated content as original human work when disclosure is required
+- Using AI to create false or misleading information
+- Fabricating citations or authorities (AI "hallucinations")
+
+### 6.4 Circumventing Controls
+
+- Disabling logging or audit features
+- Using personal accounts for firm work
+- Sharing credentials for AI tools
+- Using unapproved AI tools for client matters
+
+---
+
+## 7. Client Data Protection
+
+### 7.1 Before Using AI with Client Information
+
+1. **Confirm the tool is approved** for use with client data
+2. **Verify data handling terms** (no training, encryption, deletion)
+3. **Consider necessity:** Is AI needed, or is manual processing appropriate?
+4. **Minimize data:** Use only the information required
+
+### 7.2 Data Handling Requirements
+
+| Data Type | Public AI | Enterprise AI (Approved) |
+|-----------|-----------|--------------------------|
+| Client PII | Never | With controls |
+| Case facts | Never | With controls |
+| Privileged content | Never | Carefully, with controls |
+| Public information | Permitted | Permitted |
+| Internal admin | Permitted | Permitted |
+
+### 7.3 Engagement Letters
+
+Consider including AI disclosure language in engagement letters:
+
+> "Our firm may use artificial intelligence tools to assist with administrative tasks, document processing, and legal research. All AI-assisted work is reviewed by qualified attorneys. Client information is only used with AI tools that meet our data protection standards and is never used to train AI models."
+
+---
+
+## 8. Human Review Requirements
+
+### 8.1 Mandatory Human Review
+
+The following **always** require human review before use or transmission:
+
+- Any communication sent to clients
+- Any document filed with a court or agency
+- Any legal advice or recommendations
+- Any document sent to opposing counsel
+- Any content containing legal citations
+- Any work product leaving the firm
+
+### 8.2 Review Standards
+
+When reviewing AI output, the reviewer must:
+
+1. **Verify accuracy:** Confirm facts, citations, and calculations
+2. **Check for hallucinations:** Validate that cited cases/statutes exist
+3. **Assess appropriateness:** Ensure tone and content fit the purpose
+4. **Apply judgment:** Consider what AI cannot—strategy, nuance, ethics
+5. **Take ownership:** The reviewer is responsible for the final product
+
+---
+
+## 9. Approved AI Tools
+
+### 9.1 Currently Approved
+
+| Tool | Approved Uses | Restrictions |
+|------|---------------|--------------|
+| [TOOL 1] | [Uses] | [Restrictions] |
+| [TOOL 2] | [Uses] | [Restrictions] |
+| [PM Software AI Features] | [Uses] | [Restrictions] |
+
+### 9.2 Requesting New Tools
+
+To request approval for a new AI tool:
+
+1. Submit request to [ROLE/EMAIL]
+2. Provide: vendor name, use case, data handling terms, cost
+3. IT/Compliance will evaluate security and privacy
+4. Managing partner approval required for client data use
+5. If approved, tool is added to this policy
+
+---
+
+## 10. Logging and Audit
+
+### 10.1 What Is Logged
+
+- Use of AI tools (who, when, which tool)
+- Queries involving client matters
+- Output saved to client files
+
+### 10.2 Audit Rights
+
+The firm reserves the right to:
+- Review AI usage logs
+- Audit compliance with this policy
+- Investigate suspected violations
+
+### 10.3 Retention
+
+AI interaction logs are retained for [X YEARS] consistent with our data retention policy.
+
+---
+
+## 11. Training and Competence
+
+### 11.1 Required Training
+
+All staff must complete AI usage training:
+- Initial training upon policy adoption
+- Refresher training annually
+- Update training when policy changes
+
+### 11.2 Training Content
+
+- This policy and its requirements
+- Approved vs. prohibited uses
+- Recognizing AI limitations and errors
+- Data protection practices
+- Reporting concerns
+
+---
+
+## 12. Reporting Concerns
+
+### 12.1 What to Report
+
+- Suspected policy violations
+- AI tool security concerns
+- Potential client data exposure
+- Observed AI errors or risks
+
+### 12.2 How to Report
+
+Report concerns to [ROLE/EMAIL]. Reports may be made anonymously.
+
+### 12.3 No Retaliation
+
+The firm prohibits retaliation against anyone who reports AI concerns in good faith.
+
+---
+
+## 13. Violations
+
+### 13.1 Consequences
+
+Violations of this policy may result in:
+- Additional training
+- Revocation of AI access
+- Disciplinary action up to termination
+- Professional responsibility referral (if applicable)
+
+### 13.2 Determining Severity
+
+| Factor | Lower Severity | Higher Severity |
+|--------|----------------|-----------------|
+| Intent | Unintentional error | Deliberate bypass |
+| Impact | No client data exposed | Client data compromised |
+| Pattern | First occurrence | Repeated violations |
+| Disclosure | Self-reported | Discovered by others |
+
+---
+
+## 14. Policy Maintenance
+
+### 14.1 Review Cycle
+
+This policy will be reviewed:
+- Quarterly for the first year
+- Annually thereafter
+- Upon significant AI developments or incidents
+
+### 14.2 Updates
+
+Policy updates require:
+- Review by [ROLE]
+- Approval by [ROLE]
+- Communication to all staff
+- Training on material changes
+
+---
+
+## 15. Acknowledgment
+
+I have read and understand the AI Acceptable Use Policy. I agree to comply with its requirements.
+
+**Name:** _________________________
+
+**Title:** _________________________
+
+**Signature:** _________________________
+
+**Date:** _________________________
+
+---
+
+## Appendix A: Quick Reference Card
+
+### Always OK
+- Summarizing your own notes
+- Drafting internal emails
+- Brainstorming ideas
+- Learning about legal concepts (general)
+
+### OK with Approved Tools + Human Review
+- Drafting from templates
+- Summarizing documents
+- Client communication drafts
+- Research assistance
+
+### Never OK
+- Client data in public AI
+- Sending AI output without review
+- Fabricated citations
+- Bypassing controls
+
+### When in Doubt
+Ask [ROLE] before proceeding.
+
+---
+
+*Policy prepared with assistance from BizDeedz, Inc.*
diff --git a/BizDeedz/Offers/templates/AI-Readiness-Scorecard-Template.md b/BizDeedz/Offers/templates/AI-Readiness-Scorecard-Template.md
new file mode 100644
index 0000000..28906b4
--- /dev/null
+++ b/BizDeedz/Offers/templates/AI-Readiness-Scorecard-Template.md
@@ -0,0 +1,388 @@
+# AI Readiness Scorecard
+
+**Client:** [FIRM NAME]
+**Assessment Date:** [DATE]
+**Prepared by:** BizDeedz, Inc.
+
+---
+
+## Executive Summary
+
+### Overall Readiness Score: [X.X] / 5.0
+
+**Readiness Level:** [Not Ready | Foundational | Developing | Ready | Optimized]
+
+| Level | Score Range | Meaning |
+|-------|-------------|---------|
+| Not Ready | 0.0 - 1.0 | Significant gaps; foundational work required before AI |
+| Foundational | 1.1 - 2.0 | Basic elements in place; substantial prep needed |
+| Developing | 2.1 - 3.0 | Processes defined; targeted improvements enable AI |
+| Ready | 3.1 - 4.0 | Strong foundation; ready for controlled AI deployment |
+| Optimized | 4.1 - 5.0 | Mature operations; positioned for advanced AI |
+
+### Top 3 Strengths
+
+1. **[STRENGTH 1]:** [One sentence description]
+2. **[STRENGTH 2]:** [One sentence description]
+3. **[STRENGTH 3]:** [One sentence description]
+
+### Top 3 Gaps
+
+1. **[GAP 1]:** [One sentence description + impact]
+2. **[GAP 2]:** [One sentence description + impact]
+3. **[GAP 3]:** [One sentence description + impact]
+
+### Recommended First Move
+
+[2-3 sentences on the single most impactful action to take in the next 30 days]
+
+---
+
+## Pillar Scores
+
+| Pillar | Score | Status |
+|--------|-------|--------|
+| 1. Process Maturity | [X] / 5 | [Status] |
+| 2. Data Quality | [X] / 5 | [Status] |
+| 3. Knowledge System | [X] / 5 | [Status] |
+| 4. Tooling & Integrations | [X] / 5 | [Status] |
+| 5. Risk & Governance | [X] / 5 | [Status] |
+| 6. Change Capacity | [X] / 5 | [Status] |
+
+*Status: Critical | Needs Work | Adequate | Strong | Excellent*
+
+### Visual Summary
+
+```
+Process Maturity [████░░░░░░] 4/5
+Data Quality [██░░░░░░░░] 2/5
+Knowledge System [███░░░░░░░] 3/5
+Tooling [████░░░░░░] 4/5
+Risk & Governance [█░░░░░░░░░] 1/5
+Change Capacity [███░░░░░░░] 3/5
+```
+
+---
+
+## Pillar 1: Process Maturity
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs describing the observed process maturity across intake, case setup, drafting, filing, and post-filing workflows. Include specific observations from interviews and artifact review.]
+
+### Key Findings
+
+| Area | Finding | Impact |
+|------|---------|--------|
+| Intake | [Observation] | [Impact on AI readiness] |
+| Case Setup | [Observation] | [Impact on AI readiness] |
+| Drafting | [Observation] | [Impact on AI readiness] |
+| Filing | [Observation] | [Impact on AI readiness] |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+3. [Specific recommendation with action]
+
+---
+
+## Pillar 2: Data Quality
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs on data quality findings: field consistency, duplicates, nulls, naming conventions, taxonomy.]
+
+### Data Profiling Results
+
+| Metric | Value | Benchmark | Status |
+|--------|-------|-----------|--------|
+| Duplicate Rate | [X]% | <5% | [Pass/Fail] |
+| Null Rate (Critical Fields) | [X]% | <10% | [Pass/Fail] |
+| Taxonomy Consistency | [X]% | >90% | [Pass/Fail] |
+| Naming Convention Compliance | [X]% | >80% | [Pass/Fail] |
+
+### Critical Field Analysis
+
+| Field | Completeness | Consistency | Issue |
+|-------|--------------|-------------|-------|
+| Client Name | [X]% | [X]% | [Issue if any] |
+| Matter Type | [X]% | [X]% | [Issue if any] |
+| Open Date | [X]% | [X]% | [Issue if any] |
+| Assigned Attorney | [X]% | [X]% | [Issue if any] |
+| Status | [X]% | [X]% | [Issue if any] |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+3. [Specific recommendation with action]
+
+---
+
+## Pillar 3: Knowledge System
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs on where institutional knowledge lives: templates, SOPs, playbooks, FAQs, training materials.]
+
+### Knowledge Inventory
+
+| Asset Type | Exists | Location | Currency | Accessibility |
+|------------|--------|----------|----------|---------------|
+| Document Templates | [Y/N] | [Where] | [Current/Outdated] | [Easy/Hard] |
+| Intake Scripts | [Y/N] | [Where] | [Current/Outdated] | [Easy/Hard] |
+| Process SOPs | [Y/N] | [Where] | [Current/Outdated] | [Easy/Hard] |
+| Filing Checklists | [Y/N] | [Where] | [Current/Outdated] | [Easy/Hard] |
+| Training Materials | [Y/N] | [Where] | [Current/Outdated] | [Easy/Hard] |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+
+---
+
+## Pillar 4: Tooling & Integrations
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs on current technology stack and integration status.]
+
+### Systems Inventory
+
+| System | Tool | Integration Status | Data Flow |
+|--------|------|-------------------|-----------|
+| Practice Management | [Tool] | [Native/API/Manual/None] | [Describe] |
+| Document Management | [Tool] | [Native/API/Manual/None] | [Describe] |
+| Email | [Tool] | [Native/API/Manual/None] | [Describe] |
+| Billing | [Tool] | [Native/API/Manual/None] | [Describe] |
+| E-Sign | [Tool] | [Native/API/Manual/None] | [Describe] |
+| Intake Forms | [Tool] | [Native/API/Manual/None] | [Describe] |
+
+### Integration Gaps
+
+| Gap | Current Workaround | Impact |
+|-----|-------------------|--------|
+| [Gap 1] | [Manual process] | [Time/errors] |
+| [Gap 2] | [Manual process] | [Time/errors] |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+
+---
+
+## Pillar 5: Risk & Governance
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs on current governance posture: policies, access controls, audit trails, compliance awareness.]
+
+### Governance Assessment
+
+| Control | Status | Gap |
+|---------|--------|-----|
+| Data access controls | [None/Basic/Role-based/Full] | [Gap if any] |
+| AI usage policy | [None/Informal/Written/Enforced] | [Gap if any] |
+| Audit trail | [None/Partial/Complete] | [Gap if any] |
+| Retention policy | [None/Written/Automated] | [Gap if any] |
+| Confidentiality training | [None/Onboarding/Ongoing] | [Gap if any] |
+
+### Risk Considerations for AI
+
+| Risk | Likelihood | Impact | Current Control |
+|------|------------|--------|-----------------|
+| AI output used without review | [H/M/L] | [H/M/L] | [Control or None] |
+| Client data in public AI | [H/M/L] | [H/M/L] | [Control or None] |
+| Incorrect legal advice | [H/M/L] | [H/M/L] | [Control or None] |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+3. [Specific recommendation with action]
+
+---
+
+## Pillar 6: Change Capacity
+
+**Score: [X] / 5**
+
+### Current State
+
+[2-3 paragraphs on firm's ability to adopt new tools and processes: history, training, role clarity, executive support.]
+
+### Change Readiness Indicators
+
+| Indicator | Assessment |
+|-----------|------------|
+| Recent change history | [Success/Mixed/Failed/None] |
+| Training infrastructure | [None/Ad-hoc/Formal] |
+| Role clarity | [Unclear/Defined/Accountable] |
+| Executive sponsorship | [Skeptical/Interested/Active] |
+| Staff appetite | [Resistant/Cautious/Eager] |
+
+### Key Stakeholders
+
+| Role | Name | Stance | Influence |
+|------|------|--------|-----------|
+| Decision Maker | [Name] | [Champion/Neutral/Skeptic] | High |
+| Operations Lead | [Name] | [Champion/Neutral/Skeptic] | Medium |
+| Key User | [Name] | [Champion/Neutral/Skeptic] | Medium |
+
+### Recommendations
+
+1. [Specific recommendation with action]
+2. [Specific recommendation with action]
+
+---
+
+## Use-Case Portfolio
+
+### Recommended AI Opportunities
+
+| Rank | Use Case | Feasibility | Impact | Risk | Timeline |
+|------|----------|-------------|--------|------|----------|
+| 1 | [Use case] | [H/M/L] | [H/M/L] | [H/M/L] | [30/60/90 days] |
+| 2 | [Use case] | [H/M/L] | [H/M/L] | [H/M/L] | [30/60/90 days] |
+| 3 | [Use case] | [H/M/L] | [H/M/L] | [H/M/L] | [30/60/90 days] |
+| 4 | [Use case] | [H/M/L] | [H/M/L] | [H/M/L] | [30/60/90 days] |
+| 5 | [Use case] | [H/M/L] | [H/M/L] | [H/M/L] | [30/60/90 days] |
+
+### Combined ROI Projection
+
+| Metric | Year 1 Estimate |
+|--------|-----------------|
+| Hours saved (annual) | [X] hours |
+| Value at staff rate | $[X] |
+| Value at billable rate | $[X] |
+| Implementation investment | $[X] |
+| Net value | $[X] |
+| Payback period | [X] months |
+
+*See Use-Case One-Pagers for detailed breakdown.*
+
+---
+
+## 30/60/90 Day Roadmap
+
+### Days 1-30: Foundation
+
+**Objective:** [One-sentence goal]
+
+| Task | Owner | Deliverable |
+|------|-------|-------------|
+| [Task 1] | [Role] | [Output] |
+| [Task 2] | [Role] | [Output] |
+| [Task 3] | [Role] | [Output] |
+
+**Success Criteria:** [How you know it's done]
+
+### Days 31-60: Build
+
+**Objective:** [One-sentence goal]
+
+| Task | Owner | Deliverable |
+|------|-------|-------------|
+| [Task 1] | [Role] | [Output] |
+| [Task 2] | [Role] | [Output] |
+| [Task 3] | [Role] | [Output] |
+
+**Success Criteria:** [How you know it's done]
+
+### Days 61-90: Activate
+
+**Objective:** [One-sentence goal]
+
+| Task | Owner | Deliverable |
+|------|-------|-------------|
+| [Task 1] | [Role] | [Output] |
+| [Task 2] | [Role] | [Output] |
+| [Task 3] | [Role] | [Output] |
+
+**Success Criteria:** [How you know it's done]
+
+---
+
+## Governance Starter Kit Summary
+
+### Included Documents
+
+1. **AI Acceptable Use Policy** - Defines approved and prohibited AI uses
+2. **Access & Permissions Model** - Role-based access controls
+3. **Human-in-the-Loop Rules** - Escalation triggers and exception handling
+4. **Retention & Logging Requirements** - Audit trail specifications
+
+### Implementation Priority
+
+| Document | Priority | Effort | Timeline |
+|----------|----------|--------|----------|
+| AI Acceptable Use Policy | Critical | Low | Week 1 |
+| Access & Permissions | High | Medium | Week 2-3 |
+| Human-in-the-Loop Rules | High | Low | Week 2 |
+| Retention & Logging | Medium | Medium | Week 3-4 |
+
+---
+
+## Next Steps
+
+### Option A: Self-Implementation
+
+Use this scorecard and the accompanying governance kit to drive your own AI readiness improvements. Recommended if you have internal operations capacity.
+
+### Option B: Implementation Sprint
+
+BizDeedz can execute the 30-day roadmap items in a 2-week sprint. Fixed scope, fixed fee.
+
+**Sprint 1 typically includes:**
+- Data taxonomy standardization
+- Intake routing automation
+- Document automation foundation
+- KPI dashboard setup
+
+**Investment:** $7,500
+
+### Option C: Full Engagement
+
+Scorecard + Implementation Sprints + Ongoing Optimization Retainer
+
+**Monthly retainer includes:**
+- KPI monitoring
+- Automation health checks
+- Backlog grooming
+- Quarterly governance refresh
+
+**Investment:** $2,500/month (3-month minimum)
+
+---
+
+## Appendix
+
+### A. Interview Summary
+[Key quotes and observations from interviews]
+
+### B. Data Quality Details
+[Full profiling output]
+
+### C. Systems Architecture Diagram
+[Current state diagram]
+
+### D. Assumptions & Limitations
+[What this assessment does and does not cover]
+
+---
+
+*Assessment conducted by BizDeedz, Inc.*
+*For questions: [CONTACT EMAIL]*
diff --git a/BizDeedz/Offers/templates/Pre-Call-Questionnaire.md b/BizDeedz/Offers/templates/Pre-Call-Questionnaire.md
new file mode 100644
index 0000000..6273ae4
--- /dev/null
+++ b/BizDeedz/Offers/templates/Pre-Call-Questionnaire.md
@@ -0,0 +1,120 @@
+# Pre-Call Snapshot Questionnaire
+
+*Please complete before our discovery call. Takes 5-10 minutes.*
+
+---
+
+## Your Firm
+
+**Firm Name:** _______________
+
+**Primary Practice Area(s):** (select all that apply)
+- [ ] Bankruptcy
+- [ ] Family Law
+- [ ] Estate Planning / Probate
+- [ ] Personal Injury
+- [ ] Civil Litigation
+- [ ] Criminal Defense
+- [ ] Immigration
+- [ ] Real Estate
+- [ ] Other: _______________
+
+**Number of Attorneys:** ___
+
+**Number of Support Staff:**
+- Paralegals: ___
+- Intake/Reception: ___
+- Administrative: ___
+
+---
+
+## Your Technology
+
+**Practice Management Software:**
+- [ ] Clio
+- [ ] MyCase
+- [ ] PracticePanther
+- [ ] Smokeball
+- [ ] Filevine
+- [ ] CosmoLex
+- [ ] None
+- [ ] Other: _______________
+
+**Document Management:**
+- [ ] NetDocuments
+- [ ] iManage
+- [ ] Google Drive
+- [ ] Dropbox
+- [ ] OneDrive/SharePoint
+- [ ] Local server
+- [ ] None
+- [ ] Other: _______________
+
+**Other Key Tools:** (e.g., e-sign, billing, intake forms)
+_______________________________________________
+
+---
+
+## Your Pain Points
+
+**Top 2 Operational Challenges:** (select exactly 2)
+- [ ] Intake takes too long or drops leads
+- [ ] Drafting is slow and inconsistent
+- [ ] Missed deadlines or filing errors
+- [ ] Can't find documents or data
+- [ ] Client communication gaps
+- [ ] Too much rework and corrections
+- [ ] Reporting/visibility is poor
+- [ ] Other: _______________
+
+**How would you describe your current operations?**
+- [ ] Chaotic—different every time
+- [ ] Informal—we have ways of doing things but nothing written
+- [ ] Defined—we have SOPs but don't always follow them
+- [ ] Consistent—we follow our processes most of the time
+- [ ] Optimized—we measure and continuously improve
+
+---
+
+## Your AI Experience
+
+**Have you tried AI tools before?**
+- [ ] No
+- [ ] Yes, briefly
+- [ ] Yes, ongoing use
+
+**If yes, what tools?** _______________
+
+**What happened?** (select all that apply)
+- [ ] Worked well, want to expand
+- [ ] Useful but limited
+- [ ] Didn't stick—adoption failed
+- [ ] Concerns about accuracy
+- [ ] Concerns about confidentiality
+- [ ] Not applicable
+
+---
+
+## Your Goals
+
+**What does success look like in 90 days?**
+_______________________________________________
+_______________________________________________
+
+**Who is the decision-maker for this engagement?**
+
+Name: _______________
+Title: _______________
+Email: _______________
+
+---
+
+## Anything Else?
+
+**What should we know before our call?**
+_______________________________________________
+_______________________________________________
+
+---
+
+*Thank you! We'll review this before our call to make the most of our time together.*
diff --git a/BizDeedz/Offers/templates/Use-Case-ROI-Calculator.md b/BizDeedz/Offers/templates/Use-Case-ROI-Calculator.md
new file mode 100644
index 0000000..d5d9b39
--- /dev/null
+++ b/BizDeedz/Offers/templates/Use-Case-ROI-Calculator.md
@@ -0,0 +1,243 @@
+# Use-Case ROI Calculator
+
+**Client:** [FIRM NAME]
+**Use Case:** [USE CASE NAME]
+**Date:** [DATE]
+
+---
+
+## Use Case Summary
+
+**Problem Statement:**
+[What pain does this solve? 2-3 sentences on current state.]
+
+**Proposed Solution:**
+[What will automation do? 2-3 sentences on future state.]
+
+---
+
+## Volume Analysis
+
+| Metric | Value | Source |
+|--------|-------|--------|
+| Occurrences per week | [X] | [How calculated] |
+| Occurrences per month | [X] | [Weekly × 4.3] |
+| Occurrences per year | [X] | [Monthly × 12] |
+
+---
+
+## Time Savings
+
+### Current State
+
+| Step | Time per Occurrence | Performed By |
+|------|---------------------|--------------|
+| [Step 1] | [X] min | [Role] |
+| [Step 2] | [X] min | [Role] |
+| [Step 3] | [X] min | [Role] |
+| [Step 4] | [X] min | [Role] |
+| **Total** | **[X] min** | |
+
+### Future State (With Automation)
+
+| Step | Time per Occurrence | Performed By |
+|------|---------------------|--------------|
+| [Step 1] | [X] min | [Auto/Role] |
+| [Step 2] | [X] min | [Auto/Role] |
+| [Step 3] | [X] min | [Auto/Role] |
+| [Step 4] | [X] min | [Auto/Role] |
+| **Total** | **[X] min** | |
+
+### Time Saved
+
+| Metric | Calculation | Value |
+|--------|-------------|-------|
+| Time saved per occurrence | [Current - Future] | [X] min |
+| Weekly time saved | [Per occurrence × Weekly volume] | [X] min |
+| Monthly time saved | [Weekly × 4.3] | [X] hours |
+| Annual time saved | [Monthly × 12] | [X] hours |
+
+---
+
+## Error Reduction
+
+### Current Error Profile
+
+| Error Type | Frequency | Rework Time | Impact |
+|------------|-----------|-------------|--------|
+| [Error 1] | [X]/month | [X] min each | [Describe] |
+| [Error 2] | [X]/month | [X] min each | [Describe] |
+| [Error 3] | [X]/month | [X] min each | [Describe] |
+
+### Projected Error Reduction
+
+| Error Type | Current | Projected | Reduction |
+|------------|---------|-----------|-----------|
+| [Error 1] | [X]/month | [X]/month | [X]% |
+| [Error 2] | [X]/month | [X]/month | [X]% |
+| [Error 3] | [X]/month | [X]/month | [X]% |
+
+### Rework Time Saved
+
+| Metric | Calculation | Value |
+|--------|-------------|-------|
+| Current monthly rework | [Errors × Rework time] | [X] hours |
+| Projected monthly rework | [Reduced errors × Rework time] | [X] hours |
+| Monthly rework saved | [Current - Projected] | [X] hours |
+| Annual rework saved | [Monthly × 12] | [X] hours |
+
+---
+
+## Financial Impact
+
+### Labor Cost Assumptions
+
+| Role | Hourly Cost | Billable Rate |
+|------|-------------|---------------|
+| Partner | $[X] | $[X] |
+| Associate | $[X] | $[X] |
+| Paralegal | $[X] | $[X] |
+| Admin/Intake | $[X] | N/A |
+
+*Hourly cost = fully loaded (salary + benefits + overhead)*
+
+### Direct Labor Savings
+
+| Metric | Hours | × Rate | Value |
+|--------|-------|--------|-------|
+| Annual time saved | [X] | $[X] | $[X] |
+| Annual rework saved | [X] | $[X] | $[X] |
+| **Total labor savings** | **[X]** | | **$[X]** |
+
+### Capacity Unlocked
+
+| Metric | Calculation | Value |
+|--------|-------------|-------|
+| Hours freed annually | [Total hours saved] | [X] hours |
+| If redirected to billable work | [Hours × Billable rate × Utilization %] | $[X] |
+| If used to handle more volume | [Hours ÷ Time per matter × Avg matter value] | $[X] |
+
+### Revenue Protection / Acceleration
+
+| Impact | Assumption | Annual Value |
+|--------|------------|--------------|
+| Faster intake → Higher conversion | [X]% improvement × [X] leads × $[X] avg | $[X] |
+| Fewer missed deadlines → Fewer write-offs | [X]% reduction × $[X] at-risk | $[X] |
+| Faster cycle time → Faster collection | [X] days faster × $[X] avg AR | $[X] |
+
+---
+
+## Implementation Cost
+
+### One-Time Costs
+
+| Item | Cost |
+|------|------|
+| BizDeedz implementation sprint | $[X] |
+| Client staff time (implementation) | [X] hours × $[X] = $[X] |
+| Tool/license costs (if any) | $[X] |
+| **Total one-time** | **$[X]** |
+
+### Ongoing Costs
+
+| Item | Monthly | Annual |
+|------|---------|--------|
+| Tool/license fees | $[X] | $[X] |
+| Maintenance/monitoring | $[X] | $[X] |
+| **Total ongoing** | **$[X]** | **$[X]** |
+
+---
+
+## ROI Summary
+
+### Conservative Estimate (Labor Savings Only)
+
+| Metric | Value |
+|--------|-------|
+| Annual benefit | $[X] |
+| Year 1 cost (one-time + ongoing) | $[X] |
+| Year 1 net value | $[X] |
+| Payback period | [X] months |
+| 3-year ROI | [X]% |
+
+### Moderate Estimate (Labor + Capacity)
+
+| Metric | Value |
+|--------|-------|
+| Annual benefit | $[X] |
+| Year 1 cost | $[X] |
+| Year 1 net value | $[X] |
+| Payback period | [X] months |
+| 3-year ROI | [X]% |
+
+### Optimistic Estimate (Labor + Capacity + Revenue)
+
+| Metric | Value |
+|--------|-------|
+| Annual benefit | $[X] |
+| Year 1 cost | $[X] |
+| Year 1 net value | $[X] |
+| Payback period | [X] months |
+| 3-year ROI | [X]% |
+
+---
+
+## Sensitivity Analysis
+
+### If volume is 25% lower than estimated:
+
+| Metric | Original | Adjusted |
+|--------|----------|----------|
+| Annual time saved | [X] hours | [X] hours |
+| Annual value | $[X] | $[X] |
+| Payback period | [X] months | [X] months |
+
+### If implementation takes 50% longer:
+
+| Metric | Original | Adjusted |
+|--------|----------|----------|
+| Implementation cost | $[X] | $[X] |
+| Year 1 net value | $[X] | $[X] |
+| Payback period | [X] months | [X] months |
+
+---
+
+## Risk Factors
+
+| Risk | Likelihood | Impact | Mitigation |
+|------|------------|--------|------------|
+| Lower volume than estimated | [H/M/L] | [H/M/L] | Validate with 3-month data |
+| Staff adoption issues | [H/M/L] | [H/M/L] | Training + change mgmt |
+| Technical integration issues | [H/M/L] | [H/M/L] | Prototype first |
+| Process exceptions require workarounds | [H/M/L] | [H/M/L] | Document exceptions upfront |
+
+---
+
+## Recommendation
+
+**Proceed?** [Yes / Yes with conditions / No]
+
+**Confidence Level:** [High / Medium / Low]
+
+**Rationale:**
+[2-3 sentences summarizing why this use case is or isn't worth pursuing]
+
+**Conditions (if any):**
+- [Condition 1]
+- [Condition 2]
+
+---
+
+## Assumptions Log
+
+| Assumption | Value | Source | Sensitivity |
+|------------|-------|--------|-------------|
+| Weekly volume | [X] | [Source] | [High/Med/Low] |
+| Time per occurrence | [X] min | [Source] | [High/Med/Low] |
+| Error rate | [X]% | [Source] | [High/Med/Low] |
+| Staff hourly cost | $[X] | [Source] | [Low] |
+| Billable rate | $[X] | [Source] | [Low] |
+
+---
+
+*Calculator prepared by BizDeedz, Inc.*
diff --git a/ai-readiness-accelerator/.env.example b/ai-readiness-accelerator/.env.example
new file mode 100644
index 0000000..6345af2
--- /dev/null
+++ b/ai-readiness-accelerator/.env.example
@@ -0,0 +1 @@
+VITE_GEMINI_API_KEY=your_gemini_api_key_here
diff --git a/ai-readiness-accelerator/.gitignore b/ai-readiness-accelerator/.gitignore
new file mode 100644
index 0000000..77bdac0
--- /dev/null
+++ b/ai-readiness-accelerator/.gitignore
@@ -0,0 +1,35 @@
+# Dependencies
+node_modules
+.pnp
+.pnp.js
+
+# Build
+dist
+dist-ssr
+*.local
+
+# Environment
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Editor
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.swp
+*.swo
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Testing
+coverage
diff --git a/ai-readiness-accelerator/index.html b/ai-readiness-accelerator/index.html
new file mode 100644
index 0000000..fa93dd3
--- /dev/null
+++ b/ai-readiness-accelerator/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ AI Readiness Accelerator | BizDeedz
+
+
+
+
+
+
+
+
+
diff --git a/ai-readiness-accelerator/package-lock.json b/ai-readiness-accelerator/package-lock.json
new file mode 100644
index 0000000..6c0323d
--- /dev/null
+++ b/ai-readiness-accelerator/package-lock.json
@@ -0,0 +1,4515 @@
+{
+ "name": "ai-readiness-accelerator",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ai-readiness-accelerator",
+ "version": "1.0.0",
+ "dependencies": {
+ "lucide-react": "^0.344.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "recharts": "^2.12.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.55",
+ "@types/react-dom": "^18.2.19",
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
+ "@typescript-eslint/parser": "^7.0.1",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.17",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "postcss": "^8.4.35",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5.3.3",
+ "vite": "^5.1.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz",
+ "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.24",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
+ "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001766",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001767",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz",
+ "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+ "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.344.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz",
+ "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/ai-readiness-accelerator/package.json b/ai-readiness-accelerator/package.json
new file mode 100644
index 0000000..785fdc6
--- /dev/null
+++ b/ai-readiness-accelerator/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "ai-readiness-accelerator",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
+ },
+ "dependencies": {
+ "lucide-react": "^0.344.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "recharts": "^2.12.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.55",
+ "@types/react-dom": "^18.2.19",
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
+ "@typescript-eslint/parser": "^7.0.1",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.17",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "postcss": "^8.4.35",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5.3.3",
+ "vite": "^5.1.0"
+ }
+}
diff --git a/ai-readiness-accelerator/postcss.config.js b/ai-readiness-accelerator/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/ai-readiness-accelerator/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/ai-readiness-accelerator/src/App.tsx b/ai-readiness-accelerator/src/App.tsx
new file mode 100644
index 0000000..3d19374
--- /dev/null
+++ b/ai-readiness-accelerator/src/App.tsx
@@ -0,0 +1,127 @@
+import { useState, useCallback } from 'react';
+import { Header, Footer } from './components/layout';
+import { ErrorBoundary } from './components/shared';
+import { LandingView, AssessmentView, ResultsView } from './views';
+import type { AssessmentResponses } from './types/assessment.types';
+import type { FirmProfile } from './types/firm.types';
+
+/**
+ * View state for the application
+ * - landing: Initial landing page with value proposition
+ * - assessment: Multi-step assessment questionnaire
+ * - results: Assessment results and recommendations
+ */
+type ViewState = 'landing' | 'assessment' | 'results';
+
+/**
+ * Data collected during the assessment process
+ */
+interface AssessmentData {
+ responses: AssessmentResponses;
+ firmProfile: FirmProfile;
+}
+
+/**
+ * Main application component for the AI Readiness Accelerator
+ * Manages view routing and assessment state
+ */
+function App() {
+ // Current view state
+ const [currentView, setCurrentView] = useState('landing');
+
+ // Assessment data collected during the assessment process
+ const [assessmentData, setAssessmentData] = useState(null);
+
+ /**
+ * Navigate to the assessment view
+ */
+ const handleStartAssessment = useCallback(() => {
+ setCurrentView('assessment');
+ }, []);
+
+ /**
+ * Handle assessment completion - save data and navigate to results
+ */
+ const handleAssessmentComplete = useCallback(
+ (data: { responses: AssessmentResponses; firmProfile: FirmProfile }) => {
+ setAssessmentData(data);
+ setCurrentView('results');
+ },
+ []
+ );
+
+ /**
+ * Reset application state and return to landing page
+ */
+ const handleReset = useCallback(() => {
+ setAssessmentData(null);
+ setCurrentView('landing');
+ }, []);
+
+ /**
+ * Navigate back from assessment to landing page
+ */
+ const handleBack = useCallback(() => {
+ setCurrentView('landing');
+ }, []);
+
+ /**
+ * Render the appropriate view based on current state
+ */
+ const renderView = () => {
+ switch (currentView) {
+ case 'landing':
+ return ;
+
+ case 'assessment':
+ return (
+
+ );
+
+ case 'results':
+ if (!assessmentData) {
+ // If somehow we're on results without data, go back to landing
+ setCurrentView('landing');
+ return null;
+ }
+ return (
+
+ );
+
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {/* Header - always visible, shows reset button when not on landing */}
+
+
+ {/* Main content area */}
+
+ {renderView()}
+
+
+ {/* Footer - always visible */}
+
+
+
+ );
+}
+
+export default App;
diff --git a/ai-readiness-accelerator/src/components/assessment/AssessmentContainer.tsx b/ai-readiness-accelerator/src/components/assessment/AssessmentContainer.tsx
new file mode 100644
index 0000000..49e4e5c
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/AssessmentContainer.tsx
@@ -0,0 +1,138 @@
+import React from 'react';
+import { Card } from '../ui';
+import { PILLAR_DEFINITIONS } from '../../constants/pillars';
+import { ProgressIndicator } from './ProgressIndicator';
+import { FirmProfileForm, type FirmProfileFormData } from './FirmProfileForm';
+import { PillarSection } from './PillarSection';
+import { AssessmentNavigation } from './AssessmentNavigation';
+import type { QuestionResponse } from './QuestionCard';
+import type { PillarId } from '../../types/assessment.types';
+
+/**
+ * Map of pillar IDs to responses organized by pillar
+ */
+export type AssessmentResponses = Record;
+
+export interface AssessmentContainerProps {
+ /** Current step index (0 = profile, 1-6 = pillars) */
+ currentStep: number;
+ /** Total number of steps (7: profile + 6 pillars) */
+ totalSteps: number;
+ /** Firm profile data */
+ firmProfile: FirmProfileFormData | null;
+ /** All assessment responses organized by pillar */
+ responses: AssessmentResponses;
+ /** Callback when firm profile changes */
+ onFirmProfileChange: (profile: FirmProfileFormData) => void;
+ /** Callback when a question response changes */
+ onResponseChange: (pillarId: PillarId, response: QuestionResponse) => void;
+ /** Callback to go to next step */
+ onNext: () => void;
+ /** Callback to go to previous step */
+ onPrev: () => void;
+ /** Callback to submit the assessment */
+ onSubmit: () => void;
+ /** Whether submission is in progress */
+ isSubmitting?: boolean;
+}
+
+/**
+ * AssessmentContainer is the main wrapper component for the assessment flow.
+ * It renders the progress indicator, form content (profile or pillar section),
+ * and navigation controls.
+ */
+export const AssessmentContainer: React.FC = ({
+ currentStep,
+ totalSteps,
+ firmProfile,
+ responses,
+ onFirmProfileChange,
+ onResponseChange,
+ onNext,
+ onPrev,
+ onSubmit,
+ isSubmitting = false,
+}) => {
+ // Get pillar names for progress indicator
+ const pillarNames = PILLAR_DEFINITIONS.map((p) => p.name);
+
+ // Determine which pillar to show (if not on profile step)
+ const currentPillarIndex = currentStep - 1; // Step 1 = pillar index 0
+ const currentPillar =
+ currentStep > 0 && currentStep <= PILLAR_DEFINITIONS.length
+ ? PILLAR_DEFINITIONS[currentPillarIndex]
+ : null;
+
+ // Get responses for current pillar
+ const currentPillarResponses =
+ currentPillar && currentPillar.id
+ ? responses[currentPillar.id as PillarId] || []
+ : [];
+
+ // Determine navigation state
+ const canGoPrev = currentStep > 0;
+
+ // For profile step: require name and at least one practice area
+ const isProfileValid =
+ firmProfile !== null &&
+ firmProfile.firmName.trim() !== '' &&
+ firmProfile.practiceAreas.length > 0;
+
+ // For pillar steps: allow navigation even if not all questions answered
+ // (they can always go back and answer more)
+ const canGoNext = currentStep === 0 ? isProfileValid : true;
+
+ const isLastStep = currentStep === totalSteps - 1;
+
+ // Handle response change for current pillar
+ const handleResponseChange = (response: QuestionResponse) => {
+ if (currentPillar && currentPillar.id) {
+ onResponseChange(currentPillar.id as PillarId, response);
+ }
+ };
+
+ return (
+
+ {/* Progress Indicator */}
+
+
+ {/* Main content area */}
+
+ {currentStep === 0 ? (
+
+ ) : currentPillar ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Navigation */}
+
+
+ );
+};
+
+export default AssessmentContainer;
diff --git a/ai-readiness-accelerator/src/components/assessment/AssessmentNavigation.tsx b/ai-readiness-accelerator/src/components/assessment/AssessmentNavigation.tsx
new file mode 100644
index 0000000..2bddd02
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/AssessmentNavigation.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { ChevronLeft, ChevronRight, Send, Save } from 'lucide-react';
+import { Button } from '../ui';
+
+export interface AssessmentNavigationProps {
+ /** Whether the Previous button should be enabled */
+ canGoPrev: boolean;
+ /** Whether the Next button should be enabled */
+ canGoNext: boolean;
+ /** Whether this is the last step (shows Submit instead of Next) */
+ isLastStep: boolean;
+ /** Callback for Previous button */
+ onPrev: () => void;
+ /** Callback for Next button */
+ onNext: () => void;
+ /** Callback for Submit button (only used on last step) */
+ onSubmit: () => void;
+ /** Whether submission is in progress */
+ isSubmitting?: boolean;
+}
+
+/**
+ * AssessmentNavigation provides navigation controls for the assessment flow.
+ * Shows Previous/Next buttons with Submit on the final step.
+ * Includes an auto-save indicator.
+ */
+export const AssessmentNavigation: React.FC = ({
+ canGoPrev,
+ canGoNext,
+ isLastStep,
+ onPrev,
+ onNext,
+ onSubmit,
+ isSubmitting = false,
+}) => {
+ return (
+
+ {/* Auto-save indicator */}
+
+
+ Progress auto-saved
+
+
+ {/* Navigation buttons */}
+
+ {/* Previous button */}
+
+
+ Previous
+
+
+ {/* Next or Submit button */}
+ {isLastStep ? (
+
+ Submit Assessment
+
+
+ ) : (
+
+ Next
+
+
+ )}
+
+
+ );
+};
+
+export default AssessmentNavigation;
diff --git a/ai-readiness-accelerator/src/components/assessment/FirmProfileForm.tsx b/ai-readiness-accelerator/src/components/assessment/FirmProfileForm.tsx
new file mode 100644
index 0000000..03ddaf2
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/FirmProfileForm.tsx
@@ -0,0 +1,280 @@
+import React, { useId } from 'react';
+import { Building2 } from 'lucide-react';
+import { Input, Select } from '../ui';
+import {
+ type PracticeArea,
+ type PracticeManagementTool,
+ type OperationsMaturity,
+ PRACTICE_AREA_LABELS,
+ PRACTICE_MANAGEMENT_LABELS,
+ OPERATIONS_MATURITY_INFO,
+} from '../../types/firm.types';
+
+/**
+ * Local type for form state that extends FirmProfile with required fields
+ */
+export interface FirmProfileFormData {
+ firmName: string;
+ practiceAreas: PracticeArea[];
+ attorneyCount: number;
+ paralegalCount: number;
+ practiceManagement: PracticeManagementTool;
+ currentOperationsMaturity: OperationsMaturity;
+}
+
+export interface FirmProfileFormProps {
+ /** Current profile data (null if not yet started) */
+ profile: FirmProfileFormData | null;
+ /** Callback when any field changes */
+ onChange: (profile: FirmProfileFormData) => void;
+}
+
+/**
+ * Default values for a new firm profile
+ */
+const DEFAULT_PROFILE: FirmProfileFormData = {
+ firmName: '',
+ practiceAreas: [],
+ attorneyCount: 1,
+ paralegalCount: 0,
+ practiceManagement: 'none',
+ currentOperationsMaturity: 'informal',
+};
+
+/**
+ * FirmProfileForm collects basic information about the law firm
+ * before beginning the assessment.
+ */
+export const FirmProfileForm: React.FC = ({
+ profile,
+ onChange,
+}) => {
+ const practiceAreasId = useId();
+ const maturityId = useId();
+
+ // Use profile or default values
+ const currentProfile = profile || DEFAULT_PROFILE;
+
+ // Validation state
+ const errors = {
+ firmName: !currentProfile.firmName.trim() ? 'Firm name is required' : undefined,
+ practiceAreas:
+ currentProfile.practiceAreas.length === 0
+ ? 'Select at least one practice area'
+ : undefined,
+ };
+
+ const handleFieldChange = (
+ field: K,
+ value: FirmProfileFormData[K]
+ ) => {
+ onChange({
+ ...currentProfile,
+ [field]: value,
+ });
+ };
+
+ const handlePracticeAreaToggle = (area: PracticeArea) => {
+ const currentAreas = currentProfile.practiceAreas;
+ const newAreas = currentAreas.includes(area)
+ ? currentAreas.filter((a) => a !== area)
+ : [...currentAreas, area];
+ handleFieldChange('practiceAreas', newAreas);
+ };
+
+ // Practice management options
+ const practiceManagementOptions = Object.entries(PRACTICE_MANAGEMENT_LABELS).map(
+ ([value, label]) => ({ value, label })
+ );
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
Firm Profile
+
+ Tell us about your firm so we can tailor the assessment and recommendations.
+
+
+
+
+
+ {/* Form fields */}
+
+ {/* Firm Name */}
+
handleFieldChange('firmName', e.target.value)}
+ error={errors.firmName}
+ placeholder="Enter your firm's name"
+ required
+ />
+
+ {/* Practice Areas - Multi-select checkboxes */}
+
+
+ Practice Areas
+
+ *
+
+
+
+ {(Object.entries(PRACTICE_AREA_LABELS) as [PracticeArea, string][]).map(
+ ([value, label]) => {
+ const isChecked = currentProfile.practiceAreas.includes(value);
+ return (
+
+ handlePracticeAreaToggle(value)}
+ className="h-4 w-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
+ />
+ {label}
+
+ );
+ }
+ )}
+
+ {errors.practiceAreas && (
+
+ {errors.practiceAreas}
+
+ )}
+
+
+ {/* Staff counts - side by side on desktop */}
+
+
+ handleFieldChange('attorneyCount', Math.max(1, parseInt(e.target.value) || 1))
+ }
+ helpText="Partners, associates, and of counsel"
+ />
+
+ handleFieldChange('paralegalCount', Math.max(0, parseInt(e.target.value) || 0))
+ }
+ helpText="Including legal assistants"
+ />
+
+
+ {/* Practice Management Tool */}
+
+ handleFieldChange('practiceManagement', e.target.value as PracticeManagementTool)
+ }
+ helpText="The primary software your firm uses to manage cases and clients"
+ />
+
+ {/* Operations Maturity - Radio buttons */}
+
+
+ Current Operations Maturity
+
+
+ How would you describe your firm's current operational processes?
+
+
+ {(
+ Object.entries(OPERATIONS_MATURITY_INFO) as [
+ OperationsMaturity,
+ { label: string; description: string }
+ ][]
+ ).map(([value, { label, description }]) => {
+ const isSelected = currentProfile.currentOperationsMaturity === value;
+ return (
+
+ handleFieldChange('currentOperationsMaturity', value)}
+ className="mt-0.5 h-4 w-4 text-primary-600 border-gray-300 focus:ring-primary-500"
+ />
+
+
+ {label}
+
+
+ {description}
+
+
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default FirmProfileForm;
diff --git a/ai-readiness-accelerator/src/components/assessment/LikertScale.tsx b/ai-readiness-accelerator/src/components/assessment/LikertScale.tsx
new file mode 100644
index 0000000..dbd9dd4
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/LikertScale.tsx
@@ -0,0 +1,171 @@
+import React, { useId } from 'react';
+
+/**
+ * Likert scale labels for the maturity assessment
+ * 0 = Non-existent, 5 = Optimized
+ */
+export const MATURITY_LABELS: Record = {
+ 0: 'Non-existent',
+ 1: 'Ad-hoc',
+ 2: 'Defined',
+ 3: 'Followed',
+ 4: 'Consistent',
+ 5: 'Optimized',
+};
+
+export interface LikertScaleProps {
+ /** Current selected value (0-5 or undefined if not answered) */
+ value: 0 | 1 | 2 | 3 | 4 | 5 | undefined;
+ /** Callback when value changes */
+ onChange: (value: 0 | 1 | 2 | 3 | 4 | 5) => void;
+ /** Optional custom labels (defaults to MATURITY_LABELS) */
+ labels?: Record;
+ /** Unique name for the radio group */
+ name?: string;
+ /** Whether the scale is disabled */
+ disabled?: boolean;
+}
+
+/**
+ * LikertScale component renders a 6-point scale (0-5) as radio buttons.
+ * Used for assessment questions to rate maturity levels.
+ * Fully accessible with proper radio group semantics.
+ */
+export const LikertScale: React.FC = ({
+ value,
+ onChange,
+ labels = MATURITY_LABELS,
+ name,
+ disabled = false,
+}) => {
+ const generatedId = useId();
+ const groupName = name || `likert-${generatedId}`;
+ const groupLabelId = `${groupName}-label`;
+
+ const scaleValues = [0, 1, 2, 3, 4, 5] as const;
+
+ return (
+
+ {/* Screen reader only label */}
+
+ Rate on a scale from 0 (Non-existent) to 5 (Optimized)
+
+
+ {/* Desktop layout - horizontal */}
+
+ {scaleValues.map((scaleValue) => {
+ const isSelected = value === scaleValue;
+ const labelText = labels[scaleValue] || String(scaleValue);
+ const inputId = `${groupName}-${scaleValue}`;
+
+ return (
+
+ onChange(scaleValue)}
+ disabled={disabled}
+ className="sr-only"
+ aria-describedby={`${inputId}-label`}
+ />
+
+ {scaleValue}
+
+
+ {labelText}
+
+
+ );
+ })}
+
+
+ {/* Mobile layout - stacked */}
+
+ {scaleValues.map((scaleValue) => {
+ const isSelected = value === scaleValue;
+ const labelText = labels[scaleValue] || String(scaleValue);
+ const inputId = `${groupName}-mobile-${scaleValue}`;
+
+ return (
+
+ onChange(scaleValue)}
+ disabled={disabled}
+ className="sr-only"
+ />
+
+ {scaleValue}
+
+
+ {labelText}
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default LikertScale;
diff --git a/ai-readiness-accelerator/src/components/assessment/PillarSection.tsx b/ai-readiness-accelerator/src/components/assessment/PillarSection.tsx
new file mode 100644
index 0000000..34ee3cd
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/PillarSection.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import {
+ Workflow,
+ Database,
+ BookOpen,
+ Settings,
+ Shield,
+ TrendingUp,
+ type LucideIcon,
+} from 'lucide-react';
+import type { ExtendedPillarDefinition } from '../../constants/pillars';
+import { QuestionCard, type QuestionResponse, type Question } from './QuestionCard';
+import { ProgressBar } from '../ui';
+
+/**
+ * Map of pillar icon names to Lucide components
+ */
+const ICON_MAP: Record = {
+ Workflow,
+ Database,
+ BookOpen,
+ Settings,
+ Shield,
+ TrendingUp,
+};
+
+export interface PillarSectionProps {
+ /** Pillar definition with questions */
+ pillar: ExtendedPillarDefinition;
+ /** Array of responses for this pillar's questions */
+ responses: QuestionResponse[];
+ /** Callback when a response changes */
+ onResponseChange: (response: QuestionResponse) => void;
+}
+
+/**
+ * PillarSection displays all questions for a single assessment pillar.
+ * Includes pillar header with icon, description, and progress indicator.
+ */
+export const PillarSection: React.FC = ({
+ pillar,
+ responses,
+ onResponseChange,
+}) => {
+ const questions = pillar.questions || [];
+ const answeredCount = responses.filter((r) => r.value !== undefined).length;
+ const totalCount = questions.length;
+ const progressPercent = totalCount > 0 ? (answeredCount / totalCount) * 100 : 0;
+
+ // Get the icon component
+ const iconName = (pillar as { icon?: string }).icon || 'Settings';
+ const IconComponent = ICON_MAP[iconName] || Settings;
+
+ // Find response for a specific question
+ const getResponseForQuestion = (questionId: string): QuestionResponse | undefined => {
+ return responses.find((r) => r.questionId === questionId);
+ };
+
+ return (
+
+ {/* Pillar header */}
+
+
+ {/* Icon */}
+
+
+
+
+ {/* Title and description */}
+
+
{pillar.name}
+ {pillar.description && (
+
{pillar.description}
+ )}
+
+
+
+ {/* Progress indicator */}
+
+
+
+ {/* Questions list */}
+
+ {questions.map((question: Question, index: number) => (
+
+
+
+ ))}
+
+
+ {/* Empty state */}
+ {questions.length === 0 && (
+
+
No questions available for this pillar.
+
+ )}
+
+ );
+};
+
+export default PillarSection;
diff --git a/ai-readiness-accelerator/src/components/assessment/ProgressIndicator.tsx b/ai-readiness-accelerator/src/components/assessment/ProgressIndicator.tsx
new file mode 100644
index 0000000..59b7547
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/ProgressIndicator.tsx
@@ -0,0 +1,167 @@
+import React from 'react';
+import { Check, Building2 } from 'lucide-react';
+
+export interface ProgressIndicatorProps {
+ /** Current step index (0 = profile, 1-6 = pillars) */
+ currentStep: number;
+ /** Total number of steps (typically 7: profile + 6 pillars) */
+ totalSteps: number;
+ /** Names of the pillars for steps 1-6 */
+ pillarNames: string[];
+}
+
+/**
+ * ProgressIndicator displays a visual step indicator for the assessment flow.
+ * Shows circles connected by lines, with completed steps having checkmarks.
+ * Responsive: labels hidden on mobile, shown on desktop.
+ */
+export const ProgressIndicator: React.FC = ({
+ currentStep,
+ totalSteps,
+ pillarNames,
+}) => {
+ // Generate step labels (Profile + pillar names)
+ const stepLabels = ['Profile', ...pillarNames];
+
+ // Determine step states
+ const getStepState = (stepIndex: number): 'completed' | 'current' | 'upcoming' => {
+ if (stepIndex < currentStep) return 'completed';
+ if (stepIndex === currentStep) return 'current';
+ return 'upcoming';
+ };
+
+ return (
+
+ {/* Current step name - mobile and desktop */}
+
+
+ Step {currentStep + 1} of {totalSteps}
+
+
+ {stepLabels[currentStep] || `Step ${currentStep + 1}`}
+
+
+
+ {/* Progress bar - mobile */}
+
+
+ {/* Step circles with connecting lines - desktop */}
+
+
+
+ {Array.from({ length: totalSteps }).map((_, index) => {
+ const state = getStepState(index);
+ const label = stepLabels[index] || `Step ${index + 1}`;
+ const isLast = index === totalSteps - 1;
+
+ return (
+
+ {/* Step circle and label container */}
+
+ {/* Circle */}
+
+ {state === 'completed' ? (
+
+ ) : index === 0 ? (
+
+ ) : (
+
+ {index}
+
+ )}
+
+
+ {/* Label - hidden on smaller screens, shown on larger */}
+
+ {label}
+
+
+
+ {/* Connecting line */}
+ {!isLast && (
+
+ )}
+
+ );
+ })}
+
+
+
+ {/* Current step name - medium screens where labels are hidden */}
+
+
+ {stepLabels[currentStep] || `Step ${currentStep + 1}`}
+
+
+
+
+ {/* Screen reader summary */}
+
+ Step {currentStep + 1} of {totalSteps}: {stepLabels[currentStep]}
+
+
+ );
+};
+
+export default ProgressIndicator;
diff --git a/ai-readiness-accelerator/src/components/assessment/QuestionCard.tsx b/ai-readiness-accelerator/src/components/assessment/QuestionCard.tsx
new file mode 100644
index 0000000..d3c3743
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/QuestionCard.tsx
@@ -0,0 +1,186 @@
+import React, { useState, useId } from 'react';
+import { HelpCircle, ChevronDown, ChevronUp, MessageSquare } from 'lucide-react';
+/**
+ * Question interface for the assessment
+ * Compatible with both types/index.ts and types/assessment.types.ts
+ */
+export interface Question {
+ id: string;
+ text: string;
+ helpText?: string;
+ category?: string;
+}
+import { LikertScale } from './LikertScale';
+
+export interface QuestionResponse {
+ questionId: string;
+ value: 0 | 1 | 2 | 3 | 4 | 5;
+ notes?: string;
+}
+
+export interface QuestionCardProps {
+ /** The question to display */
+ question: Question;
+ /** Current response (if any) */
+ response: QuestionResponse | undefined;
+ /** Callback when the response changes */
+ onChange: (response: QuestionResponse) => void;
+ /** Question number for display */
+ questionNumber?: number;
+}
+
+/**
+ * QuestionCard displays an individual assessment question with a Likert scale
+ * and an optional collapsible notes section.
+ */
+export const QuestionCard: React.FC = ({
+ question,
+ response,
+ onChange,
+ questionNumber,
+}) => {
+ const [showNotes, setShowNotes] = useState(Boolean(response?.notes));
+ const [showTooltip, setShowTooltip] = useState(false);
+ const tooltipId = useId();
+ const notesId = useId();
+
+ const handleValueChange = (value: 0 | 1 | 2 | 3 | 4 | 5) => {
+ onChange({
+ questionId: question.id,
+ value,
+ notes: response?.notes,
+ });
+ };
+
+ const handleNotesChange = (e: React.ChangeEvent) => {
+ if (response) {
+ onChange({
+ ...response,
+ notes: e.target.value || undefined,
+ });
+ } else {
+ // If no response yet but user types notes, don't create response
+ // Notes can only be saved with a value
+ }
+ };
+
+ const toggleNotes = () => {
+ setShowNotes(!showNotes);
+ };
+
+ return (
+
+ {/* Question header */}
+
+
+ {questionNumber && (
+
+ {questionNumber}
+
+ )}
+
+
+
+ {question.text}
+
+ {question.helpText && (
+
+
setShowTooltip(true)}
+ onMouseLeave={() => setShowTooltip(false)}
+ onFocus={() => setShowTooltip(true)}
+ onBlur={() => setShowTooltip(false)}
+ aria-label="Show help text"
+ >
+
+
+ {showTooltip && (
+
+ )}
+
+ )}
+
+ {question.category && (
+
+ {question.category}
+
+ )}
+
+
+
+
+ {/* Likert scale */}
+
+
+
+
+ {/* Notes toggle and textarea */}
+
+
+
+ Add notes (optional)
+ {showNotes ? (
+
+ ) : (
+
+ )}
+
+
+ {showNotes && (
+
+
+ Notes for question: {question.text}
+
+
+ {!response && (
+
+ Select a rating above to enable notes
+
+ )}
+
+ )}
+
+
+ );
+};
+
+export default QuestionCard;
diff --git a/ai-readiness-accelerator/src/components/assessment/index.ts b/ai-readiness-accelerator/src/components/assessment/index.ts
new file mode 100644
index 0000000..c01230d
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/assessment/index.ts
@@ -0,0 +1,53 @@
+/**
+ * Assessment form components for the AI Readiness Accelerator
+ *
+ * This module provides a complete set of components for conducting
+ * the 6-pillar AI readiness assessment for law firms.
+ */
+
+// Main container component
+export {
+ AssessmentContainer,
+ type AssessmentContainerProps,
+ type AssessmentResponses,
+} from './AssessmentContainer';
+
+// Progress indicator
+export {
+ ProgressIndicator,
+ type ProgressIndicatorProps,
+} from './ProgressIndicator';
+
+// Firm profile form
+export {
+ FirmProfileForm,
+ type FirmProfileFormProps,
+ type FirmProfileFormData,
+} from './FirmProfileForm';
+
+// Pillar section (displays questions for a single pillar)
+export {
+ PillarSection,
+ type PillarSectionProps,
+} from './PillarSection';
+
+// Individual question card
+export {
+ QuestionCard,
+ type QuestionCardProps,
+ type QuestionResponse,
+ type Question,
+} from './QuestionCard';
+
+// Likert scale rating component
+export {
+ LikertScale,
+ type LikertScaleProps,
+ MATURITY_LABELS,
+} from './LikertScale';
+
+// Navigation controls
+export {
+ AssessmentNavigation,
+ type AssessmentNavigationProps,
+} from './AssessmentNavigation';
diff --git a/ai-readiness-accelerator/src/components/landing/CTASection.tsx b/ai-readiness-accelerator/src/components/landing/CTASection.tsx
new file mode 100644
index 0000000..d0cfd90
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/landing/CTASection.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Button } from '../ui/Button';
+
+export interface CTASectionProps {
+ /** Callback when the Start Assessment button is clicked */
+ onStartAssessment: () => void;
+}
+
+/**
+ * Call-to-action section with dark background.
+ * Features headline, subtext listing practice areas, CTA button, and trust indicators.
+ */
+export const CTASection: React.FC = ({ onStartAssessment }) => {
+ return (
+
+
+
+ {/* Headline */}
+
+ Built for Law Firms
+
+
+ {/* Practice areas subtext */}
+
+ Bankruptcy, family law, estate planning, civil litigation
+
+
+ {/* CTA Button */}
+
+
+ Start Your Assessment
+
+
+
+ {/* Trust indicators */}
+
+ No signup • Free • 10 minutes
+
+
+
+
+ );
+};
+
+export default CTASection;
diff --git a/ai-readiness-accelerator/src/components/landing/Hero.tsx b/ai-readiness-accelerator/src/components/landing/Hero.tsx
new file mode 100644
index 0000000..e702173
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/landing/Hero.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Button } from '../ui/Button';
+
+export interface HeroProps {
+ /** Callback when the Start Assessment button is clicked */
+ onStartAssessment: () => void;
+}
+
+/**
+ * Hero section for the landing page.
+ * Features a gradient background, headline, subheadline, CTA button, and badge.
+ */
+export const Hero: React.FC = ({ onStartAssessment }) => {
+ return (
+
+ {/* Background decorative elements */}
+
+
+
+
+ {/* Main headline */}
+
+ Is Your Firm AI-Ready?
+
+
+ {/* Subheadline */}
+
+ Find out in 10 minutes. Get a personalized readiness scorecard and 90-day roadmap.
+
+
+ {/* CTA Button */}
+
+
+ Start Free Assessment
+
+
+
+ {/* Trust badge */}
+
+
+ Takes 10 minutes • No signup required
+
+
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/ai-readiness-accelerator/src/components/landing/PillarOverview.tsx b/ai-readiness-accelerator/src/components/landing/PillarOverview.tsx
new file mode 100644
index 0000000..7566724
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/landing/PillarOverview.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { GitBranch, Database, BookOpen, Wrench, Shield, Users } from 'lucide-react';
+
+interface PillarCard {
+ icon: React.ReactNode;
+ title: string;
+ description: string;
+}
+
+const pillars: PillarCard[] = [
+ {
+ icon: ,
+ title: 'Process Maturity',
+ description: 'Intake, drafting, filing workflows',
+ },
+ {
+ icon: ,
+ title: 'Data Quality',
+ description: 'Consistency, duplicates, taxonomy',
+ },
+ {
+ icon: ,
+ title: 'Knowledge System',
+ description: 'Templates, SOPs, playbooks',
+ },
+ {
+ icon: ,
+ title: 'Tooling',
+ description: 'Integrations, practice management',
+ },
+ {
+ icon: ,
+ title: 'Risk & Governance',
+ description: 'Policies, access, audit',
+ },
+ {
+ icon: ,
+ title: 'Change Capacity',
+ description: 'Adoption, training, accountability',
+ },
+];
+
+/**
+ * Pillar overview section showing the 6 dimensions assessed.
+ * Displays pillar cards in a responsive 2x3 grid layout.
+ */
+export const PillarOverview: React.FC = () => {
+ return (
+
+
+ {/* Section title */}
+
+
+ What We Assess
+
+
+
+ {/* Pillar cards grid - 2x3 on desktop, stack on mobile */}
+
+ {pillars.map((pillar, index) => (
+
+ {/* Icon */}
+
+ {pillar.icon}
+
+
+ {/* Content */}
+
+ {/* Title */}
+
+ {pillar.title}
+
+
+ {/* Description */}
+
+ {pillar.description}
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default PillarOverview;
diff --git a/ai-readiness-accelerator/src/components/landing/ValueProposition.tsx b/ai-readiness-accelerator/src/components/landing/ValueProposition.tsx
new file mode 100644
index 0000000..b161e8f
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/landing/ValueProposition.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { ClipboardCheck, Target, Map } from 'lucide-react';
+
+interface ValuePropCard {
+ icon: React.ReactNode;
+ title: string;
+ description: string;
+}
+
+const valueProps: ValuePropCard[] = [
+ {
+ icon: ,
+ title: 'AI Readiness Scorecard',
+ description: 'Objective baseline across 6 critical dimensions',
+ },
+ {
+ icon: ,
+ title: 'Use-Case Portfolio',
+ description: '3-5 AI opportunities ranked by feasibility and ROI',
+ },
+ {
+ icon: ,
+ title: '90-Day Roadmap',
+ description: 'Prioritized action plan with governance controls',
+ },
+];
+
+/**
+ * Value proposition section showcasing what users will receive from the assessment.
+ * Displays 3 value prop cards in a responsive grid layout.
+ */
+export const ValueProposition: React.FC = () => {
+ return (
+
+
+ {/* Section title */}
+
+
+ What You'll Get
+
+
+
+ {/* Value prop cards grid */}
+
+ {valueProps.map((prop, index) => (
+
+ {/* Icon */}
+
+ {prop.icon}
+
+
+ {/* Title */}
+
+ {prop.title}
+
+
+ {/* Description */}
+
+ {prop.description}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default ValueProposition;
diff --git a/ai-readiness-accelerator/src/components/landing/index.ts b/ai-readiness-accelerator/src/components/landing/index.ts
new file mode 100644
index 0000000..1d988a8
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/landing/index.ts
@@ -0,0 +1,9 @@
+export { Hero } from './Hero';
+export type { HeroProps } from './Hero';
+
+export { ValueProposition } from './ValueProposition';
+
+export { PillarOverview } from './PillarOverview';
+
+export { CTASection } from './CTASection';
+export type { CTASectionProps } from './CTASection';
diff --git a/ai-readiness-accelerator/src/components/layout/Container.tsx b/ai-readiness-accelerator/src/components/layout/Container.tsx
new file mode 100644
index 0000000..3b5a374
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/layout/Container.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+
+export interface ContainerProps {
+ /** Container content */
+ children: React.ReactNode;
+ /** Container max-width size */
+ size?: 'sm' | 'md' | 'lg' | 'xl';
+ /** Additional CSS classes */
+ className?: string;
+}
+
+const sizeClasses: Record, string> = {
+ sm: 'max-w-2xl',
+ md: 'max-w-4xl',
+ lg: 'max-w-6xl',
+ xl: 'max-w-7xl',
+};
+
+/**
+ * Centered container component with configurable max-width.
+ * Provides consistent horizontal padding and centers content.
+ */
+export const Container: React.FC = ({
+ children,
+ size = 'lg',
+ className = '',
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Container;
diff --git a/ai-readiness-accelerator/src/components/layout/Footer.tsx b/ai-readiness-accelerator/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..7e20efa
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/layout/Footer.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+/**
+ * Simple footer component with company branding.
+ * Displays BizDeedz company name and tagline in muted text.
+ */
+export const Footer: React.FC = () => {
+ return (
+
+ );
+};
+
+export default Footer;
diff --git a/ai-readiness-accelerator/src/components/layout/Header.tsx b/ai-readiness-accelerator/src/components/layout/Header.tsx
new file mode 100644
index 0000000..6bf97c1
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/layout/Header.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { BrainCircuit } from 'lucide-react';
+import { Button } from '../ui/Button';
+
+export interface HeaderProps {
+ /** Whether to show the reset/start over button */
+ showReset?: boolean;
+ /** Callback when reset button is clicked */
+ onReset?: () => void;
+}
+
+/**
+ * Fixed header component with branding and optional reset button.
+ * Features BrainCircuit icon, BizDeedz branding, and "AI Readiness" subtitle.
+ */
+export const Header: React.FC = ({ showReset = false, onReset }) => {
+ return (
+
+
+
+ {/* Left: Logo and branding */}
+
+
+
+ BizDeedz
+ |
+ AI Readiness
+
+
+
+ {/* Right: Reset button (conditional) */}
+ {showReset && onReset && (
+
+ Start Over
+
+ )}
+
+
+
+ );
+};
+
+export default Header;
diff --git a/ai-readiness-accelerator/src/components/layout/index.ts b/ai-readiness-accelerator/src/components/layout/index.ts
new file mode 100644
index 0000000..a61a449
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/layout/index.ts
@@ -0,0 +1,7 @@
+export { Header } from './Header';
+export type { HeaderProps } from './Header';
+
+export { Footer } from './Footer';
+
+export { Container } from './Container';
+export type { ContainerProps } from './Container';
diff --git a/ai-readiness-accelerator/src/components/results/BookCallCTA.tsx b/ai-readiness-accelerator/src/components/results/BookCallCTA.tsx
new file mode 100644
index 0000000..a1fe1c1
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/BookCallCTA.tsx
@@ -0,0 +1,104 @@
+/**
+ * BookCallCTA Component
+ *
+ * Call-to-action component for booking a diagnostic consultation call.
+ * Features a dark gradient background with prominent CTA button.
+ */
+
+import React, { memo } from 'react';
+import { Calendar, ArrowRight, Sparkles } from 'lucide-react';
+import { Button } from '../ui';
+
+export interface BookCallCTAProps {
+ /** Callback when the user clicks to book a call */
+ onBookCall: () => void;
+}
+
+/**
+ * BookCallCTA displays a prominent call-to-action for booking
+ * a diagnostic consultation. Features a dark gradient background,
+ * compelling headline, and clear CTA button.
+ */
+export const BookCallCTA: React.FC = memo(({ onBookCall }) => {
+ return (
+
+ {/* Background decorative elements */}
+
+
+ {/* Content */}
+
+ {/* Icon badge */}
+
+
+ Free Consultation
+
+
+ {/* Headline */}
+
+ Ready to execute this roadmap?
+
+
+ {/* Description */}
+
+ Schedule a free 30-minute diagnostic call with our AI implementation
+ specialists. We'll review your assessment results, discuss your
+ specific challenges, and help you prioritize your next steps.
+
+
+ {/* Benefits list */}
+
+ {[
+ 'Personalized roadmap review',
+ 'Tool recommendations based on your needs',
+ 'Implementation timeline planning',
+ 'Budget and resource guidance',
+ ].map((benefit, index) => (
+
+
+ {benefit}
+
+ ))}
+
+
+ {/* CTA Button */}
+
+
+ Book Diagnostic Call
+
+
+ {/* Reassurance text */}
+
+ No commitment required. We'll send you a calendar invite after you submit.
+
+
+
+ );
+});
+
+BookCallCTA.displayName = 'BookCallCTA';
+
+export default BookCallCTA;
diff --git a/ai-readiness-accelerator/src/components/results/ExecutiveSummary.tsx b/ai-readiness-accelerator/src/components/results/ExecutiveSummary.tsx
new file mode 100644
index 0000000..9f83163
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/ExecutiveSummary.tsx
@@ -0,0 +1,283 @@
+/**
+ * ExecutiveSummary Component
+ *
+ * Displays the AI-generated executive summary with loading, error,
+ * and success states. Includes a regenerate option.
+ */
+
+import React, { memo } from 'react';
+import {
+ BrainCircuit,
+ AlertCircle,
+ RefreshCw,
+ CheckCircle,
+ Lightbulb,
+ AlertTriangle,
+ ArrowRight,
+} from 'lucide-react';
+import type { ExecutiveSummary as ExecutiveSummaryType } from '../../types/results.types';
+import { Card, Button, LoadingSpinner } from '../ui';
+
+export interface ExecutiveSummaryProps {
+ /** The executive summary data (null if not yet loaded) */
+ summary: ExecutiveSummaryType | null;
+ /** Whether the summary is currently being generated */
+ loading: boolean;
+ /** Error message if generation failed */
+ error: string | null;
+ /** Callback to regenerate the summary */
+ onRegenerate: () => void;
+}
+
+/**
+ * Section header component for consistent styling
+ */
+const SectionHeader: React.FC<{
+ icon: React.ReactNode;
+ title: string;
+ iconColor?: string;
+}> = memo(({ icon, title, iconColor = 'text-indigo-600' }) => (
+
+ {icon}
+
{title}
+
+));
+
+SectionHeader.displayName = 'SectionHeader';
+
+/**
+ * Loading state component
+ */
+const LoadingState: React.FC = memo(() => (
+
+
+
Generating executive summary...
+
+ Our AI is analyzing your assessment results
+
+
+));
+
+LoadingState.displayName = 'LoadingState';
+
+/**
+ * Error state component
+ */
+const ErrorState: React.FC<{ error: string; onRetry: () => void }> = memo(({ error, onRetry }) => (
+
+
+
Failed to generate summary
+
{error}
+
+
+ Try Again
+
+
+));
+
+ErrorState.displayName = 'ErrorState';
+
+/**
+ * Empty state when no summary is available
+ */
+const EmptyState: React.FC<{ onGenerate: () => void }> = memo(({ onGenerate }) => (
+
+
+
+
+
No summary available
+
+ Generate an AI-powered executive summary of your assessment
+
+
+ Generate Summary
+
+
+));
+
+EmptyState.displayName = 'EmptyState';
+
+/**
+ * ExecutiveSummary displays the AI-generated analysis with proper loading,
+ * error, and success states. Features structured sections for key findings,
+ * priority actions, risk considerations, and recommended first move.
+ */
+export const ExecutiveSummary: React.FC = memo(({
+ summary,
+ loading,
+ error,
+ onRegenerate,
+}) => {
+ // Loading state
+ if (loading) {
+ return (
+
+
+
+
+ Executive Summary
+
+
+
+
+ );
+ }
+
+ // Error state
+ if (error) {
+ return (
+
+
+
+
+ Executive Summary
+
+
+
+
+ );
+ }
+
+ // Empty state
+ if (!summary) {
+ return (
+
+
+
+
+ Executive Summary
+
+
+
+
+ );
+ }
+
+ // Success state with summary content
+ return (
+
+ {/* Header */}
+
+
+
+
+ Executive Summary
+
+
+
+
+ Regenerate
+
+
+
+ {/* Overall Assessment */}
+
+
+ {summary.overallAssessment}
+
+
+
+
+ {/* Key Findings */}
+
+ }
+ title="Key Findings"
+ />
+
+ {summary.keyFindings.map((finding, index) => (
+
+
+ {finding}
+
+ ))}
+
+
+
+ {/* Priority Actions */}
+
+ }
+ title="Priority Actions"
+ />
+
+ {summary.priorityActions.map((action, index) => (
+
+
+ {index + 1}
+
+ {action}
+
+ ))}
+
+
+
+ {/* Recommended First Move - Highlighted */}
+
+ }
+ title="Recommended First Move"
+ iconColor="text-indigo-700"
+ />
+
+ {summary.recommendedFirstMove}
+
+
+
+ {/* Risk Considerations */}
+
+ }
+ title="Risk Considerations"
+ iconColor="text-amber-600"
+ />
+
+ {summary.riskConsiderations.map((risk, index) => (
+
+
+ {risk}
+
+ ))}
+
+
+
+
+ );
+});
+
+ExecutiveSummary.displayName = 'ExecutiveSummary';
+
+export default ExecutiveSummary;
diff --git a/ai-readiness-accelerator/src/components/results/ROICalculator.tsx b/ai-readiness-accelerator/src/components/results/ROICalculator.tsx
new file mode 100644
index 0000000..269e4bc
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/ROICalculator.tsx
@@ -0,0 +1,396 @@
+/**
+ * ROICalculator Component
+ *
+ * Interactive calculator for estimating ROI from AI implementation.
+ * Displays conservative, moderate, and optimistic projections.
+ */
+
+import React, { memo, useCallback, useMemo } from 'react';
+import { Calculator, TrendingUp, Clock, DollarSign, AlertCircle } from 'lucide-react';
+import type { ROIInputs, ROIBreakdown, ROIProjection } from '../../types/roi.types';
+import { ROI_INPUT_FIELDS } from '../../types/roi.types';
+import { Card, Slider } from '../ui';
+
+export interface ROICalculatorProps {
+ /** Current input values */
+ inputs: ROIInputs;
+ /** Calculated ROI projections */
+ projections: ROIBreakdown;
+ /** Callback when an input value changes */
+ onInputChange: (key: keyof ROIInputs, value: number) => void;
+}
+
+/**
+ * Format currency for display
+ */
+const formatCurrency = (value: number): string => {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ }).format(value);
+};
+
+/**
+ * Format number with commas
+ */
+const formatNumber = (value: number): string => {
+ return new Intl.NumberFormat('en-US').format(value);
+};
+
+/**
+ * Scenario card component for displaying projection results
+ */
+const ScenarioCard: React.FC<{
+ title: string;
+ projection: ROIProjection;
+ variant: 'conservative' | 'moderate' | 'optimistic';
+}> = memo(({ title, projection, variant }) => {
+ const variantStyles = {
+ conservative: {
+ bg: 'bg-gray-50',
+ border: 'border-gray-200',
+ accent: 'text-gray-700',
+ badge: 'bg-gray-100 text-gray-700',
+ },
+ moderate: {
+ bg: 'bg-indigo-50',
+ border: 'border-indigo-200',
+ accent: 'text-indigo-700',
+ badge: 'bg-indigo-100 text-indigo-700',
+ },
+ optimistic: {
+ bg: 'bg-green-50',
+ border: 'border-green-200',
+ accent: 'text-green-700',
+ badge: 'bg-green-100 text-green-700',
+ },
+ };
+
+ const styles = variantStyles[variant];
+
+ return (
+
+
{title}
+
+
+ {/* Annual Hours Saved */}
+
+
+
+ Annual Hours Saved
+
+
+ {formatNumber(projection.annualHoursSaved)} hrs
+
+
+
+ {/* Labor Savings */}
+
+
+
+ Labor Savings
+
+
+ {formatCurrency(projection.annualLaborSavings)}
+
+
+
+ {/* Capacity Value */}
+
+
+
+ Capacity Value
+
+
+ {formatCurrency(projection.annualCapacityValue)}
+
+
+
+
+
+ {/* Total Annual Benefit */}
+
+
+ Total Annual Benefit
+
+
+ {formatCurrency(projection.totalAnnualBenefit)}
+
+
+
+ {/* Year 1 Net Value */}
+
+ Year 1 Net Value
+ = 0 ? 'text-green-600' : 'text-red-600'
+ }`}
+ >
+ {formatCurrency(projection.year1NetValue)}
+
+
+
+ {/* Payback Period */}
+
+ Payback Period
+
+ {projection.paybackMonths < 999
+ ? `${projection.paybackMonths} months`
+ : 'N/A'}
+
+
+
+ {/* 3-Year ROI */}
+
+ 3-Year ROI:
+ {projection.threeYearROI}%
+
+
+
+ );
+});
+
+ScenarioCard.displayName = 'ScenarioCard';
+
+/**
+ * Input slider component with proper formatting
+ */
+const ROIInputSlider: React.FC<{
+ field: (typeof ROI_INPUT_FIELDS)[number];
+ value: number;
+ onChange: (value: number) => void;
+}> = memo(({ field, value, onChange }) => {
+ const handleChange = useCallback(
+ (e: React.ChangeEvent) => {
+ const newValue = Number(e.target.value);
+ // Validate bounds
+ if (newValue >= field.min && newValue <= field.max) {
+ onChange(newValue);
+ }
+ },
+ [field.min, field.max, onChange]
+ );
+
+ const formatValue = useCallback(
+ (val: number): string => {
+ if (field.unit.includes('$')) {
+ return formatCurrency(val);
+ }
+ if (field.unit === '%') {
+ return `${val}%`;
+ }
+ return `${formatNumber(val)} ${field.unit}`;
+ },
+ [field.unit]
+ );
+
+ return (
+
+
+
+ {field.description}
+
+
+ );
+});
+
+ROIInputSlider.displayName = 'ROIInputSlider';
+
+/**
+ * ROICalculator provides an interactive interface for estimating
+ * return on investment from AI implementation. Features input sliders
+ * with validation and displays three projection scenarios.
+ */
+export const ROICalculator: React.FC = memo(({
+ inputs,
+ projections,
+ onInputChange,
+}) => {
+ // Memoize field groups for rendering
+ const volumeFields = useMemo(
+ () =>
+ ROI_INPUT_FIELDS.filter(f =>
+ ['weeklyIntakeVolume', 'avgMinutesPerIntake', 'weeklyDraftingTasks', 'avgMinutesPerDraft'].includes(f.key)
+ ),
+ []
+ );
+
+ const rateFields = useMemo(
+ () =>
+ ROI_INPUT_FIELDS.filter(f =>
+ ['staffHourlyRate', 'billableRate'].includes(f.key)
+ ),
+ []
+ );
+
+ const savingsFields = useMemo(
+ () =>
+ ROI_INPUT_FIELDS.filter(f =>
+ ['projectedTimeSavingsPercent', 'projectedErrorReductionPercent'].includes(f.key)
+ ),
+ []
+ );
+
+ const costFields = useMemo(
+ () =>
+ ROI_INPUT_FIELDS.filter(f =>
+ ['implementationCost', 'monthlyToolCost'].includes(f.key)
+ ),
+ []
+ );
+
+ const handleInputChange = useCallback(
+ (key: keyof ROIInputs) => (value: number) => {
+ onInputChange(key, value);
+ },
+ [onInputChange]
+ );
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ ROI Calculator
+
+
+
+ Adjust the sliders below to estimate your potential return on investment
+ from AI implementation. Projections are based on your firm's current
+ workload and costs.
+
+
+
+ {/* Input Section */}
+
+ {/* Volume Inputs */}
+
+
+ Current Workload
+
+ {volumeFields.map(field => (
+
+ ))}
+
+
+ {/* Rate Inputs */}
+
+
+ Rates & Costs
+
+ {rateFields.map(field => (
+
+ ))}
+ {costFields.map(field => (
+
+ ))}
+
+
+
+ {/* Savings Assumptions */}
+
+
+ Projected Savings
+
+
+ {savingsFields.map(field => (
+
+ ))}
+
+
+
+ {/* Projections Section */}
+
+
+ Projected Returns
+
+
+
+
+
+
+
+
+ {/* Disclaimer */}
+
+
+
+
Disclaimer
+
+ These projections are estimates based on industry benchmarks and your
+ provided inputs. Actual results may vary based on implementation
+ quality, staff adoption, and specific use cases. Conservative
+ estimates use 70% of projected savings, while optimistic estimates
+ use 130%. Capacity value assumes 30% of saved time converts to
+ additional billable work.
+
+
+
+
+ );
+});
+
+ROICalculator.displayName = 'ROICalculator';
+
+export default ROICalculator;
diff --git a/ai-readiness-accelerator/src/components/results/RadarChartSection.tsx b/ai-readiness-accelerator/src/components/results/RadarChartSection.tsx
new file mode 100644
index 0000000..4099219
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/RadarChartSection.tsx
@@ -0,0 +1,176 @@
+/**
+ * RadarChartSection Component
+ *
+ * Displays pillar scores in a radar/spider chart visualization
+ * using Recharts library.
+ */
+
+import React, { memo, useMemo } from 'react';
+import {
+ ResponsiveContainer,
+ RadarChart,
+ PolarGrid,
+ PolarAngleAxis,
+ PolarRadiusAxis,
+ Radar,
+ Tooltip,
+} from 'recharts';
+import { Card } from '../ui';
+
+/**
+ * Individual pillar score for the radar chart
+ */
+export interface PillarScoreData {
+ /** Display name of the pillar */
+ name: string;
+ /** Score value (0-100) */
+ score: number;
+}
+
+export interface RadarChartSectionProps {
+ /** Array of pillar scores to display */
+ pillarScores: PillarScoreData[];
+}
+
+/**
+ * Custom tooltip component for the radar chart
+ */
+const CustomTooltip: React.FC<{
+ active?: boolean;
+ payload?: Array<{ value: number; name: string; payload: PillarScoreData }>;
+}> = ({ active, payload }) => {
+ if (active && payload && payload.length > 0) {
+ const data = payload[0].payload;
+ return (
+
+
{data.name}
+
{data.score}%
+
+ );
+ }
+ return null;
+};
+
+/**
+ * RadarChartSection displays pillar maturity scores in a radar chart.
+ * Uses an indigo/purple color theme for visual consistency with the app design.
+ * Includes proper accessibility considerations for data visualization.
+ */
+export const RadarChartSection: React.FC = memo(({ pillarScores }) => {
+ // Memoize chart data transformation
+ const chartData = useMemo(() => {
+ return pillarScores.map(pillar => ({
+ ...pillar,
+ // Ensure scores are within valid range
+ score: Math.max(0, Math.min(100, Math.round(pillar.score))),
+ // Full mark for the outer ring
+ fullMark: 100,
+ }));
+ }, [pillarScores]);
+
+ // Handle empty data gracefully
+ if (!pillarScores || pillarScores.length === 0) {
+ return (
+
+
+ Maturity Radar
+
+
+ No pillar scores available
+
+
+ );
+ }
+
+ return (
+
+
+ Maturity Radar
+
+
+ {/* Screen reader accessible data summary */}
+
+
Pillar Scores Summary
+
+ {chartData.map(pillar => (
+
+ {pillar.name}: {pillar.score} out of 100
+
+ ))}
+
+
+
+ {/* Radar Chart */}
+
+
+ {/* Legend / Score list for smaller viewports */}
+
+ {chartData.map(pillar => (
+
+ {pillar.name}
+ {pillar.score}%
+
+ ))}
+
+
+ );
+});
+
+RadarChartSection.displayName = 'RadarChartSection';
+
+export default RadarChartSection;
diff --git a/ai-readiness-accelerator/src/components/results/ResultsDashboard.tsx b/ai-readiness-accelerator/src/components/results/ResultsDashboard.tsx
new file mode 100644
index 0000000..fc76ae0
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/ResultsDashboard.tsx
@@ -0,0 +1,297 @@
+/**
+ * ResultsDashboard Component
+ *
+ * Main container for the assessment results with tabbed navigation
+ * between Scorecard, ROI Calculator, and Roadmap views.
+ */
+
+import React, { memo, useState, useCallback, useMemo } from 'react';
+import { RefreshCw, RotateCcw, BarChart3, Calculator, Map } from 'lucide-react';
+import type { PillarScores, ExecutiveSummary as ExecutiveSummaryType, Roadmap } from '../../types/results.types';
+import type { FirmProfile } from '../../types/firm.types';
+import type { ROIInputs, ROIBreakdown } from '../../types/roi.types';
+import { Button } from '../ui';
+import { ScoreSummaryCard } from './ScoreSummaryCard';
+import { RadarChartSection, type PillarScoreData } from './RadarChartSection';
+import { ExecutiveSummary } from './ExecutiveSummary';
+import { ROICalculator } from './ROICalculator';
+import { RoadmapSection, type Blocker } from './RoadmapSection';
+
+/**
+ * Tab identifiers
+ */
+type TabId = 'scorecard' | 'roi' | 'roadmap';
+
+/**
+ * Tab configuration
+ */
+interface TabConfig {
+ id: TabId;
+ label: string;
+ icon: React.ReactNode;
+}
+
+/**
+ * Available tabs
+ */
+const TABS: TabConfig[] = [
+ { id: 'scorecard', label: 'Scorecard', icon: },
+ { id: 'roi', label: 'ROI Calculator', icon: },
+ { id: 'roadmap', label: 'Roadmap', icon: },
+];
+
+export interface ResultsDashboardProps {
+ /** Assessment scores data */
+ scores: PillarScores;
+ /** Firm profile information */
+ firmProfile: FirmProfile;
+ /** Executive summary data (null if not loaded) */
+ summary: ExecutiveSummaryType | null;
+ /** Whether the summary is being loaded */
+ summaryLoading: boolean;
+ /** Error message for summary generation */
+ summaryError: string | null;
+ /** ROI calculator input values */
+ roiInputs: ROIInputs;
+ /** Calculated ROI projections */
+ roiProjections: ROIBreakdown;
+ /** Implementation roadmap */
+ roadmap: Roadmap | null;
+ /** Identified blockers from assessment */
+ blockers: Blocker[];
+ /** Callback when ROI input changes */
+ onRoiInputChange: (key: keyof ROIInputs, value: number) => void;
+ /** Callback to regenerate executive summary */
+ onRegenerateSummary: () => void;
+ /** Callback to reset the assessment */
+ onReset: () => void;
+ /** Callback to book a consultation call */
+ onBookCall: () => void;
+}
+
+/**
+ * Tab button component with proper ARIA attributes
+ */
+const TabButton: React.FC<{
+ tab: TabConfig;
+ isSelected: boolean;
+ onClick: () => void;
+}> = memo(({ tab, isSelected, onClick }) => (
+
+ {tab.icon}
+ {tab.label}
+
+));
+
+TabButton.displayName = 'TabButton';
+
+/**
+ * ResultsDashboard is the main container for assessment results.
+ * Features tabbed navigation between Scorecard, ROI Calculator, and Roadmap views.
+ * Includes proper accessibility with ARIA tab pattern implementation.
+ */
+export const ResultsDashboard: React.FC = memo(({
+ scores,
+ firmProfile,
+ summary,
+ summaryLoading,
+ summaryError,
+ roiInputs,
+ roiProjections,
+ roadmap,
+ blockers,
+ onRoiInputChange,
+ onRegenerateSummary,
+ onReset,
+ onBookCall,
+}) => {
+ // Track active tab
+ const [activeTab, setActiveTab] = useState('scorecard');
+
+ // Handle tab selection
+ const handleTabSelect = useCallback((tabId: TabId) => {
+ setActiveTab(tabId);
+ }, []);
+
+ // Handle keyboard navigation for tabs
+ const handleTabKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ const currentIndex = TABS.findIndex(t => t.id === activeTab);
+ let newIndex = currentIndex;
+
+ if (event.key === 'ArrowRight') {
+ newIndex = (currentIndex + 1) % TABS.length;
+ } else if (event.key === 'ArrowLeft') {
+ newIndex = (currentIndex - 1 + TABS.length) % TABS.length;
+ } else if (event.key === 'Home') {
+ newIndex = 0;
+ } else if (event.key === 'End') {
+ newIndex = TABS.length - 1;
+ } else {
+ return;
+ }
+
+ event.preventDefault();
+ setActiveTab(TABS[newIndex].id);
+
+ // Focus the new tab button
+ const tabButton = document.getElementById(`tab-${TABS[newIndex].id}`);
+ tabButton?.focus();
+ },
+ [activeTab]
+ );
+
+ // Transform pillar scores for radar chart
+ const pillarScoreData: PillarScoreData[] = useMemo(() => {
+ return scores.pillars.map(pillar => ({
+ name: pillar.pillarName,
+ score: Math.round((pillar.score / pillar.maxScore) * 100),
+ }));
+ }, [scores.pillars]);
+
+ return (
+
+ {/* Header */}
+
+
+
+ Assessment Results
+
+
+ Review your AI readiness assessment for {firmProfile.firmName}
+
+
+
+
+
+ Refresh
+
+
+
+ New Assessment
+
+
+
+
+ {/* Tab navigation */}
+
+ {TABS.map(tab => (
+ handleTabSelect(tab.id)}
+ />
+ ))}
+
+
+ {/* Tab panels */}
+
+ {/* Scorecard Tab */}
+
+ {activeTab === 'scorecard' && (
+
+ {/* Score summary and radar chart */}
+
+
+
+
+
+ {/* Executive summary */}
+
+
+ )}
+
+
+ {/* ROI Calculator Tab */}
+
+ {activeTab === 'roi' && (
+
+ )}
+
+
+ {/* Roadmap Tab */}
+
+ {activeTab === 'roadmap' && (
+
+ )}
+
+
+
+ );
+});
+
+ResultsDashboard.displayName = 'ResultsDashboard';
+
+export default ResultsDashboard;
diff --git a/ai-readiness-accelerator/src/components/results/RoadmapSection.tsx b/ai-readiness-accelerator/src/components/results/RoadmapSection.tsx
new file mode 100644
index 0000000..0da1279
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/RoadmapSection.tsx
@@ -0,0 +1,335 @@
+/**
+ * RoadmapSection Component
+ *
+ * Displays the implementation roadmap with 30/60/90 day phases,
+ * tasks, blockers warning, and a call-to-action.
+ */
+
+import React, { memo, useMemo } from 'react';
+import {
+ Map,
+ AlertTriangle,
+ CheckCircle,
+ Circle,
+ Target,
+} from 'lucide-react';
+import type { Roadmap, RoadmapPhase, RoadmapTask } from '../../types/results.types';
+import { Card } from '../ui';
+import { BookCallCTA } from './BookCallCTA';
+
+/**
+ * Blocker item from assessment
+ */
+export interface Blocker {
+ /** Pillar the blocker relates to */
+ pillarName: string;
+ /** Description of the blocker */
+ description: string;
+ /** Severity level */
+ severity: 'high' | 'medium' | 'low';
+}
+
+export interface RoadmapSectionProps {
+ /** Implementation roadmap with phases */
+ roadmap: Roadmap | null;
+ /** List of identified blockers that need addressing */
+ blockers: Blocker[];
+ /** Callback when booking a diagnostic call */
+ onBookCall?: () => void;
+}
+
+/**
+ * Priority badge colors
+ */
+const PRIORITY_COLORS: Record = {
+ critical: 'bg-red-100 text-red-700 border-red-200',
+ high: 'bg-orange-100 text-orange-700 border-orange-200',
+ medium: 'bg-yellow-100 text-yellow-700 border-yellow-200',
+ low: 'bg-gray-100 text-gray-700 border-gray-200',
+};
+
+/**
+ * Phase timeline labels
+ */
+const PHASE_LABELS: Record = {
+ 1: 'Days 1-30',
+ 2: 'Days 31-60',
+ 3: 'Days 61-90',
+};
+
+/**
+ * Blockers warning component
+ */
+const BlockersWarning: React.FC<{ blockers: Blocker[] }> = memo(({ blockers }) => {
+ if (blockers.length === 0) return null;
+
+ const highSeverityCount = blockers.filter(b => b.severity === 'high').length;
+
+ return (
+
+
+
+
+
+ {highSeverityCount > 0
+ ? `${highSeverityCount} Critical Blocker${highSeverityCount > 1 ? 's' : ''} Identified`
+ : `${blockers.length} Blocker${blockers.length > 1 ? 's' : ''} Identified`}
+
+
+ The following issues should be addressed before proceeding with AI implementation:
+
+
+ {blockers.map((blocker, index) => (
+
+
+ {blocker.severity.toUpperCase()}
+
+
+ {blocker.pillarName}: {blocker.description}
+
+
+ ))}
+
+
+
+
+ );
+});
+
+BlockersWarning.displayName = 'BlockersWarning';
+
+/**
+ * Task list item component
+ */
+const TaskItem: React.FC<{ task: RoadmapTask }> = memo(({ task }) => (
+
+
+
+
+ {task.title}
+
+ {task.priority}
+
+
+
{task.description}
+
+ Owner: {task.owner}
+
+
+
+));
+
+TaskItem.displayName = 'TaskItem';
+
+/**
+ * Success criteria list component
+ */
+const SuccessCriteria: React.FC<{ criteria: string[] }> = memo(({ criteria }) => (
+
+
+
+ Success Criteria
+
+
+ {criteria.map((criterion, index) => (
+
+
+ {criterion}
+
+ ))}
+
+
+));
+
+SuccessCriteria.displayName = 'SuccessCriteria';
+
+/**
+ * Phase card component
+ */
+const PhaseCard: React.FC<{
+ phase: RoadmapPhase;
+ isFirst: boolean;
+ isLast: boolean;
+}> = memo(({ phase, isFirst, isLast }) => {
+ const phaseLabel = PHASE_LABELS[phase.phase] || `Phase ${phase.phase}`;
+
+ return (
+
+ {/* Timeline connector */}
+ {!isFirst && (
+
+ )}
+
+
+ {/* Timeline dot */}
+
+
+ {phase.phase * 30}
+
+
+
+ {/* Phase content */}
+
+
+ {/* Phase header */}
+
+
+ {phaseLabel}
+
+
+ {phase.objective}
+
+
+
+ {/* Tasks */}
+ {phase.tasks.length > 0 ? (
+
+ {phase.tasks.map((task, index) => (
+
+ ))}
+
+ ) : (
+
+ No specific tasks for this phase based on your assessment.
+
+ )}
+
+ {/* Success criteria */}
+ {phase.successCriteria.length > 0 && (
+
+ )}
+
+
+
+
+ {/* Timeline connector to next */}
+ {!isLast && (
+
+ )}
+
+ );
+});
+
+PhaseCard.displayName = 'PhaseCard';
+
+/**
+ * Empty roadmap state
+ */
+const EmptyRoadmap: React.FC = memo(() => (
+
+
+
No roadmap available
+
+ Complete the assessment to generate your implementation roadmap.
+
+
+));
+
+EmptyRoadmap.displayName = 'EmptyRoadmap';
+
+/**
+ * RoadmapSection displays the 30/60/90 day implementation roadmap
+ * with tasks organized by phase, blockers warning, and success criteria.
+ * Includes a CTA for booking a diagnostic call.
+ */
+export const RoadmapSection: React.FC = memo(({
+ roadmap,
+ blockers,
+ onBookCall,
+}) => {
+ // Sort phases by phase number
+ const sortedPhases = useMemo(() => {
+ if (!roadmap?.phases) return [];
+ return [...roadmap.phases].sort((a, b) => a.phase - b.phase);
+ }, [roadmap?.phases]);
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ Implementation Roadmap
+
+
+ {roadmap?.generatedFor && (
+
+ Customized for {roadmap.generatedFor}
+
+ )}
+
+
+ {/* Blockers warning */}
+
+
+ {/* Timeline */}
+
+ {sortedPhases.length > 0 ? (
+
+ {sortedPhases.map((phase, index) => (
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+ {/* Book call CTA */}
+ {onBookCall &&
}
+
+ );
+});
+
+RoadmapSection.displayName = 'RoadmapSection';
+
+export default RoadmapSection;
diff --git a/ai-readiness-accelerator/src/components/results/ScoreSummaryCard.tsx b/ai-readiness-accelerator/src/components/results/ScoreSummaryCard.tsx
new file mode 100644
index 0000000..3e7e597
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/ScoreSummaryCard.tsx
@@ -0,0 +1,143 @@
+/**
+ * ScoreSummaryCard Component
+ *
+ * Displays the overall assessment score with a circular progress indicator,
+ * readiness level badge, and firm name.
+ */
+
+import React, { memo, useMemo } from 'react';
+import type { ReadinessLevel } from '../../types/results.types';
+import { READINESS_LEVELS } from '../../types/results.types';
+import { Card, Badge } from '../ui';
+
+export interface ScoreSummaryCardProps {
+ /** Overall assessment score (0-100) */
+ overallScore: number;
+ /** Calculated readiness level */
+ readinessLevel: ReadinessLevel;
+ /** Name of the firm being assessed */
+ firmName: string;
+}
+
+/**
+ * Maps readiness levels to badge variants for color-coding
+ */
+const READINESS_BADGE_VARIANTS: Record = {
+ 'not-ready': 'error',
+ 'foundational': 'warning',
+ 'developing': 'warning',
+ 'ready': 'success',
+ 'optimized': 'info',
+};
+
+/**
+ * Circular progress indicator component
+ */
+const CircularProgress: React.FC<{ score: number; color: string }> = memo(({ score, color }) => {
+ const radius = 80;
+ const strokeWidth = 12;
+ const normalizedRadius = radius - strokeWidth / 2;
+ const circumference = normalizedRadius * 2 * Math.PI;
+ const strokeDashoffset = circumference - (score / 100) * circumference;
+
+ return (
+
+ {/* Background circle */}
+
+ {/* Progress circle */}
+
+
+ );
+});
+
+CircularProgress.displayName = 'CircularProgress';
+
+/**
+ * ScoreSummaryCard displays the overall assessment score with visual indicators.
+ * Features a large circular progress indicator, color-coded readiness badge,
+ * and firm name for context.
+ */
+export const ScoreSummaryCard: React.FC = memo(({
+ overallScore,
+ readinessLevel,
+ firmName,
+}) => {
+ // Get readiness level info with memoization
+ const readinessInfo = useMemo(() => {
+ return READINESS_LEVELS.find(level => level.level === readinessLevel) || READINESS_LEVELS[0];
+ }, [readinessLevel]);
+
+ const badgeVariant = READINESS_BADGE_VARIANTS[readinessLevel];
+
+ // Ensure score is within bounds
+ const clampedScore = Math.max(0, Math.min(100, Math.round(overallScore)));
+
+ return (
+
+
+ AI Readiness Score
+
+
+ {/* Firm name */}
+ {firmName}
+
+ {/* Circular progress indicator with score */}
+
+
+
+
+ {clampedScore}
+
+ out of 100
+
+
+
+ {/* Readiness level badge */}
+
+
+ {readinessInfo.label}
+
+
+
+ {/* Readiness level description */}
+
+ {readinessInfo.description}
+
+
+ );
+});
+
+ScoreSummaryCard.displayName = 'ScoreSummaryCard';
+
+export default ScoreSummaryCard;
diff --git a/ai-readiness-accelerator/src/components/results/index.ts b/ai-readiness-accelerator/src/components/results/index.ts
new file mode 100644
index 0000000..b7cc265
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/results/index.ts
@@ -0,0 +1,33 @@
+/**
+ * Results Dashboard Components
+ *
+ * Export all components related to the assessment results dashboard,
+ * including score display, charts, ROI calculator, and roadmap views.
+ */
+
+// Main dashboard container
+export { ResultsDashboard, type ResultsDashboardProps } from './ResultsDashboard';
+
+// Score display components
+export { ScoreSummaryCard, type ScoreSummaryCardProps } from './ScoreSummaryCard';
+export {
+ RadarChartSection,
+ type RadarChartSectionProps,
+ type PillarScoreData,
+} from './RadarChartSection';
+
+// Executive summary
+export { ExecutiveSummary, type ExecutiveSummaryProps } from './ExecutiveSummary';
+
+// ROI calculator
+export { ROICalculator, type ROICalculatorProps } from './ROICalculator';
+
+// Roadmap section
+export {
+ RoadmapSection,
+ type RoadmapSectionProps,
+ type Blocker,
+} from './RoadmapSection';
+
+// Call-to-action
+export { BookCallCTA, type BookCallCTAProps } from './BookCallCTA';
diff --git a/ai-readiness-accelerator/src/components/shared/ErrorBoundary.tsx b/ai-readiness-accelerator/src/components/shared/ErrorBoundary.tsx
new file mode 100644
index 0000000..71564c0
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/shared/ErrorBoundary.tsx
@@ -0,0 +1,103 @@
+import { Component, ErrorInfo, ReactNode } from 'react';
+import { Button } from '../ui/Button';
+
+export interface ErrorBoundaryProps {
+ /** Child components to render */
+ children: ReactNode;
+ /** Optional custom fallback UI */
+ fallback?: ReactNode;
+}
+
+export interface ErrorBoundaryState {
+ /** Whether an error has been caught */
+ hasError: boolean;
+ /** The caught error, if any */
+ error: Error | null;
+}
+
+/**
+ * React error boundary component that catches JavaScript errors in child components.
+ * Displays a fallback UI when an error occurs, with option to reload the page.
+ */
+export class ErrorBoundary extends Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: null,
+ };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return {
+ hasError: true,
+ error,
+ };
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
+ // Log error to console in development
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
+ }
+
+ handleReload = (): void => {
+ window.location.reload();
+ };
+
+ render(): ReactNode {
+ if (this.state.hasError) {
+ // Render custom fallback if provided
+ if (this.props.fallback) {
+ return this.props.fallback;
+ }
+
+ // Default fallback UI
+ return (
+
+
+
+
+
+ Something went wrong
+
+
+
+ We encountered an unexpected error. Please try reloading the page.
+
+
+
+ Reload
+
+
+ {import.meta.env.DEV && this.state.error && (
+
+
+ {this.state.error.message}
+
+
+ )}
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/ai-readiness-accelerator/src/components/shared/index.ts b/ai-readiness-accelerator/src/components/shared/index.ts
new file mode 100644
index 0000000..2082f8b
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/shared/index.ts
@@ -0,0 +1,2 @@
+export { ErrorBoundary } from './ErrorBoundary';
+export type { ErrorBoundaryProps, ErrorBoundaryState } from './ErrorBoundary';
diff --git a/ai-readiness-accelerator/src/components/ui/Badge.tsx b/ai-readiness-accelerator/src/components/ui/Badge.tsx
new file mode 100644
index 0000000..95125b7
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Badge.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+
+export interface BadgeProps {
+ /** Visual style variant */
+ variant?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
+ /** Size of the badge */
+ size?: 'sm' | 'md' | 'lg';
+ /** Badge content */
+ children: React.ReactNode;
+ /** Additional CSS classes */
+ className?: string;
+}
+
+const variantClasses: Record, string> = {
+ success: 'bg-green-100 text-green-800 border-green-200',
+ warning: 'bg-yellow-100 text-yellow-800 border-yellow-200',
+ error: 'bg-red-100 text-red-800 border-red-200',
+ info: 'bg-blue-100 text-blue-800 border-blue-200',
+ neutral: 'bg-gray-100 text-gray-800 border-gray-200',
+};
+
+const sizeClasses: Record, string> = {
+ sm: 'px-2 py-0.5 text-xs',
+ md: 'px-2.5 py-1 text-sm',
+ lg: 'px-3 py-1.5 text-base',
+};
+
+/**
+ * Badge component for displaying status, labels, or tags.
+ * Supports multiple color variants and sizes.
+ */
+export const Badge: React.FC = ({
+ variant = 'neutral',
+ size = 'md',
+ children,
+ className = '',
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Badge;
diff --git a/ai-readiness-accelerator/src/components/ui/Button.tsx b/ai-readiness-accelerator/src/components/ui/Button.tsx
new file mode 100644
index 0000000..e6e26cb
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Button.tsx
@@ -0,0 +1,96 @@
+import React, { forwardRef } from 'react';
+import { LoadingSpinner } from './LoadingSpinner';
+
+export interface ButtonProps extends React.ButtonHTMLAttributes {
+ /** Visual style variant */
+ variant?: 'primary' | 'secondary' | 'ghost';
+ /** Size of the button */
+ size?: 'sm' | 'md' | 'lg';
+ /** Whether the button is in a loading state */
+ loading?: boolean;
+ /** Button content */
+ children: React.ReactNode;
+}
+
+const variantClasses: Record, string> = {
+ primary: `
+ bg-primary-600 text-white
+ hover:bg-primary-700
+ focus:ring-primary-500
+ disabled:bg-primary-300
+ `,
+ secondary: `
+ bg-white text-primary-700
+ border border-primary-300
+ hover:bg-primary-50
+ focus:ring-primary-500
+ disabled:bg-gray-100 disabled:text-gray-400 disabled:border-gray-200
+ `,
+ ghost: `
+ bg-transparent text-primary-600
+ hover:bg-primary-50
+ focus:ring-primary-500
+ disabled:text-gray-400 disabled:hover:bg-transparent
+ `,
+};
+
+const sizeClasses: Record, string> = {
+ sm: 'px-3 py-1.5 text-sm',
+ md: 'px-4 py-2 text-base',
+ lg: 'px-6 py-3 text-lg',
+};
+
+/**
+ * Button component with multiple variants, sizes, and loading state support.
+ * Uses forwardRef for proper ref forwarding to the underlying button element.
+ */
+export const Button = forwardRef(
+ (
+ {
+ variant = 'primary',
+ size = 'md',
+ loading = false,
+ disabled = false,
+ children,
+ className = '',
+ type = 'button',
+ ...props
+ },
+ ref
+ ) => {
+ const isDisabled = disabled || loading;
+
+ return (
+
+ {loading && (
+
+ )}
+ {children}
+
+ );
+ }
+);
+
+Button.displayName = 'Button';
+
+export default Button;
diff --git a/ai-readiness-accelerator/src/components/ui/Card.tsx b/ai-readiness-accelerator/src/components/ui/Card.tsx
new file mode 100644
index 0000000..a4b6e77
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Card.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+
+export interface CardProps {
+ /** Card content */
+ children: React.ReactNode;
+ /** Additional CSS classes */
+ className?: string;
+ /** Padding size */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
+}
+
+const paddingClasses: Record, string> = {
+ none: 'p-0',
+ sm: 'p-3',
+ md: 'p-4 sm:p-6',
+ lg: 'p-6 sm:p-8',
+};
+
+/**
+ * Card component provides a clean container with shadow and border.
+ * Useful for grouping related content together.
+ */
+export const Card: React.FC = ({
+ children,
+ className = '',
+ padding = 'md',
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Card;
diff --git a/ai-readiness-accelerator/src/components/ui/Input.tsx b/ai-readiness-accelerator/src/components/ui/Input.tsx
new file mode 100644
index 0000000..a8698e0
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Input.tsx
@@ -0,0 +1,114 @@
+import React, { forwardRef, useId } from 'react';
+import { AlertCircle } from 'lucide-react';
+
+export interface InputProps extends Omit, 'id'> {
+ /** Label text for the input */
+ label?: string;
+ /** Error message to display */
+ error?: string;
+ /** Help text displayed below the input */
+ helpText?: string;
+ /** Custom ID (auto-generated if not provided) */
+ id?: string;
+}
+
+/**
+ * Input component with label, error, and help text support.
+ * Properly associates labels with inputs for accessibility.
+ */
+export const Input = forwardRef(
+ (
+ {
+ label,
+ error,
+ helpText,
+ id: customId,
+ className = '',
+ required,
+ ...props
+ },
+ ref
+ ) => {
+ const generatedId = useId();
+ const inputId = customId || generatedId;
+ const errorId = `${inputId}-error`;
+ const helpTextId = `${inputId}-help`;
+
+ const hasError = Boolean(error);
+ const hasHelpText = Boolean(helpText);
+
+ // Build aria-describedby based on what's present
+ const describedByIds: string[] = [];
+ if (hasError) describedByIds.push(errorId);
+ if (hasHelpText) describedByIds.push(helpTextId);
+ const ariaDescribedBy = describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
+
+ return (
+
+ {label && (
+
+ {label}
+ {required && (
+
+ *
+
+ )}
+
+ )}
+
+
+ {hasError && (
+
+ )}
+
+ {hasError && (
+
+ {error}
+
+ )}
+ {hasHelpText && !hasError && (
+
+ {helpText}
+
+ )}
+
+ );
+ }
+);
+
+Input.displayName = 'Input';
+
+export default Input;
diff --git a/ai-readiness-accelerator/src/components/ui/LoadingSpinner.tsx b/ai-readiness-accelerator/src/components/ui/LoadingSpinner.tsx
new file mode 100644
index 0000000..105761e
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/LoadingSpinner.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+
+export interface LoadingSpinnerProps {
+ /** Size of the spinner */
+ size?: 'sm' | 'md' | 'lg';
+ /** Additional CSS classes */
+ className?: string;
+}
+
+const sizeClasses: Record, string> = {
+ sm: 'w-4 h-4 border-2',
+ md: 'w-6 h-6 border-2',
+ lg: 'w-8 h-8 border-3',
+};
+
+/**
+ * LoadingSpinner component displays an animated spinner for loading states.
+ * Includes proper accessibility attributes for screen readers.
+ */
+export const LoadingSpinner: React.FC = ({
+ size = 'md',
+ className = '',
+}) => {
+ return (
+
+ Loading...
+
+ );
+};
+
+export default LoadingSpinner;
diff --git a/ai-readiness-accelerator/src/components/ui/ProgressBar.tsx b/ai-readiness-accelerator/src/components/ui/ProgressBar.tsx
new file mode 100644
index 0000000..00ac494
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/ProgressBar.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+
+export interface ProgressBarProps {
+ /** Progress value between 0 and 100 */
+ value: number;
+ /** Optional label text */
+ label?: string;
+ /** Whether to show the percentage value */
+ showPercentage?: boolean;
+ /** Additional CSS classes for the container */
+ className?: string;
+ /** Color variant based on progress */
+ variant?: 'default' | 'success' | 'warning' | 'error';
+}
+
+const variantClasses: Record, string> = {
+ default: 'bg-primary-600',
+ success: 'bg-green-500',
+ warning: 'bg-yellow-500',
+ error: 'bg-red-500',
+};
+
+/**
+ * ProgressBar component displays progress with optional label and percentage.
+ * Includes proper accessibility attributes for screen readers.
+ */
+export const ProgressBar: React.FC = ({
+ value,
+ label,
+ showPercentage = false,
+ className = '',
+ variant = 'default',
+}) => {
+ // Clamp value between 0 and 100
+ const clampedValue = Math.min(100, Math.max(0, value));
+ const roundedValue = Math.round(clampedValue);
+
+ return (
+
+ {(label || showPercentage) && (
+
+ {label && (
+
+ {label}
+
+ )}
+ {showPercentage && (
+
+ {roundedValue}%
+
+ )}
+
+ )}
+
+
+ );
+};
+
+export default ProgressBar;
diff --git a/ai-readiness-accelerator/src/components/ui/Select.tsx b/ai-readiness-accelerator/src/components/ui/Select.tsx
new file mode 100644
index 0000000..fc40419
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Select.tsx
@@ -0,0 +1,143 @@
+import React, { forwardRef, useId } from 'react';
+import { AlertCircle, ChevronDown } from 'lucide-react';
+
+export interface SelectOption {
+ value: string;
+ label: string;
+}
+
+export interface SelectProps extends Omit, 'id'> {
+ /** Label text for the select */
+ label?: string;
+ /** Array of options to display */
+ options: SelectOption[];
+ /** Error message to display */
+ error?: string;
+ /** Help text displayed below the select */
+ helpText?: string;
+ /** Custom ID (auto-generated if not provided) */
+ id?: string;
+ /** Placeholder text shown when no option is selected */
+ placeholder?: string;
+}
+
+/**
+ * Select component with label, options, error, and help text support.
+ * Supports both single and multiple selection modes.
+ */
+export const Select = forwardRef(
+ (
+ {
+ label,
+ options,
+ error,
+ helpText,
+ id: customId,
+ className = '',
+ required,
+ placeholder,
+ multiple,
+ ...props
+ },
+ ref
+ ) => {
+ const generatedId = useId();
+ const selectId = customId || generatedId;
+ const errorId = `${selectId}-error`;
+ const helpTextId = `${selectId}-help`;
+
+ const hasError = Boolean(error);
+ const hasHelpText = Boolean(helpText);
+
+ // Build aria-describedby based on what's present
+ const describedByIds: string[] = [];
+ if (hasError) describedByIds.push(errorId);
+ if (hasHelpText) describedByIds.push(helpTextId);
+ const ariaDescribedBy = describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
+
+ return (
+
+ {label && (
+
+ {label}
+ {required && (
+
+ *
+
+ )}
+
+ )}
+
+
+ {placeholder && (
+
+ {placeholder}
+
+ )}
+ {options.map((option) => (
+
+ {option.label}
+
+ ))}
+
+ {!multiple && (
+
+ {hasError ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ {hasError && (
+
+ {error}
+
+ )}
+ {hasHelpText && !hasError && (
+
+ {helpText}
+
+ )}
+
+ );
+ }
+);
+
+Select.displayName = 'Select';
+
+export default Select;
diff --git a/ai-readiness-accelerator/src/components/ui/Slider.tsx b/ai-readiness-accelerator/src/components/ui/Slider.tsx
new file mode 100644
index 0000000..2643abc
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/Slider.tsx
@@ -0,0 +1,126 @@
+import React, { forwardRef, useId } from 'react';
+
+export interface SliderProps extends Omit, 'type' | 'id'> {
+ /** Label text for the slider */
+ label?: string;
+ /** Minimum value */
+ min?: number;
+ /** Maximum value */
+ max?: number;
+ /** Step increment */
+ step?: number;
+ /** Current value */
+ value?: number;
+ /** Whether to show the current value */
+ showValue?: boolean;
+ /** Custom function to format the displayed value */
+ formatValue?: (value: number) => string;
+ /** Custom ID (auto-generated if not provided) */
+ id?: string;
+}
+
+/**
+ * Slider component with label and value display.
+ * Includes proper accessibility attributes for screen readers.
+ */
+export const Slider = forwardRef(
+ (
+ {
+ label,
+ min = 0,
+ max = 100,
+ step = 1,
+ value = 0,
+ showValue = true,
+ formatValue,
+ id: customId,
+ className = '',
+ onChange,
+ ...props
+ },
+ ref
+ ) => {
+ const generatedId = useId();
+ const sliderId = customId || generatedId;
+
+ const displayValue = formatValue ? formatValue(value) : String(value);
+
+ // Calculate percentage for custom styling
+ const percentage = ((value - min) / (max - min)) * 100;
+
+ return (
+
+ {(label || showValue) && (
+
+ {label && (
+
+ {label}
+
+ )}
+ {showValue && (
+
+ {displayValue}
+
+ )}
+
+ )}
+
+
+ );
+ }
+);
+
+Slider.displayName = 'Slider';
+
+export default Slider;
diff --git a/ai-readiness-accelerator/src/components/ui/index.ts b/ai-readiness-accelerator/src/components/ui/index.ts
new file mode 100644
index 0000000..af8efd1
--- /dev/null
+++ b/ai-readiness-accelerator/src/components/ui/index.ts
@@ -0,0 +1,9 @@
+// UI Components
+export { Button, type ButtonProps } from './Button';
+export { Card, type CardProps } from './Card';
+export { Input, type InputProps } from './Input';
+export { Select, type SelectProps, type SelectOption } from './Select';
+export { Slider, type SliderProps } from './Slider';
+export { ProgressBar, type ProgressBarProps } from './ProgressBar';
+export { Badge, type BadgeProps } from './Badge';
+export { LoadingSpinner, type LoadingSpinnerProps } from './LoadingSpinner';
diff --git a/ai-readiness-accelerator/src/constants/index.ts b/ai-readiness-accelerator/src/constants/index.ts
new file mode 100644
index 0000000..9e32659
--- /dev/null
+++ b/ai-readiness-accelerator/src/constants/index.ts
@@ -0,0 +1,76 @@
+/**
+ * Constants for the AI Readiness Accelerator application
+ */
+
+import type { PillarStatus, ReadinessLevel } from '../types';
+
+/**
+ * Score thresholds for pillar status determination
+ * Boundary values are inclusive of the lower bound
+ */
+export const PILLAR_STATUS_THRESHOLDS: Record = {
+ 'critical': { min: 0, max: 1.5 },
+ 'needs-work': { min: 1.5, max: 2.5 },
+ 'adequate': { min: 2.5, max: 3.5 },
+ 'strong': { min: 3.5, max: 4.5 },
+ 'excellent': { min: 4.5, max: 5 },
+};
+
+/**
+ * Score thresholds for overall readiness level determination
+ */
+export const READINESS_LEVEL_THRESHOLDS: Record = {
+ 'not-ready': { min: 0, max: 1.0 },
+ 'foundational': { min: 1.1, max: 2.0 },
+ 'developing': { min: 2.1, max: 3.0 },
+ 'ready': { min: 3.1, max: 4.0 },
+ 'optimized': { min: 4.1, max: 5.0 },
+};
+
+/**
+ * Critical pillar IDs that are blockers for AI deployment
+ */
+export const CRITICAL_PILLAR_IDS = ['risk', 'data'] as const;
+
+/**
+ * Minimum score threshold to not be considered a blocker
+ */
+export const BLOCKER_SCORE_THRESHOLD = 3;
+
+/**
+ * Number of top/bottom pillars to identify as strengths/gaps
+ */
+export const STRENGTHS_GAPS_COUNT = 3;
+
+/**
+ * Human-readable labels for pillar statuses
+ */
+export const PILLAR_STATUS_LABELS: Record = {
+ 'critical': 'Critical',
+ 'needs-work': 'Needs Work',
+ 'adequate': 'Adequate',
+ 'strong': 'Strong',
+ 'excellent': 'Excellent',
+};
+
+/**
+ * Human-readable labels for readiness levels
+ */
+export const READINESS_LEVEL_LABELS: Record = {
+ 'not-ready': 'Not Ready',
+ 'foundational': 'Foundational',
+ 'developing': 'Developing',
+ 'ready': 'Ready',
+ 'optimized': 'Optimized',
+};
+
+/**
+ * Descriptions for each pillar status level
+ */
+export const PILLAR_STATUS_DESCRIPTIONS: Record = {
+ 'critical': 'requires immediate attention and significant improvement',
+ 'needs-work': 'needs focused development and enhancement',
+ 'adequate': 'meets basic requirements but has room for improvement',
+ 'strong': 'demonstrates solid capabilities with minor refinements needed',
+ 'excellent': 'is a key competitive advantage and industry-leading',
+};
diff --git a/ai-readiness-accelerator/src/constants/pillars.ts b/ai-readiness-accelerator/src/constants/pillars.ts
new file mode 100644
index 0000000..3863854
--- /dev/null
+++ b/ai-readiness-accelerator/src/constants/pillars.ts
@@ -0,0 +1,255 @@
+/**
+ * Pillar definitions and assessment questions for the AI Readiness Accelerator
+ *
+ * These pillars form the core assessment framework for evaluating law firm
+ * readiness for AI adoption and automation initiatives.
+ */
+
+import type { PillarDefinition } from '../types';
+
+/**
+ * Lucide icon name type for pillar visualization
+ */
+export type PillarIcon = 'Workflow' | 'Database' | 'BookOpen' | 'Settings' | 'Shield' | 'TrendingUp';
+
+/**
+ * Extended pillar definition including icon information
+ */
+export interface ExtendedPillarDefinition extends PillarDefinition {
+ /** Lucide icon name for visual representation */
+ icon: PillarIcon;
+}
+
+/**
+ * Array of all assessment pillar definitions with their questions.
+ * Each pillar represents a critical dimension of AI readiness for law firms.
+ */
+export const PILLAR_DEFINITIONS: ExtendedPillarDefinition[] = [
+ {
+ id: 'process',
+ name: 'Process Maturity',
+ description: 'Evaluates the consistency and documentation of core legal workflows, from client intake through case resolution.',
+ icon: 'Workflow',
+ questions: [
+ {
+ id: 'process-1',
+ text: 'How standardized is your client intake process across all practice areas?',
+ category: 'intake',
+ },
+ {
+ id: 'process-2',
+ text: 'Do you have documented, repeatable procedures for case setup and matter opening?',
+ category: 'case-setup',
+ },
+ {
+ id: 'process-3',
+ text: 'How consistently do attorneys follow established drafting workflows for common document types?',
+ category: 'drafting',
+ },
+ {
+ id: 'process-4',
+ text: 'Are your court filing and service procedures standardized with clear checklists?',
+ category: 'filing',
+ },
+ {
+ id: 'process-5',
+ text: 'Do you have systematic follow-up procedures after filings, hearings, and case milestones?',
+ category: 'follow-up',
+ },
+ ],
+ },
+ {
+ id: 'data',
+ name: 'Data Quality',
+ description: 'Assesses the cleanliness, consistency, and completeness of data across practice management and document systems.',
+ icon: 'Database',
+ questions: [
+ {
+ id: 'data-1',
+ text: 'How consistent are required fields populated across your matter database (client name, matter type, responsible attorney)?',
+ category: 'field-consistency',
+ },
+ {
+ id: 'data-2',
+ text: 'What is your estimated rate of duplicate contacts, matters, or documents in your systems?',
+ category: 'duplicates',
+ },
+ {
+ id: 'data-3',
+ text: 'Do you enforce standardized naming conventions for documents, folders, and matter descriptions?',
+ category: 'naming-conventions',
+ },
+ {
+ id: 'data-4',
+ text: 'How complete is critical case information (key dates, opposing counsel, judge assignments) in your matters?',
+ category: 'completeness',
+ },
+ {
+ id: 'data-5',
+ text: 'Do you use standardized practice area codes, matter types, and document taxonomies firm-wide?',
+ category: 'taxonomy',
+ },
+ ],
+ },
+ {
+ id: 'knowledge',
+ name: 'Knowledge System',
+ description: 'Measures the organization, accessibility, and currency of institutional knowledge including templates, SOPs, and training materials.',
+ icon: 'BookOpen',
+ questions: [
+ {
+ id: 'knowledge-1',
+ text: 'Can staff easily locate the correct, approved template for any standard document type?',
+ category: 'templates',
+ },
+ {
+ id: 'knowledge-2',
+ text: 'Are standard operating procedures documented and accessible for key firm processes?',
+ category: 'sops',
+ },
+ {
+ id: 'knowledge-3',
+ text: 'How current are your practice area playbooks and procedural guides?',
+ category: 'playbooks',
+ },
+ {
+ id: 'knowledge-4',
+ text: 'Do staff have easy access to FAQs and quick-reference guides for common questions?',
+ category: 'faqs',
+ },
+ {
+ id: 'knowledge-5',
+ text: 'Is there a structured onboarding curriculum with documented training materials for new hires?',
+ category: 'training',
+ },
+ ],
+ },
+ {
+ id: 'tooling',
+ name: 'Tooling & Integrations',
+ description: 'Evaluates the adoption, integration, and effective use of legal technology across the firm.',
+ icon: 'Settings',
+ questions: [
+ {
+ id: 'tooling-1',
+ text: 'What percentage of firm workflows actively utilize your practice management system?',
+ category: 'practice-management',
+ },
+ {
+ id: 'tooling-2',
+ text: 'Is your document management system consistently used for all client-related documents?',
+ category: 'document-management',
+ },
+ {
+ id: 'tooling-3',
+ text: 'Are emails automatically or routinely captured and linked to relevant matters?',
+ category: 'email-integration',
+ },
+ {
+ id: 'tooling-4',
+ text: 'How integrated is your billing system with time tracking and matter management?',
+ category: 'billing',
+ },
+ {
+ id: 'tooling-5',
+ text: 'Do you currently use any automation tools (document assembly, workflow automation, e-signatures)?',
+ category: 'automation',
+ },
+ ],
+ },
+ {
+ id: 'risk',
+ name: 'Risk & Governance',
+ description: 'Examines security controls, compliance frameworks, and governance structures essential for responsible AI deployment.',
+ icon: 'Shield',
+ questions: [
+ {
+ id: 'risk-1',
+ text: 'Do you have role-based access controls limiting who can view sensitive client data?',
+ category: 'access-controls',
+ },
+ {
+ id: 'risk-2',
+ text: 'Has your firm established a formal policy governing the use of AI tools with client data?',
+ category: 'ai-policy',
+ },
+ {
+ id: 'risk-3',
+ text: 'Can you produce audit trails showing who accessed or modified matter information?',
+ category: 'audit-trails',
+ },
+ {
+ id: 'risk-4',
+ text: 'Are data retention and destruction policies documented and consistently enforced?',
+ category: 'retention',
+ },
+ {
+ id: 'risk-5',
+ text: 'Do all staff receive regular training on confidentiality obligations and data handling?',
+ category: 'confidentiality-training',
+ },
+ ],
+ },
+ {
+ id: 'change',
+ name: 'Change Capacity',
+ description: 'Assesses organizational readiness for change, including training infrastructure, leadership support, and staff adaptability.',
+ icon: 'TrendingUp',
+ questions: [
+ {
+ id: 'change-1',
+ text: 'Does your firm have dedicated resources or infrastructure for delivering technology training?',
+ category: 'training-infrastructure',
+ },
+ {
+ id: 'change-2',
+ text: 'How would you rate your firm\'s track record of successfully adopting new technology in the past 3 years?',
+ category: 'adoption-track-record',
+ },
+ {
+ id: 'change-3',
+ text: 'Is there clear ownership and accountability for technology initiatives at your firm?',
+ category: 'role-clarity',
+ },
+ {
+ id: 'change-4',
+ text: 'Do firm leadership actively champion and participate in technology adoption efforts?',
+ category: 'executive-sponsorship',
+ },
+ {
+ id: 'change-5',
+ text: 'How receptive is your staff generally to learning and using new tools and processes?',
+ category: 'staff-appetite',
+ },
+ ],
+ },
+];
+
+/**
+ * Mapping of pillar IDs to their icon names for easy lookup
+ */
+export const PILLAR_ICONS: Record = {
+ process: 'Workflow',
+ data: 'Database',
+ knowledge: 'BookOpen',
+ tooling: 'Settings',
+ risk: 'Shield',
+ change: 'TrendingUp',
+};
+
+/**
+ * Total number of questions across all pillars
+ */
+export const TOTAL_QUESTIONS = PILLAR_DEFINITIONS.reduce(
+ (sum, pillar) => sum + (pillar.questions?.length || 0),
+ 0
+);
+
+/**
+ * Helper to get a pillar definition by ID
+ * @param id - The pillar ID to look up
+ * @returns The pillar definition or undefined if not found
+ */
+export function getPillarById(id: string): ExtendedPillarDefinition | undefined {
+ return PILLAR_DEFINITIONS.find(pillar => pillar.id === id);
+}
diff --git a/ai-readiness-accelerator/src/constants/roadmap-templates.ts b/ai-readiness-accelerator/src/constants/roadmap-templates.ts
new file mode 100644
index 0000000..eafbe94
--- /dev/null
+++ b/ai-readiness-accelerator/src/constants/roadmap-templates.ts
@@ -0,0 +1,474 @@
+/**
+ * Roadmap task templates for the AI Readiness Accelerator
+ *
+ * Tasks are organized into 30/60/90 day phases and are conditionally
+ * included based on assessment scores. Lower scores in specific pillars
+ * trigger relevant remediation tasks.
+ */
+
+/**
+ * Pillar IDs for type safety
+ */
+export type PillarId = 'process' | 'data' | 'knowledge' | 'tooling' | 'risk' | 'change';
+
+/**
+ * Scores mapped by pillar ID
+ */
+export type PillarScores = Record;
+
+/**
+ * Task priority levels
+ */
+export type TaskPriority = 'critical' | 'high' | 'medium' | 'low';
+
+/**
+ * Roadmap task definition
+ */
+export interface RoadmapTask {
+ /** Unique task identifier */
+ id: string;
+ /** Task title */
+ title: string;
+ /** Detailed task description */
+ description: string;
+ /** Responsible party or role */
+ owner: string;
+ /** Task priority level */
+ priority: TaskPriority;
+ /** Function to determine if task applies based on scores */
+ applicableWhen: (scores: PillarScores) => boolean;
+ /** Related pillar IDs for grouping and filtering */
+ relatedPillars: PillarId[];
+}
+
+// ============================================================================
+// Phase 1: First 30 Days - Foundation & Quick Wins
+// ============================================================================
+
+/**
+ * Tasks for the first 30 days of the AI readiness roadmap.
+ * Focus: Critical gaps, governance basics, and quick wins.
+ */
+export const PHASE_30_TASKS: RoadmapTask[] = [
+ // Risk & Governance Tasks
+ {
+ id: '30-risk-policy',
+ title: 'Establish AI Usage Policy',
+ description: 'Draft and implement a firm-wide policy governing the use of AI tools with client data, including approved tools, prohibited uses, and review requirements.',
+ owner: 'Managing Partner / IT Director',
+ priority: 'critical',
+ applicableWhen: (scores) => scores.risk < 3,
+ relatedPillars: ['risk'],
+ },
+ {
+ id: '30-risk-access',
+ title: 'Audit Data Access Controls',
+ description: 'Review and document current access permissions across all systems. Identify and remediate inappropriate access to sensitive client data.',
+ owner: 'IT Director',
+ priority: 'critical',
+ applicableWhen: (scores) => scores.risk < 2.5,
+ relatedPillars: ['risk'],
+ },
+ {
+ id: '30-risk-training',
+ title: 'Schedule Confidentiality Refresher Training',
+ description: 'Organize mandatory training sessions on data handling, confidentiality obligations, and the new AI usage policy for all staff.',
+ owner: 'HR / Compliance',
+ priority: 'high',
+ applicableWhen: (scores) => scores.risk < 3.5,
+ relatedPillars: ['risk', 'change'],
+ },
+
+ // Data Quality Tasks
+ {
+ id: '30-data-audit',
+ title: 'Conduct Data Quality Assessment',
+ description: 'Sample 50 recent matters to assess field completion rates, naming convention adherence, and data accuracy. Document baseline metrics.',
+ owner: 'Operations Manager',
+ priority: 'high',
+ applicableWhen: (scores) => scores.data < 3,
+ relatedPillars: ['data'],
+ },
+ {
+ id: '30-data-duplicates',
+ title: 'Identify and Merge Duplicate Records',
+ description: 'Run duplicate detection reports on contacts and matters. Establish a process for merging duplicates and preventing future occurrences.',
+ owner: 'Database Administrator',
+ priority: 'high',
+ applicableWhen: (scores) => scores.data < 2.5,
+ relatedPillars: ['data'],
+ },
+ {
+ id: '30-data-naming',
+ title: 'Document Naming Convention Standards',
+ description: 'Create and distribute a firm-wide naming convention guide for documents, matters, and folders. Include examples for each practice area.',
+ owner: 'Knowledge Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.data < 3.5,
+ relatedPillars: ['data', 'knowledge'],
+ },
+
+ // Process Tasks
+ {
+ id: '30-process-intake',
+ title: 'Standardize Client Intake Form',
+ description: 'Create a unified intake form capturing all required information across practice areas. Implement in practice management system.',
+ owner: 'Practice Group Leaders',
+ priority: 'high',
+ applicableWhen: (scores) => scores.process < 3,
+ relatedPillars: ['process'],
+ },
+ {
+ id: '30-process-checklist',
+ title: 'Create Matter Opening Checklist',
+ description: 'Document required steps for matter setup including conflict checks, engagement letters, and system setup. Make accessible to all staff.',
+ owner: 'Operations Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.process < 3.5,
+ relatedPillars: ['process', 'knowledge'],
+ },
+
+ // Change Capacity Tasks
+ {
+ id: '30-change-champion',
+ title: 'Identify AI Champions',
+ description: 'Select 2-3 tech-savvy individuals from different practice areas to serve as AI adoption champions and early feedback providers.',
+ owner: 'Managing Partner',
+ priority: 'high',
+ applicableWhen: (scores) => scores.change < 3.5,
+ relatedPillars: ['change'],
+ },
+ {
+ id: '30-change-sponsor',
+ title: 'Secure Executive Sponsorship',
+ description: 'Brief firm leadership on AI initiative and obtain formal commitment to champion adoption, allocate resources, and model desired behaviors.',
+ owner: 'Project Lead',
+ priority: 'critical',
+ applicableWhen: (scores) => scores.change < 3,
+ relatedPillars: ['change'],
+ },
+];
+
+// ============================================================================
+// Phase 2: Days 31-60 - Building Infrastructure
+// ============================================================================
+
+/**
+ * Tasks for days 31-60 of the AI readiness roadmap.
+ * Focus: Systems integration, knowledge organization, and process documentation.
+ */
+export const PHASE_60_TASKS: RoadmapTask[] = [
+ // Tooling Tasks
+ {
+ id: '60-tooling-audit',
+ title: 'Complete Technology Stack Audit',
+ description: 'Document all current tools, their usage rates, integration points, and gaps. Identify redundancies and consolidation opportunities.',
+ owner: 'IT Director',
+ priority: 'high',
+ applicableWhen: (scores) => scores.tooling < 3.5,
+ relatedPillars: ['tooling'],
+ },
+ {
+ id: '60-tooling-email',
+ title: 'Implement Email-to-Matter Linking',
+ description: 'Configure automatic or one-click email capture to relevant matters. Train staff on proper email filing procedures.',
+ owner: 'IT Director',
+ priority: 'high',
+ applicableWhen: (scores) => scores.tooling < 3,
+ relatedPillars: ['tooling', 'data'],
+ },
+ {
+ id: '60-tooling-dms',
+ title: 'Enforce Document Management Compliance',
+ description: 'Establish policy requiring all client documents be saved to DMS. Configure desktop save locations and provide refresher training.',
+ owner: 'Knowledge Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.tooling < 3,
+ relatedPillars: ['tooling', 'data'],
+ },
+
+ // Knowledge System Tasks
+ {
+ id: '60-knowledge-templates',
+ title: 'Organize and Update Template Library',
+ description: 'Audit all document templates for currency and accuracy. Organize into logical structure with clear naming. Remove outdated versions.',
+ owner: 'Practice Group Leaders',
+ priority: 'high',
+ applicableWhen: (scores) => scores.knowledge < 3.5,
+ relatedPillars: ['knowledge'],
+ },
+ {
+ id: '60-knowledge-sops',
+ title: 'Document Critical Process SOPs',
+ description: 'Write standard operating procedures for top 10 most common firm processes. Format consistently and store in accessible location.',
+ owner: 'Operations Manager',
+ priority: 'high',
+ applicableWhen: (scores) => scores.knowledge < 3,
+ relatedPillars: ['knowledge', 'process'],
+ },
+ {
+ id: '60-knowledge-faq',
+ title: 'Create Staff FAQ Resource',
+ description: 'Compile answers to frequently asked questions about firm procedures, systems, and policies. Make searchable and easily accessible.',
+ owner: 'Knowledge Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.knowledge < 3,
+ relatedPillars: ['knowledge'],
+ },
+
+ // Process Tasks
+ {
+ id: '60-process-workflow',
+ title: 'Map Key Legal Workflows',
+ description: 'Document step-by-step workflows for common matter types including decision points, templates used, and handoffs between team members.',
+ owner: 'Practice Group Leaders',
+ priority: 'high',
+ applicableWhen: (scores) => scores.process < 3,
+ relatedPillars: ['process', 'knowledge'],
+ },
+ {
+ id: '60-process-filing',
+ title: 'Standardize Filing Procedures',
+ description: 'Create checklists and procedures for court filings including deadline tracking, service requirements, and confirmation processes.',
+ owner: 'Paralegal Supervisor',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.process < 3.5,
+ relatedPillars: ['process'],
+ },
+
+ // Data Tasks
+ {
+ id: '60-data-cleanup',
+ title: 'Execute Data Cleanup Sprint',
+ description: 'Dedicate resources to clean up historical data based on audit findings. Focus on active matters and high-value closed matters.',
+ owner: 'Database Administrator',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.data < 2.5,
+ relatedPillars: ['data'],
+ },
+ {
+ id: '60-data-validation',
+ title: 'Implement Data Validation Rules',
+ description: 'Configure practice management system to require critical fields and enforce formatting standards on new matter creation.',
+ owner: 'IT Director',
+ priority: 'high',
+ applicableWhen: (scores) => scores.data < 3,
+ relatedPillars: ['data', 'tooling'],
+ },
+
+ // Risk Tasks
+ {
+ id: '60-risk-audit-trail',
+ title: 'Enable Comprehensive Audit Logging',
+ description: 'Configure systems to log all access and modifications to client data. Establish review procedures and retention policies.',
+ owner: 'IT Director',
+ priority: 'high',
+ applicableWhen: (scores) => scores.risk < 3,
+ relatedPillars: ['risk', 'tooling'],
+ },
+];
+
+// ============================================================================
+// Phase 3: Days 61-90 - Optimization & Pilot Readiness
+// ============================================================================
+
+/**
+ * Tasks for days 61-90 of the AI readiness roadmap.
+ * Focus: Optimization, training infrastructure, and pilot preparation.
+ */
+export const PHASE_90_TASKS: RoadmapTask[] = [
+ // Change Capacity Tasks
+ {
+ id: '90-change-training',
+ title: 'Establish Training Program Infrastructure',
+ description: 'Set up learning management system or structured training program. Create schedule for ongoing technology training sessions.',
+ owner: 'HR / IT Director',
+ priority: 'high',
+ applicableWhen: (scores) => scores.change < 3.5,
+ relatedPillars: ['change'],
+ },
+ {
+ id: '90-change-metrics',
+ title: 'Define Adoption Success Metrics',
+ description: 'Establish KPIs for measuring technology adoption success including usage rates, efficiency gains, and user satisfaction.',
+ owner: 'Operations Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.change < 4,
+ relatedPillars: ['change'],
+ },
+ {
+ id: '90-change-feedback',
+ title: 'Implement Feedback Collection Process',
+ description: 'Create channels for staff to provide ongoing feedback on tools and processes. Establish regular review cadence.',
+ owner: 'Project Lead',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.change < 3.5,
+ relatedPillars: ['change'],
+ },
+
+ // Tooling Tasks
+ {
+ id: '90-tooling-automation',
+ title: 'Identify Automation Opportunities',
+ description: 'Based on documented workflows, identify 3-5 processes suitable for automation. Prioritize by impact and implementation complexity.',
+ owner: 'Operations Manager',
+ priority: 'high',
+ applicableWhen: (scores) => scores.tooling < 4 && scores.process >= 2.5,
+ relatedPillars: ['tooling', 'process'],
+ },
+ {
+ id: '90-tooling-pilot',
+ title: 'Select AI Pilot Use Case',
+ description: 'Choose initial AI tool pilot based on readiness assessment. Select use case with clear success criteria and limited risk.',
+ owner: 'Project Lead',
+ priority: 'high',
+ applicableWhen: (scores) => scores.risk >= 2.5 && scores.data >= 2.5,
+ relatedPillars: ['tooling', 'risk'],
+ },
+ {
+ id: '90-tooling-integration',
+ title: 'Plan System Integration Requirements',
+ description: 'Document integration requirements for potential AI tools including data flows, authentication, and API capabilities.',
+ owner: 'IT Director',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.tooling < 3.5,
+ relatedPillars: ['tooling'],
+ },
+
+ // Knowledge Tasks
+ {
+ id: '90-knowledge-playbooks',
+ title: 'Develop Practice Area Playbooks',
+ description: 'Create comprehensive guides for each major practice area including workflows, templates, deadlines, and best practices.',
+ owner: 'Practice Group Leaders',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.knowledge < 3.5 && scores.process >= 2.5,
+ relatedPillars: ['knowledge', 'process'],
+ },
+ {
+ id: '90-knowledge-onboarding',
+ title: 'Revamp New Hire Onboarding',
+ description: 'Create structured onboarding program incorporating documented SOPs, system training, and mentorship assignments.',
+ owner: 'HR',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.knowledge < 3 || scores.change < 3,
+ relatedPillars: ['knowledge', 'change'],
+ },
+
+ // Process Tasks
+ {
+ id: '90-process-metrics',
+ title: 'Establish Process Efficiency Baselines',
+ description: 'Measure current time and effort for key processes to establish baselines for measuring future AI-driven improvements.',
+ owner: 'Operations Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.process >= 2.5,
+ relatedPillars: ['process'],
+ },
+ {
+ id: '90-process-governance',
+ title: 'Create Process Governance Committee',
+ description: 'Establish cross-functional team responsible for process standardization, documentation maintenance, and continuous improvement.',
+ owner: 'Managing Partner',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.process < 3.5,
+ relatedPillars: ['process', 'change'],
+ },
+
+ // Risk Tasks
+ {
+ id: '90-risk-retention',
+ title: 'Formalize Data Retention Policy',
+ description: 'Document and implement data retention and destruction policies aligned with ethical obligations and business needs.',
+ owner: 'Compliance Officer',
+ priority: 'high',
+ applicableWhen: (scores) => scores.risk < 3.5,
+ relatedPillars: ['risk'],
+ },
+ {
+ id: '90-risk-vendor',
+ title: 'Develop AI Vendor Evaluation Criteria',
+ description: 'Create checklist for evaluating AI vendors including security certifications, data handling practices, and compliance capabilities.',
+ owner: 'IT Director / Compliance',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.risk >= 2.5 && scores.tooling >= 2.5,
+ relatedPillars: ['risk', 'tooling'],
+ },
+
+ // Data Tasks
+ {
+ id: '90-data-governance',
+ title: 'Establish Data Governance Framework',
+ description: 'Define data ownership, quality standards, and stewardship responsibilities. Create ongoing data quality monitoring process.',
+ owner: 'Operations Manager',
+ priority: 'high',
+ applicableWhen: (scores) => scores.data < 3.5,
+ relatedPillars: ['data'],
+ },
+ {
+ id: '90-data-taxonomy',
+ title: 'Standardize Firm-Wide Taxonomy',
+ description: 'Implement consistent practice area codes, matter types, and document categories across all systems.',
+ owner: 'Knowledge Manager',
+ priority: 'medium',
+ applicableWhen: (scores) => scores.data < 3,
+ relatedPillars: ['data', 'knowledge'],
+ },
+];
+
+/**
+ * Get all tasks for a specific phase
+ * @param phase - Phase number (30, 60, or 90)
+ * @returns Array of tasks for that phase
+ */
+export function getTasksByPhase(phase: 30 | 60 | 90): RoadmapTask[] {
+ switch (phase) {
+ case 30:
+ return PHASE_30_TASKS;
+ case 60:
+ return PHASE_60_TASKS;
+ case 90:
+ return PHASE_90_TASKS;
+ default:
+ return [];
+ }
+}
+
+/**
+ * Get all applicable tasks for given scores across all phases
+ * @param scores - The pillar scores from assessment
+ * @returns Object with tasks grouped by phase
+ */
+export function getApplicableTasks(scores: PillarScores): {
+ phase30: RoadmapTask[];
+ phase60: RoadmapTask[];
+ phase90: RoadmapTask[];
+} {
+ return {
+ phase30: PHASE_30_TASKS.filter(task => task.applicableWhen(scores)),
+ phase60: PHASE_60_TASKS.filter(task => task.applicableWhen(scores)),
+ phase90: PHASE_90_TASKS.filter(task => task.applicableWhen(scores)),
+ };
+}
+
+/**
+ * Get tasks filtered by pillar
+ * @param pillarId - The pillar to filter by
+ * @param scores - Optional scores to filter by applicability
+ * @returns Tasks related to the specified pillar
+ */
+export function getTasksByPillar(
+ pillarId: PillarId,
+ scores?: PillarScores
+): RoadmapTask[] {
+ const allTasks = [...PHASE_30_TASKS, ...PHASE_60_TASKS, ...PHASE_90_TASKS];
+ const pillarTasks = allTasks.filter(task =>
+ task.relatedPillars.includes(pillarId)
+ );
+
+ if (scores) {
+ return pillarTasks.filter(task => task.applicableWhen(scores));
+ }
+
+ return pillarTasks;
+}
diff --git a/ai-readiness-accelerator/src/constants/scoring.ts b/ai-readiness-accelerator/src/constants/scoring.ts
new file mode 100644
index 0000000..1fe9a8d
--- /dev/null
+++ b/ai-readiness-accelerator/src/constants/scoring.ts
@@ -0,0 +1,203 @@
+/**
+ * Scoring configuration for the AI Readiness Accelerator
+ *
+ * Defines thresholds, levels, and visual styling for assessment scoring.
+ * All scores use a 1-5 scale where 1 is lowest and 5 is highest.
+ */
+
+import type { PillarStatus, ReadinessLevel } from '../types';
+
+/**
+ * Score threshold definition for pillar status classification
+ */
+export interface ScoreThreshold {
+ /** Status classification */
+ status: PillarStatus;
+ /** Minimum score (inclusive) */
+ min: number;
+ /** Maximum score (exclusive, except for last tier which is inclusive) */
+ max: number;
+ /** Human-readable label for this threshold */
+ label: string;
+}
+
+/**
+ * Score thresholds for classifying individual pillar performance.
+ * Thresholds are evaluated in order; first matching range wins.
+ */
+export const SCORE_THRESHOLDS: ScoreThreshold[] = [
+ {
+ status: 'critical',
+ min: 0,
+ max: 1.5,
+ label: 'Critical',
+ },
+ {
+ status: 'needs-work',
+ min: 1.5,
+ max: 2.5,
+ label: 'Needs Work',
+ },
+ {
+ status: 'adequate',
+ min: 2.5,
+ max: 3.5,
+ label: 'Adequate',
+ },
+ {
+ status: 'strong',
+ min: 3.5,
+ max: 4.5,
+ label: 'Strong',
+ },
+ {
+ status: 'excellent',
+ min: 4.5,
+ max: 5,
+ label: 'Excellent',
+ },
+];
+
+/**
+ * Extended readiness level definition with metadata
+ */
+export interface ReadinessLevelDefinition {
+ /** Numeric level (1-5) */
+ level: number;
+ /** Internal identifier matching ReadinessLevel type */
+ id: ReadinessLevel;
+ /** Display label */
+ label: string;
+ /** Score range as [min, max] tuple (inclusive) */
+ scoreRange: [number, number];
+ /** Description of what this level means for the organization */
+ description: string;
+ /** Tailwind CSS color class for visual styling */
+ color: string;
+}
+
+/**
+ * Overall readiness level definitions.
+ * These classify the firm's aggregate readiness for AI adoption.
+ */
+export const READINESS_LEVELS: ReadinessLevelDefinition[] = [
+ {
+ level: 1,
+ id: 'not-ready',
+ label: 'Not Ready',
+ scoreRange: [1, 1.5],
+ description: 'Significant foundational work needed before AI adoption. Focus on basic process documentation and data cleanup.',
+ color: 'text-red-600 bg-red-50',
+ },
+ {
+ level: 2,
+ id: 'foundational',
+ label: 'Foundational',
+ scoreRange: [1.5, 2.5],
+ description: 'Early stage readiness with gaps in multiple areas. Prioritize addressing critical weaknesses before piloting AI tools.',
+ color: 'text-orange-600 bg-orange-50',
+ },
+ {
+ level: 3,
+ id: 'developing',
+ label: 'Developing',
+ scoreRange: [2.5, 3.5],
+ description: 'Moderate readiness with room for improvement. Good candidate for targeted AI pilots in stronger areas.',
+ color: 'text-yellow-600 bg-yellow-50',
+ },
+ {
+ level: 4,
+ id: 'ready',
+ label: 'Ready',
+ scoreRange: [3.5, 4.5],
+ description: 'Well-positioned for AI adoption. Solid foundations in place; focus on optimization and strategic deployment.',
+ color: 'text-green-600 bg-green-50',
+ },
+ {
+ level: 5,
+ id: 'optimized',
+ label: 'Optimized',
+ scoreRange: [4.5, 5],
+ description: 'Excellent readiness across all dimensions. Ideal candidate for advanced AI implementations and innovation.',
+ color: 'text-blue-600 bg-blue-50',
+ },
+];
+
+/**
+ * Tailwind CSS color classes mapped to pillar status.
+ * Use these for consistent status visualization across the application.
+ */
+export const STATUS_COLORS: Record = {
+ critical: {
+ text: 'text-red-700',
+ bg: 'bg-red-100',
+ border: 'border-red-300',
+ badge: 'bg-red-600 text-white',
+ },
+ 'needs-work': {
+ text: 'text-orange-700',
+ bg: 'bg-orange-100',
+ border: 'border-orange-300',
+ badge: 'bg-orange-500 text-white',
+ },
+ adequate: {
+ text: 'text-yellow-700',
+ bg: 'bg-yellow-100',
+ border: 'border-yellow-300',
+ badge: 'bg-yellow-500 text-white',
+ },
+ strong: {
+ text: 'text-green-700',
+ bg: 'bg-green-100',
+ border: 'border-green-300',
+ badge: 'bg-green-600 text-white',
+ },
+ excellent: {
+ text: 'text-blue-700',
+ bg: 'bg-blue-100',
+ border: 'border-blue-300',
+ badge: 'bg-blue-600 text-white',
+ },
+};
+
+/**
+ * Get the status for a given score
+ * @param score - The numeric score to classify (1-5 scale)
+ * @returns The PillarStatus corresponding to the score
+ */
+export function getStatusForScore(score: number): PillarStatus {
+ for (const threshold of SCORE_THRESHOLDS) {
+ if (score >= threshold.min && score < threshold.max) {
+ return threshold.status;
+ }
+ }
+ // Handle edge case of exactly 5
+ if (score >= 4.5) {
+ return 'excellent';
+ }
+ return 'critical';
+}
+
+/**
+ * Get the readiness level for a given overall score
+ * @param score - The overall assessment score (1-5 scale)
+ * @returns The ReadinessLevelDefinition for the score
+ */
+export function getReadinessLevel(score: number): ReadinessLevelDefinition {
+ for (const level of READINESS_LEVELS) {
+ if (score >= level.scoreRange[0] && score <= level.scoreRange[1]) {
+ return level;
+ }
+ }
+ // Default to lowest level if somehow out of range
+ return READINESS_LEVELS[0];
+}
+
+/**
+ * Get display colors for a given status
+ * @param status - The pillar status
+ * @returns Object containing Tailwind color classes
+ */
+export function getStatusColors(status: PillarStatus): typeof STATUS_COLORS[PillarStatus] {
+ return STATUS_COLORS[status];
+}
diff --git a/ai-readiness-accelerator/src/hooks/index.ts b/ai-readiness-accelerator/src/hooks/index.ts
new file mode 100644
index 0000000..1adee10
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/index.ts
@@ -0,0 +1,39 @@
+/**
+ * Custom React hooks for the AI Readiness Accelerator application.
+ *
+ * These hooks provide state management, persistence, and data computation
+ * functionality for the assessment workflow.
+ *
+ * @module hooks
+ */
+
+// useLocalStorage - Generic localStorage persistence hook
+export { useLocalStorage } from './useLocalStorage';
+export type { } from './useLocalStorage'; // No additional types exported
+
+// useAssessment - Assessment workflow state management
+export { useAssessment } from './useAssessment';
+export type {
+ FullAssessmentState,
+ AssessmentActions,
+ AssessmentComputed,
+ UseAssessmentReturn,
+} from './useAssessment';
+
+// useResults - Assessment results computation
+export { useResults } from './useResults';
+export type {
+ UseResultsProps,
+ UseResultsReturn,
+} from './useResults';
+
+// useROICalculator - ROI calculator state and projections
+export { useROICalculator } from './useROICalculator';
+export type { UseROICalculatorReturn } from './useROICalculator';
+
+// useGeminiSummary - AI-generated executive summaries
+export { useGeminiSummary } from './useGeminiSummary';
+export type {
+ UseGeminiSummaryProps,
+ UseGeminiSummaryReturn,
+} from './useGeminiSummary';
diff --git a/ai-readiness-accelerator/src/hooks/useAssessment.ts b/ai-readiness-accelerator/src/hooks/useAssessment.ts
new file mode 100644
index 0000000..f87ccad
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/useAssessment.ts
@@ -0,0 +1,441 @@
+/**
+ * Hook for managing assessment state including navigation, responses, and persistence.
+ *
+ * Features:
+ * - Tracks current step, responses, firm profile, and completion status
+ * - Auto-saves to localStorage with debouncing
+ * - Restores state from localStorage on mount
+ * - Provides computed values for progress and navigation
+ *
+ * @module useAssessment
+ */
+
+import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
+import type {
+ AssessmentState,
+ AssessmentResponses,
+ LikertValue,
+ PillarId,
+} from '../types/assessment.types';
+import {
+ createInitialAssessmentState,
+ PILLAR_ORDER,
+} from '../types/assessment.types';
+import type { FirmProfile } from '../types/firm.types';
+import { useLocalStorage } from './useLocalStorage';
+import { PILLAR_DEFINITIONS } from '../constants/pillars';
+
+/** LocalStorage key for assessment state */
+const ASSESSMENT_STORAGE_KEY = 'ai-readiness-assessment';
+
+/** LocalStorage key for firm profile */
+const FIRM_PROFILE_STORAGE_KEY = 'ai-readiness-firm-profile';
+
+/** Debounce delay for auto-save in milliseconds */
+const AUTO_SAVE_DEBOUNCE_MS = 500;
+
+/** Total number of steps: 0 = firm profile, 1-6 = pillars, 7 = review */
+const TOTAL_STEPS = 8;
+
+/**
+ * Assessment state that combines both assessment responses and firm profile
+ */
+export interface FullAssessmentState extends AssessmentState {
+ firmProfile: FirmProfile | null;
+}
+
+/**
+ * Actions available for managing assessment state
+ */
+export interface AssessmentActions {
+ /** Set or update the firm profile */
+ setFirmProfile: (profile: FirmProfile) => void;
+ /** Set a response for a specific question */
+ setResponse: (pillarId: PillarId, questionId: string, value: LikertValue) => void;
+ /** Navigate to the next step */
+ nextStep: () => void;
+ /** Navigate to the previous step */
+ prevStep: () => void;
+ /** Navigate to a specific step */
+ goToStep: (step: number) => void;
+ /** Mark the assessment as complete */
+ complete: () => void;
+ /** Reset the entire assessment to initial state */
+ reset: () => void;
+}
+
+/**
+ * Computed values derived from assessment state
+ */
+export interface AssessmentComputed {
+ /** Overall progress as a percentage (0-100) */
+ progress: number;
+ /** Whether navigation to the next step is allowed */
+ canGoNext: boolean;
+ /** Whether navigation to the previous step is allowed */
+ canGoPrev: boolean;
+ /** Total number of questions answered */
+ totalAnswered: number;
+ /** Total number of questions in the assessment */
+ totalQuestions: number;
+ /** Whether the current step has all required responses */
+ currentStepComplete: boolean;
+}
+
+/**
+ * Return type for the useAssessment hook
+ */
+export interface UseAssessmentReturn {
+ /** Current assessment state including firm profile */
+ state: FullAssessmentState;
+ /** Actions for manipulating assessment state */
+ actions: AssessmentActions;
+ /** Computed values derived from state */
+ computed: AssessmentComputed;
+}
+
+
+/**
+ * Validates that a firm profile has the minimum required fields
+ */
+function isFirmProfileValid(profile: FirmProfile | null): boolean {
+ if (!profile) return false;
+ return (
+ profile.firmName.trim().length > 0 &&
+ profile.practiceAreas.length > 0 &&
+ profile.attorneyCount >= 0
+ );
+}
+
+/**
+ * Gets the pillar ID for a given step number
+ */
+function getPillarForStep(step: number): PillarId | null {
+ if (step >= 1 && step <= 6) {
+ return PILLAR_ORDER[step - 1];
+ }
+ return null;
+}
+
+/**
+ * Counts the number of questions answered for a specific pillar
+ */
+function countPillarResponses(
+ responses: AssessmentResponses,
+ pillarId: PillarId
+): number {
+ return responses[pillarId]?.length || 0;
+}
+
+/**
+ * Gets the total number of questions for a specific pillar
+ */
+function getPillarQuestionCount(pillarId: PillarId): number {
+ const pillar = PILLAR_DEFINITIONS.find((p) => p.id === pillarId);
+ return pillar?.questions?.length || 0;
+}
+
+/**
+ * Hook for managing the entire assessment workflow.
+ *
+ * @returns Object containing state, actions, and computed values
+ *
+ * @example
+ * ```tsx
+ * function AssessmentPage() {
+ * const { state, actions, computed } = useAssessment();
+ *
+ * if (state.currentStep === 0) {
+ * return (
+ *
+ * );
+ * }
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export function useAssessment(): UseAssessmentReturn {
+ // Load persisted state from localStorage
+ const [persistedState, setPersistedState, removePersistedState] =
+ useLocalStorage(
+ ASSESSMENT_STORAGE_KEY,
+ createInitialAssessmentState()
+ );
+
+ const [persistedProfile, setPersistedProfile, removePersistedProfile] =
+ useLocalStorage(FIRM_PROFILE_STORAGE_KEY, null);
+
+ // Internal state that mirrors persisted state but allows immediate updates
+ const [assessmentState, setAssessmentState] =
+ useState(persistedState);
+ const [firmProfile, setFirmProfileState] = useState(
+ persistedProfile
+ );
+
+ // Ref to track pending auto-save
+ const saveTimeoutRef = useRef | null>(null);
+
+ // Sync persisted state changes to internal state on mount
+ useEffect(() => {
+ setAssessmentState(persistedState);
+ }, [persistedState]);
+
+ useEffect(() => {
+ setFirmProfileState(persistedProfile);
+ }, [persistedProfile]);
+
+ /**
+ * Debounced auto-save function
+ */
+ const scheduleAutoSave = useCallback(
+ (newState: AssessmentState, newProfile: FirmProfile | null) => {
+ if (saveTimeoutRef.current) {
+ clearTimeout(saveTimeoutRef.current);
+ }
+
+ saveTimeoutRef.current = setTimeout(() => {
+ setPersistedState(newState);
+ setPersistedProfile(newProfile);
+ saveTimeoutRef.current = null;
+ }, AUTO_SAVE_DEBOUNCE_MS);
+ },
+ [setPersistedState, setPersistedProfile]
+ );
+
+ // Cleanup timeout on unmount
+ useEffect(() => {
+ return () => {
+ if (saveTimeoutRef.current) {
+ clearTimeout(saveTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ /**
+ * Set or update the firm profile
+ */
+ const setFirmProfile = useCallback(
+ (profile: FirmProfile) => {
+ setFirmProfileState(profile);
+ scheduleAutoSave(assessmentState, profile);
+ },
+ [assessmentState, scheduleAutoSave]
+ );
+
+ /**
+ * Set a response for a specific question
+ */
+ const setResponse = useCallback(
+ (pillarId: PillarId, questionId: string, value: LikertValue) => {
+ setAssessmentState((prev: AssessmentState) => {
+ const pillarResponses = [...(prev.responses[pillarId] || [])];
+ const existingIndex = pillarResponses.findIndex(
+ (r) => r.questionId === questionId
+ );
+
+ if (existingIndex >= 0) {
+ pillarResponses[existingIndex] = { questionId, value };
+ } else {
+ pillarResponses.push({ questionId, value });
+ }
+
+ const newResponses: AssessmentResponses = {
+ ...prev.responses,
+ [pillarId]: pillarResponses,
+ };
+
+ const newState: AssessmentState = {
+ ...prev,
+ responses: newResponses,
+ };
+
+ scheduleAutoSave(newState, firmProfile);
+ return newState;
+ });
+ },
+ [firmProfile, scheduleAutoSave]
+ );
+
+ /**
+ * Navigate to the next step
+ */
+ const nextStep = useCallback(() => {
+ setAssessmentState((prev: AssessmentState) => {
+ if (prev.currentStep >= TOTAL_STEPS - 1) {
+ return prev;
+ }
+
+ const newState: AssessmentState = {
+ ...prev,
+ currentStep: prev.currentStep + 1,
+ };
+
+ scheduleAutoSave(newState, firmProfile);
+ return newState;
+ });
+ }, [firmProfile, scheduleAutoSave]);
+
+ /**
+ * Navigate to the previous step
+ */
+ const prevStep = useCallback(() => {
+ setAssessmentState((prev: AssessmentState) => {
+ if (prev.currentStep <= 0) {
+ return prev;
+ }
+
+ const newState: AssessmentState = {
+ ...prev,
+ currentStep: prev.currentStep - 1,
+ };
+
+ scheduleAutoSave(newState, firmProfile);
+ return newState;
+ });
+ }, [firmProfile, scheduleAutoSave]);
+
+ /**
+ * Navigate to a specific step
+ */
+ const goToStep = useCallback(
+ (step: number) => {
+ if (step < 0 || step >= TOTAL_STEPS) {
+ return;
+ }
+
+ setAssessmentState((prev: AssessmentState) => {
+ const newState: AssessmentState = {
+ ...prev,
+ currentStep: step,
+ };
+
+ scheduleAutoSave(newState, firmProfile);
+ return newState;
+ });
+ },
+ [firmProfile, scheduleAutoSave]
+ );
+
+ /**
+ * Mark the assessment as complete
+ */
+ const complete = useCallback(() => {
+ setAssessmentState((prev: AssessmentState) => {
+ const newState: AssessmentState = {
+ ...prev,
+ isComplete: true,
+ completedAt: new Date().toISOString(),
+ };
+
+ // Immediately persist completion
+ setPersistedState(newState);
+ return newState;
+ });
+ }, [setPersistedState]);
+
+ /**
+ * Reset the entire assessment to initial state
+ */
+ const reset = useCallback(() => {
+ const newState = createInitialAssessmentState();
+ setAssessmentState(newState);
+ setFirmProfileState(null);
+ removePersistedState();
+ removePersistedProfile();
+ }, [removePersistedState, removePersistedProfile]);
+
+ /**
+ * Computed values
+ */
+ const computed = useMemo(() => {
+ // Calculate total questions and answered
+ let totalQuestions = 0;
+ let totalAnswered = 0;
+
+ for (const pillarId of PILLAR_ORDER) {
+ const questionCount = getPillarQuestionCount(pillarId);
+ const answeredCount = countPillarResponses(assessmentState.responses, pillarId);
+ totalQuestions += questionCount;
+ totalAnswered += answeredCount;
+ }
+
+ // Calculate progress (firm profile counts as one "pillar" worth)
+ const firmProfileComplete = isFirmProfileValid(firmProfile);
+ const firmProfileWeight = firmProfileComplete ? 1 : 0;
+ const totalWeight = 7; // 1 for firm profile + 6 pillars
+ const pillarCompletionWeight =
+ totalQuestions > 0 ? (totalAnswered / totalQuestions) * 6 : 0;
+ const progress = Math.round(
+ ((firmProfileWeight + pillarCompletionWeight) / totalWeight) * 100
+ );
+
+ // Check if current step is complete
+ let currentStepComplete = false;
+ if (assessmentState.currentStep === 0) {
+ currentStepComplete = firmProfileComplete;
+ } else if (assessmentState.currentStep === 7) {
+ // Review step is always "complete"
+ currentStepComplete = true;
+ } else {
+ const pillarId = getPillarForStep(assessmentState.currentStep);
+ if (pillarId) {
+ const required = getPillarQuestionCount(pillarId);
+ const answered = countPillarResponses(assessmentState.responses, pillarId);
+ currentStepComplete = answered >= required;
+ }
+ }
+
+ // Navigation checks
+ const canGoPrev = assessmentState.currentStep > 0;
+ const canGoNext =
+ assessmentState.currentStep < TOTAL_STEPS - 1 && currentStepComplete;
+
+ return {
+ progress,
+ canGoNext,
+ canGoPrev,
+ totalAnswered,
+ totalQuestions,
+ currentStepComplete,
+ };
+ }, [assessmentState, firmProfile]);
+
+ // Assemble full state
+ const state: FullAssessmentState = useMemo(
+ () => ({
+ ...assessmentState,
+ firmProfile,
+ }),
+ [assessmentState, firmProfile]
+ );
+
+ // Assemble actions
+ const actions: AssessmentActions = useMemo(
+ () => ({
+ setFirmProfile,
+ setResponse,
+ nextStep,
+ prevStep,
+ goToStep,
+ complete,
+ reset,
+ }),
+ [setFirmProfile, setResponse, nextStep, prevStep, goToStep, complete, reset]
+ );
+
+ return { state, actions, computed };
+}
+
+export default useAssessment;
diff --git a/ai-readiness-accelerator/src/hooks/useGeminiSummary.ts b/ai-readiness-accelerator/src/hooks/useGeminiSummary.ts
new file mode 100644
index 0000000..043348a
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/useGeminiSummary.ts
@@ -0,0 +1,280 @@
+/**
+ * Hook for fetching and managing AI-generated executive summaries.
+ *
+ * Features:
+ * - Lazy loading with enabled flag
+ * - Loading, error, and success states
+ * - Request cancellation on cleanup
+ * - Manual regenerate functionality
+ *
+ * @module useGeminiSummary
+ */
+
+import { useState, useEffect, useCallback, useRef } from 'react';
+import type { PillarScores, ExecutiveSummary } from '../types';
+import type { FirmProfile } from '../types/firm.types';
+import { generateExecutiveSummary } from '../services/gemini.service';
+
+/**
+ * Props for the useGeminiSummary hook
+ */
+export interface UseGeminiSummaryProps {
+ /** Pillar scores from the assessment */
+ scores: PillarScores;
+ /** Firm profile for context */
+ firmProfile: FirmProfile | null;
+ /** Whether to enable fetching (useful for delaying until needed) */
+ enabled?: boolean;
+}
+
+/**
+ * Return type for the useGeminiSummary hook
+ */
+export interface UseGeminiSummaryReturn {
+ /** The generated executive summary (null if not yet loaded) */
+ summary: ExecutiveSummary | null;
+ /** Whether a request is currently in progress */
+ loading: boolean;
+ /** Error message if the request failed */
+ error: string | null;
+ /** Function to regenerate the summary */
+ regenerate: () => void;
+}
+
+/**
+ * Converts FirmProfile from firm.types to the format expected by gemini.service
+ */
+function convertFirmProfile(profile: FirmProfile): {
+ name: string;
+ practiceAreas: string[];
+ size: 'Solo' | 'Small' | 'Mid-size' | 'Large' | 'Enterprise';
+ attorneyCount?: number;
+ currentTools: string[];
+ aiGoals?: string[];
+} {
+ // Determine size based on attorney count
+ let size: 'Solo' | 'Small' | 'Mid-size' | 'Large' | 'Enterprise';
+ if (profile.attorneyCount <= 1) {
+ size = 'Solo';
+ } else if (profile.attorneyCount <= 10) {
+ size = 'Small';
+ } else if (profile.attorneyCount <= 50) {
+ size = 'Mid-size';
+ } else if (profile.attorneyCount <= 200) {
+ size = 'Large';
+ } else {
+ size = 'Enterprise';
+ }
+
+ // Convert practice areas to strings
+ const practiceAreas = profile.practiceAreas.map((area) => {
+ // Convert kebab-case to title case
+ return area
+ .split('-')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+ });
+
+ // Collect current tools
+ const currentTools: string[] = [];
+ if (profile.practiceManagement && profile.practiceManagement !== 'none') {
+ currentTools.push(profile.practiceManagement);
+ }
+ if (profile.documentManagement) {
+ currentTools.push(profile.documentManagement);
+ }
+
+ return {
+ name: profile.firmName,
+ practiceAreas,
+ size,
+ attorneyCount: profile.attorneyCount,
+ currentTools,
+ };
+}
+
+/**
+ * Hook for fetching AI-generated executive summaries from the Gemini service.
+ *
+ * The hook implements a cancellation pattern to prevent state updates after
+ * unmounting and to cancel in-flight requests when regenerating.
+ *
+ * @param props - Object containing scores, firmProfile, and enabled flag
+ * @returns Object with summary, loading, error states and regenerate function
+ *
+ * @example
+ * ```tsx
+ * function SummarySection() {
+ * const { state } = useAssessment();
+ * const { pillarScores } = useResults({ responses: state.responses });
+ *
+ * // Only fetch when user reaches results page
+ * const [showResults, setShowResults] = useState(false);
+ *
+ * const {
+ * summary,
+ * loading,
+ * error,
+ * regenerate,
+ * } = useGeminiSummary({
+ * scores: pillarScores,
+ * firmProfile: state.firmProfile,
+ * enabled: showResults && pillarScores.overall > 0,
+ * });
+ *
+ * if (loading) {
+ * return ;
+ * }
+ *
+ * if (error) {
+ * return (
+ *
+ *
Error: {error}
+ *
Retry
+ *
+ * );
+ * }
+ *
+ * if (!summary) {
+ * return null;
+ * }
+ *
+ * return (
+ *
+ *
Executive Summary
+ *
{summary.overallAssessment}
+ *
+ *
Key Findings
+ *
+ * {summary.keyFindings.map((finding, i) => (
+ * {finding}
+ * ))}
+ *
+ *
+ *
Regenerate Summary
+ *
+ * );
+ * }
+ * ```
+ */
+export function useGeminiSummary({
+ scores,
+ firmProfile,
+ enabled = true,
+}: UseGeminiSummaryProps): UseGeminiSummaryReturn {
+ const [summary, setSummary] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Track the current request for cancellation
+ const abortControllerRef = useRef(null);
+ const isMountedRef = useRef(true);
+
+ // Track if we've already fetched to avoid duplicate requests
+ const hasFetchedRef = useRef(false);
+
+ /**
+ * Fetch the executive summary from the Gemini service
+ */
+ const fetchSummary = useCallback(async () => {
+ // Don't fetch if disabled or no firm profile
+ if (!enabled || !firmProfile) {
+ return;
+ }
+
+ // Don't fetch if scores are empty/invalid
+ if (!scores || scores.overall === 0) {
+ return;
+ }
+
+ // Cancel any existing request
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort();
+ }
+
+ // Create new abort controller for this request
+ abortControllerRef.current = new AbortController();
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ // Convert firm profile to the format expected by the service
+ const convertedProfile = convertFirmProfile(firmProfile);
+
+ // Note: The gemini.service doesn't support AbortController directly,
+ // but we track mounted state to prevent state updates after unmount
+ const result = await generateExecutiveSummary(scores, convertedProfile);
+
+ // Only update state if still mounted and not cancelled
+ if (isMountedRef.current && abortControllerRef.current) {
+ setSummary(result);
+ setLoading(false);
+ hasFetchedRef.current = true;
+ }
+ } catch (err) {
+ // Only update state if still mounted and not cancelled
+ if (isMountedRef.current && abortControllerRef.current) {
+ const errorMessage =
+ err instanceof Error ? err.message : 'Failed to generate summary';
+ setError(errorMessage);
+ setLoading(false);
+ }
+ }
+ }, [enabled, firmProfile, scores]);
+
+ /**
+ * Regenerate the summary (used for manual refresh)
+ */
+ const regenerate = useCallback(() => {
+ // Reset fetched flag to allow re-fetch
+ hasFetchedRef.current = false;
+ setSummary(null);
+ fetchSummary();
+ }, [fetchSummary]);
+
+ /**
+ * Effect to fetch summary when enabled and inputs are valid
+ */
+ useEffect(() => {
+ // Set mounted flag
+ isMountedRef.current = true;
+
+ // Only auto-fetch once per mount when conditions are met
+ if (enabled && firmProfile && scores.overall > 0 && !hasFetchedRef.current) {
+ fetchSummary();
+ }
+
+ // Cleanup function
+ return () => {
+ isMountedRef.current = false;
+
+ // Cancel any in-flight request
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = null;
+ }
+ };
+ }, [enabled, firmProfile, scores.overall, fetchSummary]);
+
+ /**
+ * Reset state when disabled
+ */
+ useEffect(() => {
+ if (!enabled) {
+ setSummary(null);
+ setLoading(false);
+ setError(null);
+ hasFetchedRef.current = false;
+ }
+ }, [enabled]);
+
+ return {
+ summary,
+ loading,
+ error,
+ regenerate,
+ };
+}
+
+export default useGeminiSummary;
diff --git a/ai-readiness-accelerator/src/hooks/useLocalStorage.ts b/ai-readiness-accelerator/src/hooks/useLocalStorage.ts
new file mode 100644
index 0000000..74105e6
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/useLocalStorage.ts
@@ -0,0 +1,161 @@
+/**
+ * Generic hook for persisting state to localStorage with JSON serialization.
+ *
+ * Handles:
+ * - JSON serialization/parsing with error handling
+ * - SSR safety (checks for window object)
+ * - Storage events for cross-tab synchronization
+ * - Graceful error recovery
+ *
+ * @module useLocalStorage
+ */
+
+import { useState, useCallback, useEffect } from 'react';
+
+/**
+ * Checks if localStorage is available in the current environment.
+ * Handles SSR and private browsing modes gracefully.
+ */
+function isLocalStorageAvailable(): boolean {
+ try {
+ if (typeof window === 'undefined') {
+ return false;
+ }
+ const testKey = '__localStorage_test__';
+ window.localStorage.setItem(testKey, testKey);
+ window.localStorage.removeItem(testKey);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Custom hook for managing state that persists to localStorage.
+ *
+ * @typeParam T - The type of the stored value
+ * @param key - The localStorage key to use for storage
+ * @param initialValue - The initial value if no stored value exists
+ * @returns A tuple of [storedValue, setValue, removeValue]
+ *
+ * @example
+ * ```tsx
+ * // Simple usage
+ * const [name, setName, removeName] = useLocalStorage('user-name', '');
+ *
+ * // With complex objects
+ * const [settings, setSettings, removeSettings] = useLocalStorage(
+ * 'user-settings',
+ * { theme: 'light', notifications: true }
+ * );
+ *
+ * // Update value
+ * setSettings({ ...settings, theme: 'dark' });
+ *
+ * // Remove from storage
+ * removeSettings();
+ * ```
+ */
+export function useLocalStorage(
+ key: string,
+ initialValue: T
+): [T, (value: T | ((prev: T) => T)) => void, () => void] {
+ // Get initial value from localStorage or use provided initial value
+ const [storedValue, setStoredValue] = useState(() => {
+ if (!isLocalStorageAvailable()) {
+ return initialValue;
+ }
+
+ try {
+ const item = window.localStorage.getItem(key);
+ if (item === null) {
+ return initialValue;
+ }
+ return JSON.parse(item) as T;
+ } catch (error) {
+ console.warn(
+ `Error reading localStorage key "${key}":`,
+ error instanceof Error ? error.message : error
+ );
+ return initialValue;
+ }
+ });
+
+ /**
+ * Sets the value in both state and localStorage.
+ * Accepts either a direct value or a function that receives the previous value.
+ */
+ const setValue = useCallback(
+ (value: T | ((prev: T) => T)) => {
+ try {
+ // Handle functional updates
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+ setStoredValue(valueToStore);
+
+ if (isLocalStorageAvailable()) {
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
+ }
+ } catch (error) {
+ console.warn(
+ `Error setting localStorage key "${key}":`,
+ error instanceof Error ? error.message : error
+ );
+ }
+ },
+ [key, storedValue]
+ );
+
+ /**
+ * Removes the value from localStorage and resets to initial value.
+ */
+ const removeValue = useCallback(() => {
+ try {
+ setStoredValue(initialValue);
+
+ if (isLocalStorageAvailable()) {
+ window.localStorage.removeItem(key);
+ }
+ } catch (error) {
+ console.warn(
+ `Error removing localStorage key "${key}":`,
+ error instanceof Error ? error.message : error
+ );
+ }
+ }, [key, initialValue]);
+
+ /**
+ * Listen for storage events to sync across tabs.
+ * This ensures that changes in one tab are reflected in others.
+ */
+ useEffect(() => {
+ if (!isLocalStorageAvailable()) {
+ return;
+ }
+
+ const handleStorageChange = (event: StorageEvent) => {
+ if (event.key !== key) {
+ return;
+ }
+
+ try {
+ if (event.newValue === null) {
+ setStoredValue(initialValue);
+ } else {
+ setStoredValue(JSON.parse(event.newValue) as T);
+ }
+ } catch (error) {
+ console.warn(
+ `Error parsing storage event for key "${key}":`,
+ error instanceof Error ? error.message : error
+ );
+ }
+ };
+
+ window.addEventListener('storage', handleStorageChange);
+ return () => window.removeEventListener('storage', handleStorageChange);
+ }, [key, initialValue]);
+
+ return [storedValue, setValue, removeValue];
+}
+
+export default useLocalStorage;
diff --git a/ai-readiness-accelerator/src/hooks/useROICalculator.ts b/ai-readiness-accelerator/src/hooks/useROICalculator.ts
new file mode 100644
index 0000000..4cd90d1
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/useROICalculator.ts
@@ -0,0 +1,260 @@
+/**
+ * Hook for managing ROI calculator inputs and computing projections.
+ *
+ * Features:
+ * - Manages ROI input state with sensible defaults
+ * - Validates inputs with min/max bounds
+ * - Computes conservative, moderate, and optimistic projections
+ * - Provides reset functionality
+ *
+ * @module useROICalculator
+ */
+
+import { useState, useCallback, useMemo } from 'react';
+import type { ROIInputs, ROIBreakdown, ROIProjection } from '../types/roi.types';
+import {
+ DEFAULT_ROI_INPUTS,
+ ROI_INPUT_FIELDS,
+ calculateROIBreakdown,
+} from '../types/roi.types';
+
+/**
+ * Input validation constraints
+ */
+interface InputConstraints {
+ min: number;
+ max: number;
+}
+
+/**
+ * Map of input field constraints from ROI_INPUT_FIELDS
+ */
+const INPUT_CONSTRAINTS: Record = ROI_INPUT_FIELDS.reduce(
+ (acc, field) => {
+ acc[field.key] = { min: field.min, max: field.max };
+ return acc;
+ },
+ {} as Record
+);
+
+/**
+ * Return type for the useROICalculator hook
+ */
+export interface UseROICalculatorReturn {
+ /** Current input values */
+ inputs: ROIInputs;
+ /** Computed projections for all three scenarios */
+ projections: {
+ conservative: ROIProjection;
+ moderate: ROIProjection;
+ optimistic: ROIProjection;
+ };
+ /** Set a specific input value */
+ setInput: (key: K, value: ROIInputs[K]) => void;
+ /** Reset all inputs to default values */
+ resetToDefaults: () => void;
+ /** Full ROI breakdown including inputs */
+ breakdown: ROIBreakdown;
+ /** Validation errors by field */
+ errors: Partial>;
+ /** Whether all inputs are valid */
+ isValid: boolean;
+}
+
+/**
+ * Validates a single input value against its constraints
+ */
+function validateInput(
+ key: keyof ROIInputs,
+ value: number
+): string | null {
+ const constraints = INPUT_CONSTRAINTS[key];
+ if (!constraints) {
+ return null;
+ }
+
+ if (typeof value !== 'number' || isNaN(value)) {
+ return 'Value must be a number';
+ }
+
+ if (value < 0) {
+ return 'Value cannot be negative';
+ }
+
+ if (value < constraints.min) {
+ return `Value must be at least ${constraints.min}`;
+ }
+
+ if (value > constraints.max) {
+ return `Value cannot exceed ${constraints.max}`;
+ }
+
+ return null;
+}
+
+/**
+ * Clamps a value within the specified min/max bounds
+ */
+function clampValue(key: keyof ROIInputs, value: number): number {
+ const constraints = INPUT_CONSTRAINTS[key];
+ if (!constraints) {
+ return value;
+ }
+
+ // Ensure non-negative
+ const nonNegative = Math.max(0, value);
+
+ // Clamp to bounds
+ return Math.min(constraints.max, Math.max(constraints.min, nonNegative));
+}
+
+/**
+ * Hook for managing ROI calculator state and computations.
+ *
+ * @param initialInputs - Optional initial input values (defaults to DEFAULT_ROI_INPUTS)
+ * @returns Object containing inputs, projections, and actions
+ *
+ * @example
+ * ```tsx
+ * function ROICalculator() {
+ * const {
+ * inputs,
+ * projections,
+ * setInput,
+ * resetToDefaults,
+ * errors,
+ * isValid,
+ * } = useROICalculator();
+ *
+ * return (
+ *
+ *
ROI Calculator
+ *
+ *
+ * Weekly Intake Volume
+ * setInput('weeklyIntakeVolume', Number(e.target.value))}
+ * />
+ * {errors.weeklyIntakeVolume && (
+ * {errors.weeklyIntakeVolume}
+ * )}
+ *
+ *
+ *
Projected Annual Savings
+ *
+ *
+ * Conservative
+ * ${projections.conservative.totalAnnualBenefit.toLocaleString()}
+ *
+ *
+ * Moderate
+ * ${projections.moderate.totalAnnualBenefit.toLocaleString()}
+ *
+ *
+ * Optimistic
+ * ${projections.optimistic.totalAnnualBenefit.toLocaleString()}
+ *
+ *
+ *
+ *
Reset to Defaults
+ *
+ * );
+ * }
+ * ```
+ */
+export function useROICalculator(
+ initialInputs: Partial = {}
+): UseROICalculatorReturn {
+ // Merge initial inputs with defaults
+ const [inputs, setInputs] = useState(() => ({
+ ...DEFAULT_ROI_INPUTS,
+ ...initialInputs,
+ }));
+
+ /**
+ * Set a specific input value with validation and clamping
+ */
+ const setInput = useCallback(
+ (key: K, value: ROIInputs[K]) => {
+ setInputs((prev) => {
+ // Ensure the value is a number
+ const numValue = typeof value === 'number' ? value : Number(value);
+
+ // If not a valid number, don't update
+ if (isNaN(numValue)) {
+ return prev;
+ }
+
+ // Clamp the value to valid bounds
+ const clampedValue = clampValue(key, numValue) as ROIInputs[K];
+
+ return {
+ ...prev,
+ [key]: clampedValue,
+ };
+ });
+ },
+ []
+ );
+
+ /**
+ * Reset all inputs to default values
+ */
+ const resetToDefaults = useCallback(() => {
+ setInputs(DEFAULT_ROI_INPUTS);
+ }, []);
+
+ /**
+ * Validate all inputs and collect errors
+ */
+ const errors = useMemo>>(() => {
+ const validationErrors: Partial> = {};
+
+ for (const key of Object.keys(inputs) as Array) {
+ const error = validateInput(key, inputs[key]);
+ if (error) {
+ validationErrors[key] = error;
+ }
+ }
+
+ return validationErrors;
+ }, [inputs]);
+
+ /**
+ * Check if all inputs are valid
+ */
+ const isValid = useMemo(() => Object.keys(errors).length === 0, [errors]);
+
+ /**
+ * Compute the full ROI breakdown with all three scenarios
+ */
+ const breakdown = useMemo(() => {
+ return calculateROIBreakdown(inputs);
+ }, [inputs]);
+
+ /**
+ * Extract projections from breakdown for convenience
+ */
+ const projections = useMemo(
+ () => ({
+ conservative: breakdown.conservative,
+ moderate: breakdown.moderate,
+ optimistic: breakdown.optimistic,
+ }),
+ [breakdown]
+ );
+
+ return {
+ inputs,
+ projections,
+ setInput,
+ resetToDefaults,
+ breakdown,
+ errors,
+ isValid,
+ };
+}
+
+export default useROICalculator;
diff --git a/ai-readiness-accelerator/src/hooks/useResults.ts b/ai-readiness-accelerator/src/hooks/useResults.ts
new file mode 100644
index 0000000..963da8a
--- /dev/null
+++ b/ai-readiness-accelerator/src/hooks/useResults.ts
@@ -0,0 +1,217 @@
+/**
+ * Hook for computing assessment results including scores, readiness level,
+ * strengths, gaps, and blockers.
+ *
+ * Features:
+ * - Memoized expensive calculations for performance
+ * - Uses scoring.service.ts for all score computations
+ * - Derives strengths/gaps and blockers from pillar scores
+ *
+ * @module useResults
+ */
+
+import { useMemo } from 'react';
+import type {
+ AssessmentResponses,
+ PillarScores,
+ PillarScore,
+ ReadinessLevel,
+ StrengthsGaps,
+ StrengthGapItem,
+} from '../types';
+import type { FirmProfile } from '../types/firm.types';
+import {
+ calculateAllScores,
+ identifyStrengthsGaps,
+ getBlockers,
+} from '../services/scoring.service';
+import { PILLAR_DEFINITIONS } from '../constants/pillars';
+
+/**
+ * Props for the useResults hook
+ */
+export interface UseResultsProps {
+ /** Assessment responses organized by pillar */
+ responses: AssessmentResponses;
+ /** Optional firm profile for context */
+ firmProfile?: FirmProfile | null;
+}
+
+/**
+ * Return type for the useResults hook
+ */
+export interface UseResultsReturn {
+ /** Individual scores for each pillar */
+ scores: PillarScore[];
+ /** Overall composite score (0-5 scale) */
+ overallScore: number;
+ /** Determined readiness level based on overall score */
+ readinessLevel: ReadinessLevel;
+ /** Top performing pillars */
+ strengths: StrengthGapItem[];
+ /** Pillars needing improvement */
+ gaps: StrengthGapItem[];
+ /** Critical blockers that should be addressed before AI adoption */
+ blockers: string[];
+ /** Whether any responses exist */
+ hasResponses: boolean;
+ /** Complete pillar scores object for compatibility */
+ pillarScores: PillarScores;
+}
+
+/**
+ * Checks if the responses object contains any actual responses
+ */
+function hasAnyResponses(responses: AssessmentResponses): boolean {
+ if (!responses) return false;
+
+ return Object.values(responses).some(
+ (pillarResponses) => pillarResponses && pillarResponses.length > 0
+ );
+}
+
+/**
+ * Hook for computing assessment results from responses.
+ *
+ * All calculations are memoized to prevent unnecessary re-computation.
+ * Uses the scoring service for consistent calculation logic.
+ *
+ * @param props - Object containing responses and optional firm profile
+ * @returns Computed results including scores, strengths, gaps, and blockers
+ *
+ * @example
+ * ```tsx
+ * function ResultsPage() {
+ * const { state } = useAssessment();
+ * const {
+ * scores,
+ * overallScore,
+ * readinessLevel,
+ * strengths,
+ * gaps,
+ * blockers,
+ * } = useResults({
+ * responses: state.responses,
+ * firmProfile: state.firmProfile,
+ * });
+ *
+ * return (
+ *
+ *
Overall Score: {overallScore.toFixed(1)}/5
+ *
Readiness Level: {readinessLevel}
+ *
+ *
Strengths
+ *
+ * {strengths.map(s => (
+ * {s.pillarName}: {s.score}
+ * ))}
+ *
+ *
+ * {blockers.length > 0 && (
+ *
+ *
Blockers
+ *
+ * {blockers.map((b, i) => {b} )}
+ *
+ *
+ * )}
+ *
+ * );
+ * }
+ * ```
+ */
+export function useResults({ responses, firmProfile: _firmProfile }: UseResultsProps): UseResultsReturn {
+ // Note: firmProfile is available for future use in customizing results
+ // Currently unused but kept for API consistency
+ /**
+ * Check if we have any responses to compute results from
+ */
+ const hasResponses = useMemo(() => hasAnyResponses(responses), [responses]);
+
+ /**
+ * Compute all pillar scores using the scoring service.
+ * This is the main expensive calculation that needs memoization.
+ */
+ const pillarScores = useMemo(() => {
+ if (!hasResponses) {
+ return {
+ pillars: PILLAR_DEFINITIONS.map((pillar) => ({
+ id: pillar.id,
+ name: pillar.name,
+ score: 0,
+ status: 'critical' as const,
+ })),
+ overall: 0,
+ readinessLevel: 'not-ready' as ReadinessLevel,
+ };
+ }
+
+ return calculateAllScores(responses, PILLAR_DEFINITIONS);
+ }, [responses, hasResponses]);
+
+ /**
+ * Extract individual pillar scores array
+ */
+ const scores = useMemo(() => pillarScores.pillars, [pillarScores]);
+
+ /**
+ * Extract overall score
+ */
+ const overallScore = useMemo(() => pillarScores.overall, [pillarScores]);
+
+ /**
+ * Extract readiness level
+ */
+ const readinessLevel = useMemo(
+ () => pillarScores.readinessLevel,
+ [pillarScores]
+ );
+
+ /**
+ * Compute strengths and gaps from pillar scores
+ */
+ const strengthsGaps = useMemo(() => {
+ if (!hasResponses) {
+ return { strengths: [], gaps: [] };
+ }
+
+ return identifyStrengthsGaps(pillarScores.pillars);
+ }, [pillarScores.pillars, hasResponses]);
+
+ /**
+ * Extract strengths array
+ */
+ const strengths = useMemo(
+ () => strengthsGaps.strengths,
+ [strengthsGaps]
+ );
+
+ /**
+ * Extract gaps array
+ */
+ const gaps = useMemo(() => strengthsGaps.gaps, [strengthsGaps]);
+
+ /**
+ * Compute blockers from pillar scores
+ */
+ const blockers = useMemo(() => {
+ if (!hasResponses) {
+ return [];
+ }
+
+ return getBlockers(pillarScores.pillars);
+ }, [pillarScores.pillars, hasResponses]);
+
+ return {
+ scores,
+ overallScore,
+ readinessLevel,
+ strengths,
+ gaps,
+ blockers,
+ hasResponses,
+ pillarScores,
+ };
+}
+
+export default useResults;
diff --git a/ai-readiness-accelerator/src/index.css b/ai-readiness-accelerator/src/index.css
new file mode 100644
index 0000000..b491ba8
--- /dev/null
+++ b/ai-readiness-accelerator/src/index.css
@@ -0,0 +1,154 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ @apply antialiased text-slate-900 bg-slate-50;
+ }
+
+ /* Focus styles for accessibility */
+ :focus-visible {
+ @apply outline-2 outline-offset-2 outline-primary-500;
+ }
+}
+
+@layer components {
+ /* Card component base */
+ .card {
+ @apply bg-white rounded-xl shadow-sm border border-slate-200 p-6;
+ }
+
+ /* Button variants */
+ .btn {
+ @apply inline-flex items-center justify-center font-medium rounded-lg
+ transition-all duration-200 focus:outline-none focus-visible:ring-2
+ focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed;
+ }
+
+ .btn-primary {
+ @apply btn bg-primary-600 text-white hover:bg-primary-700
+ focus-visible:ring-primary-500 px-6 py-3;
+ }
+
+ .btn-secondary {
+ @apply btn bg-white text-slate-700 border border-slate-300
+ hover:bg-slate-50 focus-visible:ring-slate-500 px-6 py-3;
+ }
+
+ .btn-ghost {
+ @apply btn text-slate-600 hover:text-slate-900 hover:bg-slate-100 px-4 py-2;
+ }
+
+ /* Form inputs */
+ .input {
+ @apply w-full px-4 py-2.5 border border-slate-300 rounded-lg
+ focus:ring-2 focus:ring-primary-500 focus:border-primary-500
+ placeholder:text-slate-400 transition-colors;
+ }
+
+ .input-error {
+ @apply input border-red-500 focus:ring-red-500 focus:border-red-500;
+ }
+
+ .label {
+ @apply block text-sm font-medium text-slate-700 mb-1.5;
+ }
+
+ /* Likert scale */
+ .likert-container {
+ @apply flex gap-2;
+ }
+
+ .likert-option {
+ @apply flex-1 p-3 text-center border border-slate-200 rounded-lg
+ cursor-pointer transition-all hover:border-primary-300 hover:bg-primary-50;
+ }
+
+ .likert-option-selected {
+ @apply likert-option border-primary-500 bg-primary-50 text-primary-700 font-medium;
+ }
+
+ /* Progress bar */
+ .progress-bar {
+ @apply h-2 bg-slate-200 rounded-full overflow-hidden;
+ }
+
+ .progress-fill {
+ @apply h-full bg-primary-600 transition-all duration-300 ease-out;
+ }
+
+ /* Tabs */
+ .tab-list {
+ @apply flex gap-4 border-b border-slate-200;
+ }
+
+ .tab {
+ @apply pb-3 px-1 font-medium text-sm transition-colors border-b-2
+ border-transparent text-slate-500 hover:text-slate-700;
+ }
+
+ .tab-active {
+ @apply tab border-primary-600 text-primary-600;
+ }
+
+ /* Radar chart container */
+ .radar-container {
+ @apply aspect-square max-w-md mx-auto;
+ }
+
+ /* Score badge */
+ .score-badge {
+ @apply inline-flex items-center px-3 py-1 rounded-full text-sm font-medium;
+ }
+
+ .score-critical {
+ @apply score-badge bg-red-100 text-red-800;
+ }
+
+ .score-needs-work {
+ @apply score-badge bg-orange-100 text-orange-800;
+ }
+
+ .score-adequate {
+ @apply score-badge bg-yellow-100 text-yellow-800;
+ }
+
+ .score-strong {
+ @apply score-badge bg-green-100 text-green-800;
+ }
+
+ .score-excellent {
+ @apply score-badge bg-emerald-100 text-emerald-800;
+ }
+}
+
+@layer utilities {
+ /* Loading spinner */
+ .spinner {
+ @apply animate-spin h-5 w-5 border-2 border-primary-600 border-t-transparent rounded-full;
+ }
+
+ /* Gradient backgrounds */
+ .gradient-hero {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ }
+
+ .gradient-cta {
+ @apply bg-gradient-to-r from-primary-600 to-primary-800;
+ }
+
+ /* Section spacing */
+ .section {
+ @apply py-16 md:py-24;
+ }
+
+ /* Container */
+ .container-app {
+ @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
+ }
+}
diff --git a/ai-readiness-accelerator/src/main.tsx b/ai-readiness-accelerator/src/main.tsx
new file mode 100644
index 0000000..964aeb4
--- /dev/null
+++ b/ai-readiness-accelerator/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/ai-readiness-accelerator/src/services/gemini.service.ts b/ai-readiness-accelerator/src/services/gemini.service.ts
new file mode 100644
index 0000000..796ced7
--- /dev/null
+++ b/ai-readiness-accelerator/src/services/gemini.service.ts
@@ -0,0 +1,454 @@
+/**
+ * Gemini API Service for AI Readiness Accelerator
+ *
+ * This service handles communication with Google's Gemini API to generate
+ * AI-powered executive summaries based on assessment scores and firm profiles.
+ */
+
+import type {
+ PillarScores,
+ FirmProfile,
+ ExecutiveSummary,
+ GeminiResponse,
+ GeminiError,
+} from '../types';
+
+/** Gemini API endpoint for content generation */
+const GEMINI_API_URL =
+ 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent';
+
+/** Request timeout in milliseconds */
+const REQUEST_TIMEOUT_MS = 30000;
+
+/**
+ * JSON Schema for Gemini's structured output.
+ * This schema ensures the API returns data matching our ExecutiveSummary interface.
+ */
+const EXECUTIVE_SUMMARY_SCHEMA = {
+ type: 'object',
+ properties: {
+ overallAssessment: {
+ type: 'string',
+ description: '2-3 sentence executive summary of the assessment',
+ },
+ keyFindings: {
+ type: 'array',
+ items: { type: 'string' },
+ description: '3-4 bullet points of main observations',
+ },
+ priorityActions: {
+ type: 'array',
+ items: { type: 'string' },
+ description: '3 specific next steps ordered by importance',
+ },
+ recommendedFirstMove: {
+ type: 'string',
+ description: 'Single most important action for the next 30 days',
+ },
+ riskConsiderations: {
+ type: 'array',
+ items: { type: 'string' },
+ description: '2-3 risks to be aware of',
+ },
+ },
+ required: [
+ 'overallAssessment',
+ 'keyFindings',
+ 'priorityActions',
+ 'recommendedFirstMove',
+ 'riskConsiderations',
+ ],
+};
+
+/**
+ * Builds the detailed prompt for Gemini API based on scores and firm profile.
+ *
+ * @param scores - The pillar scores from the assessment
+ * @param firmProfile - The law firm's profile information
+ * @returns Formatted prompt string for the API
+ */
+function buildPrompt(scores: PillarScores, firmProfile: FirmProfile): string {
+ const pillarDetails = scores.pillars
+ .map((p) => `- ${p.name}: ${p.score}/100 (Status: ${p.status})`)
+ .join('\n');
+
+ const practiceAreas = firmProfile.practiceAreas.join(', ');
+ const currentTools = firmProfile.currentTools.length > 0
+ ? firmProfile.currentTools.join(', ')
+ : 'None specified';
+ const aiGoals = firmProfile.aiGoals?.length
+ ? firmProfile.aiGoals.join(', ')
+ : 'Not specified';
+ const attorneyCount = firmProfile.attorneyCount
+ ? `${firmProfile.attorneyCount} attorneys`
+ : firmProfile.size;
+
+ return `You are an AI readiness consultant specializing in helping law firms adopt artificial intelligence technologies. Your role is to provide strategic, actionable guidance based on assessment data.
+
+## Firm Profile
+- **Firm Name:** ${firmProfile.name}
+- **Practice Areas:** ${practiceAreas}
+- **Firm Size:** ${attorneyCount}
+- **Current Technology Tools:** ${currentTools}
+- **AI Adoption Goals:** ${aiGoals}
+
+## Assessment Results
+**Overall AI Readiness Score:** ${scores.overall}/100
+**Readiness Level:** ${scores.readinessLevel}
+
+### Pillar Scores:
+${pillarDetails}
+
+## Task
+Based on the assessment data above, generate an executive summary for this law firm. Your analysis should be:
+- Specific to their practice areas and firm size
+- Actionable with concrete next steps
+- Realistic about challenges they may face
+- Focused on quick wins and high-impact improvements
+
+## Required Output Format
+Provide your response as a JSON object with the following structure:
+
+1. **overallAssessment**: Write a 2-3 sentence executive summary that captures the firm's current AI readiness state and potential trajectory. Be direct and specific to this firm.
+
+2. **keyFindings**: Provide 3-4 bullet points highlighting the most important observations from the assessment. Each finding should reference specific pillar scores or firm characteristics.
+
+3. **priorityActions**: List exactly 3 specific, actionable next steps ordered by importance. Each action should be concrete enough to implement within 30-90 days.
+
+4. **recommendedFirstMove**: Identify the single most important action this firm should take in the next 30 days. This should be achievable, high-impact, and build momentum for further AI adoption.
+
+5. **riskConsiderations**: List 2-3 risks or challenges the firm should be aware of as they pursue AI adoption. These should be relevant to their specific situation.
+
+Remember: This is for a law firm, so consider ethical obligations, client confidentiality requirements, and the conservative nature of legal practice in your recommendations.`;
+}
+
+/**
+ * Validates and normalizes the API response to ensure it matches ExecutiveSummary interface.
+ * Fills in defaults for any missing fields.
+ *
+ * @param data - The parsed response data from the API
+ * @returns A validated ExecutiveSummary object with defaults for missing fields
+ */
+function validateAndNormalizeResponse(data: unknown): ExecutiveSummary {
+ const response = data as Partial;
+
+ return {
+ overallAssessment:
+ response.overallAssessment ||
+ 'Assessment analysis is currently unavailable. Please review your pillar scores directly.',
+ keyFindings:
+ Array.isArray(response.keyFindings) && response.keyFindings.length > 0
+ ? response.keyFindings.slice(0, 4)
+ : ['Assessment data has been recorded', 'Review individual pillar scores for detailed insights'],
+ priorityActions:
+ Array.isArray(response.priorityActions) && response.priorityActions.length > 0
+ ? response.priorityActions.slice(0, 3)
+ : [
+ 'Review assessment results with leadership team',
+ 'Identify quick-win opportunities in highest-scoring areas',
+ 'Develop an AI adoption roadmap',
+ ],
+ recommendedFirstMove:
+ response.recommendedFirstMove ||
+ 'Schedule a team meeting to review assessment results and align on AI adoption priorities.',
+ riskConsiderations:
+ Array.isArray(response.riskConsiderations) && response.riskConsiderations.length > 0
+ ? response.riskConsiderations.slice(0, 3)
+ : [
+ 'Ensure compliance with ethical obligations when implementing AI tools',
+ 'Consider data security implications for client information',
+ ],
+ };
+}
+
+/**
+ * Creates a fallback executive summary when the API call fails.
+ * This ensures users always receive some guidance even when the AI service is unavailable.
+ *
+ * @param scores - The pillar scores from the assessment
+ * @param firmProfile - The law firm's profile information
+ * @returns A basic ExecutiveSummary based on available data
+ */
+function createFallbackSummary(
+ scores: PillarScores,
+ firmProfile: FirmProfile
+): ExecutiveSummary {
+ const lowestPillar = [...scores.pillars].sort((a, b) => a.score - b.score)[0];
+ const highestPillar = [...scores.pillars].sort((a, b) => b.score - a.score)[0];
+
+ const sizeContext =
+ firmProfile.size === 'Solo' || firmProfile.size === 'Small'
+ ? 'smaller firm'
+ : 'firm of your size';
+
+ return {
+ overallAssessment: `${firmProfile.name} achieved an overall AI readiness score of ${scores.overall}/100, placing you at the "${scores.readinessLevel}" level. This assessment provides a baseline for your AI adoption journey.`,
+ keyFindings: [
+ `Your strongest area is ${highestPillar.name} with a score of ${highestPillar.score}/100`,
+ `${lowestPillar.name} presents the greatest opportunity for improvement at ${lowestPillar.score}/100`,
+ `As a ${sizeContext}, focusing on targeted improvements can yield significant results`,
+ `Current tool adoption: ${firmProfile.currentTools.length > 0 ? firmProfile.currentTools.join(', ') : 'Limited technology stack identified'}`,
+ ],
+ priorityActions: [
+ `Address gaps in ${lowestPillar.name} to strengthen your overall AI readiness`,
+ `Build on your strength in ${highestPillar.name} to create quick wins`,
+ 'Develop a phased AI adoption roadmap aligned with your practice areas',
+ ],
+ recommendedFirstMove: `Focus on improving ${lowestPillar.name} by identifying one specific, low-risk AI tool or process that can demonstrate value within 30 days.`,
+ riskConsiderations: [
+ 'Ensure all AI tools comply with bar association ethics rules and client confidentiality requirements',
+ 'Start with internal processes before client-facing AI applications to build confidence and expertise',
+ 'Plan for staff training and change management to ensure successful adoption',
+ ],
+ };
+}
+
+/**
+ * Fetches with a timeout wrapper.
+ *
+ * @param url - The URL to fetch
+ * @param options - Fetch options
+ * @param timeoutMs - Timeout in milliseconds
+ * @returns Promise resolving to the fetch Response
+ * @throws Error if the request times out
+ */
+async function fetchWithTimeout(
+ url: string,
+ options: RequestInit,
+ timeoutMs: number
+): Promise {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
+
+ try {
+ const response = await fetch(url, {
+ ...options,
+ signal: controller.signal,
+ });
+ return response;
+ } finally {
+ clearTimeout(timeoutId);
+ }
+}
+
+/**
+ * Generates an AI-powered executive summary based on assessment scores and firm profile.
+ *
+ * This function calls the Gemini API to analyze the assessment data and generate
+ * personalized recommendations for the law firm. If the API call fails, it returns
+ * a fallback summary based on the available data.
+ *
+ * @param scores - The pillar scores from the AI readiness assessment
+ * @param firmProfile - The law firm's profile information
+ * @returns Promise resolving to an ExecutiveSummary object
+ *
+ * @example
+ * ```typescript
+ * const scores: PillarScores = {
+ * pillars: [{ id: '1', name: 'Data Infrastructure', score: 75, status: 'strong' }],
+ * overall: 72,
+ * readinessLevel: 'developing'
+ * };
+ *
+ * const firmProfile: FirmProfile = {
+ * name: 'Smith & Associates',
+ * practiceAreas: ['Corporate Law', 'Litigation'],
+ * size: 'Mid-size',
+ * currentTools: ['Clio', 'NetDocuments']
+ * };
+ *
+ * const summary = await generateExecutiveSummary(scores, firmProfile);
+ * ```
+ */
+export async function generateExecutiveSummary(
+ scores: PillarScores,
+ firmProfile: FirmProfile
+): Promise {
+ const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
+
+ // If no API key is configured, return the fallback summary
+ if (!apiKey) {
+ console.warn(
+ 'Gemini API key not configured (VITE_GEMINI_API_KEY). Using fallback summary.'
+ );
+ return createFallbackSummary(scores, firmProfile);
+ }
+
+ const prompt = buildPrompt(scores, firmProfile);
+
+ const requestBody = {
+ contents: [
+ {
+ parts: [
+ {
+ text: prompt,
+ },
+ ],
+ },
+ ],
+ generationConfig: {
+ temperature: 0.7,
+ topK: 40,
+ topP: 0.95,
+ maxOutputTokens: 2048,
+ responseMimeType: 'application/json',
+ responseSchema: EXECUTIVE_SUMMARY_SCHEMA,
+ },
+ };
+
+ try {
+ const response = await fetchWithTimeout(
+ `${GEMINI_API_URL}?key=${apiKey}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(requestBody),
+ },
+ REQUEST_TIMEOUT_MS
+ );
+
+ if (!response.ok) {
+ const errorData = (await response.json()) as GeminiError;
+ const errorMessage = errorData.error?.message || 'Unknown API error';
+ console.error('Gemini API error:', errorMessage);
+ throw new Error(`AI service error: ${errorMessage}`);
+ }
+
+ const data = (await response.json()) as GeminiResponse;
+
+ // Extract the text content from the response
+ const textContent = data.candidates?.[0]?.content?.parts?.[0]?.text;
+
+ if (!textContent) {
+ console.error('Gemini API returned empty response');
+ throw new Error('AI service returned an empty response');
+ }
+
+ // Parse the JSON response
+ let parsedResponse: unknown;
+ try {
+ parsedResponse = JSON.parse(textContent);
+ } catch (parseError) {
+ console.error('Failed to parse Gemini response as JSON:', parseError);
+ throw new Error('AI service returned invalid response format');
+ }
+
+ // Validate and normalize the response
+ return validateAndNormalizeResponse(parsedResponse);
+ } catch (error) {
+ // Handle abort (timeout) errors
+ if (error instanceof Error && error.name === 'AbortError') {
+ console.error('Gemini API request timed out after', REQUEST_TIMEOUT_MS, 'ms');
+ console.warn('Returning fallback summary due to timeout');
+ return createFallbackSummary(scores, firmProfile);
+ }
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ console.error('Network error connecting to Gemini API:', error);
+ console.warn('Returning fallback summary due to network error');
+ return createFallbackSummary(scores, firmProfile);
+ }
+
+ // Log and re-throw other errors, but still provide fallback
+ console.error('Error generating executive summary:', error);
+ console.warn('Returning fallback summary due to error');
+ return createFallbackSummary(scores, firmProfile);
+ }
+}
+
+/**
+ * Mock function for development and testing without a Gemini API key.
+ *
+ * This function simulates the API response with realistic data based on
+ * the provided scores and firm profile. Useful for:
+ * - Development without API costs
+ * - Unit testing
+ * - Demo environments
+ *
+ * @param scores - The pillar scores from the AI readiness assessment
+ * @param firmProfile - The law firm's profile information
+ * @param delay - Optional delay in milliseconds to simulate API latency (default: 1500)
+ * @returns Promise resolving to a mock ExecutiveSummary object
+ *
+ * @example
+ * ```typescript
+ * // Use mock in development
+ * const summary = await generateExecutiveSummaryMock(scores, firmProfile);
+ *
+ * // Simulate slower response
+ * const summary = await generateExecutiveSummaryMock(scores, firmProfile, 3000);
+ * ```
+ */
+export async function generateExecutiveSummaryMock(
+ scores: PillarScores,
+ firmProfile: FirmProfile,
+ delay: number = 1500
+): Promise {
+ // Simulate API latency
+ await new Promise((resolve) => setTimeout(resolve, delay));
+
+ const lowestPillar = [...scores.pillars].sort((a, b) => a.score - b.score)[0];
+ const highestPillar = [...scores.pillars].sort((a, b) => b.score - a.score)[0];
+ const midPillars = [...scores.pillars]
+ .sort((a, b) => a.score - b.score)
+ .slice(1, -1);
+
+ const readinessDescriptions: Record = {
+ 'not-ready': 'early stages of AI readiness',
+ foundational: 'building foundational AI capabilities',
+ developing: 'actively developing AI readiness',
+ ready: 'well-positioned for AI adoption',
+ optimized: 'leading the way in AI adoption',
+ };
+
+ const readinessDesc =
+ readinessDescriptions[scores.readinessLevel] || 'developing AI readiness';
+
+ const sizeRecommendations: Record = {
+ Solo: 'Focus on AI tools that can multiply your individual productivity',
+ Small: 'Prioritize tools that enhance collaboration and reduce administrative burden',
+ 'Mid-size': 'Consider department-specific AI solutions with firm-wide integration',
+ Large: 'Develop a comprehensive AI governance framework alongside tool adoption',
+ Enterprise: 'Establish an AI Center of Excellence to coordinate firm-wide initiatives',
+ };
+
+ const sizeRec =
+ sizeRecommendations[firmProfile.size] ||
+ 'Develop a phased approach to AI adoption';
+
+ return {
+ overallAssessment: `${firmProfile.name} scored ${scores.overall}/100 on the AI Readiness Assessment, indicating the firm is ${readinessDesc}. With strengths in ${highestPillar.name} and opportunities for growth in ${lowestPillar.name}, ${firmProfile.name} has a clear path forward for strategic AI adoption that can enhance both efficiency and client service quality.`,
+ keyFindings: [
+ `Strong foundation in ${highestPillar.name} (${highestPillar.score}/100) provides a solid base for AI initiatives`,
+ `${lowestPillar.name} at ${lowestPillar.score}/100 represents the greatest opportunity for improvement`,
+ midPillars.length > 0
+ ? `${midPillars[0].name} shows moderate readiness (${midPillars[0].score}/100) and could benefit from targeted enhancements`
+ : `Overall pillar scores show room for improvement across multiple areas`,
+ `Current technology stack (${firmProfile.currentTools.length > 0 ? firmProfile.currentTools.slice(0, 2).join(', ') : 'limited tools'}) provides ${firmProfile.currentTools.length > 2 ? 'a good' : 'a basic'} foundation for AI integration`,
+ ],
+ priorityActions: [
+ `Invest in ${lowestPillar.name} improvements: Identify specific gaps and implement targeted solutions within Q1`,
+ `Leverage your ${highestPillar.name} strength: ${sizeRec}`,
+ `Establish AI governance: Create clear policies for AI tool evaluation, data handling, and ethical use in client matters`,
+ ],
+ recommendedFirstMove: `Within the next 30 days, conduct a focused assessment of ${lowestPillar.name} to identify 2-3 specific, low-risk AI tools that can address current gaps. Start with internal processes before expanding to client-facing applications.`,
+ riskConsiderations: [
+ `Client confidentiality: Ensure all AI tools meet bar association requirements and client data protection standards before deployment`,
+ `Change management: Plan for attorney and staff training to maximize adoption and minimize resistance to new AI-powered workflows`,
+ `Vendor selection: Carefully evaluate AI vendors for legal-specific experience, security certifications, and long-term viability`,
+ ],
+ };
+}
+
+/**
+ * Checks if the Gemini API key is configured in the environment.
+ *
+ * @returns True if the API key is present and non-empty
+ */
+export function isGeminiConfigured(): boolean {
+ const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
+ return typeof apiKey === 'string' && apiKey.length > 0;
+}
diff --git a/ai-readiness-accelerator/src/services/scoring.service.ts b/ai-readiness-accelerator/src/services/scoring.service.ts
new file mode 100644
index 0000000..5fda6f8
--- /dev/null
+++ b/ai-readiness-accelerator/src/services/scoring.service.ts
@@ -0,0 +1,291 @@
+/**
+ * Scoring Service for the AI Readiness Accelerator
+ *
+ * This service provides pure functions for calculating assessment scores,
+ * determining readiness levels, and identifying strengths and gaps.
+ */
+
+import type {
+ QuestionResponse,
+ AssessmentResponses,
+ PillarDefinition,
+ PillarScores,
+ PillarScore,
+ PillarStatus,
+ ReadinessLevel,
+ StrengthsGaps,
+ StrengthGapItem,
+} from '../types';
+
+import {
+ CRITICAL_PILLAR_IDS,
+ BLOCKER_SCORE_THRESHOLD,
+ STRENGTHS_GAPS_COUNT,
+ PILLAR_STATUS_DESCRIPTIONS,
+} from '../constants';
+
+/**
+ * Calculates the score for a single pillar based on question responses.
+ *
+ * @param responses - Array of question responses for the pillar
+ * @returns The average score rounded to 1 decimal place, or 0 if no responses
+ *
+ * @example
+ * ```ts
+ * const responses = [
+ * { questionId: 'q1', value: 4 },
+ * { questionId: 'q2', value: 3 },
+ * { questionId: 'q3', value: 5 },
+ * ];
+ * const score = calculatePillarScore(responses); // 4.0
+ * ```
+ */
+export function calculatePillarScore(responses: QuestionResponse[]): number {
+ if (!responses || responses.length === 0) {
+ return 0;
+ }
+
+ const sum = responses.reduce((acc, response) => acc + response.value, 0);
+ const average = sum / responses.length;
+
+ // Round to 1 decimal place
+ return Math.round(average * 10) / 10;
+}
+
+/**
+ * Determines the status level for a pillar based on its score.
+ *
+ * @param score - The pillar's score (0-5 scale)
+ * @returns The status level: 'critical', 'needs-work', 'adequate', 'strong', or 'excellent'
+ *
+ * @example
+ * ```ts
+ * getPillarStatus(1.2); // 'critical'
+ * getPillarStatus(2.0); // 'needs-work'
+ * getPillarStatus(3.0); // 'adequate'
+ * getPillarStatus(4.0); // 'strong'
+ * getPillarStatus(4.8); // 'excellent'
+ * ```
+ */
+export function getPillarStatus(score: number): PillarStatus {
+ if (score < 1.5) {
+ return 'critical';
+ }
+ if (score < 2.5) {
+ return 'needs-work';
+ }
+ if (score < 3.5) {
+ return 'adequate';
+ }
+ if (score < 4.5) {
+ return 'strong';
+ }
+ return 'excellent';
+}
+
+/**
+ * Determines the overall readiness level based on the average score.
+ *
+ * @param overallScore - The overall average score (0-5 scale)
+ * @returns The readiness level: 'not-ready', 'foundational', 'developing', 'ready', or 'optimized'
+ *
+ * @example
+ * ```ts
+ * getReadinessLevel(0.5); // 'not-ready'
+ * getReadinessLevel(1.5); // 'foundational'
+ * getReadinessLevel(2.5); // 'developing'
+ * getReadinessLevel(3.5); // 'ready'
+ * getReadinessLevel(4.5); // 'optimized'
+ * ```
+ */
+export function getReadinessLevel(overallScore: number): ReadinessLevel {
+ if (overallScore <= 1.0) {
+ return 'not-ready';
+ }
+ if (overallScore <= 2.0) {
+ return 'foundational';
+ }
+ if (overallScore <= 3.0) {
+ return 'developing';
+ }
+ if (overallScore <= 4.0) {
+ return 'ready';
+ }
+ return 'optimized';
+}
+
+/**
+ * Calculates scores for all pillars and determines overall readiness.
+ *
+ * @param responses - Map of pillar IDs to their question responses
+ * @param pillarDefinitions - Array of pillar definitions with id and name
+ * @returns Complete scoring results including pillar scores, overall score, and readiness level
+ *
+ * @example
+ * ```ts
+ * const responses = {
+ * 'data': [{ questionId: 'q1', value: 4 }, { questionId: 'q2', value: 3 }],
+ * 'risk': [{ questionId: 'q1', value: 2 }, { questionId: 'q2', value: 3 }],
+ * };
+ * const pillars = [
+ * { id: 'data', name: 'Data Foundation' },
+ * { id: 'risk', name: 'Risk Management' },
+ * ];
+ * const scores = calculateAllScores(responses, pillars);
+ * // {
+ * // pillars: [
+ * // { id: 'data', name: 'Data Foundation', score: 3.5, status: 'strong' },
+ * // { id: 'risk', name: 'Risk Management', score: 2.5, status: 'adequate' },
+ * // ],
+ * // overall: 3.0,
+ * // readinessLevel: 'developing'
+ * // }
+ * ```
+ */
+export function calculateAllScores(
+ responses: AssessmentResponses,
+ pillarDefinitions: PillarDefinition[]
+): PillarScores {
+ const pillarScores: PillarScore[] = pillarDefinitions.map((pillar) => {
+ const pillarResponses = responses[pillar.id] || [];
+ const score = calculatePillarScore(pillarResponses);
+ const status = getPillarStatus(score);
+
+ return {
+ id: pillar.id,
+ name: pillar.name,
+ score,
+ status,
+ };
+ });
+
+ // Calculate overall score as average of all pillar scores
+ const overall =
+ pillarScores.length > 0
+ ? Math.round(
+ (pillarScores.reduce((acc, p) => acc + p.score, 0) / pillarScores.length) * 10
+ ) / 10
+ : 0;
+
+ const readinessLevel = getReadinessLevel(overall);
+
+ return {
+ pillars: pillarScores,
+ overall,
+ readinessLevel,
+ };
+}
+
+/**
+ * Generates a description for a pillar based on its score level.
+ *
+ * @param pillarName - The name of the pillar
+ * @param status - The pillar's status level
+ * @returns A description string for the pillar
+ */
+function generatePillarDescription(pillarName: string, status: PillarStatus): string {
+ return `${pillarName} ${PILLAR_STATUS_DESCRIPTIONS[status]}`;
+}
+
+/**
+ * Identifies the top strengths and gaps from pillar scores.
+ *
+ * @param pillarScores - Array of pillar scores to analyze
+ * @returns Object containing top 3 strengths and bottom 3 gaps with descriptions
+ *
+ * @example
+ * ```ts
+ * const pillarScores = [
+ * { id: 'data', name: 'Data Foundation', score: 4.5, status: 'excellent' },
+ * { id: 'risk', name: 'Risk Management', score: 2.0, status: 'needs-work' },
+ * { id: 'talent', name: 'Talent', score: 3.5, status: 'strong' },
+ * ];
+ * const { strengths, gaps } = identifyStrengthsGaps(pillarScores);
+ * // strengths: [{ pillarId: 'data', pillarName: 'Data Foundation', score: 4.5, description: '...' }, ...]
+ * // gaps: [{ pillarId: 'risk', pillarName: 'Risk Management', score: 2.0, description: '...' }, ...]
+ * ```
+ */
+export function identifyStrengthsGaps(pillarScores: PillarScore[]): StrengthsGaps {
+ if (!pillarScores || pillarScores.length === 0) {
+ return { strengths: [], gaps: [] };
+ }
+
+ // Sort by score descending for strengths
+ const sortedByScoreDesc = [...pillarScores].sort((a, b) => b.score - a.score);
+
+ // Sort by score ascending for gaps
+ const sortedByScoreAsc = [...pillarScores].sort((a, b) => a.score - b.score);
+
+ // Take top N as strengths
+ const strengths: StrengthGapItem[] = sortedByScoreDesc
+ .slice(0, STRENGTHS_GAPS_COUNT)
+ .map((pillar) => ({
+ pillarId: pillar.id,
+ pillarName: pillar.name,
+ score: pillar.score,
+ description: generatePillarDescription(pillar.name, pillar.status),
+ }));
+
+ // Take bottom N as gaps
+ const gaps: StrengthGapItem[] = sortedByScoreAsc
+ .slice(0, STRENGTHS_GAPS_COUNT)
+ .map((pillar) => ({
+ pillarId: pillar.id,
+ pillarName: pillar.name,
+ score: pillar.score,
+ description: generatePillarDescription(pillar.name, pillar.status),
+ }));
+
+ return { strengths, gaps };
+}
+
+/**
+ * Identifies blocking issues for AI deployment based on pillar scores.
+ *
+ * @param pillarScores - Array of pillar scores to analyze
+ * @returns Array of blocker messages for any pillar with score below threshold,
+ * with special emphasis on critical pillars (risk and data)
+ *
+ * @example
+ * ```ts
+ * const pillarScores = [
+ * { id: 'data', name: 'Data Foundation', score: 2.0, status: 'needs-work' },
+ * { id: 'risk', name: 'Risk Management', score: 2.5, status: 'adequate' },
+ * { id: 'talent', name: 'Talent', score: 2.8, status: 'adequate' },
+ * ];
+ * const blockers = getBlockers(pillarScores);
+ * // [
+ * // 'CRITICAL: Data Foundation score (2.0) is below minimum threshold. This is a critical blocker for AI deployment.',
+ * // 'CRITICAL: Risk Management score (2.5) is below minimum threshold. This is a critical blocker for AI deployment.',
+ * // 'Talent score (2.8) is below the recommended threshold of 3.0'
+ * // ]
+ * ```
+ */
+export function getBlockers(pillarScores: PillarScore[]): string[] {
+ if (!pillarScores || pillarScores.length === 0) {
+ return [];
+ }
+
+ const blockers: string[] = [];
+
+ for (const pillar of pillarScores) {
+ if (pillar.score < BLOCKER_SCORE_THRESHOLD) {
+ const isCriticalPillar = CRITICAL_PILLAR_IDS.includes(
+ pillar.id as (typeof CRITICAL_PILLAR_IDS)[number]
+ );
+
+ if (isCriticalPillar) {
+ blockers.push(
+ `CRITICAL: ${pillar.name} score (${pillar.score}) is below minimum threshold. ` +
+ `This is a critical blocker for AI deployment.`
+ );
+ } else {
+ blockers.push(
+ `${pillar.name} score (${pillar.score}) is below the recommended threshold of ${BLOCKER_SCORE_THRESHOLD}`
+ );
+ }
+ }
+ }
+
+ return blockers;
+}
diff --git a/ai-readiness-accelerator/src/types/assessment.types.ts b/ai-readiness-accelerator/src/types/assessment.types.ts
new file mode 100644
index 0000000..1e15012
--- /dev/null
+++ b/ai-readiness-accelerator/src/types/assessment.types.ts
@@ -0,0 +1,179 @@
+/**
+ * Type definitions for the 6-pillar AI readiness assessment
+ * @module assessment.types
+ */
+
+/**
+ * Identifiers for each of the six assessment pillars
+ * - process: Business process documentation and standardization
+ * - data: Data quality, organization, and accessibility
+ * - knowledge: Institutional knowledge capture and management
+ * - tooling: Technology infrastructure and integration capabilities
+ * - risk: Risk management and compliance considerations
+ * - change: Change management and organizational readiness
+ */
+export type PillarId =
+ | 'process'
+ | 'data'
+ | 'knowledge'
+ | 'tooling'
+ | 'risk'
+ | 'change';
+
+/**
+ * Likert scale value for assessment questions
+ * 0 = Not applicable / Don't know
+ * 1 = Strongly disagree / Never
+ * 2 = Disagree / Rarely
+ * 3 = Neutral / Sometimes
+ * 4 = Agree / Often
+ * 5 = Strongly agree / Always
+ */
+export type LikertValue = 0 | 1 | 2 | 3 | 4 | 5;
+
+/**
+ * Individual assessment question within a pillar
+ */
+export interface Question {
+ /** Unique identifier for the question (e.g., "process-1", "data-3") */
+ id: string;
+
+ /** The question text presented to the user */
+ text: string;
+
+ /** Optional contextual help explaining what the question is asking */
+ helpText?: string;
+
+ /** Sub-category within the pillar for grouping related questions */
+ category: string;
+}
+
+/**
+ * Definition of an assessment pillar including its questions
+ */
+export interface PillarDefinition {
+ /** Unique identifier for the pillar */
+ id: PillarId;
+
+ /** Full display name of the pillar */
+ name: string;
+
+ /** Abbreviated name for compact displays */
+ shortName: string;
+
+ /** Detailed description of what this pillar assesses */
+ description: string;
+
+ /** Icon identifier (e.g., "workflow", "database", "brain") */
+ icon: string;
+
+ /** Array of questions for this pillar */
+ questions: Question[];
+}
+
+/**
+ * User's response to a single assessment question
+ */
+export interface QuestionResponse {
+ /** ID of the question being answered */
+ questionId: string;
+
+ /** Selected Likert scale value */
+ value: LikertValue;
+
+ /** Optional notes or comments from the user */
+ notes?: string;
+}
+
+/**
+ * Collection of all assessment responses organized by pillar
+ * Maps each pillar ID to an array of question responses
+ */
+export type AssessmentResponses = Record;
+
+/**
+ * Current state of the assessment workflow
+ */
+export interface AssessmentState {
+ /**
+ * Current step in the assessment process
+ * 0 = Firm profile
+ * 1-6 = Pillar assessments (process, data, knowledge, tooling, risk, change)
+ * 7 = Review/complete
+ */
+ currentStep: number;
+
+ /** All collected responses organized by pillar */
+ responses: AssessmentResponses;
+
+ /** Whether all required questions have been answered */
+ isComplete: boolean;
+
+ /** ISO timestamp when the assessment was started */
+ startedAt: string;
+
+ /** ISO timestamp when the assessment was completed (if finished) */
+ completedAt?: string;
+}
+
+/**
+ * Likert scale labels for display
+ */
+export const LIKERT_LABELS: Record = {
+ 0: 'N/A',
+ 1: 'Strongly Disagree',
+ 2: 'Disagree',
+ 3: 'Neutral',
+ 4: 'Agree',
+ 5: 'Strongly Agree',
+};
+
+/**
+ * Short Likert scale labels for compact display
+ */
+export const LIKERT_SHORT_LABELS: Record = {
+ 0: 'N/A',
+ 1: '1',
+ 2: '2',
+ 3: '3',
+ 4: '4',
+ 5: '5',
+};
+
+/**
+ * All pillar IDs in assessment order
+ */
+export const PILLAR_ORDER: PillarId[] = [
+ 'process',
+ 'data',
+ 'knowledge',
+ 'tooling',
+ 'risk',
+ 'change',
+];
+
+/**
+ * Creates an empty assessment responses object
+ */
+export function createEmptyAssessmentResponses(): AssessmentResponses {
+ return {
+ process: [],
+ data: [],
+ knowledge: [],
+ tooling: [],
+ risk: [],
+ change: [],
+ };
+}
+
+/**
+ * Creates an initial assessment state
+ */
+export function createInitialAssessmentState(): AssessmentState {
+ return {
+ currentStep: 0,
+ responses: createEmptyAssessmentResponses(),
+ isComplete: false,
+ startedAt: new Date().toISOString(),
+ };
+}
diff --git a/ai-readiness-accelerator/src/types/firm.types.ts b/ai-readiness-accelerator/src/types/firm.types.ts
new file mode 100644
index 0000000..821602f
--- /dev/null
+++ b/ai-readiness-accelerator/src/types/firm.types.ts
@@ -0,0 +1,137 @@
+/**
+ * Type definitions for law firm profile and configuration
+ * @module firm.types
+ */
+
+/**
+ * Practice areas that a law firm may specialize in
+ */
+export type PracticeArea =
+ | 'bankruptcy'
+ | 'family-law'
+ | 'estate-planning'
+ | 'personal-injury'
+ | 'civil-litigation'
+ | 'criminal-defense'
+ | 'immigration'
+ | 'real-estate'
+ | 'other';
+
+/**
+ * Practice management software tools commonly used by law firms
+ */
+export type PracticeManagementTool =
+ | 'clio'
+ | 'mycase'
+ | 'practicepanther'
+ | 'smokeball'
+ | 'filevine'
+ | 'cosmolex'
+ | 'none'
+ | 'other';
+
+/**
+ * Maturity levels for firm operations based on capability maturity model
+ * - chaotic: No defined processes, ad-hoc operations
+ * - informal: Some processes exist but are inconsistently followed
+ * - defined: Documented processes that are generally followed
+ * - consistent: Standardized processes with regular compliance
+ * - optimized: Continuous improvement with metrics-driven optimization
+ */
+export type OperationsMaturity =
+ | 'chaotic'
+ | 'informal'
+ | 'defined'
+ | 'consistent'
+ | 'optimized';
+
+/**
+ * Complete profile information for a law firm
+ * Used to customize assessment questions and recommendations
+ */
+export interface FirmProfile {
+ /** Legal name of the firm */
+ firmName: string;
+
+ /** Primary practice areas the firm handles */
+ practiceAreas: PracticeArea[];
+
+ /** Number of attorneys (partners and associates) */
+ attorneyCount: number;
+
+ /** Number of paralegals and legal assistants */
+ paralegalCount: number;
+
+ /** Number of intake and client services staff */
+ intakeStaffCount: number;
+
+ /** Number of administrative and support staff */
+ adminCount: number;
+
+ /** Current practice management software in use */
+ practiceManagement: PracticeManagementTool;
+
+ /** Document management system name (free text, e.g., "NetDocuments", "iManage", "None") */
+ documentManagement: string;
+
+ /** Self-assessed current operational maturity level */
+ currentOperationsMaturity: OperationsMaturity;
+}
+
+/**
+ * Display labels for practice areas
+ */
+export const PRACTICE_AREA_LABELS: Record = {
+ 'bankruptcy': 'Bankruptcy',
+ 'family-law': 'Family Law',
+ 'estate-planning': 'Estate Planning',
+ 'personal-injury': 'Personal Injury',
+ 'civil-litigation': 'Civil Litigation',
+ 'criminal-defense': 'Criminal Defense',
+ 'immigration': 'Immigration',
+ 'real-estate': 'Real Estate',
+ 'other': 'Other',
+};
+
+/**
+ * Display labels for practice management tools
+ */
+export const PRACTICE_MANAGEMENT_LABELS: Record = {
+ 'clio': 'Clio',
+ 'mycase': 'MyCase',
+ 'practicepanther': 'PracticePanther',
+ 'smokeball': 'Smokeball',
+ 'filevine': 'Filevine',
+ 'cosmolex': 'CosmoLex',
+ 'none': 'None',
+ 'other': 'Other',
+};
+
+/**
+ * Display labels and descriptions for operations maturity levels
+ */
+export const OPERATIONS_MATURITY_INFO: Record<
+ OperationsMaturity,
+ { label: string; description: string }
+> = {
+ 'chaotic': {
+ label: 'Chaotic',
+ description: 'No defined processes, ad-hoc operations',
+ },
+ 'informal': {
+ label: 'Informal',
+ description: 'Some processes exist but are inconsistently followed',
+ },
+ 'defined': {
+ label: 'Defined',
+ description: 'Documented processes that are generally followed',
+ },
+ 'consistent': {
+ label: 'Consistent',
+ description: 'Standardized processes with regular compliance',
+ },
+ 'optimized': {
+ label: 'Optimized',
+ description: 'Continuous improvement with metrics-driven optimization',
+ },
+};
diff --git a/ai-readiness-accelerator/src/types/index.ts b/ai-readiness-accelerator/src/types/index.ts
new file mode 100644
index 0000000..861b621
--- /dev/null
+++ b/ai-readiness-accelerator/src/types/index.ts
@@ -0,0 +1,143 @@
+/**
+ * Types for the AI Readiness Accelerator application
+ */
+
+/**
+ * Represents a single response to an assessment question
+ */
+export interface QuestionResponse {
+ questionId: string;
+ value: number; // 1-5 scale
+}
+
+/**
+ * Map of pillar IDs to their question responses
+ */
+export interface AssessmentResponses {
+ [pillarId: string]: QuestionResponse[];
+}
+
+/**
+ * Definition of an assessment pillar
+ */
+export interface PillarDefinition {
+ id: string;
+ name: string;
+ description?: string;
+ questions?: Question[];
+}
+
+/**
+ * Assessment question definition
+ */
+export interface Question {
+ id: string;
+ text: string;
+ category?: string;
+}
+
+/**
+ * Status levels for individual pillars
+ */
+export type PillarStatus = 'critical' | 'needs-work' | 'adequate' | 'strong' | 'excellent';
+
+/**
+ * Overall readiness levels
+ */
+export type ReadinessLevel = 'not-ready' | 'foundational' | 'developing' | 'ready' | 'optimized';
+
+/**
+ * Individual pillar score result
+ */
+export interface PillarScore {
+ id: string;
+ name: string;
+ score: number;
+ status: PillarStatus;
+}
+
+/**
+ * Complete scoring results for all pillars
+ */
+export interface PillarScores {
+ pillars: PillarScore[];
+ overall: number;
+ readinessLevel: ReadinessLevel;
+}
+
+/**
+ * Strength or gap item with description
+ */
+export interface StrengthGapItem {
+ pillarId: string;
+ pillarName: string;
+ score: number;
+ description: string;
+}
+
+/**
+ * Identified strengths and gaps from assessment
+ */
+export interface StrengthsGaps {
+ strengths: StrengthGapItem[];
+ gaps: StrengthGapItem[];
+}
+
+/**
+ * Law firm profile information
+ */
+export interface FirmProfile {
+ /** Name of the law firm */
+ name: string;
+ /** Practice areas the firm specializes in */
+ practiceAreas: string[];
+ /** Size category of the firm */
+ size: 'Solo' | 'Small' | 'Mid-size' | 'Large' | 'Enterprise';
+ /** Number of attorneys (optional) */
+ attorneyCount?: number;
+ /** Current technology tools in use */
+ currentTools: string[];
+ /** Primary goals for AI adoption (optional) */
+ aiGoals?: string[];
+}
+
+/**
+ * Executive summary generated by AI analysis
+ */
+export interface ExecutiveSummary {
+ /** 2-3 sentence executive summary of the assessment */
+ overallAssessment: string;
+ /** 3-4 bullet points of main observations */
+ keyFindings: string[];
+ /** 3 specific next steps ordered by importance */
+ priorityActions: string[];
+ /** Single most important action for the next 30 days */
+ recommendedFirstMove: string;
+ /** 2-3 risks to be aware of */
+ riskConsiderations: string[];
+}
+
+/**
+ * API response wrapper for Gemini
+ */
+export interface GeminiResponse {
+ candidates: Array<{
+ content: {
+ parts: Array<{
+ text: string;
+ }>;
+ };
+ finishReason: string;
+ }>;
+}
+
+/**
+ * Error response from Gemini API
+ */
+export interface GeminiError {
+ error: {
+ code: number;
+ message: string;
+ status: string;
+ };
+}
diff --git a/ai-readiness-accelerator/src/types/results.types.ts b/ai-readiness-accelerator/src/types/results.types.ts
new file mode 100644
index 0000000..6d1ac89
--- /dev/null
+++ b/ai-readiness-accelerator/src/types/results.types.ts
@@ -0,0 +1,308 @@
+/**
+ * Type definitions for assessment results and dashboard
+ * @module results.types
+ */
+
+import type { PillarId } from './assessment.types';
+
+/**
+ * Overall AI readiness level based on assessment scores
+ * - not-ready: Significant gaps that must be addressed before AI adoption
+ * - foundational: Basic capabilities in place, major improvements needed
+ * - developing: Making progress, some areas need attention
+ * - ready: Well-positioned for AI adoption with minor improvements
+ * - optimized: Fully prepared for advanced AI implementations
+ */
+export type ReadinessLevel =
+ | 'not-ready'
+ | 'foundational'
+ | 'developing'
+ | 'ready'
+ | 'optimized';
+
+/**
+ * Detailed information about a readiness level for display
+ */
+export interface ReadinessLevelInfo {
+ /** The readiness level identifier */
+ level: ReadinessLevel;
+
+ /** Human-readable label */
+ label: string;
+
+ /** Score range that maps to this level [min, max] */
+ scoreRange: [number, number];
+
+ /** Detailed description of what this level means */
+ description: string;
+
+ /** CSS color class or hex code for visual representation */
+ color: string;
+}
+
+/**
+ * Status classification for individual pillar scores
+ * - critical: Immediate attention required (0-20%)
+ * - needs-work: Significant improvement needed (21-40%)
+ * - adequate: Meets basic requirements (41-60%)
+ * - strong: Good capabilities (61-80%)
+ * - excellent: Best-in-class (81-100%)
+ */
+export type PillarStatus =
+ | 'critical'
+ | 'needs-work'
+ | 'adequate'
+ | 'strong'
+ | 'excellent';
+
+/**
+ * Individual question score within a pillar
+ */
+export interface QuestionScore {
+ /** Question identifier */
+ questionId: string;
+
+ /** Achieved score */
+ score: number;
+
+ /** Maximum possible score */
+ maxScore: number;
+
+ /** Category the question belongs to */
+ category: string;
+}
+
+/**
+ * Calculated score and status for a single pillar
+ */
+export interface PillarScore {
+ /** Identifier of the pillar */
+ pillarId: PillarId;
+
+ /** Display name of the pillar */
+ pillarName: string;
+
+ /** Calculated score for this pillar */
+ score: number;
+
+ /** Maximum possible score for this pillar */
+ maxScore: number;
+
+ /** Status classification based on score percentage */
+ status: PillarStatus;
+
+ /** Individual question scores for detailed breakdown */
+ questionScores: QuestionScore[];
+}
+
+/**
+ * Complete scoring results across all pillars
+ */
+export interface PillarScores {
+ /** Individual scores for each pillar */
+ pillars: PillarScore[];
+
+ /** Overall composite score (0-100) */
+ overall: number;
+
+ /** Determined readiness level based on overall score */
+ readinessLevel: ReadinessLevel;
+}
+
+/**
+ * Identified strength or gap from the assessment
+ */
+export interface StrengthGap {
+ /** Pillar this strength/gap relates to */
+ pillarId: PillarId;
+
+ /** Display name of the pillar */
+ pillarName: string;
+
+ /** Score percentage (0-100) */
+ score: number;
+
+ /** Descriptive text explaining the strength or gap */
+ description: string;
+}
+
+/**
+ * Collection of identified strengths and gaps
+ */
+export interface StrengthsGaps {
+ /** Top-performing areas */
+ strengths: StrengthGap[];
+
+ /** Areas needing improvement */
+ gaps: StrengthGap[];
+}
+
+/**
+ * Executive summary of assessment results
+ */
+export interface ExecutiveSummary {
+ /** High-level assessment narrative (2-3 sentences) */
+ overallAssessment: string;
+
+ /** Key findings from the assessment (3-5 items) */
+ keyFindings: string[];
+
+ /** Recommended priority actions (3-5 items) */
+ priorityActions: string[];
+
+ /** Single recommended first step to begin AI journey */
+ recommendedFirstMove: string;
+
+ /** Risk factors to consider during AI adoption */
+ riskConsiderations: string[];
+}
+
+/**
+ * Individual task within a roadmap phase
+ */
+export interface RoadmapTask {
+ /** Brief title of the task */
+ title: string;
+
+ /** Detailed description of what needs to be done */
+ description: string;
+
+ /** Responsible party or role (e.g., "IT Lead", "Managing Partner") */
+ owner: string;
+
+ /** Priority level for sequencing */
+ priority: 'critical' | 'high' | 'medium' | 'low';
+
+ /** Related pillar if applicable */
+ pillarId?: PillarId;
+}
+
+/**
+ * Phase within the implementation roadmap
+ */
+export interface RoadmapPhase {
+ /** Phase number (1-based) */
+ phase: number;
+
+ /** High-level objective for this phase */
+ objective: string;
+
+ /** Tasks to complete in this phase */
+ tasks: RoadmapTask[];
+
+ /** Measurable criteria for phase completion */
+ successCriteria: string[];
+}
+
+/**
+ * Complete implementation roadmap
+ */
+export interface Roadmap {
+ /** Ordered phases of implementation */
+ phases: RoadmapPhase[];
+
+ /** Firm name this roadmap was generated for */
+ generatedFor: string;
+}
+
+/**
+ * Readiness level configuration data
+ */
+export const READINESS_LEVELS: ReadinessLevelInfo[] = [
+ {
+ level: 'not-ready',
+ label: 'Not Ready',
+ scoreRange: [0, 20],
+ description:
+ 'Significant foundational gaps exist that must be addressed before AI adoption. Focus on establishing basic processes and data practices.',
+ color: '#dc2626', // red-600
+ },
+ {
+ level: 'foundational',
+ label: 'Foundational',
+ scoreRange: [21, 40],
+ description:
+ 'Basic capabilities are in place but major improvements are needed. Prioritize process documentation and data organization.',
+ color: '#ea580c', // orange-600
+ },
+ {
+ level: 'developing',
+ label: 'Developing',
+ scoreRange: [41, 60],
+ description:
+ 'Making solid progress toward AI readiness. Some areas need focused attention before implementing advanced AI tools.',
+ color: '#ca8a04', // yellow-600
+ },
+ {
+ level: 'ready',
+ label: 'Ready',
+ scoreRange: [61, 80],
+ description:
+ 'Well-positioned for AI adoption. Minor improvements will enhance success. Consider starting with targeted AI implementations.',
+ color: '#16a34a', // green-600
+ },
+ {
+ level: 'optimized',
+ label: 'Optimized',
+ scoreRange: [81, 100],
+ description:
+ 'Fully prepared for advanced AI implementations. Strong foundation across all pillars enables sophisticated AI adoption.',
+ color: '#0891b2', // cyan-600
+ },
+];
+
+/**
+ * Pillar status configuration data
+ */
+export const PILLAR_STATUS_INFO: Record<
+ PillarStatus,
+ { label: string; color: string; description: string }
+> = {
+ critical: {
+ label: 'Critical',
+ color: '#dc2626',
+ description: 'Immediate attention required',
+ },
+ 'needs-work': {
+ label: 'Needs Work',
+ color: '#ea580c',
+ description: 'Significant improvement needed',
+ },
+ adequate: {
+ label: 'Adequate',
+ color: '#ca8a04',
+ description: 'Meets basic requirements',
+ },
+ strong: {
+ label: 'Strong',
+ color: '#16a34a',
+ description: 'Good capabilities in place',
+ },
+ excellent: {
+ label: 'Excellent',
+ color: '#0891b2',
+ description: 'Best-in-class capabilities',
+ },
+};
+
+/**
+ * Determines pillar status based on score percentage
+ */
+export function getPillarStatus(scorePercent: number): PillarStatus {
+ if (scorePercent <= 20) return 'critical';
+ if (scorePercent <= 40) return 'needs-work';
+ if (scorePercent <= 60) return 'adequate';
+ if (scorePercent <= 80) return 'strong';
+ return 'excellent';
+}
+
+/**
+ * Determines readiness level based on overall score
+ */
+export function getReadinessLevel(overallScore: number): ReadinessLevel {
+ if (overallScore <= 20) return 'not-ready';
+ if (overallScore <= 40) return 'foundational';
+ if (overallScore <= 60) return 'developing';
+ if (overallScore <= 80) return 'ready';
+ return 'optimized';
+}
diff --git a/ai-readiness-accelerator/src/types/roi.types.ts b/ai-readiness-accelerator/src/types/roi.types.ts
new file mode 100644
index 0000000..6a38bc0
--- /dev/null
+++ b/ai-readiness-accelerator/src/types/roi.types.ts
@@ -0,0 +1,334 @@
+/**
+ * Type definitions for ROI calculator functionality
+ * @module roi.types
+ */
+
+/**
+ * Input parameters for ROI calculation
+ * All monetary values are in USD
+ */
+export interface ROIInputs {
+ /**
+ * Number of new client intakes processed per week
+ * Used to calculate time savings from automated intake
+ */
+ weeklyIntakeVolume: number;
+
+ /**
+ * Average time spent per intake in minutes
+ * Includes data entry, initial consultation scheduling, conflict checks
+ */
+ avgMinutesPerIntake: number;
+
+ /**
+ * Number of document drafting tasks per week
+ * Includes contracts, pleadings, letters, and other standard documents
+ */
+ weeklyDraftingTasks: number;
+
+ /**
+ * Average time spent per drafting task in minutes
+ * From initial template selection to final review
+ */
+ avgMinutesPerDraft: number;
+
+ /**
+ * Fully-loaded hourly cost for staff performing these tasks
+ * Should include salary, benefits, overhead
+ */
+ staffHourlyRate: number;
+
+ /**
+ * Billable rate for attorney time in USD/hour
+ * Used to calculate value of freed-up attorney capacity
+ */
+ billableRate: number;
+
+ /**
+ * Expected percentage of time savings from AI implementation
+ * Typical range: 20-50%
+ */
+ projectedTimeSavingsPercent: number;
+
+ /**
+ * Expected percentage reduction in errors/rework
+ * Typical range: 30-60%
+ */
+ projectedErrorReductionPercent: number;
+
+ /**
+ * One-time implementation cost (training, setup, consulting)
+ * In USD
+ */
+ implementationCost: number;
+
+ /**
+ * Monthly subscription/licensing cost for AI tools
+ * In USD per month
+ */
+ monthlyToolCost: number;
+}
+
+/**
+ * Projected ROI values for a single scenario
+ */
+export interface ROIProjection {
+ /**
+ * Total hours saved annually from AI implementation
+ */
+ annualHoursSaved: number;
+
+ /**
+ * Annual savings from reduced labor costs
+ * Calculated from hours saved * staff hourly rate
+ */
+ annualLaborSavings: number;
+
+ /**
+ * Annual value of increased capacity for billable work
+ * Calculated from freed attorney hours * billable rate
+ */
+ annualCapacityValue: number;
+
+ /**
+ * Total annual benefit (labor savings + capacity value)
+ */
+ totalAnnualBenefit: number;
+
+ /**
+ * Net value in year 1 after subtracting implementation and tool costs
+ */
+ year1NetValue: number;
+
+ /**
+ * Number of months to recoup implementation investment
+ */
+ paybackMonths: number;
+
+ /**
+ * Three-year return on investment percentage
+ * ((3-year benefit - 3-year cost) / 3-year cost) * 100
+ */
+ threeYearROI: number;
+}
+
+/**
+ * Complete ROI breakdown with multiple scenarios
+ */
+export interface ROIBreakdown {
+ /** Input parameters used for calculation */
+ inputs: ROIInputs;
+
+ /**
+ * Conservative estimate (lower bound)
+ * Uses 70% of projected savings percentages
+ */
+ conservative: ROIProjection;
+
+ /**
+ * Moderate estimate (expected case)
+ * Uses 100% of projected savings percentages
+ */
+ moderate: ROIProjection;
+
+ /**
+ * Optimistic estimate (upper bound)
+ * Uses 130% of projected savings percentages
+ */
+ optimistic: ROIProjection;
+}
+
+/**
+ * Default ROI input values for small law firms
+ */
+export const DEFAULT_ROI_INPUTS: ROIInputs = {
+ weeklyIntakeVolume: 10,
+ avgMinutesPerIntake: 30,
+ weeklyDraftingTasks: 15,
+ avgMinutesPerDraft: 45,
+ staffHourlyRate: 35,
+ billableRate: 250,
+ projectedTimeSavingsPercent: 35,
+ projectedErrorReductionPercent: 40,
+ implementationCost: 5000,
+ monthlyToolCost: 500,
+};
+
+/**
+ * ROI input field metadata for form generation
+ */
+export interface ROIInputField {
+ key: keyof ROIInputs;
+ label: string;
+ description: string;
+ unit: string;
+ min: number;
+ max: number;
+ step: number;
+}
+
+/**
+ * Field definitions for ROI calculator form
+ */
+export const ROI_INPUT_FIELDS: ROIInputField[] = [
+ {
+ key: 'weeklyIntakeVolume',
+ label: 'Weekly Intake Volume',
+ description: 'Number of new client intakes per week',
+ unit: 'intakes/week',
+ min: 1,
+ max: 200,
+ step: 1,
+ },
+ {
+ key: 'avgMinutesPerIntake',
+ label: 'Time per Intake',
+ description: 'Average minutes spent per intake',
+ unit: 'minutes',
+ min: 5,
+ max: 120,
+ step: 5,
+ },
+ {
+ key: 'weeklyDraftingTasks',
+ label: 'Weekly Drafting Tasks',
+ description: 'Number of documents drafted per week',
+ unit: 'documents/week',
+ min: 1,
+ max: 100,
+ step: 1,
+ },
+ {
+ key: 'avgMinutesPerDraft',
+ label: 'Time per Draft',
+ description: 'Average minutes per document draft',
+ unit: 'minutes',
+ min: 10,
+ max: 240,
+ step: 5,
+ },
+ {
+ key: 'staffHourlyRate',
+ label: 'Staff Hourly Rate',
+ description: 'Fully-loaded cost per hour for staff',
+ unit: '$/hour',
+ min: 15,
+ max: 150,
+ step: 5,
+ },
+ {
+ key: 'billableRate',
+ label: 'Attorney Billable Rate',
+ description: 'Billable rate for attorney time',
+ unit: '$/hour',
+ min: 100,
+ max: 1000,
+ step: 25,
+ },
+ {
+ key: 'projectedTimeSavingsPercent',
+ label: 'Time Savings',
+ description: 'Expected percentage of time saved with AI',
+ unit: '%',
+ min: 10,
+ max: 70,
+ step: 5,
+ },
+ {
+ key: 'projectedErrorReductionPercent',
+ label: 'Error Reduction',
+ description: 'Expected reduction in errors/rework',
+ unit: '%',
+ min: 10,
+ max: 80,
+ step: 5,
+ },
+ {
+ key: 'implementationCost',
+ label: 'Implementation Cost',
+ description: 'One-time setup, training, and consulting',
+ unit: '$',
+ min: 0,
+ max: 50000,
+ step: 500,
+ },
+ {
+ key: 'monthlyToolCost',
+ label: 'Monthly Tool Cost',
+ description: 'Monthly AI tool subscription cost',
+ unit: '$/month',
+ min: 0,
+ max: 5000,
+ step: 50,
+ },
+];
+
+/**
+ * Calculates ROI projection for given inputs and savings multiplier
+ */
+export function calculateROIProjection(
+ inputs: ROIInputs,
+ savingsMultiplier: number = 1.0
+): ROIProjection {
+ const adjustedTimeSavings =
+ inputs.projectedTimeSavingsPercent * savingsMultiplier;
+
+ // Calculate weekly time spent
+ const weeklyIntakeMinutes =
+ inputs.weeklyIntakeVolume * inputs.avgMinutesPerIntake;
+ const weeklyDraftingMinutes =
+ inputs.weeklyDraftingTasks * inputs.avgMinutesPerDraft;
+ const totalWeeklyMinutes = weeklyIntakeMinutes + weeklyDraftingMinutes;
+
+ // Calculate annual hours and savings
+ const annualMinutes = totalWeeklyMinutes * 52;
+ const annualHours = annualMinutes / 60;
+ const annualHoursSaved = annualHours * (adjustedTimeSavings / 100);
+
+ const annualLaborSavings = annualHoursSaved * inputs.staffHourlyRate;
+ const annualCapacityValue = annualHoursSaved * 0.3 * inputs.billableRate; // 30% of saved time becomes billable
+
+ const totalAnnualBenefit = annualLaborSavings + annualCapacityValue;
+
+ // Calculate costs
+ const annualToolCost = inputs.monthlyToolCost * 12;
+ const year1TotalCost = inputs.implementationCost + annualToolCost;
+ const year1NetValue = totalAnnualBenefit - year1TotalCost;
+
+ // Calculate payback period
+ const monthlyBenefit = totalAnnualBenefit / 12;
+ const paybackMonths =
+ monthlyBenefit > 0
+ ? Math.ceil(inputs.implementationCost / monthlyBenefit)
+ : 999;
+
+ // Calculate 3-year ROI
+ const threeYearBenefit = totalAnnualBenefit * 3;
+ const threeYearCost = inputs.implementationCost + annualToolCost * 3;
+ const threeYearROI =
+ threeYearCost > 0
+ ? ((threeYearBenefit - threeYearCost) / threeYearCost) * 100
+ : 0;
+
+ return {
+ annualHoursSaved: Math.round(annualHoursSaved),
+ annualLaborSavings: Math.round(annualLaborSavings),
+ annualCapacityValue: Math.round(annualCapacityValue),
+ totalAnnualBenefit: Math.round(totalAnnualBenefit),
+ year1NetValue: Math.round(year1NetValue),
+ paybackMonths,
+ threeYearROI: Math.round(threeYearROI),
+ };
+}
+
+/**
+ * Calculates complete ROI breakdown with all three scenarios
+ */
+export function calculateROIBreakdown(inputs: ROIInputs): ROIBreakdown {
+ return {
+ inputs,
+ conservative: calculateROIProjection(inputs, 0.7),
+ moderate: calculateROIProjection(inputs, 1.0),
+ optimistic: calculateROIProjection(inputs, 1.3),
+ };
+}
diff --git a/ai-readiness-accelerator/src/views/AssessmentView.tsx b/ai-readiness-accelerator/src/views/AssessmentView.tsx
new file mode 100644
index 0000000..f28e3de
--- /dev/null
+++ b/ai-readiness-accelerator/src/views/AssessmentView.tsx
@@ -0,0 +1,77 @@
+import React, { useCallback } from 'react';
+import { useAssessment } from '../hooks/useAssessment';
+import { AssessmentContainer } from '../components/assessment/AssessmentContainer';
+import type { AssessmentResponses, PillarId } from '../types/assessment.types';
+import type { FirmProfile } from '../types/firm.types';
+import type { QuestionResponse } from '../components/assessment/QuestionCard';
+
+export interface AssessmentViewProps {
+ /** Callback when assessment is completed */
+ onComplete: (data: { responses: AssessmentResponses; firmProfile: FirmProfile }) => void;
+ /** Callback when user navigates back */
+ onBack: () => void;
+}
+
+/**
+ * Assessment view that orchestrates the multi-step assessment flow.
+ * Uses useAssessment hook for state management and renders AssessmentContainer.
+ */
+export const AssessmentView: React.FC = ({ onComplete, onBack }) => {
+ const { state, actions } = useAssessment();
+
+ const { currentStep, responses, firmProfile } = state;
+ const { setFirmProfile, setResponse, nextStep, prevStep, complete } = actions;
+
+ // Total steps: 0 = firm profile, 1-6 = pillars
+ const totalSteps = 7;
+
+ /**
+ * Adapt the response change handler to match AssessmentContainer's expected signature
+ */
+ const handleResponseChange = useCallback(
+ (pillarId: PillarId, response: QuestionResponse) => {
+ setResponse(pillarId, response.questionId, response.value);
+ },
+ [setResponse]
+ );
+
+ /**
+ * Handle assessment submission when complete
+ */
+ const handleSubmit = () => {
+ if (firmProfile) {
+ complete();
+ onComplete({
+ responses,
+ firmProfile,
+ });
+ }
+ };
+
+ /**
+ * Handle back navigation - either go to previous step or exit assessment
+ */
+ const handleBack = () => {
+ if (currentStep === 0) {
+ onBack();
+ } else {
+ prevStep();
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default AssessmentView;
diff --git a/ai-readiness-accelerator/src/views/LandingView.tsx b/ai-readiness-accelerator/src/views/LandingView.tsx
new file mode 100644
index 0000000..2bdd3a3
--- /dev/null
+++ b/ai-readiness-accelerator/src/views/LandingView.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Hero } from '../components/landing/Hero';
+import { ValueProposition } from '../components/landing/ValueProposition';
+import { PillarOverview } from '../components/landing/PillarOverview';
+import { CTASection } from '../components/landing/CTASection';
+
+export interface LandingViewProps {
+ /** Callback when user starts the assessment */
+ onStartAssessment: () => void;
+}
+
+/**
+ * Landing page view that composes all landing page sections.
+ * Includes Hero, ValueProposition, PillarOverview, and CTASection components.
+ */
+export const LandingView: React.FC = ({ onStartAssessment }) => {
+ return (
+
+ {/* Hero section with main CTA */}
+
+
+ {/* Value proposition - what users will receive */}
+
+
+ {/* Pillar overview - what we assess */}
+
+
+ {/* Final CTA section */}
+
+
+ );
+};
+
+export default LandingView;
diff --git a/ai-readiness-accelerator/src/views/ResultsView.tsx b/ai-readiness-accelerator/src/views/ResultsView.tsx
new file mode 100644
index 0000000..bf1159a
--- /dev/null
+++ b/ai-readiness-accelerator/src/views/ResultsView.tsx
@@ -0,0 +1,111 @@
+import React, { useCallback } from 'react';
+import { useResults } from '../hooks/useResults';
+import { useROICalculator } from '../hooks/useROICalculator';
+import { useGeminiSummary } from '../hooks/useGeminiSummary';
+import { ResultsDashboard } from '../components/results/ResultsDashboard';
+import type { AssessmentResponses } from '../types/assessment.types';
+import type { FirmProfile } from '../types/firm.types';
+
+/** Calendly URL for booking consultation calls */
+const CALENDLY_URL = 'https://calendly.com/bizdeedz/ai-readiness-consultation';
+
+export interface AssessmentData {
+ /** User's assessment responses */
+ responses: AssessmentResponses;
+ /** User's firm profile information */
+ firmProfile: FirmProfile;
+}
+
+export interface ResultsViewProps {
+ /** Assessment data including responses and firm profile */
+ assessmentData: AssessmentData;
+ /** Callback when user resets to start over */
+ onReset: () => void;
+}
+
+/**
+ * Results view that displays assessment results, ROI calculations, and AI-generated summary.
+ * Uses multiple hooks to process and display comprehensive results.
+ */
+export const ResultsView: React.FC = ({ assessmentData, onReset }) => {
+ const { responses, firmProfile } = assessmentData;
+
+ // Calculate scores and results from assessment responses
+ const {
+ scores,
+ overallScore,
+ readinessLevel,
+ strengths,
+ gaps,
+ blockers,
+ pillarScores,
+ } = useResults({ responses, firmProfile });
+
+ // Calculate ROI projections
+ const {
+ inputs: roiInputs,
+ projections,
+ setInput,
+ resetToDefaults,
+ breakdown,
+ } = useROICalculator();
+
+ // Generate AI-powered executive summary
+ const {
+ summary,
+ loading: summaryLoading,
+ error: summaryError,
+ regenerate: retrySummary,
+ } = useGeminiSummary({
+ scores: pillarScores,
+ firmProfile,
+ enabled: true,
+ });
+
+ /**
+ * Opens Calendly in a new window for booking a consultation call
+ */
+ const handleBookCall = useCallback(() => {
+ window.open(CALENDLY_URL, '_blank', 'noopener,noreferrer');
+ }, []);
+
+ /**
+ * Handle ROI input changes
+ */
+ const handleRoiInputChange = useCallback(
+ (key: string, value: number) => {
+ setInput(key as keyof typeof roiInputs, value);
+ },
+ [setInput]
+ );
+
+ return (
+
+ );
+};
+
+export default ResultsView;
diff --git a/ai-readiness-accelerator/src/views/index.ts b/ai-readiness-accelerator/src/views/index.ts
new file mode 100644
index 0000000..21f4399
--- /dev/null
+++ b/ai-readiness-accelerator/src/views/index.ts
@@ -0,0 +1,8 @@
+export { LandingView } from './LandingView';
+export type { LandingViewProps } from './LandingView';
+
+export { AssessmentView } from './AssessmentView';
+export type { AssessmentViewProps } from './AssessmentView';
+
+export { ResultsView } from './ResultsView';
+export type { ResultsViewProps, AssessmentData } from './ResultsView';
diff --git a/ai-readiness-accelerator/src/vite-env.d.ts b/ai-readiness-accelerator/src/vite-env.d.ts
new file mode 100644
index 0000000..ccc6e87
--- /dev/null
+++ b/ai-readiness-accelerator/src/vite-env.d.ts
@@ -0,0 +1,9 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_GEMINI_API_KEY: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/ai-readiness-accelerator/tailwind.config.js b/ai-readiness-accelerator/tailwind.config.js
new file mode 100644
index 0000000..0ba59ab
--- /dev/null
+++ b/ai-readiness-accelerator/tailwind.config.js
@@ -0,0 +1,30 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: {
+ 50: '#eef2ff',
+ 100: '#e0e7ff',
+ 200: '#c7d2fe',
+ 300: '#a5b4fc',
+ 400: '#818cf8',
+ 500: '#6366f1',
+ 600: '#4f46e5',
+ 700: '#4338ca',
+ 800: '#3730a3',
+ 900: '#312e81',
+ 950: '#1e1b4b',
+ },
+ },
+ fontFamily: {
+ sans: ['Inter', 'system-ui', 'sans-serif'],
+ },
+ },
+ },
+ plugins: [],
+}
diff --git a/ai-readiness-accelerator/tsconfig.json b/ai-readiness-accelerator/tsconfig.json
new file mode 100644
index 0000000..5413626
--- /dev/null
+++ b/ai-readiness-accelerator/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/ai-readiness-accelerator/tsconfig.node.json b/ai-readiness-accelerator/tsconfig.node.json
new file mode 100644
index 0000000..97ede7e
--- /dev/null
+++ b/ai-readiness-accelerator/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/ai-readiness-accelerator/vite.config.ts b/ai-readiness-accelerator/vite.config.ts
new file mode 100644
index 0000000..9494ffe
--- /dev/null
+++ b/ai-readiness-accelerator/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ open: true
+ }
+})