← back to StudentLoanTracker
initial scaffold (gitify-all 2026-05-06)
0a30a500f83ed1fe5bdf4ebae0e3600737aa2611 · 2026-05-06 10:25:13 -0700 · Steve Abrams
Files touched
A .gitignoreA CLAUDE.mdA eslint.config.mjsA lincrusta-price-list.pdfA lincrusta-reprice.jsA migrations/001_initial_schema.sqlA next.config.tsA package-lock.jsonA package.jsonA postcss.config.mjsA public/StudentLoanTracker-Narrated-Walkthrough.mp4A public/favicon.svgA public/file.svgA public/globe.svgA public/next.svgA public/vercel.svgA public/window.svgA src/app/admin/page.tsxA src/app/api/admin/route.tsA src/app/api/calculators/route.tsA src/app/api/loans/route.tsA src/app/api/servicers/route.tsA src/app/calculator/page.tsxA src/app/dashboard/page.tsxA src/app/disclaimer/page.tsxA src/app/favicon.icoA src/app/globals.cssA src/app/layout.tsxA src/app/loans/page.tsxA src/app/page.tsxA src/app/privacy/page.tsxA src/app/pslf/page.tsxA src/app/servicers/page.tsxA src/app/summer-aid/page.tsxA src/app/terms/page.tsxA src/components/layout/Footer.tsxA src/components/layout/Navbar.tsxA src/components/ui/.gitkeepA src/lib/calculators/comparison.tsA src/lib/calculators/ibr-calculator.tsA src/lib/calculators/icr-calculator.tsA src/lib/calculators/index.tsA src/lib/calculators/paye-calculator.tsA src/lib/calculators/poverty-guidelines.tsA src/lib/calculators/pslf-calculator.tsA src/lib/calculators/save-calculator.tsA src/lib/calculators/types.tsA src/lib/db.tsA src/lib/file-intel/index.tsA test/sample-loans.csvA test/sample-servicer-statement.txtA test/sample-studentaid.txtA tsconfig.jsonA video/StudentLoanTracker-Narrated-Walkthrough.mp4A video/audio/01-home.mp3A video/audio/02-calculator.mp3A video/audio/03-pslf.mp3A video/audio/04-loans.mp3A video/audio/05-servicers.mp3A video/audio/06-summer.mp3A video/concat.txtA video/narration.jsonA video/screens/01-home.pngA video/screens/02-calculator.pngA video/screens/03-pslf.pngA video/screens/04-loans.pngA video/screens/05-servicers.pngA video/screens/06-summer.pngA video/segments/01-home.mp4A video/segments/02-calculator.mp4A video/segments/03-pslf.mp4A video/segments/04-loans.mp4A video/segments/05-servicers.mp4A video/segments/06-summer.mp4
Diff
commit 0a30a500f83ed1fe5bdf4ebae0e3600737aa2611
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 6 10:25:13 2026 -0700
initial scaffold (gitify-all 2026-05-06)
---
.gitignore | 50 +
CLAUDE.md | 30 +
eslint.config.mjs | 18 +
lincrusta-price-list.pdf | Bin 0 -> 119582 bytes
lincrusta-reprice.js | 177 +
migrations/001_initial_schema.sql | 123 +
next.config.ts | 7 +
package-lock.json | 7273 ++++++++++++++++++++
package.json | 32 +
postcss.config.mjs | 7 +
public/StudentLoanTracker-Narrated-Walkthrough.mp4 | Bin 0 -> 3360880 bytes
public/favicon.svg | 4 +
public/file.svg | 1 +
public/globe.svg | 1 +
public/next.svg | 1 +
public/vercel.svg | 1 +
public/window.svg | 1 +
src/app/admin/page.tsx | 58 +
src/app/api/admin/route.ts | 37 +
src/app/api/calculators/route.ts | 30 +
src/app/api/loans/route.ts | 33 +
src/app/api/servicers/route.ts | 55 +
src/app/calculator/page.tsx | 346 +
src/app/dashboard/page.tsx | 59 +
src/app/disclaimer/page.tsx | 57 +
src/app/favicon.ico | Bin 0 -> 25931 bytes
src/app/globals.css | 29 +
src/app/layout.tsx | 40 +
src/app/loans/page.tsx | 395 ++
src/app/page.tsx | 133 +
src/app/privacy/page.tsx | 57 +
src/app/pslf/page.tsx | 350 +
src/app/servicers/page.tsx | 126 +
src/app/summer-aid/page.tsx | 151 +
src/app/terms/page.tsx | 51 +
src/components/layout/Footer.tsx | 53 +
src/components/layout/Navbar.tsx | 72 +
src/components/ui/.gitkeep | 0
src/lib/calculators/comparison.ts | 76 +
src/lib/calculators/ibr-calculator.ts | 84 +
src/lib/calculators/icr-calculator.ts | 84 +
src/lib/calculators/index.ts | 17 +
src/lib/calculators/paye-calculator.ts | 80 +
src/lib/calculators/poverty-guidelines.ts | 64 +
src/lib/calculators/pslf-calculator.ts | 116 +
src/lib/calculators/save-calculator.ts | 105 +
src/lib/calculators/types.ts | 69 +
src/lib/db.ts | 8 +
src/lib/file-intel/index.ts | 609 ++
test/sample-loans.csv | 8 +
test/sample-servicer-statement.txt | 173 +
test/sample-studentaid.txt | 648 ++
tsconfig.json | 34 +
video/StudentLoanTracker-Narrated-Walkthrough.mp4 | Bin 0 -> 3360880 bytes
video/audio/01-home.mp3 | Bin 0 -> 141984 bytes
video/audio/02-calculator.mp3 | Bin 0 -> 200736 bytes
video/audio/03-pslf.mp3 | Bin 0 -> 193680 bytes
video/audio/04-loans.mp3 | Bin 0 -> 163152 bytes
video/audio/05-servicers.mp3 | Bin 0 -> 137088 bytes
video/audio/06-summer.mp3 | Bin 0 -> 149184 bytes
video/concat.txt | 6 +
video/narration.json | 26 +
video/screens/01-home.png | Bin 0 -> 48546 bytes
video/screens/02-calculator.png | Bin 0 -> 54260 bytes
video/screens/03-pslf.png | Bin 0 -> 75490 bytes
video/screens/04-loans.png | Bin 0 -> 52223 bytes
video/screens/05-servicers.png | Bin 0 -> 63250 bytes
video/screens/06-summer.png | Bin 0 -> 79701 bytes
video/segments/01-home.mp4 | Bin 0 -> 443599 bytes
video/segments/02-calculator.mp4 | Bin 0 -> 659800 bytes
video/segments/03-pslf.mp4 | Bin 0 -> 674826 bytes
video/segments/04-loans.mp4 | Bin 0 -> 535160 bytes
video/segments/05-servicers.mp4 | Bin 0 -> 503869 bytes
video/segments/06-summer.mp4 | Bin 0 -> 549726 bytes
74 files changed, 12065 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f522159
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,50 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+node_modules/
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+dist/
+build/
+*.bak
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..25b0ca7
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,30 @@
+# StudentLoanTracker
+
+## Overview
+Free student loan repayment calculator, PSLF tracker, servicer directory, and summer aid planner.
+
+## Infrastructure
+- **Port**: 7450
+- **PM2**: student-loan-tracker
+- **DB**: PostgreSQL schema `slt` in dw_unified
+- **Auth**: admin/DWSecure2024! (admin), Supabase Auth (users)
+
+## Tech Stack
+- Next.js 16 + TypeScript + Tailwind CSS
+- PostgreSQL (schema: slt)
+- Client-side PDF.js + Tesseract.js for file intelligence
+- Supabase Auth for optional accounts
+
+## Modules
+1. Calculator (SAVE/IBR/PAYE/ICR/comparison)
+2. Loan Intake (manual + StudentAid TXT upload)
+3. PSLF Progress Tracker
+4. Servicer Directory
+5. Summer Aid Planner
+6. Admin Panel
+
+## PRIVACY
+- Guest mode: no login required, no data stored
+- Saved mode: optional account, encrypted storage
+- Files: process in browser then discard (unless user opts in)
+- NEVER expose PII in logs, URLs, or analytics
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..05e726d
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,18 @@
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+import nextTs from "eslint-config-next/typescript";
+
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ ...nextTs,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
+
+export default eslintConfig;
diff --git a/lincrusta-price-list.pdf b/lincrusta-price-list.pdf
new file mode 100644
index 0000000..913269c
Binary files /dev/null and b/lincrusta-price-list.pdf differ
diff --git a/lincrusta-reprice.js b/lincrusta-reprice.js
new file mode 100644
index 0000000..0bec2bf
--- /dev/null
+++ b/lincrusta-reprice.js
@@ -0,0 +1,177 @@
+/**
+ * Lincrusta Price Update Script
+ * Master Price List 2026 — EX WORKS prices
+ * Formula: trade_price / 0.65 / 0.85 = retail price
+ * Sample variant always $4.25
+ */
+
+const https = require('https');
+const TOKEN = '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';
+
+// PDF Price List 2026 — mapped by RD/WM item number
+const PRICE_LIST = {
+ // SIDEWALL
+ 'RD1583FR': { name: 'ELIZABETH', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1650FR': { name: 'GEORGIAN PANEL', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1805FR': { name: 'CRICHTON', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1827FR': { name: 'LINENFOLD', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1843FR': { name: 'SEA GRASS MATTING', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1860FR': { name: 'CORDAGE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1873FR': { name: 'CARPRICE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1888FR': { name: 'SOPHIA', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1893FR': { name: 'CHEQUERS', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1902FR': { name: 'CANE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1903FR': { name: 'VILLA LOUIS', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1952FR': { name: 'ITALIAN RENAISSANCE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1954FR': { name: 'BYZANTINE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1956FR': { name: 'AMELIA', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1960FR': { name: 'ACANTHUS', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1962FR': { name: 'CLEOPATRA', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1963FR': { name: 'APHRODITE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1968FR': { name: 'KELMSCOTT', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1969FR': { name: 'ROCCO', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1970FR': { name: 'NEO', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1971FR': { name: 'PASSERI', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1972FR': { name: 'TAPESTRY', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1975FR': { name: 'FANFARE', type: 'SIDEWALL', cost: 355.20 },
+ 'RD1976FR': { name: 'TROPICAL LEAF', type: 'SIDEWALL', cost: 375.77 },
+ 'RD1978FR': { name: 'PALM', type: 'SIDEWALL', cost: 375.77 },
+ 'RD1979FR': { name: 'TRELLIS', type: 'SIDEWALL', cost: 375.77 },
+ 'WM1980FR': { name: 'WILLIAM MORRIS ACANTHUS', type: 'SIDEWALL', cost: 413.35 },
+ 'WM1981FR': { name: 'WILLIAM MORRIS FRUIT', type: 'SIDEWALL', cost: 413.35 },
+ // FRIEZE
+ 'RD1946FR': { name: 'FRANCESCA', type: 'FRIEZE', cost: 250.36 },
+ 'RD1947FR': { name: 'ANNE', type: 'FRIEZE', cost: 250.36 },
+ 'RD1948FR': { name: 'CAMEO', type: 'FRIEZE', cost: 250.36 },
+ 'RD1949FR': { name: 'ADELPHI', type: 'FRIEZE', cost: 250.36 },
+ 'RD1955FR': { name: 'ADAM', type: 'FRIEZE', cost: 355.20 },
+ 'RD1957FR': { name: 'EMPIRE', type: 'FRIEZE', cost: 267.15 },
+ 'RD1958FR': { name: 'DIANA', type: 'FRIEZE', cost: 267.15 },
+ // DADO
+ 'RD1964FR': { name: 'EDWARDIAN', type: 'DADO', cost: 355.20 },
+ 'RD1965FR': { name: 'ART NOUVEAU', type: 'DADO', cost: 355.20 },
+ 'RD1966FR': { name: 'SEVILLE', type: 'DADO', cost: 355.20 },
+ 'RD1967FR': { name: 'GOTHIC', type: 'DADO', cost: 355.20 },
+ 'WM1982FR': { name: 'WILLOW BOUGHS DADO', type: 'DADO', cost: 413.35 },
+ // BORDER
+ 'RD1639FR': { name: 'BORDER', type: 'BORDER', cost: 67.64 },
+ 'RD1640FR': { name: 'BORDER', type: 'BORDER', cost: 67.64 },
+ 'RD1953FR': { name: 'BORDER', type: 'BORDER', cost: 67.64 },
+ // ACCESSORY
+ 'RDADH10': { name: 'ADHESIVE - 1 LITRE', type: 'ACCESSORY', cost: 17.67 },
+ 'RDADH50': { name: 'ADHESIVE - 5 LITRE', type: 'ACCESSORY', cost: 64.93 },
+ 'RDDEG05': { name: 'SURFACE DEGREASER - 500ML', type: 'ACCESSORY', cost: 14.13 },
+ 'RDPRM10': { name: 'BONDING PRIMER - 1L', type: 'ACCESSORY', cost: 26.72 },
+ 'RDPRM25': { name: 'BONDING PRIMER - 2.5L', type: 'ACCESSORY', cost: 62.01 },
+};
+
+// Shopify product ID → RD number mapping (from GraphQL query)
+const PRODUCTS = {
+ 'RD1583FR': '7506087542835', 'RD1650FR': '7506087673907', 'RD1805FR': '7506087772211',
+ 'RD1827FR': '7506087870515', 'RD1843FR': '7506087968819', 'RD1860FR': '7506088067123',
+ 'RD1873FR': '7506088165427', 'RD1888FR': '7506088263731', 'RD1893FR': '7506088329267',
+ 'RD1902FR': '7506088427571', 'RD1903FR': '7506088493107', 'RD1952FR': '7506088591411',
+ 'RD1954FR': '7506088656947', 'RD1956FR': '7506088722483', 'RD1960FR': '7506088820787',
+ 'RD1962FR': '7506088886323', 'RD1963FR': '7506088984627', 'RD1968FR': '7506089082931',
+ 'RD1969FR': '7506089148467', 'RD1970FR': '7506089246771', 'RD1971FR': '7506089312307',
+ 'RD1972FR': '7506089410611', 'RD1975FR': '7506089508915', 'RD1976FR': '7506089607219',
+ 'RD1978FR': '7506089672755', 'RD1979FR': '7506089803827', 'WM1980FR': '7506089836595',
+ 'WM1981FR': '7506089902131', 'RD1946FR': '7506090000435', 'RD1947FR': '7506090131507',
+ 'RD1948FR': '7506090229811', 'RD1949FR': '7506090328115', 'RD1955FR': '7506090459187',
+ 'RD1957FR': '7506090524723', 'RD1958FR': '7506090623027', 'RD1964FR': '7506090688563',
+ 'RD1965FR': '7506090786867', 'RD1966FR': '7506090885171', 'RD1967FR': '7506091016243',
+ 'WM1982FR': '7506091081779', 'RD1639FR': '7506091147315', 'RD1640FR': '7506091245619',
+ 'RD1953FR': '7506091343923', 'RDADH10': '7506268815411', 'RDADH50': '7506268946483',
+ 'RDDEG05': '7506269044787', 'RDPRM10': '7506269110323', 'RDPRM25': '7506269208627',
+};
+
+function calcRetail(cost) {
+ return Math.round((cost / 0.65 / 0.85) * 100) / 100;
+}
+
+function shopifyRequest(method, path, body) {
+ return new Promise((resolve, reject) => {
+ const data = body ? JSON.stringify(body) : '';
+ const req = https.request({
+ hostname: 'designer-laboratory-sandbox.myshopify.com',
+ path: '/admin/api/2024-01' + path,
+ method,
+ headers: {
+ 'X-Shopify-Access-Token': TOKEN,
+ 'Content-Type': 'application/json',
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
+ },
+ }, (res) => {
+ let body = '';
+ res.on('data', d => body += d);
+ res.on('end', () => {
+ try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
+ catch { resolve({ status: res.statusCode, data: body }); }
+ });
+ });
+ req.on('error', reject);
+ if (data) req.write(data);
+ req.end();
+ });
+}
+
+async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
+
+(async () => {
+ let updated = 0, failed = 0, skipped = 0;
+
+ for (const [rdNum, priceData] of Object.entries(PRICE_LIST)) {
+ const productId = PRODUCTS[rdNum];
+ if (!productId) {
+ console.log(`SKIP: ${rdNum} — no Shopify product found`);
+ skipped++;
+ continue;
+ }
+
+ const retail = calcRetail(priceData.cost);
+
+ // Get current variants
+ const res = await shopifyRequest('GET', `/products/${productId}.json?fields=id,title,variants`);
+ if (res.status !== 200) {
+ console.log(`FAIL: ${rdNum} — HTTP ${res.status}`);
+ failed++;
+ await sleep(800);
+ continue;
+ }
+
+ const product = res.data.product;
+ const variants = product.variants || [];
+
+ // Update each variant price
+ for (const v of variants) {
+ const isSample = (v.sku || '').toLowerCase().includes('sample') || (v.title || '').toLowerCase().includes('sample');
+ const newPrice = isSample ? '4.25' : retail.toFixed(2);
+
+ const vRes = await shopifyRequest('PUT', `/variants/${v.id}.json`, {
+ variant: { id: v.id, price: newPrice }
+ });
+
+ if (vRes.status === 200) {
+ process.stdout.write('.');
+ } else {
+ process.stdout.write('x');
+ failed++;
+ }
+ await sleep(900);
+ }
+
+ updated++;
+ }
+
+ console.log(`\n\nDone! Updated: ${updated}, Failed: ${failed}, Skipped: ${skipped}`);
+
+ // Print price summary
+ console.log('\nPrice Summary (cost / 0.65 / 0.85):');
+ const tiers = new Set();
+ for (const p of Object.values(PRICE_LIST)) {
+ tiers.add(p.cost);
+ }
+ for (const cost of [...tiers].sort()) {
+ console.log(` Cost $${cost.toFixed(2)} → Retail $${calcRetail(cost).toFixed(2)}`);
+ }
+})();
diff --git a/migrations/001_initial_schema.sql b/migrations/001_initial_schema.sql
new file mode 100644
index 0000000..665c1a0
--- /dev/null
+++ b/migrations/001_initial_schema.sql
@@ -0,0 +1,123 @@
+-- StudentLoanTracker Initial Schema Migration
+-- Created: 2026-03-25
+-- Database: dw_unified, Schema: slt
+
+CREATE SCHEMA IF NOT EXISTS slt;
+
+-- Users (synced from Supabase Auth or local)
+CREATE TABLE IF NOT EXISTS slt.users (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ email TEXT,
+ phone TEXT,
+ created_at TIMESTAMPTZ DEFAULT NOW(),
+ sms_consent BOOLEAN DEFAULT FALSE,
+ sms_consent_at TIMESTAMPTZ,
+ preferences JSONB DEFAULT '{}'
+);
+
+-- Loan records (manual entry or parsed from TXT)
+CREATE TABLE IF NOT EXISTS slt.loans (
+ id SERIAL PRIMARY KEY,
+ user_id UUID REFERENCES slt.users(id),
+ session_id TEXT,
+ loan_type TEXT,
+ servicer TEXT,
+ original_balance NUMERIC(12,2),
+ current_balance NUMERIC(12,2),
+ interest_rate NUMERIC(5,3),
+ disbursement_date DATE,
+ status TEXT,
+ repayment_plan TEXT,
+ raw_data JSONB,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Calculator results (snapshots)
+CREATE TABLE IF NOT EXISTS slt.calc_results (
+ id SERIAL PRIMARY KEY,
+ user_id UUID REFERENCES slt.users(id),
+ session_id TEXT,
+ calc_type TEXT,
+ inputs JSONB,
+ outputs JSONB,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- PSLF tracking
+CREATE TABLE IF NOT EXISTS slt.pslf_progress (
+ id SERIAL PRIMARY KEY,
+ user_id UUID REFERENCES slt.users(id),
+ qualifying_payments INT DEFAULT 0,
+ employer_type TEXT,
+ employer_name TEXT,
+ current_plan TEXT,
+ start_date DATE,
+ notes TEXT,
+ updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Document uploads (metadata only)
+CREATE TABLE IF NOT EXISTS slt.documents (
+ id SERIAL PRIMARY KEY,
+ user_id UUID REFERENCES slt.users(id),
+ session_id TEXT,
+ filename TEXT,
+ mime_type TEXT,
+ classification TEXT,
+ confidence NUMERIC(3,2),
+ extracted_data JSONB,
+ retained BOOLEAN DEFAULT FALSE,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Servicer directory (admin-managed)
+CREATE TABLE IF NOT EXISTS slt.servicers (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ phone TEXT,
+ website TEXT,
+ official BOOLEAN DEFAULT TRUE,
+ notes TEXT,
+ last_verified DATE,
+ updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Seed official servicers
+INSERT INTO slt.servicers (name, phone, website, official, last_verified, notes) VALUES
+ ('Edfinancial', '1-855-337-6884', 'https://edfinancial.com', true, '2026-03-25', 'Official federal servicer'),
+ ('MOHELA', '1-888-866-4352', 'https://www.mohela.com', true, '2026-03-25', 'Official federal servicer, commonly handles PSLF'),
+ ('Aidvantage', '1-800-722-1300', 'https://aidvantage.com', true, '2026-03-25', 'Official federal servicer'),
+ ('Nelnet', '1-888-486-4722', 'https://www.nelnet.com', true, '2026-03-25', 'Official federal servicer')
+ON CONFLICT DO NOTHING;
+
+-- SMS/email communications log
+CREATE TABLE IF NOT EXISTS slt.comms_log (
+ id SERIAL PRIMARY KEY,
+ user_id UUID REFERENCES slt.users(id),
+ channel TEXT,
+ direction TEXT,
+ content_summary TEXT,
+ status TEXT,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Admin audit log
+CREATE TABLE IF NOT EXISTS slt.audit_log (
+ id SERIAL PRIMARY KEY,
+ admin_user TEXT,
+ action TEXT,
+ target_table TEXT,
+ target_id TEXT,
+ details JSONB,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_loans_user ON slt.loans(user_id);
+CREATE INDEX IF NOT EXISTS idx_loans_session ON slt.loans(session_id);
+CREATE INDEX IF NOT EXISTS idx_calc_results_user ON slt.calc_results(user_id);
+CREATE INDEX IF NOT EXISTS idx_calc_results_session ON slt.calc_results(session_id);
+CREATE INDEX IF NOT EXISTS idx_pslf_user ON slt.pslf_progress(user_id);
+CREATE INDEX IF NOT EXISTS idx_docs_user ON slt.documents(user_id);
+CREATE INDEX IF NOT EXISTS idx_comms_user ON slt.comms_log(user_id);
+CREATE INDEX IF NOT EXISTS idx_audit_created ON slt.audit_log(created_at);
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000..33c9a58
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from 'next';
+
+const nextConfig: NextConfig = {
+ turbopack: {},
+};
+
+export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..28e45e0
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,7273 @@
+{
+ "name": "student-loan-tracker",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "student-loan-tracker",
+ "version": "0.1.0",
+ "dependencies": {
+ "@supabase/supabase-js": "^2.100.0",
+ "lucide-react": "^1.6.0",
+ "next": "16.2.1",
+ "pdfjs-dist": "^5.5.207",
+ "pg": "^8.20.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "tesseract.js": "^7.0.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/pg": "^8.20.0",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.1",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
+ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
+ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/canvas": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.97.tgz",
+ "integrity": "sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==",
+ "license": "MIT",
+ "optional": true,
+ "workspaces": [
+ "e2e/*"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas-android-arm64": "0.1.97",
+ "@napi-rs/canvas-darwin-arm64": "0.1.97",
+ "@napi-rs/canvas-darwin-x64": "0.1.97",
+ "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.97",
+ "@napi-rs/canvas-linux-arm64-gnu": "0.1.97",
+ "@napi-rs/canvas-linux-arm64-musl": "0.1.97",
+ "@napi-rs/canvas-linux-riscv64-gnu": "0.1.97",
+ "@napi-rs/canvas-linux-x64-gnu": "0.1.97",
+ "@napi-rs/canvas-linux-x64-musl": "0.1.97",
+ "@napi-rs/canvas-win32-arm64-msvc": "0.1.97",
+ "@napi-rs/canvas-win32-x64-msvc": "0.1.97"
+ }
+ },
+ "node_modules/@napi-rs/canvas-android-arm64": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.97.tgz",
+ "integrity": "sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-arm64": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.97.tgz",
+ "integrity": "sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-x64": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.97.tgz",
+ "integrity": "sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.97.tgz",
+ "integrity": "sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.97.tgz",
+ "integrity": "sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-musl": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.97.tgz",
+ "integrity": "sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.97.tgz",
+ "integrity": "sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-gnu": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.97.tgz",
+ "integrity": "sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-musl": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.97.tgz",
+ "integrity": "sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-win32-arm64-msvc": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.97.tgz",
+ "integrity": "sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/canvas-win32-x64-msvc": {
+ "version": "0.1.97",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.97.tgz",
+ "integrity": "sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.1.tgz",
+ "integrity": "sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==",
+ "license": "MIT"
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.1.tgz",
+ "integrity": "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "3.3.1"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.1.tgz",
+ "integrity": "sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.1.tgz",
+ "integrity": "sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.1.tgz",
+ "integrity": "sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.1.tgz",
+ "integrity": "sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.1.tgz",
+ "integrity": "sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.1.tgz",
+ "integrity": "sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.1.tgz",
+ "integrity": "sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.1.tgz",
+ "integrity": "sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.4.0"
+ }
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@supabase/auth-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz",
+ "integrity": "sha512-pdT3ye3UVRN1Cg0wom6BmyY+XTtp5DiJaYnPi6j8ht5i8Lq8kfqxJMJz9GI9YDKk3w1nhGOPnh6Qz5qpyYm+1w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.0.tgz",
+ "integrity": "sha512-keLg79RPwP+uiwHuxFPTFgDRxPV46LM4j/swjyR2GKJgWniTVSsgiBHfbIBDcrQwehLepy09b/9QSHUywtKRWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/phoenix": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz",
+ "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==",
+ "license": "MIT"
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.0.tgz",
+ "integrity": "sha512-xYNvNbBJaXOGcrZ44wxwp5830uo1okMHGS8h8dm3u4f0xcZ39yzbryUsubTJW41MG2gbL/6U57cA4Pi6YMZ9pA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.0.tgz",
+ "integrity": "sha512-2AZs00zzEF0HuCKY8grz5eCYlwEfVi5HONLZFoNR6aDfxQivl8zdQYNjyFoqN2MZiVhQHD7u6XV/xHwM8mCEHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/phoenix": "^0.4.0",
+ "@types/ws": "^8.18.1",
+ "tslib": "2.8.1",
+ "ws": "^8.18.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.0.tgz",
+ "integrity": "sha512-d4EeuK6RNIgYNA2MU9kj8lQrLm5AzZ+WwpWjGkii6SADQNIGTC/uiaTRu02XJ5AmFALQfo8fLl9xuCkO6Xw+iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "iceberg-js": "^0.8.1",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.100.0",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.0.tgz",
+ "integrity": "sha512-r0tlcukejJXJ1m/2eG/Ya5eYs4W8AC7oZfShpG3+SIo/eIU9uIt76ZeYI1SoUwUmcmzlAbgch+HDZDR/toVQPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.100.0",
+ "@supabase/functions-js": "2.100.0",
+ "@supabase/postgrest-js": "2.100.0",
+ "@supabase/realtime-js": "2.100.0",
+ "@supabase/storage-js": "2.100.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
+ "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
+ "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-x64": "4.2.2",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.2",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
+ "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
+ "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
+ "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
+ "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
+ "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
+ "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
+ "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
+ "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
+ "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
+ "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
+ "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
+ "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz",
+ "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.2.2",
+ "@tailwindcss/oxide": "4.2.2",
+ "postcss": "^8.5.6",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
+ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
+ "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/type-utils": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.57.2",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
+ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
+ "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.57.2",
+ "@typescript-eslint/types": "^8.57.2",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
+ "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
+ "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
+ "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
+ "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
+ "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.57.2",
+ "@typescript-eslint/tsconfig-utils": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
+ "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
+ "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.11.1",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
+ "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
+ "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bmp-js": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
+ "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001781",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
+ "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/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/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/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/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.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==",
+ "dev": true,
+ "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/electron-to-chromium": {
+ "version": "1.5.325",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz",
+ "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz",
+ "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.1",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.1.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.3.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.5",
+ "math-intrinsics": "^1.1.0",
+ "safe-array-concat": "^1.1.3"
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.1.tgz",
+ "integrity": "sha512-qhabwjQZ1Mk53XzXvmogf8KQ0tG0CQXF0CZ56+2/lVhmObgmaqj7x5A1DSrWdZd3kwI7GTPGUjFne+krRxYmFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@next/eslint-plugin-next": "16.2.1",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-jsx-a11y": "^6.10.0",
+ "eslint-plugin-react": "^7.37.0",
+ "eslint-plugin-react-hooks": "^7.0.0",
+ "globals": "16.4.0",
+ "typescript-eslint": "^8.46.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
+ "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@nolyfill/is-core-module": "1.0.39",
+ "debug": "^4.4.0",
+ "get-tsconfig": "^4.10.0",
+ "is-bun-module": "^2.0.0",
+ "stable-hash": "^0.0.5",
+ "tinyglobby": "^0.2.13",
+ "unrs-resolver": "^1.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-import-resolver-typescript"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*",
+ "eslint-plugin-import-x": "*"
+ },
+ "peerDependenciesMeta": {
+ "eslint-plugin-import": {
+ "optional": true
+ },
+ "eslint-plugin-import-x": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.1",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.16.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.1",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.9",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
+ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.9",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.6",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
+ "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "node-exports-info": "^1.6.0",
+ "object-keys": "^1.1.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/generator-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
+ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
+ "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/iceberg-js": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/idb-keyval": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz",
+ "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bun-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
+ "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.7.1"
+ }
+ },
+ "node_modules/is-bun-module/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
+ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.4",
+ "generator-function": "^2.0.0",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
+ "license": "MIT"
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.6.0.tgz",
+ "integrity": "sha512-YxLKVCOF5ZDI1AhKQE5IBYMY9y/Nr4NT15+7QEWpsTSVCdn4vmZhww+6BP76jWYjQx8rSz1Z+gGme1f+UycWEw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz",
+ "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "16.2.1",
+ "@swc/helpers": "0.5.15",
+ "baseline-browser-mapping": "^2.9.19",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "16.2.1",
+ "@next/swc-darwin-x64": "16.2.1",
+ "@next/swc-linux-arm64-gnu": "16.2.1",
+ "@next/swc-linux-arm64-musl": "16.2.1",
+ "@next/swc-linux-x64-gnu": "16.2.1",
+ "@next/swc-linux-x64-musl": "16.2.1",
+ "@next/swc-win32-arm64-msvc": "16.2.1",
+ "@next/swc-win32-x64-msvc": "16.2.1",
+ "sharp": "^0.34.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/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/node-exports-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
+ "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array.prototype.flatmap": "^1.3.3",
+ "es-errors": "^1.3.0",
+ "object.entries": "^1.1.9",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/node-readable-to-web-readable-stream": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz",
+ "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
+ "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/opencollective-postinstall": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
+ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
+ "license": "MIT",
+ "bin": {
+ "opencollective-postinstall": "index.js"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pdfjs-dist": {
+ "version": "5.5.207",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz",
+ "integrity": "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20.19.0 || >=22.13.0 || >=24"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas": "^0.1.95",
+ "node-readable-to-web-readable-stream": "^0.4.2"
+ }
+ },
+ "node_modules/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.12.0",
+ "pg-pool": "^3.13.0",
+ "pg-protocol": "^1.13.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.3.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+ "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+ "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+ "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+ "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "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/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT"
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/stable-hash": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
+ "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "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 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tesseract.js": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-7.0.0.tgz",
+ "integrity": "sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bmp-js": "^0.1.0",
+ "idb-keyval": "^6.2.0",
+ "is-url": "^1.2.4",
+ "node-fetch": "^2.6.9",
+ "opencollective-postinstall": "^2.0.3",
+ "regenerator-runtime": "^0.13.3",
+ "tesseract.js-core": "^7.0.0",
+ "wasm-feature-detect": "^1.8.0",
+ "zlibjs": "^0.3.1"
+ }
+ },
+ "node_modules/tesseract.js-core": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-7.0.0.tgz",
+ "integrity": "sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "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/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/typescript-eslint": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
+ "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.57.2",
+ "@typescript-eslint/parser": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/wasm-feature-detect": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
+ "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==",
+ "license": "Apache-2.0"
+ },
+ "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"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
+ "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zlibjs": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
+ "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3509315
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "student-loan-tracker",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --port 7451",
+ "build": "next build",
+ "start": "next start --port 7451",
+ "lint": "eslint"
+ },
+ "dependencies": {
+ "@supabase/supabase-js": "^2.100.0",
+ "lucide-react": "^1.6.0",
+ "next": "16.2.1",
+ "pdfjs-dist": "^5.5.207",
+ "pg": "^8.20.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "tesseract.js": "^7.0.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/pg": "^8.20.0",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.1",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/public/StudentLoanTracker-Narrated-Walkthrough.mp4 b/public/StudentLoanTracker-Narrated-Walkthrough.mp4
new file mode 100644
index 0000000..2283f2f
Binary files /dev/null and b/public/StudentLoanTracker-Narrated-Walkthrough.mp4 differ
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..b3e6dbe
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+<rect width="32" height="32" rx="6" fill="#10b981"/>
+<text x="50%" y="55%" text-anchor="middle" dominant-baseline="middle" font-size="20" font-family="Apple Color Emoji, Segoe UI Emoji, sans-serif" fill="white">S</text>
+</svg>
\ No newline at end of file
diff --git a/public/file.svg b/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/public/file.svg
@@ -0,0 +1 @@
+<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/public/globe.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/public/window.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\ No newline at end of file
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
new file mode 100644
index 0000000..b3e76c0
--- /dev/null
+++ b/src/app/admin/page.tsx
@@ -0,0 +1,58 @@
+import { Lock, Users, Database, Activity } from 'lucide-react';
+
+export const metadata = {
+ title: 'Admin - StudentLoanTracker',
+ description: 'Admin panel for managing servicer data, analytics, and system health.',
+};
+
+export default function AdminPage() {
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl">
+ <div className="flex items-center gap-3 mb-6">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-red-500/10 text-red-600">
+ <Lock className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Admin Panel</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed mb-8">
+ Manage servicer directory data, view anonymous usage analytics, and
+ monitor system health. Requires admin credentials.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <Database className="w-8 h-8 text-red-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">Servicer Data</h3>
+ <p className="text-gray-600 text-sm">
+ Add, edit, or deactivate servicer entries. Update contact info,
+ supported plans, and operational status.
+ </p>
+ </div>
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <Users className="w-8 h-8 text-red-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">User Accounts</h3>
+ <p className="text-gray-600 text-sm">
+ View registered accounts (no PII exposed), manage roles, and
+ handle support requests.
+ </p>
+ </div>
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <Activity className="w-8 h-8 text-red-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">Analytics</h3>
+ <p className="text-gray-600 text-sm">
+ Anonymous usage metrics: calculator runs, popular plans,
+ servicer lookups, and system health.
+ </p>
+ </div>
+ </div>
+
+ <div className="mt-12 p-8 bg-gray-50 rounded-xl border border-gray-200 text-center">
+ <p className="text-gray-500 text-lg">
+ Admin authentication and panels coming soon.
+ </p>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/api/admin/route.ts b/src/app/api/admin/route.ts
new file mode 100644
index 0000000..ff47cfe
--- /dev/null
+++ b/src/app/api/admin/route.ts
@@ -0,0 +1,37 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+function isAuthorized(request: NextRequest): boolean {
+ const authHeader = request.headers.get('authorization');
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
+ return false;
+ }
+
+ const base64 = authHeader.slice(6);
+ const decoded = Buffer.from(base64, 'base64').toString('utf-8');
+ const [username, password] = decoded.split(':');
+
+ return username === 'admin' && password === process.env.ADMIN_PASSWORD;
+}
+
+export async function GET(request: NextRequest) {
+ if (!isAuthorized(request)) {
+ return NextResponse.json(
+ { success: false, error: 'Unauthorized' },
+ { status: 401, headers: { 'WWW-Authenticate': 'Basic realm="Admin"' } }
+ );
+ }
+
+ // TODO: Return admin dashboard data
+ // TODO: Anonymous usage stats, servicer count, account count, system health
+
+ return NextResponse.json({
+ success: true,
+ message: 'Admin API - system health and anonymous analytics',
+ stats: {
+ servicer_count: 0,
+ account_count: 0,
+ calculator_runs_today: 0,
+ uptime: process.uptime(),
+ },
+ });
+}
diff --git a/src/app/api/calculators/route.ts b/src/app/api/calculators/route.ts
new file mode 100644
index 0000000..4923c6a
--- /dev/null
+++ b/src/app/api/calculators/route.ts
@@ -0,0 +1,30 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ return NextResponse.json({
+ success: true,
+ message: 'Calculators API - returns available repayment plan calculators',
+ plans: ['SAVE', 'IBR', 'PAYE', 'ICR', 'Standard', 'Graduated', 'Extended'],
+ });
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+
+ // TODO: Validate input (loan balance, interest rate, income, family size, plan type)
+ // TODO: Run calculation using pure functions from src/lib/calculators/
+ // TODO: Return monthly payment, total cost, forgiveness amount, timeline
+
+ return NextResponse.json({
+ success: true,
+ message: 'Calculator endpoint - POST with loan and income data to get repayment estimates',
+ receivedFields: Object.keys(body),
+ });
+ } catch {
+ return NextResponse.json(
+ { success: false, error: 'Invalid request body' },
+ { status: 400 }
+ );
+ }
+}
diff --git a/src/app/api/loans/route.ts b/src/app/api/loans/route.ts
new file mode 100644
index 0000000..203fe78
--- /dev/null
+++ b/src/app/api/loans/route.ts
@@ -0,0 +1,33 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ // TODO: Require authentication
+ // TODO: Fetch saved loans from PostgreSQL (slt.loans table)
+
+ return NextResponse.json({
+ success: true,
+ message: 'Loans API - returns saved loan portfolio for authenticated users',
+ loans: [],
+ });
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+
+ // TODO: Require authentication
+ // TODO: Validate loan data (type, balance, rate, servicer, disbursement_date)
+ // TODO: Save to PostgreSQL (slt.loans table)
+
+ return NextResponse.json({
+ success: true,
+ message: 'Loans API - POST to save loan data (requires authentication)',
+ receivedFields: Object.keys(body),
+ });
+ } catch {
+ return NextResponse.json(
+ { success: false, error: 'Invalid request body' },
+ { status: 400 }
+ );
+ }
+}
diff --git a/src/app/api/servicers/route.ts b/src/app/api/servicers/route.ts
new file mode 100644
index 0000000..3883969
--- /dev/null
+++ b/src/app/api/servicers/route.ts
@@ -0,0 +1,55 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ // TODO: Query PostgreSQL (slt.servicers table) for active servicers
+ // TODO: Return name, phone, website, mailing address, supported plans
+
+ const placeholder = [
+ {
+ id: 1,
+ name: 'MOHELA',
+ phone: '1-888-866-4352',
+ website: 'https://www.mohela.com',
+ supports_pslf: true,
+ active: true,
+ },
+ {
+ id: 2,
+ name: 'Aidvantage',
+ phone: '1-800-848-0979',
+ website: 'https://www.aidvantage.com',
+ supports_pslf: false,
+ active: true,
+ },
+ {
+ id: 3,
+ name: 'Nelnet',
+ phone: '1-888-486-4722',
+ website: 'https://www.nelnet.com',
+ supports_pslf: false,
+ active: true,
+ },
+ {
+ id: 4,
+ name: 'ECSI (Heartland)',
+ phone: '1-888-549-3274',
+ website: 'https://www.ecsi.net',
+ supports_pslf: false,
+ active: true,
+ },
+ {
+ id: 5,
+ name: 'EdFinancial',
+ phone: '1-855-337-6884',
+ website: 'https://www.edfinancial.com',
+ supports_pslf: false,
+ active: true,
+ },
+ ];
+
+ return NextResponse.json({
+ success: true,
+ message: 'Servicer directory - federal student loan servicers',
+ servicers: placeholder,
+ });
+}
diff --git a/src/app/calculator/page.tsx b/src/app/calculator/page.tsx
new file mode 100644
index 0000000..9b18ef4
--- /dev/null
+++ b/src/app/calculator/page.tsx
@@ -0,0 +1,346 @@
+'use client';
+
+import { useState } from 'react';
+import { Calculator, ChevronDown, ChevronUp, Download, Info } from 'lucide-react';
+import {
+ compareAllPlans,
+ type LoanInputs,
+ type ComparisonResult,
+ type FilingStatus,
+} from '@/lib/calculators';
+
+const FILING_OPTIONS: { value: FilingStatus; label: string }[] = [
+ { value: 'single', label: 'Single' },
+ { value: 'married_filing_jointly', label: 'Married Filing Jointly' },
+ { value: 'married_filing_separately', label: 'Married Filing Separately' },
+ { value: 'head_of_household', label: 'Head of Household' },
+];
+
+const US_STATES = [
+ 'AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA',
+ 'KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ',
+ 'NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT',
+ 'VA','WA','WV','WI','WY','DC',
+];
+
+function formatCurrency(n: number): string {
+ return n.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
+}
+
+function formatMonths(m: number): string {
+ const years = Math.floor(m / 12);
+ const months = m % 12;
+ if (years === 0) return `${months}mo`;
+ if (months === 0) return `${years}yr`;
+ return `${years}yr ${months}mo`;
+}
+
+export default function CalculatorPage() {
+ const [inputs, setInputs] = useState<LoanInputs>({
+ totalBalance: 35000,
+ weightedRate: 0.055,
+ agi: 45000,
+ familySize: 1,
+ filingStatus: 'single',
+ state: 'CA',
+ });
+
+ const [result, setResult] = useState<ComparisonResult | null>(null);
+ const [showAdvanced, setShowAdvanced] = useState(false);
+ const [expandedPlan, setExpandedPlan] = useState<string | null>(null);
+
+ function handleCalculate() {
+ const comparison = compareAllPlans(inputs);
+ setResult(comparison);
+ }
+
+ function updateInput<K extends keyof LoanInputs>(key: K, value: LoanInputs[K]) {
+ setInputs((prev) => ({ ...prev, [key]: value }));
+ setResult(null); // Clear results when inputs change
+ }
+
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ {/* Header */}
+ <div className="max-w-3xl mb-10">
+ <div className="flex items-center gap-3 mb-4">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-teal-500/10 text-teal-600">
+ <Calculator className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Repayment Plan Comparison</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed">
+ Compare SAVE, IBR, PAYE, ICR, and Standard plans side-by-side.
+ All calculations run in your browser — nothing is sent to a server.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+ {/* Input Form */}
+ <div className="lg:col-span-1">
+ <div className="bg-white rounded-xl border border-gray-200 p-6 sticky top-6">
+ <h2 className="text-lg font-semibold mb-4">Your Information</h2>
+
+ <div className="space-y-4">
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Total Loan Balance
+ </label>
+ <div className="relative">
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">$</span>
+ <input
+ type="number"
+ value={inputs.totalBalance}
+ onChange={(e) => updateInput('totalBalance', Number(e.target.value))}
+ className="w-full pl-7 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ />
+ </div>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Interest Rate (%)
+ </label>
+ <input
+ type="number"
+ step="0.1"
+ value={(inputs.weightedRate * 100).toFixed(1)}
+ onChange={(e) => updateInput('weightedRate', Number(e.target.value) / 100)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ />
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Annual Income (AGI)
+ </label>
+ <div className="relative">
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">$</span>
+ <input
+ type="number"
+ value={inputs.agi}
+ onChange={(e) => updateInput('agi', Number(e.target.value))}
+ className="w-full pl-7 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ />
+ </div>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Family Size
+ </label>
+ <input
+ type="number"
+ min="1"
+ max="20"
+ value={inputs.familySize}
+ onChange={(e) => updateInput('familySize', Number(e.target.value))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ />
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Filing Status
+ </label>
+ <select
+ value={inputs.filingStatus}
+ onChange={(e) => updateInput('filingStatus', e.target.value as FilingStatus)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ >
+ {FILING_OPTIONS.map((o) => (
+ <option key={o.value} value={o.value}>{o.label}</option>
+ ))}
+ </select>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">State</label>
+ <select
+ value={inputs.state}
+ onChange={(e) => updateInput('state', e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ >
+ {US_STATES.map((s) => (
+ <option key={s} value={s}>{s}</option>
+ ))}
+ </select>
+ </div>
+
+ {/* Advanced Toggle */}
+ <button
+ onClick={() => setShowAdvanced(!showAdvanced)}
+ className="flex items-center gap-1 text-sm text-teal-600 hover:text-teal-700"
+ >
+ {showAdvanced ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
+ Advanced Options
+ </button>
+
+ {showAdvanced && (
+ <div className="space-y-4 pt-2 border-t border-gray-100">
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">
+ Spouse Income (if MFJ)
+ </label>
+ <div className="relative">
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">$</span>
+ <input
+ type="number"
+ value={inputs.spouseIncome || 0}
+ onChange={(e) => updateInput('spouseIncome', Number(e.target.value))}
+ className="w-full pl-7 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
+ />
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+
+ <button
+ onClick={handleCalculate}
+ className="w-full mt-6 px-6 py-3 bg-teal-500 hover:bg-teal-600 text-white font-semibold rounded-lg transition-colors"
+ >
+ Compare Plans
+ </button>
+ </div>
+ </div>
+
+ {/* Results */}
+ <div className="lg:col-span-2">
+ {!result ? (
+ <div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
+ <Calculator className="w-12 h-12 text-gray-300 mx-auto mb-4" />
+ <p className="text-gray-500 text-lg">
+ Enter your loan details and click “Compare Plans” to see results.
+ </p>
+ </div>
+ ) : (
+ <div className="space-y-6">
+ {/* Recommendations */}
+ <div className="bg-teal-50 border border-teal-200 rounded-xl p-6">
+ <h3 className="font-semibold text-teal-800 mb-3">Recommendations</h3>
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm">
+ <div>
+ <div className="text-teal-600 font-medium">Lowest Monthly</div>
+ <div className="text-teal-900 font-bold">{result.lowestMonthly}</div>
+ </div>
+ <div>
+ <div className="text-teal-600 font-medium">Lowest Total Cost</div>
+ <div className="text-teal-900 font-bold">{result.recommended}</div>
+ </div>
+ <div>
+ <div className="text-teal-600 font-medium">Fastest Payoff</div>
+ <div className="text-teal-900 font-bold">{result.fastestPayoff}</div>
+ </div>
+ </div>
+ </div>
+
+ {/* Plan Cards */}
+ {result.plans.map((plan) => {
+ const isExpanded = expandedPlan === plan.planName;
+ const isRecommended = plan.planName === result.recommended;
+
+ return (
+ <div
+ key={plan.planName}
+ className={`bg-white rounded-xl border ${
+ isRecommended ? 'border-teal-300 ring-1 ring-teal-200' : 'border-gray-200'
+ } overflow-hidden`}
+ >
+ <div className="p-6">
+ <div className="flex items-start justify-between mb-4">
+ <div>
+ <div className="flex items-center gap-2">
+ <h3 className="text-lg font-semibold">{plan.planName}</h3>
+ {isRecommended && (
+ <span className="px-2 py-0.5 bg-teal-100 text-teal-700 text-xs font-medium rounded-full">
+ Recommended
+ </span>
+ )}
+ </div>
+ </div>
+ <div className="text-right">
+ <div className="text-2xl font-bold text-[#0f1b2d]">
+ {formatCurrency(plan.monthlyPayment)}
+ </div>
+ <div className="text-sm text-gray-500">per month</div>
+ </div>
+ </div>
+
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm">
+ <div>
+ <div className="text-gray-500">Total Paid</div>
+ <div className="font-semibold">{formatCurrency(plan.totalPaid)}</div>
+ </div>
+ <div>
+ <div className="text-gray-500">Total Interest</div>
+ <div className="font-semibold">{formatCurrency(plan.totalInterest)}</div>
+ </div>
+ <div>
+ <div className="text-gray-500">Forgiven</div>
+ <div className="font-semibold">
+ {plan.forgivenessAmount > 0
+ ? formatCurrency(plan.forgivenessAmount)
+ : '—'}
+ </div>
+ </div>
+ <div>
+ <div className="text-gray-500">Timeline</div>
+ <div className="font-semibold">
+ {formatMonths(plan.repaymentMonths)}
+ </div>
+ </div>
+ </div>
+
+ {/* Expand/Collapse */}
+ <button
+ onClick={() => setExpandedPlan(isExpanded ? null : plan.planName)}
+ className="mt-4 flex items-center gap-1 text-sm text-teal-600 hover:text-teal-700"
+ >
+ <Info className="w-4 h-4" />
+ {isExpanded ? 'Hide details' : 'How we calculated this'}
+ </button>
+ </div>
+
+ {isExpanded && (
+ <div className="border-t border-gray-100 bg-gray-50 p-6 text-sm">
+ <div className="grid grid-cols-2 gap-4 mb-4">
+ <div>
+ <span className="text-gray-500">Poverty Guideline:</span>{' '}
+ <span className="font-medium">{formatCurrency(plan.povertyGuideline)}</span>
+ </div>
+ <div>
+ <span className="text-gray-500">Discretionary Income:</span>{' '}
+ <span className="font-medium">{formatCurrency(plan.discretionaryIncome)}</span>
+ </div>
+ </div>
+ {plan.forgivenessMonths > 0 && (
+ <p className="text-gray-600">
+ After {formatMonths(plan.forgivenessMonths)} of qualifying payments,
+ remaining balance of {formatCurrency(plan.forgivenessAmount)} would be
+ forgiven. Note: forgiven amounts may be taxable income (except PSLF).
+ </p>
+ )}
+ </div>
+ )}
+ </div>
+ );
+ })}
+
+ {/* Disclaimer */}
+ <div className="text-xs text-gray-400 text-center mt-8 space-y-1">
+ <p>
+ Estimates are for informational purposes only. Actual payments depend on
+ your specific loan terms, servicer, and certification.
+ </p>
+ <p>
+ Not affiliated with the U.S. Department of Education. Not legal or financial advice.
+ </p>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
new file mode 100644
index 0000000..a1a5be3
--- /dev/null
+++ b/src/app/dashboard/page.tsx
@@ -0,0 +1,59 @@
+import { LayoutDashboard, Save, History, Settings } from 'lucide-react';
+
+export const metadata = {
+ title: 'Dashboard - StudentLoanTracker',
+ description: 'Your saved loan data, calculations, and PSLF progress.',
+};
+
+export default function DashboardPage() {
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl">
+ <div className="flex items-center gap-3 mb-6">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-500/10 text-indigo-600">
+ <LayoutDashboard className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Dashboard</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed mb-8">
+ Your saved loan portfolio, calculation history, and PSLF progress.
+ This page requires an account -- create one to persist your data
+ across sessions.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <Save className="w-8 h-8 text-indigo-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">Saved Loans</h3>
+ <p className="text-gray-600 text-sm">
+ View and manage your saved loan portfolio. Update balances and
+ track payoff progress over time.
+ </p>
+ </div>
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <History className="w-8 h-8 text-indigo-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">Calculation History</h3>
+ <p className="text-gray-600 text-sm">
+ Review past repayment plan comparisons and see how your options
+ have changed over time.
+ </p>
+ </div>
+ <div className="p-6 bg-white rounded-xl border border-gray-200">
+ <Settings className="w-8 h-8 text-indigo-500 mb-3" />
+ <h3 className="font-semibold text-lg mb-2">Account Settings</h3>
+ <p className="text-gray-600 text-sm">
+ Manage your account, export your data, or delete everything.
+ You control your information.
+ </p>
+ </div>
+ </div>
+
+ <div className="mt-12 p-8 bg-gray-50 rounded-xl border border-gray-200 text-center">
+ <p className="text-gray-500 text-lg">
+ Authentication and saved mode coming soon.
+ </p>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/disclaimer/page.tsx b/src/app/disclaimer/page.tsx
new file mode 100644
index 0000000..0815399
--- /dev/null
+++ b/src/app/disclaimer/page.tsx
@@ -0,0 +1,57 @@
+export const metadata = {
+ title: 'Disclaimer - StudentLoanTracker',
+};
+
+export default function DisclaimerPage() {
+ return (
+ <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <h1 className="text-3xl font-bold mb-6">Disclaimer</h1>
+
+ <div className="prose prose-gray max-w-none space-y-6 text-gray-700 leading-relaxed">
+ <div className="p-4 bg-amber-50 border border-amber-200 rounded-lg">
+ <p className="text-amber-900 font-medium">
+ StudentLoanTracker is not affiliated with, endorsed by, or connected to
+ the U.S. Department of Education, Federal Student Aid, or any federal
+ government agency.
+ </p>
+ </div>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">Estimates Only</h2>
+ <p>
+ All repayment calculations, PSLF projections, and financial estimates provided
+ by this tool are approximations based on publicly available formulas and the
+ data you provide. Actual repayment amounts may differ due to rounding, interest
+ capitalization timing, policy changes, or other factors.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">Not Financial Advice</h2>
+ <p>
+ Nothing on this website constitutes financial, legal, or tax advice. The
+ information provided is for educational and informational purposes only. You
+ should consult with a qualified financial advisor, tax professional, or your
+ loan servicer before making decisions about your student loans.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">Accuracy of Information</h2>
+ <p>
+ While we strive to keep information current and accurate, repayment plan rules,
+ income thresholds, and servicer details change frequently. We make no guarantees
+ about the completeness or accuracy of any information on this site.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">Servicer Information</h2>
+ <p>
+ Servicer contact information and supported plans are maintained for reference
+ only. Always verify details directly with your servicer or at
+ StudentAid.gov for the most current information.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">Third-Party Links</h2>
+ <p>
+ This site may contain links to external websites. We are not responsible for
+ the content or privacy practices of any third-party sites.
+ </p>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/src/app/favicon.ico differ
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..001856a
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,29 @@
+@import "tailwindcss";
+
+:root {
+ --background: #f8fafc;
+ --foreground: #0f172a;
+ --navy: #0f1b2d;
+ --navy-light: #1a2d47;
+ --teal: #14b8a6;
+ --teal-dark: #0d9488;
+ --teal-light: #5eead4;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-navy: var(--navy);
+ --color-navy-light: var(--navy-light);
+ --color-teal: var(--teal);
+ --color-teal-dark: var(--teal-dark);
+ --color-teal-light: var(--teal-light);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..85209d9
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,40 @@
+import type { Metadata } from 'next';
+import { Geist, Geist_Mono } from 'next/font/google';
+import './globals.css';
+import Navbar from '@/components/layout/Navbar';
+import Footer from '@/components/layout/Footer';
+
+const geistSans = Geist({
+ variable: '--font-geist-sans',
+ subsets: ['latin'],
+});
+
+const geistMono = Geist_Mono({
+ variable: '--font-geist-mono',
+ subsets: ['latin'],
+});
+
+export const metadata: Metadata = {
+ title: 'StudentLoanTracker - Free Student Loan Tools',
+ description:
+ 'Calculate repayment plans, track PSLF progress, find your servicer, and plan for summer enrollment gaps. Free, no account needed.',
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+ <html
+ lang="en"
+ className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
+ >
+ <body className="min-h-full flex flex-col bg-[#f8fafc] text-[#0f172a]">
+ <Navbar />
+ <main className="flex-1">{children}</main>
+ <Footer />
+ </body>
+ </html>
+ );
+}
diff --git a/src/app/loans/page.tsx b/src/app/loans/page.tsx
new file mode 100644
index 0000000..525c156
--- /dev/null
+++ b/src/app/loans/page.tsx
@@ -0,0 +1,395 @@
+'use client';
+
+import { useState, useCallback } from 'react';
+import {
+ FileText, Upload, PenLine, Plus, Trash2, ShieldCheck, CheckCircle,
+ AlertCircle, FileSearch, Loader2, X
+} from 'lucide-react';
+import { parseFile, type ParseResult, type ParsedLoan } from '@/lib/file-intel';
+
+interface LoanEntry {
+ id: string;
+ name: string;
+ type: string;
+ balance: number;
+ rate: number;
+ servicer: string;
+ status: string;
+}
+
+let idCounter = 0;
+function makeId(): string {
+ return 'loan_' + (++idCounter) + '_' + Math.random().toString(36).slice(2, 6);
+}
+
+function fromParsed(p: ParsedLoan): LoanEntry {
+ return {
+ id: makeId(),
+ name: p.name,
+ type: p.loanType,
+ balance: p.balance,
+ rate: p.interestRate,
+ servicer: p.servicer,
+ status: p.status,
+ };
+}
+
+function newLoan(): LoanEntry {
+ return { id: makeId(), name: '', type: 'unsubsidized', balance: 0, rate: 5.5, servicer: '', status: '' };
+}
+
+export default function LoansPage() {
+ const [mode, setMode] = useState<'choose' | 'manual' | 'upload' | 'results'>('choose');
+ const [loans, setLoans] = useState<LoanEntry[]>([newLoan()]);
+ const [parseResult, setParseResult] = useState<ParseResult | null>(null);
+ const [parsing, setParsing] = useState(false);
+ const [dragActive, setDragActive] = useState(false);
+
+ const handleFile = useCallback(async (file: File) => {
+ setParsing(true);
+ setParseResult(null);
+ try {
+ const result = await parseFile(file);
+ setParseResult(result);
+
+ if (result.loans.length > 0) {
+ setLoans(result.loans.map(fromParsed));
+ setMode('results');
+ } else if (result.pslfPayments !== undefined && result.pslfPayments > 0) {
+ setMode('results');
+ } else {
+ setMode('results');
+ }
+ } catch (err) {
+ setParseResult({
+ classification: { type: 'unknown', confidence: 0, label: 'Error' },
+ loans: [],
+ rawText: '',
+ warnings: [`Failed to parse file: ${err instanceof Error ? err.message : 'Unknown error'}`],
+ });
+ setMode('results');
+ } finally {
+ setParsing(false);
+ }
+ }, []);
+
+ function handleFileInput(e: React.ChangeEvent<HTMLInputElement>) {
+ const file = e.target.files?.[0];
+ if (file) handleFile(file);
+ }
+
+ function handleDrop(e: React.DragEvent) {
+ e.preventDefault();
+ setDragActive(false);
+ const file = e.dataTransfer.files?.[0];
+ if (file) handleFile(file);
+ }
+
+ function addLoan() { setLoans([...loans, newLoan()]); }
+ function removeLoan(id: string) { setLoans(loans.filter(l => l.id !== id)); }
+ function updateLoan(id: string, field: keyof LoanEntry, value: string | number) {
+ setLoans(loans.map(l => l.id === id ? { ...l, [field]: value } : l));
+ }
+
+ const totalBalance = loans.reduce((sum, l) => sum + l.balance, 0);
+ const weightedRate = totalBalance > 0
+ ? loans.reduce((sum, l) => sum + l.balance * l.rate, 0) / totalBalance : 0;
+
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl mb-10">
+ <div className="flex items-center gap-3 mb-4">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-blue-500/10 text-blue-600">
+ <FileText className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Loan Intake</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed">
+ Import your loans to power the calculators, PSLF tracker, and planner.
+ Upload any student loan document — we'll figure out what it is.
+ </p>
+ </div>
+
+ {/* ─── Choose Mode ─── */}
+ {mode === 'choose' && (
+ <div className="space-y-8">
+ {/* Upload Drop Zone */}
+ <div
+ className={`relative border-2 border-dashed rounded-xl p-12 text-center transition-colors ${
+ dragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300 bg-white hover:border-blue-300'
+ }`}
+ onDragOver={(e) => { e.preventDefault(); setDragActive(true); }}
+ onDragLeave={() => setDragActive(false)}
+ onDrop={handleDrop}
+ >
+ {parsing ? (
+ <div className="flex flex-col items-center gap-3">
+ <Loader2 className="w-10 h-10 text-blue-500 animate-spin" />
+ <p className="text-blue-600 font-medium">Analyzing your document...</p>
+ <p className="text-gray-400 text-sm">Processing in your browser — nothing uploaded</p>
+ </div>
+ ) : (
+ <>
+ <Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
+ <p className="text-gray-700 font-medium text-lg mb-2">
+ Drop any student loan document here
+ </p>
+ <p className="text-gray-500 text-sm mb-4">
+ Supports: StudentAid.gov TXT, CSV, PDF statements, ECF results, 1098-E
+ </p>
+ <label className="inline-flex items-center gap-2 px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg cursor-pointer transition-colors">
+ <FileSearch className="w-5 h-5" />
+ Browse Files
+ <input type="file" accept=".txt,.csv,.tsv,.pdf,.doc,.docx" onChange={handleFileInput} className="hidden" />
+ </label>
+ <p className="text-xs text-gray-400 mt-4">
+ All files are processed entirely in your browser. Nothing is uploaded to any server.
+ </p>
+ </>
+ )}
+ </div>
+
+ {/* Or Manual */}
+ <div className="flex items-center gap-4">
+ <div className="flex-1 border-t border-gray-200" />
+ <span className="text-gray-400 text-sm">or</span>
+ <div className="flex-1 border-t border-gray-200" />
+ </div>
+
+ <button onClick={() => setMode('manual')}
+ className="w-full p-6 bg-white rounded-xl border border-gray-200 hover:border-blue-300 hover:shadow-lg transition-all text-left flex items-center gap-4"
+ >
+ <PenLine className="w-8 h-8 text-blue-500 shrink-0" />
+ <div>
+ <h3 className="font-semibold text-lg">Enter Loans Manually</h3>
+ <p className="text-gray-600 text-sm">Type in each loan's balance, rate, and servicer.</p>
+ </div>
+ </button>
+ </div>
+ )}
+
+ {/* ─── Parse Results ─── */}
+ {mode === 'results' && parseResult && (
+ <div className="space-y-6">
+ {/* Classification Badge */}
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <div className="flex items-center justify-between mb-4">
+ <div className="flex items-center gap-3">
+ <FileSearch className="w-6 h-6 text-blue-500" />
+ <div>
+ <h3 className="font-semibold">Document Detected</h3>
+ <p className="text-sm text-gray-500">{parseResult.classification.label}</p>
+ </div>
+ </div>
+ <div className={`px-3 py-1 rounded-full text-xs font-medium ${
+ parseResult.classification.confidence > 0.5
+ ? 'bg-green-100 text-green-700'
+ : parseResult.classification.confidence > 0.2
+ ? 'bg-amber-100 text-amber-700'
+ : 'bg-red-100 text-red-700'
+ }`}>
+ {Math.round(parseResult.classification.confidence * 100)}% confidence
+ </div>
+ </div>
+
+ {/* Warnings */}
+ {parseResult.warnings.length > 0 && (
+ <div className="space-y-2 mt-4">
+ {parseResult.warnings.map((w, i) => (
+ <div key={i} className="flex items-start gap-2 text-sm">
+ <AlertCircle className="w-4 h-4 text-amber-500 mt-0.5 shrink-0" />
+ <span className="text-amber-700">{w}</span>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+
+ {/* PSLF Result */}
+ {parseResult.pslfPayments !== undefined && parseResult.pslfPayments > 0 && (
+ <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-6">
+ <h3 className="font-semibold text-emerald-800 mb-2">PSLF Payment Count Found</h3>
+ <div className="text-3xl font-bold text-emerald-700">{parseResult.pslfPayments} / 120</div>
+ <p className="text-sm text-emerald-600 mt-2">{120 - parseResult.pslfPayments} payments remaining</p>
+ <a href={`/pslf?payments=${parseResult.pslfPayments}`}
+ className="inline-flex items-center gap-2 mt-4 px-4 py-2 bg-emerald-500 hover:bg-emerald-600 text-white text-sm font-medium rounded-lg transition-colors"
+ >
+ Open PSLF Tracker →
+ </a>
+ </div>
+ )}
+
+ {/* Extracted Loans */}
+ {loans.length > 0 && loans[0].balance > 0 && (
+ <>
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <div className="flex items-center justify-between mb-4">
+ <h3 className="font-semibold">Extracted Loans ({loans.length})</h3>
+ <button onClick={() => setMode('manual')} className="text-sm text-blue-600 hover:underline">
+ Edit manually
+ </button>
+ </div>
+ <div className="overflow-x-auto">
+ <table className="w-full text-sm">
+ <thead>
+ <tr className="border-b border-gray-100">
+ <th className="text-left py-2 text-gray-500 font-medium">Loan</th>
+ <th className="text-left py-2 text-gray-500 font-medium">Type</th>
+ <th className="text-right py-2 text-gray-500 font-medium">Balance</th>
+ <th className="text-right py-2 text-gray-500 font-medium">Rate</th>
+ <th className="text-left py-2 text-gray-500 font-medium">Servicer</th>
+ </tr>
+ </thead>
+ <tbody>
+ {loans.map((loan) => (
+ <tr key={loan.id} className="border-b border-gray-50">
+ <td className="py-2 font-medium">{loan.name || '—'}</td>
+ <td className="py-2 text-gray-600">{loan.type}</td>
+ <td className="py-2 text-right font-mono">${loan.balance.toLocaleString()}</td>
+ <td className="py-2 text-right">{loan.rate > 0 ? `${loan.rate}%` : '—'}</td>
+ <td className="py-2 text-gray-600">{loan.servicer || '—'}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ {/* Summary */}
+ <div className="bg-blue-50 border border-blue-200 rounded-xl p-6">
+ <h3 className="font-semibold text-blue-800 mb-3">Loan Summary</h3>
+ <div className="grid grid-cols-3 gap-4 text-sm">
+ <div>
+ <div className="text-blue-600">Total Balance</div>
+ <div className="text-xl font-bold text-blue-900">${totalBalance.toLocaleString()}</div>
+ </div>
+ <div>
+ <div className="text-blue-600">Weighted Avg Rate</div>
+ <div className="text-xl font-bold text-blue-900">{weightedRate.toFixed(2)}%</div>
+ </div>
+ <div>
+ <div className="text-blue-600">Number of Loans</div>
+ <div className="text-xl font-bold text-blue-900">{loans.length}</div>
+ </div>
+ </div>
+ <a href={`/calculator?balance=${totalBalance}&rate=${(weightedRate / 100).toFixed(4)}`}
+ className="inline-flex items-center gap-2 mt-4 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium rounded-lg transition-colors"
+ >
+ Use in Calculator →
+ </a>
+ </div>
+ </>
+ )}
+
+ {/* Actions */}
+ <div className="flex items-center gap-4">
+ <button onClick={() => { setMode('choose'); setParseResult(null); }}
+ className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors"
+ >
+ <Upload className="w-4 h-4" /> Upload Another File
+ </button>
+ <button onClick={() => setMode('manual')}
+ className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors"
+ >
+ <PenLine className="w-4 h-4" /> Edit Manually
+ </button>
+ </div>
+ </div>
+ )}
+
+ {/* ─── Manual Entry ─── */}
+ {mode === 'manual' && (
+ <div className="space-y-6">
+ {loans.map((loan, idx) => (
+ <div key={loan.id} className="bg-white rounded-xl border border-gray-200 p-6">
+ <div className="flex items-center justify-between mb-4">
+ <h3 className="font-semibold">Loan {idx + 1}</h3>
+ {loans.length > 1 && (
+ <button onClick={() => removeLoan(loan.id)} className="text-red-400 hover:text-red-600">
+ <Trash2 className="w-4 h-4" />
+ </button>
+ )}
+ </div>
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
+ <div>
+ <label className="block text-xs font-medium text-gray-500 mb-1">Name</label>
+ <input type="text" value={loan.name} placeholder="e.g. Direct Unsub #1"
+ onChange={(e) => updateLoan(loan.id, 'name', e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" />
+ </div>
+ <div>
+ <label className="block text-xs font-medium text-gray-500 mb-1">Type</label>
+ <select value={loan.type} onChange={(e) => updateLoan(loan.id, 'type', e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
+ <option value="subsidized">Direct Subsidized</option>
+ <option value="unsubsidized">Direct Unsubsidized</option>
+ <option value="grad_plus">Grad PLUS</option>
+ <option value="parent_plus">Parent PLUS</option>
+ <option value="consolidated">Consolidated</option>
+ <option value="perkins">Perkins</option>
+ <option value="other">Other</option>
+ </select>
+ </div>
+ <div>
+ <label className="block text-xs font-medium text-gray-500 mb-1">Balance ($)</label>
+ <input type="number" value={loan.balance || ''}
+ onChange={(e) => updateLoan(loan.id, 'balance', Number(e.target.value))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" />
+ </div>
+ <div>
+ <label className="block text-xs font-medium text-gray-500 mb-1">Rate (%)</label>
+ <input type="number" step="0.1" value={loan.rate || ''}
+ onChange={(e) => updateLoan(loan.id, 'rate', Number(e.target.value))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" />
+ </div>
+ </div>
+ </div>
+ ))}
+
+ <button onClick={addLoan}
+ className="flex items-center gap-2 px-4 py-2 border border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-blue-300 hover:text-blue-600 transition-colors"
+ >
+ <Plus className="w-4 h-4" /> Add Another Loan
+ </button>
+
+ {totalBalance > 0 && (
+ <div className="bg-blue-50 border border-blue-200 rounded-xl p-6">
+ <h3 className="font-semibold text-blue-800 mb-3">Loan Summary</h3>
+ <div className="grid grid-cols-3 gap-4 text-sm">
+ <div>
+ <div className="text-blue-600">Total Balance</div>
+ <div className="text-xl font-bold text-blue-900">${totalBalance.toLocaleString()}</div>
+ </div>
+ <div>
+ <div className="text-blue-600">Weighted Avg Rate</div>
+ <div className="text-xl font-bold text-blue-900">{weightedRate.toFixed(2)}%</div>
+ </div>
+ <div>
+ <div className="text-blue-600">Number of Loans</div>
+ <div className="text-xl font-bold text-blue-900">{loans.length}</div>
+ </div>
+ </div>
+ <a href={`/calculator?balance=${totalBalance}&rate=${(weightedRate / 100).toFixed(4)}`}
+ className="inline-flex items-center gap-2 mt-4 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium rounded-lg transition-colors"
+ >
+ Use in Calculator →
+ </a>
+ </div>
+ )}
+
+ <div className="flex items-center gap-4">
+ <button onClick={() => setMode('choose')} className="text-sm text-blue-600 hover:underline">← Back</button>
+ </div>
+
+ <div className="flex items-start gap-3 p-4 bg-emerald-50 rounded-lg border border-emerald-200">
+ <ShieldCheck className="w-5 h-5 text-emerald-600 mt-0.5 shrink-0" />
+ <p className="text-emerald-800 text-sm">
+ <strong>Privacy:</strong> Your loan data is stored only in this browser tab.
+ It is never sent to any server. Close the tab to clear everything.
+ </p>
+ </div>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..f5f7547
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,133 @@
+import Link from 'next/link';
+import {
+ Calculator,
+ FileText,
+ ShieldCheck,
+ Building2,
+ Sun,
+} from 'lucide-react';
+
+const features = [
+ {
+ href: '/calculator',
+ icon: Calculator,
+ title: 'Repayment Calculators',
+ description:
+ 'Compare SAVE, IBR, PAYE, ICR, and Standard plans side-by-side. See monthly payments, total cost, and forgiveness timelines.',
+ color: 'bg-teal-500/10 text-teal-600',
+ },
+ {
+ href: '/loans',
+ icon: FileText,
+ title: 'Loan Intake',
+ description:
+ 'Enter your loans manually or upload your StudentAid.gov data file. We parse it in your browser -- nothing leaves your device.',
+ color: 'bg-blue-500/10 text-blue-600',
+ },
+ {
+ href: '/pslf',
+ icon: ShieldCheck,
+ title: 'PSLF Tracker',
+ description:
+ 'Track qualifying payments toward Public Service Loan Forgiveness. Upload your ECF or enter counts manually.',
+ color: 'bg-emerald-500/10 text-emerald-600',
+ },
+ {
+ href: '/servicers',
+ icon: Building2,
+ title: 'Servicer Directory',
+ description:
+ 'Find your loan servicer, get their contact info, and see which repayment plans they support.',
+ color: 'bg-purple-500/10 text-purple-600',
+ },
+ {
+ href: '/summer-aid',
+ icon: Sun,
+ title: 'Summer Aid Planner',
+ description:
+ 'Plan for summer enrollment gaps. See how dropping below half-time affects your grace period and repayment timeline.',
+ color: 'bg-amber-500/10 text-amber-600',
+ },
+];
+
+export default function HomePage() {
+ return (
+ <div>
+ {/* Hero Section */}
+ <section className="bg-[#0f1b2d] text-white">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-28">
+ <div className="max-w-3xl">
+ <h1 className="text-4xl md:text-5xl font-bold leading-tight tracking-tight">
+ Free Student Loan Tools
+ </h1>
+ <p className="mt-6 text-lg md:text-xl text-gray-300 leading-relaxed">
+ Calculate repayment plans, track PSLF progress, and find your
+ servicer — no account needed.
+ </p>
+ <div className="mt-8 flex flex-wrap gap-4">
+ <Link
+ href="/calculator"
+ className="inline-flex items-center gap-2 px-6 py-3 bg-teal-500 hover:bg-teal-600 text-white font-semibold rounded-lg transition-colors"
+ >
+ <Calculator className="w-5 h-5" />
+ Start with a Calculator
+ </Link>
+ <Link
+ href="/loans"
+ className="inline-flex items-center gap-2 px-6 py-3 border border-gray-500 hover:border-gray-300 text-gray-300 hover:text-white font-semibold rounded-lg transition-colors"
+ >
+ <FileText className="w-5 h-5" />
+ Import Your Loans
+ </Link>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ {/* Feature Cards */}
+ <section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-20">
+ <h2 className="text-2xl md:text-3xl font-bold text-center mb-12">
+ Everything You Need in One Place
+ </h2>
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+ {features.map((feature) => {
+ const Icon = feature.icon;
+ return (
+ <Link
+ key={feature.href}
+ href={feature.href}
+ className="group block p-6 bg-white rounded-xl border border-gray-200 hover:border-teal-300 hover:shadow-lg transition-all"
+ >
+ <div
+ className={`inline-flex items-center justify-center w-12 h-12 rounded-lg ${feature.color} mb-4`}
+ >
+ <Icon className="w-6 h-6" />
+ </div>
+ <h3 className="text-lg font-semibold group-hover:text-teal-600 transition-colors">
+ {feature.title}
+ </h3>
+ <p className="mt-2 text-gray-600 text-sm leading-relaxed">
+ {feature.description}
+ </p>
+ </Link>
+ );
+ })}
+ </div>
+ </section>
+
+ {/* Privacy Banner */}
+ <section className="bg-[#0f1b2d] text-white">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 text-center">
+ <h2 className="text-xl md:text-2xl font-bold mb-3">
+ Your Privacy Comes First
+ </h2>
+ <p className="text-gray-400 max-w-2xl mx-auto leading-relaxed">
+ Guest mode requires no login and stores nothing. Files are processed
+ entirely in your browser and discarded immediately. Create an optional
+ account only if you want to save your progress.
+ </p>
+ </div>
+ </section>
+ </div>
+ );
+}
diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx
new file mode 100644
index 0000000..9c94519
--- /dev/null
+++ b/src/app/privacy/page.tsx
@@ -0,0 +1,57 @@
+export const metadata = {
+ title: 'Privacy Policy - StudentLoanTracker',
+};
+
+export default function PrivacyPage() {
+ return (
+ <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <h1 className="text-3xl font-bold mb-6">Privacy Policy</h1>
+
+ <div className="prose prose-gray max-w-none space-y-6 text-gray-700 leading-relaxed">
+ <p>
+ <strong>Effective Date:</strong> March 25, 2026
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">1. Privacy First</h2>
+ <p>
+ StudentLoanTracker is designed with privacy as the default. Guest mode
+ requires no login and stores no personal data on our servers.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">2. Guest Mode</h2>
+ <p>
+ In guest mode, all calculations run entirely in your browser. No loan data,
+ income information, or personal details are transmitted to or stored on our
+ servers. When you close your browser tab, the data is gone.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">3. File Processing</h2>
+ <p>
+ Uploaded files (StudentAid.gov data exports, ECF PDFs) are processed entirely
+ in your browser using client-side JavaScript. Files are never uploaded to our
+ servers unless you explicitly opt in to save mode.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">4. Saved Mode (Optional)</h2>
+ <p>
+ If you create an account, your loan data is stored encrypted in our database.
+ You can export or delete all of your data at any time from your dashboard.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">5. Analytics</h2>
+ <p>
+ We may collect anonymous, aggregated usage statistics (e.g., which calculator
+ plans are most popular). These statistics contain no personally identifiable
+ information.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">6. No PII in Logs</h2>
+ <p>
+ We do not log personal information, loan balances, income, or any data entered
+ into our tools. Server logs contain only standard request metadata (IP addresses
+ are anonymized).
+ </p>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/pslf/page.tsx b/src/app/pslf/page.tsx
new file mode 100644
index 0000000..a2ed875
--- /dev/null
+++ b/src/app/pslf/page.tsx
@@ -0,0 +1,350 @@
+'use client';
+
+import { useState } from 'react';
+import { ShieldCheck, TrendingUp } from 'lucide-react';
+import {
+ calculatePSLF,
+ type LoanInputs,
+ type FilingStatus,
+} from '@/lib/calculators';
+import type { PSLFResult } from '@/lib/calculators';
+
+function formatCurrency(n: number): string {
+ return n.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
+}
+
+export default function PSLFPage() {
+ const [inputs, setInputs] = useState({
+ totalBalance: 60000,
+ weightedRate: 0.06,
+ agi: 55000,
+ familySize: 1,
+ filingStatus: 'single' as FilingStatus,
+ state: 'CA',
+ qualifyingPayments: 48,
+ selectedPlan: 'SAVE' as 'SAVE' | 'IBR' | 'PAYE' | 'ICR' | 'Standard',
+ employerType: 'government' as 'government' | 'nonprofit' | 'for_profit',
+ });
+
+ const [result, setResult] = useState<PSLFResult | null>(null);
+
+ function handleCalculate() {
+ const r = calculatePSLF({
+ totalBalance: inputs.totalBalance,
+ weightedRate: inputs.weightedRate,
+ agi: inputs.agi,
+ familySize: inputs.familySize,
+ filingStatus: inputs.filingStatus,
+ state: inputs.state,
+ qualifyingPayments: inputs.qualifyingPayments,
+ selectedPlan: inputs.selectedPlan,
+ employerType: inputs.employerType,
+ });
+ setResult(r);
+ }
+
+ const progressPct = Math.min(100, (inputs.qualifyingPayments / 120) * 100);
+
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl mb-10">
+ <div className="flex items-center gap-3 mb-4">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-emerald-500/10 text-emerald-600">
+ <ShieldCheck className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">PSLF Progress Tracker</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed">
+ Track your progress toward 120 qualifying payments for Public Service Loan Forgiveness.
+ See how much would be forgiven and when.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+ {/* Inputs */}
+ <div className="lg:col-span-1">
+ <div className="bg-white rounded-xl border border-gray-200 p-6 sticky top-6 space-y-4">
+ <h2 className="text-lg font-semibold">Your PSLF Details</h2>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Qualifying Payments Made</label>
+ <input type="number" min="0" max="120" value={inputs.qualifyingPayments}
+ onChange={(e) => setInputs(p => ({...p, qualifyingPayments: Number(e.target.value)}))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ />
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Employer Type</label>
+ <select value={inputs.employerType}
+ onChange={(e) => setInputs(p => ({...p, employerType: e.target.value as typeof inputs.employerType}))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ >
+ <option value="government">Government</option>
+ <option value="nonprofit">501(c)(3) Nonprofit</option>
+ <option value="for_profit">For-Profit (not eligible)</option>
+ </select>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Repayment Plan</label>
+ <select value={inputs.selectedPlan}
+ onChange={(e) => setInputs(p => ({...p, selectedPlan: e.target.value as typeof inputs.selectedPlan}))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ >
+ <option value="SAVE">SAVE</option>
+ <option value="IBR">IBR</option>
+ <option value="PAYE">PAYE</option>
+ <option value="ICR">ICR</option>
+ <option value="Standard">Standard (10-Year)</option>
+ </select>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Loan Balance</label>
+ <div className="relative">
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">$</span>
+ <input type="number" value={inputs.totalBalance}
+ onChange={(e) => setInputs(p => ({...p, totalBalance: Number(e.target.value)}))}
+ className="w-full pl-7 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ />
+ </div>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Interest Rate (%)</label>
+ <input type="number" step="0.1" value={(inputs.weightedRate * 100).toFixed(1)}
+ onChange={(e) => setInputs(p => ({...p, weightedRate: Number(e.target.value) / 100}))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ />
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Annual Income (AGI)</label>
+ <div className="relative">
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">$</span>
+ <input type="number" value={inputs.agi}
+ onChange={(e) => setInputs(p => ({...p, agi: Number(e.target.value)}))}
+ className="w-full pl-7 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ />
+ </div>
+ </div>
+
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Family Size</label>
+ <input type="number" min="1" max="20" value={inputs.familySize}
+ onChange={(e) => setInputs(p => ({...p, familySize: Number(e.target.value)}))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
+ />
+ </div>
+
+ <button onClick={handleCalculate}
+ className="w-full mt-2 px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white font-semibold rounded-lg transition-colors"
+ >
+ Calculate PSLF
+ </button>
+ </div>
+ </div>
+
+ {/* Results */}
+ <div className="lg:col-span-2 space-y-6">
+ {/* Thermometer Countdown */}
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <h3 className="font-semibold mb-6 text-center">Payment Progress</h3>
+ <div className="flex items-center gap-8">
+ {/* SVG Thermometer */}
+ <div className="flex flex-col items-center shrink-0">
+ <svg width="80" height="320" viewBox="0 0 80 320" className="drop-shadow-lg">
+ {/* Outer shell */}
+ <rect x="20" y="10" width="40" height="240" rx="20" ry="20"
+ fill="#f1f5f9" stroke="#e2e8f0" strokeWidth="2" />
+ {/* Fill (bottom-up) */}
+ <clipPath id="thermClip">
+ <rect x="22" y="12" width="36" height="236" rx="18" ry="18" />
+ </clipPath>
+ <rect
+ x="22"
+ y={12 + 236 * (1 - progressPct / 100)}
+ width="36"
+ height={236 * (progressPct / 100)}
+ clipPath="url(#thermClip)"
+ className="transition-all duration-700 ease-out"
+ fill="url(#thermoGrad)"
+ />
+ {/* Gradient */}
+ <defs>
+ <linearGradient id="thermoGrad" x1="0" y1="0" x2="0" y2="1">
+ <stop offset="0%" stopColor="#10b981" />
+ <stop offset="50%" stopColor="#34d399" />
+ <stop offset="100%" stopColor="#6ee7b7" />
+ </linearGradient>
+ </defs>
+ {/* Bulb at bottom */}
+ <circle cx="40" cy="280" r="30"
+ fill={progressPct >= 100 ? '#10b981' : '#f1f5f9'}
+ stroke="#e2e8f0" strokeWidth="2"
+ className="transition-all duration-700"
+ />
+ <circle cx="40" cy="280" r="24"
+ fill={progressPct > 0 ? 'url(#thermoGrad)' : '#e2e8f0'}
+ className="transition-all duration-700"
+ />
+ {/* Tick marks every 30 payments */}
+ {[0, 30, 60, 90, 120].map((tick) => {
+ const y = 248 - (tick / 120) * 236;
+ return (
+ <g key={tick}>
+ <line x1="60" y1={y} x2="70" y2={y} stroke="#94a3b8" strokeWidth="1.5" />
+ <text x="74" y={y + 4} fontSize="10" fill="#64748b" fontFamily="sans-serif">
+ {tick}
+ </text>
+ </g>
+ );
+ })}
+ {/* Current level indicator */}
+ {progressPct > 0 && progressPct < 100 && (
+ <g>
+ <line
+ x1="10" y1={248 - (inputs.qualifyingPayments / 120) * 236}
+ x2="20" y2={248 - (inputs.qualifyingPayments / 120) * 236}
+ stroke="#059669" strokeWidth="2"
+ />
+ <text
+ x="4" y={248 - (inputs.qualifyingPayments / 120) * 236 + 4}
+ fontSize="10" fill="#059669" fontWeight="bold" textAnchor="end"
+ fontFamily="sans-serif"
+ >
+ </text>
+ </g>
+ )}
+ {/* 120 goal star */}
+ {progressPct >= 100 && (
+ <text x="40" y="285" textAnchor="middle" fontSize="20">
+ ⭐
+ </text>
+ )}
+ </svg>
+ </div>
+
+ {/* Stats beside thermometer */}
+ <div className="flex-1 space-y-4">
+ <div>
+ <div className="text-5xl font-bold text-emerald-600 tracking-tight">
+ {inputs.qualifyingPayments}
+ </div>
+ <div className="text-gray-400 text-sm mt-1">of 120 qualifying payments</div>
+ </div>
+
+ <div className="h-px bg-gray-100" />
+
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <div className="text-2xl font-bold text-[#0f1b2d]">
+ {120 - inputs.qualifyingPayments}
+ </div>
+ <div className="text-xs text-gray-500">payments remaining</div>
+ </div>
+ <div>
+ <div className="text-2xl font-bold text-[#0f1b2d]">
+ {Math.ceil((120 - inputs.qualifyingPayments) / 12)}yr {(120 - inputs.qualifyingPayments) % 12}mo
+ </div>
+ <div className="text-xs text-gray-500">time remaining</div>
+ </div>
+ </div>
+
+ <div className="h-px bg-gray-100" />
+
+ {/* Mini progress percentage */}
+ <div className="flex items-center gap-3">
+ <div className="flex-1 bg-gray-100 rounded-full h-2 overflow-hidden">
+ <div
+ className="h-full bg-emerald-500 rounded-full transition-all duration-700"
+ style={{ width: `${progressPct}%` }}
+ />
+ </div>
+ <span className="text-sm font-semibold text-emerald-600 w-14 text-right">
+ {progressPct.toFixed(1)}%
+ </span>
+ </div>
+
+ {progressPct >= 100 && (
+ <div className="bg-emerald-50 border border-emerald-200 rounded-lg p-3 text-center">
+ <span className="text-emerald-700 font-semibold">
+ Congratulations! You've reached 120 qualifying payments!
+ </span>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+
+ {inputs.employerType === 'for_profit' && (
+ <div className="bg-red-50 border border-red-200 rounded-xl p-6">
+ <p className="text-red-800 font-medium">
+ For-profit employers do not qualify for PSLF. You must work full-time for a
+ government organization or 501(c)(3) nonprofit.
+ </p>
+ </div>
+ )}
+
+ {result && inputs.employerType !== 'for_profit' && (
+ <>
+ {/* Key Metrics */}
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
+ <div className="bg-white rounded-xl border border-gray-200 p-6 text-center">
+ <div className="text-sm text-gray-500 mb-1">Monthly Payment</div>
+ <div className="text-2xl font-bold">{formatCurrency(result.monthlyPayment)}</div>
+ <div className="text-xs text-gray-400 mt-1">on {inputs.selectedPlan} plan</div>
+ </div>
+ <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-6 text-center">
+ <div className="text-sm text-emerald-600 mb-1">Amount Forgiven</div>
+ <div className="text-2xl font-bold text-emerald-700">{formatCurrency(result.forgivenessAmount)}</div>
+ <div className="text-xs text-emerald-500 mt-1">tax-free under PSLF</div>
+ </div>
+ <div className="bg-white rounded-xl border border-gray-200 p-6 text-center">
+ <div className="text-sm text-gray-500 mb-1">Forgiveness Date</div>
+ <div className="text-2xl font-bold">{result.estimatedForgivenessDate}</div>
+ <div className="text-xs text-gray-400 mt-1">{result.paymentsRemaining} months away</div>
+ </div>
+ </div>
+
+ {/* Plan Comparison */}
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <h3 className="font-semibold mb-4 flex items-center gap-2">
+ <TrendingUp className="w-5 h-5 text-emerald-500" />
+ Monthly Payment by Plan
+ </h3>
+ <div className="space-y-3">
+ {Object.entries(result.monthlyPaymentByPlan).map(([plan, payment]) => {
+ const maxPayment = Math.max(...Object.values(result.monthlyPaymentByPlan));
+ const widthPct = maxPayment > 0 ? (payment / maxPayment) * 100 : 0;
+ return (
+ <div key={plan}>
+ <div className="flex justify-between text-sm mb-1">
+ <span className={`font-medium ${plan === inputs.selectedPlan ? 'text-emerald-600' : 'text-gray-700'}`}>
+ {plan} {plan === inputs.selectedPlan && '(selected)'}
+ </span>
+ <span className="font-semibold">{formatCurrency(payment)}</span>
+ </div>
+ <div className="w-full bg-gray-100 rounded-full h-2">
+ <div
+ className={`h-full rounded-full ${plan === inputs.selectedPlan ? 'bg-emerald-500' : 'bg-gray-300'}`}
+ style={{ width: `${widthPct}%` }}
+ />
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ </div>
+
+ <div className="text-xs text-gray-400 text-center">
+ PSLF forgiveness is tax-free. Estimates assume continuous qualifying employment and on-time payments.
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/servicers/page.tsx b/src/app/servicers/page.tsx
new file mode 100644
index 0000000..e8982d2
--- /dev/null
+++ b/src/app/servicers/page.tsx
@@ -0,0 +1,126 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Building2, Phone, Globe, AlertTriangle, Search, CheckCircle } from 'lucide-react';
+
+interface Servicer {
+ id: number;
+ name: string;
+ phone: string;
+ website: string;
+ official: boolean;
+ notes: string;
+ last_verified: string;
+}
+
+// Hardcoded servicer data (mirrors DB seed — no API call needed for guest mode)
+const SERVICERS: Servicer[] = [
+ { id: 1, name: 'Edfinancial', phone: '1-855-337-6884', website: 'https://edfinancial.com', official: true, notes: 'Official federal servicer', last_verified: '2026-03-25' },
+ { id: 2, name: 'MOHELA', phone: '1-888-866-4352', website: 'https://www.mohela.com', official: true, notes: 'Official federal servicer, commonly handles PSLF', last_verified: '2026-03-25' },
+ { id: 3, name: 'Aidvantage', phone: '1-800-722-1300', website: 'https://aidvantage.com', official: true, notes: 'Official federal servicer', last_verified: '2026-03-25' },
+ { id: 4, name: 'Nelnet', phone: '1-888-486-4722', website: 'https://www.nelnet.com', official: true, notes: 'Official federal servicer', last_verified: '2026-03-25' },
+ { id: 5, name: 'ECSI (Heartland)', phone: '1-888-549-3274', website: 'https://www.heartland.ecsi.net', official: true, notes: 'Perkins and institutional loans', last_verified: '2026-03-25' },
+ { id: 6, name: 'Default Resolution Group', phone: '1-800-621-3115', website: 'https://myeddebt.ed.gov', official: true, notes: 'Handles defaulted federal loans', last_verified: '2026-03-25' },
+];
+
+export default function ServicersPage() {
+ const [search, setSearch] = useState('');
+
+ const filtered = SERVICERS.filter(s =>
+ s.name.toLowerCase().includes(search.toLowerCase()) ||
+ s.notes.toLowerCase().includes(search.toLowerCase())
+ );
+
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl mb-10">
+ <div className="flex items-center gap-3 mb-4">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-purple-500/10 text-purple-600">
+ <Building2 className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Servicer Directory</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed">
+ Official federal student loan servicers with verified contact information.
+ Not sure who services your loans? Log into StudentAid.gov to check.
+ </p>
+ </div>
+
+ {/* Search */}
+ <div className="relative max-w-md mb-8">
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
+ <input
+ type="text"
+ placeholder="Search servicers..."
+ value={search}
+ onChange={(e) => setSearch(e.target.value)}
+ className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ />
+ </div>
+
+ {/* Servicer Cards */}
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ {filtered.map((s) => (
+ <div key={s.id} className="bg-white rounded-xl border border-gray-200 p-6">
+ <div className="flex items-start justify-between mb-3">
+ <h3 className="text-lg font-semibold">{s.name}</h3>
+ {s.official && (
+ <span className="flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 text-xs font-medium rounded-full">
+ <CheckCircle className="w-3 h-3" />
+ Official
+ </span>
+ )}
+ </div>
+
+ <p className="text-gray-600 text-sm mb-4">{s.notes}</p>
+
+ <div className="space-y-2 text-sm">
+ <div className="flex items-center gap-2">
+ <Phone className="w-4 h-4 text-gray-400" />
+ <a href={`tel:${s.phone.replace(/\D/g, '')}`} className="text-purple-600 hover:underline font-medium">
+ {s.phone}
+ </a>
+ </div>
+ <div className="flex items-center gap-2">
+ <Globe className="w-4 h-4 text-gray-400" />
+ <a href={s.website} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:underline">
+ {s.website.replace('https://', '').replace('http://', '')}
+ </a>
+ </div>
+ </div>
+
+ <div className="mt-4 text-xs text-gray-400">
+ Last verified: {s.last_verified}
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {filtered.length === 0 && (
+ <div className="text-center py-12 text-gray-500">
+ No servicers match your search.
+ </div>
+ )}
+
+ {/* Scam Warning */}
+ <div className="mt-10 bg-amber-50 border border-amber-200 rounded-xl p-6">
+ <div className="flex items-start gap-3">
+ <AlertTriangle className="w-6 h-6 text-amber-600 shrink-0 mt-0.5" />
+ <div>
+ <h3 className="font-semibold text-amber-800 mb-2">Beware of Student Loan Scams</h3>
+ <ul className="text-sm text-amber-700 space-y-1 list-disc list-inside">
+ <li>You never need to pay to apply for federal repayment plans or consolidation</li>
+ <li>Your servicer will never ask for your FSA ID password</li>
+ <li>Be wary of companies promising “immediate forgiveness” or “loan elimination”</li>
+ <li>Report suspected scams to the FTC at reportfraud.ftc.gov</li>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ <div className="text-xs text-gray-400 text-center mt-8">
+ Not affiliated with the U.S. Department of Education. Contact information sourced from StudentAid.gov.
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/summer-aid/page.tsx b/src/app/summer-aid/page.tsx
new file mode 100644
index 0000000..d9ec1e8
--- /dev/null
+++ b/src/app/summer-aid/page.tsx
@@ -0,0 +1,151 @@
+'use client';
+
+import { useState } from 'react';
+import { Sun, AlertCircle, CheckCircle, Calendar, BookOpen } from 'lucide-react';
+
+interface ChecklistItem {
+ id: string;
+ task: string;
+ deadline: string;
+ done: boolean;
+ risk?: string;
+}
+
+export default function SummerAidPage() {
+ const [enrollment, setEnrollment] = useState<'full' | 'half' | 'less'>('half');
+ const [sessionStart, setSessionStart] = useState('2026-06-01');
+ const [school, setSchool] = useState('');
+ const [showChecklist, setShowChecklist] = useState(false);
+ const [checklist, setChecklist] = useState<ChecklistItem[]>([]);
+
+ function generateChecklist() {
+ const start = new Date(sessionStart);
+ const items: ChecklistItem[] = [];
+
+ const sixWeeksBefore = new Date(start);
+ sixWeeksBefore.setDate(sixWeeksBefore.getDate() - 42);
+ items.push({ id: '1', task: 'Submit Summer Aid Request form through your school portal', deadline: sixWeeksBefore.toISOString().split('T')[0], done: false });
+
+ const fourWeeksBefore = new Date(start);
+ fourWeeksBefore.setDate(fourWeeksBefore.getDate() - 28);
+ items.push({ id: '2', task: 'Verify FAFSA is on file for current aid year', deadline: fourWeeksBefore.toISOString().split('T')[0], done: false });
+ items.push({ id: '3', task: 'Register for summer courses (minimum credits for aid eligibility)', deadline: fourWeeksBefore.toISOString().split('T')[0], done: false, risk: enrollment === 'less' ? 'Below half-time: you may not qualify for most federal aid' : undefined });
+
+ const twoWeeksBefore = new Date(start);
+ twoWeeksBefore.setDate(twoWeeksBefore.getDate() - 14);
+ items.push({ id: '4', task: 'Check financial aid portal for summer award letter', deadline: twoWeeksBefore.toISOString().split('T')[0], done: false });
+ items.push({ id: '5', task: 'Accept/decline summer aid awards', deadline: twoWeeksBefore.toISOString().split('T')[0], done: false });
+
+ items.push({ id: '6', task: 'Confirm enrollment is at required intensity for aid', deadline: sessionStart, done: false, risk: enrollment === 'less' ? 'Grace period on loans may end if below half-time' : undefined });
+
+ const twoWeeksAfter = new Date(start);
+ twoWeeksAfter.setDate(twoWeeksAfter.getDate() + 14);
+ items.push({ id: '7', task: 'Verify aid has disbursed to your student account', deadline: twoWeeksAfter.toISOString().split('T')[0], done: false });
+
+ setChecklist(items);
+ setShowChecklist(true);
+ }
+
+ function toggleItem(id: string) {
+ setChecklist(checklist.map(item => item.id === id ? { ...item, done: !item.done } : item));
+ }
+
+ return (
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <div className="max-w-3xl mb-10">
+ <div className="flex items-center gap-3 mb-4">
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-amber-500/10 text-amber-600">
+ <Sun className="w-5 h-5" />
+ </div>
+ <h1 className="text-3xl font-bold">Summer Aid Planner</h1>
+ </div>
+ <p className="text-gray-600 text-lg leading-relaxed">
+ Plan for summer enrollment. See what forms to submit, by when, and understand
+ how enrollment intensity affects your financial aid and loan status.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+ <div className="lg:col-span-1">
+ <div className="bg-white rounded-xl border border-gray-200 p-6 sticky top-6 space-y-4">
+ <h2 className="text-lg font-semibold">Summer Details</h2>
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">School Name</label>
+ <input type="text" value={school} placeholder="e.g. USC, UCLA, NYU" onChange={(e) => setSchool(e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500" />
+ </div>
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Session Start Date</label>
+ <input type="date" value={sessionStart} onChange={(e) => setSessionStart(e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500" />
+ </div>
+ <div>
+ <label className="block text-sm font-medium text-gray-700 mb-1">Enrollment Intensity</label>
+ <select value={enrollment} onChange={(e) => setEnrollment(e.target.value as typeof enrollment)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500">
+ <option value="full">Full-time (12+ credits)</option>
+ <option value="half">Half-time (6-11 credits)</option>
+ <option value="less">Less than half-time (<6 credits)</option>
+ </select>
+ </div>
+ <button onClick={generateChecklist}
+ className="w-full mt-2 px-6 py-3 bg-amber-500 hover:bg-amber-600 text-white font-semibold rounded-lg transition-colors">
+ Generate Checklist
+ </button>
+ </div>
+ </div>
+
+ <div className="lg:col-span-2 space-y-6">
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <h3 className="font-semibold mb-4 flex items-center gap-2">
+ <BookOpen className="w-5 h-5 text-amber-500" /> Enrollment Impact
+ </h3>
+ <div className="space-y-3 text-sm">
+ <div className={`p-3 rounded-lg ${enrollment === 'full' ? 'bg-green-50 border border-green-200' : 'bg-gray-50'}`}>
+ <span className="font-medium">Full-time (12+ credits):</span> Eligible for maximum aid, loans remain in grace/deferment.
+ </div>
+ <div className={`p-3 rounded-lg ${enrollment === 'half' ? 'bg-amber-50 border border-amber-200' : 'bg-gray-50'}`}>
+ <span className="font-medium">Half-time (6-11 credits):</span> Eligible for most aid. Loans stay in grace period. May receive reduced amounts.
+ </div>
+ <div className={`p-3 rounded-lg ${enrollment === 'less' ? 'bg-red-50 border border-red-200' : 'bg-gray-50'}`}>
+ <span className="font-medium">Less than half-time:</span> Limited aid eligibility. Loan grace period may end — repayment could begin.
+ </div>
+ </div>
+ </div>
+
+ {showChecklist ? (
+ <div className="bg-white rounded-xl border border-gray-200 p-6">
+ <h3 className="font-semibold mb-4 flex items-center gap-2">
+ <Calendar className="w-5 h-5 text-amber-500" /> Your Summer Aid Checklist
+ </h3>
+ <div className="space-y-3">
+ {checklist.map((item) => (
+ <div key={item.id} className={`flex items-start gap-3 p-3 rounded-lg border ${item.done ? 'bg-green-50 border-green-200' : 'bg-white border-gray-100'}`}>
+ <button onClick={() => toggleItem(item.id)} className="mt-0.5 shrink-0">
+ {item.done ? <CheckCircle className="w-5 h-5 text-green-500" /> : <div className="w-5 h-5 rounded-full border-2 border-gray-300" />}
+ </button>
+ <div className="flex-1">
+ <p className={`text-sm ${item.done ? 'text-gray-400 line-through' : 'text-gray-800'}`}>{item.task}</p>
+ <p className="text-xs text-gray-400 mt-1">By {item.deadline}</p>
+ {item.risk && (
+ <div className="flex items-center gap-1 mt-2 text-xs text-red-600">
+ <AlertCircle className="w-3 h-3" /> {item.risk}
+ </div>
+ )}
+ </div>
+ </div>
+ ))}
+ </div>
+ <p className="text-xs text-gray-400 mt-4">Dates are estimates. Check your school's financial aid office for exact deadlines.</p>
+ </div>
+ ) : (
+ <div className="bg-gray-50 rounded-xl border border-gray-200 p-12 text-center">
+ <Sun className="w-12 h-12 text-gray-300 mx-auto mb-4" />
+ <p className="text-gray-500 text-lg">Enter your summer details and click “Generate Checklist” to see your personalized timeline.</p>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx
new file mode 100644
index 0000000..8043a31
--- /dev/null
+++ b/src/app/terms/page.tsx
@@ -0,0 +1,51 @@
+export const metadata = {
+ title: 'Terms of Service - StudentLoanTracker',
+};
+
+export default function TermsPage() {
+ return (
+ <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
+ <h1 className="text-3xl font-bold mb-6">Terms of Service</h1>
+
+ <div className="prose prose-gray max-w-none space-y-6 text-gray-700 leading-relaxed">
+ <p>
+ <strong>Effective Date:</strong> March 25, 2026
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">1. Acceptance of Terms</h2>
+ <p>
+ By accessing or using StudentLoanTracker, you agree to be bound by these Terms
+ of Service. If you do not agree, do not use the service.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">2. Description of Service</h2>
+ <p>
+ StudentLoanTracker provides free tools for student loan repayment estimation,
+ PSLF progress tracking, servicer information, and summer aid planning. These
+ tools provide estimates only and are not substitutes for official guidance from
+ your loan servicer or the U.S. Department of Education.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">3. No Financial Advice</h2>
+ <p>
+ This service does not provide financial, legal, or tax advice. All calculations
+ are estimates. Consult a qualified professional for personalized guidance.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">4. User Accounts</h2>
+ <p>
+ Guest mode requires no account and stores no data. Optional accounts allow data
+ persistence. You are responsible for maintaining the security of your account
+ credentials.
+ </p>
+
+ <h2 className="text-xl font-semibold text-gray-900 mt-8">5. Limitation of Liability</h2>
+ <p>
+ StudentLoanTracker is provided "as is" without warranty. We are not liable for
+ any damages arising from the use of calculations, estimates, or information
+ provided by this service.
+ </p>
+ </div>
+ </div>
+ );
+}
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..24c8265
--- /dev/null
+++ b/src/components/layout/Footer.tsx
@@ -0,0 +1,53 @@
+import Link from 'next/link';
+
+export default function Footer() {
+ return (
+ <footer className="bg-[#0a1220] border-t border-[#1a2d47] mt-auto">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
+ {/* About */}
+ <div>
+ <h3 className="text-white font-semibold mb-3">StudentLoanTracker</h3>
+ <p className="text-gray-400 text-sm leading-relaxed">
+ Free tools to help borrowers understand repayment options,
+ track PSLF progress, and plan for summer enrollment gaps.
+ </p>
+ </div>
+
+ {/* Links */}
+ <div>
+ <h3 className="text-white font-semibold mb-3">Tools</h3>
+ <ul className="space-y-2 text-sm">
+ <li><Link href="/calculator" className="text-gray-400 hover:text-teal-400 transition-colors">Repayment Calculators</Link></li>
+ <li><Link href="/loans" className="text-gray-400 hover:text-teal-400 transition-colors">Loan Intake</Link></li>
+ <li><Link href="/pslf" className="text-gray-400 hover:text-teal-400 transition-colors">PSLF Tracker</Link></li>
+ <li><Link href="/servicers" className="text-gray-400 hover:text-teal-400 transition-colors">Servicer Directory</Link></li>
+ <li><Link href="/summer-aid" className="text-gray-400 hover:text-teal-400 transition-colors">Summer Aid Planner</Link></li>
+ </ul>
+ </div>
+
+ {/* Legal */}
+ <div>
+ <h3 className="text-white font-semibold mb-3">Legal</h3>
+ <ul className="space-y-2 text-sm">
+ <li><Link href="/terms" className="text-gray-400 hover:text-teal-400 transition-colors">Terms of Service</Link></li>
+ <li><Link href="/privacy" className="text-gray-400 hover:text-teal-400 transition-colors">Privacy Policy</Link></li>
+ <li><Link href="/disclaimer" className="text-gray-400 hover:text-teal-400 transition-colors">Disclaimer</Link></li>
+ </ul>
+ </div>
+ </div>
+
+ <div className="mt-8 pt-6 border-t border-[#1a2d47]">
+ <p className="text-gray-500 text-xs text-center leading-relaxed">
+ Not affiliated with the U.S. Department of Education. Not legal or financial advice.
+ All calculations are estimates and may not reflect your actual repayment terms.
+ Consult a qualified financial advisor for personalized guidance.
+ </p>
+ <p className="text-gray-600 text-xs text-center mt-2">
+ © {new Date().getFullYear()} StudentLoanTracker. All rights reserved.
+ </p>
+ </div>
+ </div>
+ </footer>
+ );
+}
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
new file mode 100644
index 0000000..e3806b9
--- /dev/null
+++ b/src/components/layout/Navbar.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import Link from 'next/link';
+import { useState } from 'react';
+import { Menu, X, GraduationCap } from 'lucide-react';
+
+const navLinks = [
+ { href: '/', label: 'Home' },
+ { href: '/calculator', label: 'Calculators' },
+ { href: '/loans', label: 'Loans' },
+ { href: '/pslf', label: 'PSLF Tracker' },
+ { href: '/servicers', label: 'Servicers' },
+ { href: '/summer-aid', label: 'Summer Aid' },
+];
+
+export default function Navbar() {
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ return (
+ <nav className="bg-[#0f1b2d] border-b border-[#1a2d47] sticky top-0 z-50">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="flex items-center justify-between h-16">
+ {/* Logo */}
+ <Link href="/" className="flex items-center gap-2 text-white font-bold text-lg">
+ <GraduationCap className="w-6 h-6 text-teal-400" />
+ <span>StudentLoanTracker</span>
+ </Link>
+
+ {/* Desktop links */}
+ <div className="hidden md:flex items-center gap-1">
+ {navLinks.map((link) => (
+ <Link
+ key={link.href}
+ href={link.href}
+ className="px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-[#1a2d47] transition-colors"
+ >
+ {link.label}
+ </Link>
+ ))}
+ </div>
+
+ {/* Mobile menu button */}
+ <button
+ onClick={() => setMobileOpen(!mobileOpen)}
+ className="md:hidden p-2 rounded-md text-gray-400 hover:text-white hover:bg-[#1a2d47]"
+ aria-label="Toggle navigation menu"
+ >
+ {mobileOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
+ </button>
+ </div>
+ </div>
+
+ {/* Mobile menu */}
+ {mobileOpen && (
+ <div className="md:hidden bg-[#0f1b2d] border-t border-[#1a2d47]">
+ <div className="px-2 pt-2 pb-3 space-y-1">
+ {navLinks.map((link) => (
+ <Link
+ key={link.href}
+ href={link.href}
+ onClick={() => setMobileOpen(false)}
+ className="block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-[#1a2d47] transition-colors"
+ >
+ {link.label}
+ </Link>
+ ))}
+ </div>
+ </div>
+ )}
+ </nav>
+ );
+}
diff --git a/src/components/ui/.gitkeep b/src/components/ui/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/calculators/comparison.ts b/src/lib/calculators/comparison.ts
new file mode 100644
index 0000000..7c2c640
--- /dev/null
+++ b/src/lib/calculators/comparison.ts
@@ -0,0 +1,76 @@
+/**
+ * Plan Comparison Engine
+ *
+ * Runs all IDR calculators and produces a unified comparison
+ * with recommendations for lowest monthly, lowest total cost,
+ * and fastest payoff.
+ */
+
+import { LoanInputs, PlanResult, ComparisonResult } from './types';
+import { calculateSAVE } from './save-calculator';
+import { calculateIBR } from './ibr-calculator';
+import { calculatePAYE } from './paye-calculator';
+import { calculateICR } from './icr-calculator';
+
+/**
+ * Calculate the 10-year Standard repayment plan for baseline comparison.
+ */
+function calculateStandard(inputs: LoanInputs): PlanResult {
+ const { totalBalance, weightedRate } = inputs;
+ const monthlyRate = weightedRate / 12;
+ const months = 120;
+
+ const monthlyPayment =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -months))
+ : totalBalance / months;
+
+ const totalPaid = monthlyPayment * months;
+
+ return {
+ planName: 'Standard (10-Year)',
+ monthlyPayment: Math.round(monthlyPayment * 100) / 100,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round((totalPaid - totalBalance) * 100) / 100,
+ forgivenessAmount: 0,
+ forgivenessMonths: 0,
+ repaymentMonths: months,
+ discretionaryIncome: 0,
+ povertyGuideline: 0,
+ };
+}
+
+/**
+ * Compare all available repayment plans.
+ */
+export function compareAllPlans(inputs: LoanInputs): ComparisonResult {
+ const plans: PlanResult[] = [
+ calculateStandard(inputs),
+ calculateSAVE(inputs),
+ calculateIBR(inputs, true), // New IBR
+ calculateIBR(inputs, false), // Old IBR
+ calculatePAYE(inputs),
+ calculateICR(inputs),
+ ];
+
+ // Find recommendations
+ const lowestMonthly = plans.reduce((min, p) =>
+ p.monthlyPayment < min.monthlyPayment ? p : min
+ );
+
+ const lowestTotal = plans.reduce((min, p) =>
+ p.totalPaid < min.totalPaid ? p : min
+ );
+
+ const fastestPayoff = plans.reduce((min, p) =>
+ p.repaymentMonths < min.repaymentMonths ? p : min
+ );
+
+ return {
+ inputs,
+ plans,
+ recommended: lowestTotal.planName,
+ lowestMonthly: lowestMonthly.planName,
+ fastestPayoff: fastestPayoff.planName,
+ };
+}
diff --git a/src/lib/calculators/ibr-calculator.ts b/src/lib/calculators/ibr-calculator.ts
new file mode 100644
index 0000000..cd82117
--- /dev/null
+++ b/src/lib/calculators/ibr-calculator.ts
@@ -0,0 +1,84 @@
+/**
+ * IBR Plan Calculator (Income-Based Repayment)
+ *
+ * Two variants:
+ * - New IBR (loans after July 1, 2014): 10% of discretionary income, 20-year forgiveness
+ * - Old IBR (loans before July 1, 2014): 15% of discretionary income, 25-year forgiveness
+ *
+ * Key rules:
+ * - Discretionary income = AGI - 150% of poverty guideline
+ * - Payment capped at 10-year Standard payment
+ * - Must demonstrate partial financial hardship to enroll
+ * - Interest capitalizes when leaving IBR or losing hardship status
+ */
+
+import { LoanInputs, PlanResult, MonthlySnapshot } from './types';
+import { getDiscretionaryIncome, getPovertyGuideline, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+
+export function calculateIBR(inputs: LoanInputs, isNewBorrower: boolean = true): PlanResult {
+ const { totalBalance, weightedRate, agi, familySize, state } = inputs;
+
+ const paymentRate = isNewBorrower ? 0.10 : 0.15;
+ const forgivenessMonths = isNewBorrower ? 240 : 300; // 20 or 25 years
+
+ const povertyGuideline = getPovertyGuideline(familySize, state);
+ const discretionaryIncome = getDiscretionaryIncome(
+ agi, familySize, state, POVERTY_MULTIPLIERS.IBR
+ );
+
+ const monthlyPayment = Math.max(0, (discretionaryIncome * paymentRate) / 12);
+
+ // 10-year Standard payment cap
+ const monthlyRate = weightedRate / 12;
+ const standardPayment =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -120))
+ : totalBalance / 120;
+
+ const cappedPayment = Math.min(monthlyPayment, standardPayment);
+
+ // Simulate — IBR DOES capitalize unpaid interest (unlike SAVE)
+ let balance = totalBalance;
+ let totalPaid = 0;
+ let totalInterest = 0;
+ const schedule: MonthlySnapshot[] = [];
+
+ for (let month = 1; month <= forgivenessMonths; month++) {
+ const interestThisMonth = balance * monthlyRate;
+ const payment = Math.min(cappedPayment, balance + interestThisMonth);
+ const principalPaid = payment - interestThisMonth;
+
+ totalPaid += payment;
+ totalInterest += interestThisMonth;
+
+ if (principalPaid >= 0) {
+ balance = Math.max(0, balance - principalPaid);
+ } else {
+ // Unpaid interest capitalizes (adds to balance)
+ balance += Math.abs(principalPaid);
+ }
+
+ schedule.push({
+ month,
+ payment: Math.round(payment * 100) / 100,
+ principal: Math.round(Math.max(0, principalPaid) * 100) / 100,
+ interest: Math.round(interestThisMonth * 100) / 100,
+ balance: Math.round(balance * 100) / 100,
+ });
+
+ if (balance <= 0) break;
+ }
+
+ return {
+ planName: isNewBorrower ? 'IBR (New)' : 'IBR (Old)',
+ monthlyPayment: Math.round(cappedPayment * 100) / 100,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round(totalInterest * 100) / 100,
+ forgivenessAmount: Math.round(Math.max(0, balance) * 100) / 100,
+ forgivenessMonths,
+ repaymentMonths: schedule.length,
+ discretionaryIncome: Math.round(discretionaryIncome * 100) / 100,
+ povertyGuideline,
+ paymentSchedule: schedule,
+ };
+}
diff --git a/src/lib/calculators/icr-calculator.ts b/src/lib/calculators/icr-calculator.ts
new file mode 100644
index 0000000..3c21a07
--- /dev/null
+++ b/src/lib/calculators/icr-calculator.ts
@@ -0,0 +1,84 @@
+/**
+ * ICR Plan Calculator (Income-Contingent Repayment)
+ *
+ * Key rules:
+ * - Payment is the LESSER of:
+ * a) 20% of discretionary income (AGI - 100% of poverty guideline)
+ * b) Fixed 12-year payment adjusted by income percentage factor
+ * - 25-year forgiveness (300 months)
+ * - Only IDR plan available for Parent PLUS (after consolidation)
+ * - Interest capitalizes if payment doesn't cover it
+ * - No payment cap relative to Standard plan
+ */
+
+import { LoanInputs, PlanResult, MonthlySnapshot } from './types';
+import { getDiscretionaryIncome, getPovertyGuideline, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+
+const ICR_PAYMENT_RATE = 0.20;
+const FORGIVENESS_MONTHS = 300; // 25 years
+
+export function calculateICR(inputs: LoanInputs): PlanResult {
+ const { totalBalance, weightedRate, agi, familySize, state } = inputs;
+
+ const povertyGuideline = getPovertyGuideline(familySize, state);
+ const discretionaryIncome = getDiscretionaryIncome(
+ agi, familySize, state, POVERTY_MULTIPLIERS.ICR
+ );
+
+ // Option A: 20% of discretionary income / 12
+ const optionA = Math.max(0, (discretionaryIncome * ICR_PAYMENT_RATE) / 12);
+
+ // Option B: 12-year fixed payment (amortized over 12 years × income percentage factor)
+ // Simplified: use standard 12-year amortization
+ const monthlyRate = weightedRate / 12;
+ const optionB =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -144))
+ : totalBalance / 144;
+
+ // ICR payment = lesser of the two options
+ const monthlyPayment = Math.min(optionA, optionB);
+
+ let balance = totalBalance;
+ let totalPaid = 0;
+ let totalInterest = 0;
+ const schedule: MonthlySnapshot[] = [];
+
+ for (let month = 1; month <= FORGIVENESS_MONTHS; month++) {
+ const interestThisMonth = balance * monthlyRate;
+ const payment = Math.min(monthlyPayment, balance + interestThisMonth);
+ const principalPaid = payment - interestThisMonth;
+
+ totalPaid += payment;
+ totalInterest += interestThisMonth;
+
+ if (principalPaid >= 0) {
+ balance = Math.max(0, balance - principalPaid);
+ } else {
+ balance += Math.abs(principalPaid);
+ }
+
+ schedule.push({
+ month,
+ payment: Math.round(payment * 100) / 100,
+ principal: Math.round(Math.max(0, principalPaid) * 100) / 100,
+ interest: Math.round(interestThisMonth * 100) / 100,
+ balance: Math.round(balance * 100) / 100,
+ });
+
+ if (balance <= 0) break;
+ }
+
+ return {
+ planName: 'ICR',
+ monthlyPayment: Math.round(monthlyPayment * 100) / 100,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round(totalInterest * 100) / 100,
+ forgivenessAmount: Math.round(Math.max(0, balance) * 100) / 100,
+ forgivenessMonths: FORGIVENESS_MONTHS,
+ repaymentMonths: schedule.length,
+ discretionaryIncome: Math.round(discretionaryIncome * 100) / 100,
+ povertyGuideline,
+ paymentSchedule: schedule,
+ };
+}
diff --git a/src/lib/calculators/index.ts b/src/lib/calculators/index.ts
new file mode 100644
index 0000000..a28a7ec
--- /dev/null
+++ b/src/lib/calculators/index.ts
@@ -0,0 +1,17 @@
+export { calculateSAVE } from './save-calculator';
+export { calculateIBR } from './ibr-calculator';
+export { calculatePAYE } from './paye-calculator';
+export { calculateICR } from './icr-calculator';
+export { calculatePSLF } from './pslf-calculator';
+export type { PSLFResult } from './pslf-calculator';
+export { compareAllPlans } from './comparison';
+export { getPovertyGuideline, getDiscretionaryIncome, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+export type {
+ LoanInputs,
+ IndividualLoan,
+ PlanResult,
+ MonthlySnapshot,
+ ComparisonResult,
+ FilingStatus,
+ EmployerType,
+} from './types';
diff --git a/src/lib/calculators/paye-calculator.ts b/src/lib/calculators/paye-calculator.ts
new file mode 100644
index 0000000..b7b736f
--- /dev/null
+++ b/src/lib/calculators/paye-calculator.ts
@@ -0,0 +1,80 @@
+/**
+ * PAYE Plan Calculator (Pay As You Earn)
+ *
+ * Key rules:
+ * - 10% of discretionary income (AGI - 150% poverty line)
+ * - Payment capped at 10-year Standard payment
+ * - 20-year forgiveness (240 months)
+ * - Only available to "new borrowers" as of Oct 1, 2007 with loans after Oct 1, 2011
+ * - Interest capitalizes if payment doesn't cover interest
+ * - Cap on capitalized interest: original balance (subsidized loans, first 3 years)
+ */
+
+import { LoanInputs, PlanResult, MonthlySnapshot } from './types';
+import { getDiscretionaryIncome, getPovertyGuideline, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+
+const PAYE_PAYMENT_RATE = 0.10;
+const FORGIVENESS_MONTHS = 240; // 20 years
+
+export function calculatePAYE(inputs: LoanInputs): PlanResult {
+ const { totalBalance, weightedRate, agi, familySize, state } = inputs;
+
+ const povertyGuideline = getPovertyGuideline(familySize, state);
+ const discretionaryIncome = getDiscretionaryIncome(
+ agi, familySize, state, POVERTY_MULTIPLIERS.PAYE
+ );
+
+ const monthlyPayment = Math.max(0, (discretionaryIncome * PAYE_PAYMENT_RATE) / 12);
+
+ const monthlyRate = weightedRate / 12;
+ const standardPayment =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -120))
+ : totalBalance / 120;
+
+ const cappedPayment = Math.min(monthlyPayment, standardPayment);
+
+ let balance = totalBalance;
+ let totalPaid = 0;
+ let totalInterest = 0;
+ const schedule: MonthlySnapshot[] = [];
+
+ for (let month = 1; month <= FORGIVENESS_MONTHS; month++) {
+ const interestThisMonth = balance * monthlyRate;
+ const payment = Math.min(cappedPayment, balance + interestThisMonth);
+ const principalPaid = payment - interestThisMonth;
+
+ totalPaid += payment;
+ totalInterest += interestThisMonth;
+
+ if (principalPaid >= 0) {
+ balance = Math.max(0, balance - principalPaid);
+ } else {
+ // Interest capitalizes
+ balance += Math.abs(principalPaid);
+ }
+
+ schedule.push({
+ month,
+ payment: Math.round(payment * 100) / 100,
+ principal: Math.round(Math.max(0, principalPaid) * 100) / 100,
+ interest: Math.round(interestThisMonth * 100) / 100,
+ balance: Math.round(balance * 100) / 100,
+ });
+
+ if (balance <= 0) break;
+ }
+
+ return {
+ planName: 'PAYE',
+ monthlyPayment: Math.round(cappedPayment * 100) / 100,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round(totalInterest * 100) / 100,
+ forgivenessAmount: Math.round(Math.max(0, balance) * 100) / 100,
+ forgivenessMonths: FORGIVENESS_MONTHS,
+ repaymentMonths: schedule.length,
+ discretionaryIncome: Math.round(discretionaryIncome * 100) / 100,
+ povertyGuideline,
+ paymentSchedule: schedule,
+ };
+}
diff --git a/src/lib/calculators/poverty-guidelines.ts b/src/lib/calculators/poverty-guidelines.ts
new file mode 100644
index 0000000..255d2d3
--- /dev/null
+++ b/src/lib/calculators/poverty-guidelines.ts
@@ -0,0 +1,64 @@
+/**
+ * 2026 Federal Poverty Guidelines (HHS)
+ * Source: Federal Register / HHS ASPE
+ *
+ * The poverty guideline is used to calculate "discretionary income"
+ * for income-driven repayment plans:
+ * - SAVE/IBR/PAYE: AGI minus 225% of poverty guideline (SAVE uses 225%, others 150%)
+ * - ICR: AGI minus 100% of poverty guideline
+ *
+ * Update annually when HHS publishes new guidelines (typically January).
+ * Alaska and Hawaii have separate, higher guidelines.
+ */
+
+interface PovertyData {
+ baseAmount: number; // For 1-person household
+ perAdditional: number; // Additional amount per person beyond 1
+}
+
+const POVERTY_2026: Record<string, PovertyData> = {
+ // 48 contiguous states + DC
+ default: { baseAmount: 15650, perAdditional: 5530 },
+ // Alaska
+ AK: { baseAmount: 19560, perAdditional: 6910 },
+ // Hawaii
+ HI: { baseAmount: 18010, perAdditional: 6360 },
+};
+
+/**
+ * Get the federal poverty guideline for a given family size and state.
+ */
+export function getPovertyGuideline(familySize: number, state: string = 'CA'): number {
+ const upperState = state.toUpperCase();
+ const data = POVERTY_2026[upperState] || POVERTY_2026.default;
+ const size = Math.max(1, Math.round(familySize));
+ return data.baseAmount + data.perAdditional * (size - 1);
+}
+
+/**
+ * Calculate discretionary income.
+ *
+ * @param agi - Adjusted gross income
+ * @param familySize - Household size
+ * @param state - 2-letter state code
+ * @param povertyMultiplier - 1.5 for most IDR plans, 2.25 for SAVE, 1.0 for ICR
+ */
+export function getDiscretionaryIncome(
+ agi: number,
+ familySize: number,
+ state: string,
+ povertyMultiplier: number
+): number {
+ const guideline = getPovertyGuideline(familySize, state);
+ return Math.max(0, agi - guideline * povertyMultiplier);
+}
+
+/**
+ * Poverty multipliers by plan type.
+ */
+export const POVERTY_MULTIPLIERS: Record<string, number> = {
+ SAVE: 2.25, // 225% of poverty line
+ IBR: 1.5, // 150% of poverty line
+ PAYE: 1.5, // 150% of poverty line
+ ICR: 1.0, // 100% of poverty line
+};
diff --git a/src/lib/calculators/pslf-calculator.ts b/src/lib/calculators/pslf-calculator.ts
new file mode 100644
index 0000000..25e2a54
--- /dev/null
+++ b/src/lib/calculators/pslf-calculator.ts
@@ -0,0 +1,116 @@
+/**
+ * PSLF Calculator (Public Service Loan Forgiveness)
+ *
+ * Key rules:
+ * - 120 qualifying monthly payments (10 years)
+ * - Must be on qualifying IDR plan OR 10-year Standard
+ * - Must work full-time for qualifying employer (government, 501(c)(3) nonprofit)
+ * - Remaining balance forgiven tax-free after 120 payments
+ * - Can combine with any IDR plan (SAVE, IBR, PAYE, ICR)
+ */
+
+import { LoanInputs, PlanResult } from './types';
+import { getDiscretionaryIncome, getPovertyGuideline, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+
+const PSLF_QUALIFYING_PAYMENTS = 120;
+
+interface PSLFInputs extends LoanInputs {
+ qualifyingPayments?: number; // Already-made qualifying payments
+ selectedPlan?: 'SAVE' | 'IBR' | 'PAYE' | 'ICR' | 'Standard';
+}
+
+export interface PSLFResult extends PlanResult {
+ paymentsRemaining: number;
+ estimatedForgivenessDate: string; // ISO date string
+ monthlyPaymentByPlan: Record<string, number>;
+}
+
+export function calculatePSLF(inputs: PSLFInputs): PSLFResult {
+ const {
+ totalBalance,
+ weightedRate,
+ agi,
+ familySize,
+ state,
+ qualifyingPayments = 0,
+ selectedPlan = 'SAVE',
+ } = inputs;
+
+ const paymentsRemaining = Math.max(0, PSLF_QUALIFYING_PAYMENTS - qualifyingPayments);
+
+ // Calculate monthly payment under each IDR plan
+ const monthlyRate = weightedRate / 12;
+ const standardPayment =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -120))
+ : totalBalance / 120;
+
+ const povertyGuideline = getPovertyGuideline(familySize, state);
+
+ const planPayments: Record<string, number> = {};
+
+ // SAVE: 5% of DI (225% poverty) / 12
+ const diSAVE = getDiscretionaryIncome(agi, familySize, state, POVERTY_MULTIPLIERS.SAVE);
+ planPayments.SAVE = Math.round(Math.min((diSAVE * 0.05) / 12, standardPayment) * 100) / 100;
+
+ // IBR: 10% of DI (150% poverty) / 12
+ const diIBR = getDiscretionaryIncome(agi, familySize, state, POVERTY_MULTIPLIERS.IBR);
+ planPayments.IBR = Math.round(Math.min((diIBR * 0.10) / 12, standardPayment) * 100) / 100;
+
+ // PAYE: 10% of DI (150% poverty) / 12
+ planPayments.PAYE = Math.round(Math.min((diIBR * 0.10) / 12, standardPayment) * 100) / 100;
+
+ // ICR: 20% of DI (100% poverty) / 12
+ const diICR = getDiscretionaryIncome(agi, familySize, state, POVERTY_MULTIPLIERS.ICR);
+ planPayments.ICR = Math.round(Math.min((diICR * 0.20) / 12, standardPayment) * 100) / 100;
+
+ // Standard (10-year)
+ planPayments.Standard = Math.round(standardPayment * 100) / 100;
+
+ const monthlyPayment = planPayments[selectedPlan] || planPayments.SAVE;
+
+ // Simulate remaining payments
+ let balance = totalBalance;
+ let totalPaid = 0;
+ let totalInterest = 0;
+
+ for (let month = 1; month <= paymentsRemaining; month++) {
+ const interestThisMonth = balance * monthlyRate;
+ const payment = Math.min(monthlyPayment, balance + interestThisMonth);
+ const principalPaid = payment - interestThisMonth;
+
+ totalPaid += payment;
+ totalInterest += interestThisMonth;
+
+ // SAVE: no capitalization. Others: capitalize.
+ if (selectedPlan === 'SAVE') {
+ balance = Math.max(0, balance - Math.max(0, principalPaid));
+ } else if (principalPaid >= 0) {
+ balance = Math.max(0, balance - principalPaid);
+ } else {
+ balance += Math.abs(principalPaid);
+ }
+
+ if (balance <= 0) break;
+ }
+
+ // Estimate forgiveness date
+ const now = new Date();
+ const forgivenessDate = new Date(now);
+ forgivenessDate.setMonth(forgivenessDate.getMonth() + paymentsRemaining);
+
+ return {
+ planName: `PSLF (${selectedPlan})`,
+ monthlyPayment,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round(totalInterest * 100) / 100,
+ forgivenessAmount: Math.round(Math.max(0, balance) * 100) / 100,
+ forgivenessMonths: PSLF_QUALIFYING_PAYMENTS,
+ repaymentMonths: paymentsRemaining,
+ discretionaryIncome: Math.round(diSAVE * 100) / 100,
+ povertyGuideline,
+ paymentsRemaining,
+ estimatedForgivenessDate: forgivenessDate.toISOString().split('T')[0],
+ monthlyPaymentByPlan: planPayments,
+ };
+}
diff --git a/src/lib/calculators/save-calculator.ts b/src/lib/calculators/save-calculator.ts
new file mode 100644
index 0000000..9e61a98
--- /dev/null
+++ b/src/lib/calculators/save-calculator.ts
@@ -0,0 +1,105 @@
+/**
+ * SAVE Plan Calculator (Saving on a Valuable Education)
+ *
+ * Key rules:
+ * - Discretionary income = AGI - 225% of poverty guideline
+ * - Undergraduate: 5% of discretionary income ÷ 12
+ * - Graduate: 10% of discretionary income ÷ 12 (blended for mixed loans)
+ * - Payment capped: never more than 10-year Standard payment
+ * - Forgiveness: 20 years (undergrad only) or 25 years (any grad loans)
+ * - Interest subsidy: if payment < monthly interest, unpaid interest does NOT capitalize
+ * - Balances ≤ $12,000: eligible for forgiveness after 10 years
+ */
+
+import { LoanInputs, PlanResult, MonthlySnapshot } from './types';
+import { getDiscretionaryIncome, getPovertyGuideline, POVERTY_MULTIPLIERS } from './poverty-guidelines';
+
+const SAVE_PAYMENT_RATE_UNDERGRAD = 0.05;
+const SAVE_PAYMENT_RATE_GRAD = 0.10;
+const FORGIVENESS_MONTHS_UNDERGRAD = 240; // 20 years
+const FORGIVENESS_MONTHS_GRAD = 300; // 25 years
+const EARLY_FORGIVENESS_THRESHOLD = 12000;
+const EARLY_FORGIVENESS_MONTHS = 120; // 10 years
+
+export function calculateSAVE(inputs: LoanInputs): PlanResult {
+ const { totalBalance, weightedRate, agi, familySize, state } = inputs;
+
+ // Assume undergraduate for simplicity; could be extended with loan-type breakdown
+ const hasGradLoans = inputs.individualLoans?.some(
+ (l) => l.type === 'grad_plus'
+ ) ?? false;
+
+ const paymentRate = hasGradLoans
+ ? SAVE_PAYMENT_RATE_GRAD
+ : SAVE_PAYMENT_RATE_UNDERGRAD;
+
+ const forgivenessMonths = hasGradLoans
+ ? FORGIVENESS_MONTHS_GRAD
+ : FORGIVENESS_MONTHS_UNDERGRAD;
+
+ // Early forgiveness for small balances
+ const effectiveForgivenessMonths =
+ totalBalance <= EARLY_FORGIVENESS_THRESHOLD
+ ? EARLY_FORGIVENESS_MONTHS
+ : forgivenessMonths;
+
+ const povertyGuideline = getPovertyGuideline(familySize, state);
+ const discretionaryIncome = getDiscretionaryIncome(
+ agi, familySize, state, POVERTY_MULTIPLIERS.SAVE
+ );
+
+ // Monthly payment = paymentRate% of discretionary income / 12
+ const monthlyPayment = Math.max(0, (discretionaryIncome * paymentRate) / 12);
+
+ // Cap at 10-year standard payment
+ const monthlyRate = weightedRate / 12;
+ const standardPayment =
+ monthlyRate > 0
+ ? (totalBalance * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -120))
+ : totalBalance / 120;
+
+ const cappedPayment = Math.min(monthlyPayment, standardPayment);
+
+ // Simulate month-by-month (SAVE: unpaid interest does NOT capitalize)
+ let balance = totalBalance;
+ let totalPaid = 0;
+ let totalInterest = 0;
+ const schedule: MonthlySnapshot[] = [];
+
+ for (let month = 1; month <= effectiveForgivenessMonths; month++) {
+ const interestThisMonth = balance * monthlyRate;
+ const payment = Math.min(cappedPayment, balance + interestThisMonth);
+
+ const principalPaid = Math.max(0, payment - interestThisMonth);
+ totalPaid += payment;
+ totalInterest += interestThisMonth;
+
+ // SAVE benefit: unpaid interest does NOT get added to balance
+ balance = Math.max(0, balance - principalPaid);
+
+ schedule.push({
+ month,
+ payment: Math.round(payment * 100) / 100,
+ principal: Math.round(principalPaid * 100) / 100,
+ interest: Math.round(interestThisMonth * 100) / 100,
+ balance: Math.round(balance * 100) / 100,
+ });
+
+ if (balance <= 0) break;
+ }
+
+ const forgivenessAmount = Math.max(0, balance);
+
+ return {
+ planName: 'SAVE',
+ monthlyPayment: Math.round(cappedPayment * 100) / 100,
+ totalPaid: Math.round(totalPaid * 100) / 100,
+ totalInterest: Math.round(totalInterest * 100) / 100,
+ forgivenessAmount: Math.round(forgivenessAmount * 100) / 100,
+ forgivenessMonths: effectiveForgivenessMonths,
+ repaymentMonths: schedule.length,
+ discretionaryIncome: Math.round(discretionaryIncome * 100) / 100,
+ povertyGuideline,
+ paymentSchedule: schedule,
+ };
+}
diff --git a/src/lib/calculators/types.ts b/src/lib/calculators/types.ts
new file mode 100644
index 0000000..cfb765d
--- /dev/null
+++ b/src/lib/calculators/types.ts
@@ -0,0 +1,69 @@
+/**
+ * Shared types for all student loan calculators.
+ *
+ * Design: every calculator accepts a `LoanInputs` object and returns
+ * a plan-specific result that extends `PlanResult`. This keeps the
+ * signature uniform so the comparison engine can iterate over all plans.
+ */
+
+// ── Filing status for tax-related calculations ─────────────────────
+export type FilingStatus = 'single' | 'married_filing_jointly' | 'married_filing_separately' | 'head_of_household';
+
+// ── Employer type for PSLF eligibility ─────────────────────────────
+export type EmployerType = 'government' | 'nonprofit' | 'for_profit' | 'unknown';
+
+// ── Core input that every calculator accepts ───────────────────────
+export interface LoanInputs {
+ totalBalance: number; // Current total loan balance ($)
+ weightedRate: number; // Weighted average interest rate (decimal, e.g. 0.065 = 6.5%)
+ agi: number; // Adjusted gross income ($)
+ familySize: number; // Household size (1+)
+ filingStatus: FilingStatus;
+ state: string; // 2-letter state code (for future state-specific rules)
+ spouseIncome?: number; // Spouse AGI if married filing jointly ($)
+ // Advanced inputs
+ individualLoans?: IndividualLoan[];
+ qualifyingPayments?: number; // PSLF: payments already made toward 120
+ employerType?: EmployerType;
+ yearsInRepayment?: number; // Years already in repayment
+}
+
+export interface IndividualLoan {
+ name: string; // e.g. "Direct Subsidized #1"
+ balance: number;
+ rate: number; // Decimal
+ type: 'subsidized' | 'unsubsidized' | 'grad_plus' | 'parent_plus' | 'consolidated' | 'other';
+ originalBalance?: number;
+ disbursementDate?: string; // ISO date
+}
+
+// ── Standard result shape ──────────────────────────────────────────
+export interface PlanResult {
+ planName: string;
+ monthlyPayment: number; // First-year monthly payment ($)
+ totalPaid: number; // Sum of all payments over repayment period ($)
+ totalInterest: number; // Total interest paid ($)
+ forgivenessAmount: number; // Amount forgiven at end (0 if none)
+ forgivenessMonths: number; // Months until forgiveness (240, 300, or 120 for PSLF)
+ repaymentMonths: number; // Actual months of payments
+ discretionaryIncome: number; // Computed DI for transparency
+ povertyGuideline: number; // Poverty line used
+ paymentSchedule?: MonthlySnapshot[]; // Optional month-by-month breakdown
+}
+
+export interface MonthlySnapshot {
+ month: number;
+ payment: number;
+ principal: number;
+ interest: number;
+ balance: number;
+}
+
+// ── Comparison result ──────────────────────────────────────────────
+export interface ComparisonResult {
+ inputs: LoanInputs;
+ plans: PlanResult[];
+ recommended: string; // planName of lowest total cost
+ lowestMonthly: string; // planName of lowest monthly payment
+ fastestPayoff: string; // planName of shortest repayment
+}
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..d1f4a69
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,8 @@
+import { Pool } from 'pg';
+
+const pool = new Pool({
+ connectionString: process.env.DATABASE_URL || 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified',
+});
+
+export default pool;
+export { pool };
diff --git a/src/lib/file-intel/index.ts b/src/lib/file-intel/index.ts
new file mode 100644
index 0000000..1d363f9
--- /dev/null
+++ b/src/lib/file-intel/index.ts
@@ -0,0 +1,609 @@
+/**
+ * File Intelligence Library
+ *
+ * Client-side file parsing and classification for student loan documents.
+ * All processing happens in the browser — files never leave the user's device.
+ *
+ * Supported formats:
+ * - StudentAid.gov "My Aid Data" TXT export
+ * - CSV/spreadsheet loan data
+ * - PDF documents (servicer statements, ECF results, 1098-E)
+ * - Any text file with loan-related content
+ */
+
+// ── Types ──────────────────────────────────────────────────────────
+
+export interface ParsedLoan {
+ name: string;
+ loanType: string;
+ balance: number;
+ interestRate: number;
+ servicer: string;
+ disbursementDate: string;
+ status: string;
+ outstandingInterest: number;
+}
+
+export type DocumentType =
+ | 'studentaid_txt'
+ | 'csv_loans'
+ | 'ecf_pdf'
+ | 'servicer_statement'
+ | '1098e'
+ | 'fsa_letter'
+ | 'unknown';
+
+export interface ClassificationResult {
+ type: DocumentType;
+ confidence: number; // 0-1
+ label: string; // Human-readable description
+}
+
+export interface ParseResult {
+ classification: ClassificationResult;
+ loans: ParsedLoan[];
+ pslfPayments?: number;
+ rawText: string;
+ warnings: string[];
+}
+
+// ── Classifier ─────────────────────────────────────────────────────
+
+const SIGNATURES: { type: DocumentType; patterns: RegExp[]; label: string; weight: number }[] = [
+ {
+ type: 'studentaid_txt',
+ patterns: [
+ /Federal Student Aid/i,
+ /NSLDS/i,
+ /My Federal Student Aid Data/i,
+ /Loan\s+Type.*Direct/i,
+ /Outstanding\s+Principal\s+Balance/i,
+ /Loan\s+Status.*In\s+Repayment/i,
+ ],
+ label: 'StudentAid.gov Data Export',
+ weight: 0.25,
+ },
+ {
+ type: 'ecf_pdf',
+ patterns: [
+ /Public Service Loan Forgiveness/i,
+ /Employment Certification/i,
+ /ECF/,
+ /qualifying\s+payment/i,
+ /120\s+payments?/i,
+ ],
+ label: 'PSLF Employment Certification Form',
+ weight: 0.25,
+ },
+ {
+ type: 'servicer_statement',
+ patterns: [
+ /Monthly\s+Statement/i,
+ /Payment\s+Due/i,
+ /Account\s+Summary/i,
+ /MOHELA|Nelnet|Aidvantage|Edfinancial|Great\s+Lakes|FedLoan/i,
+ /Minimum\s+Payment/i,
+ /Current\s+Balance/i,
+ ],
+ label: 'Loan Servicer Statement',
+ weight: 0.2,
+ },
+ {
+ type: '1098e',
+ patterns: [
+ /1098-?E/i,
+ /Student\s+Loan\s+Interest\s+Statement/i,
+ /Interest\s+(Received|Paid).*\$/i,
+ ],
+ label: '1098-E Interest Statement',
+ weight: 0.35,
+ },
+ {
+ type: 'fsa_letter',
+ patterns: [
+ /Federal\s+Student\s+Aid/i,
+ /Income-?Driven\s+Repayment/i,
+ /IDR\s+(Application|Recertification)/i,
+ /SAVE\s+Plan|IBR|PAYE|ICR/i,
+ ],
+ label: 'FSA Letter / IDR Notice',
+ weight: 0.2,
+ },
+ {
+ type: 'csv_loans',
+ patterns: [
+ /balance.*rate|rate.*balance/i,
+ /loan.*type.*balance/i,
+ /,\d+\.?\d*,\d+\.?\d*/, // CSV-like numeric fields
+ ],
+ label: 'CSV Loan Data',
+ weight: 0.35,
+ },
+];
+
+/**
+ * Classify a document by analyzing its text content.
+ * Uses weighted pattern matching with confidence scoring.
+ */
+export function classifyDocument(text: string): ClassificationResult {
+ let bestType: DocumentType = 'unknown';
+ let bestScore = 0;
+ let bestLabel = 'Unknown Document';
+
+ for (const sig of SIGNATURES) {
+ const matchCount = sig.patterns.filter((p) => p.test(text)).length;
+ const score = (matchCount / sig.patterns.length) * sig.weight + (matchCount > 0 ? 0.3 : 0);
+
+ if (score > bestScore) {
+ bestScore = score;
+ bestType = sig.type;
+ bestLabel = sig.label;
+ }
+ }
+
+ return {
+ type: bestType,
+ confidence: Math.min(1, bestScore),
+ label: bestScore > 0.2 ? bestLabel : 'Unknown Document',
+ };
+}
+
+// ── Parsers ────────────────────────────────────────────────────────
+
+/**
+ * Parse StudentAid.gov "My Aid Data" TXT export.
+ *
+ * Real format: each line is `"Field Name:Value",`
+ * Loan blocks start with `Loan Type Code:XX` (not `Type Code:` which is aggregate totals).
+ * Servicer is in `Loan Contact Name:` within the contact sub-block after each loan.
+ *
+ * State machine: HEADER → AGGREGATE → LOAN → CONTACT → LOAN → ...
+ */
+export function parseStudentAidTXT(text: string): { loans: ParsedLoan[]; warnings: string[] } {
+ const loans: ParsedLoan[] = [];
+ const warnings: string[] = [];
+ const lines = text.split('\n');
+
+ // Parse "Field Name:Value", lines into key-value pairs
+ function parseLine(raw: string): { key: string; value: string } | null {
+ const trimmed = raw.trim();
+ // Strip surrounding quotes and trailing comma
+ const match = trimmed.match(/^"?([^"]*)"?,?\s*$/);
+ if (!match) return null;
+ const content = match[1];
+ const colonIdx = content.indexOf(':');
+ if (colonIdx < 0) return null;
+ return {
+ key: content.substring(0, colonIdx).trim(),
+ value: content.substring(colonIdx + 1).trim(),
+ };
+ }
+
+ let current: Partial<ParsedLoan> | null = null;
+ let inLoanBlock = false;
+ let schoolName = '';
+
+ for (const line of lines) {
+ const kv = parseLine(line);
+ if (!kv) continue;
+
+ const { key, value } = kv;
+ const k = key.toLowerCase();
+
+ // Detect start of a new LOAN block (not aggregate "Type Code")
+ if (key === 'Loan Type Code') {
+ // Save previous loan
+ if (current && inLoanBlock) {
+ loans.push(finalizeLoan(current));
+ }
+ current = { loanType: detectLoanTypeCode(value) };
+ inLoanBlock = true;
+ schoolName = '';
+ continue;
+ }
+
+ // Skip aggregate "Type Code" lines (no "Loan" prefix)
+ if (key === 'Type Code' || key === 'Type Description') continue;
+
+ if (!inLoanBlock || !current) continue;
+
+ // ── Loan fields ──
+ if (key === 'Loan Type Description') {
+ current.name = value;
+ } else if (key === 'Loan Attending School Name') {
+ schoolName = value;
+ } else if (key === 'Loan Date') {
+ current.disbursementDate = value;
+ } else if (key === 'Loan Outstanding Principal Balance') {
+ current.balance = parseDollar(value);
+ } else if (key === 'Loan Outstanding Interest Balance') {
+ current.outstandingInterest = parseDollar(value);
+ } else if (key === 'Loan Interest Rate' || key === 'Loan Actual Interest Rate') {
+ if (!current.interestRate) { // Use first one encountered
+ current.interestRate = parsePercent(value);
+ }
+ } else if (key === 'Current Loan Status Description') {
+ current.status = value;
+ } else if (k === 'loan status description' && !current.status) {
+ current.status = value;
+ } else if (key === 'Loan Contact Name') {
+ current.servicer = value;
+ } else if (key === 'Loan Repayment Plan Type Code Description') {
+ // Append plan info to name for clarity
+ if (current.name && !current.name.includes('(')) {
+ const school = schoolName ? ` (${titleCase(schoolName)})` : '';
+ current.name = current.name + school;
+ }
+ }
+ }
+
+ // Push last loan
+ if (current && inLoanBlock) {
+ loans.push(finalizeLoan(current));
+ }
+
+ // If the real format wasn't detected, try fallback simple parsing
+ if (loans.length === 0) {
+ return parseSimpleKeyValue(text);
+ }
+
+ const missingBalance = loans.filter((l) => l.balance === 0);
+ if (missingBalance.length > 0) {
+ warnings.push(`${missingBalance.length} loan(s) have $0 balance — verify these are correct.`);
+ }
+
+ return { loans, warnings };
+}
+
+/**
+ * Fallback parser for non-NSLDS text files with simple Key: Value format.
+ */
+function parseSimpleKeyValue(text: string): { loans: ParsedLoan[]; warnings: string[] } {
+ const loans: ParsedLoan[] = [];
+ const warnings: string[] = [];
+ const lines = text.split('\n');
+ let current: Partial<ParsedLoan> | null = null;
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed) continue;
+
+ if (/^(Direct|FFEL|Perkins|Federal)/i.test(trimmed)) {
+ if (current?.name) loans.push(finalizeLoan(current));
+ current = { name: trimmed, loanType: detectLoanType(trimmed) };
+ continue;
+ }
+
+ if (!current) continue;
+
+ const kvMatch = trimmed.match(/^([^:]+?):\s*(.+)$/);
+ if (kvMatch) {
+ const k = kvMatch[1].trim().toLowerCase();
+ const v = kvMatch[2].trim();
+ if (k.includes('balance') || k.includes('principal')) current.balance = parseDollar(v);
+ else if (k.includes('interest rate') || k.includes('rate')) current.interestRate = parsePercent(v);
+ else if (k.includes('servicer')) current.servicer = v;
+ else if (k.includes('status')) current.status = v;
+ else if (k.includes('disburs') || k.includes('loan date')) current.disbursementDate = v;
+ }
+ }
+
+ if (current?.name) loans.push(finalizeLoan(current));
+
+ if (loans.length === 0) {
+ warnings.push('No loan records found. The file format may not be recognized.');
+ }
+ return { loans, warnings };
+}
+
+/** Map NSLDS type codes to human-readable types */
+function detectLoanTypeCode(code: string): string {
+ const map: Record<string, string> = {
+ D1: 'subsidized', D6: 'subsidized',
+ D2: 'unsubsidized', D5: 'consolidated',
+ D3: 'grad_plus', D4: 'parent_plus',
+ // FFEL
+ SF: 'subsidized', SU: 'unsubsidized',
+ GP: 'grad_plus', PP: 'parent_plus',
+ CL: 'consolidated',
+ // Perkins
+ PK: 'perkins',
+ };
+ return map[code.toUpperCase()] || 'other';
+}
+
+function titleCase(s: string): string {
+ return s.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
+}
+
+/**
+ * Parse CSV loan data. Expects header row with columns like:
+ * name/type, balance, rate, servicer, status
+ */
+export function parseCSV(text: string): { loans: ParsedLoan[]; warnings: string[] } {
+ const loans: ParsedLoan[] = [];
+ const warnings: string[] = [];
+ const lines = text.split('\n').filter((l) => l.trim());
+
+ if (lines.length < 2) {
+ warnings.push('CSV file appears empty or has no data rows.');
+ return { loans, warnings };
+ }
+
+ // Parse header
+ const delimiter = lines[0].includes('\t') ? '\t' : ',';
+ const headers = lines[0].split(delimiter).map((h) => h.trim().toLowerCase().replace(/['"]/g, ''));
+
+ // Map columns
+ const colMap: Record<string, number> = {};
+ const aliases: Record<string, string[]> = {
+ name: ['name', 'loan', 'loan name', 'description', 'loan type', 'type'],
+ balance: ['balance', 'amount', 'principal', 'outstanding', 'current balance'],
+ rate: ['rate', 'interest', 'interest rate', 'apr'],
+ servicer: ['servicer', 'loan servicer', 'lender', 'provider'],
+ status: ['status', 'loan status', 'repayment status'],
+ };
+
+ for (const [field, names] of Object.entries(aliases)) {
+ const idx = headers.findIndex((h) => names.some((n) => h.includes(n)));
+ if (idx >= 0) colMap[field] = idx;
+ }
+
+ if (!('balance' in colMap)) {
+ warnings.push('Could not find a "balance" column. Looking for numeric values instead.');
+ }
+
+ // Parse data rows
+ for (let i = 1; i < lines.length; i++) {
+ const cells = lines[i].split(delimiter).map((c) => c.trim().replace(/^["']|["']$/g, ''));
+ if (cells.length < 2) continue;
+
+ const loan: ParsedLoan = {
+ name: cells[colMap.name] || `Loan ${i}`,
+ loanType: detectLoanType(cells[colMap.name] || ''),
+ balance: parseDollar(cells[colMap.balance] || '0'),
+ interestRate: parsePercent(cells[colMap.rate] || '0'),
+ servicer: cells[colMap.servicer] || '',
+ status: cells[colMap.status] || '',
+ disbursementDate: '',
+ outstandingInterest: 0,
+ };
+
+ if (loan.balance > 0 || loan.name) {
+ loans.push(loan);
+ }
+ }
+
+ return { loans, warnings };
+}
+
+/**
+ * Extract text from a PDF file using pdfjs-dist (client-side).
+ * Returns the raw text content of all pages.
+ */
+export async function extractPDFText(file: File): Promise<string> {
+ // Dynamic import to avoid SSR issues
+ const pdfjsLib = await import('pdfjs-dist');
+
+ // Set worker source
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.mjs`;
+
+ const arrayBuffer = await file.arrayBuffer();
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
+
+ const pages: string[] = [];
+ for (let i = 1; i <= pdf.numPages; i++) {
+ const page = await pdf.getPage(i);
+ const content = await page.getTextContent();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const text = content.items
+ .map((item: any) => item.str || '')
+ .join(' ');
+ pages.push(text);
+ }
+
+ return pages.join('\n\n');
+}
+
+/**
+ * Parse a servicer statement (typically PDF text).
+ * Extracts balance, payment due, and servicer name.
+ */
+export function parseServicerStatement(text: string): { loans: ParsedLoan[]; warnings: string[] } {
+ const loans: ParsedLoan[] = [];
+ const warnings: string[] = [];
+
+ // Try to find servicer name
+ const servicerMatch = text.match(/(?:MOHELA|Nelnet|Aidvantage|Edfinancial|Great\s*Lakes|FedLoan|ECSI|Heartland)/i);
+ const servicer = servicerMatch ? servicerMatch[0] : '';
+
+ // Find balance(s)
+ const balanceMatches = text.matchAll(/(?:(?:current|outstanding|principal)\s*balance|balance\s*(?:due|owed)?)[:\s]*\$?([\d,]+\.?\d*)/gi);
+ for (const match of balanceMatches) {
+ const balance = parseDollar(match[1]);
+ if (balance > 100) { // Filter out small noise values
+ loans.push({
+ name: `Loan from ${servicer || 'Statement'}`,
+ loanType: 'unknown',
+ balance,
+ interestRate: 0,
+ servicer,
+ disbursementDate: '',
+ status: 'In Repayment',
+ outstandingInterest: 0,
+ });
+ }
+ }
+
+ // Try to find interest rate
+ const rateMatch = text.match(/(?:interest rate|rate)[:\s]*([\d.]+)\s*%/i);
+ if (rateMatch && loans.length > 0) {
+ loans[0].interestRate = parseFloat(rateMatch[1]);
+ }
+
+ // Find payment amount
+ const paymentMatch = text.match(/(?:payment\s*(?:due|amount)|minimum\s*payment|monthly\s*payment)[:\s]*\$?([\d,]+\.?\d*)/i);
+ if (paymentMatch) {
+ warnings.push(`Monthly payment found: $${parseDollar(paymentMatch[1]).toLocaleString()}`);
+ }
+
+ if (loans.length === 0) {
+ warnings.push('Could not extract loan data from this statement. You may need to enter values manually.');
+ }
+
+ return { loans, warnings };
+}
+
+/**
+ * Parse ECF / PSLF tracking result.
+ * Looks for qualifying payment counts.
+ */
+export function parseECFDocument(text: string): { pslfPayments: number; warnings: string[] } {
+ const warnings: string[] = [];
+
+ // Look for qualifying payment count
+ const paymentPatterns = [
+ /(\d+)\s*(?:qualifying|eligible)\s*payments?/i,
+ /qualifying\s*payments?[:\s]*(\d+)/i,
+ /payment\s*count[:\s]*(\d+)/i,
+ /(\d+)\s*of\s*120/i,
+ ];
+
+ for (const pattern of paymentPatterns) {
+ const match = text.match(pattern);
+ if (match) {
+ const count = parseInt(match[1], 10);
+ if (count >= 0 && count <= 120) {
+ return { pslfPayments: count, warnings };
+ }
+ }
+ }
+
+ warnings.push('Could not find qualifying payment count. Enter it manually on the PSLF Tracker page.');
+ return { pslfPayments: 0, warnings };
+}
+
+/**
+ * Master parse function — classifies the file and routes to the appropriate parser.
+ */
+export async function parseFile(file: File): Promise<ParseResult> {
+ let rawText = '';
+ const warnings: string[] = [];
+
+ // Step 1: Extract text based on file type
+ const ext = file.name.split('.').pop()?.toLowerCase() || '';
+ const mime = file.type;
+
+ if (ext === 'pdf' || mime === 'application/pdf') {
+ try {
+ rawText = await extractPDFText(file);
+ } catch (err) {
+ warnings.push(`PDF extraction failed: ${err instanceof Error ? err.message : 'Unknown error'}. Try a TXT or CSV file instead.`);
+ return {
+ classification: { type: 'unknown', confidence: 0, label: 'Unreadable PDF' },
+ loans: [],
+ rawText: '',
+ warnings,
+ };
+ }
+ } else {
+ // Text-based files (TXT, CSV, TSV)
+ rawText = await file.text();
+ }
+
+ // Step 2: Classify
+ const classification = classifyDocument(rawText);
+
+ // Step 3: Route to appropriate parser
+ let loans: ParsedLoan[] = [];
+ let pslfPayments: number | undefined;
+
+ switch (classification.type) {
+ case 'studentaid_txt': {
+ const result = parseStudentAidTXT(rawText);
+ loans = result.loans;
+ warnings.push(...result.warnings);
+ break;
+ }
+ case 'csv_loans': {
+ const result = parseCSV(rawText);
+ loans = result.loans;
+ warnings.push(...result.warnings);
+ break;
+ }
+ case 'ecf_pdf': {
+ const result = parseECFDocument(rawText);
+ pslfPayments = result.pslfPayments;
+ warnings.push(...result.warnings);
+ break;
+ }
+ case 'servicer_statement': {
+ const result = parseServicerStatement(rawText);
+ loans = result.loans;
+ warnings.push(...result.warnings);
+ break;
+ }
+ case '1098e': {
+ warnings.push('1098-E detected. This shows interest paid for tax purposes, not loan details. Use your servicer statement or StudentAid export for loan data.');
+ break;
+ }
+ default: {
+ // Try all parsers and pick the one that extracts the most
+ const txtResult = parseStudentAidTXT(rawText);
+ const csvResult = parseCSV(rawText);
+ const stmtResult = parseServicerStatement(rawText);
+
+ if (txtResult.loans.length >= csvResult.loans.length && txtResult.loans.length >= stmtResult.loans.length) {
+ loans = txtResult.loans;
+ warnings.push(...txtResult.warnings);
+ } else if (csvResult.loans.length >= stmtResult.loans.length) {
+ loans = csvResult.loans;
+ warnings.push(...csvResult.warnings);
+ } else {
+ loans = stmtResult.loans;
+ warnings.push(...stmtResult.warnings);
+ }
+
+ if (loans.length === 0) {
+ warnings.push('Could not auto-detect document type or extract loan data. You can enter your loans manually.');
+ }
+ }
+ }
+
+ return { classification, loans, pslfPayments, rawText, warnings };
+}
+
+// ── Helpers ────────────────────────────────────────────────────────
+
+function parseDollar(s: string): number {
+ return parseFloat(s.replace(/[$,\s]/g, '')) || 0;
+}
+
+function parsePercent(s: string): number {
+ const n = parseFloat(s.replace(/[%\s]/g, ''));
+ return isNaN(n) ? 0 : n;
+}
+
+function detectLoanType(text: string): string {
+ const t = text.toLowerCase();
+ if (t.includes('subsidized') && !t.includes('unsubsidized')) return 'subsidized';
+ if (t.includes('unsubsidized') || t.includes('unsub')) return 'unsubsidized';
+ if (t.includes('grad plus') || t.includes('grad-plus') || t.includes('graduate plus')) return 'grad_plus';
+ if (t.includes('parent plus') || t.includes('parent-plus')) return 'parent_plus';
+ if (t.includes('consolidat')) return 'consolidated';
+ if (t.includes('perkins')) return 'perkins';
+ return 'other';
+}
+
+function finalizeLoan(partial: Partial<ParsedLoan>): ParsedLoan {
+ return {
+ name: partial.name || 'Unknown Loan',
+ loanType: partial.loanType || 'other',
+ balance: partial.balance || 0,
+ interestRate: partial.interestRate || 0,
+ servicer: partial.servicer || '',
+ disbursementDate: partial.disbursementDate || '',
+ status: partial.status || '',
+ outstandingInterest: partial.outstandingInterest || 0,
+ };
+}
diff --git a/test/sample-loans.csv b/test/sample-loans.csv
new file mode 100644
index 0000000..f320b70
--- /dev/null
+++ b/test/sample-loans.csv
@@ -0,0 +1,8 @@
+name,type,balance,rate,servicer,status
+Direct Subsidized Loan (UO 2018),Direct Subsidized,3112.44,5.05,MOHELA,In Repayment
+Direct Subsidized Loan (PSU 2020),Direct Subsidized,4108.00,2.75,MOHELA,In Repayment
+Direct Unsubsidized Loan (UO 2018),Direct Unsubsidized,5844.17,5.05,MOHELA,In Repayment
+Direct Unsubsidized Loan (PSU 2020),Direct Unsubsidized,8366.91,2.75,MOHELA,In Repayment
+Grad PLUS Loan (PSU 2021),Grad PLUS,21804.33,6.54,MOHELA,In Repayment
+Grad PLUS Loan (PSU 2022),Grad PLUS,20303.67,7.54,MOHELA,In Repayment
+Direct Consolidated Unsubsidized (UO 2019),Direct Consolidated Unsubsidized,8146.15,5.05,Aidvantage,In Repayment
diff --git a/test/sample-servicer-statement.txt b/test/sample-servicer-statement.txt
new file mode 100644
index 0000000..34aa09c
--- /dev/null
+++ b/test/sample-servicer-statement.txt
@@ -0,0 +1,173 @@
+MOHELA
+633 Spirit Drive
+Chesterfield, MO 63005
+1-888-866-4352
+www.mohela.com
+
+
+ACCOUNT STATEMENT
+Statement Date: August 1, 2025
+Account Number: MHLA-7731049
+Payment Due Date: September 1, 2025
+
+
+BORROWER INFORMATION
+------------------------------------------------------------
+Name: Alex R. Martinez
+Address: 4412 Pinecrest Ave
+ Portland, OR 97201
+SSN: XXX-XX-1049
+Email: alex.martinez@email.com
+Phone: (503) 387-1049
+
+
+PAYMENT SUMMARY
+------------------------------------------------------------
+Total Amount Due: $433.00
+Payment Due Date: September 1, 2025
+Current Repayment Plan: Income-Based Repayment (IBR)
+ (Loans 1-4)
+Current Repayment Plan: Standard Repayment
+ (Loans 5-6)
+
+ To avoid late fees, pay by September 1, 2025.
+ Autopay is NOT currently enrolled for this account.
+
+
+LOAN DETAIL
+------------------------------------------------------------
+
+Loan 1 of 6
+ Loan Type: Direct Subsidized Loan
+ Original Principal: $3,500.00
+ Disbursement Date(s): 08/20/2018, 01/15/2019
+ School: University of Oregon
+ Interest Rate: 5.05%
+ Repayment Plan: Income-Based Repayment (IBR)
+ Current Principal Balance: $3,112.44
+ Accrued Interest: $287.11
+ Outstanding Balance: $3,399.55
+ Payment Amount Due: $46.75
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $46.75 on 08/01/2025
+
+Loan 2 of 6
+ Loan Type: Direct Subsidized Loan
+ Original Principal: $4,500.00
+ Disbursement Date(s): 08/25/2020, 01/20/2021
+ School: Portland State University
+ Interest Rate: 2.75%
+ Repayment Plan: Income-Based Repayment (IBR)
+ Current Principal Balance: $4,108.00
+ Accrued Interest: $378.22
+ Outstanding Balance: $4,486.22
+ Payment Amount Due: $46.75
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $46.75 on 08/01/2025
+
+Loan 3 of 6
+ Loan Type: Direct Unsubsidized Loan
+ Original Principal: $6,000.00
+ Disbursement Date(s): 08/20/2018, 01/15/2019
+ School: University of Oregon
+ Interest Rate: 5.05%
+ Repayment Plan: Income-Based Repayment (IBR)
+ Current Principal Balance: $5,844.17
+ Accrued Interest: $703.22
+ Outstanding Balance: $6,547.39
+ Payment Amount Due: $46.75
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $46.75 on 08/01/2025
+
+Loan 4 of 6
+ Loan Type: Direct Unsubsidized Loan
+ Original Principal: $8,000.00
+ Disbursement Date(s): 08/25/2020, 01/20/2021
+ School: Portland State University
+ Interest Rate: 2.75%
+ Repayment Plan: Income-Based Repayment (IBR)
+ Current Principal Balance: $8,366.91
+ Accrued Interest: $940.69
+ Outstanding Balance: $9,307.60
+ Payment Amount Due: $46.75
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $46.75 on 08/01/2025
+
+Loan 5 of 6
+ Loan Type: Direct PLUS Loan (Grad)
+ Original Principal: $20,500.00
+ Disbursement Date(s): 08/25/2021, 01/18/2022
+ School: Portland State University
+ Interest Rate: 6.54%
+ Repayment Plan: Standard Repayment
+ Current Principal Balance: $21,804.33
+ Accrued Interest: $1,894.17
+ Outstanding Balance: $23,698.50
+ Payment Amount Due: $123.00
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $123.00 on 08/01/2025
+
+Loan 6 of 6
+ Loan Type: Direct PLUS Loan (Grad)
+ Original Principal: $21,500.00
+ Disbursement Date(s): 08/22/2022, 01/19/2023
+ School: Portland State University
+ Interest Rate: 7.54%
+ Repayment Plan: Standard Repayment
+ Current Principal Balance: $20,303.67
+ Accrued Interest: $1,526.83
+ Outstanding Balance: $21,830.50
+ Payment Amount Due: $123.00
+ Next Payment Due: September 1, 2025
+ Last Payment Received: $123.00 on 08/01/2025
+
+
+ACCOUNT TOTALS
+------------------------------------------------------------
+ Total Principal Balance: $63,539.52
+ Total Accrued Interest: $5,730.24
+ Total Outstanding Balance: $69,269.76
+ Total Amount Due September 1, 2025: $433.00
+
+
+INTEREST RATE SUMMARY
+------------------------------------------------------------
+ Loan Rate Plan
+ ----------------------------------------------------
+ Sub. Loan (UO 2018) 5.05% IBR
+ Sub. Loan (PSU 2020) 2.75% IBR
+ Unsub. Loan (UO 2018) 5.05% IBR
+ Unsub. Loan (PSU 2020) 2.75% IBR
+ PLUS Loan (PSU 2021) 6.54% Standard
+ PLUS Loan (PSU 2022) 7.54% Standard
+
+
+RECENT PAYMENT HISTORY
+------------------------------------------------------------
+ Date Amount Principal Interest Balance
+ 08/01/2025 $433.00 $246.22 $186.78 $63,539.52
+ 07/01/2025 $433.00 $241.09 $191.91 $63,785.74
+ 06/01/2025 $433.00 $238.77 $194.23 $64,026.83
+ 05/01/2025 $433.00 $236.44 $196.56 $64,265.60
+ 04/01/2025 $433.00 $234.11 $198.89 $64,502.04
+
+
+IMPORTANT NOTICES
+------------------------------------------------------------
+ * Interest accruing during IBR may be capitalized upon
+ plan change or recertification. Your annual income
+ recertification is due by November 15, 2025.
+
+ * Your IBR monthly payment is $187.00 total across
+ Loans 1-4, adjusted based on your last certified AGI
+ of $54,200 (2024 tax return).
+
+ * To make a payment or manage your account online, visit:
+ www.mohela.com or call 1-888-866-4352
+ Mon-Fri 7:00am - 9:00pm CT
+ Sat 8:00am - 5:00pm CT
+
+
+------------------------------------------------------------
+MOHELA | 633 Spirit Drive | Chesterfield, MO 63005
+This communication is from a loan servicer. Account Number: MHLA-7731049
diff --git a/test/sample-studentaid.txt b/test/sample-studentaid.txt
new file mode 100644
index 0000000..f78f58e
--- /dev/null
+++ b/test/sample-studentaid.txt
@@ -0,0 +1,648 @@
+"File Source:U.S. DEPARTMENT OF EDUCATION, NATIONAL STUDENT LOAN DATA SYSTEM (NSLDS)",
+"File Request Date:2025-08-15 09:23:11",
+"Student First Name:ALEX",
+"Student Middle Initial:R",
+"Student Last Name:MARTINEZ",
+"Student Street Address 1:4412 PINECREST AVE",
+"Student Street Address 2:",
+"Student City:PORTLAND",
+"Student State Code:OR",
+"Student Country Code:US",
+"Student Zip Code:97201",
+"Student Email Address:ALEX.MARTINEZ@EMAIL.COM",
+"Student Home Phone Country Code:",
+"Student Home Phone Number:",
+"Student Home Phone Preferred:",
+"Student Cell Phone Country Code:1",
+"Student Cell Phone Number:5033871049",
+"Student Cell Phone Preferred:Y",
+"Student Work Phone Country Code:",
+"Student Work Phone Number:",
+"Student Work Phone Preferred:",
+"Student SULA Maximum Eligibility Period:6",
+"Student SULA Subsidized Usage Period:3.5",
+"Student SULA Remaining Eligibility Period:2.5",
+"Student Enrollment Status Code:G",
+"Student Enrollment Status Code Description:GRADUATED",
+"Student Enrollment Status Effective Date:05/15/2022",
+"Undergraduate Subsidized Loan Limit Flag:N",
+"Undergraduate Combined Loan Limit Flag:N",
+"Undergraduate Award Year:2018",
+"Undergraduate Dependency Indicator:D",
+"Undergraduate Aggregate Subsidized Total:$11,500.00",
+"Undergraduate Aggregate Unsubsidized Total:$16,000.00",
+"Undergraduate Aggregate Combined Total:$27,500.00",
+"Graduate Subsidized Loan Limit Flag:N",
+"Graduate Combined Loan Limit Flag:N",
+"Graduate Award Year:2022",
+"Graduate Dependency Indicator:I",
+"Graduate Aggregate Subsidized Total:$24,000.00",
+"Graduate Aggregate Unsubsidized Total:$31,000.00",
+"Graduate Aggregate Combined Total:$55,000.00",
+"Aggregate Subsidized Total (Undergraduate and Graduate):$35,500.00",
+"Aggregate Unsubsidized Total (Undergraduate and Graduate):$47,000.00",
+"Aggregate Combined Total (Undergraduate and Graduate):$82,500.00",
+"Student Total All Loans Outstanding Principal:$74,312.45",
+"Student Total All Loans Outstanding Interest:$6,189.73",
+"Student Pell Lifetime Eligibility Used:450.000%",
+"Student Iraq and Afghanistan Service Lifetime Eligibility Used:0.000%",
+"Student Total All Grants:$11,750.00",
+"Type Code:D1",
+"Type Description:DIRECT STAFFORD SUBSIDIZED",
+"Total <D1> Outstanding Principal:$9,847.22",
+"Total <D1> Outstanding Interest:$812.34",
+"Type Code:D2",
+"Type Description:DIRECT STAFFORD UNSUBSIDIZED",
+"Total <D2> Outstanding Principal:$14,211.08",
+"Total <D2> Outstanding Interest:$1,643.91",
+"Type Code:D3",
+"Type Description:DIRECT PLUS GRADUATE",
+"Total <D3> Outstanding Principal:$42,108.00",
+"Total <D3> Outstanding Interest:$3,421.00",
+"Type Code:D5",
+"Type Description:DIRECT CONSOLIDATED UNSUBSIDIZED",
+"Total <D5> Outstanding Principal:$8,146.15",
+"Total <D5> Outstanding Interest:$312.48",
+"Loan Type Code:D1",
+"Loan Type Description:DIRECT STAFFORD SUBSIDIZED",
+"Loan Award ID:*****1003A001APXXXXYYY",
+"Loan Attending School Name:UNIVERSITY OF OREGON",
+"Loan Attending School OPEID:00351700",
+"Loan Date:08/20/2018",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/20/2018",
+"Loan Period End Date:05/15/2019",
+"Loan Amount:$3,500.00",
+"Loan Disbursed Amount:$3,500.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$3,112.44",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$287.11",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:5.05%",
+"Loan Actual Interest Rate:5.05%",
+"Loan Statutory Interest Rate:5.05%",
+"Loan Repayment Plan Type Code:IB",
+"Loan Repayment Plan Type Code Description:INCOME-BASED REPAYMENT (IBR)",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$187.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:11/15/2025",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:3.5",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$3,248.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:2",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2018",
+"Capitalized Interest:$0.00",
+"Net Loan Amount:$3,500.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:$3,112.44",
+"Calculated Unsubsidized Aggregate OPB:",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/20/2018",
+"Loan Disbursement Amount:$1,750.00",
+"Loan Disbursement Date:01/15/2019",
+"Loan Disbursement Amount:$1,750.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D1",
+"Loan Type Description:DIRECT STAFFORD SUBSIDIZED",
+"Loan Award ID:*****1003A002APXXXXYYY",
+"Loan Attending School Name:PORTLAND STATE UNIVERSITY",
+"Loan Attending School OPEID:00384500",
+"Loan Date:08/25/2020",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/25/2020",
+"Loan Period End Date:05/10/2021",
+"Loan Amount:$4,500.00",
+"Loan Disbursed Amount:$4,500.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$4,108.00",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$378.22",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:2.75%",
+"Loan Actual Interest Rate:2.75%",
+"Loan Statutory Interest Rate:2.75%",
+"Loan Repayment Plan Type Code:IB",
+"Loan Repayment Plan Type Code Description:INCOME-BASED REPAYMENT (IBR)",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$187.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:11/15/2025",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:3.5",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$1,841.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:3",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2020",
+"Capitalized Interest:$0.00",
+"Net Loan Amount:$4,500.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:$4,108.00",
+"Calculated Unsubsidized Aggregate OPB:",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/25/2020",
+"Loan Disbursement Amount:$2,250.00",
+"Loan Disbursement Date:01/20/2021",
+"Loan Disbursement Amount:$2,250.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D2",
+"Loan Type Description:DIRECT STAFFORD UNSUBSIDIZED",
+"Loan Award ID:*****1003B001APXXXXYYY",
+"Loan Attending School Name:UNIVERSITY OF OREGON",
+"Loan Attending School OPEID:00351700",
+"Loan Date:08/20/2018",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/20/2018",
+"Loan Period End Date:05/15/2019",
+"Loan Amount:$6,000.00",
+"Loan Disbursed Amount:$6,000.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$5,844.17",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$703.22",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:5.05%",
+"Loan Actual Interest Rate:5.05%",
+"Loan Statutory Interest Rate:5.05%",
+"Loan Repayment Plan Type Code:IB",
+"Loan Repayment Plan Type Code Description:INCOME-BASED REPAYMENT (IBR)",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$187.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:11/15/2025",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$2,914.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:2",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2018",
+"Capitalized Interest:$412.88",
+"Net Loan Amount:$6,000.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:",
+"Calculated Unsubsidized Aggregate OPB:$5,844.17",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/20/2018",
+"Loan Disbursement Amount:$3,000.00",
+"Loan Disbursement Date:01/15/2019",
+"Loan Disbursement Amount:$3,000.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D2",
+"Loan Type Description:DIRECT STAFFORD UNSUBSIDIZED",
+"Loan Award ID:*****1003B002APXXXXYYY",
+"Loan Attending School Name:PORTLAND STATE UNIVERSITY",
+"Loan Attending School OPEID:00384500",
+"Loan Date:08/25/2020",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/25/2020",
+"Loan Period End Date:05/10/2021",
+"Loan Amount:$8,000.00",
+"Loan Disbursed Amount:$8,000.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$8,366.91",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$940.69",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:2.75%",
+"Loan Actual Interest Rate:2.75%",
+"Loan Statutory Interest Rate:2.75%",
+"Loan Repayment Plan Type Code:IB",
+"Loan Repayment Plan Type Code Description:INCOME-BASED REPAYMENT (IBR)",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$187.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:11/15/2025",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$1,044.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:3",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2020",
+"Capitalized Interest:$901.14",
+"Net Loan Amount:$8,000.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:",
+"Calculated Unsubsidized Aggregate OPB:$8,366.91",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/25/2020",
+"Loan Disbursement Amount:$4,000.00",
+"Loan Disbursement Date:01/20/2021",
+"Loan Disbursement Amount:$4,000.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D3",
+"Loan Type Description:DIRECT PLUS GRADUATE",
+"Loan Award ID:*****1003C001APXXXXYYY",
+"Loan Attending School Name:PORTLAND STATE UNIVERSITY",
+"Loan Attending School OPEID:00384500",
+"Loan Date:08/25/2021",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/25/2021",
+"Loan Period End Date:05/10/2022",
+"Loan Amount:$20,500.00",
+"Loan Disbursed Amount:$20,500.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$21,804.33",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$1,894.17",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:6.54%",
+"Loan Actual Interest Rate:6.54%",
+"Loan Statutory Interest Rate:6.54%",
+"Loan Repayment Plan Type Code:SF",
+"Loan Repayment Plan Type Code Description:STANDARD REPAYMENT",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$246.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$7,872.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:A",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2021",
+"Capitalized Interest:$1,488.52",
+"Net Loan Amount:$20,500.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:",
+"Calculated Unsubsidized Aggregate OPB:$21,804.33",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/25/2021",
+"Loan Disbursement Amount:$10,250.00",
+"Loan Disbursement Date:01/18/2022",
+"Loan Disbursement Amount:$10,250.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D3",
+"Loan Type Description:DIRECT PLUS GRADUATE",
+"Loan Award ID:*****1003C002APXXXXYYY",
+"Loan Attending School Name:PORTLAND STATE UNIVERSITY",
+"Loan Attending School OPEID:00384500",
+"Loan Date:08/22/2022",
+"Loan Repayment Begin Date:11/15/2022",
+"Loan Period Begin Date:08/22/2022",
+"Loan Period End Date:05/15/2023",
+"Loan Amount:$21,500.00",
+"Loan Disbursed Amount:$21,500.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$20,303.67",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$1,526.83",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:7.54%",
+"Loan Actual Interest Rate:7.54%",
+"Loan Statutory Interest Rate:7.54%",
+"Loan Repayment Plan Type Code:SF",
+"Loan Repayment Plan Type Code Description:STANDARD REPAYMENT",
+"Loan Repayment Plan Begin Date:11/15/2022",
+"Loan Repayment Plan Scheduled Amount:$246.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$7,626.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:B",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2022",
+"Capitalized Interest:$812.44",
+"Net Loan Amount:$21,500.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:",
+"Calculated Unsubsidized Aggregate OPB:$20,303.67",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:11/15/2022",
+"Loan Disbursement Date:08/22/2022",
+"Loan Disbursement Amount:$10,750.00",
+"Loan Disbursement Date:01/19/2023",
+"Loan Disbursement Amount:$10,750.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:700",
+"Loan Contact Name:MOHELA",
+"Loan Contact Street Address 1:633 SPIRIT DRIVE",
+"Loan Contact Street Address 2:",
+"Loan Contact City:CHESTERFIELD",
+"Loan Contact State Code:MO",
+"Loan Contact Zip Code:63005",
+"Loan Contact Phone Number:888-866-4352",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:BORROWERS@MOHELA.COM",
+"Loan Contact Web Site Address:WWW.MOHELA.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Loan Type Code:D5",
+"Loan Type Description:DIRECT CONSOLIDATED UNSUBSIDIZED",
+"Loan Award ID:*****1003D001APXXXXYYY",
+"Loan Attending School Name:UNIVERSITY OF OREGON",
+"Loan Attending School OPEID:00351700",
+"Loan Date:06/15/2019",
+"Loan Repayment Begin Date:07/01/2019",
+"Loan Period Begin Date:06/15/2019",
+"Loan Period End Date:06/14/2019",
+"Loan Amount:$9,500.00",
+"Loan Disbursed Amount:$9,500.00",
+"Loan Canceled Amount:$0.00",
+"Loan Canceled Date:",
+"Loan Outstanding Principal Balance:$8,146.15",
+"Loan Outstanding Principal Balance as of Date:08/15/2025",
+"Loan Outstanding Interest Balance:$312.48",
+"Loan Outstanding Interest Balance as of Date:08/15/2025",
+"Loan Interest Rate Type Code:F",
+"Loan Interest Rate Type Description:FIXED",
+"Loan Interest Rate:5.05%",
+"Loan Actual Interest Rate:5.05%",
+"Loan Statutory Interest Rate:5.05%",
+"Loan Repayment Plan Type Code:GR",
+"Loan Repayment Plan Type Code Description:GRADUATED REPAYMENT",
+"Loan Repayment Plan Begin Date:07/01/2019",
+"Loan Repayment Plan Scheduled Amount:$97.00",
+"Loan Repayment Plan IDR Plan Anniversary Date:",
+"Loan Confirmed Subsidy Status:",
+"Loan Subsidized Usage in Years:",
+"Loan Reaffirmation Date:",
+"Loan Most Recent Payment Effective Date:08/01/2025",
+"Loan Next Payment Due Date:09/01/2025",
+"Loan Cumulative Payment Amount:$4,611.00",
+"Loan PSLF Cumulative Matched Months:0",
+"Academic Level:4",
+"Additional Unsubsidized Loan Flag:N",
+"Award Year:2019",
+"Capitalized Interest:$0.00",
+"Net Loan Amount:$9,500.00",
+"Reaffirmation flag:N",
+"Calculated Subsidized Aggregate OPB:",
+"Calculated Unsubsidized Aggregate OPB:$8,146.15",
+"Calculated Combined Aggregate OPB:",
+"UpdtDt:07/22/2025",
+"DelinqDate:",
+"Current Loan Status:RP",
+"Current Loan Status Description:IN REPAYMENT",
+"Loan Status:RP",
+"Loan Status Description:IN REPAYMENT",
+"Loan Status Effective Date:07/01/2019",
+"Loan Disbursement Date:06/15/2019",
+"Loan Disbursement Amount:$9,500.00",
+"Loan Contact Type:Current ED Servicer",
+"Loan Contact Code:756",
+"Loan Contact Name:AIDVANTAGE",
+"Loan Contact Street Address 1:PO BOX 300001",
+"Loan Contact Street Address 2:",
+"Loan Contact City:GREENVILLE",
+"Loan Contact State Code:TX",
+"Loan Contact Zip Code:75403",
+"Loan Contact Phone Number:800-722-1300",
+"Loan Contact Phone Extension:",
+"Loan Contact Email Address:",
+"Loan Contact Web Site Address:WWW.AIDVANTAGE.COM",
+"Most Relevant:Y",
+"Loan Special Contact Reason:",
+"Loan Special Contact:",
+"Grant Type Code:PL",
+"Grant Type Code Description:FEDERAL PELL GRANT",
+"Grant Attending School Name:UNIVERSITY OF OREGON",
+"Grant Attending School OPEID:00351700",
+"Grant Award Year:2018 - 2019",
+"Grant Scheduled Amount:$6,095.00",
+"Grant Award Amount:$3,048.00",
+"Grant Disbursed Amount:$3,048.00",
+"Grant Remaining Amount to be Paid:$0.00",
+"Grant Additional Eligibility:No",
+"Award ID:",
+"Conversion Date:",
+"Children Of Fallen Heroes Indicator:No",
+"Aid Recipient's Percent Eligibility Used (EU):50.000%",
+"Aid Recipient's Total Percent Eligibility Used (TEU):50.000%",
+"Grant Contact Type:Attending School",
+"Grant Contact Code:00351700",
+"Grant Contact Name:UNIVERSITY OF OREGON",
+"Grant Contact Street Address 1:1585 E 13TH AVE",
+"Grant Contact Street Address 2:",
+"Grant Contact City:EUGENE",
+"Grant Contact State Code:OR",
+"Grant Contact Zip Code:97403",
+"Phone Number:541-346-3221",
+"Phone Extension:",
+"Email Address:FINAID@UOREGON.EDU",
+"Web Site Address:HTTPS://FINANCIALAID.UOREGON.EDU/",
+"Grant Type Code:PL",
+"Grant Type Code Description:FEDERAL PELL GRANT",
+"Grant Attending School Name:UNIVERSITY OF OREGON",
+"Grant Attending School OPEID:00351700",
+"Grant Award Year:2019 - 2020",
+"Grant Scheduled Amount:$6,195.00",
+"Grant Award Amount:$3,098.00",
+"Grant Disbursed Amount:$3,098.00",
+"Grant Remaining Amount to be Paid:$0.00",
+"Grant Additional Eligibility:No",
+"Award ID:",
+"Conversion Date:",
+"Children Of Fallen Heroes Indicator:No",
+"Aid Recipient's Percent Eligibility Used (EU):50.000%",
+"Aid Recipient's Total Percent Eligibility Used (TEU):100.000%",
+"Grant Contact Type:Attending School",
+"Grant Contact Code:00351700",
+"Grant Contact Name:UNIVERSITY OF OREGON",
+"Grant Contact Street Address 1:1585 E 13TH AVE",
+"Grant Contact Street Address 2:",
+"Grant Contact City:EUGENE",
+"Grant Contact State Code:OR",
+"Grant Contact Zip Code:97403",
+"Phone Number:541-346-3221",
+"Phone Extension:",
+"Email Address:FINAID@UOREGON.EDU",
+"Web Site Address:HTTPS://FINANCIALAID.UOREGON.EDU/",
+"Grant Type Code:PL",
+"Grant Type Code Description:FEDERAL PELL GRANT",
+"Grant Attending School Name:PORTLAND STATE UNIVERSITY",
+"Grant Attending School OPEID:00384500",
+"Grant Award Year:2020 - 2021",
+"Grant Scheduled Amount:$6,345.00",
+"Grant Award Amount:$5,604.00",
+"Grant Disbursed Amount:$5,604.00",
+"Grant Remaining Amount to be Paid:$0.00",
+"Grant Additional Eligibility:No",
+"Award ID:",
+"Conversion Date:",
+"Children Of Fallen Heroes Indicator:No",
+"Aid Recipient's Percent Eligibility Used (EU):88.200%",
+"Aid Recipient's Total Percent Eligibility Used (TEU):250.000%",
+"Grant Contact Type:Attending School",
+"Grant Contact Code:00384500",
+"Grant Contact Name:PORTLAND STATE UNIVERSITY",
+"Grant Contact Street Address 1:PO BOX 751",
+"Grant Contact Street Address 2:",
+"Grant Contact City:PORTLAND",
+"Grant Contact State Code:OR",
+"Grant Contact Zip Code:97207",
+"Phone Number:503-725-3461",
+"Phone Extension:",
+"Email Address:FINAID@PDXEDU.EDU",
+"Web Site Address:HTTPS://WWW.PDX.EDU/FINAID/",
+"Program Status Code:C",
+"Program Status Code Description:COMPLETED",
+"Program Status Effective Date:05/15/2022",
+"Program School Name:PORTLAND STATE UNIVERSITY",
+"Program School OPEID:00384500",
+"Program Cip Year:2022",
+"Program Cip Code:520101",
+"Program CIP Title:Business Administration and Management, General.",
+"Program Credential Level Code:05",
+"Program Credential Level Code Description:MASTERS DEGREE",
+"Program Begin Date:08/25/2020",
+"Program Published Length in Years:2.0",
+"Special Program Indicator Code:N",
+"Special Program Indicator Description:NOT APPLICABLE",
+"Enrolled Status Code Description:GRADUATED",
+"Enrolled Status Date:05/15/2022",
+"Enrolled School Name:PORTLAND STATE UNIVERSITY",
+"Authorization Organization Name:MOHELA",
+"Authorization Organization Code:700",
+"Authorization Organization Type:Servicer",
+"Authorization End Date:08/15/2026"
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..cf9c65d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/video/StudentLoanTracker-Narrated-Walkthrough.mp4 b/video/StudentLoanTracker-Narrated-Walkthrough.mp4
new file mode 100644
index 0000000..2283f2f
Binary files /dev/null and b/video/StudentLoanTracker-Narrated-Walkthrough.mp4 differ
diff --git a/video/audio/01-home.mp3 b/video/audio/01-home.mp3
new file mode 100644
index 0000000..2314f68
Binary files /dev/null and b/video/audio/01-home.mp3 differ
diff --git a/video/audio/02-calculator.mp3 b/video/audio/02-calculator.mp3
new file mode 100644
index 0000000..a0af79d
Binary files /dev/null and b/video/audio/02-calculator.mp3 differ
diff --git a/video/audio/03-pslf.mp3 b/video/audio/03-pslf.mp3
new file mode 100644
index 0000000..e087dd5
Binary files /dev/null and b/video/audio/03-pslf.mp3 differ
diff --git a/video/audio/04-loans.mp3 b/video/audio/04-loans.mp3
new file mode 100644
index 0000000..b536060
Binary files /dev/null and b/video/audio/04-loans.mp3 differ
diff --git a/video/audio/05-servicers.mp3 b/video/audio/05-servicers.mp3
new file mode 100644
index 0000000..f42f43d
Binary files /dev/null and b/video/audio/05-servicers.mp3 differ
diff --git a/video/audio/06-summer.mp3 b/video/audio/06-summer.mp3
new file mode 100644
index 0000000..fa18bde
Binary files /dev/null and b/video/audio/06-summer.mp3 differ
diff --git a/video/concat.txt b/video/concat.txt
new file mode 100644
index 0000000..3020ec3
--- /dev/null
+++ b/video/concat.txt
@@ -0,0 +1,6 @@
+file 'segments/01-home.mp4'
+file 'segments/02-calculator.mp4'
+file 'segments/03-pslf.mp4'
+file 'segments/04-loans.mp4'
+file 'segments/05-servicers.mp4'
+file 'segments/06-summer.mp4'
diff --git a/video/narration.json b/video/narration.json
new file mode 100644
index 0000000..e809130
--- /dev/null
+++ b/video/narration.json
@@ -0,0 +1,26 @@
+[
+ {
+ "id": "01-home",
+ "text": "Welcome to Student Loan Tracker, a free platform that helps borrowers understand their repayment options. No account needed. Everything runs in your browser, so your data stays private. From the home page, you can jump into any of our five tools: repayment calculators, loan intake, PSLF tracker, servicer directory, and summer aid planner."
+ },
+ {
+ "id": "02-calculator",
+ "text": "The repayment calculator lets you compare all federal plans side by side. Enter your loan balance, interest rate, income, family size, and filing status. Click Compare Plans, and you instantly see monthly payments for Standard, SAVE, IBR, PAYE, and ICR. The SAVE plan is often the lowest at just 41 dollars a month for a 35 thousand dollar balance. The tool also shows total cost, forgiveness amounts, and timelines for each plan."
+ },
+ {
+ "id": "03-pslf",
+ "text": "The PSLF Progress Tracker features a visual thermometer countdown showing exactly where you stand on the path to 120 qualifying payments. In this example, 48 payments are complete, with 72 remaining, about 6 years away. After clicking Calculate, you see your monthly payment on the SAVE plan is just 82 dollars, and a full 60 thousand dollars would be forgiven tax-free. The bar chart compares monthly costs across all eligible plans."
+ },
+ {
+ "id": "04-loans",
+ "text": "The loan intake page accepts any student loan document. Drag and drop a Student Aid dot gov text export, a CSV file, a PDF servicer statement, or an employment certification form. Our file intelligence engine auto-detects the document type and extracts your loan data, entirely in the browser. Nothing is uploaded to any server. You can also enter loans manually if you prefer."
+ },
+ {
+ "id": "05-servicers",
+ "text": "The servicer directory lists all official federal loan servicers with verified phone numbers and websites. Each card shows an Official badge and a last-verified date. You can search by name, and there's a prominent scam warning section reminding borrowers that they never need to pay for free federal services like consolidation or IDR applications."
+ },
+ {
+ "id": "06-summer",
+ "text": "The summer aid planner generates a personalized checklist based on your session start date and enrollment intensity. It shows exactly what forms to submit and by when. The enrollment impact panel explains how dropping below half-time affects your financial aid and loan grace period. Check off items as you complete them. All dates are calculated automatically from your session start date."
+ }
+]
diff --git a/video/screens/01-home.png b/video/screens/01-home.png
new file mode 100644
index 0000000..b01c3ec
Binary files /dev/null and b/video/screens/01-home.png differ
diff --git a/video/screens/02-calculator.png b/video/screens/02-calculator.png
new file mode 100644
index 0000000..2ba4d7c
Binary files /dev/null and b/video/screens/02-calculator.png differ
diff --git a/video/screens/03-pslf.png b/video/screens/03-pslf.png
new file mode 100644
index 0000000..4183a90
Binary files /dev/null and b/video/screens/03-pslf.png differ
diff --git a/video/screens/04-loans.png b/video/screens/04-loans.png
new file mode 100644
index 0000000..47da170
Binary files /dev/null and b/video/screens/04-loans.png differ
diff --git a/video/screens/05-servicers.png b/video/screens/05-servicers.png
new file mode 100644
index 0000000..8287a27
Binary files /dev/null and b/video/screens/05-servicers.png differ
diff --git a/video/screens/06-summer.png b/video/screens/06-summer.png
new file mode 100644
index 0000000..50f9894
Binary files /dev/null and b/video/screens/06-summer.png differ
diff --git a/video/segments/01-home.mp4 b/video/segments/01-home.mp4
new file mode 100644
index 0000000..64b1201
Binary files /dev/null and b/video/segments/01-home.mp4 differ
diff --git a/video/segments/02-calculator.mp4 b/video/segments/02-calculator.mp4
new file mode 100644
index 0000000..8d7d2f7
Binary files /dev/null and b/video/segments/02-calculator.mp4 differ
diff --git a/video/segments/03-pslf.mp4 b/video/segments/03-pslf.mp4
new file mode 100644
index 0000000..2c06392
Binary files /dev/null and b/video/segments/03-pslf.mp4 differ
diff --git a/video/segments/04-loans.mp4 b/video/segments/04-loans.mp4
new file mode 100644
index 0000000..61453c4
Binary files /dev/null and b/video/segments/04-loans.mp4 differ
diff --git a/video/segments/05-servicers.mp4 b/video/segments/05-servicers.mp4
new file mode 100644
index 0000000..a1f6766
Binary files /dev/null and b/video/segments/05-servicers.mp4 differ
diff --git a/video/segments/06-summer.mp4 b/video/segments/06-summer.mp4
new file mode 100644
index 0000000..c6527a1
Binary files /dev/null and b/video/segments/06-summer.mp4 differ
(oldest)
·
back to StudentLoanTracker
·
security: strip hardcoded dw_admin DSN password -> env-first b82ac86 →