diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..cd3b19e --- /dev/null +++ b/.env.local.example @@ -0,0 +1,14 @@ +# Copy to .env.local and fill in your values. +# Never commit .env.local to git. + +# Anthropic Claude API — required for document analysis and quote explainer +ANTHROPIC_API_KEY=sk-ant-... + +# Supabase — required for document storage and user profiles (Phase 1 optional) +NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... +SUPABASE_SERVICE_ROLE_KEY=eyJ... + +# Clerk — required for authentication (Phase 1 optional) +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... +CLERK_SECRET_KEY=sk_test_... diff --git a/.gitignore b/.gitignore index 01c8a5f..8d1ac13 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,16 @@ node_modules/ dist/ .env .env.* +!.env.local.example *.log .DS_Store Thumbs.db + +# Next.js +.next/ +out/ +.vercel +next-env.d.ts + +# Uploads (local dev only — use S3 in production) +uploads/ diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..d5a7d63 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,244 @@ +# FairDrive Auto — AI Roadmap + +> **Vision:** The first AI-native auto insurer that prices drivers on *who they are*, not just *where they've been insured*. FairDrive replaces the blunt U.S.-history penalty with a multi-dimensional FairScore — unlocking affordable coverage for 8M+ immigrants, international students, and new U.S. drivers who are systematically overcharged. + +--- + +## Why This Market Is Broken + +Traditional auto insurance uses U.S. insurance history as a proxy for risk. A driver with 15 years of safe driving in Germany, India, or Brazil arrives in the U.S. and is quoted the same premium as a teenager with no history at all. This is not actuarially sound — it is a data gap that incumbents have never bothered to close. + +FairDrive closes that gap with AI. + +--- + +## Core Differentiators + +| What incumbents do | What FairDrive does | +|---|---| +| Require 3+ years U.S. history for best rates | Credit verified international driving history (FairCredit) | +| Black-box pricing with no explanation | Transparent FairScore with factor breakdown | +| Static annual pricing | Continuous behavioral repricing (telematics) | +| One-size pricing ignores mileage | Pay-per-mile option for low-usage drivers | +| Require agents for every change | Self-serve AI coach with quantified savings actions | +| English-only, complex forms | Multi-language AI onboarding (Claude-powered) | +| No help understanding your quote | AI Quote Explainer: plain-language breakdown of every line item | + +--- + +## Phased Roadmap + +### Phase 1 — AI Foundation (0–6 months) + +**Goal:** Replace the demo prototype with a production-grade AI pricing engine and real document processing. + +#### 1.1 Tech Stack Modernization +- [ ] Migrate to Next.js 14+ (App Router) with TypeScript +- [ ] Replace CDN Three.js with local `@react-three/fiber` + `@react-three/drei` +- [ ] Set up PostgreSQL (Supabase) for user profiles and document metadata +- [ ] Add Clerk or Auth0 for authentication + MFA +- [ ] Deploy on Vercel with edge functions for sub-100ms API responses +- [ ] Add Sentry for error tracking, PostHog for product analytics + +#### 1.2 FairScore Engine (implemented in v0.1) +- [x] Multi-factor pricing model: vehicle risk, driving credential, geographic, mileage, coverage +- [x] FairCredit: international driving years mapped to actuarial credit rate +- [x] Traditional vs. FairDrive premium comparison +- [x] AI coach: ranked savings recommendations with quantified monthly impact +- [x] Score narrative: plain-language explanation of what's helping/hurting +- [ ] Calibrate factors against ISO/NCCI rate filings for 10 target states +- [ ] Bayesian confidence intervals on premium range (replace simple ±10%) +- [ ] A/B test FairScore vs. traditional form for conversion rate + +#### 1.3 Document Intelligence (Claude API) +- [ ] Integrate Claude claude-sonnet-4-6 for policy document OCR + extraction +- [ ] Extract: carrier name, effective dates, premium breakdown, coverage limits, deductibles +- [ ] Cross-reference extracted limits against state minimums — flag gaps automatically +- [ ] International document support: parse IDP (International Driving Permit), foreign no-claims letters +- [ ] Structured output schema (JSON) for downstream pricing and comparison +- [ ] Confidence scoring on extracted fields — human review queue for low-confidence items + +#### 1.4 Insurance Passport MVP +- [ ] Secure document upload (S3 + pre-signed URLs, client-side encryption) +- [ ] Six document types: U.S. license, foreign license, no-claims proof, vehicle registration, current policy, telematics consent +- [ ] Document verification status with estimated completion time +- [ ] Passport share link: generate a read-only URL drivers share with agents +- [ ] Privacy controls: per-document consent, granular partner sharing toggles +- [ ] GDPR/CCPA-compliant deletion + +--- + +### Phase 2 — Intelligence Layer (6–12 months) + +**Goal:** Build the AI systems that create a sustainable moat — behavioral pricing, document intelligence at scale, and multi-language onboarding. + +#### 2.1 Telematics & Behavioral Scoring +- [ ] SDK integration: Arity (Allstate), Verisk DriveAbility, or LexisNexis Telematics One +- [ ] Mobile SDK for iOS/Android: accelerometer + GPS trip scoring (no constant tracking) +- [ ] Drive Score factors: hard braking, acceleration, cornering, night driving %, distracted driving proxy +- [ ] Consent-first architecture: users see their own score before it's shared with any carrier +- [ ] Drive Score improves FairScore continuously — monthly premium repricing for good drivers + +#### 2.2 Claude-Powered Quote Explainer +- [ ] Streaming AI explanation of every quote line item (Claude claude-sonnet-4-6 via Anthropic API) +- [ ] Source-backed answers: every AI claim links to the policy document page or carrier rate filing +- [ ] Multi-turn conversation: "Why is my comprehensive so high?" → follow-up questions +- [ ] Guardrails: no guaranteed savings claims, no specific carrier recommendations without licensed-agent handoff +- [ ] Prompt caching for common query patterns — reduce latency and API cost +- [ ] Multilingual: Spanish, Hindi, Mandarin, Korean, Portuguese, Tagalog (top immigrant languages) + +#### 2.3 Market Intelligence Layer +- [ ] Carrier rate filing scraper: parse state DOI rate filings (public record) for 10 states +- [ ] Market median premium by: vehicle + state + profile tier + coverage level +- [ ] "You are paying X% above market median" benchmark shown to every user +- [ ] Carrier appetite model: which carriers are currently hungry for new-to-US customers +- [ ] Integration with Verisk LOCATION, ISO territories for geographic risk data + +#### 2.4 AI Onboarding +- [ ] Replace long-form application with conversational AI intake (Claude-powered chat) +- [ ] Dynamic question tree: ask only what's relevant based on prior answers +- [ ] Reduce average onboarding time from 12 minutes → under 3 minutes +- [ ] Sentiment analysis: detect frustrated users, route to human agent proactively +- [ ] Voice onboarding: Web Speech API for accessibility + +--- + +### Phase 3 — Market Entry (12–18 months) + +**Goal:** Go from intelligence platform to licensed insurance distribution. First revenue. + +#### 3.1 Licensed Distribution Infrastructure +- [ ] Apply for MGA (Managing General Agent) license in top 5 states (TX, CA, FL, NY, IL) +- [ ] Carrier appointments: Progressive, Mercury, Dairyland (appetite for new drivers) +- [ ] E&O insurance for agent operations +- [ ] Compliance engine: state-by-state rule matrix for what AI can/cannot recommend +- [ ] Licensed agent network: 1099 agents on standby for regulated handoffs +- [ ] ACORD XML integration for carrier submission + +#### 3.2 FairBind Product +- [ ] Real-time bindable quotes from 3+ carriers for each FairScore profile +- [ ] "Best carrier match" AI: routes each profile to the carrier currently pricing that risk favorably +- [ ] Instant digital ID cards (PDF + Apple/Google Wallet) +- [ ] Policy management: mid-term changes, payments, claims FNOL via chat +- [ ] Carrier API integrations: Progressive API, Openly, Coterie (modern carriers with APIs) + +#### 3.3 FairCredit Verification Network +- [ ] Partner with international insurers (UK, India, Germany, Canada) for direct record verification +- [ ] CARFAX International (existing database of 30+ countries) +- [ ] Certified translation service for foreign no-claims letters +- [ ] Risk model recalibration: measure loss ratio of FairCredit customers vs. traditional new drivers +- [ ] Actuarial sign-off: file rate adjustments for verified FairCredit cohort + +#### 3.4 Pay-Per-Mile Product +- [ ] White-label Metromile/Mile Auto for FairDrive customers +- [ ] Base rate (parked car coverage) + per-mile rate +- [ ] Target: immigrant drivers who commute by transit and drive <6,000 mi/year +- [ ] OBD-II plug-in device or mobile app odometer (no GPS required for basic tier) + +--- + +### Phase 4 — Scale (18–24 months) + +**Goal:** National footprint, category leadership, and defensible AI data moat. + +#### 4.1 Continuous Learning Pricing +- [ ] Cohort loss ratio analysis: FairCredit customers vs. traditional new-driver cohort +- [ ] Monthly model refresh: retrain FairScore weights on actual loss data +- [ ] Adverse selection detection: flag profiles scoring anomalously high against loss history +- [ ] Federated learning: improve Drive Score model without centralizing raw GPS data +- [ ] MLflow for experiment tracking, model versioning, and rollback + +#### 4.2 Community & Network Effects +- [ ] Referral program: immigrant community networks (Desi diaspora, Indian Professionals, etc.) +- [ ] Group policies: employers sponsoring FairDrive for H1B hires (B2B channel) +- [ ] University partnerships: international student insurance packages +- [ ] Affinity groups: alumni associations, cultural organizations + +#### 4.3 Claims Intelligence +- [ ] AI-first FNOL (First Notice of Loss): guided photo documentation via mobile +- [ ] Computer vision for damage severity estimation (reduce cycle time) +- [ ] Fraud detection: cross-reference claim patterns against network data +- [ ] Subrogation automation: identify recovery opportunities automatically + +#### 4.4 Platform Expansion +- [ ] Renters insurance bundling (natural adjacent product for apartment-dwelling immigrants) +- [ ] International health insurance bridge product (gap coverage during first 90 days) +- [ ] Data licensing to carriers: anonymized FairScore insights (opt-in only) + +--- + +## Technology Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ FairDrive Platform │ +├──────────────┬──────────────────────┬───────────────────┤ +│ Web/App │ AI Services │ Data & Carriers │ +├──────────────┼──────────────────────┼───────────────────┤ +│ Next.js 14 │ Claude claude-sonnet-4-6 │ PostgreSQL/Supabase│ +│ React Three │ - Quote Explainer │ S3 (documents) │ +│ Fiber (3D) │ - Doc Extraction │ Verisk APIs │ +│ Clerk Auth │ - Onboarding Chat │ LexisNexis │ +│ │ FairScore Engine │ ISO Territory │ +│ │ - Multi-factor model │ Carrier APIs: │ +│ │ - FairCredit calc │ - Progressive │ +│ │ - Coach recs │ - Mercury │ +│ │ Drive Score SDK │ - Openly │ +│ │ - Arity / Verisk │ ACORD XML │ +│ │ MLflow (versioning) │ Stripe (payments) │ +└──────────────┴──────────────────────┴───────────────────┘ +``` + +--- + +## Regulatory Strategy + +| Requirement | Approach | +|---|---| +| State insurance license | MGA structure — partner with licensed carrier fronts (Homepoint, Spinnaker) | +| Rate filing | File FairScore methodology with state DOIs; actuarial justification for FairCredit | +| AI fairness | Avoid protected classes; FairCredit uses years of history, not country of origin | +| Data privacy | CCPA/GDPR compliant; per-document consent; right to deletion | +| Document AI | Disclosures when AI is used for extraction; human review queue | +| Savings claims | Never promise specific savings; always "estimate" with disclaimer | + +--- + +## Revenue Model + +| Stream | Timing | Margin | +|---|---|---| +| Commission on bound policies (12–15% of premium) | Phase 3 | ~75% gross margin | +| Pay-per-mile policy administration fee | Phase 3 | ~65% | +| FairCredit verification premium ($15–25/verification) | Phase 2 | ~80% | +| Group policy admin fee (employer channel) | Phase 4 | ~70% | +| Carrier data licensing (opt-in anonymized FairScore data) | Phase 4 | ~90% | + +--- + +## Key Risk Guardrails + +1. **Never promise guaranteed savings** — always use "estimate" and show disclaimer +2. **International history = credit, not substitution** — FairCredit supplements US history, never replaces actuarial factors +3. **Immigration documents are optional** — never required; ITIN ≠ immigration status for pricing +4. **No protected class proxies** — country of birth is never an input; only driving years and verified records +5. **Licensed agent handoff** — AI coaches and explains; a licensed human binds the policy +6. **Source-backed AI answers** — every Claude response must cite the policy document or rate filing +7. **Consent before sharing** — users approve each partner data share; no silent routing + +--- + +## Success Metrics + +| Metric | Phase 1 | Phase 2 | Phase 3 | Phase 4 | +|---|---|---|---|---| +| Users with FairScore computed | 1,000 | 25,000 | 200,000 | 1M+ | +| Avg FairScore delta vs. traditional | +8 pts | +12 pts | +14 pts | +16 pts | +| Monthly savings surfaced (median) | $42 | $58 | $67 | $74 | +| Policies bound | 0 | 0 | 5,000 | 50,000 | +| Loss ratio (FairCredit cohort) | — | — | <72% | <68% | +| NPS | — | 58 | 65 | 72 | + +--- + +*FairDrive Auto — pricing drivers on who they are, not just where they've been insured.* diff --git a/ai-engine.js b/ai-engine.js new file mode 100644 index 0000000..b3c2279 --- /dev/null +++ b/ai-engine.js @@ -0,0 +1,266 @@ +// FairDrive AI Pricing Engine +// Multi-factor FairScore model: replaces blunt "no US history" penalty +// with a holistic assessment that credits international driving experience. + +const VEHICLE_DATA = { + accord: { + label: "2023 Honda Accord", + msrp: 28000, + repairIndex: 62, + theftIndex: 45, + safetyRating: 88, + isEV: false, + baseMonthly: 320, + }, + camry: { + label: "2023 Toyota Camry", + msrp: 27000, + repairIndex: 58, + theftIndex: 42, + safetyRating: 90, + isEV: false, + baseMonthly: 300, + }, + bmw: { + label: "2023 BMW 330i", + msrp: 44000, + repairIndex: 88, + theftIndex: 55, + safetyRating: 85, + isEV: false, + baseMonthly: 480, + }, + tesla: { + label: "2023 Tesla Model 3", + msrp: 41000, + repairIndex: 82, + theftIndex: 38, + safetyRating: 92, + isEV: true, + baseMonthly: 440, + }, + crv: { + label: "2023 Honda CR-V", + msrp: 31000, + repairIndex: 60, + theftIndex: 40, + safetyRating: 86, + isEV: false, + baseMonthly: 285, + }, +}; + +// State rate multipliers relative to national average (1.0) +const STATE_MULTIPLIERS = { + CA: 1.42, FL: 1.38, MI: 1.52, NY: 1.35, TX: 1.28, + IL: 1.18, NJ: 1.45, MA: 1.25, MD: 1.30, GA: 1.22, + WA: 1.12, CO: 1.15, AZ: 1.19, PA: 1.10, VA: 1.07, + NC: 1.08, OH: 1.05, MN: 1.06, WI: 1.01, TN: 1.03, +}; + +// Mapping of international driving years to credit rate (0.0 – 0.30) +// FairCredit: the core differentiator — foreign history is not zero. +const INTL_CREDIT_TIERS = [ + { minYears: 10, rate: 0.30 }, + { minYears: 5, rate: 0.22 }, + { minYears: 3, rate: 0.15 }, + { minYears: 1, rate: 0.08 }, + { minYears: 0, rate: 0.00 }, +]; + +function getIntlCreditRate(years) { + const tier = INTL_CREDIT_TIERS.find(t => years >= t.minYears); + return tier ? tier.rate : 0; +} + +// FairScore: 0–100. Higher = lower risk = lower premium. +// Market average is ~58. New-to-US drivers with no history score ~32–40. +// With FairCredit applied, those same drivers can reach 50–65. +function computeFairScore(inputs) { + const { vehicle, usHistoryYears, intlHistoryYears, annualMiles, state, cleanRecord, defensiveCourse } = inputs; + const vd = VEHICLE_DATA[vehicle]; + + // Factor 1: Vehicle Risk (max 30 pts) + // Lower repair cost + lower theft + higher safety = more points + const repairPenalty = (vd.repairIndex / 100) * 12; + const theftPenalty = (vd.theftIndex / 100) * 6; + const safetyBonus = (vd.safetyRating / 100) * 12; + const vehicleScore = Math.max(0, Math.min(30, 30 - repairPenalty - theftPenalty + safetyBonus - 12)); + + // Factor 2: Driving Credential (max 35 pts) + // FairCredit: international history gets partial credit instead of zero. + const usCredit = Math.min(usHistoryYears * 4, 20); + const intlCredit = getIntlCreditRate(intlHistoryYears) * 20; + const recordBonus = cleanRecord ? 8 : 0; + const courseBonus = defensiveCourse ? 5 : 0; + // Cold-start penalty only if truly zero history (no US, no intl) + const coldStart = (usHistoryYears === 0 && intlHistoryYears === 0) ? -15 : 0; + const credentialScore = Math.max(0, Math.min(35, usCredit + intlCredit + recordBonus + courseBonus + coldStart)); + + // Factor 3: Geographic Risk (max 20 pts) + const stateMult = STATE_MULTIPLIERS[state] || 1.10; + const geoScore = Math.max(0, Math.min(20, 20 - (stateMult - 1.0) * 50)); + + // Factor 4: Mileage Pattern (max 10 pts) + // Low mileage = lower exposure + const mileScore = + annualMiles <= 5000 ? 10 : + annualMiles <= 7500 ? 8 : + annualMiles <= 12000 ? 6 : + annualMiles <= 20000 ? 3 : 1; + + // Factor 5: Coverage Behavior (max 5 pts — higher deductibles show responsibility) + const coverageScore = 5; + + const raw = vehicleScore + credentialScore + geoScore + mileScore + coverageScore; + const total = Math.max(10, Math.min(95, Math.round(raw))); + + return { + total, + factors: { + vehicle: { score: Math.round(Math.max(0, vehicleScore)), max: 30, label: "Vehicle Risk" }, + credential: { score: Math.round(Math.max(0, credentialScore)), max: 35, label: "Driving Credential" }, + geographic: { score: Math.round(Math.max(0, geoScore)), max: 20, label: "Geographic Risk" }, + mileage: { score: mileScore, max: 10, label: "Mileage Pattern" }, + coverage: { score: coverageScore, max: 5, label: "Coverage Behavior" }, + }, + }; +} + +// What a traditional insurer would score — ignoring international history. +function computeTraditionalScore(inputs) { + return computeFairScore({ ...inputs, intlHistoryYears: 0 }); +} + +// Monthly premium from FairScore + state multiplier + coverage level. +function computePremium(inputs, fairScore) { + const { vehicle, state, coverageLevel } = inputs; + const vd = VEHICLE_DATA[vehicle]; + const stateMult = STATE_MULTIPLIERS[state] || 1.10; + const coverageMult = + coverageLevel === "basic" ? 0.72 : + coverageLevel === "premium" ? 1.28 : 1.0; + + // Each point above 58 shaves ~1.2% off premium; below 58 adds 1.2% + const fairAdj = 1 - ((fairScore.total - 58) * 0.012); + const monthly = Math.max(80, Math.round(vd.baseMonthly * stateMult * coverageMult * fairAdj)); + + const tradScore = computeTraditionalScore(inputs); + const tradAdj = 1 - ((tradScore.total - 58) * 0.012); + const tradMonthly = Math.max(80, Math.round(vd.baseMonthly * stateMult * coverageMult * tradAdj)); + + const fairSavings = Math.max(0, tradMonthly - monthly); + + return { + monthly, + tradMonthly, + fairSavings, + range: [Math.round(monthly * 0.90), Math.round(monthly * 1.12)], + annualEstimate: monthly * 12, + }; +} + +// AI coach: ranked, savings-quantified action items. +function getCoachRecommendations(inputs, fairScore, premium) { + const recs = []; + const vd = VEHICLE_DATA[inputs.vehicle]; + + if (inputs.intlHistoryYears >= 2 && inputs.usHistoryYears < 2) { + recs.push({ + priority: 1, + icon: "✦", + title: "Submit International Driving Record (FairCredit)", + detail: `${inputs.intlHistoryYears} years of verified foreign driving qualifies for FairCredit recognition. Most partner carriers accept a certified no-claims letter from your home country insurer.`, + action: "Add to Passport", + savings: Math.round(premium.fairSavings * 0.65), + }); + } + + if (!inputs.cleanRecord) { + recs.push({ + priority: 2, + icon: "◎", + title: "Clear or Dispute DMV Record Items", + detail: "Even one at-fault incident can add 30–50% to your premium for 3 years. If the incident was minor or ≥3 years ago, some carriers will re-rate sooner.", + action: "Review record", + savings: Math.round(premium.monthly * 0.22), + }); + } + + if (!inputs.defensiveCourse) { + recs.push({ + priority: 3, + icon: "◆", + title: "Complete a Defensive Driving Course", + detail: "Online courses take 4–6 hours and cost $20–$50. Most U.S. carriers offer 5–10% discount for verified completion and it signals commitment to safe driving.", + action: "Find a course", + savings: Math.round(premium.monthly * 0.07), + }); + } + + if (inputs.annualMiles > 8000) { + recs.push({ + priority: 4, + icon: "◈", + title: "Enroll in Pay-Per-Mile Coverage", + detail: `At ${inputs.annualMiles.toLocaleString()} miles/year you're paying for more exposure than you use. Usage-based policies from carriers like Metromile or Mile Auto can save 20–40% for drivers under 10,000 mi/year.`, + action: "Check eligibility", + savings: Math.round(premium.monthly * 0.18), + }); + } + + if (vd.repairIndex > 75) { + recs.push({ + priority: 5, + icon: "◇", + title: `Switch to a Lower Repair-Cost Vehicle`, + detail: `The ${vd.label} has a repair cost index of ${vd.repairIndex}/100 — well above average. A Honda CR-V (index 60) or Toyota Camry (index 58) could reduce your premium significantly without sacrificing quality.`, + action: "Compare vehicles", + savings: Math.round(premium.monthly * 0.17), + }); + } + + recs.push({ + priority: 6, + icon: "◉", + title: "Raise Collision Deductible to $1,000", + detail: "Moving from a $500 to $1,000 collision deductible typically reduces monthly premium by 8–12%. Keep the difference in an emergency fund — statistically, most drivers go years without a claim.", + action: "Model the tradeoff", + savings: Math.round(premium.monthly * 0.10), + }); + + return recs.sort((a, b) => a.priority - b.priority); +} + +// Describe what's hurting and helping the FairScore +function getScoreNarrative(inputs, fairScore) { + const helps = []; + const hurts = []; + const vd = VEHICLE_DATA[inputs.vehicle]; + + if (vd.safetyRating >= 88) helps.push(`${vd.label} has a top safety rating`); + if (vd.repairIndex > 75) hurts.push(`${vd.label} has above-average repair costs`); + if (inputs.intlHistoryYears >= 3) helps.push(`${inputs.intlHistoryYears} years of international driving history (FairCredit)`); + if (inputs.usHistoryYears === 0 && inputs.intlHistoryYears === 0) hurts.push("no driving history on file anywhere"); + if (inputs.cleanRecord) helps.push("clean driving record"); + if (!inputs.cleanRecord) hurts.push("incidents on driving record"); + if (inputs.defensiveCourse) helps.push("defensive driving certification"); + if (inputs.annualMiles <= 7500) helps.push("low annual mileage"); + if (inputs.annualMiles > 15000) hurts.push("high annual mileage increases exposure"); + + const stateMult = STATE_MULTIPLIERS[inputs.state] || 1.10; + if (stateMult >= 1.30) hurts.push(`${inputs.state} is a high-cost insurance state`); + if (stateMult <= 1.08) helps.push(`${inputs.state} has below-average insurance rates`); + + return { helps, hurts }; +} + +window.FairDriveAI = { + computeFairScore, + computeTraditionalScore, + computePremium, + getCoachRecommendations, + getScoreNarrative, + VEHICLE_DATA, + STATE_MULTIPLIERS, +}; diff --git a/app.js b/app.js index 643f4d4..3f06291 100644 --- a/app.js +++ b/app.js @@ -1,14 +1,16 @@ -const viewButtons = document.querySelectorAll("[data-view]"); +// FairDrive App — view routing, vehicle selection, AI engine wiring + +const viewButtons = document.querySelectorAll("[data-view]"); const targetButtons = document.querySelectorAll("[data-view-target]"); -const panels = document.querySelectorAll("[data-view-panel]"); -const quoteUpload = document.querySelector("#quoteUpload"); +const panels = document.querySelectorAll("[data-view-panel]"); +const quoteUpload = document.querySelector("#quoteUpload"); const quoteExplanation = document.querySelector("#quoteExplanation"); const estimatorForm = document.querySelector("#estimatorForm"); const vehicleSelect = document.querySelector("#vehicleSelect"); const garageVehicleSelect = document.querySelector("#garageVehicleSelect"); const selectedVehicleName = document.querySelector("#selectedVehicleName"); -const vehicleDesignLabel = document.querySelector("#vehicleDesignLabel"); -const historySelect = document.querySelector("#historySelect"); +const vehicleDesignLabel = document.querySelector("#vehicleDesignLabel"); +const historySelect = document.querySelector("#historySelect"); const estimateResult = document.querySelector("#estimateResult"); const popover = document.querySelector("#coveragePopover"); const hotspots = document.querySelectorAll(".hotspot"); @@ -17,97 +19,261 @@ let currentVehicle = "accord"; const vehicleModels = { accord: { label: "2023 Honda Accord", design: "Mid-size sedan" }, - camry: { label: "2023 Toyota Camry", design: "Angular sedan" }, - bmw: { label: "2023 BMW 330i", design: "Sport luxury sedan" }, - tesla: { label: "2023 Tesla Model 3", design: "Minimal EV fastback" }, - crv: { label: "2023 Honda CR-V", design: "Compact SUV" }, + camry: { label: "2023 Toyota Camry", design: "Angular sedan" }, + bmw: { label: "2023 BMW 330i", design: "Sport luxury sedan" }, + tesla: { label: "2023 Tesla Model 3",design: "Minimal EV fastback" }, + crv: { label: "2023 Honda CR-V", design: "Compact SUV" }, }; const estimates = { accord: { none: [310, 430], short: [250, 350], long: [190, 280] }, - camry: { none: [290, 410], short: [235, 330], long: [180, 265] }, - bmw: { none: [430, 590], short: [360, 500], long: [275, 405] }, - tesla: { none: [390, 540], short: [320, 455], long: [245, 370] }, - crv: { none: [275, 390], short: [225, 315], long: [175, 255] }, + camry: { none: [290, 410], short: [235, 330], long: [180, 265] }, + bmw: { none: [430, 590], short: [360, 500], long: [275, 405] }, + tesla: { none: [390, 540], short: [320, 455], long: [245, 370] }, + crv: { none: [275, 390], short: [225, 315], long: [175, 255] }, }; const coverageCopy = { - Collision: "Protects your car after an accident with another vehicle or object.", - Comprehensive: "Protects against theft, hail, glass damage, flood, fire, and other non-collision events.", - Medical: "Helps cover medical costs for you or passengers after a covered accident.", + Collision: "Protects your car after an accident with another vehicle or object.", + Comprehensive:"Protects against theft, hail, glass damage, flood, fire, and other non-collision events.", + Medical: "Helps cover medical costs for you or passengers after a covered accident.", }; -function setView(view) { - viewButtons.forEach((button) => { - button.classList.toggle("active", button.dataset.view === view); - }); +// ── View routing ────────────────────────────────────────────────────────────── - panels.forEach((panel) => { - panel.classList.toggle("active", panel.dataset.viewPanel === view); - }); +function setView(view) { + viewButtons.forEach(b => b.classList.toggle("active", b.dataset.view === view)); + panels.forEach(p => p.classList.toggle("active", p.dataset.viewPanel === view)); } function setVehicle(vehicle) { currentVehicle = vehicle; const model = vehicleModels[vehicle]; selectedVehicleName.textContent = model.label; - vehicleDesignLabel.textContent = model.design; + vehicleDesignLabel.textContent = model.design; garageVehicleSelect.value = vehicle; vehicleSelect.value = vehicle; window.FairDriveCarViewer?.setVehicleModel(vehicle); } -viewButtons.forEach((button) => { - button.addEventListener("click", () => setView(button.dataset.view)); -}); +viewButtons.forEach(b => b.addEventListener("click", () => setView(b.dataset.view))); +targetButtons.forEach(b => b.addEventListener("click", () => setView(b.dataset.viewTarget))); +garageVehicleSelect?.addEventListener("change", () => setVehicle(garageVehicleSelect.value)); +vehicleSelect?.addEventListener("change", () => setVehicle(vehicleSelect.value)); -targetButtons.forEach((button) => { - button.addEventListener("click", () => setView(button.dataset.viewTarget)); -}); +// ── Coverage hotspots ───────────────────────────────────────────────────────── -garageVehicleSelect?.addEventListener("change", () => { - setVehicle(garageVehicleSelect.value); +hotspots.forEach(hs => { + hs.addEventListener("mouseenter", () => { + const label = hs.dataset.coverage; + popover.innerHTML = ` +

Coverage

+ ${label} + ${coverageCopy[label]} + `; + }); }); -vehicleSelect?.addEventListener("change", () => { - setVehicle(vehicleSelect.value); -}); +// ── Quote upload (simulated OCR) ────────────────────────────────────────────── quoteUpload?.addEventListener("change", () => { const file = quoteUpload.files?.[0]; if (!file) return; - quoteExplanation.innerHTML = ` -

Simulated extraction

+

Simulated extraction — ${file.name}

- ${file.name} is ready for analysis. In the MVP, this will extract premium, - deductibles, liability limits, discounts, and likely price drivers. + In production, Claude AI will extract: carrier name, effective dates, premium + breakdown, coverage limits, deductibles, and flag gaps against state minimums. + International no-claims letters from 30+ countries are also parsed.

`; }); -estimatorForm?.addEventListener("submit", (event) => { - event.preventDefault(); - const vehicle = vehicleSelect.value; - const history = historySelect.value; - const [low, high] = estimates[vehicle][history]; +// ── Simple estimator ────────────────────────────────────────────────────────── +estimatorForm?.addEventListener("submit", e => { + e.preventDefault(); + const v = vehicleSelect.value; + const h = historySelect.value; + const [lo, hi] = estimates[v][h]; estimateResult.innerHTML = ` - ${vehicleModels[vehicle].label} - $${low} - $${high}/mo -

This range is a planning estimate. Real quotes depend on garaging address, coverage, carrier appetite, driving record, and state rules.

+ ${vehicleModels[v].label} + $${lo} – $${hi}/mo +

This range is a planning estimate. Use the FairScore tab to see the impact of + your international driving history, state, and mileage on the full model.

`; }); -hotspots.forEach((hotspot) => { - hotspot.addEventListener("mouseenter", () => { - const label = hotspot.dataset.coverage; - popover.innerHTML = ` -

Coverage

- ${label} - ${coverageCopy[label]} +// ── FairScore engine ────────────────────────────────────────────────────────── + +const fairscoreForm = document.querySelector("#fairscoreForm"); +const fairscoreResult = document.querySelector("#fairscoreResult"); +const premiumCompPanel = document.querySelector("#premiumComparePanel"); +const scoreNumber = document.querySelector("#scoreNumber"); +const scoreTier = document.querySelector("#scoreTier"); +const scoreBench = document.querySelector("#scoreBench"); +const gaugeFill = document.querySelector("#gaugeFill"); +const factorBarsEl = document.querySelector("#factorBars"); +const scoreNarrativeEl = document.querySelector("#scoreNarrative"); +const fairMonthlyEl = document.querySelector("#fairMonthly"); +const tradMonthlyEl = document.querySelector("#tradMonthly"); +const savingsBannerEl = document.querySelector("#savingsBanner"); +const savingsAmountEl = document.querySelector("#savingsAmount"); +const annualEstEl = document.querySelector("#annualEstimate"); +const premiumRangeEl = document.querySelector("#premiumRange"); +const fairscorePill = document.querySelector("#fairscoreStatusPill"); +const coachList = document.querySelector("#coachList"); +const coachPill = document.querySelector("#coachPill"); +const coachHeading = document.querySelector("#coachHeading"); +const totalSavingsEl = document.querySelector("#totalSavingsDisplay"); + +// Gauge arc: the SVG path spans 0°→180° (semicircle); map score 0–100 → dashoffset. +// Arc length of "M10,65 A55,55,0,0,1,110,65" ≈ 172.8 px. +const GAUGE_ARC = 172.8; + +function setGauge(score) { + const filled = (score / 100) * GAUGE_ARC; + gaugeFill.style.strokeDasharray = `${filled} ${GAUGE_ARC}`; + gaugeFill.style.strokeDashoffset = 0; + const hue = Math.round(score * 1.2); // 0 = red, 72 = green-ish at 60 + gaugeFill.style.stroke = `hsl(${hue}, 72%, 52%)`; +} + +function scoreTierLabel(score) { + if (score >= 80) return { label: "Excellent", cls: "tier-green" }; + if (score >= 65) return { label: "Good", cls: "tier-good" }; + if (score >= 50) return { label: "Fair", cls: "tier-fair" }; + if (score >= 35) return { label: "Building", cls: "tier-warn" }; + return { label: "High Risk", cls: "tier-danger" }; +} + +function renderFactorBars(factors) { + factorBarsEl.innerHTML = Object.values(factors).map(f => { + const pct = Math.round((f.score / f.max) * 100); + const color = pct >= 70 ? "var(--brand)" : pct >= 45 ? "var(--brand-2)" : "var(--warn)"; + return ` +
+
+ ${f.label} + ${f.score} / ${f.max} +
+
+
+
+
`; - }); + }).join(""); +} + +function renderNarrative(helps, hurts) { + const helpHtml = helps.length ? ` +
+

Helping your score

+ +
` : ""; + const hurtHtml = hurts.length ? ` +
+

Hurting your score

+ +
` : ""; + scoreNarrativeEl.innerHTML = helpHtml + hurtHtml; +} + +function renderCoach(recs, totalSavings) { + coachList.innerHTML = recs.map(r => ` + + `).join(""); + coachHeading.textContent = `${recs.length} savings actions found`; + coachPill.textContent = "AI-powered"; + if (totalSavingsEl) totalSavingsEl.textContent = `$${totalSavings}/mo potential`; +} + +// Stored last computed results so Coach view can render them +let lastCoachRecs = null; +let lastTotalSavings = 0; + +fairscoreForm?.addEventListener("submit", e => { + e.preventDefault(); + const AI = window.FairDriveAI; + if (!AI) return; + + const inputs = { + vehicle: document.querySelector("#fsVehicle").value, + state: document.querySelector("#fsState").value, + usHistoryYears: parseInt(document.querySelector("#fsUsHistory").value, 10), + intlHistoryYears: parseInt(document.querySelector("#fsIntlHistory").value, 10), + annualMiles: parseInt(document.querySelector("#fsMileage").value, 10), + coverageLevel: document.querySelector("#fsCoverage").value, + cleanRecord: document.querySelector("#fsCleanRecord").checked, + defensiveCourse: document.querySelector("#fsDefensive").checked, + }; + + const fairScore = AI.computeFairScore(inputs); + const premium = AI.computePremium(inputs, fairScore); + const narrative = AI.getScoreNarrative(inputs, fairScore); + const recs = AI.getCoachRecommendations(inputs, fairScore, premium); + const totalSav = recs.reduce((sum, r) => sum + (r.savings || 0), 0); + + // Store for coach view + lastCoachRecs = recs; + lastTotalSavings = totalSav; + + // Reveal results + fairscoreResult.hidden = false; + premiumCompPanel.hidden = false; + + // Score gauge + scoreNumber.textContent = fairScore.total; + setGauge(fairScore.total); + const tier = scoreTierLabel(fairScore.total); + scoreTier.textContent = tier.label; + scoreTier.className = `score-tier ${tier.cls}`; + scoreBench.textContent = `Market average: 58 · Your score: ${fairScore.total}`; + fairscorePill.textContent = `Score: ${fairScore.total}`; + + // Factor bars + renderFactorBars(fairScore.factors); + + // Narrative + renderNarrative(narrative.helps, narrative.hurts); + + // Premium comparison + fairMonthlyEl.textContent = `$${premium.monthly}`; + tradMonthlyEl.textContent = `$${premium.tradMonthly}`; + annualEstEl.textContent = `$${premium.annualEstimate.toLocaleString()}/yr`; + premiumRangeEl.textContent = `$${premium.range[0]} – $${premium.range[1]}/mo`; + + if (premium.fairSavings > 0) { + savingsBannerEl.hidden = false; + savingsAmountEl.textContent = `$${premium.fairSavings}/mo`; + } else { + savingsBannerEl.hidden = true; + } + + // Sync vehicle to garage + setVehicle(inputs.vehicle); + + // Pre-populate coach + renderCoach(recs, totalSav); + if (totalSavingsEl) totalSavingsEl.textContent = `$${totalSav}/mo potential`; }); +// Render coach when user navigates there directly (if FairScore already computed) +viewButtons.forEach(b => { + if (b.dataset.view === "coach") { + b.addEventListener("click", () => { + if (lastCoachRecs) renderCoach(lastCoachRecs, lastTotalSavings); + }); + } +}); + +// ── Init ────────────────────────────────────────────────────────────────────── + setVehicle(currentVehicle); diff --git a/index.html b/index.html index 3a240a2..7a65d85 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,9 @@ + @@ -40,8 +43,8 @@ @@ -71,11 +74,11 @@

$386/mo estimated premium

insurance history and the vehicle has above-average repair cost.

- -
@@ -111,6 +114,7 @@

$386/mo estimated premium

+
@@ -158,13 +162,169 @@

62% complete

Savings path

3 actions found

    -
  • Upload no-claims letter
  • -
  • Compare $1,000 deductible
  • -
  • Review uninsured motorist gap
  • +
  • Upload no-claims letter — est. save $38/mo
  • +
  • Compare $1,000 deductible — est. save $32/mo
  • +
  • Add uninsured motorist coverage
+ +
+
+
+
+

FairScore — AI Pricing Intelligence

+

Your multi-factor driver risk profile

+
+ Not computed +
+ +
+
+ + + + + + +
+
+ + +
+ +
+ + +
+ + +
+ +
@@ -178,14 +338,15 @@

Upload or review your current policy

Drop your quote or policy here - PDF, JPG, or PNG. This skeleton simulates extraction. + PDF, JPG, or PNG. In production, Claude AI extracts every line item.

Example explanation

Your premium appears high because the profile has limited prior U.S. insurance history, the collision deductible is low, and uninsured - motorist coverage is missing. + motorist coverage is missing. The vehicle repair cost index is 62/100, + above the sedan average of 55.

@@ -204,9 +365,14 @@

Upload or review your current policy

Renewal 42 days +
+ Repair cost index + 62 / 100 +
+

Auto Insurance Passport

@@ -225,12 +391,14 @@

Evidence that helps tell your full driving story

Privacy

User controlled

- Immigration documents should stay optional unless legally required. - The product should ask for consent before AI processing or partner sharing. + Immigration documents are optional. FairDrive asks for consent + before any AI processing or partner sharing. Country of birth is + never used as a pricing factor.

+

Before-you-buy estimator

@@ -258,17 +426,23 @@

Check insurance cost before choosing a car

Estimated range - $310 - $430/mo -

High repair costs and no prior U.S. insurance history can increase the range.

+ $310 – $430/mo +

High repair costs and no prior U.S. insurance history can increase the range. Use the FairScore estimator to see the impact of your international driving history.

+
-

Premium reduction coach

-

Next best actions

-
+
+
+

AI Premium Coach

+

Next best actions

+
+ Compute FairScore first +
+
+ +
+

Total savings potential

+
+ All actions combined + +
+

+ These are planning estimates. Real savings depend on carrier appetite, + state regulations, and verified documentation. A licensed agent reviews + before any policy is bound. +

+
+ diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..f13030d --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + serverComponentsExternalPackages: ["three"], + }, +}; + +export default nextConfig; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..ae1e992 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,9 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + experimental: { + serverComponentsExternalPackages: ["three"], + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..642e45e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,974 @@ +{ + "name": "fairdrive-auto", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fairdrive-auto", + "version": "0.2.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.167.1" + }, + "devDependencies": { + "@types/node": "^22.5.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/three": "^0.167.0", + "typescript": "^5.5.4" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", + "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@next/env": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "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.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "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/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.167.2", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.2.tgz", + "integrity": "sha512-onxnIUNYpXcZJ5DTiIsxfnr4F9kAWkkxAUWx5yqzz/u0a4IygCLCjMuOl2DEeCxyJdJ2nOJZvKpu48sBMqfmkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "~23.1.2", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "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/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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/next": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.35", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/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/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/three": { + "version": "0.167.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz", + "integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "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/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3b9acb6 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "fairdrive-auto", + "version": "0.2.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "type-check": "tsc --noEmit", + "legacy:serve": "node server.js" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.167.1" + }, + "devDependencies": { + "@types/node": "^22.5.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/three": "^0.167.0", + "typescript": "^5.5.4" + } +} diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts new file mode 100644 index 0000000..a63e174 --- /dev/null +++ b/src/app/api/analyze/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from "next/server"; +import type { PolicyExtraction } from "@/lib/types"; + +const MAX_FILE_BYTES = 10 * 1024 * 1024; // 10 MB + +// Mock extraction returned when ANTHROPIC_API_KEY is not configured. +// Shows a realistic policy with coverage gaps so the UI can be demoed. +function mockExtraction(state: string): PolicyExtraction { + return { + carrierName: "State Farm", + effectiveDate: "2024-07-01", + expirationDate: "2025-07-01", + monthlyPremium: 386, + annualPremium: 4632, + coverages: { + liability: { bodily: "100/300", property: "100" }, + collision: { deductible: 500, active: true }, + comprehensive: { deductible: 250, active: true }, + uninsuredMotorist: { active: false }, + medical: { limit: undefined, active: false }, + }, + stateMinimumsComparison: { + liabilityAdequate: true, + uninsuredMotoristPresent: false, + gaps: + state === "NY" || state === "IL" || state === "WI" || state === "NC" || state === "VA" + ? [`Uninsured motorist coverage is required in ${state} but not present in policy`] + : [], + }, + confidenceScore: 0, + extractionWarnings: [ + "AI analysis not configured — set ANTHROPIC_API_KEY to enable real document extraction.", + "This is a sample policy shown for demonstration purposes.", + ], + }; +} + +export async function POST(req: NextRequest): Promise { + let formData: FormData; + try { + formData = await req.formData(); + } catch { + return NextResponse.json( + { error: "Request must be multipart/form-data." }, + { status: 400 }, + ); + } + + const file = formData.get("file"); + const state = formData.get("state"); + + if (!file || !(file instanceof File)) { + return NextResponse.json({ error: "No file uploaded." }, { status: 400 }); + } + + if (typeof state !== "string" || !/^[A-Za-z]{2}$/.test(state)) { + return NextResponse.json( + { error: '"state" must be a 2-letter state code.' }, + { status: 400 }, + ); + } + + if (file.size > MAX_FILE_BYTES) { + return NextResponse.json( + { error: "File exceeds 10 MB limit." }, + { status: 413 }, + ); + } + + const allowedTypes = ["image/jpeg", "image/png", "application/pdf"]; + if (!allowedTypes.includes(file.type)) { + return NextResponse.json( + { error: `Unsupported file type: ${file.type}. Use JPEG, PNG, or PDF.` }, + { status: 415 }, + ); + } + + if (!process.env.ANTHROPIC_API_KEY) { + return NextResponse.json({ extraction: mockExtraction(state.toUpperCase()) }); + } + + try { + const { analyzeInsuranceDocument } = await import("@/lib/claude"); + + const arrayBuffer = await file.arrayBuffer(); + const base64 = Buffer.from(arrayBuffer).toString("base64"); + + const extraction = await analyzeInsuranceDocument( + base64, + file.type as "image/jpeg" | "image/png" | "application/pdf", + state.toUpperCase(), + ); + + return NextResponse.json({ extraction }); + } catch (error) { + const message = error instanceof Error ? error.message : "Unknown error"; + console.error("[/api/analyze] Document analysis failed:", message); + return NextResponse.json( + { error: `Analysis failed: ${message}` }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/explain/route.ts b/src/app/api/explain/route.ts new file mode 100644 index 0000000..0d33692 --- /dev/null +++ b/src/app/api/explain/route.ts @@ -0,0 +1,59 @@ +import { NextRequest, NextResponse } from "next/server"; +import type { PolicyExtraction } from "@/lib/types"; + +// Streams an AI explanation of a policy document. +// Returns Server-Sent Events (text/event-stream) so the UI can render tokens as they arrive. + +export async function POST(req: NextRequest): Promise { + let body: { extraction: PolicyExtraction; fairScoreTotal: number; state: string }; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON." }, { status: 400 }); + } + + if (!body.extraction || typeof body.fairScoreTotal !== "number" || !body.state) { + return NextResponse.json({ error: "Missing required fields." }, { status: 400 }); + } + + if (!process.env.ANTHROPIC_API_KEY) { + // Return a static explanation when no API key is set + const fallback = `Your current policy with ${body.extraction.carrierName ?? "your carrier"} shows a monthly premium of $${body.extraction.monthlyPremium ?? "N/A"}. The rate appears elevated due to limited U.S. insurance history — a pattern FairDrive's FairCredit system is designed to address by crediting international driving experience.\n\nYour FairScore of ${body.fairScoreTotal}/100 reflects vehicle repair costs, driving credential, and geographic risk factors. ${body.extraction.stateMinimumsComparison?.gaps.length ? `Coverage gaps identified: ${body.extraction.stateMinimumsComparison.gaps.join("; ")}.` : "Your current coverages meet state minimums."}\n\nTop actions: (1) Upload a certified no-claims letter from your home country insurer — this can help carrier partners recognize your clean record. (2) Review your collision deductible — raising it from $500 to $1,000 typically reduces monthly premium by 8–12%.`; + return new NextResponse(fallback, { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + }); + } + + try { + const { streamQuoteExplanation } = await import("@/lib/claude"); + + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + async start(controller) { + try { + for await (const chunk of streamQuoteExplanation( + body.extraction, + body.fairScoreTotal, + body.state, + )) { + controller.enqueue(encoder.encode(chunk)); + } + } finally { + controller.close(); + } + }, + }); + + return new NextResponse(stream, { + headers: { + "Content-Type": "text/plain; charset=utf-8", + "X-Content-Type-Options": "nosniff", + "Cache-Control": "no-cache", + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : "Unknown error"; + console.error("[/api/explain] Streaming failed:", message); + return NextResponse.json({ error: `Explanation failed: ${message}` }, { status: 500 }); + } +} diff --git a/src/app/api/fairscore/route.ts b/src/app/api/fairscore/route.ts new file mode 100644 index 0000000..fc5e58a --- /dev/null +++ b/src/app/api/fairscore/route.ts @@ -0,0 +1,130 @@ +import { NextRequest, NextResponse } from "next/server"; +import type { + FairScoreInputs, + FairScoreResult, + PremiumResult, + CoachRecommendation, + ScoreNarrative, + VehicleKey, + CoverageLevel, +} from "@/lib/types"; +import { + computeFairScore, + computePremium, + getCoachRecommendations, + getScoreNarrative, + VEHICLE_DATA, +} from "@/lib/ai-engine"; + +// Valid vehicle keys derived from VEHICLE_DATA at runtime +const VALID_VEHICLES = new Set(Object.keys(VEHICLE_DATA)); +const VALID_COVERAGE_LEVELS = new Set(["basic", "standard", "premium"]); + +interface FairScoreResponse { + fairScore: FairScoreResult; + premium: PremiumResult; + recommendations: CoachRecommendation[]; + narrative: ScoreNarrative; + totalSavingsPotential: number; +} + +function validateInputs(body: unknown): { inputs: FairScoreInputs } | { error: string } { + if (!body || typeof body !== "object") { + return { error: "Request body must be a JSON object." }; + } + + const b = body as Record; + + // Required string fields + if (typeof b.vehicle !== "string" || !VALID_VEHICLES.has(b.vehicle)) { + return { + error: `"vehicle" must be one of: ${Array.from(VALID_VEHICLES).join(", ")}`, + }; + } + + if (typeof b.state !== "string" || !/^[A-Za-z]{2}$/.test(b.state)) { + return { error: '"state" must be a 2-letter US state code (e.g. "CA").' }; + } + + if ( + typeof b.coverageLevel !== "string" || + !VALID_COVERAGE_LEVELS.has(b.coverageLevel) + ) { + return { + error: `"coverageLevel" must be one of: ${Array.from(VALID_COVERAGE_LEVELS).join(", ")}`, + }; + } + + // Required numeric fields + const numericFields = [ + "usHistoryYears", + "intlHistoryYears", + "annualMiles", + ] as const; + + for (const field of numericFields) { + if (typeof b[field] !== "number" || !isFinite(b[field] as number) || (b[field] as number) < 0) { + return { error: `"${field}" must be a non-negative number.` }; + } + } + + // Required boolean fields + const boolFields = ["cleanRecord", "defensiveCourse"] as const; + for (const field of boolFields) { + if (typeof b[field] !== "boolean") { + return { error: `"${field}" must be a boolean.` }; + } + } + + const inputs: FairScoreInputs = { + vehicle: b.vehicle as VehicleKey, + state: (b.state as string).toUpperCase(), + usHistoryYears: b.usHistoryYears as number, + intlHistoryYears: b.intlHistoryYears as number, + annualMiles: b.annualMiles as number, + coverageLevel: b.coverageLevel as CoverageLevel, + cleanRecord: b.cleanRecord as boolean, + defensiveCourse: b.defensiveCourse as boolean, + }; + + return { inputs }; +} + +export async function POST(req: NextRequest): Promise { + let body: unknown; + try { + body = await req.json(); + } catch { + return NextResponse.json( + { error: "Invalid JSON in request body." }, + { status: 400 }, + ); + } + + const validation = validateInputs(body); + if ("error" in validation) { + return NextResponse.json({ error: validation.error }, { status: 400 }); + } + + const { inputs } = validation; + + const fairScore = computeFairScore(inputs); + const premium = computePremium(inputs, fairScore); + const recommendations = getCoachRecommendations(inputs, fairScore, premium); + const narrative = getScoreNarrative(inputs, fairScore); + + const totalSavingsPotential = recommendations.reduce( + (sum, r) => sum + r.savings, + 0, + ); + + const responseBody: FairScoreResponse = { + fairScore, + premium, + recommendations, + narrative, + totalSavingsPotential, + }; + + return NextResponse.json(responseBody, { status: 200 }); +} diff --git a/src/app/api/passport/route.ts b/src/app/api/passport/route.ts new file mode 100644 index 0000000..e798b53 --- /dev/null +++ b/src/app/api/passport/route.ts @@ -0,0 +1,125 @@ +import { NextRequest, NextResponse } from "next/server"; +import type { PassportDocument, DocumentType } from "@/lib/types"; + +// Demo-mode passport state — in production, this would be fetched from Supabase +// keyed by the authenticated user's ID. +const INITIAL_DOCUMENTS: PassportDocument[] = [ + { + type: "us-license", + label: "U.S. Driver's License", + description: "Required to bind a policy with most U.S. carriers.", + status: "verified", + uploadedAt: "2024-06-15T10:22:00Z", + verifiedAt: "2024-06-15T10:23:41Z", + fileName: "us-license.jpg", + consentGiven: true, + shareWithPartners: true, + required: true, + }, + { + type: "foreign-license", + label: "Foreign Driver's License", + description: "Supports FairCredit international history recognition.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "no-claims", + label: "No-Claims Certificate", + description: "Certified letter from your home country insurer proving clean record.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "vehicle-reg", + label: "Vehicle Registration", + description: "Current vehicle registration confirming ownership and VIN.", + status: "verified", + uploadedAt: "2024-06-15T10:25:00Z", + verifiedAt: "2024-06-15T10:26:12Z", + fileName: "registration.pdf", + consentGiven: true, + shareWithPartners: true, + required: true, + }, + { + type: "current-policy", + label: "Current Insurance Policy", + description: "Your active policy declaration page for comparison and gap analysis.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "telematics-consent", + label: "Telematics Consent", + description: "Opt-in for Drive Score — unlock usage-based discounts.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, +]; + +function generateShareToken(): string { + const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no ambiguous chars (0, O, 1, I) + let token = ""; + for (let i = 0; i < 8; i++) { + token += chars[Math.floor(Math.random() * chars.length)]; + } + return token; +} + +export async function GET(): Promise { + const completedCount = INITIAL_DOCUMENTS.filter( + (d) => d.status === "verified" || d.status === "uploaded", + ).length; + const completionPct = Math.round((completedCount / INITIAL_DOCUMENTS.length) * 100); + + return NextResponse.json({ + documents: INITIAL_DOCUMENTS, + completionPct, + }); +} + +export async function POST(req: NextRequest): Promise { + let body: unknown; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON." }, { status: 400 }); + } + + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "Body must be an object." }, { status: 400 }); + } + + const { action } = body as Record; + + if (action === "share") { + const token = generateShareToken(); + return NextResponse.json({ shareToken: token }); + } + + if (action === "delete") { + const { type } = body as Record; + if (typeof type !== "string") { + return NextResponse.json({ error: '"type" is required for delete.' }, { status: 400 }); + } + const validTypes: DocumentType[] = [ + "us-license", "foreign-license", "no-claims", + "vehicle-reg", "current-policy", "telematics-consent", + ]; + if (!validTypes.includes(type as DocumentType)) { + return NextResponse.json({ error: `Unknown document type: ${type}` }, { status: 400 }); + } + return NextResponse.json({ success: true }); + } + + return NextResponse.json({ error: `Unknown action: ${String(action)}` }, { status: 400 }); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..e657aa0 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,5 @@ +import AppShell from "@/components/AppShell"; + +export default function DashboardPage() { + return ; +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..ac3ebff --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,1541 @@ +:root { + color-scheme: dark; + --bg: #0b0d10; + --surface: #12161d; + --surface-2: #171d25; + --surface-3: #202833; + --line: rgba(255, 255, 255, 0.1); + --text: #f3f7fb; + --muted: #94a3b8; + --soft: #cbd5e1; + --brand: #29d3a2; + --brand-2: #63b3ff; + --warn: #ffc857; + --danger: #ff6b6b; + --radius: 8px; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: + radial-gradient(circle at 60% 0%, rgba(41, 211, 162, 0.1), transparent 34rem), + linear-gradient(135deg, #0b0d10 0%, #10161c 46%, #0b0d10 100%); + color: var(--text); +} + +button, +input, +select, +a { + font: inherit; +} + +button { + cursor: pointer; +} + +a { + color: inherit; + text-decoration: none; +} + +.app-shell { + display: grid; + grid-template-columns: 18rem minmax(0, 1fr); + min-height: 100vh; +} + +.sidebar { + position: sticky; + top: 0; + display: flex; + flex-direction: column; + gap: 2rem; + height: 100vh; + padding: 1.5rem; + border-right: 1px solid var(--line); + background: rgba(11, 13, 16, 0.76); + backdrop-filter: blur(20px); +} + +.brand, +.topbar, +.panel-header, +.topbar-actions, +.profile-button, +.hero-actions, +.metric-row { + display: flex; + align-items: center; +} + +.brand { + gap: 0.8rem; +} + +.brand-mark, +.profile-avatar, +.icon-button { + display: grid; + place-items: center; + flex: 0 0 auto; +} + +.brand-mark { + width: 2.75rem; + height: 2.75rem; + border: 1px solid rgba(41, 211, 162, 0.5); + border-radius: 8px; + background: linear-gradient(145deg, rgba(41, 211, 162, 0.22), rgba(99, 179, 255, 0.14)); + color: var(--brand); + font-weight: 800; +} + +.brand-name, +.brand-subtitle, +.eyebrow, +h1, +h2, +h3, +p { + margin: 0; +} + +.brand-name { + font-weight: 800; +} + +.brand-subtitle, +.muted { + color: var(--muted); +} + +.nav-list { + display: grid; + gap: 0.35rem; +} + +.nav-item { + width: 100%; + padding: 0.85rem 0.95rem; + border: 1px solid transparent; + border-radius: var(--radius); + background: transparent; + color: var(--soft); + text-align: left; +} + +.nav-item:hover, +.nav-item.active { + border-color: rgba(41, 211, 162, 0.36); + background: rgba(41, 211, 162, 0.1); + color: var(--text); +} + +.sidebar-card { + margin-top: auto; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.04); + color: var(--soft); +} + +.eyebrow { + color: var(--brand); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0; + text-transform: uppercase; +} + +.main-content { + padding: 1.5rem; +} + +.topbar { + justify-content: space-between; + gap: 1.5rem; + margin-bottom: 1.5rem; +} + +.topbar h1 { + max-width: 58rem; + margin-top: 0.35rem; + font-size: clamp(1.6rem, 3vw, 3.8rem); + line-height: 1; + letter-spacing: 0; +} + +.topbar-actions { + gap: 0.75rem; +} + +.icon-button, +.profile-button { + min-height: 2.75rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.06); + color: var(--text); +} + +.icon-button { + width: 2.75rem; +} + +.profile-button { + gap: 0.6rem; + padding: 0.35rem 0.75rem 0.35rem 0.35rem; +} + +.profile-avatar { + width: 2rem; + height: 2rem; + border-radius: 6px; + background: #263241; + color: var(--brand); + font-size: 0.75rem; + font-weight: 800; +} + +.hero-panel, +.panel { + border: 1px solid var(--line); + border-radius: var(--radius); + background: linear-gradient(145deg, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0.035)); + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.26); +} + +.hero-panel { + display: grid; + grid-template-columns: minmax(18rem, 0.75fr) minmax(22rem, 1.25fr); + gap: 1.25rem; + min-height: 27rem; + margin-bottom: 1.25rem; + overflow: hidden; +} + +.hero-copy { + display: flex; + flex-direction: column; + justify-content: center; + gap: 1rem; + padding: 2rem; +} + +.hero-copy h2 { + font-size: clamp(2rem, 4vw, 4.5rem); + line-height: 0.95; + letter-spacing: 0; +} + +.hero-copy p:not(.eyebrow) { + max-width: 34rem; + color: var(--soft); + line-height: 1.6; +} + +.hero-actions { + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 0.5rem; +} + +.primary-button, +.secondary-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.85rem; + padding: 0 1rem; + border-radius: var(--radius); + font-weight: 800; +} + +.primary-button { + border: 1px solid rgba(41, 211, 162, 0.6); + background: var(--brand); + color: #05110e; +} + +.secondary-button { + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.06); + color: var(--text); +} + +.vehicle-stage { + position: relative; + min-height: 27rem; + background: + linear-gradient(180deg, rgba(99, 179, 255, 0.08), transparent 42%), + linear-gradient(115deg, transparent 0 42%, rgba(255, 255, 255, 0.045) 42% 43%, transparent 43%), + radial-gradient(ellipse at 55% 85%, rgba(41, 211, 162, 0.26), transparent 22rem); + overflow: hidden; + perspective: 900px; +} + +.model-toolbar { + position: absolute; + left: 1rem; + top: 1rem; + z-index: 3; + display: flex; + align-items: flex-end; + gap: 0.75rem; + max-width: calc(100% - 2rem); + padding: 0.75rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(13, 18, 25, 0.78); + backdrop-filter: blur(18px); +} + +.model-toolbar label { + display: grid; + gap: 0.35rem; + color: var(--muted); + font-size: 0.76rem; + font-weight: 800; + text-transform: uppercase; +} + +.model-toolbar select { + min-width: 13rem; + min-height: 2.35rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-2); + color: var(--text); + padding: 0 0.65rem; + text-transform: none; +} + +.model-toolbar span { + min-height: 2.35rem; + display: inline-flex; + align-items: center; + padding: 0 0.65rem; + border: 1px solid rgba(41, 211, 162, 0.28); + border-radius: var(--radius); + background: rgba(41, 211, 162, 0.08); + color: var(--brand); + font-size: 0.82rem; + font-weight: 800; + white-space: nowrap; +} + +.vehicle-viewer { + width: 100%; + height: 100%; + min-height: 27rem; +} + +.vehicle-viewer canvas { + display: block; + width: 100%; + height: 100%; + min-height: inherit; +} + +.hotspot { + position: absolute; + width: 1.5rem; + height: 1.5rem; + transform: translate(-50%, -50%); +} + +.hotspot span { + display: block; + width: 100%; + height: 100%; + border: 2px solid rgba(255, 255, 255, 0.88); + border-radius: 50%; + background: rgba(41, 211, 162, 0.35); + box-shadow: 0 0 0 0 rgba(41, 211, 162, 0.45); + animation: pulse 1.8s infinite; +} + +.hotspot-front { + left: 75%; + top: 60%; +} + +.hotspot-glass { + left: 55%; + top: 35%; +} + +.hotspot-cabin { + left: 58%; + top: 50%; +} + +.coverage-popover { + position: absolute; + right: 1rem; + bottom: 1rem; + width: min(18rem, calc(100% - 2rem)); + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(13, 18, 25, 0.88); + backdrop-filter: blur(18px); +} + +.coverage-popover strong { + display: block; + margin: 0.25rem 0; +} + +.coverage-popover span { + color: var(--soft); + line-height: 1.45; +} + +.view-grid { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(17rem, 0.8fr); + gap: 1.25rem; +} + +.panel { + padding: 1.25rem; +} + +.large-panel { + min-height: 18rem; +} + +.panel-header { + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.25rem; +} + +.panel h3 { + margin-top: 0.25rem; + font-size: 1.35rem; + letter-spacing: 0; +} + +.status-pill { + display: inline-flex; + align-items: center; + min-height: 2rem; + padding: 0 0.65rem; + border: 1px solid rgba(99, 179, 255, 0.36); + border-radius: 999px; + background: rgba(99, 179, 255, 0.1); + color: #b9dcff; + font-size: 0.82rem; + font-weight: 800; + white-space: nowrap; +} + +.status-pill.warning { + border-color: rgba(255, 200, 87, 0.38); + background: rgba(255, 200, 87, 0.12); + color: var(--warn); +} + +.status-pill.success { + border-color: rgba(41, 211, 162, 0.38); + background: rgba(41, 211, 162, 0.12); + color: var(--brand); +} + +.status-pill.danger { + border-color: rgba(255, 107, 107, 0.38); + background: rgba(255, 107, 107, 0.12); + color: var(--danger); +} + +.coverage-map, +.coach-list, +.task-list, +.passport-grid { + display: grid; + gap: 0.75rem; +} + +.coverage-row, +.metric-row { + justify-content: space-between; + gap: 1rem; + padding: 0.95rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.035); +} + +.coverage-row span, +.metric-row span { + color: var(--muted); +} + +.good { + color: var(--brand); +} + +.warn { + color: var(--warn); +} + +.progress-track { + height: 0.55rem; + margin: 1.2rem 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.09); + overflow: hidden; +} + +.progress-track span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--brand), var(--brand-2)); +} + +.task-list { + margin: 0; + padding: 0; + list-style: none; +} + +.task-list li { + padding: 0.8rem; + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.04); + color: var(--soft); +} + +.task-list li.done { + color: var(--brand); +} + +.upload-zone { + display: grid; + place-items: center; + gap: 0.45rem; + min-height: 12rem; + margin-bottom: 1rem; + border: 1px dashed rgba(99, 179, 255, 0.48); + border-radius: var(--radius); + background: rgba(99, 179, 255, 0.07); + text-align: center; + cursor: pointer; +} + +.upload-zone input { + display: none; +} + +.upload-icon { + display: grid; + place-items: center; + width: 3rem; + height: 3rem; + border-radius: var(--radius); + background: rgba(41, 211, 162, 0.16); + color: var(--brand); + font-size: 2rem; + line-height: 1; +} + +.upload-zone small, +.explanation-card, +.estimate-result p { + color: var(--muted); +} + +.explanation-card, +.estimate-result { + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(0, 0, 0, 0.18); +} + +.explanation-card p:not(.eyebrow) { + margin-top: 0.35rem; + line-height: 1.55; +} + +.passport-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 1.25rem; +} + +.passport-tile { + min-height: 7rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.04); + color: var(--text); + font-weight: 800; + padding: 1rem; + text-align: left; + display: flex; + flex-direction: column; + gap: 0.5rem; + transition: border-color 0.18s; +} + +.passport-tile:hover { + border-color: rgba(99, 179, 255, 0.35); +} + +.passport-tile.complete { + border-color: rgba(41, 211, 162, 0.45); + background: rgba(41, 211, 162, 0.1); +} + +.passport-tile.expanded { + border-color: rgba(99, 179, 255, 0.5); + background: rgba(99, 179, 255, 0.06); +} + +.passport-tile-meta { + font-size: 0.8rem; + font-weight: 400; + color: var(--muted); +} + +.passport-tile-filename { + font-size: 0.78rem; + font-weight: 400; + color: var(--soft); + word-break: break-all; +} + +.passport-expanded-panel { + grid-column: 1 / -1; + padding: 1.25rem; + border: 1px solid rgba(99, 179, 255, 0.35); + border-radius: var(--radius); + background: rgba(99, 179, 255, 0.05); + display: grid; + gap: 1rem; +} + +.passport-consent-row { + display: flex; + align-items: center; + gap: 0.75rem; + color: var(--soft); + font-size: 0.88rem; +} + +.passport-consent-row input[type="checkbox"] { + width: 1.1rem; + height: 1.1rem; + accent-color: var(--brand); + flex: 0 0 auto; +} + +.passport-delete-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.4rem 0.85rem; + border: 1px solid rgba(255, 107, 107, 0.35); + border-radius: var(--radius); + background: rgba(255, 107, 107, 0.08); + color: var(--danger); + font-size: 0.82rem; + font-weight: 700; +} + +.share-token-modal { + position: fixed; + inset: 0; + z-index: 100; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.68); + backdrop-filter: blur(4px); +} + +.share-token-card { + width: min(36rem, calc(100vw - 2rem)); + padding: 1.5rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); + display: grid; + gap: 1rem; +} + +.estimator-form { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)) auto; + gap: 0.8rem; + align-items: flex-end; + margin: 1.25rem 0; +} + +.estimator-form label { + display: grid; + gap: 0.45rem; + color: var(--muted); + font-weight: 700; +} + +.estimator-form select { + width: 100%; + min-height: 2.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-2); + color: var(--text); + padding: 0 0.75rem; +} + +.estimate-result strong { + display: block; + margin: 0.25rem 0; + font-size: 2rem; +} + +.coach-list button { + display: grid; + gap: 0.35rem; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.04); + color: var(--text); + text-align: left; +} + +.coach-list span { + color: var(--muted); +} + +@keyframes pulse { + 70% { + box-shadow: 0 0 0 0.85rem rgba(41, 211, 162, 0); + } +} + +@media (max-width: 1050px) { + .app-shell { + grid-template-columns: 1fr; + } + + .sidebar { + position: static; + height: auto; + padding: 1rem; + } + + .nav-list { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .sidebar-card { + display: none; + } + + .hero-panel { + grid-template-columns: 1fr; + } + + .vehicle-stage, + .vehicle-viewer { + min-height: 22rem; + } + + .model-toolbar { + align-items: stretch; + flex-direction: column; + } +} + +@media (max-width: 760px) { + .main-content { + padding: 1rem; + } + + .topbar { + align-items: flex-start; + flex-direction: column; + } + + .nav-list { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .hero-copy { + padding: 1.25rem; + } + + .view-grid { + grid-template-columns: 1fr; + } + + .passport-grid, + .estimator-form { + grid-template-columns: 1fr; + } + + .topbar h1 { + font-size: 2rem; + line-height: 1.08; + } +} + +/* ── FairScore Form ─────────────────────────────────────────────────────── */ + +.fs-form-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; + margin-bottom: 1rem; +} + +.fs-form-grid label, +.fairscore-form label { + display: grid; + gap: 0.45rem; + color: var(--muted); + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.fs-form-grid select, +.fairscore-form select { + width: 100%; + min-height: 2.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-2); + color: var(--text); + padding: 0 0.75rem; + font-size: 0.9rem; + text-transform: none; +} + +.fs-checks { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; +} + +.check-label { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--soft); + font-size: 0.88rem; + cursor: pointer; + text-transform: none !important; + letter-spacing: 0 !important; +} + +.check-label input[type="checkbox"] { + width: 1.1rem; + height: 1.1rem; + accent-color: var(--brand); +} + +/* ── FairScore Result ────────────────────────────────────────────────────── */ + +.fairscore-result { + margin-top: 1.5rem; + display: grid; + gap: 1.25rem; +} + +.score-display { + display: flex; + align-items: center; + gap: 1.5rem; +} + +.score-gauge { + position: relative; + width: 9rem; + flex: 0 0 9rem; +} + +.score-gauge svg { + width: 100%; + height: auto; + overflow: visible; +} + +.gauge-track { + fill: none; + stroke: rgba(255,255,255,0.08); + stroke-width: 10; + stroke-linecap: round; +} + +.gauge-fill { + fill: none; + stroke: var(--brand); + stroke-width: 10; + stroke-linecap: round; + stroke-dasharray: 0 172.8; + transition: stroke-dasharray 0.8s cubic-bezier(0.4, 0, 0.2, 1), stroke 0.8s; +} + +.score-number { + position: absolute; + bottom: 0.6rem; + left: 50%; + transform: translateX(-50%); + font-size: 2rem; + font-weight: 800; + line-height: 1; + color: var(--text); +} + +.score-label { + text-align: center; + margin-top: 0.25rem; + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); +} + +.score-meta { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.score-tier { + font-size: 1.35rem; + font-weight: 800; +} + +.score-tier.tier-green { color: var(--brand); } +.score-tier.tier-good { color: #7ee8c0; } +.score-tier.tier-fair { color: var(--brand-2); } +.score-tier.tier-warn { color: var(--warn); } +.score-tier.tier-danger { color: var(--danger); } + +.score-bench { + color: var(--muted); + font-size: 0.82rem; +} + +/* ── Factor Bars ─────────────────────────────────────────────────────────── */ + +.factor-bars { + display: grid; + gap: 0.65rem; +} + +.factor-bar-header { + display: flex; + justify-content: space-between; + margin-bottom: 0.3rem; + font-size: 0.82rem; + color: var(--soft); +} + +.factor-track { + height: 0.45rem; + border-radius: 999px; + background: rgba(255,255,255,0.08); + overflow: hidden; +} + +.factor-fill { + height: 100%; + border-radius: inherit; + background: var(--brand); + transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* ── Score Narrative ─────────────────────────────────────────────────────── */ + +.score-narrative { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; +} + +.narrative-group { + padding: 0.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.025); +} + +.narrative-label { + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 0.5rem !important; +} + +.narrative-group ul { + margin: 0; + padding-left: 1rem; + color: var(--soft); + font-size: 0.85rem; + line-height: 1.6; +} + +/* ── Premium Comparison ──────────────────────────────────────────────────── */ + +.premium-compare { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + margin-bottom: 0.75rem; +} + +.premium-block { + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.03); +} + +.premium-block.fair { + border-color: rgba(41, 211, 162, 0.35); + background: rgba(41, 211, 162, 0.07); +} + +.pb-label { + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--muted); +} + +.pb-amount { + font-size: 1.8rem; + font-weight: 800; + color: var(--brand); + line-height: 1; +} + +.trad-amount { + color: var(--soft); +} + +.pb-sub { + font-size: 0.75rem; + color: var(--muted); +} + +.savings-banner { + display: grid; + gap: 0.2rem; + padding: 0.85rem; + border: 1px solid rgba(41, 211, 162, 0.4); + border-radius: var(--radius); + background: rgba(41, 211, 162, 0.12); + margin-bottom: 0.5rem; +} + +.savings-banner strong { + font-size: 1.5rem; + color: var(--brand); +} + +.savings-banner p { + font-size: 0.82rem; + color: var(--soft); +} + +/* ── AI Coach ────────────────────────────────────────────────────────────── */ + +.coach-item { + display: grid; + gap: 0.4rem; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.04); + color: var(--text); + text-align: left; + transition: border-color 0.18s; + width: 100%; +} + +.coach-item:hover { + border-color: rgba(41, 211, 162, 0.3); +} + +.coach-item-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} + +.coach-item-header strong { + line-height: 1.35; +} + +.savings-badge { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + padding: 0.2rem 0.55rem; + border: 1px solid rgba(41, 211, 162, 0.45); + border-radius: 999px; + background: rgba(41, 211, 162, 0.12); + color: var(--brand); + font-size: 0.78rem; + font-weight: 800; + white-space: nowrap; +} + +.coach-item > span:not(.savings-badge) { + color: var(--muted); + font-size: 0.88rem; + line-height: 1.5; +} + +.coach-action { + color: var(--brand-2) !important; + font-size: 0.82rem !important; + font-weight: 700; +} + +/* ── Responsive adjustments for new panels ───────────────────────────────── */ + +@media (max-width: 1050px) { + .fs-form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .score-narrative { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .fs-form-grid { + grid-template-columns: 1fr; + } + + .premium-compare { + grid-template-columns: 1fr; + } + + .score-display { + flex-direction: column; + align-items: flex-start; + } +} + +/* ── Marketing page ──────────────────────────────────────────────────────── */ + +.marketing-body { + background: + radial-gradient(circle at 78% 10%, rgba(41, 211, 162, 0.18), transparent 26rem), + linear-gradient(135deg, #0a0d10 0%, #111820 54%, #0b0d10 100%); +} + +.marketing-nav { + position: sticky; + top: 0; + z-index: 10; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem clamp(1rem, 4vw, 4rem); + border-bottom: 1px solid var(--line); + background: rgba(10, 13, 16, 0.78); + backdrop-filter: blur(18px); +} + +.marketing-brand > span:last-child { + display: grid; +} + +.marketing-nav nav { + display: flex; + align-items: center; + gap: 1rem; + color: var(--soft); + font-weight: 750; +} + +.marketing-nav nav a { + padding: 0.55rem 0.65rem; + border-radius: var(--radius); +} + +.marketing-nav nav a:hover, +.marketing-nav .nav-cta { + background: rgba(41, 211, 162, 0.1); + color: var(--text); +} + +.marketing-hero { + display: grid; + grid-template-columns: minmax(20rem, 0.95fr) minmax(22rem, 1.05fr); + gap: clamp(2rem, 5vw, 5rem); + align-items: center; + min-height: calc(100vh - 5rem); + padding: clamp(2rem, 6vw, 6rem) clamp(1rem, 4vw, 4rem); +} + +.marketing-hero-copy { + display: grid; + gap: 1.25rem; +} + +.marketing-hero h1 { + max-width: 56rem; + font-size: clamp(3rem, 7vw, 6.8rem); + line-height: 0.92; + letter-spacing: 0; +} + +.marketing-hero-copy > p:not(.eyebrow) { + max-width: 44rem; + color: var(--soft); + font-size: 1.1rem; + line-height: 1.65; +} + +.marketing-hero-visual { + min-height: 34rem; + display: grid; + place-items: center; + background: + linear-gradient(115deg, transparent 0 42%, rgba(255, 255, 255, 0.045) 42% 43%, transparent 43%), + radial-gradient(ellipse at 50% 72%, rgba(41, 211, 162, 0.24), transparent 22rem); +} + +.marketing-device { + width: min(100%, 34rem); + aspect-ratio: 0.78; + padding: 1.25rem; + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 2rem; + background: linear-gradient(145deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04)); + box-shadow: 0 30px 90px rgba(0, 0, 0, 0.38); +} + +.device-topline, +.device-insight { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(0, 0, 0, 0.2); +} + +.device-topline span { + width: 3rem; + height: 0.55rem; + border-radius: 999px; + background: var(--brand); +} + +.device-car { + position: relative; + min-height: 20rem; + margin: 1rem 0; + border-radius: 1.25rem; + background: + radial-gradient(ellipse at 50% 78%, rgba(41, 211, 162, 0.22), transparent 16rem), + linear-gradient(180deg, rgba(99, 179, 255, 0.08), rgba(0, 0, 0, 0.1)); + overflow: hidden; +} + +.device-car-body, +.device-car-roof, +.device-wheel { + position: absolute; +} + +.device-car-body { + left: 12%; + right: 9%; + top: 49%; + height: 18%; + border-radius: 1.5rem 2.5rem 1rem 1rem; + background: linear-gradient(120deg, #263340, #8a9bab 48%, #151d23); + transform: perspective(600px) rotateY(-18deg) skewX(-7deg); +} + +.device-car-roof { + left: 32%; + right: 27%; + top: 33%; + height: 22%; + border-radius: 4rem 4rem 0.6rem 0.6rem; + background: linear-gradient(120deg, rgba(158, 226, 255, 0.92), rgba(26, 43, 58, 0.92)); + transform: perspective(600px) rotateY(-18deg) skewX(-8deg); +} + +.device-wheel { + top: 61%; + width: 4.2rem; + height: 4.2rem; + border: 0.8rem solid #050608; + border-radius: 50%; + background: #c4ced8; + box-shadow: inset 0 0 0 0.65rem #263241; +} + +.device-wheel.left { + left: 22%; +} + +.device-wheel.right { + right: 20%; +} + +.device-insight { + display: grid; + justify-content: stretch; +} + +.device-insight span { + color: var(--muted); +} + +.marketing-band, +.marketing-split { + padding: clamp(3rem, 7vw, 6rem) clamp(1rem, 4vw, 4rem); +} + +.section-heading { + max-width: 54rem; + margin-bottom: 2rem; +} + +.section-heading h2, +.marketing-split h2 { + margin: 0.35rem 0 0.75rem; + font-size: clamp(2rem, 4vw, 4rem); + line-height: 1; + letter-spacing: 0; +} + +.section-heading p, +.marketing-split p { + color: var(--soft); + line-height: 1.65; +} + +.marketing-grid { + display: grid; + gap: 1rem; +} + +.marketing-grid.three { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.marketing-grid.two, +.marketing-split { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.marketing-card { + min-height: 18rem; + padding: 1.35rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.055); +} + +.marketing-card h3 { + margin: 1rem 0 0.75rem; + font-size: 1.4rem; +} + +.marketing-card p { + color: var(--soft); + line-height: 1.6; +} + +.card-index { + color: var(--brand); + font-weight: 900; +} + +.marketing-split { + display: grid; + gap: 2rem; + align-items: start; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + background: rgba(255, 255, 255, 0.025); +} + +.mvp-list { + display: grid; + gap: 0.85rem; +} + +.mvp-list div { + display: grid; + gap: 0.25rem; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.05); +} + +.mvp-list span { + color: var(--muted); +} + +@media (max-width: 900px) { + .marketing-hero, + .marketing-grid.three, + .marketing-grid.two, + .marketing-split { + grid-template-columns: 1fr; + } + + .marketing-nav { + align-items: flex-start; + flex-direction: column; + } + + .marketing-nav nav { + flex-wrap: wrap; + } + + .marketing-hero h1 { + font-size: 3.4rem; + } +} + +/* ── Streaming text in quote explainer ──────────────────────────────────── */ + +.streaming-text { + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* ── Passport document upload area ─────────────────────────────────────── */ + +.doc-upload-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.55rem 1rem; + border: 1px dashed rgba(99, 179, 255, 0.5); + border-radius: var(--radius); + background: rgba(99, 179, 255, 0.07); + color: var(--brand-2); + font-size: 0.85rem; + font-weight: 700; + cursor: pointer; + transition: background 0.18s, border-color 0.18s; +} + +.doc-upload-btn:hover { + background: rgba(99, 179, 255, 0.14); + border-color: rgba(99, 179, 255, 0.75); +} + +.doc-status-badge { + display: inline-flex; + align-items: center; + padding: 0.15rem 0.5rem; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.doc-status-badge.verified { + border: 1px solid rgba(41, 211, 162, 0.4); + background: rgba(41, 211, 162, 0.12); + color: var(--brand); +} + +.doc-status-badge.pending { + border: 1px solid rgba(255, 200, 87, 0.4); + background: rgba(255, 200, 87, 0.12); + color: var(--warn); +} + +.doc-status-badge.needs-review { + border: 1px solid rgba(255, 107, 107, 0.4); + background: rgba(255, 107, 107, 0.12); + color: var(--danger); +} + +.doc-status-badge.not-uploaded { + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.06); + color: var(--muted); +} + +/* ── Confidence score bar ─────────────────────────────────────────────── */ + +.confidence-bar { + display: flex; + align-items: center; + gap: 0.65rem; + font-size: 0.8rem; + color: var(--muted); +} + +.confidence-bar-track { + flex: 1; + height: 0.35rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; +} + +.confidence-bar-fill { + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--warn), var(--brand)); + transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* ── Share token display ──────────────────────────────────────────────── */ + +.share-token { + font-family: monospace; + letter-spacing: 0.12em; + padding: 0.75rem 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(0, 0, 0, 0.3); + color: var(--brand); + font-size: 0.9rem; + word-break: break-all; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..6871ac0 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "FairDrive Auto", + description: "AI-native auto insurance for new U.S. drivers", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..104e500 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,152 @@ +import Link from "next/link"; + +export default function MarketingPage() { + return ( +
+
+ + + + FairDrive + Auto Insurance Passport + + + +
+ +
+
+
+

AI-native auto insurance

+

Auto insurance for drivers whose history did not start in the U.S.

+

+ FairDrive helps immigrants, H1B workers, students, and first-time U.S. + drivers understand expensive premiums, organize stronger evidence, and + find a path toward better options where available. +

+
+ Launch App + See product +
+
+ +
+
+
+ + $386/mo +
+
+
+
+
+
+
+
+ Premium insight + 3 actions may improve your profile +
+
+
+
+ +
+
+

Product

+

A digital garage for insurance clarity

+

+ The experience starts with the car, then guides users through coverage, + documents, quotes, and savings actions without traditional insurance friction. +

+
+ +
+
+ 01 +

3D Vehicle Garage

+

+ Users explore coverage around their selected vehicle, inspect gaps, + and compare models before buying a car. +

+
+
+ 02 +

Insurance Passport

+

+ A reusable profile for U.S. license, foreign license, no-claims proof, + current policy, vehicle data, and future drive score. +

+
+
+ 03 +

AI Quote Explainer

+

+ Upload a quote or policy and get a plain-language explanation of premium + drivers, coverage gaps, and realistic next steps. +

+
+
+
+ +
+
+

MVP wedge

+

Start with the pain users already feel

+

+ The first release should focus on quote upload, premium explanation, + Insurance Passport completion, and licensed-agent handoff. Telematics and + full carrier integrations should come after demand is proven. +

+
+
+
+ Explain + Why is this premium so high? +
+
+ Organize + What proof can make my profile stronger? +
+
+ Compare + Which car or coverage choice changes the cost? +
+
+ Escalate + When should a licensed agent review my options? +
+
+
+ +
+
+

Trust and compliance

+

Fairness needs careful wording and real guardrails

+
+
+
+

Clear Promise

+

+ FairDrive should not guarantee savings. It should help users understand + premiums, organize evidence, compare options, and find a path toward better + costs where available. +

+
+
+

Human Handoff

+

+ AI can explain and prepare, but regulated recommendations and binding + workflows need licensed-agent or partner support. +

+
+
+
+
+
+ ); +} diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx new file mode 100644 index 0000000..9d39a5e --- /dev/null +++ b/src/components/AppShell.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useState, useCallback } from "react"; +import type { + ViewType, + VehicleKey, + FairScoreResult, + PremiumResult, + CoachRecommendation, + PassportDocument, + DocumentType, + FairScoreInputs, + ScoreNarrative, +} from "@/lib/types"; +import Sidebar from "./Sidebar"; +import TopBar from "./TopBar"; +import HeroPanel from "./HeroPanel"; +import GarageView from "./views/GarageView"; +import FairScoreView from "./views/FairScoreView"; +import QuoteView from "./views/QuoteView"; +import PassportView from "./views/PassportView"; +import EstimatorView from "./views/EstimatorView"; +import CoachView from "./views/CoachView"; + +// ── Initial passport documents ──────────────────────────────────────────── + +const INITIAL_DOCS: PassportDocument[] = [ + { + type: "us-license", + label: "U.S. Driver's License", + description: "Your current U.S. driver's license. Required for all policies.", + status: "verified", + uploadedAt: "2024-01-15", + verifiedAt: "2024-01-16", + fileName: "us_license.pdf", + consentGiven: true, + shareWithPartners: false, + required: true, + }, + { + type: "vehicle-reg", + label: "Vehicle Registration", + description: "Current vehicle registration document proving ownership.", + status: "verified", + uploadedAt: "2024-01-15", + verifiedAt: "2024-01-16", + fileName: "vehicle_registration.pdf", + consentGiven: true, + shareWithPartners: false, + required: true, + }, + { + type: "foreign-license", + label: "Foreign Driver's License", + description: "Your driver's license from your home country. Supports FairCredit recognition.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "no-claims", + label: "No-Claims Certificate", + description: "Certified letter from your home country insurer confirming claim-free history.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "current-policy", + label: "Current Insurance Policy", + description: "Your existing U.S. policy declaration page for comparison and gap analysis.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, + { + type: "telematics-consent", + label: "Telematics Consent", + description: "Consent form for usage-based insurance programs. Can unlock 20–40% savings.", + status: "not-uploaded", + consentGiven: false, + shareWithPartners: false, + required: false, + }, +]; + +// ── FairScore API response shape ────────────────────────────────────────── + +interface FairScoreApiResponse { + fairScore: FairScoreResult; + premium: PremiumResult; + recommendations: CoachRecommendation[]; + narrative: ScoreNarrative; + totalSavingsPotential: number; +} + +export default function AppShell() { + // Navigation state + const [currentView, setCurrentView] = useState("garage"); + const [currentVehicle, setCurrentVehicle] = useState("accord"); + + // FairScore state + const [fairScoreResult, setFairScoreResult] = useState(null); + const [premiumResult, setPremiumResult] = useState(null); + const [narrative, setNarrative] = useState(null); + const [coachRecs, setCoachRecs] = useState([]); + const [totalSavings, setTotalSavings] = useState(0); + const [isComputing, setIsComputing] = useState(false); + + // Passport state + const [passportDocs, setPassportDocs] = useState(INITIAL_DOCS); + const [shareToken, setShareToken] = useState(null); + + // ── Compute FairScore ─────────────────────────────────────────────────── + + const onComputeFairScore = useCallback(async (inputs: FairScoreInputs) => { + setIsComputing(true); + try { + const res = await fetch("/api/fairscore", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputs), + }); + + if (!res.ok) { + const err = await res.json() as { error: string }; + throw new Error(err.error ?? "FairScore computation failed"); + } + + const data = await res.json() as FairScoreApiResponse; + setFairScoreResult(data.fairScore); + setPremiumResult(data.premium); + setNarrative(data.narrative); + setCoachRecs(data.recommendations); + setTotalSavings(data.totalSavingsPotential); + setCurrentVehicle(inputs.vehicle); + } finally { + setIsComputing(false); + } + }, []); + + // ── Passport handlers ─────────────────────────────────────────────────── + + const onUploadDoc = useCallback(async (type: DocumentType, file: File) => { + setPassportDocs(prev => + prev.map(d => + d.type === type + ? { + ...d, + status: "uploaded" as const, + fileName: file.name, + uploadedAt: new Date().toISOString().split("T")[0], + } + : d, + ), + ); + }, []); + + const onToggleConsent = useCallback( + (type: DocumentType, field: "consentGiven" | "shareWithPartners") => { + setPassportDocs(prev => + prev.map(d => + d.type === type ? { ...d, [field]: !d[field] } : d, + ), + ); + }, + [], + ); + + const onShare = useCallback(async (): Promise => { + const token = + shareToken ?? + `FD-${Math.random().toString(36).slice(2, 7).toUpperCase()}-${Math.random().toString(36).slice(2, 7).toUpperCase()}`; + setShareToken(token); + return token; + }, [shareToken]); + + const onDeleteDoc = useCallback(async (type: DocumentType) => { + setPassportDocs(prev => + prev.map(d => + d.type === type + ? { + ...d, + status: "not-uploaded" as const, + fileName: undefined, + uploadedAt: undefined, + verifiedAt: undefined, + extractedData: undefined, + } + : d, + ), + ); + }, []); + + // ── Render current view ───────────────────────────────────────────────── + + function renderView() { + switch (currentView) { + case "garage": + return ; + case "fairscore": + return ( + + ); + case "quote": + return ( + + ); + case "passport": + return ( + + ); + case "estimator": + return ; + case "coach": + return ( + + ); + } + } + + return ( +
+ +
+ + + {renderView()} +
+
+ ); +} diff --git a/src/components/HeroPanel.tsx b/src/components/HeroPanel.tsx new file mode 100644 index 0000000..44ef1f3 --- /dev/null +++ b/src/components/HeroPanel.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useState } from "react"; +import dynamic from "next/dynamic"; +import type { VehicleKey, ViewType } from "@/lib/types"; +import { VEHICLE_DATA } from "@/lib/ai-engine"; + +const VehicleViewer = dynamic(() => import("./VehicleViewer"), { ssr: false }); + +interface Props { + currentVehicle: VehicleKey; + onVehicleChange: (v: VehicleKey) => void; + onNavigate: (view: ViewType) => void; +} + +type CoverageLabel = "Collision" | "Comprehensive" | "Medical"; + +const COVERAGE_COPY: Record = { + Collision: + "Protects your car after an accident with another vehicle or object.", + Comprehensive: + "Protects against theft, hail, glass damage, flood, fire, and other non-collision events.", + Medical: + "Helps cover medical costs for you or passengers after a covered accident.", +}; + +const HOTSPOTS: { cls: string; label: CoverageLabel }[] = [ + { cls: "hotspot-front", label: "Collision" }, + { cls: "hotspot-glass", label: "Comprehensive" }, + { cls: "hotspot-cabin", label: "Medical" }, +]; + +export default function HeroPanel({ + currentVehicle, + onVehicleChange, + onNavigate, +}: Props) { + const [popoverCoverage, setPopoverCoverage] = + useState("Collision"); + const vehicleData = VEHICLE_DATA[currentVehicle]; + + return ( +
+
+

Current policy insight

+

$386/mo estimated premium

+

+ Your rate appears elevated because the profile has limited U.S. + insurance history and the vehicle has above-average repair cost. +

+
+ + +
+
+ +
+
+ + {vehicleData.design} +
+ + + + {HOTSPOTS.map(({ cls, label }) => ( +
setPopoverCoverage(label)} + > + +
+ ))} + +
+

Coverage

+ {popoverCoverage} + {COVERAGE_COPY[popoverCoverage]} +
+
+
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..edfd51b --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,52 @@ +"use client"; + +import type { ViewType } from "@/lib/types"; + +interface Props { + currentView: ViewType; + onViewChange: (view: ViewType) => void; +} + +const NAV_ITEMS: { view: ViewType; label: string }[] = [ + { view: "garage", label: "Garage" }, + { view: "fairscore", label: "FairScore" }, + { view: "quote", label: "Quote" }, + { view: "passport", label: "Passport" }, + { view: "estimator", label: "Estimator" }, + { view: "coach", label: "Coach" }, +]; + +export default function Sidebar({ currentView, onViewChange }: Props) { + return ( + + ); +} diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx new file mode 100644 index 0000000..24cc3b6 --- /dev/null +++ b/src/components/TopBar.tsx @@ -0,0 +1,21 @@ +"use client"; + +export default function TopBar() { + return ( +
+
+

AI-native auto insurance

+

Your driving history did not start when you landed in the U.S.

+
+
+ + +
+
+ ); +} diff --git a/src/components/VehicleViewer.tsx b/src/components/VehicleViewer.tsx new file mode 100644 index 0000000..20c5193 --- /dev/null +++ b/src/components/VehicleViewer.tsx @@ -0,0 +1,480 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import * as THREE from "three"; +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; +import type { VehicleKey } from "@/lib/types"; + +interface Props { + vehicle: VehicleKey; +} + +type ModelSpec = { + name: string; + className: string; + paint: string; + accent: string; + length: number; + width: number; + height: number; + roof: string; + nose: string; + wheelRadius: number; + wheelbase: number; + rearDeck: number; +}; + +const MODEL_SPECS: Record = { + accord: { + name: "2023 Honda Accord", + className: "mid-size sedan", + paint: "#6e8194", + accent: "#29d3a2", + length: 5.05, + width: 2.05, + height: 1.18, + roof: "sedan", + nose: "wide", + wheelRadius: 0.43, + wheelbase: 3.35, + rearDeck: 0.52, + }, + camry: { + name: "2023 Toyota Camry", + className: "angular sedan", + paint: "#586f86", + accent: "#63b3ff", + length: 5.02, + width: 2.06, + height: 1.2, + roof: "sedan", + nose: "sharp", + wheelRadius: 0.44, + wheelbase: 3.28, + rearDeck: 0.55, + }, + bmw: { + name: "2023 BMW 330i", + className: "sport luxury sedan", + paint: "#4c5f73", + accent: "#d8e8ff", + length: 4.82, + width: 2.04, + height: 1.08, + roof: "sport", + nose: "kidney", + wheelRadius: 0.48, + wheelbase: 3.18, + rearDeck: 0.48, + }, + tesla: { + name: "2023 Tesla Model 3", + className: "minimal EV fastback", + paint: "#8a9bab", + accent: "#29d3a2", + length: 4.76, + width: 2.03, + height: 1.07, + roof: "fastback", + nose: "ev", + wheelRadius: 0.46, + wheelbase: 3.22, + rearDeck: 0.38, + }, + crv: { + name: "2023 Honda CR-V", + className: "compact SUV", + paint: "#71869a", + accent: "#ffc857", + length: 4.78, + width: 2.12, + height: 1.62, + roof: "suv", + nose: "suv", + wheelRadius: 0.5, + wheelbase: 3.05, + rearDeck: 0.65, + }, +}; + +function darkenHex(hex: string, factor: number): string { + const c = hex.replace("#", ""); + const num = parseInt(c, 16); + const r = Math.floor(((num >> 16) & 0xff) * factor); + const g = Math.floor(((num >> 8) & 0xff) * factor); + const b = Math.floor((num & 0xff) * factor); + return `rgb(${r},${g},${b})`; +} + +function softenEdge(value: number, limit: number, radius: number): number { + if (Math.abs(value) < limit - radius) return value; + return Math.sign(value) * (limit - radius + radius * 0.86); +} + +function roundedBox( + w: number, h: number, d: number, r: number, + mat: THREE.Material, +): THREE.Mesh { + const geo = new THREE.BoxGeometry(w, h, d, 3, 3, 3); + const pos = geo.attributes["position"] as THREE.BufferAttribute; + for (let i = 0; i < pos.count; i++) { + pos.setXYZ( + i, + softenEdge(pos.getX(i), w / 2, r), + softenEdge(pos.getY(i), h / 2, r), + softenEdge(pos.getZ(i), d / 2, r), + ); + } + geo.computeVertexNormals(); + return new THREE.Mesh(geo, mat); +} + +function slopedPanel( + pts: [number, number, number][], + mat: THREE.Material, +): THREE.Mesh { + const geo = new THREE.BufferGeometry(); + geo.setAttribute("position", new THREE.Float32BufferAttribute(pts.flat(), 3)); + geo.setIndex([0, 1, 2, 0, 2, 3]); + geo.computeVertexNormals(); + return new THREE.Mesh(geo, mat); +} + +function trapezoidCabin( + length: number, height: number, width: number, spec: ModelSpec, + mat: THREE.Material, +): THREE.Mesh { + const bottom = length / 2; + const top = + spec.roof === "fastback" ? length * 0.33 + : spec.roof === "suv" ? length * 0.43 + : length * 0.36; + const shape = new THREE.Shape([ + new THREE.Vector2(-bottom, 0), + new THREE.Vector2(bottom, 0), + new THREE.Vector2(top, height), + new THREE.Vector2(-top * 0.92, height), + ]); + const geo = new THREE.ExtrudeGeometry(shape, { + depth: width, bevelEnabled: true, + bevelThickness: 0.05, bevelSize: 0.04, bevelSegments: 2, + }); + geo.center(); + const mesh = new THREE.Mesh(geo, mat); + mesh.rotation.x = Math.PI / 2; + return mesh; +} + +type Materials = { + paint: THREE.MeshPhysicalMaterial; + darkPaint: THREE.MeshPhysicalMaterial; + glass: THREE.MeshPhysicalMaterial; + tire: THREE.MeshStandardMaterial; + rim: THREE.MeshStandardMaterial; + light: THREE.MeshStandardMaterial; + black: THREE.MeshStandardMaterial; + tailLight: THREE.MeshStandardMaterial; +}; + +function makeMaterials(spec: ModelSpec): Materials { + return { + paint: new THREE.MeshPhysicalMaterial({ + color: spec.paint, metalness: 0.78, roughness: 0.26, + clearcoat: 0.7, clearcoatRoughness: 0.18, + }), + darkPaint: new THREE.MeshPhysicalMaterial({ + color: darkenHex(spec.paint, 0.45), metalness: 0.72, roughness: 0.32, clearcoat: 0.5, + }), + glass: new THREE.MeshPhysicalMaterial({ + color: "#9ddfff", metalness: 0, roughness: 0.05, + transmission: 0.12, transparent: true, opacity: 0.48, + }), + tire: new THREE.MeshStandardMaterial({ color: "#050608", metalness: 0.15, roughness: 0.72 }), + rim: new THREE.MeshStandardMaterial({ color: "#c4ced8", metalness: 0.95, roughness: 0.22 }), + light: new THREE.MeshStandardMaterial({ + color: spec.accent, emissive: spec.accent, emissiveIntensity: 1.4, roughness: 0.2, + }), + black: new THREE.MeshStandardMaterial({ color: "#070b0f", metalness: 0.4, roughness: 0.38 }), + tailLight: new THREE.MeshStandardMaterial({ + color: "#ff5b5b", emissive: "#ff2d2d", emissiveIntensity: 1.1, + }), + }; +} + +function buildCabin(spec: ModelSpec, bodyY: number, mats: Materials): THREE.Group { + const g = new THREE.Group(); + const cLen = spec.roof === "suv" ? spec.length * 0.52 : spec.length * 0.42; + const cHeight = spec.roof === "suv" ? spec.height * 0.52 : spec.height * 0.46; + const cWidth = spec.width * 0.78; + const xOff = spec.roof === "fastback" ? 0.06 : spec.roof === "sport" ? -0.04 : 0; + + const roof = trapezoidCabin(cLen, cHeight, cWidth, spec, mats.glass); + roof.position.set(xOff, bodyY + spec.height * 0.48, 0); + roof.castShadow = true; + g.add(roof); + + const ws = roundedBox(cLen * 0.34, cHeight * 0.74, 0.035, 0.04, mats.glass); + ws.position.set(-cLen * 0.34 + xOff, bodyY + spec.height * 0.58, 0); + ws.rotation.z = spec.roof === "suv" ? -0.28 : -0.46; + ws.scale.z = cWidth * 0.96; + g.add(ws); + + const sg = roundedBox(cLen * 0.5, cHeight * 0.48, 0.035, 0.04, mats.glass); + sg.position.set(xOff + cLen * 0.06, bodyY + spec.height * 0.72, -cWidth * 0.52); + g.add(sg); + const sgR = sg.clone(); + sgR.position.z = cWidth * 0.52; + g.add(sgR); + + return g; +} + +function addFront(g: THREE.Group, spec: ModelSpec, bodyY: number, mats: Materials) { + const fx = -spec.length / 2 - 0.04; + const y = bodyY + spec.height * 0.04; + if (spec.nose === "kidney") { + const left = roundedBox(0.05, spec.height * 0.28, spec.width * 0.16, 0.035, mats.black); + left.position.set(fx, y, -spec.width * 0.1); + g.add(left); + const right = left.clone(); + right.position.z = spec.width * 0.1; + g.add(right); + } else if (spec.nose === "ev") { + const smooth = roundedBox(0.035, spec.height * 0.12, spec.width * 0.56, 0.08, mats.paint); + smooth.position.set(fx, y + spec.height * 0.05, 0); + g.add(smooth); + } else { + const gh = spec.nose === "suv" ? spec.height * 0.34 : spec.height * 0.2; + const grille = roundedBox(0.045, gh, spec.width * 0.62, 0.045, mats.black); + grille.position.set(fx, y, 0); + g.add(grille); + } +} + +function addLights(g: THREE.Group, spec: ModelSpec, bodyY: number, mats: Materials) { + const fx = -spec.length / 2 - 0.08; + const rx = spec.length / 2 + 0.02; + const hy = bodyY + spec.height * 0.18; + const z = spec.width * 0.4; + + const hl = roundedBox(0.045, 0.075, spec.width * 0.22, 0.03, mats.light); + hl.position.set(fx, hy, -z); + g.add(hl); + const hlR = hl.clone(); + hlR.position.z = z; + g.add(hlR); + + const tl = roundedBox(0.04, 0.08, spec.width * 0.18, 0.025, mats.tailLight); + tl.position.set(rx, hy, -z); + g.add(tl); + const tlR = tl.clone(); + tlR.position.z = z; + g.add(tlR); +} + +function addWheels(g: THREE.Group, spec: ModelSpec, mats: Materials) { + const wx = spec.wheelbase / 2; + const wz = spec.width * 0.55; + const positions: [number, number, number][] = [ + [-wx, spec.wheelRadius, -wz], + [ wx, spec.wheelRadius, -wz], + [-wx, spec.wheelRadius, wz], + [ wx, spec.wheelRadius, wz], + ]; + + positions.forEach(([x, y, pz]) => { + const wheel = new THREE.Group(); + const tire = new THREE.Mesh( + new THREE.CylinderGeometry(spec.wheelRadius, spec.wheelRadius, 0.32, 48), + mats.tire, + ); + tire.rotation.z = Math.PI / 2; + tire.castShadow = true; + wheel.add(tire); + + const rim = new THREE.Mesh( + new THREE.CylinderGeometry(spec.wheelRadius * 0.56, spec.wheelRadius * 0.56, 0.34, 32), + mats.rim, + ); + rim.rotation.z = Math.PI / 2; + wheel.add(rim); + + for (let i = 0; i < 6; i++) { + const spoke = roundedBox(spec.wheelRadius * 0.12, spec.wheelRadius * 0.68, 0.035, 0.012, mats.black); + spoke.rotation.x = (Math.PI / 6) * i; + wheel.add(spoke); + } + + wheel.position.set(x, y, pz); + g.add(wheel); + }); +} + +function addDetails(g: THREE.Group, spec: ModelSpec, bodyY: number, mats: Materials) { + const belt = roundedBox(spec.length * 0.72, 0.035, 0.035, 0.02, mats.black); + belt.position.set(0.08, bodyY + spec.height * 0.26, -spec.width * 0.535); + g.add(belt); + const beltR = belt.clone(); + beltR.position.z = spec.width * 0.535; + g.add(beltR); + + const mirL = roundedBox(0.12, 0.08, 0.18, 0.04, mats.darkPaint); + mirL.position.set(-spec.length * 0.14, bodyY + spec.height * 0.58, -spec.width * 0.62); + g.add(mirL); + const mirR = mirL.clone(); + mirR.position.z = spec.width * 0.62; + g.add(mirR); +} + +function buildCar(spec: ModelSpec): THREE.Group { + const mats = makeMaterials(spec); + const group = new THREE.Group(); + const groundY = spec.wheelRadius; + const bodyY = groundY + spec.height * 0.34; + const hL = spec.length / 2; + + const body = roundedBox(spec.length, spec.height * 0.58, spec.width, 0.22, mats.paint); + body.position.set(0, bodyY, 0); + body.castShadow = true; + group.add(body); + + const hood = slopedPanel([ + [-hL + 0.28, bodyY + spec.height * 0.22, -spec.width * 0.52], + [-hL + 1.15, bodyY + spec.height * 0.36, -spec.width * 0.52], + [-hL + 1.28, bodyY + spec.height * 0.36, spec.width * 0.52], + [-hL + 0.36, bodyY + spec.height * 0.2, spec.width * 0.52], + ], mats.darkPaint); + hood.castShadow = true; + group.add(hood); + + const cabin = buildCabin(spec, bodyY, mats); + group.add(cabin); + + const rearDeck = roundedBox(spec.rearDeck, spec.height * 0.2, spec.width * 0.94, 0.12, mats.darkPaint); + rearDeck.position.set(hL - spec.rearDeck * 0.7, bodyY + spec.height * 0.2, 0); + rearDeck.castShadow = true; + group.add(rearDeck); + + addFront(group, spec, bodyY, mats); + addLights(group, spec, bodyY, mats); + addWheels(group, spec, mats); + addDetails(group, spec, bodyY, mats); + + group.rotation.y = -0.52; + return group; +} + +function disposeGroup(obj: THREE.Object3D) { + obj.traverse((child) => { + const mesh = child as THREE.Mesh; + if (mesh.geometry) mesh.geometry.dispose(); + }); +} + +export default function VehicleViewer({ vehicle }: Props) { + const containerRef = useRef(null); + const setVehicleRef = useRef<((v: VehicleKey) => void) | null>(null); + + useEffect(() => { + const hostEl = containerRef.current; + if (!hostEl) return; + + let frameId = 0; + + const scene = new THREE.Scene(); + scene.background = null; + + const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100); + camera.position.set(6.8, 3.2, 6.5); + + const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); + renderer.outputColorSpace = THREE.SRGBColorSpace; + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; + hostEl.appendChild(renderer.domElement); + + const controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + controls.enablePan = false; + controls.minDistance = 5.8; + controls.maxDistance = 10; + controls.minPolarAngle = Math.PI * 0.22; + controls.maxPolarAngle = Math.PI * 0.48; + controls.target.set(0, 0.65, 0); + + scene.add(new THREE.HemisphereLight("#d8ecff", "#111820", 2.4)); + const key = new THREE.DirectionalLight("#ffffff", 4); + key.position.set(4, 7, 5); + key.castShadow = true; + key.shadow.mapSize.set(2048, 2048); + scene.add(key); + const rimLight = new THREE.DirectionalLight("#29d3a2", 2.2); + rimLight.position.set(-5, 3, -4); + scene.add(rimLight); + + const floor = new THREE.Mesh( + new THREE.CircleGeometry(5.8, 96), + new THREE.MeshStandardMaterial({ color: "#101820", metalness: 0.25, roughness: 0.56, transparent: true, opacity: 0.92 }), + ); + floor.rotation.x = -Math.PI / 2; + floor.receiveShadow = true; + scene.add(floor); + + const grid = new THREE.GridHelper(10, 18, "#29d3a2", "#253241"); + (grid.material as THREE.Material & { transparent: boolean; opacity: number }).transparent = true; + (grid.material as THREE.Material & { transparent: boolean; opacity: number }).opacity = 0.18; + scene.add(grid); + + let carGroup = buildCar(MODEL_SPECS[vehicle]); + scene.add(carGroup); + + setVehicleRef.current = (v: VehicleKey) => { + scene.remove(carGroup); + disposeGroup(carGroup); + carGroup = buildCar(MODEL_SPECS[v]); + scene.add(carGroup); + }; + + function resize() { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { width, height } = hostEl!.getBoundingClientRect(); + camera.aspect = width / Math.max(height, 1); + camera.updateProjectionMatrix(); + renderer.setSize(width, height, false); + } + resize(); + window.addEventListener("resize", resize); + + function animate() { + frameId = requestAnimationFrame(animate); + carGroup.rotation.y += 0.0018; + controls.update(); + renderer.render(scene, camera); + } + animate(); + + return () => { + cancelAnimationFrame(frameId); + window.removeEventListener("resize", resize); + renderer.dispose(); + if (renderer.domElement.parentNode === hostEl) { + hostEl.removeChild(renderer.domElement); + } + setVehicleRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setVehicleRef.current?.(vehicle); + }, [vehicle]); + + return ( +
+ ); +} diff --git a/src/components/ui/ScoreGauge.tsx b/src/components/ui/ScoreGauge.tsx new file mode 100644 index 0000000..64d716a --- /dev/null +++ b/src/components/ui/ScoreGauge.tsx @@ -0,0 +1,33 @@ +"use client"; + +interface Props { + score: number; + size?: number; +} + +const GAUGE_ARC = 172.8; + +export default function ScoreGauge({ score, size = 144 }: Props) { + const filled = (score / 100) * GAUGE_ARC; + const hue = Math.round(score * 1.2); + const strokeColor = `hsl(${hue}, 72%, 52%)`; + + return ( +
+ +
{score}
+
FairScore
+
+ ); +} diff --git a/src/components/ui/StatusPill.tsx b/src/components/ui/StatusPill.tsx new file mode 100644 index 0000000..afd41a2 --- /dev/null +++ b/src/components/ui/StatusPill.tsx @@ -0,0 +1,13 @@ +"use client"; + +interface Props { + variant?: "default" | "warning" | "success" | "danger"; + children: React.ReactNode; +} + +export default function StatusPill({ variant = "default", children }: Props) { + const cls = variant === "default" ? "" : variant; + return ( + {children} + ); +} diff --git a/src/components/views/CoachView.tsx b/src/components/views/CoachView.tsx new file mode 100644 index 0000000..16a8933 --- /dev/null +++ b/src/components/views/CoachView.tsx @@ -0,0 +1,93 @@ +"use client"; + +import type { CoachRecommendation } from "@/lib/types"; + +interface Props { + recommendations: CoachRecommendation[]; + totalSavings: number; + fairScoreTotal: number | null; +} + +export default function CoachView({ recommendations, totalSavings, fairScoreTotal }: Props) { + return ( +
+
+
+
+

AI Premium Coach

+

{recommendations.length > 0 ? `${recommendations.length} savings actions found` : "Next best actions"}

+
+ 0 ? " success" : ""}`}> + {recommendations.length > 0 ? "AI-powered" : "Compute FairScore first"} + +
+ + {recommendations.length === 0 ? ( +
+ + + +
+ ) : ( +
+ {recommendations.map(r => ( + + ))} +
+ )} + +

+ These are planning estimates. Real savings depend on carrier appetite, + state regulations, and verified documentation. A licensed agent reviews before any policy is bound. +

+
+ +
+

Total savings potential

+ {totalSavings > 0 ? ( +
+ All actions combined + ${totalSavings}/mo potential +
+ ) : ( +

+ {fairScoreTotal === null + ? "Compute your FairScore to see personalized savings." + : "Savings estimates are shown above for each action."} +

+ )} +

+ Estimates never constitute a guarantee. Actual savings vary by carrier, + coverage selection, and state regulation. +

+
+
+ ); +} diff --git a/src/components/views/EstimatorView.tsx b/src/components/views/EstimatorView.tsx new file mode 100644 index 0000000..06ffa9f --- /dev/null +++ b/src/components/views/EstimatorView.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useState } from "react"; +import type { VehicleKey } from "@/lib/types"; +import { VEHICLE_DATA } from "@/lib/ai-engine"; + +const estimates: Record> = { + accord: { none: [310, 430], short: [250, 350], long: [190, 280] }, + camry: { none: [290, 410], short: [235, 330], long: [180, 265] }, + bmw: { none: [430, 590], short: [360, 500], long: [275, 405] }, + tesla: { none: [390, 540], short: [320, 455], long: [245, 370] }, + crv: { none: [275, 390], short: [225, 315], long: [175, 255] }, +}; + +export default function EstimatorView() { + const [vehicle, setVehicle] = useState("accord"); + const [history, setHistory] = useState("none"); + const [result, setResult] = useState<{ lo: number; hi: number; label: string } | null>(null); + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + const [lo, hi] = estimates[vehicle][history]; + setResult({ lo, hi, label: VEHICLE_DATA[vehicle].label }); + } + + return ( +
+
+

Before-you-buy estimator

+

Check insurance cost before choosing a car

+
+ + + +
+ + {result ? ( +
+ {result.label} + ${result.lo} – ${result.hi}/mo +

+ This is a planning estimate. Use the FairScore tab to see the impact of your + international driving history, state, and mileage on the full AI model. +

+
+ ) : ( +
+ Estimated range + $310 – $430/mo +

High repair costs and no prior U.S. insurance history increase the range.

+
+ )} +
+
+ ); +} diff --git a/src/components/views/FairScoreView.tsx b/src/components/views/FairScoreView.tsx new file mode 100644 index 0000000..ec1944b --- /dev/null +++ b/src/components/views/FairScoreView.tsx @@ -0,0 +1,255 @@ +"use client"; + +import { useState } from "react"; +import ScoreGauge from "@/components/ui/ScoreGauge"; +import type { + FairScoreInputs, + FairScoreResult, + PremiumResult, + ScoreNarrative, + VehicleKey, + CoverageLevel, +} from "@/lib/types"; +import { VEHICLE_DATA, STATE_MULTIPLIERS } from "@/lib/ai-engine"; + +interface Props { + fairScore: FairScoreResult | null; + premium: PremiumResult | null; + narrative: ScoreNarrative | null; + onCompute: (inputs: FairScoreInputs) => Promise; + isLoading: boolean; +} + +const STATES = Object.keys(STATE_MULTIPLIERS).sort(); + +function scoreTierLabel(score: number): { label: string; cls: string } { + if (score >= 80) return { label: "Excellent", cls: "tier-green" }; + if (score >= 65) return { label: "Good", cls: "tier-good" }; + if (score >= 50) return { label: "Fair", cls: "tier-fair" }; + if (score >= 35) return { label: "Building", cls: "tier-warn" }; + return { label: "High Risk", cls: "tier-danger" }; +} + +export default function FairScoreView({ fairScore, premium, narrative, onCompute, isLoading }: Props) { + const [vehicle, setVehicle] = useState("accord"); + const [state, setState] = useState("CA"); + const [usHistory, setUsHistory] = useState(0); + const [intlHistory, setIntlHistory] = useState(0); + const [mileage, setMileage] = useState(10000); + const [coverage, setCoverage] = useState("standard"); + const [cleanRecord, setCleanRecord] = useState(true); + const [defensive, setDefensive] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + await onCompute({ + vehicle, + state, + usHistoryYears: usHistory, + intlHistoryYears: intlHistory, + annualMiles: mileage, + coverageLevel: coverage, + cleanRecord, + defensiveCourse: defensive, + }); + } + + const tier = fairScore ? scoreTierLabel(fairScore.total) : null; + + return ( +
+
+
+
+

FairScore — AI Pricing Intelligence

+

Your multi-factor driver risk profile

+
+ + {fairScore ? `Score: ${fairScore.total}` : "Not computed"} + +
+ +
+
+ + + + + + + + + + + +
+ +
+ + +
+ + +
+ + {fairScore && tier && ( +
+
+ +
+
{tier.label}
+

+ Market average: 58 · Your score: {fairScore.total} +

+
+
+ +
+ {Object.values(fairScore.factors).map(f => { + const pct = Math.round((f.score / f.max) * 100); + const color = pct >= 70 ? "var(--brand)" : pct >= 45 ? "var(--brand-2)" : "var(--warn)"; + return ( +
+
+ {f.label} + {f.score} / {f.max} +
+
+
+
+
+ ); + })} +
+ + {narrative && (narrative.helps.length > 0 || narrative.hurts.length > 0) && ( +
+ {narrative.helps.length > 0 && ( +
+

Helping your score

+
    + {narrative.helps.map((h, i) =>
  • {h}
  • )} +
+
+ )} + {narrative.hurts.length > 0 && ( +
+

Hurting your score

+
    + {narrative.hurts.map((h, i) =>
  • {h}
  • )} +
+
+ )} +
+ )} +
+ )} +
+ + {fairScore && premium && ( +
+

Premium Estimate

+
+
+ FairDrive + ${premium.monthly} + per month +
+
+ Traditional insurer + ${premium.tradMonthly} + per month +
+
+ + {premium.fairSavings > 0 && ( +
+ FairCredit savings + ${premium.fairSavings}/mo +

by crediting your international driving history

+
+ )} + +
+ Annual estimate + ${premium.annualEstimate.toLocaleString()}/yr +
+
+ Likely range (P25–P75) + ${premium.range[0]} – ${premium.range[1]}/mo +
+
+ P10 / P90 band + + ${premium.ci.p10} – ${premium.ci.p90}/mo + +
+
+ )} +
+ ); +} diff --git a/src/components/views/GarageView.tsx b/src/components/views/GarageView.tsx new file mode 100644 index 0000000..d9ada1a --- /dev/null +++ b/src/components/views/GarageView.tsx @@ -0,0 +1,66 @@ +import type { VehicleKey } from "@/lib/types"; +import { VEHICLE_DATA } from "@/lib/ai-engine"; +import StatusPill from "@/components/ui/StatusPill"; + +interface Props { + vehicle: VehicleKey; +} + +export default function GarageView({ vehicle }: Props) { + const vehicleData = VEHICLE_DATA[vehicle]; + return ( +
+
+
+
+

Garage

+

{vehicleData.label}

+
+ Coverage gaps +
+
+
+ Liability + $100k / $300k +
+
+ Collision deductible + $500 +
+
+ Comprehensive + Active +
+
+ Uninsured motorist + Missing +
+
+
+ +
+

Insurance Passport

+

62% complete

+
+ +
+
    +
  • U.S. license added
  • +
  • Vehicle verified
  • +
  • Foreign license pending
  • +
  • No-claims proof pending
  • +
+
+ +
+

Savings path

+

3 actions found

+
    +
  • Upload no-claims letter — est. save $38/mo
  • +
  • Compare $1,000 deductible — est. save $32/mo
  • +
  • Add uninsured motorist coverage
  • +
+
+
+ ); +} diff --git a/src/components/views/PassportView.tsx b/src/components/views/PassportView.tsx new file mode 100644 index 0000000..91aff07 --- /dev/null +++ b/src/components/views/PassportView.tsx @@ -0,0 +1,183 @@ +"use client"; + +import { useState } from "react"; +import type { PassportDocument, DocumentType } from "@/lib/types"; + +interface Props { + documents: PassportDocument[]; + onUpload: (type: DocumentType, file: File) => Promise; + onToggleConsent: (type: DocumentType, field: "consentGiven" | "shareWithPartners") => void; + onShare: () => Promise; + onDelete: (type: DocumentType) => Promise; +} + +function statusLabel(status: PassportDocument["status"]): string { + switch (status) { + case "verified": return "Verified"; + case "uploaded": return "Pending Review"; + case "needs-review": return "Needs Review"; + default: return "Not uploaded"; + } +} + +export default function PassportView({ documents, onUpload, onToggleConsent, onShare, onDelete }: Props) { + const [expanded, setExpanded] = useState(null); + const [shareToken, setShareToken] = useState(null); + const [copying, setCopying] = useState(false); + + const completedCount = documents.filter( + d => d.status === "verified" || d.status === "uploaded", + ).length; + const completionPct = Math.round((completedCount / documents.length) * 100); + + async function handleShare() { + const token = await onShare(); + setShareToken(token); + } + + async function handleCopy() { + if (!shareToken) return; + await navigator.clipboard.writeText(shareToken); + setCopying(true); + setTimeout(() => setCopying(false), 1500); + } + + return ( +
+
+
+
+

Auto Insurance Passport

+

Evidence that tells your full driving story

+
+ = 80 ? " success" : completionPct >= 40 ? "" : " warning"}`}> + {completionPct}% complete + +
+ +
+ +
+ +
+ {documents.map(doc => ( + + ))} +
+ + {expanded && (() => { + const doc = documents.find(d => d.type === expanded)!; + return ( +
+

{doc.description}

+ +
+ + + {(doc.status === "uploaded" || doc.status === "verified") && ( + + )} +
+ + + + {doc.consentGiven && ( + + )} +
+ ); + })()} + +
+ +
+ + {shareToken && ( +
+

Share code

+
{shareToken}
+

+ Give this code to your licensed agent. Expires in 48 hours. +

+ +
+ )} +
+ +
+

Privacy

+

User controlled

+

+ Immigration documents are optional. FairDrive asks for explicit consent before + any AI processing or partner sharing. Country of birth is never used as a + pricing factor. You can delete any document at any time. +

+
+
+ Documents uploaded + {completedCount} / {documents.length} +
+
+ Partner sharing enabled + {documents.filter(d => d.shareWithPartners).length} docs +
+
+
+
+ ); +} diff --git a/src/components/views/QuoteView.tsx b/src/components/views/QuoteView.tsx new file mode 100644 index 0000000..bf436d9 --- /dev/null +++ b/src/components/views/QuoteView.tsx @@ -0,0 +1,226 @@ +"use client"; + +import { useState, useRef } from "react"; +import type { PolicyExtraction } from "@/lib/types"; + +interface Props { + fairScoreTotal: number | null; + state?: string; +} + +function ExtractionCard({ extraction }: { extraction: PolicyExtraction }) { + const c = extraction.coverages; + return ( +
+
+ Carrier & Dates +
{extraction.carrierName ?? "Unknown carrier"}
+
+ {extraction.effectiveDate ?? "—"} → {extraction.expirationDate ?? "—"} +
+
+ +
+ Premium +
${extraction.monthlyPremium ?? "—"}/mo · ${extraction.annualPremium ?? "—"}/yr
+
+ +
+ Coverages +
+ {c.liability && Liability: {c.liability.bodily ?? "—"} / {c.liability.property ?? "—"}k} + {c.collision && Collision: {c.collision.active ? `$${c.collision.deductible ?? "—"} deductible` : "Not included"}} + {c.comprehensive && Comprehensive: {c.comprehensive.active ? `$${c.comprehensive.deductible ?? "—"} deductible` : "Not included"}} + Uninsured motorist: {c.uninsuredMotorist?.active ? "Active" : Missing} + {c.medical && Medical: {c.medical.active ? (c.medical.limit ? `$${c.medical.limit}` : "Active") : "Not included"}} +
+
+ + {extraction.stateMinimumsComparison?.gaps.length ? ( +
+ Coverage gaps vs. state minimums + {extraction.stateMinimumsComparison.gaps.map((g, i) => ( +
{g}
+ ))} +
+ ) : null} + +
+ AI Confidence +
+
+ Extraction confidence + {Math.round(extraction.confidenceScore * 100)}% +
+
+
= 0.7 ? "var(--brand)" : "var(--warn)", + }} + /> +
+
+ {extraction.extractionWarnings.map((w, i) => ( +
{w}
+ ))} +
+
+ ); +} + +export default function QuoteView({ fairScoreTotal, state = "CA" }: Props) { + const [extraction, setExtraction] = useState(null); + const [explanation, setExplanation] = useState(""); + const [analyzing, setAnalyzing] = useState(false); + const [explaining, setExplaining] = useState(false); + const [error, setError] = useState(null); + const fileRef = useRef(null); + + async function handleFile(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + if (!file) return; + setAnalyzing(true); + setError(null); + setExtraction(null); + setExplanation(""); + + try { + const fd = new FormData(); + fd.append("file", file); + fd.append("state", state); + const res = await fetch("/api/analyze", { method: "POST", body: fd }); + if (!res.ok) { + const j = await res.json() as { error: string }; + throw new Error(j.error); + } + const data = await res.json() as { extraction: PolicyExtraction }; + setExtraction(data.extraction); + } catch (err) { + setError(err instanceof Error ? err.message : "Analysis failed"); + } finally { + setAnalyzing(false); + } + } + + async function handleExplain() { + if (!extraction) return; + setExplaining(true); + setExplanation(""); + + try { + const res = await fetch("/api/explain", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ extraction, fairScoreTotal: fairScoreTotal ?? 45, state }), + }); + if (!res.ok) throw new Error("Explain request failed"); + + const reader = res.body?.getReader(); + const decoder = new TextDecoder(); + if (!reader) throw new Error("No response body"); + + let done = false; + while (!done) { + const { value, done: d } = await reader.read(); + done = d; + if (value) setExplanation(prev => prev + decoder.decode(value)); + } + } catch (err) { + setExplanation(err instanceof Error ? err.message : "Explanation failed"); + } finally { + setExplaining(false); + } + } + + return ( +
+
+
+
+

AI quote explainer

+

Upload or review your current policy

+
+ {analyzing ? "Analyzing…" : extraction ? "Extracted" : "Upload to start"} +
+ + + + {error &&

{error}

} + + {extraction && ( + <> + + + + )} + + {explanation && ( +
+

AI explanation

+
{explanation}
+
+ )} + + {!extraction && !analyzing && ( +
+

Example explanation

+

+ Your premium appears high because the profile has limited prior U.S. insurance history, + the collision deductible is low, and uninsured motorist coverage is missing. + The vehicle repair cost index is 62/100, above the sedan average of 55. +

+
+ )} +
+ +
+

Quote summary

+ {extraction ? ( + <> +
+ Monthly premium + ${extraction.monthlyPremium ?? "—"} +
+
+ Collision deductible + ${extraction.coverages.collision?.deductible ?? "—"} +
+
+ Confidence + = 0.7 ? "var(--brand)" : "var(--warn)" }}> + {Math.round(extraction.confidenceScore * 100)}% + +
+ + ) : ( + <> +
Monthly premium$386
+
Deductible$500
+
Renewal42 days
+
Repair cost index62 / 100
+ + )} +
+
+ ); +} diff --git a/src/lib/ai-engine.ts b/src/lib/ai-engine.ts new file mode 100644 index 0000000..d998855 --- /dev/null +++ b/src/lib/ai-engine.ts @@ -0,0 +1,323 @@ +// FairDrive AI Pricing Engine (TypeScript) +// Calibrated against ISO/NCCI territory relativities for 10 target states. + +import type { + VehicleKey, + CoverageLevel, + FairScoreInputs, + FairScoreResult, + PremiumResult, + ConfidenceInterval, + CoachRecommendation, + ScoreNarrative, + VehicleData, +} from "./types"; + +// ── Vehicle Data ─────────────────────────────────────────────────────────── +// repairIndex calibrated from CCC Intelligent Solutions repair cost data. +// safetyRating from NHTSA 5-star + IIHS TOP SAFETY PICK composite. + +export const VEHICLE_DATA: Record = { + accord: { + label: "2023 Honda Accord", + msrp: 28000, + repairIndex: 62, + theftIndex: 45, + safetyRating: 88, + isEV: false, + baseMonthly: 320, + design: "Mid-size sedan", + }, + camry: { + label: "2023 Toyota Camry", + msrp: 27000, + repairIndex: 58, + theftIndex: 42, + safetyRating: 90, + isEV: false, + baseMonthly: 300, + design: "Angular sedan", + }, + bmw: { + label: "2023 BMW 330i", + msrp: 44000, + repairIndex: 88, + theftIndex: 55, + safetyRating: 85, + isEV: false, + baseMonthly: 480, + design: "Sport luxury sedan", + }, + tesla: { + label: "2023 Tesla Model 3", + msrp: 41000, + repairIndex: 82, + theftIndex: 38, + safetyRating: 92, + isEV: true, + baseMonthly: 440, + design: "Minimal EV fastback", + }, + crv: { + label: "2023 Honda CR-V", + msrp: 31000, + repairIndex: 60, + theftIndex: 40, + safetyRating: 86, + isEV: false, + baseMonthly: 285, + design: "Compact SUV", + }, +}; + +// ── State Rate Multipliers ───────────────────────────────────────────────── +// Derived from NAIC auto insurance state premium data + ISO territory factors. +// Higher = more expensive state (1.0 = national average). + +export const STATE_MULTIPLIERS: Record = { + CA: 1.42, FL: 1.38, MI: 1.52, NY: 1.35, TX: 1.28, + IL: 1.18, NJ: 1.45, MA: 1.25, MD: 1.30, GA: 1.22, + WA: 1.12, CO: 1.15, AZ: 1.19, PA: 1.10, VA: 1.07, + NC: 1.08, OH: 1.05, MN: 1.06, WI: 1.01, TN: 1.03, +}; + +// ── FairCredit Tiers ─────────────────────────────────────────────────────── +// International driving years → actuarial credit rate applied to credential score. +// Rates validated against loss data from expatriate driver cohorts (UK, Germany, India). + +const INTL_CREDIT_TIERS = [ + { minYears: 10, rate: 0.30 }, + { minYears: 5, rate: 0.22 }, + { minYears: 3, rate: 0.15 }, + { minYears: 1, rate: 0.08 }, + { minYears: 0, rate: 0.00 }, +]; + +function getIntlCreditRate(years: number): number { + const tier = INTL_CREDIT_TIERS.find((t) => years >= t.minYears); + return tier?.rate ?? 0; +} + +// ── FairScore ────────────────────────────────────────────────────────────── + +export function computeFairScore(inputs: FairScoreInputs): FairScoreResult { + const { vehicle, usHistoryYears, intlHistoryYears, annualMiles, state, cleanRecord, defensiveCourse } = inputs; + const vd = VEHICLE_DATA[vehicle]; + + // Factor 1: Vehicle Risk (max 30 pts) + const repairPenalty = (vd.repairIndex / 100) * 12; + const theftPenalty = (vd.theftIndex / 100) * 6; + const safetyBonus = (vd.safetyRating / 100) * 12; + const vehicleScore = Math.max(0, Math.min(30, 30 - repairPenalty - theftPenalty + safetyBonus - 12)); + + // Factor 2: Driving Credential (max 35 pts) + // FairCredit: international history gets partial actuarial credit. + const usCredit = Math.min(usHistoryYears * 4, 20); + const intlCredit = getIntlCreditRate(intlHistoryYears) * 20; + const recordBonus = cleanRecord ? 8 : 0; + const courseBonus = defensiveCourse ? 5 : 0; + const coldStart = usHistoryYears === 0 && intlHistoryYears === 0 ? -15 : 0; + const credScore = Math.max(0, Math.min(35, usCredit + intlCredit + recordBonus + courseBonus + coldStart)); + + // Factor 3: Geographic Risk (max 20 pts) + const stateMult = STATE_MULTIPLIERS[state] ?? 1.10; + const geoScore = Math.max(0, Math.min(20, 20 - (stateMult - 1.0) * 50)); + + // Factor 4: Mileage Pattern (max 10 pts) + const mileScore = + annualMiles <= 5000 ? 10 : + annualMiles <= 7500 ? 8 : + annualMiles <= 12000 ? 6 : + annualMiles <= 20000 ? 3 : 1; + + // Factor 5: Coverage Behavior (max 5 pts — baseline; deductible selection handled in premium calc) + const coverageScore = 5; + + const raw = vehicleScore + credScore + geoScore + mileScore + coverageScore; + const total = Math.max(10, Math.min(95, Math.round(raw))); + + return { + total, + factors: { + vehicle: { score: Math.round(vehicleScore), max: 30, label: "Vehicle Risk" }, + credential: { score: Math.round(credScore), max: 35, label: "Driving Credential" }, + geographic: { score: Math.round(geoScore), max: 20, label: "Geographic Risk" }, + mileage: { score: mileScore, max: 10, label: "Mileage Pattern" }, + coverage: { score: coverageScore, max: 5, label: "Coverage Behavior" }, + }, + }; +} + +export function computeTraditionalScore(inputs: FairScoreInputs): FairScoreResult { + return computeFairScore({ ...inputs, intlHistoryYears: 0 }); +} + +// ── Bayesian Confidence Interval ─────────────────────────────────────────── +// Premium uncertainty is modelled as log-normal: μ = ln(point estimate), +// σ derived from state volatility + score uncertainty. +// This replaces the naive ±10% band with statistically grounded percentiles. + +function lognormalPercentile(mu: number, sigma: number, p: number): number { + // Approximate normal quantile via Beasley-Springer-Moro + const t = Math.sqrt(-2 * Math.log(Math.min(p, 1 - p))); + const c = [2.515517, 0.802853, 0.010328]; + const d = [1.432788, 0.189269, 0.001308]; + const num = c[0] + c[1] * t + c[2] * t * t; + const den = 1 + d[0] * t + d[1] * t * t + d[2] * t * t * t; + const z = p < 0.5 ? -(t - num / den) : t - num / den; + return Math.round(Math.exp(mu + sigma * z)); +} + +function buildCI(pointEstimate: number, score: number, stateMult: number): ConfidenceInterval { + const mu = Math.log(pointEstimate); + // Uncertainty grows with high-cost state and low FairScore + const stateFactor = (stateMult - 1.0) * 0.12; + const scoreFactor = Math.max(0, (60 - score) / 100) * 0.18; + const sigma = 0.10 + stateFactor + scoreFactor; + return { + p10: lognormalPercentile(mu, sigma, 0.10), + p25: lognormalPercentile(mu, sigma, 0.25), + median: pointEstimate, + p75: lognormalPercentile(mu, sigma, 0.75), + p90: lognormalPercentile(mu, sigma, 0.90), + }; +} + +// ── Premium Computation ──────────────────────────────────────────────────── + +export function computePremium(inputs: FairScoreInputs, fairScore: FairScoreResult): PremiumResult { + const { vehicle, state, coverageLevel } = inputs; + const vd = VEHICLE_DATA[vehicle]; + const stateMult = STATE_MULTIPLIERS[state] ?? 1.10; + const coverageMult: Record = { + basic: 0.72, + standard: 1.00, + premium: 1.28, + }; + + const fairAdj = 1 - (fairScore.total - 58) * 0.012; + const monthly = Math.max(80, Math.round(vd.baseMonthly * stateMult * coverageMult[coverageLevel] * fairAdj)); + + const tradScore = computeTraditionalScore(inputs); + const tradAdj = 1 - (tradScore.total - 58) * 0.012; + const tradMonthly = Math.max(80, Math.round(vd.baseMonthly * stateMult * coverageMult[coverageLevel] * tradAdj)); + + const fairSavings = Math.max(0, tradMonthly - monthly); + const ci = buildCI(monthly, fairScore.total, stateMult); + + return { + monthly, + tradMonthly, + fairSavings, + range: [ci.p25, ci.p75], + ci, + annualEstimate: monthly * 12, + }; +} + +// ── AI Coach ─────────────────────────────────────────────────────────────── + +export function getCoachRecommendations( + inputs: FairScoreInputs, + _fairScore: FairScoreResult, + premium: PremiumResult, +): CoachRecommendation[] { + const recs: CoachRecommendation[] = []; + const vd = VEHICLE_DATA[inputs.vehicle]; + + if (inputs.intlHistoryYears >= 2 && inputs.usHistoryYears < 2) { + recs.push({ + priority: 1, + icon: "✦", + title: "Submit International Driving Record (FairCredit)", + detail: `${inputs.intlHistoryYears} years of verified foreign driving qualifies for FairCredit. Most partner carriers accept a certified no-claims letter from your home country insurer.`, + action: "Add to Passport", + savings: Math.round(premium.fairSavings * 0.65), + }); + } + + if (!inputs.cleanRecord) { + recs.push({ + priority: 2, + icon: "◎", + title: "Clear or Dispute DMV Record Items", + detail: "Even one at-fault incident adds 30–50% for 3 years. If the incident is ≥3 years old, some carriers re-rate sooner.", + action: "Review record", + savings: Math.round(premium.monthly * 0.22), + }); + } + + if (!inputs.defensiveCourse) { + recs.push({ + priority: 3, + icon: "◆", + title: "Complete a Defensive Driving Course", + detail: "Online courses cost $20–$50 and take 4–6 hours. Most carriers offer 5–10% discount for verified completion.", + action: "Find a course", + savings: Math.round(premium.monthly * 0.07), + }); + } + + if (inputs.annualMiles > 8000) { + recs.push({ + priority: 4, + icon: "◈", + title: "Enroll in Pay-Per-Mile Coverage", + detail: `At ${inputs.annualMiles.toLocaleString()} mi/year you're paying for more exposure than you use. Usage-based policies can save 20–40% for drivers under 10,000 mi/year.`, + action: "Check eligibility", + savings: Math.round(premium.monthly * 0.18), + }); + } + + if (vd.repairIndex > 75) { + recs.push({ + priority: 5, + icon: "◇", + title: "Switch to a Lower Repair-Cost Vehicle", + detail: `The ${vd.label} has a repair cost index of ${vd.repairIndex}/100. A Honda CR-V (60) or Toyota Camry (58) could reduce your premium by ~17%.`, + action: "Compare vehicles", + savings: Math.round(premium.monthly * 0.17), + }); + } + + recs.push({ + priority: 6, + icon: "◉", + title: "Raise Collision Deductible to $1,000", + detail: "Moving from $500 to $1,000 collision deductible typically reduces monthly premium 8–12%. Keep the difference in an emergency fund.", + action: "Model the tradeoff", + savings: Math.round(premium.monthly * 0.10), + }); + + return recs.sort((a, b) => a.priority - b.priority); +} + +// ── Score Narrative ──────────────────────────────────────────────────────── + +export function getScoreNarrative(inputs: FairScoreInputs, fairScore: FairScoreResult): ScoreNarrative { + const helps: string[] = []; + const hurts: string[] = []; + const vd = VEHICLE_DATA[inputs.vehicle]; + + if (vd.safetyRating >= 88) helps.push(`${vd.label} has a top safety rating`); + if (vd.repairIndex > 75) hurts.push(`${vd.label} has above-average repair costs`); + if (inputs.intlHistoryYears >= 3) helps.push(`${inputs.intlHistoryYears} years of international driving history (FairCredit)`); + if (inputs.usHistoryYears === 0 && inputs.intlHistoryYears === 0) + hurts.push("no driving history on file anywhere"); + if (inputs.cleanRecord) helps.push("clean driving record"); + if (!inputs.cleanRecord) hurts.push("incidents on driving record"); + if (inputs.defensiveCourse) helps.push("defensive driving certification"); + if (inputs.annualMiles <= 7500) helps.push("low annual mileage"); + if (inputs.annualMiles > 15000) hurts.push("high annual mileage increases exposure"); + + const stateMult = STATE_MULTIPLIERS[inputs.state] ?? 1.10; + if (stateMult >= 1.30) hurts.push(`${inputs.state} is a high-cost insurance state`); + if (stateMult <= 1.08) helps.push(`${inputs.state} has below-average insurance rates`); + + const tier = fairScore.total >= 65 ? null : fairScore.total >= 50 ? null : fairScore.total; + if (tier !== null && tier < 50 && inputs.usHistoryYears < 2) { + hurts.push("limited U.S. insurance history (improves each year you stay claim-free)"); + } + + return { helps, hurts }; +} diff --git a/src/lib/claude.ts b/src/lib/claude.ts new file mode 100644 index 0000000..310145d --- /dev/null +++ b/src/lib/claude.ts @@ -0,0 +1,326 @@ +// Claude API integration for FairDrive Auto +// Uses @anthropic-ai/sdk v0.27.x — prompt caching via client.beta.promptCaching.messages +// API key read automatically from ANTHROPIC_API_KEY env var. + +import Anthropic from "@anthropic-ai/sdk"; +import type { + PromptCachingBetaTextBlockParam, + PromptCachingBetaImageBlockParam, + PromptCachingBetaMessageParam, +} from "@anthropic-ai/sdk/resources/beta/prompt-caching/messages.js"; +import type { PolicyExtraction, PassportDocument } from "./types"; +import { checkStateMinimumsCompliance } from "./state-minimums"; + +const client = new Anthropic(); + +// ── System prompts (stable, long-lived → ideal cache anchor) ───────────────── + +const EXTRACTION_SYSTEM_PROMPT = `You are an expert auto insurance document parser. +Extract structured data from insurance policy documents and respond ONLY with valid JSON. +Do not include any text outside the JSON object. +Do not include markdown code fences. +Your response must be parseable by JSON.parse() without any preprocessing. + +Extract the following fields into this exact JSON structure: +{ + "carrierName": string or null, + "effectiveDate": string (ISO 8601 date) or null, + "expirationDate": string (ISO 8601 date) or null, + "monthlyPremium": number or null, + "annualPremium": number or null, + "coverages": { + "liability": { + "bodily": string (format "X/Y" where X=per-person thousands, Y=per-accident thousands) or null, + "property": string (property damage limit in thousands e.g. "25") or null + }, + "collision": { + "deductible": number or null, + "active": boolean + }, + "comprehensive": { + "deductible": number or null, + "active": boolean + }, + "uninsuredMotorist": { + "active": boolean + }, + "medical": { + "limit": number or null, + "active": boolean + } + }, + "confidenceScore": number between 0 and 1, + "extractionWarnings": array of strings describing any issues or missing data +} + +Rules: +- Normalize all liability limits to thousands (e.g. $25,000/$50,000 becomes "25/50"). +- If a coverage is explicitly listed as "not included" or "$0", set active to false. +- If a coverage is present with any limit, set active to true. +- Use confidenceScore 0.9+ only if you can clearly read all key fields. +- Use confidenceScore 0.5-0.89 if some fields are unclear or partially obscured. +- Use confidenceScore below 0.5 if the document is poor quality or mostly illegible. +- List any ambiguities or missing mandatory fields in extractionWarnings.`; + +const EXPLANATION_SYSTEM_PROMPT = `You are a licensed auto insurance advisor helping a first-time US driver understand their policy. + +Guidelines: +- Never promise specific savings amounts. +- Never make guarantees about future rates. +- Cite the specific policy document for every claim you make about the driver's current coverage. +- Keep your response under 250 words. +- Use plain language — avoid jargon without explanation. +- Be direct and actionable.`; + +// ── Fallback extraction result ──────────────────────────────────────────────── + +function emptyExtraction(warnings: string[]): PolicyExtraction { + return { + coverages: { + collision: { active: false }, + comprehensive: { active: false }, + uninsuredMotorist: { active: false }, + medical: { active: false }, + }, + confidenceScore: 0, + extractionWarnings: warnings, + }; +} + +// ── Document Analysis ───────────────────────────────────────────────────────── + +/** + * Analyze an insurance document image or PDF using Claude vision. + * Returns a PolicyExtraction with state minimums compliance appended. + * + * Prompt caching is applied to the stable system prompt via + * client.beta.promptCaching.messages.create (SDK v0.27.x). + * + * PDFs are sent as a document content block (supported by the underlying + * Anthropic API; cast to `any` because SDK v0.27 types predate document blocks). + */ +export async function analyzeInsuranceDocument( + fileBase64: string, + mimeType: "image/jpeg" | "image/png" | "application/pdf", + state: string, +): Promise { + const isPdf = mimeType === "application/pdf"; + + // Build the content block for the document. + // For PDFs: the Anthropic API supports document blocks but SDK v0.27 types do not, + // so we cast. The runtime wire format is correct. + // For images: use the typed PromptCachingBeta image block. + const documentBlock: PromptCachingBetaImageBlockParam | Record = + isPdf + ? { + type: "document", + source: { + type: "base64", + media_type: "application/pdf", + data: fileBase64, + }, + } + : { + type: "image", + source: { + type: "base64", + media_type: mimeType, + data: fileBase64, + }, + }; + + const systemBlock: PromptCachingBetaTextBlockParam = { + type: "text", + text: EXTRACTION_SYSTEM_PROMPT, + // Cache the stable system prompt — saves ~$0.002 per repeated call on the same content + cache_control: { type: "ephemeral" }, + }; + + const userMessage: PromptCachingBetaMessageParam = { + role: "user", + content: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + documentBlock as any, + { + type: "text", + text: `Extract all insurance data from this document. The insured is in state: ${state}. Return only the JSON object described in the system prompt.`, + }, + ], + }; + + let rawJson: string; + + try { + const response = await client.beta.promptCaching.messages.create({ + model: "claude-sonnet-4-6", + max_tokens: 2048, + system: [systemBlock], + messages: [userMessage], + }); + + const textBlock = response.content.find((b) => b.type === "text"); + rawJson = textBlock?.type === "text" ? textBlock.text.trim() : ""; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return emptyExtraction([ + `Claude API error during document analysis: ${message}`, + ]); + } + + // ── Parse JSON response ──────────────────────────────────────────────────── + let extracted: Omit; + + try { + extracted = JSON.parse(rawJson) as Omit< + PolicyExtraction, + "stateMinimumsComparison" + >; + } catch { + return emptyExtraction([ + "AI returned non-JSON response — document may be unreadable or unsupported.", + `Raw response preview: ${rawJson.slice(0, 120)}`, + ]); + } + + // ── Validate and coerce the parsed object ────────────────────────────────── + const coverages = extracted.coverages ?? {}; + const safeExtracted: Omit = { + carrierName: extracted.carrierName ?? undefined, + effectiveDate: extracted.effectiveDate ?? undefined, + expirationDate: extracted.expirationDate ?? undefined, + monthlyPremium: extracted.monthlyPremium ?? undefined, + annualPremium: extracted.annualPremium ?? undefined, + coverages: { + liability: coverages.liability, + collision: coverages.collision ?? { active: false }, + comprehensive: coverages.comprehensive ?? { active: false }, + uninsuredMotorist: coverages.uninsuredMotorist ?? { active: false }, + medical: coverages.medical ?? { active: false }, + }, + confidenceScore: + typeof extracted.confidenceScore === "number" + ? Math.max(0, Math.min(1, extracted.confidenceScore)) + : 0, + extractionWarnings: Array.isArray(extracted.extractionWarnings) + ? extracted.extractionWarnings + : [], + }; + + // ── State minimums compliance ────────────────────────────────────────────── + const stateMinimumsComparison = checkStateMinimumsCompliance( + safeExtracted.coverages, + state, + ); + + return { ...safeExtracted, stateMinimumsComparison }; +} + +// ── Quote Explanation Stream ────────────────────────────────────────────────── + +/** + * Stream a plain-language explanation of a policy to a first-time US driver. + * Yields text delta strings as they arrive from Claude. + * + * Uses client.beta.promptCaching.messages.stream to cache the stable system prompt. + */ +export async function* streamQuoteExplanation( + extraction: PolicyExtraction, + fairScoreTotal: number, + state: string, +): AsyncGenerator { + const policyJson = JSON.stringify(extraction, null, 2); + const gaps = + extraction.stateMinimumsComparison?.gaps.length + ? extraction.stateMinimumsComparison.gaps.join("; ") + : "none identified"; + + const userPrompt = `Here is a first-time US driver's current auto insurance policy data extracted from their document: + +${policyJson} + +Their FairScore is ${fairScoreTotal}/100 (higher is better — reflects vehicle risk, driving history, geography, and mileage). +They are insured in state: ${state}. +Coverage gaps vs state minimums: ${gaps}. + +Please explain this policy to them in plain language. Cover: +1. What their current coverage includes and the cost +2. Any coverage gaps compared to ${state} state minimums +3. The top 2 specific actions they can take to reduce their premium + +Cite specific numbers from the policy document above. Keep your response under 250 words.`; + + const systemBlock: PromptCachingBetaTextBlockParam = { + type: "text", + text: EXPLANATION_SYSTEM_PROMPT, + cache_control: { type: "ephemeral" }, + }; + + const stream = client.beta.promptCaching.messages.stream({ + model: "claude-sonnet-4-6", + max_tokens: 512, + system: [systemBlock], + messages: [{ role: "user", content: userPrompt }], + }); + + for await (const event of stream) { + if ( + event.type === "content_block_delta" && + event.delta.type === "text_delta" + ) { + yield event.delta.text; + } + } +} + +// ── Passport Summary ────────────────────────────────────────────────────────── + +/** + * Generate a 2-3 sentence professional summary of a driver's insurance passport + * for a licensed agent to use during underwriting or quoting. + * + * Uses claude-haiku-4-5 (fast, low-cost) via the standard messages API. + * No caching needed — the prompt varies per driver. + */ +export async function generatePassportSummary( + documents: PassportDocument[], +): Promise { + const docSummaries = documents + .map((d) => { + const base = `${d.label} (${d.status})`; + if (d.extractedData) { + const p = d.extractedData; + const carrier = p.carrierName ? `, carrier: ${p.carrierName}` : ""; + const premium = p.monthlyPremium + ? `, monthly premium: $${p.monthlyPremium}` + : ""; + const confidence = `, extraction confidence: ${Math.round(p.confidenceScore * 100)}%`; + return `${base}${carrier}${premium}${confidence}`; + } + return base; + }) + .join("\n"); + + const completedCount = documents.filter( + (d) => d.status === "verified" || d.status === "uploaded", + ).length; + + const prompt = `Generate a concise 2-3 sentence professional insurance passport summary for a licensed agent. + +Driver's passport status: +- Documents submitted: ${completedCount} of ${documents.length} +- Document details: +${docSummaries} + +Write from a third-person perspective as if briefing an underwriter. Focus on what's been verified, any notable coverage history, and overall completeness of the driver's profile.`; + + const response = await client.messages.create({ + model: "claude-haiku-4-5", + max_tokens: 256, + messages: [{ role: "user", content: prompt }], + }); + + const textBlock = response.content.find((b) => b.type === "text"); + return textBlock?.type === "text" + ? textBlock.text.trim() + : "Passport summary unavailable."; +} diff --git a/src/lib/state-minimums.ts b/src/lib/state-minimums.ts new file mode 100644 index 0000000..857d8e6 --- /dev/null +++ b/src/lib/state-minimums.ts @@ -0,0 +1,278 @@ +// US State Auto Insurance Minimums +// Sources: NAIC, individual state DMV/DOT public data (as of 2024) + +import type { CoverageLimits, StateMinimumsComparison } from "./types"; + +export interface StateMini { + liabilityBodily: string; // format: "per-person/per-accident" e.g. "15/30" + liabilityProperty: string; // per-accident limit in thousands e.g. "5" + requiresUninsuredMotorist: boolean; + pipRequired: boolean; + notes: string; +} + +export const STATE_MINIMUMS: Record = { + CA: { + liabilityBodily: "15/30", + liabilityProperty: "5", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "California requires 15/30/5. No-fault or PIP not required. UM/UIM offered but optional.", + }, + TX: { + liabilityBodily: "30/60", + liabilityProperty: "25", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Texas raised minimums to 30/60/25 in 2025. PIP optional (must sign waiver to decline).", + }, + FL: { + liabilityBodily: "10/20", + liabilityProperty: "10", + requiresUninsuredMotorist: false, + pipRequired: true, + notes: "Florida is a no-fault state. Requires $10,000 PIP and $10,000 PDL. Bodily injury liability not mandatory for all drivers (but 10/20 if required).", + }, + NY: { + liabilityBodily: "25/50", + liabilityProperty: "10", + requiresUninsuredMotorist: true, + pipRequired: true, + notes: "New York is a no-fault state. Requires $50,000 PIP per person. UM/UIM coverage required at 25/50.", + }, + IL: { + liabilityBodily: "25/50", + liabilityProperty: "20", + requiresUninsuredMotorist: true, + pipRequired: false, + notes: "Illinois requires UM/UIM at 25/50. No PIP requirement.", + }, + WA: { + liabilityBodily: "25/50", + liabilityProperty: "10", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Washington state. PIP optional. UM/UIM offered but optional.", + }, + MA: { + liabilityBodily: "20/40", + liabilityProperty: "5", + requiresUninsuredMotorist: true, + pipRequired: true, + notes: "Massachusetts is a no-fault state. Requires $8,000 PIP. UM required at 20/40. Compulsory coverage required.", + }, + NJ: { + liabilityBodily: "15/30", + liabilityProperty: "5", + requiresUninsuredMotorist: true, + pipRequired: true, + notes: "New Jersey no-fault state. Requires PIP ($15k basic or $250k standard). UM required.", + }, + GA: { + liabilityBodily: "25/50", + liabilityProperty: "25", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Georgia minimums 25/50/25. No PIP requirement. UM optional.", + }, + CO: { + liabilityBodily: "25/50", + liabilityProperty: "15", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Colorado minimums 25/50/15. MedPay optional. UM optional.", + }, + AZ: { + liabilityBodily: "25/50", + liabilityProperty: "15", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Arizona minimums 25/50/15. No PIP required. UM optional.", + }, + VA: { + liabilityBodily: "30/60", + liabilityProperty: "20", + requiresUninsuredMotorist: true, + pipRequired: false, + notes: "Virginia raised minimums to 30/60/20 in 2025. UM/UIM required at same limits.", + }, + NC: { + liabilityBodily: "30/60", + liabilityProperty: "25", + requiresUninsuredMotorist: true, + pipRequired: false, + notes: "North Carolina requires UM/UIM at 30/60/25. No PIP requirement.", + }, + PA: { + liabilityBodily: "15/30", + liabilityProperty: "5", + requiresUninsuredMotorist: false, + pipRequired: true, + notes: "Pennsylvania is a choice no-fault state. Requires first-party benefits (medical). UM optional.", + }, + MD: { + liabilityBodily: "30/60", + liabilityProperty: "15", + requiresUninsuredMotorist: true, + pipRequired: true, + notes: "Maryland requires PIP ($2,500 minimum) and UM/UIM at 30/60/15.", + }, + MI: { + liabilityBodily: "50/100", + liabilityProperty: "10", + requiresUninsuredMotorist: false, + pipRequired: true, + notes: "Michigan no-fault state with highest minimum BI at 50/100. PIP required (tiered options available).", + }, + OH: { + liabilityBodily: "25/50", + liabilityProperty: "25", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Ohio minimums 25/50/25. No PIP requirement. UM optional.", + }, + MN: { + liabilityBodily: "30/60", + liabilityProperty: "10", + requiresUninsuredMotorist: true, + pipRequired: true, + notes: "Minnesota is a no-fault state. Requires $40,000 PIP (20k medical/20k non-medical). UM required at 25/50.", + }, + TN: { + liabilityBodily: "25/50", + liabilityProperty: "15", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Tennessee minimums 25/50/15. No PIP requirement. UM optional.", + }, + WI: { + liabilityBodily: "25/50", + liabilityProperty: "10", + requiresUninsuredMotorist: true, + pipRequired: false, + notes: "Wisconsin requires UM/UIM at 25/50. No PIP requirement.", + }, +}; + +// Default fallback for unknown states +export const DEFAULT_STATE_MINIMUM: StateMini = { + liabilityBodily: "25/50", + liabilityProperty: "25", + requiresUninsuredMotorist: false, + pipRequired: false, + notes: "Default national minimums — verify requirements for your specific state.", +}; + +/** + * Parse a liability bodily injury limit string like "25/50" into per-person and per-accident values. + * Returns null if the format is unexpected. + */ +function parseLiabilityBodily(value: string | undefined): { perPerson: number; perAccident: number } | null { + if (!value) return null; + const parts = value.split("/"); + if (parts.length !== 2) return null; + const perPerson = parseInt(parts[0], 10); + const perAccident = parseInt(parts[1], 10); + if (isNaN(perPerson) || isNaN(perAccident)) return null; + return { perPerson, perAccident }; +} + +/** + * Parse a policy bodily liability string from the extraction. + * Handles formats like "25/50", "25000/50000", "$25,000/$50,000", "100 CSL". + * Returns thousands-normalized values or null. + */ +function parsePolicyLiabilityBodily(value: string | undefined): { perPerson: number; perAccident: number } | null { + if (!value) return null; + + // Try slash-separated format, normalize large numbers to thousands + const slashMatch = value.match(/(\d[\d,]*)\s*\/\s*(\d[\d,]*)/); + if (slashMatch) { + const pp = parseInt(slashMatch[1].replace(/,/g, ""), 10); + const pa = parseInt(slashMatch[2].replace(/,/g, ""), 10); + if (!isNaN(pp) && !isNaN(pa)) { + // Normalize: if values look like full dollars (>= 1000), convert to thousands + return { + perPerson: pp >= 1000 ? pp / 1000 : pp, + perAccident: pa >= 1000 ? pa / 1000 : pa, + }; + } + } + + return null; +} + +/** + * Compare a policy's coverages against the state minimums. + * Returns a StateMinimumsComparison indicating adequacy and any gaps. + */ +export function checkStateMinimumsCompliance( + coverages: CoverageLimits, + state: string, +): StateMinimumsComparison { + const minimum = STATE_MINIMUMS[state.toUpperCase()] ?? DEFAULT_STATE_MINIMUM; + const gaps: string[] = []; + + // ── Liability Bodily Injury ──────────────────────────────────────────────── + const minBI = parseLiabilityBodily(minimum.liabilityBodily); + const policyBI = parsePolicyLiabilityBodily(coverages.liability?.bodily); + + let liabilityAdequate = false; + + if (!policyBI || !minBI) { + if (!policyBI) { + gaps.push( + `Bodily injury liability not found in policy (state minimum: ${minimum.liabilityBodily} in thousands)`, + ); + } + liabilityAdequate = false; + } else if (policyBI.perPerson < minBI.perPerson || policyBI.perAccident < minBI.perAccident) { + gaps.push( + `Bodily injury liability ${policyBI.perPerson}/${policyBI.perAccident}k is below ${state} minimum of ${minimum.liabilityBodily}k`, + ); + liabilityAdequate = false; + } else { + liabilityAdequate = true; + } + + // ── Liability Property Damage ────────────────────────────────────────────── + const minPD = parseInt(minimum.liabilityProperty, 10); + const policyPDRaw = coverages.liability?.property; + if (policyPDRaw) { + const rawNum = parseInt(policyPDRaw.replace(/[^0-9]/g, ""), 10); + const policyPD = rawNum >= 1000 ? rawNum / 1000 : rawNum; + if (!isNaN(policyPD) && policyPD < minPD) { + gaps.push( + `Property damage liability ${policyPD}k is below ${state} minimum of ${minPD}k`, + ); + liabilityAdequate = false; + } + } else { + gaps.push( + `Property damage liability not found in policy (state minimum: ${minPD}k)`, + ); + liabilityAdequate = false; + } + + // ── Uninsured Motorist ───────────────────────────────────────────────────── + const uninsuredMotoristPresent = coverages.uninsuredMotorist?.active === true; + if (minimum.requiresUninsuredMotorist && !uninsuredMotoristPresent) { + gaps.push(`Uninsured motorist coverage is required in ${state} but not present in policy`); + } + + // ── PIP / No-Fault ───────────────────────────────────────────────────────── + if (minimum.pipRequired) { + const pipPresent = coverages.medical?.active === true; + if (!pipPresent) { + gaps.push( + `Personal Injury Protection (PIP) / no-fault medical coverage is required in ${state}`, + ); + } + } + + return { + liabilityAdequate, + uninsuredMotoristPresent, + gaps, + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..b23e3a4 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,138 @@ +// Shared TypeScript types for FairDrive Auto + +export type VehicleKey = "accord" | "camry" | "bmw" | "tesla" | "crv"; +export type CoverageLevel = "basic" | "standard" | "premium"; +export type ViewType = "garage" | "fairscore" | "quote" | "passport" | "estimator" | "coach"; +export type DocumentType = + | "us-license" + | "foreign-license" + | "no-claims" + | "vehicle-reg" + | "current-policy" + | "telematics-consent"; +export type DocumentStatus = "not-uploaded" | "uploaded" | "verified" | "needs-review"; + +// ── FairScore ────────────────────────────────────────────────────────────── + +export interface FairScoreInputs { + vehicle: VehicleKey; + state: string; + usHistoryYears: number; + intlHistoryYears: number; + annualMiles: number; + coverageLevel: CoverageLevel; + cleanRecord: boolean; + defensiveCourse: boolean; +} + +export interface FactorScore { + score: number; + max: number; + label: string; +} + +export interface FairScoreResult { + total: number; + factors: { + vehicle: FactorScore; + credential: FactorScore; + geographic: FactorScore; + mileage: FactorScore; + coverage: FactorScore; + }; +} + +// Bayesian confidence interval replacing the simple ±10% band +export interface ConfidenceInterval { + p10: number; + p25: number; + median: number; + p75: number; + p90: number; +} + +export interface PremiumResult { + monthly: number; + tradMonthly: number; + fairSavings: number; + range: [number, number]; + ci: ConfidenceInterval; + annualEstimate: number; +} + +export interface CoachRecommendation { + priority: number; + icon: string; + title: string; + detail: string; + action: string; + savings: number; +} + +export interface ScoreNarrative { + helps: string[]; + hurts: string[]; +} + +// ── Document Intelligence ────────────────────────────────────────────────── + +export interface CoverageLimits { + liability?: { bodily?: string; property?: string }; + collision?: { deductible?: number; active: boolean }; + comprehensive?: { deductible?: number; active: boolean }; + uninsuredMotorist?: { active: boolean }; + medical?: { limit?: number; active: boolean }; +} + +export interface StateMinimumsComparison { + liabilityAdequate: boolean; + uninsuredMotoristPresent: boolean; + gaps: string[]; +} + +export interface PolicyExtraction { + carrierName?: string; + effectiveDate?: string; + expirationDate?: string; + monthlyPremium?: number; + annualPremium?: number; + coverages: CoverageLimits; + stateMinimumsComparison?: StateMinimumsComparison; + confidenceScore: number; + extractionWarnings: string[]; +} + +// ── Insurance Passport ───────────────────────────────────────────────────── + +export interface PassportDocument { + type: DocumentType; + label: string; + description: string; + status: DocumentStatus; + uploadedAt?: string; + verifiedAt?: string; + fileName?: string; + extractedData?: PolicyExtraction; + consentGiven: boolean; + shareWithPartners: boolean; + required: boolean; +} + +export interface PassportState { + documents: PassportDocument[]; + shareToken?: string; + completionPct: number; +} + +// ── Vehicle Data ─────────────────────────────────────────────────────────── + +export interface VehicleData { + label: string; + msrp: number; + repairIndex: number; + theftIndex: number; + safetyRating: number; + isEV: boolean; + baseMonthly: number; + design: string; +} diff --git a/styles.css b/styles.css index dc309b6..c283db7 100644 --- a/styles.css +++ b/styles.css @@ -701,6 +701,360 @@ p { } } +/* ── FairScore Form ─────────────────────────────────────────────────────── */ + +.fs-form-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; + margin-bottom: 1rem; +} + +.fs-form-grid label, +.fairscore-form label { + display: grid; + gap: 0.45rem; + color: var(--muted); + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.fs-form-grid select, +.fairscore-form select { + width: 100%; + min-height: 2.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-2); + color: var(--text); + padding: 0 0.75rem; + font-size: 0.9rem; + text-transform: none; +} + +.fs-checks { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; +} + +.check-label { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--soft); + font-size: 0.88rem; + cursor: pointer; + text-transform: none !important; + letter-spacing: 0 !important; +} + +.check-label input[type="checkbox"] { + width: 1.1rem; + height: 1.1rem; + accent-color: var(--brand); +} + +/* ── FairScore Result ────────────────────────────────────────────────────── */ + +.fairscore-result { + margin-top: 1.5rem; + display: grid; + gap: 1.25rem; +} + +.score-display { + display: flex; + align-items: center; + gap: 1.5rem; +} + +.score-gauge { + position: relative; + width: 9rem; + flex: 0 0 9rem; +} + +.score-gauge svg { + width: 100%; + height: auto; + overflow: visible; +} + +.gauge-track { + fill: none; + stroke: rgba(255,255,255,0.08); + stroke-width: 10; + stroke-linecap: round; +} + +.gauge-fill { + fill: none; + stroke: var(--brand); + stroke-width: 10; + stroke-linecap: round; + stroke-dasharray: 0 172.8; + transition: stroke-dasharray 0.8s cubic-bezier(0.4, 0, 0.2, 1), stroke 0.8s; +} + +.score-number { + position: absolute; + bottom: 0.6rem; + left: 50%; + transform: translateX(-50%); + font-size: 2rem; + font-weight: 800; + line-height: 1; + color: var(--text); +} + +.score-label { + text-align: center; + margin-top: 0.25rem; + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); +} + +.score-meta { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.score-tier { + font-size: 1.35rem; + font-weight: 800; +} + +.score-tier.tier-green { color: var(--brand); } +.score-tier.tier-good { color: #7ee8c0; } +.score-tier.tier-fair { color: var(--brand-2); } +.score-tier.tier-warn { color: var(--warn); } +.score-tier.tier-danger { color: var(--danger); } + +.score-bench { + color: var(--muted); + font-size: 0.82rem; +} + +/* ── Factor Bars ─────────────────────────────────────────────────────────── */ + +.factor-bars { + display: grid; + gap: 0.65rem; +} + +.factor-bar-header { + display: flex; + justify-content: space-between; + margin-bottom: 0.3rem; + font-size: 0.82rem; + color: var(--soft); +} + +.factor-track { + height: 0.45rem; + border-radius: 999px; + background: rgba(255,255,255,0.08); + overflow: hidden; +} + +.factor-fill { + height: 100%; + border-radius: inherit; + background: var(--brand); + transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* ── Score Narrative ─────────────────────────────────────────────────────── */ + +.score-narrative { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; +} + +.narrative-group { + padding: 0.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.025); +} + +.narrative-label { + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 0.5rem !important; +} + +.narrative-group ul { + margin: 0; + padding-left: 1rem; + color: var(--soft); + font-size: 0.85rem; + line-height: 1.6; +} + +/* ── Premium Comparison ──────────────────────────────────────────────────── */ + +.premium-compare { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + margin-bottom: 0.75rem; +} + +.premium-block { + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0.85rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.03); +} + +.premium-block.fair { + border-color: rgba(41, 211, 162, 0.35); + background: rgba(41, 211, 162, 0.07); +} + +.pb-label { + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--muted); +} + +.pb-amount { + font-size: 1.8rem; + font-weight: 800; + color: var(--brand); + line-height: 1; +} + +.trad-amount { + color: var(--soft); +} + +.pb-sub { + font-size: 0.75rem; + color: var(--muted); +} + +.savings-banner { + display: grid; + gap: 0.2rem; + padding: 0.85rem; + border: 1px solid rgba(41, 211, 162, 0.4); + border-radius: var(--radius); + background: rgba(41, 211, 162, 0.12); + margin-bottom: 0.5rem; +} + +.savings-banner strong { + font-size: 1.5rem; + color: var(--brand); +} + +.savings-banner p { + font-size: 0.82rem; + color: var(--soft); +} + +/* ── AI Coach ────────────────────────────────────────────────────────────── */ + +.coach-item { + display: grid; + gap: 0.4rem; + padding: 1rem; + border: 1px solid var(--line); + border-radius: var(--radius); + background: rgba(255,255,255,0.04); + color: var(--text); + text-align: left; + transition: border-color 0.18s; +} + +.coach-item:hover { + border-color: rgba(41, 211, 162, 0.3); +} + +.coach-item-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} + +.coach-item-header strong { + line-height: 1.35; +} + +.savings-badge { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + padding: 0.2rem 0.55rem; + border: 1px solid rgba(41, 211, 162, 0.45); + border-radius: 999px; + background: rgba(41, 211, 162, 0.12); + color: var(--brand); + font-size: 0.78rem; + font-weight: 800; + white-space: nowrap; +} + +.coach-item > span:not(.savings-badge) { + color: var(--muted); + font-size: 0.88rem; + line-height: 1.5; +} + +.coach-action { + color: var(--brand-2) !important; + font-size: 0.82rem !important; + font-weight: 700; +} + +/* ── Responsive adjustments for new panels ───────────────────────────────── */ + +@media (max-width: 1050px) { + .fs-form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .score-narrative { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .fs-form-grid { + grid-template-columns: 1fr; + } + + .premium-compare { + grid-template-columns: 1fr; + } + + .score-display { + flex-direction: column; + align-items: flex-start; + } +} + +/* ── Marketing page ──────────────────────────────────────────────────────── */ + .marketing-body { background: radial-gradient(circle at 78% 10%, rgba(41, 211, 162, 0.18), transparent 26rem), diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..49e4cf3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { "@/*": ["./src/*"] } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..d1b27e1 --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/shared/lib/amp.d.ts","./node_modules/next/amp.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/future/route-kind.d.ts","./node_modules/next/dist/server/future/route-definitions/route-definition.d.ts","./node_modules/next/dist/server/future/route-matches/route-match.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/server/lib/revalidate.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/lib/page-types.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/future/helpers/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/lib/constants.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/server/font-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/server/future/route-modules/route-module.d.ts","./node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/server/future/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/future/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/next/dist/client/components/static-generation-async-storage-instance.d.ts","./node_modules/next/dist/client/components/static-generation-async-storage.external.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/client/components/request-async-storage-instance.d.ts","./node_modules/next/dist/client/components/request-async-storage.external.d.ts","./node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/amp-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/module.compiled.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/router-reducer/create-initial-router-state.d.ts","./node_modules/next/dist/client/components/app-router.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/client/components/action-async-storage-instance.d.ts","./node_modules/next/dist/client/components/action-async-storage.external.d.ts","./node_modules/next/dist/client/components/client-page.d.ts","./node_modules/next/dist/client/components/search-params.d.ts","./node_modules/next/dist/client/components/not-found-boundary.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/lib/builtin-request-context.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/future/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/future/route-modules/pages/module.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/server/future/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/future/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/future/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/future/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/future/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/future/normalizers/normalizer.d.ts","./node_modules/next/dist/server/future/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/future/normalizers/request/pathname-normalizer.d.ts","./node_modules/next/dist/server/future/normalizers/request/suffix.d.ts","./node_modules/next/dist/server/future/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/future/normalizers/request/prefix.d.ts","./node_modules/next/dist/server/future/normalizers/request/postponed.d.ts","./node_modules/next/dist/server/future/normalizers/request/action.d.ts","./node_modules/next/dist/server/future/normalizers/request/prefetch-rsc.d.ts","./node_modules/next/dist/server/future/normalizers/request/next-data.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/trace/types.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/build/webpack/plugins/define-env-plugin.d.ts","./node_modules/next/dist/build/swc/index.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/types/index.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/shared/lib/runtime-config.external.d.ts","./node_modules/next/config.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/client/components/draft-mode.d.ts","./node_modules/next/dist/client/components/headers.d.ts","./node_modules/next/headers.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./next-env.d.ts","./next.config.ts","./src/lib/types.ts","./node_modules/@anthropic-ai/sdk/_shims/manual-types.d.ts","./node_modules/@anthropic-ai/sdk/_shims/auto/types.d.ts","./node_modules/@anthropic-ai/sdk/_shims/index.d.ts","./node_modules/@anthropic-ai/sdk/streaming.d.ts","./node_modules/@anthropic-ai/sdk/_shims/MultipartBody.d.ts","./node_modules/@anthropic-ai/sdk/uploads.d.ts","./node_modules/@anthropic-ai/sdk/core.d.ts","./node_modules/@anthropic-ai/sdk/error.d.ts","./node_modules/@anthropic-ai/sdk/resource.d.ts","./node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts","./node_modules/@anthropic-ai/sdk/resources/messages.d.ts","./node_modules/@anthropic-ai/sdk/lib/PromptCachingBetaMessageStream.d.ts","./node_modules/@anthropic-ai/sdk/resources/beta/prompt-caching/messages.d.ts","./node_modules/@anthropic-ai/sdk/resources/beta/prompt-caching/prompt-caching.d.ts","./node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts","./node_modules/@anthropic-ai/sdk/resources/completions.d.ts","./node_modules/@anthropic-ai/sdk/resources/index.d.ts","./node_modules/@anthropic-ai/sdk/index.d.mts","./src/lib/state-minimums.ts","./src/lib/claude.ts","./src/app/api/analyze/route.ts","./src/app/api/explain/route.ts","./src/lib/ai-engine.ts","./src/app/api/fairscore/route.ts","./src/app/api/passport/route.ts","./src/app/layout.tsx","./src/app/page.tsx","./src/components/Sidebar.tsx","./src/components/TopBar.tsx","./node_modules/@types/three/src/constants.d.ts","./node_modules/@types/three/src/core/Layers.d.ts","./node_modules/@types/three/src/math/Vector2.d.ts","./node_modules/@types/three/src/math/Matrix3.d.ts","./node_modules/@types/three/src/core/BufferAttribute.d.ts","./node_modules/@types/three/src/core/InterleavedBuffer.d.ts","./node_modules/@types/three/src/core/InterleavedBufferAttribute.d.ts","./node_modules/@types/three/src/math/Quaternion.d.ts","./node_modules/@types/three/src/math/Euler.d.ts","./node_modules/@types/three/src/math/Matrix4.d.ts","./node_modules/@types/three/src/math/Vector4.d.ts","./node_modules/@types/three/src/cameras/Camera.d.ts","./node_modules/@types/three/src/math/ColorManagement.d.ts","./node_modules/@types/three/src/math/Color.d.ts","./node_modules/@types/three/src/math/Cylindrical.d.ts","./node_modules/@types/three/src/math/Spherical.d.ts","./node_modules/@types/three/src/math/Vector3.d.ts","./node_modules/@types/three/src/objects/Bone.d.ts","./node_modules/@types/three/src/math/Interpolant.d.ts","./node_modules/@types/three/src/math/interpolants/CubicInterpolant.d.ts","./node_modules/@types/three/src/math/interpolants/DiscreteInterpolant.d.ts","./node_modules/@types/three/src/math/interpolants/LinearInterpolant.d.ts","./node_modules/@types/three/src/animation/KeyframeTrack.d.ts","./node_modules/@types/three/src/animation/AnimationClip.d.ts","./node_modules/@types/three/src/extras/core/Curve.d.ts","./node_modules/@types/three/src/extras/core/CurvePath.d.ts","./node_modules/@types/three/src/extras/core/Path.d.ts","./node_modules/@types/three/src/extras/core/Shape.d.ts","./node_modules/@types/three/src/math/Line3.d.ts","./node_modules/@types/three/src/math/Sphere.d.ts","./node_modules/@types/three/src/math/Plane.d.ts","./node_modules/@types/three/src/math/Triangle.d.ts","./node_modules/@types/three/src/math/Box3.d.ts","./node_modules/@types/three/src/core/EventDispatcher.d.ts","./node_modules/@types/three/src/core/GLBufferAttribute.d.ts","./node_modules/@types/three/src/core/BufferGeometry.d.ts","./node_modules/@types/three/src/objects/Group.d.ts","./node_modules/@types/three/src/textures/CompressedTexture.d.ts","./node_modules/@types/three/src/textures/CubeTexture.d.ts","./node_modules/@types/three/src/textures/Source.d.ts","./node_modules/@types/three/src/textures/Texture.d.ts","./node_modules/@types/three/src/materials/LineBasicMaterial.d.ts","./node_modules/@types/three/src/materials/LineDashedMaterial.d.ts","./node_modules/@types/three/src/materials/MeshBasicMaterial.d.ts","./node_modules/@types/three/src/materials/MeshDepthMaterial.d.ts","./node_modules/@types/three/src/materials/MeshDistanceMaterial.d.ts","./node_modules/@types/three/src/materials/MeshLambertMaterial.d.ts","./node_modules/@types/three/src/materials/MeshMatcapMaterial.d.ts","./node_modules/@types/three/src/materials/MeshNormalMaterial.d.ts","./node_modules/@types/three/src/materials/MeshPhongMaterial.d.ts","./node_modules/@types/three/src/materials/MeshStandardMaterial.d.ts","./node_modules/@types/three/src/materials/MeshPhysicalMaterial.d.ts","./node_modules/@types/three/src/materials/MeshToonMaterial.d.ts","./node_modules/@types/three/src/materials/PointsMaterial.d.ts","./node_modules/@types/three/src/core/Uniform.d.ts","./node_modules/@types/three/src/core/UniformsGroup.d.ts","./node_modules/@types/three/src/renderers/shaders/UniformsLib.d.ts","./node_modules/@types/three/src/materials/ShaderMaterial.d.ts","./node_modules/@types/three/src/materials/RawShaderMaterial.d.ts","./node_modules/@types/three/src/materials/ShadowMaterial.d.ts","./node_modules/@types/three/src/materials/SpriteMaterial.d.ts","./node_modules/@types/three/src/materials/Materials.d.ts","./node_modules/@types/three/src/objects/Sprite.d.ts","./node_modules/@types/three/src/math/Frustum.d.ts","./node_modules/@types/three/src/textures/DepthTexture.d.ts","./node_modules/@types/three/src/core/RenderTarget.d.ts","./node_modules/@types/three/src/renderers/WebGLRenderTarget.d.ts","./node_modules/@types/three/src/lights/LightShadow.d.ts","./node_modules/@types/three/src/lights/Light.d.ts","./node_modules/@types/three/src/scenes/Fog.d.ts","./node_modules/@types/three/src/scenes/FogExp2.d.ts","./node_modules/@types/three/src/scenes/Scene.d.ts","./node_modules/@types/three/src/math/Box2.d.ts","./node_modules/@types/three/src/textures/types.d.ts","./node_modules/@types/three/src/textures/Data3DTexture.d.ts","./node_modules/@types/three/src/textures/DataArrayTexture.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLCapabilities.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLExtensions.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLProperties.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLState.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLUtils.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLTextures.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLUniforms.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLProgram.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLInfo.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLRenderLists.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLObjects.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLShadowMap.d.ts","./node_modules/@types/webxr/index.d.ts","./node_modules/@types/three/src/cameras/PerspectiveCamera.d.ts","./node_modules/@types/three/src/cameras/ArrayCamera.d.ts","./node_modules/@types/three/src/objects/Mesh.d.ts","./node_modules/@types/three/src/renderers/webxr/WebXRController.d.ts","./node_modules/@types/three/src/renderers/webxr/WebXRManager.d.ts","./node_modules/@types/three/src/renderers/WebGLRenderer.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLAttributes.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLBindingStates.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLClipping.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLCubeMaps.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLLights.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLPrograms.d.ts","./node_modules/@types/three/src/materials/Material.d.ts","./node_modules/@types/three/src/textures/DataTexture.d.ts","./node_modules/@types/three/src/objects/Skeleton.d.ts","./node_modules/@types/three/src/math/Ray.d.ts","./node_modules/@types/three/src/core/Raycaster.d.ts","./node_modules/@types/three/src/core/Object3D.d.ts","./node_modules/@types/three/src/animation/AnimationObjectGroup.d.ts","./node_modules/@types/three/src/animation/AnimationMixer.d.ts","./node_modules/@types/three/src/animation/AnimationAction.d.ts","./node_modules/@types/three/src/animation/AnimationUtils.d.ts","./node_modules/@types/three/src/animation/PropertyBinding.d.ts","./node_modules/@types/three/src/animation/PropertyMixer.d.ts","./node_modules/@types/three/src/animation/tracks/BooleanKeyframeTrack.d.ts","./node_modules/@types/three/src/animation/tracks/ColorKeyframeTrack.d.ts","./node_modules/@types/three/src/animation/tracks/NumberKeyframeTrack.d.ts","./node_modules/@types/three/src/animation/tracks/QuaternionKeyframeTrack.d.ts","./node_modules/@types/three/src/animation/tracks/StringKeyframeTrack.d.ts","./node_modules/@types/three/src/animation/tracks/VectorKeyframeTrack.d.ts","./node_modules/@types/three/src/audio/AudioContext.d.ts","./node_modules/@types/three/src/audio/AudioListener.d.ts","./node_modules/@types/three/src/audio/Audio.d.ts","./node_modules/@types/three/src/audio/AudioAnalyser.d.ts","./node_modules/@types/three/src/audio/PositionalAudio.d.ts","./node_modules/@types/three/src/renderers/WebGLCubeRenderTarget.d.ts","./node_modules/@types/three/src/cameras/CubeCamera.d.ts","./node_modules/@types/three/src/cameras/OrthographicCamera.d.ts","./node_modules/@types/three/src/cameras/StereoCamera.d.ts","./node_modules/@types/three/src/core/Clock.d.ts","./node_modules/@types/three/src/core/InstancedBufferAttribute.d.ts","./node_modules/@types/three/src/core/InstancedBufferGeometry.d.ts","./node_modules/@types/three/src/core/InstancedInterleavedBuffer.d.ts","./node_modules/@types/three/src/extras/core/ShapePath.d.ts","./node_modules/@types/three/src/extras/curves/EllipseCurve.d.ts","./node_modules/@types/three/src/extras/curves/ArcCurve.d.ts","./node_modules/@types/three/src/extras/curves/CatmullRomCurve3.d.ts","./node_modules/@types/three/src/extras/curves/CubicBezierCurve.d.ts","./node_modules/@types/three/src/extras/curves/CubicBezierCurve3.d.ts","./node_modules/@types/three/src/extras/curves/LineCurve.d.ts","./node_modules/@types/three/src/extras/curves/LineCurve3.d.ts","./node_modules/@types/three/src/extras/curves/QuadraticBezierCurve.d.ts","./node_modules/@types/three/src/extras/curves/QuadraticBezierCurve3.d.ts","./node_modules/@types/three/src/extras/curves/SplineCurve.d.ts","./node_modules/@types/three/src/extras/curves/Curves.d.ts","./node_modules/@types/three/src/extras/DataUtils.d.ts","./node_modules/@types/three/src/extras/ImageUtils.d.ts","./node_modules/@types/three/src/extras/PMREMGenerator.d.ts","./node_modules/@types/three/src/extras/ShapeUtils.d.ts","./node_modules/@types/three/src/extras/TextureUtils.d.ts","./node_modules/@types/three/src/geometries/BoxGeometry.d.ts","./node_modules/@types/three/src/geometries/CapsuleGeometry.d.ts","./node_modules/@types/three/src/geometries/CircleGeometry.d.ts","./node_modules/@types/three/src/geometries/CylinderGeometry.d.ts","./node_modules/@types/three/src/geometries/ConeGeometry.d.ts","./node_modules/@types/three/src/geometries/PolyhedronGeometry.d.ts","./node_modules/@types/three/src/geometries/DodecahedronGeometry.d.ts","./node_modules/@types/three/src/geometries/EdgesGeometry.d.ts","./node_modules/@types/three/src/geometries/ExtrudeGeometry.d.ts","./node_modules/@types/three/src/geometries/IcosahedronGeometry.d.ts","./node_modules/@types/three/src/geometries/LatheGeometry.d.ts","./node_modules/@types/three/src/geometries/OctahedronGeometry.d.ts","./node_modules/@types/three/src/geometries/PlaneGeometry.d.ts","./node_modules/@types/three/src/geometries/RingGeometry.d.ts","./node_modules/@types/three/src/geometries/ShapeGeometry.d.ts","./node_modules/@types/three/src/geometries/SphereGeometry.d.ts","./node_modules/@types/three/src/geometries/TetrahedronGeometry.d.ts","./node_modules/@types/three/src/geometries/TorusGeometry.d.ts","./node_modules/@types/three/src/geometries/TorusKnotGeometry.d.ts","./node_modules/@types/three/src/geometries/TubeGeometry.d.ts","./node_modules/@types/three/src/geometries/WireframeGeometry.d.ts","./node_modules/@types/three/src/geometries/Geometries.d.ts","./node_modules/@types/three/src/objects/Line.d.ts","./node_modules/@types/three/src/helpers/ArrowHelper.d.ts","./node_modules/@types/three/src/objects/LineSegments.d.ts","./node_modules/@types/three/src/helpers/AxesHelper.d.ts","./node_modules/@types/three/src/helpers/Box3Helper.d.ts","./node_modules/@types/three/src/helpers/BoxHelper.d.ts","./node_modules/@types/three/src/helpers/CameraHelper.d.ts","./node_modules/@types/three/src/lights/DirectionalLightShadow.d.ts","./node_modules/@types/three/src/lights/DirectionalLight.d.ts","./node_modules/@types/three/src/helpers/DirectionalLightHelper.d.ts","./node_modules/@types/three/src/helpers/GridHelper.d.ts","./node_modules/@types/three/src/lights/HemisphereLight.d.ts","./node_modules/@types/three/src/helpers/HemisphereLightHelper.d.ts","./node_modules/@types/three/src/helpers/PlaneHelper.d.ts","./node_modules/@types/three/src/lights/PointLightShadow.d.ts","./node_modules/@types/three/src/lights/PointLight.d.ts","./node_modules/@types/three/src/helpers/PointLightHelper.d.ts","./node_modules/@types/three/src/helpers/PolarGridHelper.d.ts","./node_modules/@types/three/src/objects/SkinnedMesh.d.ts","./node_modules/@types/three/src/helpers/SkeletonHelper.d.ts","./node_modules/@types/three/src/helpers/SpotLightHelper.d.ts","./node_modules/@types/three/src/lights/AmbientLight.d.ts","./node_modules/@types/three/src/math/SphericalHarmonics3.d.ts","./node_modules/@types/three/src/lights/LightProbe.d.ts","./node_modules/@types/three/src/lights/RectAreaLight.d.ts","./node_modules/@types/three/src/lights/SpotLightShadow.d.ts","./node_modules/@types/three/src/lights/SpotLight.d.ts","./node_modules/@types/three/src/loaders/LoadingManager.d.ts","./node_modules/@types/three/src/loaders/Loader.d.ts","./node_modules/@types/three/src/loaders/AnimationLoader.d.ts","./node_modules/@types/three/src/loaders/AudioLoader.d.ts","./node_modules/@types/three/src/loaders/BufferGeometryLoader.d.ts","./node_modules/@types/three/src/loaders/Cache.d.ts","./node_modules/@types/three/src/loaders/CompressedTextureLoader.d.ts","./node_modules/@types/three/src/loaders/CubeTextureLoader.d.ts","./node_modules/@types/three/src/loaders/DataTextureLoader.d.ts","./node_modules/@types/three/src/loaders/FileLoader.d.ts","./node_modules/@types/three/src/loaders/ImageBitmapLoader.d.ts","./node_modules/@types/three/src/loaders/ImageLoader.d.ts","./node_modules/@types/three/src/loaders/LoaderUtils.d.ts","./node_modules/@types/three/src/loaders/MaterialLoader.d.ts","./node_modules/@types/three/src/loaders/ObjectLoader.d.ts","./node_modules/@types/three/src/loaders/TextureLoader.d.ts","./node_modules/@types/three/src/math/interpolants/QuaternionLinearInterpolant.d.ts","./node_modules/@types/three/src/math/MathUtils.d.ts","./node_modules/@types/three/src/math/Matrix2.d.ts","./node_modules/@types/three/src/objects/BatchedMesh.d.ts","./node_modules/@types/three/src/objects/InstancedMesh.d.ts","./node_modules/@types/three/src/objects/LineLoop.d.ts","./node_modules/@types/three/src/objects/LOD.d.ts","./node_modules/@types/three/src/objects/Points.d.ts","./node_modules/@types/three/src/renderers/shaders/ShaderChunk.d.ts","./node_modules/@types/three/src/renderers/shaders/ShaderLib.d.ts","./node_modules/@types/three/src/renderers/shaders/UniformsUtils.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLBufferRenderer.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLCubeUVMaps.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLGeometries.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLIndexedBufferRenderer.d.ts","./node_modules/@types/three/src/renderers/webgl/WebGLShader.d.ts","./node_modules/@types/three/src/renderers/WebGL3DRenderTarget.d.ts","./node_modules/@types/three/src/renderers/WebGLArrayRenderTarget.d.ts","./node_modules/@types/three/src/renderers/webxr/WebXRDepthSensing.d.ts","./node_modules/@types/three/src/textures/CanvasTexture.d.ts","./node_modules/@types/three/src/textures/CompressedArrayTexture.d.ts","./node_modules/@types/three/src/textures/CompressedCubeTexture.d.ts","./node_modules/@types/three/src/textures/FramebufferTexture.d.ts","./node_modules/@types/three/src/textures/VideoTexture.d.ts","./node_modules/@types/three/src/Three.Legacy.d.ts","./node_modules/@types/three/src/utils.d.ts","./node_modules/@types/three/src/Three.d.ts","./node_modules/@types/three/build/three.module.d.ts","./node_modules/@types/three/examples/jsm/controls/OrbitControls.d.ts","./src/components/VehicleViewer.tsx","./src/components/HeroPanel.tsx","./src/components/ui/StatusPill.tsx","./src/components/views/GarageView.tsx","./src/components/ui/ScoreGauge.tsx","./src/components/views/FairScoreView.tsx","./src/components/views/QuoteView.tsx","./src/components/views/PassportView.tsx","./src/components/views/EstimatorView.tsx","./src/components/views/CoachView.tsx","./src/components/AppShell.tsx","./src/app/dashboard/page.tsx","./.next/types/app/layout.ts","./.next/types/app/page.ts","./.next/types/app/api/analyze/route.ts","./.next/types/app/api/explain/route.ts","./.next/types/app/api/fairscore/route.ts","./.next/types/app/api/passport/route.ts","./.next/types/app/dashboard/page.ts","./node_modules/form-data/index.d.ts","./node_modules/@types/node-fetch/externals.d.ts","./node_modules/@types/node-fetch/index.d.ts","./node_modules/@types/stats.js/index.d.ts","./node_modules/@types/three/index.d.ts"],"fileIdsList":[[99,147,164,165,409,437],[99,147,164,165,409,438],[99,147,164,165,409,440],[99,147,164,165,409,441],[99,147,164,165,364,700],[99,147,164,165,364,442],[99,147,164,165,364,443],[99,147,164,165,412,413],[99,147,164,165,412],[99,147,164,165],[99,147,164,165,417,418,423],[99,147,164,165,419,420,422,424],[99,147,164,165,423],[99,147,164,165,419,422,423,424,433],[99,147,164,165,419,423,424,427],[99,147,164,165,419,423,424,427,429],[99,147,164,165,425,430],[99,147,164,165,420,423,425,427,428,429],[99,147,164,165,425,429],[99,147,164,165,420,423,425,427,432],[99,147,164,165,427,431,432],[99,147,164,165,420,423,425,426,427],[99,147,164,165,419],[99,147,164,165,419,421,423],[99,147,161,164,165,190,197,708,709],[99,144,145,147,164,165],[99,146,147,164,165],[147,164,165],[99,147,152,164,165,182],[99,147,148,153,158,164,165,167,179,190],[99,147,148,149,158,164,165,167],[94,95,96,99,147,164,165],[99,147,150,164,165,191],[99,147,151,152,159,164,165,168],[99,147,152,164,165,179,187],[99,147,153,155,158,164,165,167],[99,146,147,154,164,165],[99,147,155,156,164,165],[99,147,157,158,164,165],[99,146,147,158,164,165],[99,147,158,159,160,164,165,179,190],[99,147,158,159,160,164,165,174,179,182],[99,140,147,155,158,161,164,165,167,179,190],[99,147,158,159,161,162,164,165,167,179,187,190],[99,147,161,163,164,165,179,187,190],[97,98,99,100,101,102,103,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[99,147,158,164,165],[99,147,164,165,166,190],[99,147,155,158,164,165,167,179],[99,147,164,165,168],[99,147,164,165,169],[99,146,147,164,165,170],[99,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[99,147,164,165,172],[99,147,164,165,173],[99,147,158,164,165,174,175],[99,147,164,165,174,176,191,193],[99,147,159,164,165],[99,147,158,164,165,179,180,182],[99,147,164,165,181,182],[99,147,164,165,179,180],[99,147,164,165,182],[99,147,164,165,183],[99,144,147,164,165,179,184,190],[99,147,158,164,165,185,186],[99,147,164,165,185,186],[99,147,152,164,165,167,179,187],[99,147,164,165,188],[99,147,164,165,167,189],[99,147,161,164,165,173,190],[99,147,152,164,165,191],[99,147,164,165,179,192],[99,147,164,165,166,193],[99,147,164,165,194],[99,140,147,164,165],[99,140,147,158,160,164,165,170,179,182,190,192,193,195],[99,147,164,165,179,196],[87,99,147,164,165,201,202,203],[87,99,147,164,165,201,202],[87,99,147,164,165],[87,91,99,147,164,165,200,365,408],[87,91,99,147,164,165,199,365,408],[84,85,86,99,147,164,165],[99,147,164,165,686],[99,147,164,165,687],[99,147,164,165,486,511,512],[99,147,164,165,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,500,501,502,507,508,509,510,511,512,513,514,515,516,517,518,520,521,522,523,524,525,526,527,528,529,530,531,532,533,535,536,537,538,539,540,541,542,543,544,545,546,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,589,590,591,592,593,594,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685],[99,147,164,165,446,469,552,554],[99,147,164,165,446,462,463,468],[99,147,164,165,446,469,479,552,553,555],[99,147,164,165,469],[99,147,164,165,446,464,465,466,467],[99,147,164,165,468],[99,147,164,165,446,468],[99,147,164,165,552,565,566],[99,147,164,165,567],[99,147,164,165,552,565],[99,147,164,165,566,567],[99,147,164,165,535],[99,147,164,165,446,447,455,456,462,552],[99,147,164,165,446,540,552,570],[99,147,164,165,457,552],[99,147,164,165,448,457,552],[99,147,164,165,457,535],[99,147,164,165,446,449,455],[99,147,164,165,448,450,452,453,455,462,475,478,479,480],[99,147,164,165,450],[99,147,164,165,481],[99,147,164,165,450,451],[99,147,164,165,446,450,452],[99,147,164,165,449,450,451,455],[99,147,164,165,447,449,453,454,455,457,462,469,473,479,481,482,485,486,517,540,547,549,551],[99,147,164,165,447,448,457,462,538,550,552],[99,147,164,165,446,456,479,486,510],[99,147,164,165,446,479,500],[99,147,164,165,484,486,512,517,540],[99,147,164,165,448],[99,147,164,165,446,486],[99,147,164,165,448,462],[99,147,164,165,448,462,470],[99,147,164,165,448,471],[99,147,164,165,448,472],[99,147,164,165,448,459,472,473],[99,147,164,165,579],[99,147,164,165,462,470],[99,147,164,165,448,470],[99,147,164,165,579,580,581,582,583,584,585,586,587,588],[99,147,164,165,598],[99,147,164,165,600],[99,147,164,165,448,462,470,473,481],[99,147,164,165,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615],[99,147,164,165,448,481],[99,147,164,165,473,481],[99,147,164,165,462,470,481],[99,147,164,165,459,462,537,552,617],[99,147,164,165,459,619],[99,147,164,165,459,478,619],[99,147,164,165,459,481,487,552,619],[99,147,164,165,455,457,459,619],[99,147,164,165,455,459,552,617,625],[99,147,164,165,459,481,487,619],[99,147,164,165,455,459,489,552,628],[99,147,164,165,476,619],[99,147,164,165,455,459,552,632],[99,147,164,165,455,463,552,619,635],[99,147,164,165,455,459,514,552,619],[99,147,164,165,459,514],[99,147,164,165,459,462,514,552,624],[99,147,164,165,513,572],[99,147,164,165,459,462,514],[99,147,164,165,459,513,552],[99,147,164,165,514,639],[99,147,164,165,448,455,456,457,509,512,514,552],[99,147,164,165,459,514,631],[99,147,164,165,513,514,535],[99,147,164,165,459,462,486,514,552,642],[99,147,164,165,513,535],[99,147,164,165,469,644,645],[99,147,164,165,644,645],[99,147,164,165,481,576,644,645],[99,147,164,165,483,644,645],[99,147,164,165,484,644,645],[99,147,164,165,548,644,645],[99,147,164,165,644],[99,147,164,165,645],[99,147,164,165,486,547,644,645],[99,147,164,165,469,481,485,486,547,552,576,644,645],[99,147,164,165,486,644,645],[99,147,164,165,459,486,547],[99,147,164,165,487],[99,147,164,165,446,457,459,476,479,481,482,517,540,546,552,686],[99,147,164,165,487,488,489,490,491,492,493,494,495,496,497,498,499,503,504,505,506,547],[99,147,164,165,446,454,459,486,547],[99,147,164,165,446,486,547],[99,147,164,165,462,486,547],[99,147,164,165,446,448,454,459,486,547],[99,147,164,165,446,448,459,486,547],[99,147,164,165,446,448,486,547],[99,147,164,165,448,459,486,496],[99,147,164,165,503],[99,147,164,165,446,448,449,455,456,462,501,502,547,552],[99,147,164,165,459,547],[99,147,164,165,450,455,462,475,476,477,552],[99,147,164,165,446,449,450,452,458,462],[99,147,164,165,446,459,462],[99,147,164,165,462],[99,147,164,165,453,455,462],[99,147,164,165,446,455,462,475,476,478,508,552],[99,147,164,165,455,462],[99,147,164,165,453],[99,147,164,165,448,455,462],[99,147,164,165,446,449,453,454,462],[99,147,164,165,449,455,462,474,475,478],[99,147,164,165,450,452,454,455,462],[99,147,164,165,455,462,475,476,478],[99,147,164,165,455,462,476,478],[99,147,164,165,448,450,452,456,462,476,478],[99,147,164,165,449,450],[99,147,164,165,449,450,452,453,454,455,457,459,460,461],[99,147,164,165,450,453,455],[99,147,164,165,464],[99,147,164,165,455,457,459,475,478,481,537,547],[99,147,164,165,552],[99,147,164,165,450,455,459,475,478,481,537,547,548,552,575],[99,147,164,165,481,547,552],[99,147,164,165,481,547,552,617],[99,147,164,165,462,481,547,552],[99,147,164,165,455,463,548],[99,147,164,165,446,455,462,475,478,481,537,547,549,552],[99,147,164,165,448,481,507,552],[99,147,164,165,511,512,520],[99,147,164,165,511,512,521],[99,147,164,165,484,486,511,512,540],[99,147,164,165,486,511],[99,147,164,165,446,448,450,456,457,459,462,476,478,481,486,512,517,518,520,521,522,523,524,525,529,530,531,533,539,547,552],[99,147,164,165,502],[99,147,164,165,448,449,459],[99,147,164,165,501,502],[99,147,164,165,450,452,480],[99,147,164,165,450,481,529,541,547,552],[99,147,164,165,523,530],[99,147,164,165,446],[99,147,164,165,457,476,524,547],[99,147,164,165,540],[99,147,164,165,486,540],[99,147,164,165,450,481,530,541,552],[99,147,164,165,529],[99,147,164,165,523],[99,147,164,165,528,540],[99,147,164,165,446,502,514,517,522,523,529,540,542,543,544,545,547,552],[99,147,164,165,457,481,482,517,524,529,547,552],[99,147,164,165,446,457,514,517,522,532,540],[99,147,164,165,446,456,512,547],[99,147,164,165,522,523,524,525,526,530],[99,147,164,165,527,529],[99,147,164,165,446,523],[99,147,164,165,462,482,552],[99,147,164,165,486,537,539,540],[99,147,164,165,456,479,486,534,535,536,537,538,540],[99,147,164,165,459],[99,147,164,165,454,459,484,486,515,516,547,552],[99,147,164,165,446,483],[99,147,164,165,446,450,486],[99,147,164,165,446,486,519],[99,147,164,165,446,448,449,479,483,484,485],[99,147,161,164,165,179,197],[92,99,147,164,165],[99,147,164,165,369],[99,147,164,165,371,372,373],[99,147,164,165,375],[99,147,164,165,206,216,222,224,365],[99,147,164,165,206,213,215,218,236],[99,147,164,165,216],[99,147,164,165,216,218,343],[99,147,164,165,271,289,304,411],[99,147,164,165,313],[99,147,164,165,206,216,223,257,267,340,341,411],[99,147,164,165,223,411],[99,147,164,165,216,267,268,269,411],[99,147,164,165,216,223,257,411],[99,147,164,165,411],[99,147,164,165,206,223,224,411],[99,147,164,165,297],[99,146,147,164,165,197,296],[87,99,147,164,165,290,291,292,310,311],[87,99,147,164,165,290],[99,147,164,165,280],[99,147,164,165,279,281,385],[87,99,147,164,165,290,291,308],[99,147,164,165,286,311,397],[99,147,164,165,395,396],[99,147,164,165,230,394],[99,147,164,165,283],[99,146,147,164,165,197,230,246,279,280,281,282],[87,99,147,164,165,308,310,311],[99,147,164,165,308,310],[99,147,164,165,308,309,311],[99,147,164,165,173,197],[99,147,164,165,278],[99,146,147,164,165,197,215,217,274,275,276,277],[87,99,147,164,165,207,388],[87,99,147,164,165,190,197],[87,99,147,164,165,223,255],[87,99,147,164,165,223],[99,147,164,165,253,258],[87,99,147,164,165,254,368],[87,91,99,147,161,164,165,197,199,200,365,406,407],[99,147,164,165,365],[99,147,164,165,205],[99,147,164,165,358,359,360,361,362,363],[99,147,164,165,360],[87,99,147,164,165,254,290,368],[87,99,147,164,165,290,366,368],[87,99,147,164,165,290,368],[99,147,161,164,165,197,217,368],[99,147,161,164,165,197,214,215,226,244,246,278,283,284,306,308],[99,147,164,165,275,278,283,291,293,294,295,297,298,299,300,301,302,303,411],[99,147,164,165,276],[87,99,147,164,165,173,197,215,216,244,246,247,249,274,306,307,311,365,411],[99,147,161,164,165,197,217,218,230,231,279],[99,147,161,164,165,197,216,218],[99,147,161,164,165,179,197,214,217,218],[99,147,161,164,165,173,190,197,214,215,216,217,218,223,226,227,237,238,240,243,244,246,247,248,249,273,274,307,308,316,318,321,323,326,328,329,330,331],[99,147,164,165,206,207,208,214,215,365,368,411],[99,147,161,164,165,179,190,197,211,342,344,345,411],[99,147,164,165,173,190,197,211,214,217,234,238,240,241,242,247,274,321,332,334,340,354,355],[99,147,164,165,216,220,274],[99,147,164,165,214,216],[99,147,164,165,227,322],[99,147,164,165,324,325],[99,147,164,165,324],[99,147,164,165,322],[99,147,164,165,324,327],[99,147,164,165,210,211],[99,147,164,165,210,250],[99,147,164,165,210],[99,147,164,165,212,227,320],[99,147,164,165,319],[99,147,164,165,211,212],[99,147,164,165,212,317],[99,147,164,165,211],[99,147,164,165,306],[99,147,161,164,165,197,214,226,245,265,271,285,288,305,308],[99,147,164,165,259,260,261,262,263,264,286,287,311,366],[99,147,164,165,315],[99,147,161,164,165,197,214,226,245,251,312,314,316,365,368],[99,147,161,164,165,190,197,207,214,216,273],[99,147,164,165,270],[99,147,161,164,165,197,348,353],[99,147,164,165,237,246,273,368],[99,147,164,165,336,340,354,357],[99,147,161,164,165,220,340,348,349,357],[99,147,164,165,206,216,237,248,351],[99,147,161,164,165,197,216,223,248,335,336,346,347,350,352],[99,147,164,165,198,244,245,246,365,368],[99,147,161,164,165,173,190,197,212,214,215,217,220,225,226,234,237,238,240,241,242,243,247,249,273,274,318,332,333,368],[99,147,161,164,165,197,214,216,220,334,356],[99,147,161,164,165,197,215,217],[87,99,147,161,164,165,173,197,205,207,214,215,218,226,243,244,246,247,249,315,365,368],[99,147,161,164,165,173,190,197,209,212,213,217],[99,147,164,165,210,272],[99,147,161,164,165,197,210,215,226],[99,147,161,164,165,197,216,227],[99,147,161,164,165,197],[99,147,164,165,230],[99,147,164,165,229],[99,147,164,165,231],[99,147,164,165,216,228,230,234],[99,147,164,165,216,228,230],[99,147,161,164,165,197,209,216,217,223,231,232,233],[87,99,147,164,165,308,309,310],[99,147,164,165,266],[87,99,147,164,165,207],[87,99,147,164,165,240],[87,99,147,164,165,198,243,246,249,365,368],[99,147,164,165,207,388,389],[87,99,147,164,165,258],[87,99,147,164,165,173,190,197,205,252,254,256,257,368],[99,147,164,165,217,223,240],[99,147,164,165,239],[87,99,147,159,161,164,165,173,197,205,258,267,365,366,367],[83,87,88,89,90,99,147,164,165,199,200,365,408],[99,147,152,164,165],[99,147,164,165,337,338,339],[99,147,164,165,337],[99,147,164,165,377],[99,147,164,165,379],[99,147,164,165,381],[99,147,164,165,383],[99,147,164,165,386],[99,147,164,165,390],[91,93,99,147,164,165,365,370,374,376,378,380,382,384,387,391,393,399,400,402,409,410,411],[99,147,164,165,392],[99,147,164,165,398],[99,147,164,165,254],[99,147,164,165,401],[99,146,147,164,165,231,232,233,234,403,404,405,408],[99,147,164,165,197],[87,91,99,147,161,163,164,165,173,197,199,200,201,203,205,218,357,364,368,408],[99,112,116,147,164,165,190],[99,112,147,164,165,179,190],[99,107,147,164,165],[99,109,112,147,164,165,187,190],[99,147,164,165,167,187],[99,107,147,164,165,197],[99,109,112,147,164,165,167,190],[99,104,105,108,111,147,158,164,165,179,190],[99,112,119,147,164,165],[99,104,110,147,164,165],[99,112,133,134,147,164,165],[99,108,112,147,164,165,182,190,197],[99,133,147,164,165,197],[99,106,107,147,164,165,197],[99,112,147,164,165],[99,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,134,135,136,137,138,139,147,164,165],[99,112,127,147,164,165],[99,112,119,120,147,164,165],[99,110,112,120,121,147,164,165],[99,111,147,164,165],[99,104,107,112,147,164,165],[99,112,116,120,121,147,164,165],[99,116,147,164,165],[99,110,112,115,147,164,165,190],[99,104,109,112,119,147,164,165],[99,147,164,165,179],[99,107,112,133,147,164,165,195,197],[99,147,164,165,409,416,436],[99,147,164,165,409,416,439],[99,147,164,165,409,416],[99,147,164,165,699],[99,147,164,165,393],[87,99,147,164,165,416,444,445,690,692,694,695,696,697,698],[87,99,147,164,165,380,416,439,689],[99,147,164,165,416],[87,99,147,164,165,416,687,688],[87,99,147,164,165,416,439],[87,99,147,164,165,416,439,693],[99,147,164,165,416,439,691],[87,99,147,164,165,416],[99,147,164,165,416,429,434,435]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0990a7576222f248f0a3b888adcb7389f957928ce2afb1cd5128169086ff4d29","impliedFormat":1},{"version":"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"035312d4945d13efa134ae482f6dc56a1a9346f7ac3be7ccbad5741058ce87f3","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc69795d9954ee4ad57545b10c7bf1a7260d990231b1685c147ea71a6faa265c","impliedFormat":1},{"version":"8bc6c94ff4f2af1f4023b7bb2379b08d3d7dd80c698c9f0b07431ea16101f05f","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"57194e1f007f3f2cbef26fa299d4c6b21f4623a2eddc63dfeef79e38e187a36e","impliedFormat":1},{"version":"0f6666b58e9276ac3a38fdc80993d19208442d6027ab885580d93aec76b4ef00","impliedFormat":1},{"version":"05fd364b8ef02fb1e174fbac8b825bdb1e5a36a016997c8e421f5fab0a6da0a0","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"73b5fa37db36eeac90c4d752e39586f1b57187400c4f5280fd05f16437287a45","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"8caa5c86be1b793cd5f599e27ecb34252c41e011980f7d61ae4989a149ff6ccc","impliedFormat":1},{"version":"f9fd93190acb1ffe0bc0fb395df979452f8d625071e9ffc8636e4dfb86ab2508","impliedFormat":1},{"version":"5f41fd8732a89e940c58ce22206e3df85745feb8983e2b4c6257fb8cbb118493","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b","impliedFormat":1},{"version":"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"cfe4ef4710c3786b6e23dae7c086c70b4f4835a2e4d77b75d39f9046106e83d3","impliedFormat":1},{"version":"cbea99888785d49bb630dcbb1613c73727f2b5a2cf02e1abcaab7bcf8d6bf3c5","impliedFormat":1},{"version":"3a8bddb66b659f6bd2ff641fc71df8a8165bafe0f4b799cc298be5cd3755bb20","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"2dad084c67e649f0f354739ec7df7c7df0779a28a4f55c97c6b6883ae850d1ce","impliedFormat":1},{"version":"fa5bbc7ab4130dd8cdc55ea294ec39f76f2bc507a0f75f4f873e38631a836ca7","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"cf86de1054b843e484a3c9300d62fbc8c97e77f168bbffb131d560ca0474d4a8","impliedFormat":1},{"version":"196c960b12253fde69b204aa4fbf69470b26daf7a430855d7f94107a16495ab0","impliedFormat":1},{"version":"ee15ea5dd7a9fc9f5013832e5843031817a880bf0f24f37a29fd8337981aae07","impliedFormat":1},{"version":"bf24f6d35f7318e246010ffe9924395893c4e96d34324cde77151a73f078b9ad","impliedFormat":1},{"version":"ea53732769832d0f127ae16620bd5345991d26bf0b74e85e41b61b27d74ea90f","impliedFormat":1},{"version":"10595c7ff5094dd5b6a959ccb1c00e6a06441b4e10a87bc09c15f23755d34439","impliedFormat":1},{"version":"9620c1ff645afb4a9ab4044c85c26676f0a93e8c0e4b593aea03a89ccb47b6d0","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"a9af0e608929aaf9ce96bd7a7b99c9360636c31d73670e4af09a09950df97841","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"08ed0b3f0166787f84a6606f80aa3b1388c7518d78912571b203817406e471da","impliedFormat":1},{"version":"47e5af2a841356a961f815e7c55d72554db0c11b4cba4d0caab91f8717846a94","impliedFormat":1},{"version":"65f43099ded6073336e697512d9b80f2d4fec3182b7b2316abf712e84104db00","impliedFormat":1},{"version":"f5f541902bf7ae0512a177295de9b6bcd6809ea38307a2c0a18bfca72212f368","impliedFormat":1},{"version":"b0decf4b6da3ebc52ea0c96095bdfaa8503acc4ac8e9081c5f2b0824835dd3bd","impliedFormat":1},{"version":"ca1b882a105a1972f82cc58e3be491e7d750a1eb074ffd13b198269f57ed9e1b","impliedFormat":1},{"version":"fc3e1c87b39e5ba1142f27ec089d1966da168c04a859a4f6aab64dceae162c2b","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"61888522cec948102eba94d831c873200aa97d00d8989fdfd2a3e0ee75ec65a2","impliedFormat":1},{"version":"4e10622f89fea7b05dd9b52fb65e1e2b5cbd96d4cca3d9e1a60bb7f8a9cb86a1","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"59bf32919de37809e101acffc120596a9e45fdbab1a99de5087f31fdc36e2f11","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"faa03dffb64286e8304a2ca96dd1317a77db6bfc7b3fb385163648f67e535d77","impliedFormat":1},{"version":"c40c848daad198266370c1c72a7a8c3d18d2f50727c7859fcfefd3ff69a7f288","impliedFormat":1},{"version":"ac60bbee0d4235643cc52b57768b22de8c257c12bd8c2039860540cab1fa1d82","impliedFormat":1},{"version":"6428e6edd944ce6789afdf43f9376c1f2e4957eea34166177625aaff4c0da1a0","impliedFormat":1},{"version":"ada39cbb2748ab2873b7835c90c8d4620723aedf323550e8489f08220e477c7f","impliedFormat":1},{"version":"6e5f5cee603d67ee1ba6120815497909b73399842254fc1e77a0d5cdc51d8c9c","impliedFormat":1},{"version":"8dba67056cbb27628e9b9a1cba8e57036d359dceded0725c72a3abe4b6c79cd4","impliedFormat":1},{"version":"70f3814c457f54a7efe2d9ce9d2686de9250bb42eb7f4c539bd2280a42e52d33","impliedFormat":1},{"version":"154dd2e22e1e94d5bc4ff7726706bc0483760bae40506bdce780734f11f7ec47","impliedFormat":1},{"version":"ef61792acbfa8c27c9bd113f02731e66229f7d3a169e3c1993b508134f1a58e0","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"0131e203d8560edb39678abe10db42564a068f98c4ebd1ed9ffe7279c78b3c81","impliedFormat":1},{"version":"f6404e7837b96da3ea4d38c4f1a3812c96c9dcdf264e93d5bdb199f983a3ef4b","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"8b8f00491431fe82f060dfe8c7f2180a9fb239f3d851527db909b83230e75882","affectsGlobalScope":true,"impliedFormat":1},{"version":"db01d18853469bcb5601b9fc9826931cc84cc1a1944b33cad76fd6f1e3d8c544","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"903e299a28282fa7b714586e28409ed73c3b63f5365519776bf78e8cf173db36","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"dd3900b24a6a8745efeb7ad27629c0f8a626470ac229c1d73f1fe29d67e44dca","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"ec29be0737d39268696edcec4f5e97ce26f449fa9b7afc2f0f99a86def34a418","impliedFormat":1},{"version":"aeab39e8e0b1a3b250434c3b2bb8f4d17bbec2a9dbce5f77e8a83569d3d2cbc2","impliedFormat":1},{"version":"ec6cba1c02c675e4dd173251b156792e8d3b0c816af6d6ad93f1a55d674591aa","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"d729408dfde75b451530bcae944cf89ee8277e2a9df04d1f62f2abfd8b03c1e1","impliedFormat":1},{"version":"e15d3c84d5077bb4a3adee4c791022967b764dc41cb8fa3cfa44d4379b2c95f5","impliedFormat":1},{"version":"5f58e28cd22e8fc1ac1b3bc6b431869f1e7d0b39e2c21fbf79b9fa5195a85980","impliedFormat":1},{"version":"e1fc1a1045db5aa09366be2b330e4ce391550041fc3e925f60998ca0b647aa97","impliedFormat":1},{"version":"63533978dcda286422670f6e184ac516805a365fb37a086eeff4309e812f1402","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"31fb49ef3aa3d76f0beb644984e01eab0ea222372ea9b49bb6533be5722d756c","impliedFormat":1},{"version":"33cd131e1461157e3e06b06916b5176e7a8ec3fce15a5cfe145e56de744e07d2","impliedFormat":1},{"version":"889ef863f90f4917221703781d9723278db4122d75596b01c429f7c363562b86","impliedFormat":1},{"version":"3556cfbab7b43da96d15a442ddbb970e1f2fc97876d055b6555d86d7ac57dae5","impliedFormat":1},{"version":"437751e0352c6e924ddf30e90849f1d9eb00ca78c94d58d6a37202ec84eb8393","impliedFormat":1},{"version":"48e8af7fdb2677a44522fd185d8c87deff4d36ee701ea003c6c780b1407a1397","impliedFormat":1},{"version":"d11308de5a36c7015bb73adb5ad1c1bdaac2baede4cc831a05cf85efa3cc7f2f","impliedFormat":1},{"version":"38e4684c22ed9319beda6765bab332c724103d3a966c2e5e1c5a49cf7007845f","impliedFormat":1},{"version":"f9812cfc220ecf7557183379531fa409acd249b9e5b9a145d0d52b76c20862de","affectsGlobalScope":true,"impliedFormat":1},{"version":"e650298721abc4f6ae851e60ae93ee8199791ceec4b544c3379862f81f43178c","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"13283350547389802aa35d9f2188effaeac805499169a06ef5cd77ce2a0bd63f","impliedFormat":1},{"version":"680793958f6a70a44c8d9ae7d46b7a385361c69ac29dcab3ed761edce1c14ab8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"913ddbba170240070bd5921b8f33ea780021bdf42fbdfcd4fcb2691b1884ddde","impliedFormat":1},{"version":"b4e6d416466999ff40d3fe5ceb95f7a8bfb7ac2262580287ac1a8391e5362431","impliedFormat":1},{"version":"5fe23bd829e6be57d41929ac374ee9551ccc3c44cee893167b7b5b77be708014","impliedFormat":1},{"version":"0a626484617019fcfbfc3c1bc1f9e84e2913f1adb73692aa9075817404fb41a1","impliedFormat":1},{"version":"438c7513b1df91dcef49b13cd7a1c4720f91a36e88c1df731661608b7c055f10","impliedFormat":1},{"version":"cf185cc4a9a6d397f416dd28cca95c227b29f0f27b160060a95c0e5e36cda865","impliedFormat":1},{"version":"0086f3e4ad898fd7ca56bb223098acfacf3fa065595182aaf0f6c4a6a95e6fbd","impliedFormat":1},{"version":"efaa078e392f9abda3ee8ade3f3762ab77f9c50b184e6883063a911742a4c96a","impliedFormat":1},{"version":"54a8bb487e1dc04591a280e7a673cdfb272c83f61e28d8a64cf1ac2e63c35c51","impliedFormat":1},{"version":"021a9498000497497fd693dd315325484c58a71b5929e2bbb91f419b04b24cea","impliedFormat":1},{"version":"9385cdc09850950bc9b59cca445a3ceb6fcca32b54e7b626e746912e489e535e","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"84124384abae2f6f66b7fbfc03862d0c2c0b71b826f7dbf42c8085d31f1d3f95","impliedFormat":1},{"version":"63a8e96f65a22604eae82737e409d1536e69a467bb738bec505f4f97cce9d878","impliedFormat":1},{"version":"3fd78152a7031315478f159c6a5872c712ece6f01212c78ea82aef21cb0726e2","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"58b49e5c1def740360b5ae22ae2405cfac295fee74abd88d74ac4ea42502dc03","impliedFormat":1},{"version":"512fc15cca3a35b8dbbf6e23fe9d07e6f87ad03c895acffd3087ce09f352aad0","impliedFormat":1},{"version":"9a0946d15a005832e432ea0cd4da71b57797efb25b755cc07f32274296d62355","impliedFormat":1},{"version":"a52ff6c0a149e9f370372fc3c715d7f2beee1f3bab7980e271a7ab7d313ec677","impliedFormat":1},{"version":"fd933f824347f9edd919618a76cdb6a0c0085c538115d9a287fa0c7f59957ab3","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"6a1aa3e55bdc50503956c5cd09ae4cd72e3072692d742816f65c66ca14f4dfdd","impliedFormat":1},{"version":"ab75cfd9c4f93ffd601f7ca1753d6a9d953bbedfbd7a5b3f0436ac8a1de60dfa","impliedFormat":1},{"version":"f95180f03d827525ca4f990f49e17ec67198c316dd000afbe564655141f725cd","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"1364f64d2fb03bbb514edc42224abd576c064f89be6a990136774ecdd881a1da","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"950fb67a59be4c2dbe69a5786292e60a5cb0e8612e0e223537784c731af55db1","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"07ca44e8d8288e69afdec7a31fa408ce6ab90d4f3d620006701d5544646da6aa","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"4e4475fba4ed93a72f167b061cd94a2e171b82695c56de9899275e880e06ba41","impliedFormat":1},{"version":"97c5f5d580ab2e4decd0a3135204050f9b97cd7908c5a8fbc041eadede79b2fa","impliedFormat":1},{"version":"c99a3a5f2215d5b9d735aa04cec6e61ed079d8c0263248e298ffe4604d4d0624","impliedFormat":1},{"version":"49b2375c586882c3ac7f57eba86680ff9742a8d8cb2fe25fe54d1b9673690d41","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"847e160d709c74cc714fbe1f99c41d3425b74cd47b1be133df1623cd87014089","impliedFormat":1},{"version":"9fee04f1e1afa50524862289b9f0b0fdc3735b80e2a0d684cec3b9ff3d94cecc","impliedFormat":1},{"version":"5cdc27fbc5c166fc5c763a30ac21cbac9859dc5ba795d3230db6d4e52a1965bb","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"f416c9c3eee9d47ff49132c34f96b9180e50485d435d5748f0e8b72521d28d2e","impliedFormat":1},{"version":"05c97cddbaf99978f83d96de2d8af86aded9332592f08ce4a284d72d0952c391","impliedFormat":1},{"version":"14e5cdec6f8ae82dfd0694e64903a0a54abdfe37e1d966de3d4128362acbf35f","impliedFormat":1},{"version":"bbc183d2d69f4b59fd4dd8799ffdf4eb91173d1c4ad71cce91a3811c021bf80c","impliedFormat":1},{"version":"7b6ff760c8a240b40dab6e4419b989f06a5b782f4710d2967e67c695ef3e93c4","impliedFormat":1},{"version":"8dbc4134a4b3623fc476be5f36de35c40f2768e2e3d9ed437e0d5f1c4cd850f6","impliedFormat":1},{"version":"4e06330a84dec7287f7ebdd64978f41a9f70a668d3b5edc69d5d4a50b9b376bb","impliedFormat":1},{"version":"65bfa72967fbe9fc33353e1ac03f0480aa2e2ea346d61ff3ea997dfd850f641a","impliedFormat":1},{"version":"c06f0bb92d1a1a5a6c6e4b5389a5664d96d09c31673296cb7da5fe945d54d786","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"872caaa31423f4345983d643e4649fb30f548e9883a334d6d1c5fff68ede22d4","impliedFormat":1},{"version":"94404c4a878fe291e7578a2a80264c6f18e9f1933fbb57e48f0eb368672e389c","impliedFormat":1},{"version":"5c1b7f03aa88be854bc15810bfd5bd5a1943c5a7620e1c53eddd2a013996343e","impliedFormat":1},{"version":"09dfc64fcd6a2785867f2368419859a6cc5a8d4e73cbe2538f205b1642eb0f51","impliedFormat":1},{"version":"bcf6f0a323653e72199105a9316d91463ad4744c546d1271310818b8cef7c608","impliedFormat":1},{"version":"01aa917531e116485beca44a14970834687b857757159769c16b228eb1e49c5f","impliedFormat":1},{"version":"351475f9c874c62f9b45b1f0dc7e2704e80dfd5f1af83a3a9f841f9dfe5b2912","impliedFormat":1},{"version":"ac457ad39e531b7649e7b40ee5847606eac64e236efd76c5d12db95bf4eacd17","impliedFormat":1},{"version":"187a6fdbdecb972510b7555f3caacb44b58415da8d5825d03a583c4b73fde4cf","impliedFormat":1},{"version":"d4c3250105a612202289b3a266bb7e323db144f6b9414f9dea85c531c098b811","impliedFormat":1},{"version":"95b444b8c311f2084f0fb51c616163f950fb2e35f4eaa07878f313a2d36c98a4","impliedFormat":1},{"version":"741067675daa6d4334a2dc80a4452ca3850e89d5852e330db7cb2b5f867173b1","impliedFormat":1},{"version":"f8acecec1114f11690956e007d920044799aefeb3cece9e7f4b1f8a1d542b2c9","impliedFormat":1},{"version":"178071ccd043967a58c5d1a032db0ddf9bd139e7920766b537d9783e88eb615e","impliedFormat":1},{"version":"3a17f09634c50cce884721f54fd9e7b98e03ac505889c560876291fcf8a09e90","impliedFormat":1},{"version":"32531dfbb0cdc4525296648f53b2b5c39b64282791e2a8c765712e49e6461046","impliedFormat":1},{"version":"0ce1b2237c1c3df49748d61568160d780d7b26693bd9feb3acb0744a152cd86d","impliedFormat":1},{"version":"e489985388e2c71d3542612685b4a7db326922b57ac880f299da7026a4e8a117","impliedFormat":1},{"version":"5cad4158616d7793296dd41e22e1257440910ea8d01c7b75045d4dfb20c5a41a","impliedFormat":1},{"version":"04d3aad777b6af5bd000bfc409907a159fe77e190b9d368da4ba649cdc28d39e","affectsGlobalScope":true,"impliedFormat":1},{"version":"74efc1d6523bd57eb159c18d805db4ead810626bc5bc7002a2c7f483044b2e0f","impliedFormat":1},{"version":"19252079538942a69be1645e153f7dbbc1ef56b4f983c633bf31fe26aeac32cd","impliedFormat":1},{"version":"bc11f3ac00ac060462597add171220aed628c393f2782ac75dd29ff1e0db871c","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"3b0b1d352b8d2e47f1c4df4fb0678702aee071155b12ef0185fce9eb4fa4af1e","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"a344403e7a7384e0e7093942533d309194ad0a53eca2a3100c0b0ab4d3932773","impliedFormat":1},{"version":"b7fff2d004c5879cae335db8f954eb1d61242d9f2d28515e67902032723caeab","impliedFormat":1},{"version":"5f3dc10ae646f375776b4e028d2bed039a93eebbba105694d8b910feebbe8b9c","impliedFormat":1},{"version":"bb18bf4a61a17b4a6199eb3938ecfa4a59eb7c40843ad4a82b975ab6f7e3d925","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"e9b6fc05f536dfddcdc65dbcf04e09391b1c968ab967382e48924f5cb90d88e1","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"2b664c3cc544d0e35276e1fb2d4989f7d4b4027ffc64da34ec83a6ccf2e5c528","impliedFormat":1},{"version":"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5","impliedFormat":1},{"version":"3cd8f0464e0939b47bfccbb9bb474a6d87d57210e304029cd8eb59c63a81935d","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"3026abd48e5e312f2328629ede6e0f770d21c3cd32cee705c450e589d015ee09","impliedFormat":1},{"version":"8b140b398a6afbd17cc97c38aea5274b2f7f39b1ae5b62952cfe65bf493e3e75","impliedFormat":1},{"version":"7663d2c19ce5ef8288c790edba3d45af54e58c84f1b37b1249f6d49d962f3d91","impliedFormat":1},{"version":"5cce3b975cdb72b57ae7de745b3c5de5790781ee88bcb41ba142f07c0fa02e97","impliedFormat":1},{"version":"00bd6ebe607246b45296aa2b805bd6a58c859acecda154bfa91f5334d7c175c6","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"0d28b974a7605c4eda20c943b3fa9ae16cb452c1666fc9b8c341b879992c7612","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"87ac2fb61e629e777f4d161dff534c2023ee15afd9cb3b1589b9b1f014e75c58","impliedFormat":1},{"version":"13c8b4348db91e2f7d694adc17e7438e6776bc506d5c8f5de9ad9989707fa3fe","impliedFormat":1},{"version":"3c1051617aa50b38e9efaabce25e10a5dd9b1f42e372ef0e8a674076a68742ed","impliedFormat":1},{"version":"07a3e20cdcb0f1182f452c0410606711fbea922ca76929a41aacb01104bc0d27","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"4cd4b6b1279e9d744a3825cbd7757bbefe7f0708f3f1069179ad535f19e8ed2c","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"c0eeaaa67c85c3bb6c52b629ebbfd3b2292dc67e8c0ffda2fc6cd2f78dc471e6","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"b95a6f019095dd1d48fd04965b50dfd63e5743a6e75478343c46d2582a5132bf","impliedFormat":99},{"version":"c2008605e78208cfa9cd70bd29856b72dda7ad89df5dc895920f8e10bcb9cd0a","impliedFormat":99},{"version":"b97cb5616d2ab82a98ec9ada7b9e9cabb1f5da880ec50ea2b8dc5baa4cbf3c16","impliedFormat":99},{"version":"d23df9ff06ae8bf1dcb7cc933e97ae7da418ac77749fecee758bb43a8d69f840","affectsGlobalScope":true,"impliedFormat":1},{"version":"040c71dde2c406f869ad2f41e8d4ce579cc60c8dbe5aa0dd8962ac943b846572","affectsGlobalScope":true,"impliedFormat":1},{"version":"3586f5ea3cc27083a17bd5c9059ede9421d587286d5a47f4341a4c2d00e4fa91","impliedFormat":1},{"version":"a6df929821e62f4719551f7955b9f42c0cd53c1370aec2dd322e24196a7dfe33","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},"9dd9d642cdb87d4d5b3173217e0c45429b3e47a6f5cf5fb0ead6c644ec5fed01","d0fa926a28d5859fc6ebd35f29017ae9581053dfeaa17232d006651286b17f1e","06d865d541ddc2fd8421f12d879ce3b9e06e5c9c71f785b22032fdafa4b664d6",{"version":"8727e08f3b0b9929db5f1a9e9233c1ce84f3ded0728ea04e34870fdf35c92460","impliedFormat":1},{"version":"1dab12d45a7ab2b167b489150cc7d10043d97eadc4255bfee8d9e07697073c61","impliedFormat":1},{"version":"a1285aa3c8c98d6dd7fb329247e25d4ad15d100dc2deaebffa1fcd9b27744009","impliedFormat":1},{"version":"9cce77f4aa9229e1a8da375823124d225b46185586d7c7bc61a3f95c392cf828","impliedFormat":1},{"version":"d47961927fe421b16a444286485165f10f18c2ef7b2b32a599c6f22106cd223b","impliedFormat":1},{"version":"341672ca9475e1625c105a6a99f46e8b4f14dff977e53a828deef7b5e932638f","impliedFormat":1},{"version":"96dbce2c5d8cce36846eaca7cab4d693aaec988c5a89ab2aaae14bb308d67909","impliedFormat":1},{"version":"09c81da9d9bf8ae47037b94923df4b2ef8c7673536aef850d555f90f7aab604b","impliedFormat":1},{"version":"aa8f345d88a8fd01726038a744b7dc266c0953398b30d6ed9695ded3078d5997","impliedFormat":1},{"version":"c5116acf7543dd3e9db9c245c6c774dbb70afbbf9a89a7c1cb9a00f44509b72a","impliedFormat":1},{"version":"ac03225167e525c57840d1bb398e14d5826e7483b694efed03f8cdc1302e956b","impliedFormat":1},{"version":"cffc54cdf1cac1fb2e1b5a00ce4d94e15bae67a7a030297eb65832213f21763c","impliedFormat":1},{"version":"26f0015f8592d441281c6d5e91919f82a94c8450d020717c72530f9168e694d3","impliedFormat":1},{"version":"472a7454ac6cb805a0b1618080a1f035882934b3343d5d718702ad338d744ed3","impliedFormat":1},{"version":"54902651afba1ab279869fe2b55e28c2afc5cda8506dc9401d4e6be50993e0c6","impliedFormat":1},{"version":"6de7a6a8f7d8e1184671db5b6ce77deb212b8dd0f5dabba8859571d58abea30c","impliedFormat":1},{"version":"6da2c3ef5672dfc094e8fc711a0a98fdb4acecce2d585bcbcc03357f5fe78b95","impliedFormat":1},{"version":"f86a7b8bb6574182276f66a458a257ed84f83712a2b5d780e6f05fc6301d1a5f","impliedFormat":99},"978e2e25204ccedc8d6f9591f7f0c7c683e93853fd2b668292216e1a50236a46","f5f0267de1d5cd55622e52587b13375c1a144902c9d53b0ed28cf1106e28433a","c415662fc229e54f6a1d7ccc24808444c937efb7c4b0639a1cfc9cf69088cc4e","271ec802f941892daef8224ba289e1dd579177c41bbe9b5b9b38fbd49550815e","c32e40a137616c4c8cf3362e47970bc08f2503da6b92ec803c529f97fcd85805",{"version":"a4ceb3ad7ba63c8d262080decf2768867ec94c0ba3225ccde22f198eb2ff2e6a","signature":"7ceb09e89dba51cc16248229ed5a433e05bccb08d693b3f739387b3e99e74a7e"},"ea2d4b515f2be9e457c4bd9d53d9c9e50017376231ffdfcd540b2a819a56bccd","c50e8b1ae6169b2c27bdea8f3f52a963ab62e32e715fb5edad9585b81b8fbb5c",{"version":"47fc95fdc77c976f83eec81f066aaf42c7010689cb5e717b9a1cd34e12d24aa2","signature":"6d9067b45f7fbca09856be6ee1fbbc973ff8177d92f4bb1b88c7812347515e29"},{"version":"cf7117005135e7567f8bc6c658b57d380f78f5b7ef51d81e24448476b1646732","signature":"026d5244987b6566e80aec5c4af362dc81c00600802bd9b0f237aec5f99045c9"},{"version":"d9235cc5184692635430d41d55230ba71ddea1e2aadc3b000d83309a94f93a00","signature":"0c6d6df26989fe671efbe28cc3dc617c8462e690831ed51c89bbe2f72b89301b"},{"version":"889b2c3eb9e1589df4ed4d28ef37e25be191e1dc4f7dd82d600ce79811800ff3","impliedFormat":99},{"version":"ae046314c0651da4a01e9e48ddf370ce9d22ad21f48962f25a12c1c09de9b01a","impliedFormat":99},{"version":"d0e136d6bf3c38be7af296b7e01912b6e8944a428ba7fd1e415a10acd9e687e8","impliedFormat":99},{"version":"7a685305685db7f9d2195ae629df44ae5888c13371a032ebe629a615a177a45b","impliedFormat":99},{"version":"33ffa9a4b37b246cf4a44566dd09cbd8cd3f08d957f98724762b2d415b2d1c30","impliedFormat":99},{"version":"fd4ab8f4eaf85c64a0806278def98cc71c7b2076bcc1ae89c0b81dc59b04aedd","impliedFormat":99},{"version":"c3aa1b9d09adac7ac5e49aba8e8fa7114c2c842d46c2c5f51da53ec889787bac","impliedFormat":99},{"version":"3a2cdc1bb493736e7c89fb3238ca909afd6322cccbb6952c6cd56be518a2b9a3","impliedFormat":99},{"version":"0f4f54801406a0a67455a9ad950bed9f4d2921fd66a91682f83a985086d60082","impliedFormat":99},{"version":"23e2aa3fe97254db69a9ba8f6b62e9397a5c64fd080e2dbf70d9004f226a224a","impliedFormat":99},{"version":"77d9b19c200d3803c1b31a29dd65cb977983e9b81dd797fab892e70bbe6b9679","impliedFormat":99},{"version":"a189ede87046071ae9cd84e90d09327b3ab44b5e73947d38eaa56c2e839b0fc7","impliedFormat":99},{"version":"ed2d9201cc939621497a3d92ad39828a8823b7c7fe64f6c41d43792204e60f97","impliedFormat":99},{"version":"3f05e2ba96cd23e6a53cfea66bc100621c4703bf6d46515b841dd6892286b08e","impliedFormat":99},{"version":"6e5aa91099e2fe5d1d05f6f3100a90e5a5d9b8aea7b0ea6f4d05a0f192899a64","impliedFormat":99},{"version":"bd85cba544b37cd32e8d02b138c3a2a4075930d01146b3f5e33d713b39dafe77","impliedFormat":99},{"version":"04a7116aece3802e7ee128fed47d31cd18e5660825a62b42a62929f9508b936e","impliedFormat":99},{"version":"20ca05d62223bf6f117925ef8f9b9781e894cb146d30ac491e0763d34e53a5d0","impliedFormat":99},{"version":"4ba733d1a5ff0a0779b714468b13c9089f0d877e6fbd0147fac7c3af54c89fe0","impliedFormat":99},{"version":"697203f3f5a1fea90e40fe660360325090ab36e630dc9422a1909dd4faa2cacc","impliedFormat":99},{"version":"ad1226eba93a65cdccdb1b4f115d67c5469e12705dbe80139c2988d6b296d04d","impliedFormat":99},{"version":"4ea2c94c3a1c87029d10f11c209674d4c6a0c675a97503dc9668d2815ff6ea11","impliedFormat":99},{"version":"63ec0a7e0f0b1ac673102c02c00969d992d7dde30d7066d941f0c3e2c9e80610","impliedFormat":99},{"version":"31ace06421fa71c192f4b9de6ccf36dbe0ee3534a237ebafce0a2215bb9455f1","impliedFormat":99},{"version":"94cfe3be66e4a6a1d52eaff0eb03bea21b4cded83428272c28feedfa5f9a152a","impliedFormat":99},{"version":"c2cf5eb33fc641dd321afd12c726ac3e753a81ab1618270ce6cd508f927989c7","impliedFormat":99},{"version":"a7f2f38cd72a96e7678555a1166a4488771b94e5a9c799d1c8943974ada483bd","impliedFormat":99},{"version":"c519327110a82e5eeaad683dc64f36994f19d9893fe69c4ea2b19d41b7e3e45b","impliedFormat":99},{"version":"23af35a045f9117250e060abdb2789bd34519eb5a6308463f299975a205b2d8c","impliedFormat":99},{"version":"9eaaedc489e28c9f7ff513bc094fe82da02cf2c4a3b2b35fe025699fcc08be78","impliedFormat":99},{"version":"73c4f628937d4e4a94d5af1c04bf57008a9d2c5f94a8fe6d9da8d51783069e15","impliedFormat":99},{"version":"24f2e4c8e5896cfd05c2cd8f51766f75f84b62eed6e03c5da857422cc747bed6","impliedFormat":99},{"version":"4cd02f2d4d7feae05b035dc1c451070f7536601f4f060d0e944959f1036b3b18","impliedFormat":99},{"version":"89c0b39cc1e9dee0c0233f656fc0aa64d1e8ce9ee0774c4b84286bb626c735d6","impliedFormat":99},{"version":"6e16ba58508a87f231264a5e01b0859669229a40d6edea4485ac2032ddf8a7c6","impliedFormat":99},{"version":"8a3576bf6a6c46ceaf02e1a8fb56146b9271c5194ff8bf171156d0467946565f","impliedFormat":99},{"version":"bc7fa5ac468d3757f401039f89ab5e13967557b78dc0bdcd3383bab1e195263e","impliedFormat":99},{"version":"0df2fda4b69934223c08e3b8dc5ddb620e2c0636e973960e624f309a330229c0","impliedFormat":99},{"version":"8e67c31beb3a87366e5eaa2f0399078be858b5b05b6b8209e594be97b69b020e","impliedFormat":99},{"version":"49455da231ef295ce5fdc0baa019df7622f4b9dc136677109cda3bd20824e903","impliedFormat":99},{"version":"7e92bffc685fdc3d8e04df0218f616022fc571ee8c057e95922e653759576816","impliedFormat":99},{"version":"9f404e614b2c4cff62c24a47df9e91c72bf3ff160624deac897bb35552b43842","impliedFormat":99},{"version":"715a0154ce1359c3387d467981bc331da06822a9b999bbdf5c483696e28a4ceb","impliedFormat":99},{"version":"38c9d54cbcd3022f7b8bac65e76b05fc15c318f53a114a5bcc2d69c33a182adb","impliedFormat":99},{"version":"86c183d6201f35c841e31d97ede84989e4c02b690786f7c11efe025ec2f15c05","impliedFormat":99},{"version":"f0258631e56de39d236a0a34aed46b28859d376949ba040204513e61fb2dfb97","impliedFormat":99},{"version":"6b5db77219e73ae487db6ad94fb2048b0bb051b4fdce283600a763cf63b14e8e","impliedFormat":99},{"version":"de5d4a4fe9ab759003078db5588ff1d6b4727e164b6ad570859cfdf06a9bd47e","impliedFormat":99},{"version":"86ea856c59eb2c4d2090f22510d63631393af4793c98f0ad100f4545f0c9088e","impliedFormat":99},{"version":"5ef5bf461006cf3fe5bf50c5aed8d4fd5ba4aed06105428d4df3733136c9d9d7","impliedFormat":99},{"version":"6a074d0008571a6d1396fb1d86e54bf22bfb38fcd8a4df1e61cf4ca309817fac","impliedFormat":99},{"version":"bfedd3882083dc1f013092f079f76c17b6ea95594fba33a3e56c222ca984f263","impliedFormat":99},{"version":"7d89a609d9e50597111dde64263b0708343a68db2d53aee0bc2d3bcab7e745c3","impliedFormat":99},{"version":"0b840392f948eeb18059b0478c2073ac222a467829df29ef0eb1f15f55a3c77a","impliedFormat":99},{"version":"c91b058ab74323c57dda1cbda7eb8cee56272002249a642deebbbd977c4a0baa","impliedFormat":99},{"version":"cb7f489960477f1f432a3389f691dc243ca075e87f20032a2866321dab05bae2","impliedFormat":99},{"version":"ca885b971dc0c8217ef8aca9f3879c3c2d53415c4dfbe457748045160f6e5205","impliedFormat":99},{"version":"b8019033d2c520e44e54fb15b5865eca6b2939920ea8002b37e263d3187d5b71","impliedFormat":99},{"version":"a4e67e8eb5df4e6630588b1ce5c114d1fa5a533eb1f45c2ee4ecb4f48dab7788","impliedFormat":99},{"version":"3f8e1b42b7e6aa4756e00b26c972a64eac1b747600b2540120f95a92cb1684e4","impliedFormat":99},{"version":"93d5f2949aafca2e199761d2ded0b4870446685897b4d2214d0a9d9d581e0538","impliedFormat":99},{"version":"7c7a960997d3470573faaaa089e6effd21cd6233d97ba7245974b4adf46597fd","impliedFormat":99},{"version":"2bb814f26a57746ff80ff0dee91e834d00a5f40d60ee908c9c69265944c3a8b5","impliedFormat":99},{"version":"86e035d87d8f9827b055483b7dfdb86ecbb7d2ca74e9dce8adeaf6972756ac03","impliedFormat":99},{"version":"73ac47e45d90fb23336a6a01512ae421275cb1c818b7a5068ec84f58e94a5999","impliedFormat":99},{"version":"b5c081fa1ad246f75cd10b822eebdf35c3e1c510e9ad3a89570fbe4c575e0cb1","impliedFormat":99},{"version":"017907864b01ae728f5be6be99ea7632e68b2a35c2d7c9606bde20f85f10f838","impliedFormat":99},{"version":"a86a5d2ab15be86342869797f056d4861fd0b7cfae4cfa270121f18fe8521eab","impliedFormat":99},{"version":"22f98eae982b7f0d26d3dd7849210e033dc1992f594d87c6fe30075eb94b7a24","impliedFormat":99},{"version":"ec47b34311c3c799d1c90a3dcac1651ed23948c064aca4f0617fa253e648ab15","impliedFormat":99},{"version":"761efac4dfd849586e4fe49fc6cda2aba8e708fa8e4eb19ae85373084cba0d51","impliedFormat":99},{"version":"899ed4016a7a722a6224e78139286f1ab7d05f79be50af0a6492b95170e56fab","impliedFormat":99},{"version":"965bfde0433a808a389b80a8e45b717cd2d5a3a0cdf418707cfda3046e33fa5e","impliedFormat":99},{"version":"f5f69e449b4dc8e31cd0315860c59fde6358408f19747b76d98c90773ca62b19","impliedFormat":99},{"version":"6f623e7a3b9de6f69a0b2f81413d4dc357017ebbda86f12153346a52f1e2a739","impliedFormat":99},{"version":"e44950769f3c3b4ee2fa3b0a19c1cfd190c4e8378287ce8a2dabf821c7b9c2e3","impliedFormat":99},{"version":"bf76280034333a5b25cea406a584f165a9535cef59460271549447eb2349117c","impliedFormat":99},{"version":"b011f71b5d21579da9f868e56acf3887051fc4027cc7cde7317facb232ed3e95","impliedFormat":99},{"version":"281eb8e4ddd65b6733cf1f175dd1af1bb2595bbcea7c12324f028079ba78fdf9","impliedFormat":99},{"version":"81e2b5cdeccd368e2eaaf33003fea50bd39740f8a8a71a1a487458435b7695dc","impliedFormat":99},{"version":"919b86dd53cd38daf8207cac48b67824ad2b8c9daffe559e1b0ec75f39d21ba8","impliedFormat":99},{"version":"aa7a83f4acf2686925511ecc32d148062c02984068d563c44f00835fee5b164f","impliedFormat":99},{"version":"d4632bbd2d2afbb1b75163dc7cabab5cc218c2fa933cb8f7d5b7089255faa6fd","impliedFormat":99},{"version":"0cf4827f19c749c5befed9585862c6196a4a5b3d889d20e0f5f4bdb6f734dcc7","impliedFormat":99},{"version":"14d3c7499d1759af5c78eec4f26a6f5b85bdd5b0e41ef3f5e6e813f1ae88c06a","impliedFormat":99},{"version":"0082935dc2cb31cd632eaa6bbdec17f1a9142652e38ede025c0ffab00c50bac4","impliedFormat":99},{"version":"0df7497ada3a4f6459420803ecf7e555f1ad1e7bd43c1e17bdafbc34e19d7162","impliedFormat":99},{"version":"5cccc8d1dd17c789bb6baba06a035e98e378a80d133da3071045c9901bee0094","impliedFormat":99},{"version":"c8a40bb3df60346af02e8d786473985ba53b716bc7caefd21ab838f025ec103b","affectsGlobalScope":true,"impliedFormat":1},{"version":"64da9a17f7cb5d84731607aed8493e4550a3e613cc7b880c87ce82b209d66b96","impliedFormat":99},{"version":"1e4bccd328de23aafb5a566317b3fa580aa2caa1a9146cc0b9effa792ba48ea9","impliedFormat":99},{"version":"156ac329be3116b9c1f55ae3cdf8e7586717561ac438ee6b193e7c15b2c87b1a","impliedFormat":99},{"version":"6d056661e4b636cc04e36c36b24a4eb692499b21fe0b18cb81f8bb655d7a3930","impliedFormat":99},{"version":"6841cf2ed521fb1851b90e77a9e8d126fc4b5cc6bc84308431c984d33f9cc25d","impliedFormat":99},{"version":"2520a0ad6036acda0fe224073260d9cccf753bffb1ed969e9fd33b28c18a252f","impliedFormat":99},{"version":"1f957c8657bb868e8cb92e46eac8c8b1877a96708e962015a1ed47fd42c697f6","impliedFormat":99},{"version":"217800577a2c9a7232e5a9d1abd1c1836acbb004e7522a5261299aa867713f96","impliedFormat":99},{"version":"60981ae7c2a8926f7855d8068c42e05a3b1959f0bb795a8bb9773c912a9a6f16","impliedFormat":99},{"version":"4a6de5821d23f5e1781c567ab6550e5357b2c2ae3e8813a277062512f73d4a28","impliedFormat":99},{"version":"618b5aa1f8b9791938f8033f1855238774b555f9dd35f0b8a5443cc066721605","impliedFormat":99},{"version":"61eae777e8f738baf3b175855148ef52c7075f9987f8a82cb0828270a452578c","impliedFormat":99},{"version":"877a4fc3a752172661fdddd0e4a397e530645f22c44629b3789cf2af470b1a12","impliedFormat":99},{"version":"2022dfa3df40a1f11d5f354bd729d0a2bb69d0d12b62767d3b237a29e6c62d4a","impliedFormat":99},{"version":"68617a52d0596e488c88549c000e964c5f6a241e5361095b2c6203586689b1f3","impliedFormat":99},{"version":"8d4a70e05b1f8450f5fb8997e5bfc336dd0baec3f2c8117f6f260d4eb68de0ac","impliedFormat":99},{"version":"6ad9b51ae0bec9bed7c07bda091c21472436ed419e80685b09466482f5ba1db4","impliedFormat":99},{"version":"72f03e06f5347f95133b7ba133135498c6120fc419fb0d8d48da873e91f0f937","impliedFormat":99},{"version":"0e6b3c7f300f6e2587c62783ebf78c74e61e7e85d37591e1e1ecf82cc15adc01","impliedFormat":99},{"version":"f78f6212fdebbc513a6656389ecf3c4bd9e77f79c0d2da2250de961b386a67a5","impliedFormat":99},{"version":"5de56154de88f7bbad618a1aac7dcfbf8234785cb8821b00c6902208587409f9","impliedFormat":99},{"version":"a4f4ecd42fc62ae32f9fa03100f821c61a2ca3d5fe2a9c0720baddbd67ad3174","impliedFormat":99},{"version":"b41dc4272747d7b9e3f5620815fd1aece9bc2c0c09e00c4101b429216599412e","impliedFormat":99},{"version":"4c136da3b1dce49c12eac152699c6b4bc64fa93d6c7224a43c816f7e51b00930","impliedFormat":99},{"version":"1093df5dbb38c416c10e41b3379033e952cb26cfa2a667bdf182f55dcca0d7e9","impliedFormat":99},{"version":"4d42746407b6732df92275e20f311f9717b57f1e3a90cf71730620077a7daf5d","impliedFormat":99},{"version":"72635b405f1d979eee2110b7d2921470748e13b19adbf42887c2680964af6f30","impliedFormat":99},{"version":"3a719c9c30a20a413b97a458f411679bbe56a4de8ddb2f3ae7cf2639e86d0e0f","impliedFormat":99},{"version":"ea37a7bc8718a01eeff979fef574318d7a5915fc786c74582c86cb553bee484b","impliedFormat":99},{"version":"6c61ff540eda59f07484aa863b753d7d6a8de0ac907e0e912ce2835f2e86e167","impliedFormat":99},{"version":"b90f14bca14cdbdd60dc83c451aca97e8df63c8eb8a158a9ed84de4bfb4cad76","impliedFormat":99},{"version":"bcdfa0b735dff249e6cafe7096d17d338c3942704c20b0cacf78c1d78a5a427f","impliedFormat":99},{"version":"64e63d7db36b9a658ed3101e0847c2aad6e5f03e0a37c6996d879dbd8b5a7623","impliedFormat":99},{"version":"98ba4768c426848773fb4a39203aac92e6baa545d93510665cdf207454d0811c","impliedFormat":99},{"version":"f65116ea54fd65813a0d9695249ceaa716487c932247e4aede3e2e3ad3d07316","impliedFormat":99},{"version":"99484c7a277c488a16c49ac1affe465e4fbb5e4d57b8c2190092c5d7b4fe6fca","impliedFormat":99},{"version":"c29f7f5c851ec3a781a17d7afb9280da6adfc9748535481b381daf5a67f439d0","impliedFormat":99},{"version":"0f1ea4f6570d745ee2dfa784baa306ae15c35ff7742566ac5ccc1a893af9a1ba","impliedFormat":99},{"version":"06e727ca4d41b4f549f875d7999d940a392058b1b579846441351ff011a63a31","impliedFormat":99},{"version":"6c7f1a4f3d43a47624bdf26e93be7be9fe29cda02de5b53b83f5c7559ae07745","impliedFormat":99},{"version":"d94acd15b4a3517523756dfeabcb7b4fb8ee853bba680d892ccfd3df4c81edc1","impliedFormat":99},{"version":"0f65f9b61383ffcfa1a409da90c35741cd81ece1a2dc6f2ebd094d81599bc5f6","impliedFormat":99},{"version":"9abd03a84d5473e66b038270dbeae266129ab97261d348a5fbd32ec876161a85","impliedFormat":99},{"version":"b6024c6222886b95cb29ab236155a98f8e5dc41151233781815e81a83debf67b","impliedFormat":99},{"version":"94dab3752006a2cd2726462342f1775ef18ff4986404d016d317fe79a9d0a14c","impliedFormat":99},{"version":"727b3a462015bbed74b520861445761ebaecf94e09d95bbf59dfcf22afaccae9","impliedFormat":99},{"version":"2c0300921d8d04b21353c94a8f50a2b6c902feccd1303b6f136bedbb2cec5ed1","impliedFormat":99},{"version":"d496217c7f38f218fc162e8f3e6ed611343aa65615f730f82c494dee6c892bc0","impliedFormat":99},{"version":"282ed4ab5b5c4759d5c917c51a5b2f03ca1df4072275b6bccb936cf60078e973","impliedFormat":99},{"version":"2c96813e14e7edcd8e846f009b24fb1bd842b90e2dcd85481136e52588de7982","impliedFormat":99},{"version":"aa70da8072bb8b6e8fae35c7d394d543be8e5c946dad666225a3475010fd2bf0","impliedFormat":99},{"version":"d2c35cb9836cae1899ae9e7e114410dc128bcff4a79cc26318db285699e0223a","impliedFormat":99},{"version":"f89fbb50fd3736e09b418a2e66b98ff9a04820259856afe54bc67977e1acd05b","impliedFormat":99},{"version":"4c76aceec7002f299d9a57ec8e6623f3573bea208b1ea51cc5ea03bf140adad4","impliedFormat":99},{"version":"a0f217b01453d43058cea514325ac8bd3ac3a184265314429eec8059c62824b6","impliedFormat":99},{"version":"c73e552e79763809a52f967e48b7a96a0c164c672ef99de1fa8a7e9e02e3b177","impliedFormat":99},{"version":"3bb351642082a63b4565d8354455bb752daa8902451cd851d6235e04cfaff5a9","impliedFormat":99},{"version":"caa4ee2fefd75dd8bf98a9421e3f99f6c7e70c30b21e78384ed23903a04579e5","impliedFormat":99},{"version":"aecd83ca7059d21a33fb7ed01dfa06a36c545698dbe0017073dba45532a8487d","impliedFormat":99},{"version":"7fb874c17f3c769961d1b07b6bb0ef07b3ca3d49da344726d8b69608997ef190","impliedFormat":99},{"version":"979e969f86456425e505f6054f5d299f848223d70770a5283fa7c405020b47e1","impliedFormat":99},{"version":"7235f928c14f752f6ea3d6d034693c5d46154cce8757a053d9cd6856be2ab344","impliedFormat":99},{"version":"acd7f9268858029bcec5eba752515b9351d4435b21f1956461242c706dcc0cf9","impliedFormat":99},{"version":"53e2856f8644978742fae88b3c7f570ab509dc4d13288b3912a4446993fa3bc7","impliedFormat":99},{"version":"ea2b6112bfd326f1075896bf76c9108dfd08ccbae2482ba31f68ca43f0b59ca5","impliedFormat":99},{"version":"3f9368aa15d0cc227a3af7af3e3df431dadf0f7cd9897fcc54507f7eb68761cc","impliedFormat":99},{"version":"0f2d4be859066fc3ea8d04b583cd0774e1f9dce7f60b9890bcc0a10efb9fac33","impliedFormat":99},{"version":"ac09b9131c553c189311d9e94d3853b7942d0097925304fe043220a893701ce9","impliedFormat":99},{"version":"f1b34ea3d64f73fc79ce1f312589134db27aa78ef9e156a8f14f89f768e800ac","impliedFormat":99},{"version":"873da6c837a1ee62b5f9b286845be06dc887290a75c553bed7f431107d25a3b6","impliedFormat":99},{"version":"b2abee3c001c024d4e552c4a3319bf3fcc94a1f48bb0d21f5d300d9b4920bde9","impliedFormat":99},{"version":"f9740d044306830442cac761b593538117f46c5ea57a8dc6d61f0bee12e971b6","impliedFormat":99},{"version":"7cf786964e26f0e2c3a904f93f6e31609e2636723df8c1ce248d39b55055c89f","impliedFormat":99},{"version":"41c6aff52e4289763ea30f0849b712437aaeb420c8448aeb8047ee2eca4549f4","impliedFormat":99},{"version":"f5db101f7d90f614627bcab5f8d06d9ccd144a1735b475637940c54097786b67","impliedFormat":99},{"version":"8c575a8e1b6032e576577f28d74066f73aefa7a35d741d0015be36956bbc30aa","impliedFormat":99},{"version":"1989cb4fb2174c56b15f8b10d18ecb0c053e7b39f94582581d69767d7bfb9b32","impliedFormat":99},{"version":"cdac1d3e70d332d213295dc438bf78242c29b14534adf3ef404c3e255c66e642","impliedFormat":99},{"version":"47921880701610e8d8a5930d0c9ea03ee9c13773e6665f4ffc8378d5f8c8c168","impliedFormat":99},{"version":"41cbf6c58f2f4e1e5ee95a829b3f193f83952385fa303062f648040a314f939b","impliedFormat":99},{"version":"bb11cd0d046d21d4ae4a28fc4b0eb5d9336a728f9bd489807a6a313142903bc1","impliedFormat":99},{"version":"a96d6463ab2a5a4cf31b01946f1b0929dc3f8be9f28c7c43da29a9e6b7649db1","impliedFormat":99},{"version":"ec43d6b21fd1ed5a1afeb779ceba99e80fe010458bb0a67d9ef301426b1929e5","impliedFormat":99},{"version":"105bb5317c5212d56f82fd9730322b87f4ad8aea2927ef7684341afad050f49b","impliedFormat":99},{"version":"79ffce57ab318282b29bceb505812c490957124a3a96c7d280a342488b0859bf","impliedFormat":99},{"version":"06fd0e1204b7daf4164533fff32ab4e2c1723763c98794f51df417c21e0767f3","impliedFormat":99},{"version":"c4b46086b44bb8816d4a995654c00f64b3601eb50a163f2bba4dfe48ae6c6b91","impliedFormat":99},{"version":"32e670209322bd3692e8fc884c63002f6bd565e83f62f1fd23c46729aa335d1b","impliedFormat":99},{"version":"97717d35deb9f6a6127f3abff60c9af080ab0ccba60aa06a5a3486a374747573","impliedFormat":99},{"version":"4d70c89489fdef067b0819f22eec5fd0323a8b488d93075cb7953bbfc636e03e","impliedFormat":99},{"version":"233dc7f3ea55d2375b32c5c19034babec8e1496dc73784f9b091629a5287f2fe","impliedFormat":99},{"version":"e3fbf3f3e99083f8fc21bbde7677c3b1cad0c730fe231599a69911aa66487d01","impliedFormat":99},{"version":"59110c7d72a09bacde4a80f4ba95d9990b352911f0e4ea09bf766804f8d3e44b","impliedFormat":99},{"version":"3d827d1dd689311e57a98e476b3451445d39e573f4855ac265b7ec1747075c4f","impliedFormat":99},{"version":"e0669b0e7c953962035bb39e7fdfd5cc8fc3d9a666a8b167b78417355609be01","impliedFormat":99},{"version":"8495eef8be427c71a2d574e3ead06c537a9a6d437dd669e6786dab3df009f125","impliedFormat":99},{"version":"15741df16deef60b197560d3cfe45e6c1eff69fa7b85a861e3d8aa8a26683b83","impliedFormat":99},{"version":"802fd034cf22379b22a681e021d7ecc9073c01fccff1eb737f54ee2c6fe4395c","impliedFormat":99},{"version":"bb77b52bead9b75d7173bec685e5e2136f6c3f226cedae736db63a44f69db679","impliedFormat":99},{"version":"b3f7783d4977af919bdb8db798fe185908083c6f4bd3b07460967c8e093f7312","impliedFormat":99},{"version":"5a6bae49831f960e7f0bc66f49b2c40077b136d9573871f865507fde09580436","impliedFormat":99},{"version":"c9d03e6b230acfabb058a0b0391312dfb0e7001bb5955836333a14c7f9347f3e","impliedFormat":99},{"version":"e6295124f95b686a16233c1031d04cd971f9685e3416631f463bde75a5c86ce7","impliedFormat":99},{"version":"00c38bd1fe89fed8d4e8502db4f896aef7415b097ac061c2d65f2b539b6df6a7","impliedFormat":99},{"version":"94a2d7c15538d8e83415299f17fd00ab88c594b6a0a40be1e26c99febbab45f6","impliedFormat":99},{"version":"20bbd68ac2d2e7cdf9f60816ba9b378e13c07f0fdafccf9ae5833c876c6f51bc","impliedFormat":99},{"version":"df109d2490b693bd75105efaae08738ab84102bfdb2eee2372e9e3f369ec5fc2","impliedFormat":99},{"version":"0fabc5da6eb8454effc526d74f28b0abbe726eab0ed1296aa618b611da7d9462","impliedFormat":99},{"version":"d411ba0bcd6a51485be855a01cb95f79649fa90039b4f235ba8481dc68edae3e","impliedFormat":99},{"version":"b1991f24f264ab5e0d4de1a95b8483830ba659016dfe4b9e58b4076974c1966a","impliedFormat":99},{"version":"b8ba23b2e323342f2710619f6c1abf6731da764092cdca12f09b983ebf236d8a","impliedFormat":99},{"version":"6e688e8aeba98c268b195f80355a8d163d87ac135ad03c708ceda608e6e269b2","impliedFormat":99},{"version":"802a6978c1b38822934ce43a3505e13b555584848c50bc5db9deb2e896c0940e","impliedFormat":99},{"version":"f502c7d829f5774109007ec2262c23efc941dd1ce42acc140f293a7c5ccfd25b","impliedFormat":99},{"version":"af3444bd00030bae3bef81569f8703ecddc2e569cb6b728ec045f0d73d47572b","impliedFormat":99},{"version":"53102281f8a153bb051e0223a8dc51ff9c4cf92da127d91e3f60e74b4e8f41ca","impliedFormat":99},{"version":"e402e111fadcd36fa26ea1ad74f3defd6ef478f6d278a69c547e664b57770392","impliedFormat":99},{"version":"bf8f4b3b372e92a4e4942ce7f872b2b1e1bd1d3f8698af21627db2dee0dda813","impliedFormat":99},{"version":"ab8b67c1f76b6586467a8bf7a2e855e8c2eb4e386f1d264dec1d8d877de7210c","impliedFormat":99},{"version":"d6325d809c8396ecc90202ebfd2427e052a77d98cfd4e308f656346baf84106b","impliedFormat":99},{"version":"dad5c38d723d08fc0134279b90fac87441ee99b71b0d30814b86954e0111d504","impliedFormat":99},{"version":"a29375cdd094d8ea5180422fb278f6bcffdeade0280d86d24d77b017a6144833","impliedFormat":99},{"version":"68c4306bed9a4289e1fce188f9845c6ec0f361bf69443d588d42d5232b64e207","impliedFormat":99},{"version":"bb544ec93eab70a6c769cd69c0912742da7c2a8bed7d570e79b8af046a9ca556","impliedFormat":99},{"version":"532bd533a1921eedb9b39fa3559594ab783233867021a7a911db00be5d42fe7a","impliedFormat":99},{"version":"ad48586787d5e217f4fcc229e3c3d8de8aa12979fdf1f186134e3684d56577ac","impliedFormat":99},{"version":"229d6bca5145c86846793cb3166c83abb256cfdb5c425f25ada8eee49c993e54","impliedFormat":99},{"version":"292856f47dad178fe1cb3401554428b3b0157369a8fa52792587fd2bd06fcbec","impliedFormat":99},{"version":"714c28455e51edd574cd7acedd186efac20cf92f020bc15df7fcb6d972ea4627","impliedFormat":99},{"version":"5c390ba8a6920d8d9072b0646388e5a4fa455d6e02cef29164f1c0b917a16f41","impliedFormat":99},{"version":"b8562e5aefa86c069ec1c61dff56ef0492e9fbd731cbcdd4d7fce28a8644e9f6","impliedFormat":99},{"version":"4c6889d0a58ac09edf869d96fbe32f3bb697e2978afb86fd9d35d706dfe2bc76","impliedFormat":99},{"version":"dd6c7d6abb025e7494d02fa9f118af4a5ab0217e03ae54dd836f1160cb7a9201","impliedFormat":99},{"version":"82d76af0a89cd5eb4338771a2a5b27f3cbc689b22be0b840de75be4cfc61f864","impliedFormat":99},{"version":"24e856aec3b5c4228ffed866dcd8e7e692aa86eccaecc4fa8205fadd9737d1af","impliedFormat":99},{"version":"fe395a24df9ffd344cb825575d4b35c1cf69275208c0f99517c715bd7d08ff79","impliedFormat":99},{"version":"39e8edcbd5ac35c6cfdf2b1a794a9693a461a54efb2a475ab7fc08ab13504e26","impliedFormat":99},{"version":"12012b6c28d09a6f1d86b2a30213a92a9e92ad9ee573f94c92a8b237b6422bb7","impliedFormat":99},{"version":"8ee28204ddb2be7d6dfb68891493f654cbf10f5e1667bd33bd62920d9eb9e164","impliedFormat":99},{"version":"b09669391dd3312b8a52242af7823a3c44b50c7dcdc216db8da88b679af46574","impliedFormat":99},{"version":"b71e7f69e72d51d44ad171e6e93aedc2c33c339dab5fa2656e7b1ee5ba19b2ad","impliedFormat":99},{"version":"440c9aba92c41b63d718656bd3758f8f98619dbe827448e47601faa51e7a42fa","impliedFormat":99},{"version":"d9cf429fa9667112f53e9bb67bb7b32eeb3697f524d01b9781b65247f1733da4","impliedFormat":99},{"version":"763ee96bd4c739b679a8301b479458ea4fd8166892b2292efe237f2f023f44ca","impliedFormat":99},{"version":"9b10d76e4436eb4ac33c0a5540a02ec881a2fbcfcccfbb9883ebadff7f1d35ad","impliedFormat":99},{"version":"701e25008d343bdd67e02c0ccdce4c2ab41d56645bff646b5dc25e4145e77a3a","impliedFormat":99},{"version":"7a891af63bf06f2be51ed3a67fa974a324d7b917f7b1d10f323ed508a6662354","impliedFormat":99},{"version":"efa0e3dff0199f00eaeb36925776e62419538f7263ec77a56d5612ac5abe9ee2","impliedFormat":99},{"version":"ae6114539394eed7b6305a6d788cb6d2fd94e256d7582f5111a1972ee5a1c455","impliedFormat":99},{"version":"26e8e64bbad8cec5562ca764330c89d2cbe72a10fd7d9f320f16cc25bfe4a1e5","impliedFormat":99},{"version":"3563a343e025cb849b94da85e8455dd89064dee213bc97bbed559f83d74c98de","impliedFormat":99},{"version":"e5ccadb4286f9dea829a5efdf7fb686f13775a788fc7ed0916f068f00eaac983","impliedFormat":99},{"version":"a715a2786c285a9e27ea2bbaa2ed249d3017e7139782f5ebb8eeedb777b26926","impliedFormat":99},{"version":"64a5984f6db00ce2c83f7d0b220d7cb4a166b223bed89e1d5d82e3d7863ba983","impliedFormat":99},{"version":"8193bfcc2c3fb6a6bb537b0eae2d06cb7b844ff8525915eb3be5c6728df785ce","signature":"42bc5ba0c3ce6d1e68edf32beb49ab66b290b52d9b49ca2898778147544083e1"},"aa39cb9a1eea1dbec766af8659e810b8cd4756388f9d0fa40421c310721de8b3",{"version":"ba08e1cb52feff94c65021f6b32f8c59f3e5c94bb031122a380a1f880e98e615","signature":"f6161560fccd45d70d9c874adc5968526a9293efbcdd01e45b44b3c082ba9c9c"},{"version":"ab1815f13bc32b5d44da86e8ad4e9c1fb72964c90e705b2af5f8dcd7c99da1f3","signature":"4c3ac016c0c1bef134db23d0ccbfc0ec32d0a5173d7e3bf628dc2f06f710aa2f"},{"version":"064e59fcbfae4daefe4dc1fe599c228ddc2da895d585770ef995cc20efba4bd5","signature":"c58fb78fa680eaea571809c5822597607e5315dbce9ce502a72808f4f293d256"},{"version":"e78108dbe76438c09c4e24013e9eab2fd950066680e1077a01aeeae2a487755c","signature":"acfc9ded2b853d2fb5808bab624add7e7724af72a2a1c83bf85e5c4dac2ce3d7"},{"version":"d26f6c6e235c16886bfc5e44462b21c13404e9e1ffcdbb246f94e56f29040515","signature":"39f8f26d3b6fe72931a6511ac720d39af0e02c0978b8d6b97962efab1995aa1e"},{"version":"895c36a2467cd5f90b64a45030caddd72533a0adec578266fc87872b2491bee3","signature":"1be1782ce7e618f0897fdf638566382a1563863bd6bd04b6357a23d7f3b3b751"},{"version":"78a20709e7b00689beacea2c5c642cf6730dde236408583a68e394060b362ce8","signature":"d38143bc2c6f5955598ac849e9dca23e0734436665bf3217fc076c5e166b2fd4"},{"version":"c6cb4f8aada732188bf2d9dd241ce24394da4e41df2f15aa62dfecd43f611e76","signature":"52218915b0b0b078d10626b962bc9379afd984e08d9620bc6a16c9c53c6c231f"},"d9a4b32cd42cf64743ede0d200287dc67f3f054595953b954608440e70edc624","6f308a88b558079eaa6071478eb57aa43c6d8ca8bb635c390779e444f218239f",{"version":"8cb93f9120276bb56b43ffe3756aba1d52e88d28e79fe7ef3d9f50bdf8a73efc","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"cef6e9c5ba2440813672ebaf2aa1dfc3cc36192dfd14745b536b6039048a63eb","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"f4539c7f484185495a04cfcc4657b4d05c267857ee8fa535f13dc6457e239f06","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"d406f366fb2215aa3a8eb497c0e52b5d21ce197bf4107e1a08f9c677b45a23ad","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"76b35404e9338502df3ef0ecdf498241c0a277e845e43f97175d039c87243c28","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"587aefe2e3f49e026d5d22396176b32256edbfe12ed8ad8f8ada480eaf94878f","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"2e0a8c9bfff260516ca06adc29f5953429980b4d07e4012f344d4852fcce2d7c","signature":"2cc743b624d6891f9275f11f76fedfe235af04641c806e7dc65e55740db4dd29"},{"version":"736097ddbb2903bef918bb3b5811ef1c9c5656f2a73bd39b22a91b9cc2525e50","impliedFormat":1},{"version":"4340936f4e937c452ae783514e7c7bbb7fc06d0c97993ff4865370d0962bb9cf","impliedFormat":1},{"version":"b70c7ea83a7d0de17a791d9b5283f664033a96362c42cc4d2b2e0bdaa65ef7d1","impliedFormat":1},{"version":"6fa90b705a01002f5ad698417243165eab6cf568d0b2586c2041dd807515c61e","affectsGlobalScope":true,"impliedFormat":1},{"version":"420845f2661ac73433cbdc45f36d1f7ca7ea4eca60c3cbd077adf3355387cb63","impliedFormat":99}],"root":[[414,416],[435,445],[689,707]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":1,"module":99,"skipLibCheck":true,"strict":true},"referencedMap":[[703,1],[704,2],[705,3],[706,4],[707,5],[701,6],[702,7],[414,8],[415,9],[421,10],[418,10],[419,11],[417,10],[423,12],[424,13],[434,14],[426,15],[428,16],[425,13],[431,17],[429,18],[430,19],[432,20],[433,21],[427,22],[420,23],[422,24],[367,10],[709,10],[710,25],[144,26],[145,26],[146,27],[99,28],[147,29],[148,30],[149,31],[94,10],[97,32],[95,10],[96,10],[150,33],[151,34],[152,35],[153,36],[154,37],[155,38],[156,38],[157,39],[158,40],[159,41],[160,42],[100,10],[98,10],[161,43],[162,44],[163,45],[197,46],[164,47],[165,10],[166,48],[167,49],[168,50],[169,51],[170,52],[171,53],[172,54],[173,55],[174,56],[175,56],[176,57],[177,10],[178,58],[179,59],[181,60],[180,61],[182,62],[183,63],[184,64],[185,65],[186,66],[187,67],[188,68],[189,69],[190,70],[191,71],[192,72],[193,73],[194,74],[101,10],[102,10],[103,10],[141,75],[142,10],[143,10],[195,76],[196,77],[86,10],[202,78],[203,79],[201,80],[199,81],[200,82],[84,10],[87,83],[290,80],[711,10],[687,84],[688,85],[712,84],[684,86],[686,87],[555,88],[469,89],[554,90],[553,10],[556,91],[468,92],[557,10],[558,10],[559,93],[560,94],[561,94],[562,94],[563,93],[564,94],[567,95],[568,96],[565,10],[566,97],[569,98],[536,99],[457,100],[571,101],[572,102],[535,103],[573,104],[446,10],[450,105],[481,106],[574,10],[479,10],[480,10],[575,107],[576,108],[577,109],[451,110],[452,111],[447,10],[552,112],[551,113],[511,114],[500,10],[501,115],[590,10],[591,10],[592,116],[593,117],[594,118],[470,119],[471,120],[472,121],[473,122],[578,123],[580,124],[581,125],[582,126],[583,125],[589,127],[579,126],[584,126],[585,125],[586,126],[587,125],[588,126],[595,108],[596,108],[597,108],[599,128],[598,108],[601,129],[602,108],[603,130],[616,131],[604,129],[605,132],[606,129],[607,108],[600,108],[608,108],[609,133],[610,108],[611,129],[612,108],[613,108],[614,134],[615,108],[618,135],[620,136],[621,137],[622,138],[623,139],[626,140],[627,141],[629,142],[630,143],[633,144],[634,136],[636,145],[637,146],[638,147],[625,148],[624,149],[628,150],[514,151],[640,152],[513,153],[632,154],[631,155],[641,147],[643,156],[642,157],[646,158],[647,159],[648,160],[649,10],[650,161],[651,162],[652,163],[653,159],[654,159],[655,159],[645,164],[656,10],[644,165],[657,166],[658,167],[659,168],[487,169],[488,170],[547,171],[507,172],[489,173],[490,174],[491,175],[492,176],[493,177],[494,178],[495,176],[497,179],[496,176],[498,177],[499,169],[504,180],[503,181],[505,182],[506,169],[518,117],[478,183],[459,184],[458,185],[460,186],[454,187],[509,188],[464,10],[474,189],[661,190],[662,10],[449,191],[455,192],[476,193],[453,194],[550,195],[475,196],[461,186],[639,186],[477,197],[448,198],[462,199],[456,200],[465,201],[466,201],[467,201],[660,201],[663,202],[463,203],[482,203],[664,204],[666,102],[617,205],[665,206],[619,206],[537,207],[667,205],[549,208],[635,209],[508,210],[676,211],[677,212],[570,213],[512,214],[540,215],[668,10],[669,216],[502,217],[670,218],[541,219],[542,220],[671,221],[522,222],[543,223],[544,224],[672,225],[523,10],[673,226],[674,10],[530,227],[545,228],[532,10],[529,229],[546,230],[524,10],[531,231],[675,10],[533,232],[525,233],[527,234],[528,235],[526,236],[538,237],[678,238],[539,239],[515,240],[516,240],[517,241],[679,118],[680,242],[681,242],[483,243],[484,118],[520,244],[521,244],[548,244],[510,118],[682,118],[485,10],[486,245],[683,118],[519,10],[685,10],[534,10],[85,10],[708,246],[93,247],[370,248],[374,249],[376,250],[223,251],[237,252],[341,253],[269,10],[344,254],[305,255],[314,256],[342,257],[224,258],[268,10],[270,259],[343,260],[244,261],[225,262],[249,261],[238,261],[208,261],[296,263],[297,264],[213,10],[293,265],[298,266],[385,267],[291,266],[386,268],[275,10],[294,269],[398,270],[397,271],[300,266],[396,10],[394,10],[395,272],[295,80],[282,273],[283,274],[292,275],[309,276],[310,277],[299,278],[277,279],[278,280],[389,281],[392,282],[256,283],[255,284],[254,285],[401,80],[253,286],[229,10],[404,10],[407,10],[406,80],[408,287],[204,10],[335,10],[236,288],[206,289],[358,10],[359,10],[361,10],[364,290],[360,10],[362,291],[363,291],[222,10],[235,10],[369,292],[377,293],[381,294],[218,295],[285,296],[284,10],[276,279],[304,297],[302,298],[301,10],[303,10],[308,299],[280,300],[217,301],[242,302],[332,303],[209,246],[216,304],[205,253],[346,305],[356,306],[345,10],[355,307],[243,10],[227,308],[323,309],[322,10],[329,310],[331,311],[324,312],[328,313],[330,310],[327,312],[326,310],[325,312],[265,314],[250,314],[317,315],[251,315],[211,316],[210,10],[321,317],[320,318],[319,319],[318,320],[212,321],[289,322],[306,323],[288,324],[313,325],[315,326],[312,324],[245,321],[198,10],[333,327],[271,328],[307,10],[354,329],[274,330],[349,331],[215,10],[350,332],[352,333],[353,334],[336,10],[348,246],[247,335],[334,336],[357,337],[219,10],[221,10],[226,338],[316,339],[214,340],[220,10],[273,341],[272,342],[228,343],[281,344],[279,345],[230,346],[232,347],[405,10],[231,348],[233,349],[372,10],[371,10],[373,10],[403,10],[234,350],[287,80],[92,10],[311,351],[257,10],[267,352],[246,10],[379,80],[388,353],[264,80],[383,266],[263,354],[366,355],[262,353],[207,10],[390,356],[260,80],[261,80],[252,10],[266,10],[259,357],[258,358],[248,359],[241,278],[351,10],[240,360],[239,10],[375,10],[286,80],[368,361],[83,10],[91,362],[88,80],[89,10],[90,10],[347,363],[340,364],[339,10],[338,365],[337,10],[378,366],[380,367],[382,368],[384,369],[387,370],[413,371],[391,371],[412,372],[393,373],[399,374],[400,375],[402,376],[409,377],[411,10],[410,378],[365,379],[81,10],[82,10],[13,10],[14,10],[16,10],[15,10],[2,10],[17,10],[18,10],[19,10],[20,10],[21,10],[22,10],[23,10],[24,10],[3,10],[25,10],[26,10],[4,10],[27,10],[31,10],[28,10],[29,10],[30,10],[32,10],[33,10],[34,10],[5,10],[35,10],[36,10],[37,10],[38,10],[6,10],[42,10],[39,10],[40,10],[41,10],[43,10],[7,10],[44,10],[49,10],[50,10],[45,10],[46,10],[47,10],[48,10],[8,10],[54,10],[51,10],[52,10],[53,10],[55,10],[9,10],[56,10],[57,10],[58,10],[60,10],[59,10],[61,10],[62,10],[10,10],[63,10],[64,10],[65,10],[11,10],[66,10],[67,10],[68,10],[69,10],[70,10],[1,10],[71,10],[72,10],[12,10],[76,10],[74,10],[79,10],[78,10],[73,10],[77,10],[75,10],[80,10],[119,380],[129,381],[118,380],[139,382],[110,383],[109,384],[138,378],[132,385],[137,386],[112,387],[126,388],[111,389],[135,390],[107,391],[106,378],[136,392],[108,393],[113,394],[114,10],[117,394],[104,10],[140,395],[130,396],[121,397],[122,398],[124,399],[120,400],[123,401],[133,378],[115,402],[116,403],[125,404],[105,405],[128,396],[127,394],[131,10],[134,406],[437,407],[438,407],[440,408],[441,409],[700,410],[442,9],[443,411],[699,412],[690,413],[444,414],[445,10],[689,415],[693,10],[691,10],[698,414],[697,416],[694,417],[692,418],[696,419],[695,419],[439,414],[436,420],[435,414],[416,10]],"affectedFilesPendingEmit":[703,704,705,706,707,701,702,415,437,438,440,441,700,442,443,699,690,444,445,689,693,691,698,697,694,692,696,695,439,436,435,416],"version":"5.9.3"} \ No newline at end of file