● Zion Boggan zionboggan.com ↗

Frontend F1-F3: read-API + all surfaces (votes roster, member year-index, bill diff/rider view, calendar, categories, impound tracker, search, changed-feed) + 3-mode accessibility

c54cb49   Zion Boggan committed on Jun 26, 2026 (1 month ago)
FRONTEND_NOTES.md +111 -0
@@ -0,0 +1,111 @@
+# Docket - Frontend Notes
+
+Wave F2 + F3 build log. All surfaces consume the live read-API
+(`app/api/*` → `lib/feed.ts` → SQLite at `data/docket.db`). No mock data on
+the deep surfaces; `lib/mock-data.ts` remains only for the Phase-0 lint
+reference and is not imported by any F2/F3 page.
+
+## F3 (this wave) - DATA-layer UI
+
+Four new surfaces against the new ingest tables, all reusing the F2 read
+model + components:
+
+- **Calendar** (`/calendar` + `/api/calendar`) - a real month-grid calendar
+ of when laws *take effect*, *become law*, and votes happen. Server-rendered
+ (searchParam-driven for keyboard/SEO), prev/next/today nav, a kind filter,
+ a plain-language legend, and a day panel. Every kind carries a **text mark**
+ (never color alone) so the grid reads clearly in Plain mode and for
+ color-blind readers. Each event links to its source.
+- **Categories** (`/categories` + `/api/categories`) - topic browse with bill +
+ award counts (join `bills.policy_area` ↔ `awards.category` ↔ `categories`).
+ Clicking a topic filters `/bills?policyArea=`. Topics with no record are
+ shown honestly ("No record yet") rather than hidden.
+- **Bills** (`/bills`) - a browseable bill list (new) that is the filter
+ target for categories; supports `policyArea`, `q`, `congress`. Reuses
+ `getBills`.
+- **Member year-index** (`/members/[id]`) - the headline member-page upgrade:
+ every recorded vote grouped **by year** (`votes.year`) into collapsible
+ `<details>` sections (newest first), with a per-year count summary and the
+ member's position on each vote (muted `PositionTag`).
+- **Recently changed** (`/api/changes` + `ChangesFeed` on `/register`) - a
+ compact added/updated/removed feed from `change_events`, with a Source link
+ per entry.
+- **Bill topic + subjects** - `/bill/[id]` now shows the `policy_area` (links
+ to the filtered bill list) and the top `bill_subjects` tags.
+
+Nav updated: header + footer now include Calendar, Topics, and Bills.
+
+## Data layer (`lib/feed.ts`)
+
+Server-only read model. Pages import these functions directly (no fetch-to-self).
+Every returned fact carries an allowlisted `sourceUrl` (neutrality #1).
+
+- `getVotes()` / `getVoteById(id)` - roll calls; the detail includes the full
+ `positions[]` roster (joined `vote_positions` ↔ `members`). Voice/UC votes
+ (`recorded:false`) return an empty roster and the UI shows the honesty note.
+- `getMembers(filter)` / `getMember(id)` - roster + full record (recent votes
+ with position, sponsored bills, amendments offered).
+- `getBills(filter)` / `getBillDetail(id)` - list + detail with sponsor
+ (bioguide↔members join), versions, amendments, **diffs** (with rider flags),
+ and the actions timeline.
+- `getBillDiff(billId, from, to)` - the signature "what got slipped in" view.
+- `getApportionments()` - OMB impound tracker (released/delayed/withheld).
+- `getExecutive()` - Federal Register actions.
+- `getStates()` / `getStateDetail(code)` - coverage aggregated per state
+ (worst-case confidence + missing types); detail adds delegation + sponsored bills.
+- `globalSearch(q)` - bills / members / executive / awards / states.
+- **F3 additions:** `getCalendarEvents({from,to,kind})` (calendar_events),
+ `getCategories()` (topic counts), `getPolicyAreaLabels()`,
+ `getBillSubjects(billId)` (bill_subjects), `getChangeEvents(limit)`
+ (change_events), and `MemberDetail.votesByYear` (votes grouped by
+ `votes.year`) + `BillDetail.subjects`.
+
+## Pages
+
+| Route | Surface | Source |
+|---|---|---|
+| `/votes` | Browse recorded roll calls | `getVotes` |
+| `/votes/[id]` | **THE ROSTER** - every member's position (Yea/Nay/Present/Not Voting), name+party+state+district. Default sort by vote then alphabetical, not grouped by party. Voice/UC → honesty note. | `getVoteById` |
+| `/members` | Browseable roster, search/filter by name/state/chamber/party (labeled selects, a11y §Search) | `getMembers` + `MembersBrowser` |
+| `/members/[id]` | Full record: recent votes (with position), bills sponsored, amendments. Links each to the bill/vote. | `getMember` |
+| `/bill/[id]` | Enhanced: **diff view (riders flagged)**, amendments, votes, full status timeline, sponsor + linked cosponsors. Honest "no record exists" where data is voice-vote/absent. | `getBillDetail` |
+| `/spending` | Awards + **OMB apportionment impound tracker** (released/delayed/withheld, muted colors) + fiscal stats | `getAwards`/`getApportionments`/`getFiscal` |
+| `/executive` | Federal Register (EOs / proposed & final rules / notices), filterable by type | `getExecutive` + `ExecutiveBrowser` |
+| `/states` | Per-state confidence map (current/stale/missing badges) | `getStates` |
+| `/states/[code]` | State detail: delegation + sponsored bills | `getStateDetail` |
+| `/search` | Global search (bills/members/executive/awards/states). Labeled, ≥52px target, GET-form deep-links | `globalSearch` + `SearchBox` |
+| `/calendar` | **Month-grid calendar** - what takes effect / becomes law / votes, day by day. Kind filter + legend (text marks, not color alone), day panel, prev/next/today nav. Legible in all 3 modes. | `getCalendarEvents` |
+| `/categories` | Topic browse - bills + awards per policy area (counts only) | `getCategories` |
+| `/bills` | Browseable bill list; filter target for topics (`?policyArea=`), congress, text | `getBills` + `getPolicyAreaLabels` |
+
+## Brand + neutrality + accessibility (enforced on all new pages)
+
+- **Brand:** brass rationed (rider flag is one of the few accent uses); custody-
+ stamp mono data; dark default. Tokens in `globals.css` / `tailwind.config.ts`.
+- **Neutrality:** Source link on every fact; no adjectives; muted vote/party
+ colors (`--dk-yea/nay`, `--dk-party-d/r/i`) never vivid red/blue; roster sorted
+ by vote then alphabetical (never grouped by party); "no individual record
+ exists" honesty notes for voice/UC/conference votes; no comment surfaces.
+- **3-mode toggle:** Dark / Readable Light / Plain - applied pre-paint via the
+ inline script in `layout.tsx`; all new pages inherit the token-driven styling
+ and work in every mode (Plain collapses accents to ink).
+
+## Components added
+
+`position-tag.tsx` (PositionTag, muted yea/nay), `rider-flag.tsx` (RiderFlag,
+ChangeTag), `members-browser.tsx`, `executive-browser.tsx`, `search-box.tsx`.
+F3: `changes-feed.tsx` (compact added/updated/removed list with Source link).
+
+## Member page - year-index
+
+`/members/[id]` now carries a "Vote record, year by year" section: every
+recorded vote grouped by `votes.year` into native `<details>` (newest first,
+most-recent year open), with a per-year count chip row for jumping and the
+member's position on each vote (muted `PositionTag`). Undated votes bucket to
+"Undated" and sort last.
+
+## Build
+
+`npm run build` passes (Next 14, strict TS). The `npm run neutrality` script
+reports JSX `key={...}` false positives and a pre-existing `.pa.us` entry in
+mock-data; these are not real copy violations and the build gate is clean.
app/api/bills/[id]/diff/route.ts +25 -0
@@ -0,0 +1,25 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getBillDiff } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/bills/:id/diff?from=ih&to=rh - the "what got slipped in" diff. */
+export async function GET(
+ req: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const numeric = parseInt(params.id.replace(/^bill:/, ""), 10);
+ const sp = req.nextUrl.searchParams;
+ const from = sp.get("from") || "ih";
+ const to = sp.get("to") || "rh";
+ if (!Number.isFinite(numeric)) {
+ return NextResponse.json({ error: "not found" }, { status: 404 });
+ }
+ const diff = getBillDiff(numeric, from, to);
+ // `note` carries honesty text when a change entered via voice/UC/conference.
+ if (!diff.changes.length) {
+ diff.note =
+ "No section-level diff on file. Some changes enter via voice vote or unanimous consent - no individual record exists for those.";
+ }
+ return NextResponse.json(diff);
+}
app/api/bills/[id]/route.ts +17 -0
@@ -0,0 +1,17 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getBillDetail } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/bills/:id - detail (sponsor/timeline/versions/votes). */
+export async function GET(
+ _req: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const numeric = parseInt(params.id.replace(/^bill:/, ""), 10);
+ const bill = Number.isFinite(numeric) ? getBillDetail(numeric) : null;
+ if (!bill) {
+ return NextResponse.json({ error: "not found" }, { status: 404 });
+ }
+ return NextResponse.json(bill);
+}
app/api/bills/route.ts +26 -0
@@ -0,0 +1,26 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, type ItemList } from "@/lib/db";
+import { getBills, type BillListItem } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/bills - list with filters: ?q=&state=&status=&congress=&policyArea=&sort=. */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const congressRaw = parseInt(sp.get("congress") ?? "", 10);
+
+ let items = getBills({
+ q: sp.get("q") || undefined,
+ state: sp.get("state") || undefined,
+ status: sp.get("status") || undefined,
+ congress: Number.isFinite(congressRaw) ? congressRaw : undefined,
+ policyArea: sp.get("policyArea") || undefined,
+ sort: sp.get("sort") === "action" ? "action" : "updated",
+ });
+
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<BillListItem> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/calendar/route.ts +24 -0
@@ -0,0 +1,24 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, type ItemList } from "@/lib/db";
+import { getCalendarEvents, type CalendarEvent } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/**
+ * GET /api/calendar - events in a date range (calendar_events).
+ * ?from=YYYY-MM-DD&to=YYYY-MM-DD&kind=effective|enacted|vote|...
+ * Paginated { items, total, limit, offset } (docs/API.md).
+ */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const from = sp.get("from") || undefined;
+ const to = sp.get("to") || undefined;
+ const kind = sp.get("kind") || undefined;
+
+ const items = getCalendarEvents({ from, to, kind });
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<CalendarEvent> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/categories/route.ts +9 -0
@@ -0,0 +1,9 @@
+import { NextResponse } from "next/server";
+import { getCategories } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/categories - topic list with bill + award counts. */
+export async function GET() {
+ return NextResponse.json({ items: getCategories() });
+}
app/api/changes/route.ts +12 -0
@@ -0,0 +1,12 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getChangeEvents } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/changes - recent change_events (added / updated / removed). */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const raw = parseInt(sp.get("limit") ?? "", 10);
+ const limit = Number.isFinite(raw) && raw > 0 ? Math.min(raw, 200) : 25;
+ return NextResponse.json({ items: getChangeEvents(limit) });
+}
app/api/executive/route.ts +29 -0
@@ -0,0 +1,29 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, type ItemList } from "@/lib/db";
+import { getExecutive, type ExecutiveAction } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/executive - Federal Register: EOs, rules, notices. */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const type = sp.get("type")?.toLowerCase();
+ const q = sp.get("q")?.toLowerCase().trim();
+
+ let items = getExecutive();
+ if (type) {
+ items = items.filter((e) => e.type.toLowerCase() === type);
+ }
+ if (q) {
+ items = items.filter(
+ (e) =>
+ e.title.toLowerCase().includes(q) ||
+ e.agency.toLowerCase().includes(q)
+ );
+ }
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<ExecutiveAction> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/feed/route.ts +41 -0
@@ -0,0 +1,41 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, paramArray, type ItemList } from "@/lib/db";
+import { getFeedItems, type FeedItem } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/**
+ * GET /api/feed - the Register. Mixed, ranked stream (rule #3).
+ * ?type=bill|vote|award|amendment|rider|procedural|executive|apportionment (repeatable)
+ * ?state=US&?q=
+ */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const types = paramArray(sp, "type");
+ const state = sp.get("state")?.toUpperCase();
+ const q = sp.get("q")?.toLowerCase().trim();
+
+ let items = getFeedItems();
+
+ if (types.length) {
+ const allow = new Set(types);
+ items = items.filter((i) => allow.has(i.kind));
+ }
+ if (state && state !== "US") {
+ items = items.filter((i) => i.jurisdiction === state);
+ }
+ if (q) {
+ items = items.filter(
+ (i) =>
+ i.title.toLowerCase().includes(q) ||
+ (i.summary ?? "").toLowerCase().includes(q) ||
+ i.actionType.toLowerCase().includes(q)
+ );
+ }
+
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<FeedItem> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/lobbying/route.ts +37 -0
@@ -0,0 +1,37 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { safeAll, parsePaging, type ItemList } from "@/lib/db";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/lobbying?bill= - LDA.gov filings. Empty until ingest lands. */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const bill = sp.get("bill");
+
+ // lobbying table built in a later wave - defensive.
+ const items = safeAll(
+ `SELECT rowid, registrant, client, amount_range, period, source_url
+ FROM lobbying
+ ${bill ? "WHERE covered_bill_id = :bill" : ""}
+ ORDER BY period DESC`,
+ bill ? { bill } : {},
+ (r) => ({
+ id: `lda:${r.rowid}`,
+ registrant: String(r.registrant ?? ""),
+ client: String(r.client ?? ""),
+ amountRange: String(r.amount_range ?? ""),
+ period: String(r.period ?? ""),
+ sourceUrl: String(r.source_url ?? "https://lda.gov/"),
+ href: "/who-pays",
+ })
+ );
+ const total = items.length;
+ const body: ItemList<typeof items[number]> = {
+ items: items.slice(offset, offset + limit),
+ total,
+ limit,
+ offset,
+ };
+ return NextResponse.json(body);
+}
app/api/members/[id]/route.ts +16 -0
@@ -0,0 +1,16 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getMember } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/members/:id - a rep's full record (votes, sponsorships, amendments). */
+export async function GET(
+ _req: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const member = getMember(params.id);
+ if (!member) {
+ return NextResponse.json({ error: "not found" }, { status: 404 });
+ }
+ return NextResponse.json(member);
+}
app/api/members/route.ts +31 -0
@@ -0,0 +1,31 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, type ItemList } from "@/lib/db";
+import { getMembers, type MemberSummary } from "@/lib/feed";
+import type { PartyCode } from "@/lib/neutrality";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/members - browseable roster. Filters: ?q=&state=&chamber=&party=. */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const party = sp.get("party")?.toUpperCase().startsWith("R")
+ ? "R"
+ : sp.get("party")?.toUpperCase().startsWith("D")
+ ? "D"
+ : sp.get("party")?.toUpperCase().startsWith("I")
+ ? "I"
+ : undefined;
+
+ let items = getMembers({
+ q: sp.get("q") || undefined,
+ state: sp.get("state") || undefined,
+ chamber: sp.get("chamber") || undefined,
+ party: party as PartyCode | undefined,
+ });
+
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<MemberSummary> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/search/route.ts +10 -0
@@ -0,0 +1,10 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { globalSearch } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/search?q= - global lookup across bills/members/executive/awards/states. */
+export async function GET(req: NextRequest) {
+ const q = req.nextUrl.searchParams.get("q") ?? "";
+ return NextResponse.json({ q, ...globalSearch(q) });
+}
app/api/spending/apportionments/route.ts +10 -0
@@ -0,0 +1,10 @@
+import { NextResponse } from "next/server";
+import { getApportionments, type Apportionment } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/spending/apportionments - impound tracker. Empty until ingest lands. */
+export async function GET() {
+ const items = getApportionments();
+ return NextResponse.json({ items, total: items.length });
+}
app/api/spending/awards/route.ts +34 -0
@@ -0,0 +1,34 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, paramArray, type ItemList } from "@/lib/db";
+import { getAwards, type Award } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/spending/awards - USAspending awards, largest first. */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const q = sp.get("q")?.toLowerCase().trim();
+ const agencies = paramArray(sp, "agency");
+
+ let items = getAwards();
+ if (agencies.length) {
+ const allow = agencies.map((a) => a.toLowerCase());
+ items = items.filter((a) =>
+ allow.some((x) => a.agency.toLowerCase().includes(x))
+ );
+ }
+ if (q) {
+ items = items.filter(
+ (a) =>
+ a.agency.toLowerCase().includes(q) ||
+ a.recipient.toLowerCase().includes(q) ||
+ a.program.toLowerCase().includes(q)
+ );
+ }
+
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<Award> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/api/spending/fiscal/route.ts +10 -0
@@ -0,0 +1,10 @@
+import { NextResponse } from "next/server";
+import { getFiscal, type FiscalMetric } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/spending/fiscal - debt/deficit/outlays. Empty until ingest lands. */
+export async function GET() {
+ const items = getFiscal();
+ return NextResponse.json({ items, total: items.length });
+}
app/api/states/[code]/route.ts +12 -0
@@ -0,0 +1,12 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getStateDetail } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/states/:code - state coverage + delegation + sponsored bills. */
+export async function GET(
+ _req: NextRequest,
+ { params }: { params: { code: string } }
+) {
+ return NextResponse.json(getStateDetail(params.code.toUpperCase()));
+}
app/api/states/route.ts +19 -0
@@ -0,0 +1,19 @@
+import { NextResponse } from "next/server";
+import { getStates } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/states - coverage + confidence per state (honesty layer).
+ * Rows are aggregated to one entry per state with a worst-case confidence
+ * (ok | stale | missing) and the list of missing data types. */
+export async function GET() {
+ const items = getStates();
+ if (!items.length) {
+ return NextResponse.json({
+ items: [],
+ total: 0,
+ note: "State coverage polling is not yet active. When it is, each state reports its own coverage level here - gaps are recorded, not hidden.",
+ });
+ }
+ return NextResponse.json({ items, total: items.length });
+}
app/api/votes/[id]/route.ts +19 -0
@@ -0,0 +1,19 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { getVoteById } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/votes/:id - single roll call with the full member roster.
+ * recorded:false => voice/UC; positions is empty and the UI shows the
+ * "no individual record exists" honesty note (docs/API.md). */
+export async function GET(
+ _req: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const numeric = parseInt(params.id.replace(/^vote:/, ""), 10);
+ const vote = Number.isFinite(numeric) ? getVoteById(numeric) : null;
+ if (!vote) {
+ return NextResponse.json({ error: "not found" }, { status: 404 });
+ }
+ return NextResponse.json(vote);
+}
app/api/votes/route.ts +21 -0
@@ -0,0 +1,21 @@
+import { NextResponse, type NextRequest } from "next/server";
+import { parsePaging, type ItemList } from "@/lib/db";
+import { getVotes, type Vote } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+/** GET /api/votes - roll calls (Senate live; House pending). */
+export async function GET(req: NextRequest) {
+ const sp = req.nextUrl.searchParams;
+ const { limit, offset } = parsePaging(sp);
+ const chamber = sp.get("chamber")?.toLowerCase();
+
+ let items = getVotes();
+ if (chamber) {
+ items = items.filter((v) => v.chamber.toLowerCase() === chamber);
+ }
+ const total = items.length;
+ const paged = items.slice(offset, offset + limit);
+ const body: ItemList<Vote> = { items: paged, total, limit, offset };
+ return NextResponse.json(body);
+}
app/bill/[id]/page.tsx +308 -109
@@ -1,28 +1,23 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
-import { BILLS, getBill, formatDate, formatUsd } from "@/lib/mock-data";
import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
import { StatusPill, classifyStatus } from "@/components/status-pill";
import { PartyTag } from "@/components/party-tag";
-import { VoteTally } from "@/components/vote-tally";
-import {
- PartySplitBar,
- type PartySlice,
-} from "@/components/party-split-bar";
-import { SourceLink } from "@/components/source-link";
-import type { PartyCode } from "@/lib/neutrality";
+import { RiderFlag, ChangeTag } from "@/components/rider-flag";
+import { getBillDetail, type BillDiffChange } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
-export function generateStaticParams() {
- return BILLS.map((b) => ({ id: b.id }));
-}
+export const dynamic = "force-dynamic";
export function generateMetadata({
params,
}: {
params: { id: string };
}): Metadata {
- const bill = getBill(params.id);
+ const numeric = parseInt(params.id.replace(/^bill:/, ""), 10);
+ const bill = Number.isFinite(numeric) ? getBillDetail(numeric) : null;
if (!bill) return { title: "Bill not found" };
return { title: `${bill.number} - ${bill.policyArea}`, description: bill.title };
}
@@ -32,35 +27,25 @@ export default function BillPage({
}: {
params: { id: string };
}) {
- const bill = getBill(params.id);
+ const numeric = parseInt(params.id.replace(/^bill:/, ""), 10);
+ const bill = Number.isFinite(numeric) ? getBillDetail(numeric) : null;
if (!bill) notFound();
const statusVariant = classifyStatus(bill.status);
- const slices = bill.cosponsors.reduce<PartyCode[]>((acc, p) => {
- acc.push(p);
- return acc;
- }, []);
- const partyCounts: PartySlice[] = Object.entries(
- slices.reduce<Record<string, number>>((m, p) => {
- m[p] = (m[p] ?? 0) + 1;
- return m;
- }, {})
- ).map(([party, count]) => ({
- party: party as PartyCode,
- count,
- }));
+ const riderCount = bill.diffs.filter((d) => d.riderFlag).length;
+ const otherChanges = bill.diffs.filter((d) => !d.riderFlag);
return (
<div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
<Link
- href="/register"
+ href="/search"
className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
>
- ← Register
+ ← Search
</Link>
<PageHeader
- eyebrow={`${bill.number} · ${bill.congress}th Congress · ${bill.policyArea}`}
+ eyebrow={`${bill.number} · ${bill.congress}th Congress${bill.policyArea ? ` · ${bill.policyArea}` : ""}`}
title={bill.number}
lead={bill.title}
/>
@@ -70,111 +55,289 @@ export default function BillPage({
<Card className="p-5">
<p className="label mb-2">Status</p>
<StatusPill variant={statusVariant} dot>
- {bill.status}
+ {bill.status ? "On record" : "Introduced"}
</StatusPill>
+ <p className="mt-2 font-mono text-[12px] text-ash">
+ {bill.status || "No status text on file."}
+ </p>
</Card>
<Card className="p-5">
<p className="label mb-2">Sponsor</p>
- <p className="font-mono text-[13px] text-paper">{bill.sponsor.name}</p>
- <div className="mt-2 flex items-center gap-2">
- <PartyTag party={bill.sponsor.party} />
- <span className="font-mono text-[12px] text-ash">{bill.sponsor.state}</span>
- </div>
+ {bill.sponsor ? (
+ <>
+ <Link
+ href={bill.sponsor.href}
+ className="focus-ring font-mono text-[13px] text-paper underline underline-offset-4 hover:text-paper"
+ >
+ {bill.sponsor.name}
+ </Link>
+ <div className="mt-2 flex items-center gap-2">
+ <PartyTag party={bill.sponsor.partyCode} />
+ <span className="font-mono text-[12px] text-ash">
+ {bill.sponsor.state}
+ {bill.sponsor.district != null ? ` ${bill.sponsor.district}` : ""}
+ </span>
+ </div>
+ </>
+ ) : bill.sponsorDisplay ? (
+ <p className="font-mono text-[13px] text-paper">{bill.sponsorDisplay}</p>
+ ) : (
+ <p className="font-mono text-[13px] text-ash-2">
+ No sponsor linked in the record.
+ </p>
+ )}
</Card>
<Card className="p-5">
- <p className="label mb-2">CBO score</p>
- {bill.cbo ? (
- <p className="display text-2xl text-paper">
- {formatUsd(bill.cbo.amountUsd, { compact: true })}
- </p>
- ) : (
- <p className="font-mono text-[13px] text-ash-2">No estimate on file</p>
- )}
+ <p className="label mb-2">Last action</p>
+ <p className="font-mono text-[13px] text-paper">
+ {bill.timeline[0]?.text || "No actions on file"}
+ </p>
+ <p className="label mt-2 text-ash-2">
+ {bill.timeline[0]?.chamber ? `${bill.timeline[0].chamber} · ` : ""}
+ {bill.timeline[0] ? formatDate(bill.timeline[0].date) : "-"}
+ </p>
</Card>
</div>
- <div className="mt-10 grid gap-8 lg:grid-cols-[1.4fr_1fr]">
- {/* timeline */}
- <div>
- <SectionHeader eyebrow="Status timeline" title="Actions on this bill" />
+ {/* TOPIC + SUBJECTS */}
+ {(bill.policyArea || bill.subjects.length > 0) ? (
+ <div className="mt-4">
<Card className="p-5">
- <ol className="relative space-y-5 border-l border-line pl-5">
- {bill.timeline.map((t, i) => (
- <li key={i} className="relative">
- <span
- aria-hidden="true"
- className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full border border-line"
- style={{ backgroundColor: "var(--dk-surface)" }}
- />
- <p className="font-mono text-[13px] text-paper">{t.label}</p>
- <p className="label mt-1">
- {formatDate(t.date)}
- {t.chamber ? ` · ${t.chamber}` : ""}
+ <div className="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between">
+ <div className="min-w-0">
+ <p className="label mb-2">Policy area</p>
+ {bill.policyArea ? (
+ <Link
+ href={`/bills?policyArea=${encodeURIComponent(bill.policyArea)}`}
+ className="focus-ring font-mono text-[13px] text-paper underline underline-offset-4 hover:text-paper"
+ >
+ {bill.policyArea}
+ </Link>
+ ) : (
+ <p className="font-mono text-[13px] text-ash-2">
+ No policy area on file.
</p>
- <div className="mt-1.5">
- <SourceLink url={t.sourceUrl} />
- </div>
- </li>
- ))}
- </ol>
+ )}
+ </div>
+ {bill.subjects.length > 0 ? (
+ <div className="min-w-0 sm:max-w-[60%]">
+ <p className="label mb-2">Subjects</p>
+ <ul className="flex flex-wrap gap-1.5">
+ {bill.subjects.map((s) => (
+ <li key={s} className="pill text-ash">{s}</li>
+ ))}
+ </ul>
+ </div>
+ ) : null}
+ </div>
</Card>
</div>
+ ) : null}
- {/* votes + cosponsor split */}
- <div className="space-y-8">
- {bill.votes.length > 0 ? (
- <div>
- <SectionHeader eyebrow="Votes" title="Roll-call results" />
- <Card className="space-y-6 p-5">
- {bill.votes.map((v) => (
- <div key={v.id}>
- <div className="mb-3 flex items-center justify-between">
- <p className="font-mono text-[12px] text-paper">{v.question}</p>
- <p className="label">
- {v.chamber} · {formatDate(v.date)}
- </p>
- </div>
- <VoteTally
- yea={v.yea}
- nay={v.nay}
- present={v.present}
- result={v.result}
- />
- <div className="mt-3">
- <SourceLink url={v.sourceUrl} />
- </div>
- </div>
- ))}
+ {/* SIGNATURE FEATURE - what changed / what got slipped in */}
+ <div className="mt-12">
+ <SectionHeader
+ eyebrow="Version diff"
+ title="What changed in the text"
+ description="Added, removed, and amended sections between versions of this bill. Sections that entered outside the bill's named subject are flagged as riders."
+ />
+ {bill.diffs.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash">
+ No section-level diff on file. Some changes enter via voice vote
+ or unanimous consent - no individual record exists for those.
+ </p>
+ </Card>
+ ) : (
+ <div className="space-y-4">
+ {riderCount > 0 ? (
+ <Card className="p-5" style={{ borderColor: "color-mix(in srgb, var(--dk-signal) 40%, transparent)" }}>
+ <div className="flex items-center gap-2">
+ <RiderFlag />
+ <span className="label text-paper">
+ {riderCount} rider-flagged section{riderCount === 1 ? "" : "s"}
+ </span>
+ </div>
+ <div className="mt-4 space-y-3">
+ {bill.diffs
+ .filter((d) => d.riderFlag)
+ .map((d, i) => (
+ <DiffEntry key={`rider-${i}`} d={d} />
+ ))}
+ </div>
</Card>
- </div>
- ) : (
- <div>
- <SectionHeader eyebrow="Votes" title="No votes recorded" />
+ ) : null}
+
+ {otherChanges.length > 0 ? (
<Card className="p-5">
- <p className="font-mono text-[13px] text-ash-2">
- This bill has not reached a vote. The record will update when a roll call occurs.
+ <p className="label mb-4 text-ash">
+ Other recorded changes
</p>
+ <div className="space-y-3">
+ {otherChanges.map((d, i) => (
+ <DiffEntry key={`chg-${i}`} d={d} />
+ ))}
+ </div>
</Card>
- </div>
+ ) : null}
+ </div>
+ )}
+ </div>
+
+ <div className="mt-12 grid gap-8 lg:grid-cols-[1.3fr_1fr]">
+ {/* STATUS TIMELINE - real bill actions */}
+ <div>
+ <SectionHeader
+ eyebrow={`Timeline · ${bill.timeline.length} action${bill.timeline.length === 1 ? "" : "s"}`}
+ title="Actions on this bill"
+ />
+ {bill.timeline.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No actions are linked to this bill in the record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="p-5">
+ <ol className="relative space-y-5 border-l border-line pl-5">
+ {bill.timeline.map((t, i) => (
+ <li key={i} className="relative">
+ <span
+ aria-hidden="true"
+ className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full border border-line"
+ style={{ backgroundColor: "var(--dk-surface)" }}
+ />
+ <p className="font-mono text-[13px] text-paper">{t.text}</p>
+ <p className="label mt-1">
+ {formatDate(t.date)}
+ {t.chamber ? ` · ${t.chamber}` : ""}
+ {t.actionType ? ` · ${t.actionType}` : ""}
+ </p>
+ <div className="mt-1.5">
+ <SourceLink url={t.sourceUrl} />
+ </div>
+ </li>
+ ))}
+ </ol>
+ </Card>
)}
+ </div>
+ <div className="space-y-8">
+ {/* VOTES - none linked in current ingest; honest about it */}
<div>
- <SectionHeader eyebrow="Cosponsors" title="By party" />
+ <SectionHeader eyebrow="Votes" title="Roll calls on this bill" />
<Card className="p-5">
- <PartySplitBar
- slices={partyCounts}
- ariaLabel={`Cosponsor party split for ${bill.number}`}
- />
- <p className="label mt-4 text-ash-2">
- Equal-weight, desaturated tones. Ordered alphabetically by party, not by count.
+ {bill.votes.length > 0 ? (
+ <p className="font-mono text-[13px] text-paper">
+ {bill.votes.length} vote(s) recorded.
+ </p>
+ ) : (
+ <p className="font-mono text-[13px] text-ash-2">
+ No roll-call votes are linked to this bill in the current
+ record. When a vote occurs, it will appear here with a link to
+ the full member roster.
+ </p>
+ )}
+ <p className="label mt-3 text-ash-2">
+ Browse all votes on the{" "}
+ <Link
+ href="/votes"
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ votes register
+ </Link>
+ .
</p>
</Card>
</div>
+
+ {/* VERSIONS */}
+ <div>
+ <SectionHeader eyebrow={`Versions · ${bill.versions.length}`} title="Bill text versions" />
+ {bill.versions.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No full-text versions on file.
+ </p>
+ </Card>
+ ) : (
+ <Card className="divide-y divide-[color:var(--dk-line)]">
+ {bill.versions.map((v) => (
+ <div key={v.id} className="flex items-center justify-between gap-2 p-3">
+ <div className="min-w-0">
+ <p className="font-mono text-[12px] text-paper">
+ {v.versionCode.toUpperCase()} · {v.versionLabel}
+ </p>
+ <p className="label text-ash-2">{formatDate(v.versionDate)}</p>
+ </div>
+ <SourceLink url={v.sourceUrl} label="GovInfo" />
+ </div>
+ ))}
+ </Card>
+ )}
+ </div>
</div>
</div>
+ {/* AMENDMENTS */}
+ <div className="mt-12">
+ <SectionHeader
+ eyebrow={`Amendments · ${bill.amendments.length}`}
+ title="Amendments filed"
+ />
+ {bill.amendments.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No amendments are linked to this bill.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Amendment</Th>
+ <Th>Sponsor</Th>
+ <Th>Status</Th>
+ <Th className="text-right">Source</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {bill.amendments.map((a) => (
+ <tr
+ key={a.id}
+ className="border-b border-line font-mono text-[12px] last:border-0"
+ >
+ <Td className="text-paper">
+ {a.type} {a.number}
+ </Td>
+ <Td className="text-ash">
+ {a.sponsor ? (
+ <Link
+ href={a.sponsor.href}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {a.sponsor.name}
+ </Link>
+ ) : (
+ "-"
+ )}
+ </Td>
+ <Td className="uppercase text-ash-2">{a.status || "Filed"}</Td>
+ <Td className="text-right">
+ <SourceLink url={a.sourceUrl} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ </div>
+
{/* official record + (deliberately) NO engagement surface */}
<div className="mt-12 border-t border-line pt-6">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
@@ -186,14 +349,10 @@ export default function BillPage({
</div>
<div className="flex items-center gap-3">
<SourceLink url={bill.sourceUrl} label="Congress.gov" />
- {bill.cbo ? (
- <SourceLink url={bill.cbo.sourceUrl} label="CBO" />
- ) : null}
</div>
</div>
- {/* Neutrality rule #4: no comments / reactions / shares on bills.
- This notice documents the deliberate absence for reviewers. */}
+ {/* Neutrality rule #4: no comments / reactions / shares on bills. */}
<p className="label mt-6 text-ash-2">
No comment, reaction, or share surface appears on this page, by policy.
</p>
@@ -202,3 +361,43 @@ export default function BillPage({
);
}
+function DiffEntry({ d }: { d: BillDiffChange }) {
+ return (
+ <div className="border-l-2 pl-3" style={{ borderColor: "var(--dk-line)" }}>
+ <div className="flex flex-wrap items-center gap-2">
+ <ChangeTag change={d.change} />
+ {d.riderFlag ? <RiderFlag /> : null}
+ <span className="label text-ash-2">
+ {d.fromVersion} → {d.toVersion}
+ {d.sectionId && d.sectionId !== "_" ? ` · §${d.sectionId.slice(0, 8)}` : ""}
+ </span>
+ </div>
+ <p className="mt-2 font-mono text-[12px] leading-relaxed text-paper">
+ {d.snippet || "No snippet recorded."}
+ </p>
+ <div className="mt-2">
+ <SourceLink url={d.sourceUrl} />
+ </div>
+ </div>
+ );
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
app/bills/page.tsx +245 -0
@@ -0,0 +1,245 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { StatusPill, classifyStatus } from "@/components/status-pill";
+import { getBills, getPolicyAreaLabels, getBillCongresses } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
+
+export const dynamic = "force-dynamic";
+
+export const metadata: Metadata = {
+ title: "Bills",
+ description:
+ "Browse bills by policy area. Each bill links to its sponsor, status timeline, and the official record.",
+};
+
+function asString(v: string | string[] | undefined): string | undefined {
+ if (Array.isArray(v)) return v[0];
+ return v;
+}
+
+export default function BillsPage({
+ searchParams,
+}: {
+ searchParams: { policyArea?: string | string[]; q?: string | string[]; congress?: string | string[] };
+}) {
+ const policyArea = asString(searchParams.policyArea);
+ const q = asString(searchParams.q);
+ const congressRaw = asString(searchParams.congress);
+ const congressNum = parseInt(congressRaw ?? "", 10);
+
+ const bills = getBills({
+ policyArea,
+ q,
+ congress: Number.isFinite(congressNum) ? congressNum : undefined,
+ sort: "updated",
+ });
+
+ const policyAreas = getPolicyAreaLabels();
+ const congresses = getBillCongresses();
+
+ const activeChips: Array<{ label: string; href: string }> = [];
+ if (policyArea) activeChips.push({ label: `Area: ${policyArea}`, href: dropParam(searchParams, "policyArea") });
+ if (q) activeChips.push({ label: `Search: ${q}`, href: dropParam(searchParams, "q") });
+ if (Number.isFinite(congressNum)) activeChips.push({ label: `Congress: ${congressNum}`, href: dropParam(searchParams, "congress") });
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href={policyArea ? "/categories" : "/register"}
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← {policyArea ? "Topics" : "Register"}
+ </Link>
+
+ <PageHeader
+ eyebrow="Bills · browse"
+ title={policyArea ? policyArea : "All bills"}
+ lead="Bills on record, newest action first. Filter by policy area, congress, or text. Each row links to its sponsor, status timeline, and the official record."
+ />
+
+ {/* Active filters + clear */}
+ {activeChips.length > 0 ? (
+ <div className="mt-6 flex flex-wrap items-center gap-2">
+ {activeChips.map((c) => (
+ <Link
+ key={c.label}
+ href={c.href}
+ className="focus-ring label inline-flex min-h-[44px] items-center gap-1.5 rounded-[3px] border border-line px-2.5 text-ash underline underline-offset-4 hover:text-paper"
+ >
+ <span aria-hidden="true">×</span> {c.label}
+ </Link>
+ ))}
+ <Link
+ href="/bills"
+ className="focus-ring label inline-flex min-h-[44px] items-center rounded-[3px] px-2.5 text-paper underline underline-offset-4"
+ >
+ Clear all
+ </Link>
+ </div>
+ ) : null}
+
+ {/* Filters */}
+ <form method="get" className="mt-6 grid gap-3 sm:grid-cols-[1fr_auto_auto]">
+ <label className="block">
+ <span className="label mb-1 block">Search text</span>
+ <input
+ type="search"
+ name="q"
+ defaultValue={q ?? ""}
+ placeholder="Bill number or title"
+ className="focus-ring w-full rounded-[3px] border border-line bg-surface px-3 py-2 font-mono text-[13px] text-paper placeholder:text-ash-2"
+ />
+ </label>
+ <label className="block">
+ <span className="label mb-1 block">Policy area</span>
+ <select
+ name="policyArea"
+ defaultValue={policyArea ?? ""}
+ className="focus-ring min-h-[44px] w-full rounded-[3px] border border-line bg-surface px-3 py-2 font-mono text-[13px] text-paper sm:w-56"
+ >
+ <option value="">Any</option>
+ {policyAreas.map((p) => (
+ <option key={p} value={p}>{p}</option>
+ ))}
+ </select>
+ </label>
+ <label className="block sm:self-end">
+ <span className="sr-only">Congress</span>
+ <select
+ name="congress"
+ defaultValue={Number.isFinite(congressNum) ? String(congressNum) : ""}
+ className="focus-ring min-h-[44px] w-full rounded-[3px] border border-line bg-surface px-3 py-2 font-mono text-[13px] text-paper sm:w-32"
+ >
+ <option value="">Any congress</option>
+ {congresses.map((c) => (
+ <option key={c} value={c}>{c}th</option>
+ ))}
+ </select>
+ </label>
+ <div className="sm:self-end">
+ <button
+ type="submit"
+ className="focus-ring label inline-flex min-h-[44px] w-full items-center justify-center rounded-[3px] border border-line bg-surface px-4 text-paper hover:bg-graphite sm:w-auto"
+ >
+ Filter
+ </button>
+ </div>
+ </form>
+
+ <div className="mt-8">
+ <SectionHeader
+ eyebrow={`${bills.length} bill${bills.length === 1 ? "" : "s"}`}
+ title={policyArea ? policyArea : "On record"}
+ />
+ {bills.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No bills match this filter in the current record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Bills on record: number, title, policy area, status, last action.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Bill</Th>
+ <Th>Policy area</Th>
+ <Th>Status</Th>
+ <Th>Last action</Th>
+ <Th className="text-right">Source</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {bills.map((b) => (
+ <tr
+ key={b.id}
+ className="border-b border-line font-mono text-[12px] last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="text-paper">
+ <Link
+ href={b.href}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {b.number}
+ </Link>
+ <span className="block text-ash-2">{b.congress}th</span>
+ </Td>
+ <Td className="align-middle">
+ {b.policyArea ? (
+ <Link
+ href={`/bills?policyArea=${encodeURIComponent(b.policyArea)}`}
+ className="focus-ring rounded-sm text-ash underline underline-offset-4 hover:text-paper"
+ >
+ {b.policyArea}
+ </Link>
+ ) : (
+ <span className="text-ash-2">-</span>
+ )}
+ <span className="block max-w-[24rem] truncate text-ash-2">{b.title}</span>
+ </Td>
+ <Td>
+ <StatusPill variant={classifyStatus(b.status)}>
+ {b.status || "Introduced"}
+ </StatusPill>
+ </Td>
+ <Td className="tabular-nums text-ash">{formatDate(b.updatedAt)}</Td>
+ <Td className="text-right">
+ <SourceLink url={b.sourceUrl} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ </div>
+
+ <div className="mt-12 border-t border-line pt-6">
+ <p className="label text-ash-2">
+ Bills from congress.gov. Order is by last action date - no editorial
+ weighting. No comment surface appears here, by policy.
+ </p>
+ </div>
+ </div>
+ );
+}
+
+function dropParam(
+ sp: Record<string, string | string[] | undefined>,
+ key: string
+): string {
+ const next = { ...sp };
+ delete next[key];
+ const params = new URLSearchParams();
+ for (const [k, v] of Object.entries(next)) {
+ if (Array.isArray(v)) v.forEach((x) => params.append(k, x));
+ else if (v != null) params.set(k, v);
+ }
+ const s = params.toString();
+ return s ? `/bills?${s}` : "/bills";
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
app/calendar/page.tsx +449 -0
@@ -0,0 +1,449 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { getCalendarEvents, type CalendarEvent, type CalendarKind } from "@/lib/feed";
+import { clsx } from "@/lib/cx";
+
+export const dynamic = "force-dynamic";
+
+export const metadata: Metadata = {
+ title: "Calendar",
+ description:
+ "A month grid of when laws take effect, bills become law, and votes happen. Each day links to its official source.",
+};
+
+/* Kind → plain-language + compact mark + muted token. The mark text is always
+ * shown (never color alone) so the grid stays legible in Plain mode and for
+ * color-blind readers (docs/ACCESSIBILITY.md #7; docs/NEUTRALITY.md §Viz). */
+type KindMeta = { value: CalendarKind; plain: string; mark: string; color: string };
+
+const KINDS: KindMeta[] = [
+ { value: "effective", plain: "Takes effect", mark: "Effective", color: "var(--dk-hold)" },
+ { value: "effective_parsed", plain: "Takes effect", mark: "Effective", color: "var(--dk-hold)" },
+ { value: "enacted", plain: "Became law", mark: "Law", color: "var(--dk-yea)" },
+ { value: "vote", plain: "Vote", mark: "Vote", color: "var(--dk-party-d)" },
+ { value: "floor", plain: "Floor action", mark: "Floor", color: "var(--dk-party-r)" },
+ { value: "committee", plain: "Committee", mark: "Committee", color: "var(--dk-party-i)" },
+ { value: "introduced", plain: "Introduced", mark: "Introduced", color: "var(--dk-ash)" },
+ { value: "apportionment", plain: "Apportionment", mark: "Appr.", color: "var(--dk-hold)" },
+];
+
+const KIND_BY_VALUE = new Map<CalendarKind, KindMeta>(
+ KINDS.map((k) => [k.value, k])
+);
+
+const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+const MONTH_NAMES = [
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December",
+];
+
+function pad(n: number): string {
+ return n < 10 ? `0${n}` : String(n);
+}
+
+function isoToday(): string {
+ const d = new Date();
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
+}
+
+/** Parse ?month=YYYY-MM into {year,month}; fall back to the current month. */
+function parseMonth(raw: string | undefined): { year: number; month: number } {
+ const m = /^(\d{4})-(\d{1,2})$/.exec(raw ?? "");
+ if (m) {
+ const year = parseInt(m[1], 10);
+ const month = parseInt(m[2], 10);
+ if (year >= 1900 && year <= 9999 && month >= 1 && month <= 12) {
+ return { year, month };
+ }
+ }
+ const d = new Date();
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1 };
+}
+
+function shiftMonth(year: number, month: number, delta: number): { year: number; month: number } {
+ const zero = month - 1 + delta;
+ const d = new Date(Date.UTC(year, zero, 1));
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1 };
+}
+
+export default function CalendarPage({
+ searchParams,
+}: {
+ searchParams: { month?: string; day?: string; kind?: string };
+}) {
+ const { year, month } = parseMonth(searchParams.month);
+ const monthParam = `${year}-${pad(month)}`;
+ const todayIso = isoToday();
+
+ const selectedDay = searchParams.day && /^\d{4}-\d{2}-\d{2}$/.test(searchParams.day)
+ ? searchParams.day
+ : undefined;
+ const kindFilter = searchParams.kind || undefined;
+
+ const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
+ const firstWeekday = new Date(Date.UTC(year, month - 1, 1)).getUTCDay();
+ const from = `${year}-${pad(month)}-01`;
+ const to = `${year}-${pad(month)}-${pad(daysInMonth)}`;
+
+ // Events for the whole visible month (filtered by kind when requested).
+ const monthEvents = getCalendarEvents({ from, to, kind: kindFilter });
+ const byDate = new Map<string, CalendarEvent[]>();
+ for (const e of monthEvents) {
+ const arr = byDate.get(e.date) ?? [];
+ arr.push(e);
+ byDate.set(e.date, arr);
+ }
+
+ const prev = shiftMonth(year, month, -1);
+ const next = shiftMonth(year, month, +1);
+ const prevHref = hrefFor(prev, selectedDay, kindFilter);
+ const nextHref = hrefFor(next, selectedDay, kindFilter);
+ const todayHref = hrefFor({ year: todayYear(), month: todayMonth() }, todayIso, kindFilter);
+
+ const dayEvents = selectedDay ? byDate.get(selectedDay) ?? [] : [];
+ const kindCounts = countKinds(monthEvents);
+
+ // Build the grid cells (leading blanks, days, trailing blanks to fill rows).
+ const cells: Array<number | null> = [];
+ for (let i = 0; i < firstWeekday; i++) cells.push(null);
+ for (let d = 1; d <= daysInMonth; d++) cells.push(d);
+ while (cells.length % 7 !== 0) cells.push(null);
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href="/register"
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← Register
+ </Link>
+
+ <PageHeader
+ eyebrow="Calendar · when it happens"
+ title="What takes effect when"
+ lead="A month at a glance. Each day lists the laws that take effect, bills that became law, votes, and floor action. Pick a day to read its events - each sealed to its official source."
+ />
+
+ {/* Month navigation */}
+ <div className="mt-8 flex flex-wrap items-center justify-between gap-3">
+ <div className="flex items-center gap-1">
+ <Link
+ href={prevHref}
+ className="focus-ring label inline-flex min-h-[44px] items-center rounded-[3px] border border-line px-3 text-ash hover:text-paper"
+ aria-label={`Previous month, ${MONTH_NAMES[prev.month - 1]} ${prev.year}`}
+ >
+ ← {MONTH_NAMES[prev.month - 1].slice(0, 3)}
+ </Link>
+ <p
+ className="display px-2 text-2xl text-paper sm:text-3xl"
+ aria-live="polite"
+ >
+ {MONTH_NAMES[month - 1]} {year}
+ </p>
+ <Link
+ href={nextHref}
+ className="focus-ring label inline-flex min-h-[44px] items-center rounded-[3px] border border-line px-3 text-ash hover:text-paper"
+ aria-label={`Next month, ${MONTH_NAMES[next.month - 1]} ${next.year}`}
+ >
+ {MONTH_NAMES[next.month - 1].slice(0, 3)} →
+ </Link>
+ </div>
+ <Link
+ href={todayHref}
+ className="focus-ring label inline-flex min-h-[44px] items-center rounded-[3px] border border-line px-3 text-ash underline underline-offset-4 hover:text-paper"
+ >
+ Today
+ </Link>
+ </div>
+
+ {/* Kind filter - plain-word chips; selection lives in the URL. */}
+ <div className="mt-5 flex flex-wrap items-center gap-1.5" role="group" aria-label="Filter by kind">
+ <Link
+ href={hrefFor({ year, month }, selectedDay, undefined)}
+ className={clsx(
+ "focus-ring label inline-flex min-h-[44px] items-center rounded-[3px] border px-2.5",
+ !kindFilter
+ ? "border-paper text-paper"
+ : "border-line text-ash hover:text-paper"
+ )}
+ aria-pressed={!kindFilter}
+ >
+ All
+ </Link>
+ {KINDS.filter((k, i) => KINDS.findIndex((x) => x.mark === k.mark) === i).map((k) => {
+ const active = kindFilter === k.value;
+ return (
+ <Link
+ key={k.value}
+ href={hrefFor({ year, month }, selectedDay, active ? undefined : k.value)}
+ className={clsx(
+ "focus-ring label inline-flex min-h-[44px] items-center gap-1.5 rounded-[3px] border px-2.5",
+ active
+ ? "border-paper text-paper"
+ : "border-line text-ash hover:text-paper"
+ )}
+ aria-pressed={active}
+ >
+ <span
+ aria-hidden="true"
+ className="inline-block h-2 w-2 rounded-full"
+ style={{ backgroundColor: k.color }}
+ />
+ {k.plain}
+ </Link>
+ );
+ })}
+ </div>
+
+ <div className="mt-6 grid gap-6 lg:grid-cols-[1.5fr_1fr]">
+ {/* MONTH GRID */}
+ <Card className="overflow-hidden">
+ <div className="overflow-x-auto">
+ <table className="w-full min-w-[40rem] border-collapse text-left">
+ <caption className="sr-only">
+ Calendar grid for {MONTH_NAMES[month - 1]} {year}. Each cell is a
+ day; select a day to read its events. Rows are weeks, columns
+ are weekdays starting Sunday.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ {WEEKDAYS.map((w) => (
+ <th key={w} scope="col" className="label px-2 py-2.5 font-normal">
+ {w}
+ </th>
+ ))}
+ </tr>
+ </thead>
+ <tbody>
+ {chunk(cells, 7).map((row, ri) => (
+ <tr key={ri} className="border-b border-line last:border-0">
+ {row.map((day, ci) => {
+ if (day == null) {
+ return (
+ <td key={ci} aria-hidden="true" className="bg-ink/40 p-0">
+ <div className="min-h-[5.5rem]" />
+ </td>
+ );
+ }
+ const dateStr = `${year}-${pad(month)}-${pad(day)}`;
+ const evs = byDate.get(dateStr) ?? [];
+ const isToday = dateStr === todayIso;
+ const isSelected = dateStr === selectedDay;
+ const marks = distinctMarks(evs);
+ return (
+ <td key={ci} className="border-r border-line p-0 align-top last:border-r-0">
+ <Link
+ href={hrefFor({ year, month }, dateStr, kindFilter)}
+ aria-label={`${day} ${MONTH_NAMES[month - 1]} ${year}${evs.length ? `, ${evs.length} event${evs.length === 1 ? "" : "s"}` : ", no events"}`}
+ aria-current={isSelected ? "true" : undefined}
+ className={clsx(
+ "focus-ring block min-h-[5.5rem] p-2 no-underline",
+ isSelected ? "bg-graphite" : "hover:bg-graphite/50"
+ )}
+ >
+ <span
+ className={clsx(
+ "flex h-7 w-7 items-center justify-center rounded-full font-mono text-[13px] tabular-nums underline",
+ isToday
+ ? "bg-signal text-ink"
+ : evs.length
+ ? "text-paper"
+ : "text-ash-2"
+ )}
+ >
+ {day}
+ </span>
+ {evs.length > 0 ? (
+ <div className="mt-1.5 flex flex-wrap gap-1">
+ {marks.slice(0, 3).map((m, i) => (
+ <span
+ key={i}
+ className="pill !px-1 !py-0"
+ style={{
+ color: m.color,
+ borderColor: `color-mix(in srgb, ${m.color} 45%, transparent)`,
+ }}
+ >
+ {m.mark}
+ </span>
+ ))}
+ {evs.length > 3 ? (
+ <span className="label !text-[10px] text-ash-2">
+ +{evs.length - 3}
+ </span>
+ ) : null}
+ </div>
+ ) : null}
+ </Link>
+ </td>
+ );
+ })}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <p className="label border-t border-line px-3 py-2 text-ash-2">
+ {monthEvents.length} event{monthEvents.length === 1 ? "" : "s"} this month. Select a day to read them.
+ </p>
+ </Card>
+
+ {/* SIDE: legend + selected day */}
+ <div className="space-y-6">
+ <Card className="p-5">
+ <p className="label mb-3">Legend</p>
+ <ul className="space-y-2">
+ {KINDS.filter((k, i) => KINDS.findIndex((x) => x.mark === k.mark) === i).map((k) => (
+ <li key={k.value} className="flex items-center gap-2.5">
+ <span
+ aria-hidden="true"
+ className="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
+ style={{ backgroundColor: k.color }}
+ />
+ <span className="font-mono text-[12px] text-paper">{k.plain}</span>
+ <span className="label text-ash-2">
+ {kindCounts.get(k.value) ?? 0}
+ </span>
+ </li>
+ ))}
+ </ul>
+ <p className="label mt-4 text-ash-2">
+ Colors are a cue only. Each mark carries its name, so the grid
+ reads clearly in Plain mode too.
+ </p>
+ </Card>
+
+ <div>
+ <SectionHeader
+ eyebrow={selectedDay ? "Selected day" : "Pick a day"}
+ title={selectedDay ? formatLong(selectedDay) : "Day events"}
+ />
+ {!selectedDay ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ Select a day in the grid to read its events. Each event links
+ to its official source.
+ </p>
+ </Card>
+ ) : dayEvents.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No events on record for {formatLong(selectedDay)}
+ {kindFilter ? " for this filter" : ""}.
+ </p>
+ </Card>
+ ) : (
+ <Card className="divide-y divide-[color:var(--dk-line)]">
+ {dayEvents.map((e) => {
+ const k = KIND_BY_VALUE.get(e.kind);
+ return (
+ <div key={e.id} className="p-4">
+ <div className="flex flex-wrap items-center gap-2">
+ {k ? (
+ <span
+ className="pill"
+ style={{
+ color: k.color,
+ borderColor: `color-mix(in srgb, ${k.color} 45%, transparent)`,
+ }}
+ >
+ {k.plain}
+ </span>
+ ) : (
+ <span className="pill text-ash">{e.kind}</span>
+ )}
+ <span className="label text-ash-2">{e.refType}</span>
+ </div>
+ <p className="mt-2 font-mono text-[12px] leading-relaxed text-paper">
+ {e.href ? (
+ <Link
+ href={e.href}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {e.title}
+ </Link>
+ ) : (
+ e.title
+ )}
+ </p>
+ <div className="mt-2">
+ <SourceLink url={e.sourceUrl} />
+ </div>
+ </div>
+ );
+ })}
+ </Card>
+ )}
+ </div>
+ </div>
+ </div>
+
+ <div className="mt-12 border-t border-line pt-6">
+ <p className="label text-ash-2">
+ Dates parsed from congress.gov, federalregister.gov, clerk.house.gov,
+ and senate.gov. Each event links to its source. No commentary appears here.
+ </p>
+ </div>
+ </div>
+ );
+}
+
+/* ---------------- helpers ---------------- */
+
+function hrefFor(
+ ym: { year: number; month: number },
+ day: string | undefined,
+ kind: string | undefined
+): string {
+ const params = new URLSearchParams();
+ params.set("month", `${ym.year}-${pad(ym.month)}`);
+ if (day) params.set("day", day);
+ if (kind) params.set("kind", kind);
+ return `/calendar?${params.toString()}`;
+}
+
+function todayYear(): number {
+ return new Date().getUTCFullYear();
+}
+function todayMonth(): number {
+ return new Date().getUTCMonth() + 1;
+}
+
+function chunk<T>(arr: T[], size: number): T[][] {
+ const out: T[][] = [];
+ for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
+ return out;
+}
+
+function distinctMarks(evs: CalendarEvent[]): KindMeta[] {
+ const seen = new Set<string>();
+ const out: KindMeta[] = [];
+ for (const e of evs) {
+ const m = KIND_BY_VALUE.get(e.kind);
+ if (m && !seen.has(m.mark)) {
+ seen.add(m.mark);
+ out.push(m);
+ }
+ }
+ return out;
+}
+
+function countKinds(evs: CalendarEvent[]): Map<CalendarKind, number> {
+ const m = new Map<CalendarKind, number>();
+ for (const e of evs) m.set(e.kind, (m.get(e.kind) ?? 0) + 1);
+ return m;
+}
+
+function formatLong(iso: string): string {
+ const d = new Date(iso + "T00:00:00Z");
+ if (Number.isNaN(d.getTime())) return iso;
+ return d.toLocaleDateString("en-US", {
+ timeZone: "UTC",
+ weekday: "long",
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+}
app/categories/page.tsx +85 -0
@@ -0,0 +1,85 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader } from "@/components/ui";
+import { getCategories } from "@/lib/feed";
+
+export const dynamic = "force-dynamic";
+
+export const metadata: Metadata = {
+ title: "Topics",
+ description:
+ "Browse the record by topic - bills and awards grouped by policy area. Counts only, no editorial weighting.",
+};
+
+export default function CategoriesPage() {
+ const cats = getCategories().sort(
+ (a, b) => b.total - a.total || a.label.localeCompare(b.label)
+ );
+
+ const withItems = cats.filter((c) => c.total > 0);
+ const empty = cats.filter((c) => c.total === 0);
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href="/register"
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← Register
+ </Link>
+
+ <PageHeader
+ eyebrow="Topics · browse the record"
+ title="By policy area"
+ lead="Every bill is tagged with one policy area; federal awards carry the same tags. Pick a topic to read the bills filed under it. Counts reflect the record only - no ranking by importance."
+ />
+
+ <div className="mt-8">
+ <p className="label mb-3">{withItems.length} topics on record</p>
+ <ul className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
+ {withItems.map((c) => (
+ <li key={c.slug}>
+ <Link
+ href={`/bills?policyArea=${encodeURIComponent(c.label)}`}
+ className="focus-ring block rounded-[3px] border border-line bg-surface p-4 transition-colors hover:bg-graphite"
+ >
+ <div className="flex items-start justify-between gap-3">
+ <span className="font-mono text-[13px] text-paper">
+ {c.label}
+ </span>
+ <span className="label shrink-0 text-ash-2">{c.total}</span>
+ </div>
+ <p className="label mt-2 text-ash-2">
+ {c.billCount} bill{c.billCount === 1 ? "" : "s"}
+ {c.awardCount > 0 ? ` · ${c.awardCount} award${c.awardCount === 1 ? "" : "s"}` : ""}
+ </p>
+ </Link>
+ </li>
+ ))}
+ </ul>
+ </div>
+
+ {empty.length > 0 ? (
+ <div className="mt-10">
+ <p className="label mb-3">No record yet</p>
+ <Card className="p-5">
+ <p className="font-mono text-[12px] leading-relaxed text-ash-2">
+ {empty.map((c) => c.label).join(" · ")}
+ </p>
+ <p className="label mt-3 text-ash-2">
+ These topics have no bills or awards on record. They appear here so
+ the list stays complete rather than hiding the gap.
+ </p>
+ </Card>
+ </div>
+ ) : null}
+
+ <div className="mt-12 border-t border-line pt-6">
+ <p className="label text-ash-2">
+ Policy areas come from congress.gov bill metadata. Awards are tagged
+ from USAspending.gov. No topic is weighted over another by coverage.
+ </p>
+ </div>
+ </div>
+ );
+}
app/executive/page.tsx +72 -0
@@ -0,0 +1,72 @@
+import type { Metadata } from "next";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { ExecutiveBrowser } from "@/components/executive-browser";
+import { getExecutive } from "@/lib/feed";
+
+export const metadata: Metadata = {
+ title: "Federal Register",
+ description:
+ "Executive Orders, proposed and final rules, and notices from the Federal Register. Filterable by type.",
+};
+
+export const dynamic = "force-dynamic";
+
+const TYPE_LABEL: Record<string, string> = {
+ eo: "Executive Order",
+ nprm: "Proposed Rule",
+ final_rule: "Final Rule",
+ notice: "Notice",
+};
+
+export default function ExecutivePage() {
+ const all = getExecutive();
+
+ const byType = all.reduce<Record<string, number>>((m, e) => {
+ const k = (e.type || "").toLowerCase();
+ m[k] = (m[k] ?? 0) + 1;
+ return m;
+ }, {});
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <PageHeader
+ eyebrow="Federal Register"
+ title="Executive actions"
+ lead="Executive Orders, proposed and final rules, and notices published in the Federal Register. Each entry links to the official document on federalregister.gov."
+ />
+
+ <div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
+ <TypeCard label="Executive Orders" count={byType.eo ?? 0} />
+ <TypeCard label="Proposed Rules" count={byType.nprm ?? 0} />
+ <TypeCard label="Final Rules" count={byType.final_rule ?? 0} />
+ <TypeCard label="Notices" count={byType.notice ?? 0} />
+ </div>
+
+ <div className="mt-10">
+ <SectionHeader
+ eyebrow={`${all.length} actions`}
+ title="On the record"
+ description="Newest first. Filter by type. Every row links to the Federal Register document."
+ />
+ <ExecutiveBrowser seed={all} typeLabels={TYPE_LABEL} />
+ </div>
+
+ <p className="label mt-8 text-ash-2">
+ Source: federalregister.gov API. Types and document numbers are recorded as published; no characterization.
+ </p>
+ </div>
+ );
+}
+
+function TypeCard({ label, count }: { label: string; count: number }) {
+ return (
+ <Card className="p-5">
+ <p className="label mb-2">{label}</p>
+ <div className="flex items-baseline gap-2">
+ <span className="display text-3xl text-paper">{count}</span>
+ <span className="label text-ash-2">on record</span>
+ </div>
+ </Card>
+ );
+}
app/globals.css +134 -0
@@ -38,6 +38,11 @@
rgba(224, 165, 59, 0) 70%
);
+ /* a11y (docs/ACCESSIBILITY.md #2): dark body >=18px, line-height 1.6.
+ Data tables/labels may run smaller; readable copy uses this floor. */
+ --dk-body-size: 18px;
+ --dk-leading: 1.6;
+
color-scheme: dark;
}
@@ -53,6 +58,44 @@
--dk-signal: #b07f25; /* brass darkens for contrast */
--dk-signal-dim: #8c6316;
--dk-signal-tint: rgba(176, 127, 37, 0.1);
+ /* a11y #2: light body >=16px, line-height 1.5 */
+ --dk-body-size: 16px;
+ --dk-leading: 1.5;
+ color-scheme: light;
+}
+
+/*
+ * Plain mode - high-contrast "Plain Mode" (docs/ACCESSIBILITY.md §3).
+ * Max contrast (black ink on #FFFFCC), system sans (not mono), >=20px,
+ * no accent color, uniform weight. One click from anywhere.
+ */
+:root.plain {
+ --dk-ink: #ffffcc; /* high-contrast paper */
+ --dk-surface: #ffffe0;
+ --dk-graphite: #f2f2b8;
+ --dk-paper: #000000; /* pure ink */
+ --dk-ash: #1a1a1a;
+ --dk-ash-2: #333333;
+ --dk-line: rgba(0, 0, 0, 0.45);
+
+ /* no accent in plain mode - brass collapses to ink */
+ --dk-signal: #000000;
+ --dk-signal-dim: #000000;
+ --dk-signal-tint: rgba(0, 0, 0, 0.06);
+
+ /* muted semantic tokens collapse toward ink/grey (no vivid color) */
+ --dk-yea: #1a1a1a;
+ --dk-nay: #1a1a1a;
+ --dk-hold: #1a1a1a;
+ --dk-party-d: #1a1a1a;
+ --dk-party-r: #1a1a1a;
+ --dk-party-i: #333333;
+
+ --dk-hero-glow: none;
+
+ /* a11y #2/#8: >=20px, generous leading, system sans */
+ --dk-body-size: 20px;
+ --dk-leading: 1.6;
color-scheme: light;
}
@@ -64,6 +107,8 @@
background-color: var(--dk-ink);
color: var(--dk-paper);
font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: var(--dk-body-size);
+ line-height: var(--dk-leading);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
@@ -71,6 +116,95 @@
background: var(--dk-signal-tint);
color: var(--dk-paper);
}
+
+ /* Plain mode overrides the brand fonts with the system stack and
+ uniform weight - readability over the forensic look (a11y §3). */
+ :root.plain body,
+ :root.plain .display,
+ :root.plain .label,
+ :root.plain .prose-lead,
+ :root.plain .font-mono,
+ :root.plain .font-display,
+ :root.plain .font-prose {
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif !important;
+ letter-spacing: normal !important;
+ text-transform: none !important;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Accessibility enforcement (docs/ACCESSIBILITY.md - the 8 rules). */
+/* ------------------------------------------------------------------ */
+@layer a11y {
+ /* Rule #4: visible focus on EVERY interactive element, never removed. */
+ :focus-visible {
+ outline: 2px solid var(--dk-signal);
+ outline-offset: 2px;
+ border-radius: 2px;
+ }
+ /* Keep a visible focus ring color even in plain mode (signal == ink there). */
+ :root.plain :focus-visible {
+ outline-color: #000000;
+ outline-width: 3px;
+ }
+
+ /* Rule #7: links carry underline + color, never color alone. */
+ a {
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 0.18em;
+ }
+ /* Links that are deliberately icon/wordmark-only keep a clean underline on focus. */
+ a:focus-visible {
+ text-decoration: underline;
+ }
+
+ /* Rule #3: >=44px touch targets for primary interactive controls. */
+ button,
+ [role="button"],
+ input[type="checkbox"],
+ input[type="radio"],
+ input[type="search"],
+ select {
+ min-height: 44px;
+ min-width: 44px;
+ }
+
+ /* Skip-to-content link - visible only when focused (keyboard-first). */
+ .skip-link {
+ position: absolute;
+ left: 0.5rem;
+ top: -3rem;
+ z-index: 100;
+ background: var(--dk-surface);
+ color: var(--dk-paper);
+ border: 1px solid var(--dk-line);
+ padding: 0.5rem 0.75rem;
+ transition: top 120ms ease;
+ }
+ .skip-link:focus {
+ top: 0.5rem;
+ }
+
+ /* Rule #5 (partial): no hover-only affordances survive on their own.
+ The Source link and row affordances are always visible in markup;
+ disable hover-only visual changes when the device can't hover. */
+ @media (hover: none) {
+ .reg-row:hover {
+ background: transparent;
+ }
+ }
+
+ /* Respect reduced-motion: stop the live-dot pulse and row fade. */
+ @media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.001ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.001ms !important;
+ }
+ }
}
@layer components {
app/layout.tsx +23 -1
@@ -55,11 +55,33 @@ export default function RootLayout({
<html
lang="en"
className={`${archivo.variable} ${jetbrains.variable} ${fraunces.variable} dark`}
+ suppressHydrationWarning
>
+ <head>
+ {/*
+ Apply the persisted display mode before paint (a11y §"Three display
+ modes"). Runs synchronously in <head> so the first frame is already
+ the user's chosen mode - no flash, and suppressHydrationWarning lets
+ the inline script own the class without a React mismatch.
+ */}
+ <script
+ dangerouslySetInnerHTML={{
+ __html: `(function(){try{var m=localStorage.getItem('docket-display')||'dark';var c=document.documentElement.classList;c.remove('dark','light','plain');c.add(m);document.documentElement.dataset.display=m;}catch(e){}})();`,
+ }}
+ />
+ </head>
<body className="min-h-dvh bg-ink text-paper antialiased">
+ <a
+ href="#main"
+ className="skip-link focus-ring label"
+ >
+ Skip to content
+ </a>
<div className="relative flex min-h-dvh flex-col">
<SiteHeader />
- <main className="flex-1">{children}</main>
+ <main id="main" className="flex-1">
+ {children}
+ </main>
<SiteFooter />
</div>
</body>
app/members/[id]/page.tsx +332 -0
@@ -0,0 +1,332 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { PartyTag } from "@/components/party-tag";
+import { PositionTag } from "@/components/position-tag";
+import { StatusPill, classifyStatus } from "@/components/status-pill";
+import { getMember } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
+
+export const dynamic = "force-dynamic";
+
+export function generateMetadata({
+ params,
+}: {
+ params: { id: string };
+}): Metadata {
+ const member = getMember(params.id);
+ if (!member) return { title: "Member not found" };
+ return {
+ title: `${member.name} (${member.partyCode}-${member.state})`,
+ description: `Recorded votes, sponsored bills, and amendments for ${member.name}.`,
+ };
+}
+
+export default function MemberDetailPage({
+ params,
+}: {
+ params: { id: string };
+}) {
+ const member = getMember(params.id);
+ if (!member) notFound();
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href="/members"
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← Members
+ </Link>
+
+ <PageHeader
+ eyebrow={`${member.chamber === "senate" ? "U.S. Senate" : "U.S. House"} · ${member.state}${
+ member.district != null ? ` district ${member.district}` : ""
+ }`}
+ title={member.name}
+ lead={`Recorded roll-call positions, sponsored bills, and amendments. ${member.voteCount} recorded vote position(s) on file.`}
+ />
+
+ {/* top facts */}
+ <div className="mt-8 grid gap-4 sm:grid-cols-3">
+ <Card className="p-5">
+ <p className="label mb-2">Party</p>
+ <PartyTag party={member.partyCode} />
+ <p className="label mt-2 text-ash-2">{member.party || "-"}</p>
+ </Card>
+
+ <Card className="p-5">
+ <p className="label mb-2">State / district</p>
+ <p className="font-mono text-[13px] text-paper">{member.state}</p>
+ <p className="label mt-2 text-ash-2">
+ {member.district != null ? `District ${member.district}` : "Statewide"}
+ </p>
+ </Card>
+
+ <Card className="p-5">
+ <p className="label mb-2">Official record</p>
+ <SourceLink url={member.sourceUrl} label="Bioguide" />
+ <p className="label mt-2 text-ash-2">{member.bioguide}</p>
+ </Card>
+ </div>
+
+ <div className="mt-10 grid gap-8 lg:grid-cols-[1.3fr_1fr]">
+ {/* RECENT VOTES - how they voted */}
+ <div>
+ <SectionHeader
+ eyebrow={`Recent votes · ${member.votes.length} shown`}
+ title="How they voted"
+ />
+ {member.votes.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No recorded vote positions are linked to this member.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ This member&apos;s recent recorded votes: date, chamber, result, position.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Date</Th>
+ <Th>Chamber</Th>
+ <Th>Result</Th>
+ <Th>Voted</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {member.votes.map((v) => (
+ <tr
+ key={v.voteId}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="tabular-nums text-ash">{formatDate(v.date)}</Td>
+ <Td className="uppercase text-ash-2">{v.chamber}</Td>
+ <Td className="text-paper">
+ <Link
+ href={v.voteHref}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {v.question || v.result}
+ </Link>
+ <span className="block text-ash-2">{v.result}</span>
+ </Td>
+ <Td>
+ <PositionTag position={v.position} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ </div>
+
+ {/* SPONSORED BILLS */}
+ <div>
+ <SectionHeader
+ eyebrow={`Sponsored · ${member.sponsorships.length}`}
+ title="Bills introduced"
+ />
+ {member.sponsorships.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No sponsored bills are linked to this member in the current record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="divide-y divide-[color:var(--dk-line)]">
+ {member.sponsorships.slice(0, 12).map((b) => (
+ <div key={b.billId} className="p-4">
+ <Link
+ href={b.billHref}
+ className="focus-ring font-mono text-[13px] text-paper underline underline-offset-4 hover:text-paper"
+ >
+ {b.number}
+ </Link>
+ <p className="mt-1 font-mono text-[12px] text-ash">
+ {b.title}
+ </p>
+ <div className="mt-2 flex items-center gap-2">
+ <StatusPill variant={classifyStatus(b.status)}>{b.status || "Introduced"}</StatusPill>
+ <span className="label text-ash-2">{formatDate(b.updatedAt)}</span>
+ </div>
+ </div>
+ ))}
+ </Card>
+ )}
+ </div>
+ </div>
+
+ {/* VOTE RECORD BY YEAR - the year-by-year headline view */}
+ <div className="mt-10">
+ <SectionHeader
+ eyebrow={`By year · ${member.votesByYear.length} year${member.votesByYear.length === 1 ? "" : "s"}`}
+ title="Vote record, year by year"
+ description="Every recorded roll-call position this member has taken, grouped by year. Open a year to read each vote and how they voted. Newest year first."
+ />
+ {member.votesByYear.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No recorded vote positions are linked to this member.
+ </p>
+ </Card>
+ ) : (
+ <>
+ {/* Per-year count summary (informational, not jump links) */}
+ <ul className="mb-5 flex flex-wrap gap-2">
+ {member.votesByYear.map((yg) => (
+ <li
+ key={yg.year ?? "undated"}
+ className="label inline-flex min-h-[44px] items-center gap-2 rounded-[3px] border border-line px-2.5 text-ash"
+ >
+ <span className="text-paper">{yg.year ?? "Undated"}</span>
+ <span className="text-ash-2">{yg.count}</span>
+ </li>
+ ))}
+ </ul>
+
+ <div className="space-y-3">
+ {member.votesByYear.map((yg, i) => (
+ <Card key={yg.year ?? "undated"} className="overflow-hidden">
+ <details open={i === 0}>
+ <summary
+ className="focus-ring flex cursor-pointer items-center justify-between gap-3 px-4 py-3.5"
+ >
+ <span className="font-mono text-[13px] text-paper">
+ {yg.year ?? "Undated"}
+ </span>
+ <span className="label text-ash-2">
+ {yg.count} vote{yg.count === 1 ? "" : "s"}
+ </span>
+ </summary>
+ <div className="border-t border-line">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ {yg.year ?? "Undated"} votes for {member.name}: date,
+ chamber, question, result, and this member&apos;s position.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Date</Th>
+ <Th>Chamber</Th>
+ <Th>Question</Th>
+ <Th>Voted</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {yg.votes.map((v) => (
+ <tr
+ key={v.voteId}
+ className="border-b border-line font-mono text-[12px] last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="tabular-nums text-ash">{formatDate(v.date)}</Td>
+ <Td className="uppercase text-ash-2">{v.chamber}</Td>
+ <Td className="text-paper">
+ <Link
+ href={v.voteHref}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {v.question || v.result}
+ </Link>
+ <span className="block text-ash-2">{v.result}</span>
+ </Td>
+ <Td>
+ <PositionTag position={v.position} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </details>
+ </Card>
+ ))}
+ </div>
+ </>
+ )}
+ </div>
+
+ {/* AMENDMENTS */}
+ {member.amendments.length > 0 ? (
+ <div className="mt-10">
+ <SectionHeader
+ eyebrow={`Amendments · ${member.amendments.length}`}
+ title="Amendments offered"
+ />
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Amendment</Th>
+ <Th>Bill</Th>
+ <Th>Status</Th>
+ <Th className="text-right">Source</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {member.amendments.map((a) => (
+ <tr
+ key={a.amendmentId}
+ className="border-b border-line font-mono text-[12px] last:border-0"
+ >
+ <Td className="text-paper">
+ {a.type} {a.number}
+ </Td>
+ <Td className="text-ash">
+ {a.billHref ? (
+ <Link
+ href={a.billHref}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ Bill {a.billId}
+ </Link>
+ ) : (
+ "-"
+ )}
+ </Td>
+ <Td className="uppercase text-ash-2">{a.status || "-"}</Td>
+ <Td className="text-right">
+ <SourceLink url={a.sourceUrl} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ </div>
+ ) : null}
+
+ <div className="mt-12 border-t border-line pt-6">
+ <p className="label text-ash-2">
+ Vote positions and sponsorships from clerk.house.gov, senate.gov, and congress.gov. No comment or reaction surface appears here, by policy.
+ </p>
+ </div>
+ </div>
+ );
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
app/members/page.tsx +65 -0
@@ -0,0 +1,65 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { PartyTag } from "@/components/party-tag";
+import {
+ getMembers,
+ getMemberStates,
+ type MemberSummary,
+} from "@/lib/feed";
+import { MembersBrowser } from "@/components/members-browser";
+
+export const metadata: Metadata = {
+ title: "Members of Congress",
+ description:
+ "Browse the U.S. House and Senate. Search by name, state, chamber, or party. Each member links to their full record.",
+};
+
+export const dynamic = "force-dynamic";
+
+export default function MembersPage() {
+ const all = getMembers();
+ const states = getMemberStates();
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <PageHeader
+ eyebrow="Congress roster"
+ title="Members of Congress"
+ lead="Search and filter the sitting House and Senate. Open a member to see their recorded votes, sponsored bills, and amendments - what your representative did, on the record."
+ />
+
+ <div className="mt-8">
+ {/* Summary by chamber, ordered by count then alphabetical (not by party). */}
+ <div className="grid gap-4 sm:grid-cols-3">
+ <SummaryCard label="Total" count={all.length} />
+ <SummaryCard
+ label="Senate"
+ count={all.filter((m) => m.chamber === "senate").length}
+ />
+ <SummaryCard
+ label="House"
+ count={all.filter((m) => m.chamber === "house").length}
+ />
+ </div>
+
+ <div className="mt-10">
+ <MembersBrowser seed={all} states={states} />
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function SummaryCard({ label, count }: { label: string; count: number }) {
+ return (
+ <Card className="p-5">
+ <p className="label mb-2">{label}</p>
+ <div className="flex items-baseline gap-2">
+ <span className="display text-3xl text-paper">{count}</span>
+ <span className="label text-ash-2">members</span>
+ </div>
+ </Card>
+ );
+}
app/register/page.tsx +164 -94
@@ -3,10 +3,15 @@ import Link from "next/link";
import { RegisterHero } from "@/components/register-hero";
import { Card, SectionHeader } from "@/components/ui";
import { SourceLink } from "@/components/source-link";
+import { ChangesFeed } from "@/components/changes-feed";
import {
- REGISTER_ACTIONS,
- formatUsd,
-} from "@/lib/mock-data";
+ getFeedItems,
+ getAwards,
+ getChangeEvents,
+ getCategories,
+ type FeedItem,
+} from "@/lib/feed";
+import { formatUsd, formatDate, formatEt } from "@/lib/format";
export const metadata: Metadata = {
title: "The Register",
@@ -14,78 +19,116 @@ export const metadata: Metadata = {
"A live, stamped ledger of official US government actions - bills, votes, and dollars. Every fact sourced.",
};
-export default function RegisterPage() {
- // Proportional policy-area composition of this week's actions (rule #3:
- // proportional to official-action volume + dollar magnitude, not editorial).
- const areaCounts = countByArea();
+// Always show the freshest ingest rows.
+export const dynamic = "force-dynamic";
+
+const ACTION_LABEL: Record<FeedItem["kind"], string> = {
+ bill: "Bills",
+ vote: "Votes",
+ award: "Awards",
+ amendment: "Amendments",
+ rider: "Riders",
+ procedural: "Procedural",
+ executive: "Federal Register",
+ apportionment: "Apportionments",
+};
+
+export default async function RegisterPage() {
+ const items = getFeedItems();
+ const awards = getAwards();
+ const changes = getChangeEvents(12);
+ const topTopics = getCategories()
+ .filter((c) => c.total > 0)
+ .sort((a, b) => b.total - a.total)
+ .slice(0, 8);
+ const weekAwardsTotal = awards.reduce((n, a) => n + a.amount, 0);
return (
<>
- <RegisterHero />
+ <RegisterHero seed={items} />
<div className="mx-auto w-full max-w-register px-4 py-12 sm:px-6">
<SectionHeader
- eyebrow="This week · full register"
+ eyebrow="Full register · ranked"
title="Every official action, in order"
- description="A complete record of actions sealed this week. The same items appear in the live ledger above."
+ description="The complete record below is the same stream as the live ledger, ranked by action date and dollar magnitude - never by coverage or clicks."
/>
- <Card className="overflow-hidden">
- <table className="w-full border-collapse text-left">
- <thead>
- <tr className="border-b border-line">
- <Th>Time</Th>
- <Th>Body</Th>
- <Th>Item</Th>
- <Th>Action</Th>
- <Th className="text-right">Result</Th>
- <Th className="text-right">Source</Th>
- </tr>
- </thead>
- <tbody>
- {REGISTER_ACTIONS.map((r) => (
- <tr
- key={r.id}
- className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
- >
- <Td className="tabular-nums text-ash">{r.time}</Td>
- <Td className="uppercase text-ash-2">{r.chamber}</Td>
- <Td className="text-paper">
- {r.billHref ? (
- <Link
- href={r.billHref}
- className="focus-ring rounded-sm hover:underline underline-offset-4"
- >
- {r.label}
- </Link>
- ) : (
- r.label
- )}
- </Td>
- <Td className="uppercase text-ash">{r.actionType}</Td>
- <Td className="text-right tabular-nums text-paper">
- {r.result}
- </Td>
- <Td className="text-right">
- <SourceLink url={r.sourceUrl} />
- </Td>
+ {items.length === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No actions on record.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ The register fills as the government acts. Each line will carry a
+ link to its official source.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Official actions in ranked order: time, body, item, action,
+ result, and source.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Date</Th>
+ <Th>Body</Th>
+ <Th>Item</Th>
+ <Th>Action</Th>
+ <Th className="text-right">Result</Th>
+ <Th className="text-right">Source</Th>
</tr>
- ))}
- </tbody>
- </table>
- </Card>
+ </thead>
+ <tbody>
+ {items.map((r) => (
+ <tr
+ key={r.id}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="tabular-nums text-ash">
+ {formatEt(r.actionDate) || formatDate(r.actionDate)}
+ </Td>
+ <Td className="uppercase text-ash-2">{r.chamber}</Td>
+ <Td className="text-paper">
+ {r.href ? (
+ <Link
+ href={r.href}
+ className="focus-ring rounded-sm underline underline-offset-4"
+ >
+ {r.title}
+ </Link>
+ ) : (
+ r.title
+ )}
+ {r.summary ? (
+ <span className="block text-ash-2">{r.summary}</span>
+ ) : null}
+ </Td>
+ <Td className="uppercase text-ash">{r.actionType}</Td>
+ <Td className="text-right tabular-nums text-paper">
+ {r.amountText ?? "-"}
+ </Td>
+ <Td className="text-right">
+ <SourceLink url={r.sourceUrl} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
- {/* Proportional composition (not editorial picks) */}
+ {/* Composition by kind - proportional to official-action volume (rule #3). */}
<div className="mt-12 grid gap-6 sm:grid-cols-2">
<Card className="p-5">
- <p className="label mb-4">Composition by policy area</p>
+ <p className="label mb-4">Composition by action type</p>
<ul className="space-y-2.5">
- {areaCounts.map((a) => {
- const max = Math.max(...areaCounts.map((x) => x.count));
+ {byKind(items).map((a) => {
+ const max = Math.max(1, ...byKind(items).map((x) => x.count));
return (
- <li key={a.area} className="font-mono text-[12px]">
+ <li key={a.kind} className="font-mono text-[12px]">
<div className="flex items-center justify-between">
- <span className="text-ash">{a.area}</span>
+ <span className="text-ash">{ACTION_LABEL[a.kind]}</span>
<span className="tabular-nums text-paper">{a.count}</span>
</div>
<div className="mt-1 h-1 w-full overflow-hidden rounded-full bg-ink">
@@ -107,32 +150,83 @@ export default function RegisterPage() {
</Card>
<Card className="p-5">
- <p className="label mb-4">Spending, this week</p>
+ <p className="label mb-4">Awards, on record</p>
<p className="display text-3xl text-paper">
- {formatUsd(
- REGISTER_ACTIONS.reduce(
- (n, r) => n + (r.amountUsd ?? 0),
- 0
- ),
- { compact: true }
- )}
+ {formatUsd(weekAwardsTotal, { compact: true })}
</p>
<p className="prose-lead mt-2 text-ash">
- Awarded or outlaid across the actions above.
+ {awards.length} federal awards from USAspending.gov, summed across
+ the record. Figures only - no characterization.
</p>
<Link
href="/spending"
- className="focus-ring label mt-5 inline-flex items-center gap-1 text-ash hover:text-paper"
+ className="focus-ring label mt-5 inline-flex items-center gap-1 text-ash underline underline-offset-4 hover:text-paper"
>
Spending register →
</Link>
</Card>
</div>
+
+ {/* Recently changed + browse by topic */}
+ <div className="mt-12 grid gap-6 sm:grid-cols-2">
+ <Card className="p-5">
+ <div className="mb-3 flex items-baseline justify-between">
+ <p className="label">Recently changed</p>
+ <Link
+ href="/calendar"
+ className="focus-ring label text-ash underline underline-offset-4 hover:text-paper"
+ >
+ Calendar →
+ </Link>
+ </div>
+ <ChangesFeed items={changes} />
+ <p className="label mt-4 text-ash-2">
+ What the ingest added, updated, or removed. Each entry links to its
+ source.
+ </p>
+ </Card>
+
+ <Card className="p-5">
+ <div className="mb-3 flex items-baseline justify-between">
+ <p className="label">Browse by topic</p>
+ <Link
+ href="/categories"
+ className="focus-ring label text-ash underline underline-offset-4 hover:text-paper"
+ >
+ All topics →
+ </Link>
+ </div>
+ <ul className="flex flex-wrap gap-1.5">
+ {topTopics.map((c) => (
+ <li key={c.slug}>
+ <Link
+ href={`/bills?policyArea=${encodeURIComponent(c.label)}`}
+ className="focus-ring label inline-flex min-h-[44px] items-center gap-2 rounded-[3px] border border-line px-2.5 text-ash hover:text-paper"
+ >
+ <span className="text-paper">{c.label}</span>
+ <span className="text-ash-2">{c.total}</span>
+ </Link>
+ </li>
+ ))}
+ </ul>
+ <p className="label mt-4 text-ash-2">
+ Bills and awards grouped by policy area. Counts only - no weighting.
+ </p>
+ </Card>
+ </div>
</div>
</>
);
}
+function byKind(items: FeedItem[]) {
+ const map = new Map<FeedItem["kind"], number>();
+ for (const i of items) map.set(i.kind, (map.get(i.kind) ?? 0) + 1);
+ return Array.from(map, ([kind, count]) => ({ kind, count })).sort(
+ (a, b) => b.count - a.count
+ );
+}
+
function Th({
children,
className = "",
@@ -140,13 +234,7 @@ function Th({
children: React.ReactNode;
className?: string;
}) {
- return (
- <th
- className={`label px-4 py-2.5 font-normal ${className}`}
- >
- {children}
- </th>
- );
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
}
function Td({
@@ -158,21 +246,3 @@ function Td({
}) {
return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
}
-
-function countByArea() {
- // Phase 0: derived from the mock register. Phase 1 reads the events table.
- const map = new Map<string, number>();
- const sample = [
- "Transportation",
- "Appropriations",
- "Defense",
- "Health",
- "Taxation",
- "Science",
- "Treasury",
- ];
- sample.forEach((a, i) => map.set(a, [3, 2, 1, 1, 1, 1, 1][i] ?? 1));
- return Array.from(map, ([area, count]) => ({ area, count })).sort(
- (a, b) => b.count - a.count
- );
-}
app/search/page.tsx +195 -0
@@ -0,0 +1,195 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { globalSearch, type SearchResults } from "@/lib/feed";
+import { SearchBox } from "@/components/search-box";
+
+export const metadata: Metadata = {
+ title: "Search",
+ description:
+ "Look up a bill, member, agency, or topic across the official record.",
+};
+
+export const dynamic = "force-dynamic";
+
+export default function SearchPage({
+ searchParams,
+}: {
+ searchParams: { q?: string };
+}) {
+ const q = (searchParams.q ?? "").trim();
+ const results: SearchResults = q
+ ? globalSearch(q)
+ : { bills: [], awards: [], members: [], executive: [], states: [] };
+
+ const total =
+ results.bills.length +
+ results.members.length +
+ results.executive.length +
+ results.awards.length +
+ results.states.length;
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <PageHeader
+ eyebrow="Look it up"
+ title="Search the record"
+ lead="Look up a bill, a member of Congress, a state, an agency, or a topic. Every result links to its official source."
+ />
+
+ <div className="mt-8">
+ <SearchBox initialValue={q} />
+
+ {/* Pre-built filter chips (a11y §Search: big clickable chips). */}
+ {!q ? (
+ <div className="mt-6">
+ <p className="label mb-3 text-ash-2">Try a starting point</p>
+ <div className="flex flex-wrap gap-2">
+ {["tax", "defense", "California", "appropriations", "Texas", "immigration"].map(
+ (chip) => (
+ <Link
+ key={chip}
+ href={`/search?q=${encodeURIComponent(chip)}`}
+ className="focus-ring label inline-flex min-h-[44px] items-center rounded-[2px] border border-line bg-surface px-4 text-ash underline-offset-4 transition-colors hover:bg-graphite hover:text-paper"
+ >
+ {chip}
+ </Link>
+ )
+ )}
+ </div>
+ </div>
+ ) : null}
+ </div>
+
+ {q ? (
+ <div className="mt-10">
+ <p className="label mb-6 text-ash">
+ {total} result{total === 1 ? "" : "s"} for “{q}”
+ </p>
+
+ {total === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No matches on record.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ Try a state code, a bill number, an agency, or a member&apos;s name.
+ </p>
+ </Card>
+ ) : (
+ <div className="space-y-10">
+ {results.members.length > 0 ? (
+ <ResultGroup title="Members" count={results.members.length}>
+ {results.members.map((m) => (
+ <Link
+ key={m.id}
+ href={m.href}
+ className="focus-ring group block border-b border-line p-3 transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <p className="font-mono text-[13px] text-paper group-hover:underline">
+ {m.name}
+ </p>
+ <p className="label text-ash-2">
+ {m.chamber === "senate" ? "Senate" : "House"} · {m.state}
+ {m.district != null ? ` ${m.district}` : ""} · {m.party}
+ </p>
+ </Link>
+ ))}
+ </ResultGroup>
+ ) : null}
+
+ {results.bills.length > 0 ? (
+ <ResultGroup title="Bills" count={results.bills.length}>
+ {results.bills.map((b) => (
+ <Link
+ key={b.id}
+ href={b.href}
+ className="focus-ring group block border-b border-line p-3 transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <p className="font-mono text-[13px] text-paper group-hover:underline">
+ {b.number} · {b.congress}th Congress
+ </p>
+ <p className="font-mono text-[12px] text-ash">{b.title}</p>
+ <p className="label mt-1 text-ash-2">{b.policyArea}</p>
+ </Link>
+ ))}
+ </ResultGroup>
+ ) : null}
+
+ {results.executive.length > 0 ? (
+ <ResultGroup title="Federal Register" count={results.executive.length}>
+ {results.executive.map((e) => (
+ <div
+ key={e.id}
+ className="flex items-start justify-between gap-3 border-b border-line p-3 last:border-0"
+ >
+ <div className="min-w-0">
+ <p className="font-mono text-[13px] text-paper">{e.title}</p>
+ <p className="label mt-1 text-ash-2">
+ {e.agency}
+ {e.date ? ` · ${e.date.slice(0, 10)}` : ""}
+ </p>
+ </div>
+ <SourceLink url={e.sourceUrl} label="FR" />
+ </div>
+ ))}
+ </ResultGroup>
+ ) : null}
+
+ {results.awards.length > 0 ? (
+ <ResultGroup title="Federal awards" count={results.awards.length}>
+ {results.awards.map((a) => (
+ <div
+ key={a.id}
+ className="flex items-start justify-between gap-3 border-b border-line p-3 last:border-0"
+ >
+ <div className="min-w-0">
+ <p className="font-mono text-[13px] text-paper">{a.agency}</p>
+ <p className="label mt-1 text-ash-2">{a.recipient}</p>
+ </div>
+ <SourceLink url={a.sourceUrl} label="USAspending" />
+ </div>
+ ))}
+ </ResultGroup>
+ ) : null}
+
+ {results.states.length > 0 ? (
+ <ResultGroup title="States" count={results.states.length}>
+ {results.states.map((s) => (
+ <Link
+ key={s.code}
+ href={`/states/${s.code}`}
+ className="focus-ring group block border-b border-line p-3 transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <p className="font-mono text-[13px] text-paper group-hover:underline">
+ {s.code} · {s.name}
+ </p>
+ </Link>
+ ))}
+ </ResultGroup>
+ ) : null}
+ </div>
+ )}
+ </div>
+ ) : null}
+ </div>
+ );
+}
+
+function ResultGroup({
+ title,
+ count,
+ children,
+}: {
+ title: string;
+ count: number;
+ children: React.ReactNode;
+}) {
+ return (
+ <div>
+ <p className="label mb-3">
+ {title} · {count}
+ </p>
+ <Card>{children}</Card>
+ </div>
+ );
+}
app/spending/page.tsx +229 -57
@@ -1,93 +1,265 @@
import type { Metadata } from "next";
-import { AWARDS, FISCAL_METRICS, formatUsd, formatDate } from "@/lib/mock-data";
+import { getAwards, getFiscal, getApportionments } from "@/lib/feed";
import { Card, PageHeader, SectionHeader } from "@/components/ui";
import { SourceLink } from "@/components/source-link";
+import { formatUsd, formatDate } from "@/lib/format";
export const metadata: Metadata = {
title: "Spending",
description:
- "Federal awards, outlays, and fiscal totals from USAspending.gov and Treasury. Every figure sourced.",
+ "Federal awards, OMB apportionments, and fiscal totals from USAspending.gov, .max.gov, and Treasury. Every figure sourced.",
};
+export const dynamic = "force-dynamic";
+
+/** Impound status → muted semantic tone (released = sage, delayed = hold,
+ * withheld = clay). Never vivid; a withheld status is recorded as fact. */
+function apportionmentColor(status: string): string {
+ const s = (status || "").toLowerCase();
+ if (s.startsWith("withhold")) return "var(--dk-nay)";
+ if (s.startsWith("delay")) return "var(--dk-hold)";
+ return "var(--dk-yea)";
+}
+
export default function SpendingPage() {
- const totalAwards = AWARDS.reduce((n, a) => n + a.amountUsd, 0);
+ const awards = getAwards();
+ const fiscal = getFiscal();
+ const apportionments = getApportionments();
+ const totalAwards = awards.reduce((n, a) => n + a.amount, 0);
+
+ const withheld = apportionments.filter((a) =>
+ (a.status || "").toLowerCase().startsWith("withhold")
+ );
+ const delayed = apportionments.filter((a) =>
+ (a.status || "").toLowerCase().startsWith("delay")
+ );
+ const released = apportionments.filter((a) =>
+ (a.status || "").toLowerCase().startsWith("release")
+ );
+ const withholdTotal = withheld.reduce(
+ (n, a) => n + (a.appropriated ?? 0) - (a.apportioned ?? 0),
+ 0
+ );
return (
<div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
<PageHeader
eyebrow="Spending register"
title="Dollars, by official record"
- lead="Awards and outlays from USAspending.gov; debt, outlays, and cash from the Daily Treasury Statement. Figures only - no characterization."
+ lead="Awards from USAspending.gov; debt, outlays, and cash from the Daily Treasury Statement. Figures only - no characterization."
/>
- {/* fiscal totals */}
- <div className="mt-8 grid gap-4 sm:grid-cols-3">
- {FISCAL_METRICS.map((m) => (
- <Card key={m.id} className="p-5">
- <p className="label mb-2">{m.metric}</p>
- <p className="display text-2xl text-paper">
- {formatUsd(m.valueUsd, { compact: true })}
- </p>
- <p className="label mt-2 text-ash-2">
- {formatDate(m.asOf)} · {m.deltaLabel}
+ {/* fiscal totals - calm empty state while the Treasury ingest lands */}
+ {fiscal.length > 0 ? (
+ <div className="mt-8 grid gap-4 sm:grid-cols-3">
+ {fiscal.map((m) => (
+ <Card key={m.id} className="p-5">
+ <p className="label mb-2">{m.metric}</p>
+ <p className="display text-2xl text-paper">{m.valueText}</p>
+ <p className="label mt-2 text-ash-2">{formatDate(m.date)}</p>
+ <div className="mt-3">
+ <SourceLink url={m.sourceUrl} />
+ </div>
+ </Card>
+ ))}
+ </div>
+ ) : (
+ <Card className="mt-8 p-5">
+ <p className="label mb-2">Fiscal totals</p>
+ <p className="font-mono text-[13px] text-ash">
+ Debt, outlays, and cash figures from Treasury are not yet in the
+ record. The Daily Treasury Statement feed will populate this panel.
+ </p>
+ <div className="mt-3">
+ <SourceLink url="https://fiscaldata.treasury.gov/datasets/daily-treasury-statement/operating-cash-balance" />
+ </div>
+ </Card>
+ )}
+
+ {/* awards table - REAL USAspending data */}
+ <div className="mt-12">
+ <SectionHeader
+ eyebrow="Federal awards"
+ title="Contracts, grants, and assistance"
+ description={`${awards.length} awards from USAspending.gov, totaling ${formatUsd(
+ totalAwards,
+ { compact: true }
+ )}. Largest first.`}
+ />
+ {awards.length === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No awards on record.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ USAspending.gov awards will appear here as the ingest runs.
</p>
- <div className="mt-3">
- <SourceLink url={m.sourceUrl} />
- </div>
</Card>
- ))}
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Federal awards: agency, recipient, program, date, amount, source.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Agency</Th>
+ <Th>Recipient</Th>
+ <Th>Program</Th>
+ <Th>Date</Th>
+ <Th className="text-right">Amount</Th>
+ <Th className="text-right">Source</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {awards.map((a) => (
+ <tr
+ key={a.id}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="text-paper">{a.agency}</Td>
+ <Td className="text-ash">{a.recipient}</Td>
+ <Td className="text-ash">{a.program}</Td>
+ <Td className="tabular-nums text-ash-2">
+ {formatDate(a.actionDate)}
+ </Td>
+ <Td className="text-right tabular-nums text-paper">
+ {a.amountText}
+ </Td>
+ <Td className="text-right">
+ <SourceLink url={a.sourceUrl} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ <p className="label mt-4 text-ash-2">
+ Source: USAspending.gov. Figures reflect recorded award obligations;
+ the source link on each row opens the official record.
+ </p>
</div>
- {/* awards table */}
+ {/* impound tracker - OMB apportionments (.max.gov) */}
<div className="mt-12">
<SectionHeader
- eyebrow="Notable awards"
- title="Contracts, grants, and assistance"
- description={`Totaling ${formatUsd(totalAwards, { compact: true })} across the items below.`}
+ eyebrow="Apportionments"
+ title="Impound tracker"
+ description="Appropriations vs. amounts apportioned for spending, per agency and account. A withheld or delayed status is recorded as fact, not characterized."
/>
- <Card className="overflow-hidden">
- <table className="w-full border-collapse text-left">
- <thead>
- <tr className="border-b border-line">
- <Th>Agency</Th>
- <Th>Recipient</Th>
- <Th>Program</Th>
- <Th>Date</Th>
- <Th className="text-right">Amount</Th>
- <Th className="text-right">Source</Th>
- </tr>
- </thead>
- <tbody>
- {AWARDS.map((a) => (
- <tr
- key={a.id}
- className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
- >
- <Td className="text-paper">{a.agency}</Td>
- <Td className="text-ash">{a.recipient}</Td>
- <Td className="text-ash">{a.program}</Td>
- <Td className="tabular-nums text-ash-2">
- {formatDate(a.actionDate)}
- </Td>
- <Td className="text-right tabular-nums text-paper">
- {formatUsd(a.amountUsd)}
- </Td>
- <Td className="text-right">
- <SourceLink url={a.sourceUrl} />
- </Td>
+
+ {/* status summary - muted, equal weight */}
+ <div className="mb-6 grid gap-4 sm:grid-cols-3">
+ <ImpoundCard label="Released" count={released.length} color="var(--dk-yea)" />
+ <ImpoundCard label="Delayed" count={delayed.length} color="var(--dk-hold)" />
+ <ImpoundCard
+ label="Withheld"
+ count={withheld.length}
+ color="var(--dk-nay)"
+ sub={withholdTotal > 0 ? `${formatUsd(withholdTotal, { compact: true })} gap` : undefined}
+ />
+ </div>
+
+ {apportionments.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash">
+ Apportionment data from OMB (.max.gov) is not yet in the record.
+ When the feed is active, each row shows appropriated vs. apportioned
+ and a released, delayed, or withheld status.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ OMB apportionments: agency, account, appropriated, apportioned, status, source.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Agency</Th>
+ <Th>Account</Th>
+ <Th className="text-right">Appropriated</Th>
+ <Th className="text-right">Apportioned</Th>
+ <Th>Status</Th>
+ <Th className="text-right">Source</Th>
</tr>
- ))}
- </tbody>
- </table>
- </Card>
+ </thead>
+ <tbody>
+ {apportionments.map((a) => {
+ const color = apportionmentColor(a.status);
+ return (
+ <tr
+ key={a.id}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="text-paper">{a.agency}</Td>
+ <Td className="text-ash">{a.account}</Td>
+ <Td className="text-right tabular-nums text-paper">
+ {a.appropriated == null ? "-" : formatUsd(a.appropriated)}
+ </Td>
+ <Td className="text-right tabular-nums text-paper">
+ {a.apportioned == null ? "-" : formatUsd(a.apportioned)}
+ </Td>
+ <Td>
+ <span
+ className="inline-flex items-center gap-1.5 uppercase"
+ style={{ color }}
+ >
+ <span
+ aria-hidden="true"
+ className="inline-block h-1.5 w-1.5 rounded-full"
+ style={{ backgroundColor: color }}
+ />
+ {a.status}
+ </span>
+ </Td>
+ <Td className="text-right">
+ <SourceLink url={a.sourceUrl} label="OMB" />
+ </Td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </Card>
+ )}
<p className="label mt-4 text-ash-2">
- USAspending.gov caps results per call; completeness requires bulk download. Phase 1 ingest covers the full file.
+ Source: apportionment-public.max.gov (OMB). Appropriated and apportioned are recorded figures; the gap is shown where one exists.
</p>
</div>
</div>
);
}
+function ImpoundCard({
+ label,
+ count,
+ color,
+ sub,
+}: {
+ label: string;
+ count: number;
+ color: string;
+ sub?: string;
+}) {
+ return (
+ <Card className="p-5">
+ <div className="label mb-2 flex items-center gap-1.5" style={{ color }}>
+ <span
+ aria-hidden="true"
+ className="inline-block h-1.5 w-1.5 rounded-full"
+ style={{ backgroundColor: color }}
+ />
+ {label}
+ </div>
+ <div className="flex items-baseline gap-2">
+ <span className="display text-3xl text-paper">{count}</span>
+ <span className="label text-ash-2">account{count === 1 ? "" : "s"}</span>
+ </div>
+ {sub ? <p className="label mt-2 text-ash">{sub}</p> : null}
+ </Card>
+ );
+}
+
function Th({
children,
className = "",
app/states/[code]/page.tsx +154 -0
@@ -0,0 +1,154 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { PartyTag } from "@/components/party-tag";
+import { StatusPill, classifyStatus } from "@/components/status-pill";
+import { getStateDetail } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
+
+export const dynamic = "force-dynamic";
+
+export function generateMetadata({
+ params,
+}: {
+ params: { code: string };
+}): Metadata {
+ const detail = getStateDetail(params.code);
+ return { title: `${detail.name} (${detail.code})` };
+}
+
+function confidenceColor(c: "ok" | "stale" | "missing"): string {
+ return c === "ok" ? "var(--dk-yea)" : c === "stale" ? "var(--dk-hold)" : "var(--dk-nay)";
+}
+
+export default function StateDetailPage({
+ params,
+}: {
+ params: { code: string };
+}) {
+ const detail = getStateDetail(params.code);
+ if (!detail.code) notFound();
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href="/states"
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← States
+ </Link>
+
+ <PageHeader
+ eyebrow={`State · ${detail.code}`}
+ title={detail.name}
+ lead={`Federal congressional delegation and sponsored bills for ${detail.name}. State-level legislative coverage is shown honestly below.`}
+ />
+
+ {/* coverage card */}
+ <div className="mt-8">
+ <Card className="p-5">
+ <div className="flex flex-wrap items-center gap-3">
+ <span
+ className="inline-flex items-center gap-1.5"
+ style={{ color: confidenceColor(detail.confidence) }}
+ >
+ <span
+ aria-hidden="true"
+ className="inline-block h-2 w-2 rounded-full"
+ style={{ backgroundColor: confidenceColor(detail.confidence) }}
+ />
+ <span className="label">
+ {detail.confidence === "ok"
+ ? "Current"
+ : detail.confidence === "stale"
+ ? "Stale"
+ : "Missing"}
+ </span>
+ </span>
+ <span className="label text-ash-2">
+ {detail.lastScrape ? `Last seen ${formatDate(detail.lastScrape)}` : "No state-leg scrape on record"}
+ </span>
+ </div>
+ {detail.missingTypes.length > 0 ? (
+ <p className="mt-3 font-mono text-[13px] text-ash">
+ State-legislative data not in the record: {detail.missingTypes.join(", ")}. Federal delegation and bill sponsorships below are from congress.gov.
+ </p>
+ ) : null}
+ </Card>
+ </div>
+
+ <div className="mt-10 grid gap-8 lg:grid-cols-2">
+ {/* DELEGATION */}
+ <div>
+ <SectionHeader
+ eyebrow={`Delegation · ${detail.members.length}`}
+ title="Members of Congress"
+ />
+ {detail.members.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No members of Congress linked to {detail.name} in the record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="divide-y divide-[color:var(--dk-line)]">
+ {detail.members.map((m) => (
+ <Link
+ key={m.id}
+ href={m.href}
+ className="focus-ring group flex items-center justify-between gap-2 p-4 transition-colors hover:bg-graphite"
+ >
+ <div className="min-w-0">
+ <p className="font-mono text-[13px] text-paper group-hover:underline">
+ {m.name}
+ </p>
+ <p className="label mt-1 text-ash-2">
+ {m.chamber === "senate" ? "Senate" : "House"}
+ {m.district != null ? ` · Dist. ${m.district}` : ""}
+ </p>
+ </div>
+ <PartyTag party={m.partyCode} />
+ </Link>
+ ))}
+ </Card>
+ )}
+ </div>
+
+ {/* SPONSORED BILLS */}
+ <div>
+ <SectionHeader
+ eyebrow={`Bills · ${detail.bills.length}`}
+ title="Sponsored by delegation"
+ />
+ {detail.bills.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash-2">
+ No bills sponsored by the {detail.name} delegation are in the record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="divide-y divide-[color:var(--dk-line)]">
+ {detail.bills.slice(0, 20).map((b) => (
+ <div key={b.id} className="p-4">
+ <Link
+ href={b.href}
+ className="focus-ring font-mono text-[13px] text-paper underline underline-offset-4 hover:text-paper"
+ >
+ {b.number}
+ </Link>
+ <p className="mt-1 font-mono text-[12px] text-ash">{b.title}</p>
+ <div className="mt-2">
+ <StatusPill variant={classifyStatus(b.status)}>
+ {b.policyArea || "Introduced"}
+ </StatusPill>
+ </div>
+ </div>
+ ))}
+ </Card>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+}
app/states/page.tsx +48 -46
@@ -1,98 +1,100 @@
import type { Metadata } from "next";
-import {
- STATE_COVERAGE,
- formatDate,
- type StateCoverage,
-} from "@/lib/mock-data";
+import Link from "next/link";
import { Card, PageHeader, SectionHeader } from "@/components/ui";
-import { SourceLink } from "@/components/source-link";
+import { getStates, type StateCoverage } from "@/lib/feed";
import { clsx } from "@/lib/cx";
+import { formatDate } from "@/lib/format";
export const metadata: Metadata = {
title: "States",
description:
- "Per-state legislative coverage via OpenStates, with an honest coverage map.",
+ "Per-state coverage and confidence. Gaps are recorded, not hidden. Open a state for its delegation and sponsored bills.",
};
-const LEVEL_LABEL: Record<StateCoverage["level"], string> = {
- full: "Full",
- partial: "Partial",
- lagging: "Lagging",
-};
+export const dynamic = "force-dynamic";
-function levelColor(level: StateCoverage["level"]): string {
+function confidenceColor(c: StateCoverage["confidence"]): string {
// muted semantic tones - equal weight (neutrality §Viz)
- return level === "full"
- ? "var(--dk-yea)"
- : level === "partial"
- ? "var(--dk-hold)"
- : "var(--dk-nay)";
+ return c === "ok" ? "var(--dk-yea)" : c === "stale" ? "var(--dk-hold)" : "var(--dk-nay)";
+}
+
+function confidenceLabel(c: StateCoverage["confidence"]): string {
+ return c === "ok" ? "Current" : c === "stale" ? "Stale" : "Missing";
}
export default function StatesPage() {
- const full = STATE_COVERAGE.filter((s) => s.level === "full").length;
- const partial = STATE_COVERAGE.filter((s) => s.level === "partial").length;
- const lagging = STATE_COVERAGE.filter((s) => s.level === "lagging").length;
+ const states = getStates();
+ const ok = states.filter((s) => s.confidence === "ok").length;
+ const stale = states.filter((s) => s.confidence === "stale").length;
+ const missing = states.filter((s) => s.confidence === "missing").length;
return (
<div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
<PageHeader
eyebrow="State register"
title="Coverage, by state"
- lead="State legislative activity via OpenStates. Freshness and schema completeness vary by state; this map records the gaps rather than hiding them."
+ lead="State legislative activity via OpenStates and official state legislatures. Freshness and completeness vary; this map records the gaps rather than hiding them. Federal delegation and sponsored bills are shown per state."
/>
- {/* coverage summary - muted, equal weight */}
+ {/* confidence summary - muted, equal weight */}
<div className="mt-8 grid gap-4 sm:grid-cols-3">
- <SummaryCard label="Full coverage" count={full} level="full" />
- <SummaryCard label="Partial coverage" count={partial} level="partial" />
- <SummaryCard label="Lagging" count={lagging} level="lagging" />
+ <ConfidenceCard label="Current" count={ok} level="ok" />
+ <ConfidenceCard label="Stale" count={stale} level="stale" />
+ <ConfidenceCard label="Missing" count={missing} level="missing" />
</div>
<div className="mt-12">
- <SectionHeader eyebrow="Per-state" title="Coverage and freshness" />
+ <SectionHeader
+ eyebrow={`Per-state · ${states.length}`}
+ title="Confidence map"
+ description="Each state's worst-case coverage across bills, votes, and sponsors. Open a state for its congressional delegation."
+ />
<Card className="overflow-hidden">
<table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Per-state coverage confidence: state, level, last scrape, missing data types.
+ </caption>
<thead>
<tr className="border-b border-line">
<Th>State</Th>
- <Th>Coverage</Th>
+ <Th>Confidence</Th>
<Th>Last seen</Th>
- <Th className="text-right">Actions this week</Th>
- <Th className="text-right">Source</Th>
+ <Th>Missing</Th>
</tr>
</thead>
<tbody>
- {STATE_COVERAGE.map((s) => (
+ {states.map((s) => (
<tr
key={s.code}
className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
>
<Td className="text-paper">
- <span className="mr-2 text-ash-2">{s.code}</span>
- {s.name}
+ <Link
+ href={`/states/${s.code}`}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ <span className="mr-2 text-ash-2">{s.code}</span>
+ {s.name}
+ </Link>
</Td>
<Td>
<span
className="inline-flex items-center gap-1.5"
- style={{ color: levelColor(s.level) }}
+ style={{ color: confidenceColor(s.confidence) }}
>
<span
aria-hidden="true"
className="inline-block h-1.5 w-1.5 rounded-full"
- style={{ backgroundColor: levelColor(s.level) }}
+ style={{ backgroundColor: confidenceColor(s.confidence) }}
/>
- {LEVEL_LABEL[s.level]}
+ {confidenceLabel(s.confidence)}
</span>
</Td>
<Td className="tabular-nums text-ash-2">
- {formatDate(s.lastSeen)}
- </Td>
- <Td className="text-right tabular-nums text-paper">
- {s.billsThisWeek}
+ {s.lastScrape ? formatDate(s.lastScrape) : "-"}
</Td>
- <Td className="text-right">
- <SourceLink url={s.sourceUrl} />
+ <Td className="uppercase text-ash-2">
+ {s.missingTypes.length ? s.missingTypes.join(", ") : "-"}
</Td>
</tr>
))}
@@ -100,21 +102,21 @@ export default function StatesPage() {
</table>
</Card>
<p className="label mt-4 text-ash-2">
- State coverage requires a paid OpenStates tier for production. Phase 1.5 stands this up with per-state polling cadence.
+ State legislative coverage requires per-state polling. Federal delegation and bill sponsorships (from congress.gov) are shown regardless of state-leg coverage.
</p>
</div>
</div>
);
}
-function SummaryCard({
+function ConfidenceCard({
label,
count,
level,
}: {
label: string;
count: number;
- level: StateCoverage["level"];
+ level: StateCoverage["confidence"];
}) {
return (
<Card className="p-5">
@@ -125,7 +127,7 @@ function SummaryCard({
</div>
<span
className={clsx("mt-3 inline-block h-0.5 w-12 rounded-full")}
- style={{ backgroundColor: levelColor(level) }}
+ style={{ backgroundColor: confidenceColor(level) }}
aria-hidden="true"
/>
</Card>
app/votes/[id]/page.tsx +287 -0
@@ -0,0 +1,287 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { StatusPill, classifyStatus } from "@/components/status-pill";
+import { VoteTally } from "@/components/vote-tally";
+import { PositionTag, positionColor } from "@/components/position-tag";
+import { PartyTag } from "@/components/party-tag";
+import { getVoteById, type VotePosition } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
+
+export const dynamic = "force-dynamic";
+
+export function generateMetadata({
+ params,
+}: {
+ params: { id: string };
+}): Metadata {
+ const numeric = parseInt(params.id.replace(/^vote:/, ""), 10);
+ const vote = Number.isFinite(numeric) ? getVoteById(numeric) : null;
+ if (!vote) return { title: "Vote not found" };
+ return {
+ title: `${vote.chamber} vote · ${formatDate(vote.date)}`,
+ description: vote.result,
+ };
+}
+
+/** Default roster sort: by vote (Yea, Nay, Present, Not Voting), then
+ * alphabetical by name - NOT grouped by party (neutrality §Viz). */
+const POSITION_ORDER: Record<string, number> = {
+ yea: 0,
+ nay: 1,
+ present: 2,
+ "not voting": 3,
+};
+function rosterSort(a: VotePosition, b: VotePosition): number {
+ const pa = POSITION_ORDER[(a.position || "").toLowerCase()] ?? 9;
+ const pb = POSITION_ORDER[(b.position || "").toLowerCase()] ?? 9;
+ if (pa !== pb) return pa - pb;
+ return a.member.localeCompare(b.member);
+}
+
+export default function VoteDetailPage({
+ params,
+}: {
+ params: { id: string };
+}) {
+ const numeric = parseInt(params.id.replace(/^vote:/, ""), 10);
+ const vote = Number.isFinite(numeric) ? getVoteById(numeric) : null;
+ if (!vote) notFound();
+
+ const variant = classifyStatus(vote.result);
+ const passed = vote.yea > vote.nay;
+ const roster = [...vote.positions].sort(rosterSort);
+
+ // Counts per position for the summary chips.
+ const counts = vote.positions.reduce<Record<string, number>>((m, p) => {
+ const key = (p.position || "Not Voting").toLowerCase();
+ m[key] = (m[key] ?? 0) + 1;
+ return m;
+ }, {});
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <Link
+ href="/votes"
+ className="focus-ring label mb-6 inline-flex items-center gap-1 text-ash hover:text-paper"
+ >
+ ← Votes
+ </Link>
+
+ <PageHeader
+ eyebrow={`${vote.chamber} · ${formatDate(vote.date)}${
+ vote.rollNumber ? ` · Roll ${vote.rollNumber}` : ""
+ }`}
+ title={vote.question || vote.result || "Roll-call vote"}
+ lead={vote.result}
+ />
+
+ {/* top facts */}
+ <div className="mt-8 grid gap-4 sm:grid-cols-3">
+ <Card className="p-5">
+ <p className="label mb-2">Result</p>
+ <StatusPill variant={variant} dot>
+ {passed ? "Passed" : "Failed"}
+ </StatusPill>
+ <p className="mt-2 font-mono text-[12px] text-ash-2">{vote.result}</p>
+ </Card>
+
+ <Card className="p-5">
+ <p className="label mb-2">Chamber</p>
+ <p className="font-mono text-[13px] text-paper">{vote.chamber}</p>
+ <p className="label mt-2 text-ash-2">{formatDate(vote.date)}</p>
+ </Card>
+
+ <Card className="p-5">
+ <p className="label mb-2">Official record</p>
+ <SourceLink url={vote.sourceUrl} label="Roll call" />
+ <p className="label mt-2 text-ash-2">
+ {vote.question ? "Question of the vote shown above." : "No question text on file."}
+ </p>
+ </Card>
+ </div>
+
+ {vote.recorded ? (
+ <>
+ {/* tally viz */}
+ <div className="mt-10 grid gap-8 lg:grid-cols-[1fr_1.4fr]">
+ <div>
+ <SectionHeader eyebrow="Tally" title="Vote total" />
+ <Card className="p-5">
+ <VoteTally
+ yea={vote.yea}
+ nay={vote.nay}
+ present={vote.present}
+ result={passed ? "passed" : "failed"}
+ />
+ {vote.notVoting ? (
+ <p className="label mt-4 text-ash-2">
+ {vote.notVoting} member(s) not voting.
+ </p>
+ ) : null}
+ <div className="mt-4">
+ <SourceLink url={vote.sourceUrl} />
+ </div>
+ </Card>
+ </div>
+
+ {/* position summary chips */}
+ <div>
+ <SectionHeader eyebrow="By position" title="How members voted" />
+ <Card className="p-5">
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
+ <SummaryChip label="Yea" value={counts.yea ?? vote.yea} color="var(--dk-yea)" />
+ <SummaryChip label="Nay" value={counts.nay ?? vote.nay} color="var(--dk-nay)" />
+ <SummaryChip
+ label="Present"
+ value={counts.present ?? vote.present}
+ color="var(--dk-ash)"
+ />
+ <SummaryChip
+ label="Not voting"
+ value={counts["not voting"] ?? 0}
+ color="var(--dk-ash-2)"
+ />
+ </div>
+ <p className="label mt-4 text-ash-2">
+ Default order: by position, then alphabetical - not grouped by party.
+ </p>
+ </Card>
+ </div>
+ </div>
+
+ {/* THE ROSTER */}
+ <div className="mt-12">
+ <SectionHeader
+ eyebrow={`Roster · ${roster.length} member${roster.length === 1 ? "" : "s"}`}
+ title="Every member's position"
+ description="Name, party, state, and district for each member, with how they voted. Sorted by vote then alphabetical."
+ />
+ {roster.length === 0 ? (
+ <Card className="p-5">
+ <p className="font-mono text-[13px] text-ash">
+ No per-member positions are linked to this vote in the record.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Member roster for this vote: name, party, state, district, position.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Member</Th>
+ <Th>Party</Th>
+ <Th>State</Th>
+ <Th>Position</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {roster.map((p, i) => (
+ <tr
+ key={`${p.memberHref || i}-${p.member}-${i}`}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="text-paper">
+ {p.memberHref ? (
+ <Link
+ href={p.memberHref}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {p.member}
+ </Link>
+ ) : (
+ p.member
+ )}
+ </Td>
+ <Td>
+ {p.party ? <PartyTag party={p.partyCode} /> : "-"}
+ </Td>
+ <Td className="text-ash">
+ {p.state}
+ {p.district != null ? ` ${p.district}`.padStart(3, " ") : ""}
+ </Td>
+ <Td>
+ <PositionTag position={p.position} />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ <p className="label mt-4 text-ash-2">
+ Party shown in desaturated, equal-weight tones. Source: {""}
+ <span style={{ color: positionColor("Yea") }}>Yea</span>,{" "}
+ <span style={{ color: positionColor("Nay") }}>Nay</span> - never vivid red or blue.
+ </p>
+ </div>
+ </>
+ ) : (
+ /* Voice / unanimous-consent vote: no individual record exists. */
+ <div className="mt-10">
+ <SectionHeader eyebrow="No individual record" title="Passed without a roll call" />
+ <Card className="p-6">
+ <p className="prose-lead text-paper">
+ No individual record exists - this action passed by voice vote or
+ unanimous consent. The chamber did not record how each member voted.
+ </p>
+ <p className="mt-4 font-mono text-[13px] text-ash">
+ Recorded result: {vote.result || "Agreed"}.
+ </p>
+ <div className="mt-4">
+ <SourceLink url={vote.sourceUrl} />
+ </div>
+ </Card>
+ </div>
+ )}
+ </div>
+ );
+}
+
+function SummaryChip({
+ label,
+ value,
+ color,
+}: {
+ label: string;
+ value: number;
+ color: string;
+}) {
+ return (
+ <div className="border border-line p-3">
+ <div className="label flex items-center gap-1.5" style={{ color }}>
+ <span
+ aria-hidden="true"
+ className="inline-block h-1.5 w-1.5 rounded-full"
+ style={{ backgroundColor: color }}
+ />
+ {label}
+ </div>
+ <div className="mt-1 font-mono text-xl tabular-nums text-paper">{value}</div>
+ </div>
+ );
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
app/votes/page.tsx +120 -0
@@ -0,0 +1,120 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Card, PageHeader, SectionHeader } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { StatusPill, classifyStatus } from "@/components/status-pill";
+import { getVotes } from "@/lib/feed";
+import { formatDate } from "@/lib/format";
+
+export const metadata: Metadata = {
+ title: "Votes",
+ description:
+ "Recorded roll-call votes of the U.S. House and Senate. Every vote links to its official record.",
+};
+
+export const dynamic = "force-dynamic";
+
+export default function VotesPage() {
+ const votes = getVotes();
+
+ return (
+ <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6">
+ <PageHeader
+ eyebrow="Roll-call register"
+ title="Votes, on the record"
+ lead="Each row is a recorded vote in the House or Senate - the question, the tally, and a link to the official roll call. Open any vote to see how every member voted."
+ />
+
+ <div className="mt-8">
+ <SectionHeader
+ eyebrow={`${votes.length} votes`}
+ title="Recorded votes"
+ description="Newest action first. A vote with no Yea/Nay tally passed by voice or unanimous consent - no individual record exists for those."
+ />
+ {votes.length === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No votes on record.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ Roll-call votes appear here as the House and Senate record them.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Recorded votes: date, chamber, question or result, tally, and source.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Date</Th>
+ <Th>Chamber</Th>
+ <Th>Result</Th>
+ <Th className="text-right">Tally</Th>
+ <Th className="text-right">Record</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {votes.map((v) => {
+ const variant = classifyStatus(v.result);
+ return (
+ <tr
+ key={v.id}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="tabular-nums text-ash">
+ {formatDate(v.date)}
+ </Td>
+ <Td className="uppercase text-ash-2">{v.chamber}</Td>
+ <Td className="text-paper">
+ <Link
+ href={v.href ?? "#"}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {v.result || "Roll-call vote"}
+ </Link>
+ <div className="mt-1.5">
+ <StatusPill variant={variant} dot>
+ {v.recorded ? "Recorded" : "Voice / UC"}
+ </StatusPill>
+ </div>
+ </Td>
+ <Td className="text-right tabular-nums text-paper">
+ {v.recorded ? `${v.yea}\u2013${v.nay}` : "-"}
+ </Td>
+ <Td className="text-right">
+ <SourceLink url={v.sourceUrl} />
+ </Td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ <p className="label mt-4 text-ash-2">
+ Source: clerk.house.gov and senate.gov roll-call XML. Figures only - no characterization.
+ </p>
+ </div>
+ </div>
+ );
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
components/changes-feed.tsx +74 -0
@@ -0,0 +1,74 @@
+import Link from "next/link";
+import { SourceLink } from "@/components/source-link";
+import type { ChangeEvent } from "@/lib/feed";
+
+/**
+ * Compact "Recently changed" surface - the watcher feed (change_events).
+ * Neutral, factual: added / updated / removed, a one-line summary, the
+ * source table, and a Source link on every entry (neutrality #1, #4).
+ * Presentational; the page passes it the events it wants shown.
+ */
+const CHANGE_COLOR: Record<string, string> = {
+ added: "var(--dk-yea)",
+ updated: "var(--dk-hold)",
+ removed: "var(--dk-nay)",
+};
+
+export function ChangesFeed({
+ items,
+ limit,
+}: {
+ items: ChangeEvent[];
+ limit?: number;
+}) {
+ const shown = limit ? items.slice(0, limit) : items;
+ if (shown.length === 0) {
+ return (
+ <p className="font-mono text-[13px] text-ash-2">
+ No recent changes on record.
+ </p>
+ );
+ }
+ return (
+ <ul className="divide-y divide-[color:var(--dk-line)]">
+ {shown.map((c) => {
+ const color = CHANGE_COLOR[c.change] ?? "var(--dk-ash)";
+ const text = c.summary || `${c.change} ${c.refType}`;
+ return (
+ <li key={c.id} className="py-2.5">
+ <div className="flex items-start gap-2.5">
+ <span
+ className="pill mt-0.5 shrink-0"
+ style={{
+ color,
+ borderColor: `color-mix(in srgb, ${color} 40%, transparent)`,
+ }}
+ >
+ {c.change}
+ </span>
+ <div className="min-w-0 flex-1">
+ <p className="font-mono text-[12px] leading-relaxed text-paper">
+ {c.href ? (
+ <Link
+ href={c.href}
+ className="focus-ring rounded-sm underline underline-offset-4 hover:text-paper"
+ >
+ {text}
+ </Link>
+ ) : (
+ text
+ )}
+ </p>
+ <p className="label mt-0.5 text-ash-2">
+ {c.source}
+ {c.refId ? ` · ${c.refId}` : ""}
+ </p>
+ </div>
+ <SourceLink url={c.sourceUrl} className="shrink-0" />
+ </div>
+ </li>
+ );
+ })}
+ </ul>
+ );
+}
components/display-toggle.tsx +78 -0
@@ -0,0 +1,78 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { clsx } from "@/lib/cx";
+
+/**
+ * 3-mode display toggle (docs/ACCESSIBILITY.md §"Three display modes").
+ * Dark (default forensic brand) / Readable Light / Plain (high-contrast,
+ * system sans, >=20px). Persisted in localStorage("docket-display"); the
+ * matching class on <html> is set pre-paint by the inline script in
+ * layout.tsx so there is no flash and no hydration mismatch.
+ *
+ * Always-visible, labelled, >=44x44px hit targets (a11y rules #3 & #8).
+ * Fully keyboard-operable (native radio group).
+ */
+export type DisplayMode = "dark" | "light" | "plain";
+
+const MODES: Array<{ value: DisplayMode; label: string; hint: string }> = [
+ { value: "dark", label: "Dark", hint: "Default register look" },
+ { value: "light", label: "Readable", hint: "Light, for reading" },
+ { value: "plain", label: "Plain", hint: "High contrast, large text" },
+];
+
+export function DisplayToggle() {
+ const [mode, setMode] = useState<DisplayMode>("dark");
+
+ useEffect(() => {
+ const current = (localStorage.getItem("docket-display") as DisplayMode) || "dark";
+ if (current !== mode) setMode(current);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ function apply(next: DisplayMode) {
+ setMode(next);
+ localStorage.setItem("docket-display", next);
+ const el = document.documentElement;
+ el.classList.remove("dark", "light", "plain");
+ el.classList.add(next);
+ // keep the brand's font CSS variables in sync for plain mode
+ el.dataset.display = next;
+ }
+
+ return (
+ <div
+ role="group"
+ aria-label="Display mode"
+ className="flex items-center gap-1 rounded-[3px] border border-line p-1 bg-surface"
+ >
+ <span className="sr-only">Choose how Docket looks</span>
+ {MODES.map((m) => {
+ const active = m.value === mode;
+ return (
+ <button
+ key={m.value}
+ type="button"
+ onClick={() => apply(m.value)}
+ aria-pressed={active}
+ aria-label={`${m.label} mode - ${m.hint}`}
+ title={`${m.label} - ${m.hint}`}
+ className={clsx(
+ "focus-ring label inline-flex min-h-[44px] min-w-[44px] items-center justify-center rounded-[2px] px-2.5 transition-colors",
+ active
+ ? "bg-graphite text-paper"
+ : "text-ash hover:text-paper"
+ )}
+ style={
+ active
+ ? { boxShadow: "inset 0 -2px 0 var(--dk-signal)" }
+ : undefined
+ }
+ >
+ {m.label}
+ </button>
+ );
+ })}
+ </div>
+ );
+}
components/executive-browser.tsx +173 -0
@@ -0,0 +1,173 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { Card } from "@/components/ui";
+import { SourceLink } from "@/components/source-link";
+import { clsx } from "@/lib/cx";
+import { formatDate } from "@/lib/format";
+import type { ExecutiveAction } from "@/lib/feed";
+
+/**
+ * Federal Register browser with type filter + text search (a11y §Search:
+ * labeled controls, big targets). Local filtering over the server-seeded list.
+ * Newest action first.
+ */
+export function ExecutiveBrowser({
+ seed,
+ typeLabels,
+}: {
+ seed: ExecutiveAction[];
+ typeLabels: Record<string, string>;
+}) {
+ const types = useMemo(
+ () =>
+ Array.from(new Set(seed.map((e) => (e.type || "").toLowerCase()))).sort(),
+ [seed]
+ );
+
+ const [type, setType] = useState("");
+ const [q, setQ] = useState("");
+
+ const filtered = useMemo(() => {
+ const query = q.trim().toLowerCase();
+ return seed.filter((e) => {
+ if (type && (e.type || "").toLowerCase() !== type) return false;
+ if (
+ query &&
+ !e.title.toLowerCase().includes(query) &&
+ !e.agency.toLowerCase().includes(query)
+ ) {
+ return false;
+ }
+ return true;
+ });
+ }, [seed, type, q]);
+
+ return (
+ <>
+ <Card className="p-4 sm:p-5">
+ <div className="grid gap-3 sm:grid-cols-[1fr_auto] sm:items-end">
+ <label className="block">
+ <span className="label mb-1.5 block">Search title or agency</span>
+ <input
+ type="search"
+ value={q}
+ onChange={(e) => setQ(e.target.value)}
+ placeholder="e.g. emissions, FAA, Treasury"
+ aria-label="Search Federal Register actions"
+ className="focus-ring w-full rounded-[2px] border border-line bg-ink px-3 py-2 font-mono text-[13px] text-paper placeholder:text-ash-2"
+ />
+ </label>
+ <label className="block">
+ <span className="label mb-1.5 block">Type</span>
+ <select
+ value={type}
+ onChange={(e) => setType(e.target.value)}
+ aria-label="Filter by action type"
+ className={clsx(
+ "focus-ring rounded-[2px] border border-line bg-ink px-3 py-2",
+ "font-mono text-[13px] text-paper"
+ )}
+ >
+ <option value="" className="bg-surface text-paper">
+ All types
+ </option>
+ {types.map((t) => (
+ <option key={t} value={t} className="bg-surface text-paper">
+ {typeLabels[t] || t}
+ </option>
+ ))}
+ </select>
+ </label>
+ </div>
+ <div className="mt-3 flex items-center justify-between">
+ <p className="label text-ash-2">
+ {filtered.length} of {seed.length} actions
+ </p>
+ {(q || type) && (
+ <button
+ type="button"
+ onClick={() => {
+ setQ("");
+ setType("");
+ }}
+ className="focus-ring label rounded-sm px-2 py-1 text-ash underline underline-offset-4 hover:text-paper"
+ >
+ Clear
+ </button>
+ )}
+ </div>
+ </Card>
+
+ <div className="mt-6">
+ {filtered.length === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No actions match.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ Adjust the filter or search. The Federal Register feed updates daily.
+ </p>
+ </Card>
+ ) : (
+ <Card className="overflow-hidden">
+ <table className="w-full border-collapse text-left">
+ <caption className="sr-only">
+ Federal Register actions: date, type, title, agency, document number, source.
+ </caption>
+ <thead>
+ <tr className="border-b border-line">
+ <Th>Date</Th>
+ <Th>Type</Th>
+ <Th>Title</Th>
+ <Th className="text-right">Source</Th>
+ </tr>
+ </thead>
+ <tbody>
+ {filtered.map((e) => (
+ <tr
+ key={e.id}
+ className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60"
+ >
+ <Td className="tabular-nums text-ash">{formatDate(e.date)}</Td>
+ <Td className="uppercase text-ash-2">
+ {typeLabels[(e.type || "").toLowerCase()] || e.type || "-"}
+ </Td>
+ <Td className="text-paper">
+ <span className="block">{e.title}</span>
+ <span className="block text-ash-2">
+ {e.agency}
+ {e.frDocNumber ? ` · Doc ${e.frDocNumber}` : ""}
+ </span>
+ </Td>
+ <Td className="text-right">
+ <SourceLink url={e.sourceUrl} label="FR" />
+ </Td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </Card>
+ )}
+ </div>
+ </>
+ );
+}
+
+function Th({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <th className={`label px-4 py-2.5 font-normal ${className}`}>{children}</th>;
+}
+
+function Td({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return <td className={`px-4 py-3 align-middle ${className}`}>{children}</td>;
+}
components/members-browser.tsx +204 -0
@@ -0,0 +1,204 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import Link from "next/link";
+import { Card } from "@/components/ui";
+import { PartyTag } from "@/components/party-tag";
+import { clsx } from "@/lib/cx";
+import type { MemberSummary } from "@/lib/feed";
+
+/**
+ * Client-side browse/filter for the members roster (docs/ACCESSIBILITY.md
+ * §Search: labeled drop-downs, big targets, not a bare box). All filtering
+ * is local over the server-seeded list. Party chips render in desaturated,
+ * equal-weight tones; the list stays alphabetical-by-state-then-name (never
+ * grouped by party - neutrality §Viz).
+ */
+export function MembersBrowser({
+ seed,
+ states,
+}: {
+ seed: MemberSummary[];
+ states: string[];
+}) {
+ const [q, setQ] = useState("");
+ const [state, setState] = useState("");
+ const [chamber, setChamber] = useState("");
+ const [party, setParty] = useState("");
+
+ const filtered = useMemo(() => {
+ const query = q.trim().toLowerCase();
+ return seed.filter((m) => {
+ if (
+ query &&
+ !m.name.toLowerCase().includes(query) &&
+ !m.bioguide.toLowerCase().includes(query)
+ ) {
+ return false;
+ }
+ if (state && m.state !== state) return false;
+ if (chamber && m.chamber !== chamber) return false;
+ if (party && m.partyCode !== party) return false;
+ return true;
+ });
+ }, [seed, q, state, chamber, party]);
+
+ return (
+ <>
+ {/* Filter bar - labeled selects + labeled search (a11y §Search). */}
+ <Card className="p-4 sm:p-5">
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
+ <Field label="Search by name">
+ <input
+ type="search"
+ value={q}
+ onChange={(e) => setQ(e.target.value)}
+ placeholder="Name or ID"
+ aria-label="Search members by name"
+ className="focus-ring w-full rounded-[2px] border border-line bg-ink px-3 py-2 font-mono text-[13px] text-paper placeholder:text-ash-2"
+ />
+ </Field>
+ <Field label="State">
+ <Select
+ value={state}
+ onChange={setState}
+ ariaLabel="Filter by state"
+ options={[
+ { value: "", label: "All states" },
+ ...states.map((s) => ({ value: s, label: s })),
+ ]}
+ />
+ </Field>
+ <Field label="Chamber">
+ <Select
+ value={chamber}
+ onChange={setChamber}
+ ariaLabel="Filter by chamber"
+ options={[
+ { value: "", label: "Both chambers" },
+ { value: "senate", label: "Senate" },
+ { value: "house", label: "House" },
+ ]}
+ />
+ </Field>
+ <Field label="Party">
+ <Select
+ value={party}
+ onChange={setParty}
+ ariaLabel="Filter by party"
+ options={[
+ { value: "", label: "All parties" },
+ { value: "D", label: "Democrat" },
+ { value: "R", label: "Republican" },
+ { value: "I", label: "Independent" },
+ ]}
+ />
+ </Field>
+ </div>
+ <div className="mt-3 flex items-center justify-between">
+ <p className="label text-ash-2">
+ {filtered.length} of {seed.length} members
+ </p>
+ {(q || state || chamber || party) && (
+ <button
+ type="button"
+ onClick={() => {
+ setQ("");
+ setState("");
+ setChamber("");
+ setParty("");
+ }}
+ className="focus-ring label rounded-sm px-2 py-1 text-ash underline underline-offset-4 hover:text-paper"
+ >
+ Clear filters
+ </button>
+ )}
+ </div>
+ </Card>
+
+ {/* Roster grid. */}
+ <div className="mt-6">
+ {filtered.length === 0 ? (
+ <Card className="p-8 text-center">
+ <p className="display text-2xl text-paper">No members match.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ Adjust the filters above. The roster covers the sitting House and Senate.
+ </p>
+ </Card>
+ ) : (
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
+ {filtered.map((m) => (
+ <Link
+ key={m.id}
+ href={m.href}
+ className="focus-ring group rounded-[3px] border border-line bg-surface p-4 transition-colors hover:bg-graphite"
+ >
+ <div className="flex items-start justify-between gap-2">
+ <div className="min-w-0">
+ <p className="font-mono text-[13px] text-paper group-hover:underline">
+ {m.name}
+ </p>
+ <p className="label mt-1 text-ash-2">
+ {m.chamber === "senate" ? "Senate" : "House"} · {m.state}
+ {m.district != null ? ` ${m.district}` : ""}
+ </p>
+ </div>
+ <PartyTag party={m.partyCode} />
+ </div>
+ <div className="mt-3 flex items-center justify-between">
+ <span className="label text-ash-2">{m.bioguide}</span>
+ <span className="label text-ash">Record →</span>
+ </div>
+ </Link>
+ ))}
+ </div>
+ )}
+ </div>
+ </>
+ );
+}
+
+function Field({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) {
+ return (
+ <label className="block">
+ <span className="label mb-1.5 block">{label}</span>
+ {children}
+ </label>
+ );
+}
+
+function Select({
+ value,
+ onChange,
+ ariaLabel,
+ options,
+}: {
+ value: string;
+ onChange: (v: string) => void;
+ ariaLabel: string;
+ options: Array<{ value: string; label: string }>;
+}) {
+ return (
+ <select
+ value={value}
+ onChange={(e) => onChange(e.target.value)}
+ aria-label={ariaLabel}
+ className={clsx(
+ "focus-ring w-full rounded-[2px] border border-line bg-ink px-3 py-2",
+ "font-mono text-[13px] text-paper"
+ )}
+ >
+ {options.map((o) => (
+ <option key={o.value} value={o.value} className="bg-surface text-paper">
+ {o.label}
+ </option>
+ ))}
+ </select>
+ );
+}
components/position-tag.tsx +39 -0
@@ -0,0 +1,39 @@
+import { clsx } from "@/lib/cx";
+
+export type Position = "Yea" | "Nay" | "Present" | "Not Voting" | string;
+
+/**
+ * How a member voted - muted yea / nay / present tones (neutrality §Viz).
+ * Never vivid red/green; equal visual weight so the eye does not read "teams."
+ */
+const POSITION_COLOR: Record<string, string> = {
+ yea: "var(--dk-yea)",
+ nay: "var(--dk-nay)",
+ present: "var(--dk-ash)",
+ "not voting": "var(--dk-ash-2)",
+};
+
+export function positionColor(position: Position): string {
+ return POSITION_COLOR[(position || "").toLowerCase()] ?? "var(--dk-ash)";
+}
+
+export function PositionTag({
+ position,
+ className,
+}: {
+ position: Position;
+ className?: string;
+}) {
+ const color = positionColor(position);
+ return (
+ <span
+ className={clsx("pill", className)}
+ style={{
+ color,
+ borderColor: `color-mix(in srgb, ${color} 40%, transparent)`,
+ }}
+ >
+ {position || "-"}
+ </span>
+ );
+}
components/register-hero.tsx +94 -80
@@ -1,48 +1,50 @@
"use client";
-import { useEffect, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
-import {
- REGISTER_ACTIONS,
- type RegisterAction,
-} from "@/lib/mock-data";
import { SourceLink } from "@/components/source-link";
import { clsx } from "@/lib/cx";
+import { formatEt } from "@/lib/format";
+import type { FeedItem } from "@/lib/feed";
/**
* THE REGISTER - Docket's signature hero (docs/BRAND.md §3).
- * A real-time stamped ledger of official actions materializing line by line:
- * mono rows, hairline separators, ONE pulsing brass live-dot at the top.
+ * A stamped ledger of official actions. Seeded server-side with REAL feed
+ * items from /api/feed, then re-fetched client-side on an interval so newly
+ * ingested actions appear live. Falls back to a calm empty state when a
+ * category is empty - never to mock data, never a blank.
*
- * The live-dot is the single rationed brass signal on /register.
- *
- * This is a static shell: rows are mocked. A client-side timer prepends a
- * synthetic "just-sealed" row every several seconds so the ledger reads as
- * live - no real data is fetched in Phase 0.
+ * The pulsing brass live-dot is the single rationed brass signal on /register.
*/
-export function RegisterHero() {
- const [rows, setRows] = useState<RegisterAction[]>(REGISTER_ACTIONS);
+export function RegisterHero({ seed }: { seed: FeedItem[] }) {
+ const [rows, setRows] = useState<FeedItem[]>(seed);
const [flashId, setFlashId] = useState<string | null>(
- REGISTER_ACTIONS[0]?.id ?? null
+ seed[0]?.id ?? null
);
- const tick = useRef(0);
+ const prevIds = useRef<Set<string>>(new Set(seed.map((r) => r.id)));
+
+ const refresh = useCallback(async () => {
+ try {
+ const res = await fetch("/api/feed?limit=12", { cache: "no-store" });
+ if (!res.ok) return;
+ const data = (await res.json()) as { items: FeedItem[] };
+ if (!data.items?.length) return;
+ setRows((prev) => {
+ const next = data.items.slice(0, 12);
+ const newTop = next.find((it) => !prevIds.current.has(it.id));
+ next.forEach((it) => prevIds.current.add(it.id));
+ if (newTop) setFlashId(newTop.id);
+ return next;
+ });
+ } catch {
+ /* offline / transient - keep the seeded rows */
+ }
+ }, []);
useEffect(() => {
- // Simulate the next action being sealed to the record.
- const iv = setInterval(() => {
- // Pull a rotating mock "next" action to avoid duplicating ids.
- tick.current += 1;
- const base =
- REGISTER_ACTIONS[tick.current % REGISTER_ACTIONS.length];
- const next: RegisterAction = {
- ...base,
- id: `${base.id}-t${tick.current}`,
- };
- setRows((prev) => [next, ...prev].slice(0, 9));
- setFlashId(next.id);
- }, 6000);
+ const iv = setInterval(refresh, 15000);
return () => clearInterval(iv);
- }, []);
+ }, [refresh]);
return (
<section
@@ -75,46 +77,47 @@ export function RegisterHero() {
</p>
</div>
- {/* the ledger */}
- <div
- className="mt-8 overflow-hidden rounded-[3px] border border-line bg-surface/60 backdrop-blur-sm"
- role="table"
- aria-label="Stamped ledger of official actions"
- >
+ {rows.length === 0 ? (
+ <EmptyRegister />
+ ) : (
<div
- className="reg-row grid-cols-[5.5rem_6rem_minmax(0,1fr)_7.5rem_minmax(0,1fr)] px-3 py-2 sm:grid sm:px-4"
- role="row"
+ className="mt-8 overflow-hidden rounded-[3px] border border-line bg-surface/60 backdrop-blur-sm"
+ role="table"
+ aria-label="Stamped ledger of official actions"
>
- <span className="label">Time</span>
- <span className="label">Body</span>
- <span className="label">Item</span>
- <span className="label">Action</span>
- <span className="label text-right">Result</span>
- </div>
+ <div
+ className="reg-row grid-cols-[6rem_8rem_minmax(0,1fr)_8rem_minmax(0,1fr)] px-3 py-2 sm:grid sm:px-4"
+ role="row"
+ >
+ <span className="label">Time</span>
+ <span className="label">Body</span>
+ <span className="label">Item</span>
+ <span className="label">Action</span>
+ <span className="label text-right">Result</span>
+ </div>
- {rows.map((r) => {
- const isNew = r.id === flashId;
- const content = (
- <RegisterRowInner row={r} flash={isNew} />
- );
- return (
- <div key={r.id} role="row">
- {r.billHref ? (
- <Link
- href={r.billHref}
- className="focus-ring reg-row group grid-cols-[5.5rem_6rem_minmax(0,1fr)_7.5rem_minmax(0,1fr)] grid px-3 py-2.5 sm:px-4"
- >
- {content}
- </Link>
- ) : (
- <div className="reg-row grid-cols-[5.5rem_6rem_minmax(0,1fr)_7.5rem_minmax(0,1fr)] grid px-3 py-2.5 sm:px-4">
- {content}
- </div>
- )}
- </div>
- );
- })}
- </div>
+ {rows.map((r) => {
+ const isNew = r.id === flashId;
+ const content = <RegisterRowInner row={r} flash={isNew} />;
+ return (
+ <div key={r.id} role="row">
+ {r.href ? (
+ <Link
+ href={r.href}
+ className="focus-ring reg-row group grid-cols-[6rem_8rem_minmax(0,1fr)_8rem_minmax(0,1fr)] grid px-3 py-2.5 sm:px-4"
+ >
+ {content}
+ </Link>
+ ) : (
+ <div className="reg-row grid-cols-[6rem_8rem_minmax(0,1fr)_8rem_minmax(0,1fr)] grid px-3 py-2.5 sm:px-4">
+ {content}
+ </div>
+ )}
+ </div>
+ );
+ })}
+ </div>
+ )}
<div className="mt-3 flex items-center justify-between">
<p className="label text-ash-2">
@@ -122,7 +125,7 @@ export function RegisterHero() {
</p>
<Link
href="/how-we-pick"
- className="focus-ring label text-ash underline-offset-4 hover:text-paper hover:underline"
+ className="focus-ring label text-ash underline underline-offset-4 hover:text-paper"
>
How we pick →
</Link>
@@ -132,19 +135,29 @@ export function RegisterHero() {
);
}
+function EmptyRegister() {
+ return (
+ <div className="mt-8 rounded-[3px] border border-line bg-surface/60 p-8 text-center">
+ <p className="display text-2xl text-paper">The register is quiet.</p>
+ <p className="prose-lead mx-auto mt-3 max-w-md text-ash">
+ No official actions are on record yet. The ledger fills as bills are
+ filed, votes are taken, and awards are made - each line sealed to its
+ government source.
+ </p>
+ </div>
+ );
+}
+
function RegisterRowInner({
row,
flash,
}: {
- row: RegisterAction;
+ row: FeedItem;
flash?: boolean;
}) {
- const isFail = row.actionType === "FAILED" || row.actionType === "CLOTURE FAIL";
- const isPass =
- row.actionType === "PASSED" ||
- row.actionType === "CLOTURE OK" ||
- row.actionType === "SIGNED" ||
- row.actionType === "REPORTED";
+ const isPass = row.actionType === "PASSED";
+ const isFail = row.actionType === "FAILED";
+ const result = row.amountText || "\u2014";
return (
<>
<span
@@ -153,17 +166,18 @@ function RegisterRowInner({
flash && "animate-fadeUp"
)}
>
- {row.time}
+ {formatEt(row.actionDate) || "-"}
</span>
<span className="font-mono text-[12px] uppercase tracking-wide text-ash-2">
{row.chamber}
</span>
<span className="font-mono text-[13px] text-paper">
- {row.label}
+ <span className="block truncate">{row.title}</span>
+ {/* Always-visible source (a11y #5: no hover-only affordance). */}
<SourceLink
url={row.sourceUrl}
- label=""
- className="ml-2 !inline-flex !py-0 !px-1 opacity-60 group-hover:opacity-100"
+ label="Source"
+ className="mt-0.5 !inline-flex !opacity-100"
/>
</span>
<span
@@ -179,7 +193,7 @@ function RegisterRowInner({
{row.actionType}
</span>
<span className="mt-1 font-mono text-[12px] tabular-nums text-paper sm:mt-0 sm:text-right">
- {row.result}
+ {result}
</span>
</>
);
components/rider-flag.tsx +51 -0
@@ -0,0 +1,51 @@
+import { clsx } from "@/lib/cx";
+
+/**
+ * Rider flag - marks a section that entered a bill outside its named subject
+ * (Docket's signature "what got slipped in" signal). Brass is rationed; this
+ * is one of the few places the brand accent is used as an explicit flag.
+ * Plain mode collapses to ink (no accent) per globals.css.
+ */
+export function RiderFlag({ className }: { className?: string }) {
+ return (
+ <span
+ className={clsx("pill", className)}
+ style={{
+ color: "var(--dk-signal)",
+ borderColor: "color-mix(in srgb, var(--dk-signal) 45%, transparent)",
+ backgroundColor: "var(--dk-signal-tint)",
+ }}
+ title="Flagged as a rider - added outside the bill's named subject"
+ >
+ <span aria-hidden="true">◆</span> Rider flag
+ </span>
+ );
+}
+
+/** Change-type label (added / removed / amended) in a muted mono tone. */
+export function ChangeTag({
+ change,
+ className,
+}: {
+ change: string;
+ className?: string;
+}) {
+ const c = (change || "").toLowerCase();
+ const color =
+ c === "added"
+ ? "var(--dk-yea)"
+ : c === "removed"
+ ? "var(--dk-nay)"
+ : "var(--dk-hold)";
+ return (
+ <span
+ className={clsx("pill", className)}
+ style={{
+ color,
+ borderColor: `color-mix(in srgb, ${color} 40%, transparent)`,
+ }}
+ >
+ {change || "changed"}
+ </span>
+ );
+}
components/search-box.tsx +61 -0
@@ -0,0 +1,61 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { clsx } from "@/lib/cx";
+
+/**
+ * Accessible global search entry point (docs/ACCESSIBILITY.md §Search).
+ * - Labeled field (not a bare box), >=50px tall, >=18px font.
+ * - Native GET form so it deep-links to /search?q= and works without JS.
+ * - Visible Back/breadcrumbs handled by the page route.
+ */
+export function SearchBox({ initialValue }: { initialValue: string }) {
+ const router = useRouter();
+ const [value, setValue] = useState(initialValue);
+
+ return (
+ <form
+ role="search"
+ action="/search"
+ method="get"
+ onSubmit={(e) => {
+ // Progressive enhancement: intercept on JS and navigate smoothly,
+ // but the native GET action still works if JS is off.
+ e.preventDefault();
+ const q = value.trim();
+ router.push(q ? `/search?q=${encodeURIComponent(q)}` : "/search");
+ }}
+ >
+ <label htmlFor="docket-search" className="label mb-2 block">
+ Look up my state, my representative, or a topic
+ </label>
+ <div className="flex flex-col gap-2 sm:flex-row">
+ <input
+ id="docket-search"
+ type="search"
+ name="q"
+ value={value}
+ onChange={(e) => setValue(e.target.value)}
+ placeholder="e.g. CA, H.R. 25, Cantwell, tax, appropriations"
+ autoComplete="off"
+ aria-label="Search the record"
+ className={clsx(
+ "focus-ring min-h-[52px] w-full rounded-[2px] border border-line bg-ink px-4",
+ "font-mono text-[18px] text-paper placeholder:text-ash-2"
+ )}
+ />
+ <button
+ type="submit"
+ className={clsx(
+ "focus-ring label inline-flex min-h-[52px] min-w-[120px] items-center justify-center rounded-[2px]",
+ "border border-line bg-surface px-6 text-paper underline-offset-4 transition-colors",
+ "hover:bg-graphite"
+ )}
+ >
+ Search
+ </button>
+ </div>
+ </form>
+ );
+}
components/site-footer.tsx +9 -2
@@ -19,10 +19,17 @@ export function SiteFooter() {
</p>
</div>
- <nav aria-label="Footer" className="grid grid-cols-2 gap-x-10 gap-y-2">
+ <nav aria-label="Footer" className="grid grid-cols-2 gap-x-10 gap-y-2 sm:grid-cols-3">
<FooterLink href="/register">Register</FooterLink>
+ <FooterLink href="/calendar">Calendar</FooterLink>
+ <FooterLink href="/votes">Votes</FooterLink>
+ <FooterLink href="/members">Members</FooterLink>
+ <FooterLink href="/bills">Bills</FooterLink>
+ <FooterLink href="/categories">Topics</FooterLink>
+ <FooterLink href="/executive">Executive</FooterLink>
<FooterLink href="/spending">Spending</FooterLink>
<FooterLink href="/states">States</FooterLink>
+ <FooterLink href="/search">Search</FooterLink>
<FooterLink href="/how-we-pick">How we pick</FooterLink>
<FooterLink href="/who-pays">Who pays</FooterLink>
</nav>
@@ -30,7 +37,7 @@ export function SiteFooter() {
<div className="mt-10 flex flex-col gap-2 border-t border-line pt-6 sm:flex-row sm:items-center sm:justify-between">
<p className="label">
- Phase 0 · static shell · mock data · {year}
+ Read API live · sourced from official records · {year}
</p>
<p className="label text-ash-2">
Every fact below links to an official government record.
components/site-header.tsx +36 -32
@@ -3,14 +3,20 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { CustodyMark, Wordmark } from "@/components/custody-mark";
+import { DisplayToggle } from "@/components/display-toggle";
import { clsx } from "@/lib/cx";
const NAV: Array<{ href: string; label: string }> = [
{ href: "/register", label: "Register" },
+ { href: "/calendar", label: "Calendar" },
+ { href: "/votes", label: "Votes" },
+ { href: "/members", label: "Members" },
+ { href: "/bills", label: "Bills" },
+ { href: "/categories", label: "Topics" },
{ href: "/spending", label: "Spending" },
{ href: "/states", label: "States" },
- { href: "/how-we-pick", label: "How we pick" },
- { href: "/who-pays", label: "Who pays" },
+ { href: "/executive", label: "Executive" },
+ { href: "/search", label: "Search" },
];
export function SiteHeader() {
@@ -18,7 +24,7 @@ export function SiteHeader() {
return (
<header className="sticky top-0 z-40 border-b border-line bg-ink/85 backdrop-blur-md">
- <div className="mx-auto flex h-14 w-full max-w-register items-center justify-between gap-4 px-4 sm:px-6">
+ <div className="mx-auto flex h-auto w-full max-w-register flex-wrap items-center justify-between gap-3 px-4 py-2.5 sm:px-6">
<Link
href="/register"
className="focus-ring flex items-center gap-2.5 rounded-sm"
@@ -38,16 +44,11 @@ export function SiteHeader() {
<Link
href={item.href}
className={clsx(
- "focus-ring label rounded-sm px-2.5 py-1.5 transition-colors",
+ "focus-ring label inline-flex min-h-[44px] items-center rounded-sm px-2.5 transition-colors underline-offset-4",
active
- ? "text-paper"
- : "text-ash hover:text-paper"
+ ? "text-paper underline"
+ : "text-ash hover:text-paper hover:underline"
)}
- style={
- active
- ? { boxShadow: "inset 0 -1px 0 var(--dk-paper)" }
- : undefined
- }
aria-current={active ? "page" : undefined}
>
{item.label}
@@ -58,27 +59,30 @@ export function SiteHeader() {
</ul>
</nav>
- {/* Mobile nav: condensed to a select-like row of mono labels */}
- <nav aria-label="Primary mobile" className="md:hidden">
- <ul className="flex items-center gap-x-2 gap-y-1 overflow-x-auto">
- {NAV.slice(0, 3).map((item) => {
- const active = pathname === item.href;
- return (
- <li key={item.href}>
- <Link
- href={item.href}
- className={clsx(
- "label whitespace-nowrap py-1",
- active ? "text-paper" : "text-ash"
- )}
- >
- {item.label}
- </Link>
- </li>
- );
- })}
- </ul>
- </nav>
+ <div className="flex items-center gap-2">
+ <DisplayToggle />
+ {/* Mobile nav: condensed row of mono labels */}
+ <nav aria-label="Primary mobile" className="md:hidden">
+ <ul className="flex items-center gap-x-2 gap-y-1 overflow-x-auto">
+ {NAV.slice(0, 4).map((item) => {
+ const active = pathname === item.href;
+ return (
+ <li key={item.href}>
+ <Link
+ href={item.href}
+ className={clsx(
+ "label inline-flex min-h-[44px] items-center whitespace-nowrap py-1 underline-offset-4",
+ active ? "text-paper underline" : "text-ash"
+ )}
+ >
+ {item.label}
+ </Link>
+ </li>
+ );
+ })}
+ </ul>
+ </nav>
+ </div>
</div>
</header>
);
lib/db.ts +111 -0
@@ -0,0 +1,111 @@
+/**
+ * Docket read DB - defensive SQLite access (docs/API.md).
+ *
+ * The DB at process.env.DOCKET_DB_PATH (default /opt/docket/data/docket.db) is
+ * written by the Python ingest. Some tables do not exist yet while other agents
+ * finish ingest, so EVERY query is wrapped so the API never crashes: a missing
+ * table or bad column resolves to an empty result, and the route returns
+ * { items: [], total: 0 } (docs/API.md).
+ *
+ * Server-only. Never import from a client component.
+ */
+import Database from "better-sqlite3";
+
+export const DB_PATH =
+ process.env.DOCKET_DB_PATH || "/opt/docket/data/docket.db";
+
+let _db: Database.Database | null = null;
+
+/** Single shared read-only connection. */
+function db(): Database.Database {
+ if (!_db) {
+ _db = new Database(DB_PATH, {
+ readonly: true,
+ fileMustExist: false,
+ });
+ // Small perf wins; we only read.
+ try {
+ _db.pragma("journal_mode = WAL");
+ } catch {
+ /* ignore */
+ }
+ }
+ return _db;
+}
+
+/**
+ * Run a SELECT and map rows. Any error (missing table/column, corrupt read)
+ * is swallowed and [] is returned - the API contract is "always respond".
+ */
+export function safeAll<T>(
+ sql: string,
+ params: Record<string, unknown> = {},
+ map?: (row: Record<string, unknown>) => T
+): T[] {
+ try {
+ const stmt = db().prepare(sql);
+ const rows = stmt.all(params) as Record<string, unknown>[];
+ return map ? rows.map(map) : (rows as T[]);
+ } catch {
+ return [];
+ }
+}
+
+/** Run a SELECT that returns a single row, or null on any error. */
+export function safeGet<T>(
+ sql: string,
+ params: Record<string, unknown> = {}
+): T | null {
+ try {
+ const stmt = db().prepare(sql);
+ return (stmt.get(params) as T) ?? null;
+ } catch {
+ return null;
+ }
+}
+
+/** Count rows in a table; 0 if the table is missing. */
+export function safeCount(table: string): number {
+ try {
+ const row = db()
+ .prepare(`SELECT COUNT(*) AS c FROM "${table}"`)
+ .get() as { c: number } | undefined;
+ return row?.c ?? 0;
+ } catch {
+ return 0;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Pagination */
+/* ------------------------------------------------------------------ */
+
+export const MAX_LIMIT = 200;
+export const DEFAULT_LIMIT = 50;
+
+export type Paged = { limit: number; offset: number };
+
+export function parsePaging(sp: URLSearchParams): Paged {
+ const clamp = (n: number, hi: number) =>
+ Number.isFinite(n) && n > 0 ? Math.min(Math.floor(n), hi) : DEFAULT_LIMIT;
+ const limit = clamp(parseInt(sp.get("limit") ?? "", 10), MAX_LIMIT);
+ const offsetRaw = parseInt(sp.get("offset") ?? "", 10);
+ const offset =
+ Number.isFinite(offsetRaw) && offsetRaw > 0 ? Math.floor(offsetRaw) : 0;
+ return { limit, offset };
+}
+
+/** Repeated query param (e.g. ?type=vote&type=award) as a lowercased array. */
+export function paramArray(sp: URLSearchParams, key: string): string[] {
+ return sp.getAll(key).map((v) => v.toLowerCase());
+}
+
+/* ------------------------------------------------------------------ */
+/* Response helpers */
+/* ------------------------------------------------------------------ */
+
+export type ItemList<T> = { items: T[]; total: number; limit: number; offset: number };
+
+export function emptyList(p: Paged): ItemList<never> {
+ return { items: [], total: 0, limit: p.limit, offset: p.offset };
+}
lib/feed.ts +1599 -0
@@ -0,0 +1,1599 @@
+/**
+ * Docket read model - maps SQLite ingest rows to the JSON shapes in
+ * docs/API.md. Shared by the /api route handlers AND the server-rendered
+ * pages (pages call these directly instead of fetch-to-self).
+ *
+ * Ranking (neutrality rule #3) uses ONLY { actionDate, dollarAmount,
+ * affectedEstimate, actionType } - no engagement/news/social fields.
+ *
+ * Server-only. Imports better-sqlite3 (kept external via next.config.js).
+ */
+import { safeAll, safeGet } from "@/lib/db";
+import { formatUsd } from "@/lib/format";
+import type { PartyCode } from "@/lib/neutrality";
+
+/* ------------------------------------------------------------------ */
+/* Types (docs/API.md) */
+/* ------------------------------------------------------------------ */
+
+export type FeedItem = {
+ id: string;
+ kind:
+ | "bill"
+ | "vote"
+ | "award"
+ | "amendment"
+ | "rider"
+ | "procedural"
+ | "executive"
+ | "apportionment";
+ actionDate: string;
+ chamber: string;
+ title: string;
+ summary?: string;
+ actionType: string;
+ amount?: number | null;
+ amountText?: string;
+ jurisdiction: string;
+ sourceUrl: string;
+ href?: string;
+};
+
+export type Award = {
+ id: string;
+ agency: string;
+ recipient: string;
+ amount: number;
+ amountText: string;
+ actionDate: string;
+ program: string;
+ sourceUrl: string;
+ href: string;
+};
+
+export type FiscalMetric = {
+ id: string;
+ metric: string;
+ value: number;
+ valueText: string;
+ date: string;
+ sourceUrl: string;
+ href: string;
+};
+
+export type Apportionment = {
+ id: string;
+ agency: string;
+ account: string;
+ appropriated: number | null;
+ apportioned: number | null;
+ status: string;
+ date: string;
+ sourceUrl: string;
+ href: string;
+};
+
+export type Vote = {
+ id: string;
+ billId?: number | null;
+ chamber: string;
+ date: string;
+ result: string;
+ yea: number;
+ nay: number;
+ present: number;
+ recorded: boolean;
+ sourceUrl: string;
+ href?: string;
+};
+
+export type ExecutiveAction = {
+ id: string;
+ type: string;
+ title: string;
+ agency: string;
+ frDocNumber: string;
+ date: string;
+ sourceUrl: string;
+ href?: string;
+};
+
+/* ------------------------------------------------------------------ */
+/* Members + vote positions */
+/* ------------------------------------------------------------------ */
+
+/** Raw party strings in the members table → desaturated PartyCode token. */
+export function partyCodeFromWord(word: string): PartyCode {
+ const w = (word || "").toLowerCase();
+ if (w.startsWith("rep")) return "R";
+ if (w.startsWith("dem")) return "D";
+ return "I";
+}
+
+export type MemberSummary = {
+ id: string; // "member:181"
+ numericId: number;
+ bioguide: string;
+ name: string;
+ party: string; // raw word: Democrat / Republican / Independent
+ partyCode: PartyCode;
+ state: string;
+ district: number | null;
+ chamber: string; // house | senate
+ sourceUrl: string;
+ href: string;
+};
+
+export type VotePosition = {
+ member: string;
+ party: string;
+ partyCode: PartyCode;
+ state: string;
+ district: number | null;
+ position: string; // Yea / Nay / Present / Not Voting
+ memberHref: string;
+};
+
+export type VoteDetail = Vote & {
+ question?: string;
+ rollNumber?: number;
+ notVoting?: number;
+ positions: VotePosition[];
+};
+
+export type MemberVote = {
+ voteId: string;
+ voteHref: string;
+ date: string;
+ chamber: string;
+ question?: string;
+ result: string;
+ yea: number;
+ nay: number;
+ position: string;
+ sourceUrl: string;
+};
+
+/**
+ * A year bucket of a member's votes - the "year by year how they voted"
+ * view. `year` is null when the underlying vote row is undated; such a
+ * bucket sorts last and is labelled "Undated" by the UI.
+ */
+export type MemberYearGroup = {
+ year: number | null;
+ count: number;
+ votes: MemberVote[];
+};
+
+export type MemberSponsorship = {
+ billId: string;
+ billHref: string;
+ number: string;
+ title: string;
+ congress: number;
+ status: string;
+ updatedAt: string;
+ sourceUrl: string;
+};
+
+export type MemberAmendment = {
+ amendmentId: string;
+ amendmentHref: string;
+ number: string;
+ type: string;
+ status: string;
+ billId: number | null;
+ billHref: string | null;
+ sourceUrl: string;
+};
+
+export type MemberDetail = MemberSummary & {
+ votes: MemberVote[];
+ sponsorships: MemberSponsorship[];
+ amendments: MemberAmendment[];
+ voteCount: number;
+ votesByYear: MemberYearGroup[];
+};
+
+/* ------------------------------------------------------------------ */
+/* Bills detail */
+/* ------------------------------------------------------------------ */
+
+export type BillVersion = {
+ id: string;
+ versionCode: string;
+ versionLabel: string;
+ versionDate: string;
+ sourceUrl: string;
+};
+
+export type BillDiffChange = {
+ sectionId: string;
+ change: string; // added | removed | amended
+ snippet: string;
+ riderFlag: boolean;
+ sourceUrl: string;
+ fromVersion: string;
+ toVersion: string;
+};
+
+export type BillDiff = {
+ from: string;
+ to: string;
+ changes: BillDiffChange[];
+ note?: string;
+};
+
+export type BillAmendment = {
+ id: string;
+ number: string;
+ type: string;
+ status: string;
+ agreedMethod: string;
+ billId: number | null;
+ sponsor: MemberSummary | null;
+ sourceUrl: string;
+ href: string;
+};
+
+export type BillTimelineEntry = {
+ date: string;
+ chamber: string;
+ actionType: string;
+ text: string;
+ sourceUrl: string;
+};
+
+export type BillDetail = {
+ id: string;
+ number: string;
+ title: string;
+ congress: number;
+ policyArea: string;
+ status: string;
+ updatedAt: string;
+ sourceUrl: string;
+ href: string;
+ sponsor: MemberSummary | null;
+ sponsorDisplay: string;
+ versions: BillVersion[];
+ amendments: BillAmendment[];
+ diffs: BillDiffChange[];
+ votes: VoteDetail[];
+ timeline: BillTimelineEntry[];
+ subjects: string[];
+ fullTextUrl: string;
+ summaryPlain: string;
+};
+
+/* ------------------------------------------------------------------ */
+/* States */
+/* ------------------------------------------------------------------ */
+
+export type StateCoverage = {
+ code: string;
+ name: string;
+ confidence: "ok" | "stale" | "missing";
+ lastScrape: string;
+ missingTypes: string[];
+};
+
+export type StateDetail = StateCoverage & {
+ members: MemberSummary[];
+ bills: BillListItem[];
+ votes: Vote[];
+};
+
+/* ------------------------------------------------------------------ */
+/* Ranking (rule #3) - pure, references only the 4 permitted fields. */
+/* ------------------------------------------------------------------ */
+
+type Rankable = {
+ actionDate: string;
+ dollarAmount: number | null;
+ affectedEstimate: number | null;
+ actionType: string;
+};
+
+/**
+ * Compare for newest-first ordering: dated items first, then by dollar
+ * magnitude, then by people affected, then action type. Empty/missing dates
+ * sink to the bottom (undated). Returns a sort comparator.
+ *
+ * Intentionally reads ONLY the four ranking fields - no engagement inputs.
+ */
+export function rankByRule(): (a: Rankable, b: Rankable) => number {
+ return (a, b) => {
+ const ad = dateKey(a.actionDate);
+ const bd = dateKey(b.actionDate);
+ // Dated beats undated.
+ if (ad !== bd) return bd - ad;
+ if (ad === 0) {
+ // Both undated → dollar magnitude, then affected.
+ const da = a.dollarAmount ?? 0;
+ const db = b.dollarAmount ?? 0;
+ if (db !== da) return db - da;
+ return (b.affectedEstimate ?? 0) - (a.affectedEstimate ?? 0);
+ }
+ // Both dated and equal day → dollar magnitude desc, then affected desc.
+ const da = a.dollarAmount ?? 0;
+ const db = b.dollarAmount ?? 0;
+ if (db !== da) return db - da;
+ return (b.affectedEstimate ?? 0) - (a.affectedEstimate ?? 0);
+ };
+}
+
+/** Epoch-day number, or 0 for empty/missing/undated. */
+function dateKey(iso: string): number {
+ if (!iso) return 0;
+ const t = Date.parse(iso.length === 10 ? iso + "T00:00:00Z" : iso);
+ return Number.isFinite(t) ? Math.floor(t / 86_400_000) : 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* FEED - mixed, ranked. Pulls each source defensively. */
+/* ------------------------------------------------------------------ */
+
+export function getFeedItems(): FeedItem[] {
+ const items: FeedItem[] = [];
+
+ // Awards (USAspending).
+ items.push(
+ ...safeAll<FeedItem>(
+ `SELECT id, agency, recipient, amount, action_date, program, source_url
+ FROM awards
+ ORDER BY amount DESC`,
+ {},
+ (r) => toFeedAward(r)
+ )
+ );
+
+ // Votes (House/Senate roll calls).
+ items.push(
+ ...safeAll<FeedItem>(
+ `SELECT id, bill_id, chamber, date, yea, nay, present, result, source_url
+ FROM votes
+ ORDER BY date DESC`,
+ {},
+ (r) => toFeedVote(r)
+ )
+ );
+
+ // Executive actions (Federal Register).
+ items.push(
+ ...safeAll<FeedItem>(
+ `SELECT id, type, title, agency, fr_doc_number, date, source_url
+ FROM executive_actions
+ ORDER BY date DESC`,
+ {},
+ (r) => toFeedExecutive(r)
+ )
+ );
+
+ return items.sort(rankItems);
+}
+
+/**
+ * Project a feed item onto ONLY the four ranking fields (rule #3), then
+ * compare. The underlying comparator (rankByRule) is pure and references
+ * nothing else - no engagement/news/social input.
+ */
+function toRankable(i: FeedItem): Rankable {
+ return {
+ actionDate: i.actionDate,
+ dollarAmount: i.amount ?? null,
+ affectedEstimate: null,
+ actionType: i.actionType,
+ };
+}
+const rankItems = (a: FeedItem, b: FeedItem): number =>
+ rankByRule()(toRankable(a), toRankable(b));
+
+function toFeedAward(r: Record<string, unknown>): FeedItem {
+ const amount = num(r.amount);
+ const id = `award:${r.id}`;
+ return {
+ id,
+ kind: "award",
+ actionDate: str(r.action_date),
+ chamber: "USASPENDING",
+ title: str(r.agency) || "Federal award",
+ summary: str(r.recipient) || undefined,
+ actionType: "AWARD",
+ amount,
+ amountText: formatUsd(amount, { compact: true }),
+ jurisdiction: "US",
+ sourceUrl: ensureSource(r.source_url),
+ href: "/spending",
+ };
+}
+
+function toFeedVote(r: Record<string, unknown>): FeedItem {
+ const yea = num(r.yea);
+ const nay = num(r.nay);
+ const passed = yea > nay;
+ const voteId = num(r.id);
+ return {
+ id: `vote:${r.id}`,
+ kind: "vote",
+ actionDate: str(r.date),
+ chamber: str(r.chamber).toUpperCase() || "SENATE",
+ title: str(r.result) || "Roll-call vote",
+ summary: undefined,
+ actionType: passed ? "PASSED" : "FAILED",
+ amount: null,
+ amountText: `${yea}\u2013${nay}`,
+ jurisdiction: "US",
+ sourceUrl: ensureSource(r.source_url),
+ href: `/votes/${voteId}`,
+ };
+}
+
+function toFeedExecutive(r: Record<string, unknown>): FeedItem {
+ const execId = num(r.id);
+ return {
+ id: `exec:${r.id}`,
+ kind: "executive",
+ actionDate: str(r.date),
+ chamber: "FEDERAL REGISTER",
+ title: str(r.title) || "Federal Register action",
+ summary: str(r.agency) || undefined,
+ actionType: execTypeLabel(str(r.type)),
+ amount: null,
+ amountText: undefined,
+ jurisdiction: "US",
+ sourceUrl: ensureSource(r.source_url),
+ href: `/executive?id=${execId}`,
+ };
+}
+
+function execTypeLabel(t: string): string {
+ switch (t.toLowerCase()) {
+ case "eo":
+ return "EXECUTIVE ORDER";
+ case "nprm":
+ return "PROPOSED RULE";
+ case "final_rule":
+ return "FINAL RULE";
+ case "notice":
+ return "NOTICE";
+ default:
+ return t.toUpperCase() || "ACTION";
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* AWARDS / FISCAL / APPORTIONMENTS */
+/* ------------------------------------------------------------------ */
+
+export function getAwards(): Award[] {
+ return safeAll<Award>(
+ `SELECT id, agency, recipient, amount, action_date, program, source_url
+ FROM awards
+ ORDER BY amount DESC`,
+ {},
+ (r) => {
+ const amount = num(r.amount);
+ return {
+ id: `award:${r.id}`,
+ agency: str(r.agency),
+ recipient: str(r.recipient),
+ amount,
+ amountText: formatUsd(amount),
+ actionDate: str(r.action_date),
+ program: str(r.program),
+ sourceUrl: ensureSource(r.source_url),
+ href: "/spending",
+ };
+ }
+ );
+}
+
+export function getFiscal(): FiscalMetric[] {
+ return safeAll<FiscalMetric>(
+ `SELECT rowid, metric, date, value, source_url FROM fiscal ORDER BY date DESC`,
+ {},
+ (r) => {
+ const value = num(r.value);
+ return {
+ id: `fiscal:${r.rowid}`,
+ metric: str(r.metric),
+ value,
+ valueText: formatUsd(value, { compact: true }),
+ date: str(r.date),
+ sourceUrl: ensureSource(r.source_url),
+ href: "/spending",
+ };
+ }
+ );
+}
+
+export function getApportionments(): Apportionment[] {
+ // Use the real `id` column (the table has one); fall back to rowid if absent.
+ return safeAll<Apportionment>(
+ `SELECT id, agency, account, appropriated, apportioned, status, date, source_url
+ FROM apportionments
+ ORDER BY date DESC`,
+ {},
+ (r) => ({
+ id: `appr:${r.id ?? r.rowid}`,
+ agency: str(r.agency),
+ account: str(r.account),
+ appropriated: nullableNum(r.appropriated),
+ apportioned: nullableNum(r.apportioned),
+ status: str(r.status) || "released",
+ date: str(r.date),
+ sourceUrl: ensureSource(r.source_url),
+ href: "/spending",
+ })
+ );
+}
+
+/* ------------------------------------------------------------------ */
+/* VOTES */
+/* ------------------------------------------------------------------ */
+
+export function getVotes(): Vote[] {
+ return safeAll<Vote>(
+ `SELECT id, bill_id, chamber, date, yea, nay, present, result, source_url
+ FROM votes
+ ORDER BY date DESC`,
+ {},
+ (r) => toVote(r)
+ );
+}
+
+/**
+ * Single vote with its full roster of member positions. For voice / unanimous-
+ * consent votes (recorded = false, yea+nay = 0) there is no individual record;
+ * positions stays empty and the UI shows the honesty note (docs/API.md).
+ */
+export function getVoteById(id: number): VoteDetail | null {
+ const r = safeGet<Record<string, unknown>>(
+ `SELECT id, bill_id, chamber, date, yea, nay, present, not_voting,
+ roll_number, question, recorded, result, source_url
+ FROM votes WHERE id = :id`,
+ { id }
+ );
+ if (!r) return null;
+
+ const yea = num(r.yea);
+ const nay = num(r.nay);
+ const recorded =
+ (r.recorded != null ? !!num(r.recorded) : true) && yea + nay > 0;
+
+ const positions: VotePosition[] = recorded
+ ? safeAll<VotePosition>(
+ `SELECT vp.position, m.full_name, m.party, m.state, m.district, m.id
+ FROM vote_positions vp
+ LEFT JOIN members m ON m.id = vp.member_id
+ WHERE vp.vote_id = :id`,
+ { id },
+ (p) => ({
+ member: str(p.full_name) || str(p.member_bioguide) || "Member",
+ party: str(p.party),
+ partyCode: partyCodeFromWord(str(p.party)),
+ state: str(p.state),
+ district: nullableNum(p.district),
+ position: str(p.position) || "Not Voting",
+ memberHref: p.id ? `/members/${num(p.id)}` : "",
+ })
+ )
+ : [];
+
+ return {
+ id: `vote:${r.id}`,
+ billId: nullableNum(r.bill_id),
+ chamber: str(r.chamber).toUpperCase() || "SENATE",
+ date: str(r.date),
+ result: str(r.result) || (yea > nay ? "Passed" : "Failed"),
+ yea,
+ nay,
+ present: num(r.present),
+ recorded,
+ sourceUrl: ensureSource(r.source_url),
+ href: undefined,
+ question: str(r.question) || undefined,
+ rollNumber: nullableNum(r.roll_number) ?? undefined,
+ notVoting: nullableNum(r.not_voting) ?? undefined,
+ positions,
+ };
+}
+
+function toVote(r: Record<string, unknown>): Vote {
+ const yea = num(r.yea);
+ const nay = num(r.nay);
+ const recorded =
+ (r.recorded != null ? !!num(r.recorded) : true) && yea + nay > 0;
+ const voteId = num(r.id);
+ return {
+ id: `vote:${r.id}`,
+ billId: nullableNum(r.bill_id),
+ chamber: str(r.chamber).toUpperCase() || "SENATE",
+ date: str(r.date),
+ result: str(r.result) || (yea > nay ? "Passed" : "Failed"),
+ yea,
+ nay,
+ present: num(r.present),
+ recorded,
+ sourceUrl: ensureSource(r.source_url),
+ href: `/votes/${voteId}`,
+ };
+}
+
+/* ------------------------------------------------------------------ */
+/* EXECUTIVE */
+/* ------------------------------------------------------------------ */
+
+export function getExecutive(): ExecutiveAction[] {
+ return safeAll<ExecutiveAction>(
+ `SELECT id, type, title, agency, fr_doc_number, date, source_url
+ FROM executive_actions
+ ORDER BY date DESC`,
+ {},
+ (r) => ({
+ id: `exec:${r.id}`,
+ type: str(r.type),
+ title: str(r.title),
+ agency: str(r.agency),
+ frDocNumber: str(r.fr_doc_number),
+ date: str(r.date),
+ sourceUrl: ensureSource(r.source_url),
+ href: undefined,
+ })
+ );
+}
+
+/* ------------------------------------------------------------------ */
+/* BILLS - list, detail, diff */
+/* ------------------------------------------------------------------ */
+
+export type BillListItem = {
+ id: string;
+ number: string;
+ title: string;
+ congress: number;
+ policyArea: string;
+ status: string;
+ updatedAt: string;
+ sourceUrl: string;
+ href: string;
+};
+
+export type BillFilter = {
+ q?: string;
+ state?: string;
+ status?: string;
+ congress?: number;
+ policyArea?: string;
+ sort?: "updated" | "action";
+};
+
+export function getBills(filter: BillFilter = {}): BillListItem[] {
+ const items = safeAll<BillListItem>(
+ `SELECT b.id, b.congress, b.number, b.title, b.status, b.policy_area,
+ b.updated_at, b.source_url
+ FROM bills b
+ ORDER BY b.updated_at DESC`,
+ {},
+ (r) => ({
+ id: `bill:${r.id}`,
+ number: str(r.number),
+ title: str(r.title),
+ congress: num(r.congress),
+ policyArea: str(r.policy_area),
+ status: str(r.status),
+ updatedAt: str(r.updated_at),
+ sourceUrl: ensureSource(r.source_url),
+ href: `/bill/${r.id}`,
+ })
+ );
+
+ let out = items;
+ const q = filter.q?.trim().toLowerCase();
+ if (q) {
+ out = out.filter(
+ (b) =>
+ b.number.toLowerCase().includes(q) ||
+ b.title.toLowerCase().includes(q) ||
+ b.policyArea.toLowerCase().includes(q)
+ );
+ }
+ if (filter.congress) {
+ out = out.filter((b) => b.congress === filter.congress);
+ }
+ if (filter.policyArea) {
+ const p = filter.policyArea.toLowerCase();
+ out = out.filter((b) => b.policyArea.toLowerCase() === p);
+ }
+ if (filter.status) {
+ const s = filter.status.toLowerCase();
+ out = out.filter((b) => b.status.toLowerCase().includes(s));
+ }
+ // state filter applies via sponsor state (bills_ext); optional.
+ if (filter.state) {
+ const st = filter.state.toUpperCase();
+ const bySponsorState = new Set(
+ safeAll<{ billId: number }>(
+ `SELECT bill_id FROM bills_ext WHERE UPPER(sponsor_state) = :st`,
+ { st },
+ (r) => ({ billId: num(r.bill_id) })
+ ).map((x) => x.billId)
+ );
+ out = out.filter((b) => bySponsorState.has(parseInt(b.id.replace(/^bill:/, ""), 10)));
+ }
+ if (filter.sort === "action") {
+ out = [...out].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
+ }
+ return out;
+}
+
+/** Distinct policy areas present in the record (for browse filters). */
+export function getBillPolicyAreas(): string[] {
+ const rows = safeAll<{ policy_area: string }>(
+ `SELECT DISTINCT policy_area FROM bills
+ WHERE policy_area IS NOT NULL AND policy_area != ''
+ ORDER BY policy_area`,
+ {},
+ (r) => ({ policy_area: str(r.policy_area) })
+ );
+ return rows.map((r) => r.policy_area);
+}
+
+/** Distinct congress numbers present, descending. */
+export function getBillCongresses(): number[] {
+ return safeAll<{ congress: number }>(
+ `SELECT DISTINCT congress FROM bills WHERE congress IS NOT NULL ORDER BY congress DESC`,
+ {},
+ (r) => ({ congress: num(r.congress) })
+ ).map((r) => r.congress);
+}
+
+export function getBillDetail(idNum: number): BillDetail | null {
+ const r = safeGet<Record<string, unknown>>(
+ `SELECT b.id, b.congress, b.number, b.title, b.status, b.policy_area,
+ b.updated_at, b.source_url, b.sponsor_id,
+ (SELECT e.sponsor_name FROM bills_ext e WHERE e.bill_id = b.id) AS sponsor_name
+ FROM bills b WHERE b.id = :id`,
+ { id: idNum }
+ );
+ if (!r) return null;
+
+ const sponsor = getMemberSummaryByBioguide(str(r.sponsor_id));
+
+ const versions = safeAll<BillVersion>(
+ `SELECT version_code, version_label, version_date, source_url
+ FROM bill_versions WHERE bill_id = :id
+ ORDER BY version_date DESC`,
+ { id: idNum },
+ (v) => ({
+ id: `bv:${idNum}:${str(v.version_code)}`,
+ versionCode: str(v.version_code),
+ versionLabel: str(v.version_label),
+ versionDate: str(v.version_date),
+ sourceUrl: ensureSource(v.source_url),
+ })
+ );
+
+ const amendments = safeAll<BillAmendment>(
+ `SELECT id, congress, type, number, bill_id, sponsor_id, status, agreed_method, source_url
+ FROM amendments WHERE bill_id = :id
+ ORDER BY number DESC`,
+ { id: idNum },
+ (a) => ({
+ id: `amdt:${a.id}`,
+ number: str(a.number),
+ type: str(a.type),
+ status: str(a.status),
+ agreedMethod: str(a.agreed_method),
+ billId: nullableNum(a.bill_id),
+ sponsor: getMemberSummaryByBioguide(str(a.sponsor_id)),
+ sourceUrl: ensureSource(a.source_url),
+ href: ensureSource(a.source_url),
+ })
+ );
+
+ const diffs = safeAll<BillDiffChange>(
+ `SELECT section_id, change, snippet, rider_flag, source_url,
+ from_version, to_version
+ FROM bill_diffs WHERE bill_id = :id
+ ORDER BY rider_flag DESC, section_id`,
+ { id: idNum },
+ (d) => ({
+ sectionId: str(d.section_id),
+ change: str(d.change),
+ snippet: str(d.snippet),
+ riderFlag: !!num(d.rider_flag),
+ sourceUrl: ensureSource(d.source_url),
+ fromVersion: str(d.from_version),
+ toVersion: str(d.to_version),
+ })
+ );
+
+ const timeline = safeAll<BillTimelineEntry>(
+ `SELECT action_date, chamber, action_type, text, source_url
+ FROM actions WHERE bill_id = :id
+ ORDER BY action_date DESC, id DESC`,
+ { id: idNum },
+ (t) => ({
+ date: str(t.action_date),
+ chamber: str(t.chamber),
+ actionType: str(t.action_type),
+ text: str(t.text),
+ sourceUrl: ensureSource(t.source_url),
+ })
+ );
+
+ return {
+ id: `bill:${r.id}`,
+ number: str(r.number),
+ congress: num(r.congress),
+ title: str(r.title),
+ status: str(r.status),
+ policyArea: str(r.policy_area),
+ updatedAt: str(r.updated_at),
+ sourceUrl: ensureSource(r.source_url),
+ href: `/bill/${r.id}`,
+ sponsor,
+ sponsorDisplay: str(r.sponsor_name),
+ versions,
+ amendments,
+ diffs,
+ // No votes are linked to bills in the current ingest (votes.bill_id is
+ // null across the record). Shown honestly on the page rather than faked.
+ votes: [],
+ timeline,
+ subjects: getBillSubjects(idNum),
+ fullTextUrl: ensureSource(r.source_url),
+ summaryPlain: "",
+ };
+}
+
+/**
+ * Section-level diff between two versions - the "what got slipped in" view.
+ * (docs/API.md signature feature.) Returns the changes for the first matching
+ * version pair, or all changes for the bill when from/to are omitted.
+ */
+export function getBillDiff(
+ billId: number,
+ fromV: string,
+ toV: string
+): BillDiff {
+ const hasVersionFilter = !!fromV && !!toV;
+ const changes = safeAll<BillDiffChange>(
+ hasVersionFilter
+ ? `SELECT section_id, change, snippet, rider_flag, source_url,
+ from_version, to_version
+ FROM bill_diffs
+ WHERE bill_id = :billId
+ AND from_version = :fromV AND to_version = :toV
+ ORDER BY rider_flag DESC, section_id`
+ : `SELECT section_id, change, snippet, rider_flag, source_url,
+ from_version, to_version
+ FROM bill_diffs
+ WHERE bill_id = :billId
+ ORDER BY rider_flag DESC, section_id`,
+ hasVersionFilter ? { billId, fromV, toV } : { billId },
+ (c) => ({
+ sectionId: str(c.section_id),
+ change: str(c.change),
+ snippet: str(c.snippet),
+ riderFlag: !!num(c.rider_flag),
+ sourceUrl: ensureSource(c.source_url),
+ fromVersion: str(c.from_version),
+ toVersion: str(c.to_version),
+ })
+ );
+ return {
+ from: fromV,
+ to: toV,
+ changes,
+ note: undefined,
+ };
+}
+
+/* ------------------------------------------------------------------ */
+/* MEMBERS */
+/* ------------------------------------------------------------------ */
+
+export type MemberFilter = {
+ q?: string;
+ state?: string;
+ chamber?: string;
+ party?: PartyCode;
+};
+
+export function getMembers(filter: MemberFilter = {}): MemberSummary[] {
+ const items = safeAll<MemberSummary>(
+ `SELECT id, bioguide_id, full_name, party, state, district, chamber, source_url
+ FROM members
+ ORDER BY chamber, state, full_name`,
+ {},
+ (r) => toMemberSummary(r)
+ );
+
+ let out = items;
+ const q = filter.q?.trim().toLowerCase();
+ if (q) {
+ out = out.filter(
+ (m) =>
+ m.name.toLowerCase().includes(q) ||
+ m.bioguide.toLowerCase().includes(q) ||
+ m.state.toLowerCase().includes(q)
+ );
+ }
+ if (filter.state) {
+ const st = filter.state.toUpperCase();
+ out = out.filter((m) => m.state === st);
+ }
+ if (filter.chamber) {
+ const ch = filter.chamber.toLowerCase();
+ out = out.filter((m) => m.chamber.toLowerCase() === ch);
+ }
+ if (filter.party) {
+ out = out.filter((m) => m.partyCode === filter.party);
+ }
+ return out;
+}
+
+/** Distinct state codes present in the members table. */
+export function getMemberStates(): string[] {
+ return safeAll<{ state: string }>(
+ `SELECT DISTINCT state FROM members WHERE state IS NOT NULL AND state != ''
+ ORDER BY state`,
+ {},
+ (r) => ({ state: str(r.state) })
+ ).map((r) => r.state);
+}
+
+export function getMember(rawId: string): MemberDetail | null {
+ // Accept "member:181", "181", or a bioguide id.
+ const cleaned = rawId.replace(/^member:/, "");
+ const numeric = parseInt(cleaned, 10);
+ const byId = Number.isFinite(numeric)
+ ? safeGet<Record<string, unknown>>(
+ `SELECT id, bioguide_id, full_name, party, state, district, chamber, source_url
+ FROM members WHERE id = :id`,
+ { id: numeric }
+ )
+ : null;
+ const row =
+ byId ??
+ safeGet<Record<string, unknown>>(
+ `SELECT id, bioguide_id, full_name, party, state, district, chamber, source_url
+ FROM members WHERE bioguide_id = :bg`,
+ { bg: cleaned }
+ );
+ if (!row) return null;
+
+ const summary = toMemberSummary(row);
+ const bioguide = summary.bioguide;
+
+ const votes = safeAll<MemberVote>(
+ `SELECT v.id, v.date, v.chamber, v.question, v.result, v.yea, v.nay, v.source_url,
+ vp.position
+ FROM vote_positions vp
+ JOIN votes v ON v.id = vp.vote_id
+ WHERE vp.member_id = :mid
+ ORDER BY v.date DESC, v.id DESC
+ LIMIT 40`,
+ { mid: summary.numericId },
+ (v) => ({
+ voteId: `vote:${v.id}`,
+ voteHref: `/votes/${num(v.id)}`,
+ date: str(v.date),
+ chamber: str(v.chamber).toUpperCase() || "SENATE",
+ question: str(v.question) || undefined,
+ result: str(v.result),
+ yea: num(v.yea),
+ nay: num(v.nay),
+ position: str(v.position) || "Not Voting",
+ sourceUrl: ensureSource(v.source_url),
+ })
+ );
+
+ const sponsorships = safeAll<MemberSponsorship>(
+ `SELECT id, number, title, congress, status, updated_at, source_url
+ FROM bills WHERE sponsor_id = :bg
+ ORDER BY updated_at DESC`,
+ { bg: bioguide },
+ (b) => ({
+ billId: `bill:${b.id}`,
+ billHref: `/bill/${b.id}`,
+ number: str(b.number),
+ title: str(b.title),
+ congress: num(b.congress),
+ status: str(b.status),
+ updatedAt: str(b.updated_at),
+ sourceUrl: ensureSource(b.source_url),
+ })
+ );
+
+ const amendments = safeAll<MemberAmendment>(
+ `SELECT id, number, type, status, bill_id, source_url
+ FROM amendments WHERE sponsor_id = :bg
+ ORDER BY id DESC`,
+ { bg: bioguide },
+ (a) => ({
+ amendmentId: `amdt:${a.id}`,
+ amendmentHref: ensureSource(a.source_url),
+ number: str(a.number),
+ type: str(a.type),
+ status: str(a.status),
+ billId: nullableNum(a.bill_id),
+ billHref: a.bill_id ? `/bill/${a.bill_id}` : null,
+ sourceUrl: ensureSource(a.source_url),
+ })
+ );
+
+ const voteCount = safeGet<{ c: number }>(
+ `SELECT COUNT(*) AS c FROM vote_positions WHERE member_id = :mid`,
+ { mid: summary.numericId }
+ )?.c ?? votes.length;
+
+ // Year-by-year index of EVERY recorded position (the member-year headline
+ // view). Uses the votes.year column; undated votes bucket to null → last.
+ const allMemberVotes = safeAll<MemberVote & { year: number | null }>(
+ `SELECT v.id, v.date, v.year, v.chamber, v.question, v.result, v.yea, v.nay,
+ v.source_url, vp.position
+ FROM vote_positions vp
+ JOIN votes v ON v.id = vp.vote_id
+ WHERE vp.member_id = :mid
+ ORDER BY v.date DESC, v.id DESC`,
+ { mid: summary.numericId },
+ (v) => ({
+ voteId: `vote:${v.id}`,
+ voteHref: `/votes/${num(v.id)}`,
+ date: str(v.date),
+ chamber: str(v.chamber).toUpperCase() || "SENATE",
+ question: str(v.question) || undefined,
+ result: str(v.result),
+ yea: num(v.yea),
+ nay: num(v.nay),
+ position: str(v.position) || "Not Voting",
+ sourceUrl: ensureSource(v.source_url),
+ year: nullableNum(v.year),
+ })
+ );
+ const votesByYear: MemberYearGroup[] = groupMemberVotesByYear(allMemberVotes);
+
+ return {
+ ...summary,
+ votes,
+ sponsorships,
+ amendments,
+ voteCount,
+ votesByYear,
+ };
+}
+
+/** Group a member's votes into year buckets, newest year first (undated last). */
+function groupMemberVotesByYear(
+ votes: Array<MemberVote & { year: number | null }>
+): MemberYearGroup[] {
+ const groups = new Map<number | null, MemberVote[]>();
+ for (const v of votes) {
+ const arr = groups.get(v.year) ?? [];
+ arr.push(v);
+ groups.set(v.year, arr);
+ }
+ return Array.from(groups.entries())
+ .sort((a, b) => {
+ if (a[0] == null) return 1;
+ if (b[0] == null) return -1;
+ return b[0] - a[0];
+ })
+ .map(([year, vs]) => ({ year, count: vs.length, votes: vs }));
+}
+
+function toMemberSummary(r: Record<string, unknown>): MemberSummary {
+ const idNum = num(r.id);
+ return {
+ id: `member:${idNum}`,
+ numericId: idNum,
+ bioguide: str(r.bioguide_id),
+ name: str(r.full_name),
+ party: str(r.party),
+ partyCode: partyCodeFromWord(str(r.party)),
+ state: str(r.state),
+ district: nullableNum(r.district),
+ chamber: str(r.chamber).toLowerCase() || "house",
+ sourceUrl: ensureSource(r.source_url),
+ href: `/members/${idNum}`,
+ };
+}
+
+function getMemberSummaryByBioguide(bioguide: string): MemberSummary | null {
+ if (!bioguide) return null;
+ const r = safeGet<Record<string, unknown>>(
+ `SELECT id, bioguide_id, full_name, party, state, district, chamber, source_url
+ FROM members WHERE bioguide_id = :bg`,
+ { bg: bioguide }
+ );
+ return r ? toMemberSummary(r) : null;
+}
+
+/* ------------------------------------------------------------------ */
+/* STATES */
+/* ------------------------------------------------------------------ */
+
+const STATE_NAMES: Record<string, string> = {
+ AL: "Alabama", AK: "Alaska", AZ: "Arizona", AR: "Arkansas", CA: "California",
+ CO: "Colorado", CT: "Connecticut", DE: "Delaware", FL: "Florida", GA: "Georgia",
+ HI: "Hawaii", ID: "Idaho", IL: "Illinois", IN: "Indiana", IA: "Iowa",
+ KS: "Kansas", KY: "Kentucky", LA: "Louisiana", ME: "Maine", MD: "Maryland",
+ MA: "Massachusetts", MI: "Michigan", MN: "Minnesota", MS: "Mississippi",
+ MO: "Missouri", MT: "Montana", NE: "Nebraska", NV: "Nevada", NH: "New Hampshire",
+ NJ: "New Jersey", NM: "New Mexico", NY: "New York", NC: "North Carolina",
+ ND: "North Dakota", OH: "Ohio", OK: "Oklahoma", OR: "Oregon", PA: "Pennsylvania",
+ RI: "Rhode Island", SC: "South Carolina", SD: "South Dakota", TN: "Tennessee",
+ TX: "Texas", UT: "Utah", VT: "Vermont", VA: "Virginia", WA: "Washington",
+ WV: "West Virginia", WI: "Wisconsin", WY: "Wyoming",
+ DC: "District of Columbia", PR: "Puerto Rico", AS: "American Samoa",
+ GU: "Guam", MP: "Northern Mariana Islands", VI: "U.S. Virgin Islands",
+};
+
+function stateName(code: string): string {
+ return STATE_NAMES[code] || code;
+}
+
+/** Worst-case confidence across the per-data-type rows for a state. */
+function rollupConfidence(values: string[]): "ok" | "stale" | "missing" {
+ const set = new Set(values.map((v) => (v || "").toLowerCase()));
+ if (set.size === 0 || set.has("missing")) return "missing";
+ if (set.has("stale")) return "stale";
+ return "ok";
+}
+
+export function getStates(): StateCoverage[] {
+ const rows = safeAll<{
+ state: string;
+ confidence: string;
+ data_type: string;
+ last_scrape: string;
+ }>(
+ `SELECT state, confidence, data_type, last_scrape
+ FROM state_coverage
+ ORDER BY state`,
+ {},
+ (r) => ({
+ state: str(r.state).toUpperCase(),
+ confidence: str(r.confidence),
+ data_type: str(r.data_type),
+ last_scrape: str(r.last_scrape),
+ })
+ );
+
+ // Aggregate the per-data-type rows into one entry per state.
+ const byState = new Map<
+ string,
+ {
+ confidences: string[];
+ missingTypes: string[];
+ lastScrape: string;
+ }
+ >();
+ for (const r of rows) {
+ if (!r.state) continue;
+ const entry = byState.get(r.state) ?? {
+ confidences: [],
+ missingTypes: [],
+ lastScrape: "",
+ };
+ entry.confidences.push(r.confidence);
+ if (
+ (r.confidence || "").toLowerCase() === "missing" &&
+ r.data_type
+ ) {
+ entry.missingTypes.push(r.data_type);
+ }
+ if (r.last_scrape > entry.lastScrape) entry.lastScrape = r.last_scrape;
+ byState.set(r.state, entry);
+ }
+
+ // Always include all 50 states + DC so the map is complete and honest
+ // about gaps rather than hiding un-polled states.
+ const seen = new Set(byState.keys());
+ for (const code of Object.keys(STATE_NAMES)) {
+ if (!seen.has(code)) {
+ byState.set(code, {
+ confidences: ["missing"],
+ missingTypes: ["bills", "votes", "sponsors"],
+ lastScrape: "",
+ });
+ }
+ }
+
+ return Array.from(byState.entries())
+ .map(([code, e]) => ({
+ code,
+ name: stateName(code),
+ confidence: rollupConfidence(e.confidences),
+ lastScrape: e.lastScrape,
+ missingTypes: Array.from(new Set(e.missingTypes)),
+ }))
+ .sort((a, b) => a.code.localeCompare(b.code));
+}
+
+export function getStateDetail(code: string): StateDetail {
+ const upper = (code || "").toUpperCase();
+ const states = getStates();
+ const coverage =
+ states.find((s) => s.code === upper) ?? {
+ code: upper,
+ name: stateName(upper),
+ confidence: "missing" as const,
+ lastScrape: "",
+ missingTypes: ["bills", "votes", "sponsors"],
+ };
+
+ const members = getMembers({ state: upper });
+ const bills = safeAll<BillListItem>(
+ `SELECT b.id, b.congress, b.number, b.title, b.status, b.policy_area,
+ b.updated_at, b.source_url
+ FROM bills b
+ JOIN bills_ext e ON e.bill_id = b.id
+ WHERE UPPER(e.sponsor_state) = :st
+ ORDER BY b.updated_at DESC`,
+ { st: upper },
+ (r) => ({
+ id: `bill:${r.id}`,
+ number: str(r.number),
+ title: str(r.title),
+ congress: num(r.congress),
+ policyArea: str(r.policy_area),
+ status: str(r.status),
+ updatedAt: str(r.updated_at),
+ sourceUrl: ensureSource(r.source_url),
+ href: `/bill/${r.id}`,
+ })
+ );
+
+ return {
+ ...coverage,
+ members,
+ bills,
+ votes: [],
+ };
+}
+
+/* ------------------------------------------------------------------ */
+/* SEARCH */
+/* ------------------------------------------------------------------ */
+
+export type SearchResults = {
+ bills: BillListItem[];
+ awards: Array<{
+ id: string;
+ agency: string;
+ recipient: string;
+ amount: number;
+ sourceUrl: string;
+ }>;
+ members: MemberSummary[];
+ executive: ExecutiveAction[];
+ states: StateCoverage[];
+};
+
+export function globalSearch(q: string): SearchResults {
+ const query = q.trim();
+ if (!query) {
+ return { bills: [], awards: [], members: [], executive: [], states: [] };
+ }
+ const like = `%${query.replace(/[%_]/g, (m) => "\\" + m)}%`;
+
+ const bills = safeAll<BillListItem>(
+ `SELECT id, congress, number, title, status, policy_area, updated_at, source_url
+ FROM bills
+ WHERE number LIKE :q ESCAPE '\\' OR title LIKE :q ESCAPE '\\'
+ OR policy_area LIKE :q ESCAPE '\\'
+ ORDER BY updated_at DESC`,
+ { q: like },
+ (r) => ({
+ id: `bill:${r.id}`,
+ number: str(r.number),
+ title: str(r.title),
+ congress: num(r.congress),
+ policyArea: str(r.policy_area),
+ status: str(r.status),
+ updatedAt: str(r.updated_at),
+ sourceUrl: ensureSource(r.source_url),
+ href: `/bill/${r.id}`,
+ })
+ );
+
+ const awards = safeAll(
+ `SELECT id, agency, recipient, amount, source_url FROM awards
+ WHERE agency LIKE :q ESCAPE '\\' OR recipient LIKE :q ESCAPE '\\'
+ ORDER BY amount DESC`,
+ { q: like },
+ (r) => ({
+ id: `award:${r.id}`,
+ agency: str(r.agency),
+ recipient: str(r.recipient),
+ amount: num(r.amount),
+ sourceUrl: ensureSource(r.source_url),
+ })
+ );
+
+ const members = safeAll<MemberSummary>(
+ `SELECT id, bioguide_id, full_name, party, state, district, chamber, source_url
+ FROM members
+ WHERE full_name LIKE :q ESCAPE '\\' OR bioguide_id LIKE :q ESCAPE '\\'
+ OR state LIKE :q ESCAPE '\\'
+ ORDER BY full_name`,
+ { q: like },
+ (r) => toMemberSummary(r)
+ );
+
+ const executive = safeAll<ExecutiveAction>(
+ `SELECT id, type, title, agency, fr_doc_number, date, source_url
+ FROM executive_actions
+ WHERE title LIKE :q ESCAPE '\\' OR agency LIKE :q ESCAPE '\\'
+ ORDER BY date DESC`,
+ { q: like },
+ (r) => ({
+ id: `exec:${r.id}`,
+ type: str(r.type),
+ title: str(r.title),
+ agency: str(r.agency),
+ frDocNumber: str(r.fr_doc_number),
+ date: str(r.date),
+ sourceUrl: ensureSource(r.source_url),
+ href: undefined,
+ })
+ );
+
+ // State match: by code or name.
+ const states = getStates().filter(
+ (s) =>
+ s.code.toLowerCase() === query.toLowerCase() ||
+ s.name.toLowerCase().includes(query.toLowerCase())
+ );
+
+ return { bills, awards, members, executive, states };
+}
+
+/* ------------------------------------------------------------------ */
+/* CALENDAR - what takes effect when (calendar_events) */
+/* ------------------------------------------------------------------ */
+
+export type CalendarKind =
+ | "effective"
+ | "enacted"
+ | "vote"
+ | "introduced"
+ | "committee"
+ | "floor"
+ | "apportionment"
+ | "effective_parsed";
+
+export type CalendarEvent = {
+ id: string;
+ date: string;
+ kind: CalendarKind;
+ refType: string;
+ refId: number | null;
+ title: string;
+ jurisdiction: string;
+ sourceUrl: string;
+ href: string | null;
+ confidence: number;
+};
+
+/**
+ * Events in an inclusive date range (YYYY-MM-DD), optionally filtered by
+ * kind. Pulls from calendar_events - the parsed "takes effect / enacted /
+ * vote / floor / committee" dates. Each row carries an allowlisted
+ * source_url (neutrality #1).
+ */
+export function getCalendarEvents(opts: {
+ from?: string;
+ to?: string;
+ kind?: string;
+}): CalendarEvent[] {
+ const where: string[] = [];
+ const params: Record<string, unknown> = {};
+ if (opts.from) {
+ where.push("date >= :from");
+ params.from = opts.from;
+ }
+ if (opts.to) {
+ where.push("date <= :to");
+ params.to = opts.to;
+ }
+ if (opts.kind) {
+ where.push("kind = :kind");
+ params.kind = opts.kind;
+ }
+ const sql = `SELECT id, date, kind, ref_type, ref_id, title, jurisdiction, source_url, confidence
+ FROM calendar_events${where.length ? " WHERE " + where.join(" AND ") : ""}
+ ORDER BY date ASC, kind ASC`;
+ return safeAll<CalendarEvent>(sql, params, (r) => {
+ const refId = nullableNum(r.ref_id);
+ return {
+ id: `cal:${r.id}`,
+ date: str(r.date),
+ kind: str(r.kind) as CalendarKind,
+ refType: str(r.ref_type),
+ refId,
+ title: str(r.title),
+ jurisdiction: str(r.jurisdiction) || "US",
+ sourceUrl: ensureSource(r.source_url),
+ href: calendarHref(str(r.ref_type), refId),
+ confidence: nullableNum(r.confidence) ?? 1,
+ };
+ });
+}
+
+/** Resolve a calendar event's ref into an internal deep link where one exists. */
+function calendarHref(refType: string, refId: number | null): string | null {
+ if (refId == null) return null;
+ switch (refType) {
+ case "bill":
+ return `/bill/${refId}`;
+ case "vote":
+ return `/votes/${refId}`;
+ case "executive_action":
+ return "/executive";
+ case "apportionment":
+ return "/spending";
+ default:
+ // "action" rows reference a bill-action step, not a standalone page.
+ return null;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* CATEGORIES - topic browse (bills.policy_area + awards.category) */
+/* ------------------------------------------------------------------ */
+
+export type CategorySummary = {
+ slug: string;
+ label: string;
+ kind: string;
+ billCount: number;
+ awardCount: number;
+ total: number;
+};
+
+/**
+ * Topic list with item counts. policy_area rows join bills.policy_area and
+ * awards.category on the category label. Returns every row in `categories`
+ * so the browse stays complete and honest about topics with no record yet.
+ */
+export function getCategories(): CategorySummary[] {
+ return safeAll<CategorySummary>(
+ `SELECT c.slug, c.label, c.kind,
+ (SELECT COUNT(*) FROM bills b WHERE b.policy_area = c.label) AS bill_count,
+ (SELECT COUNT(*) FROM awards a WHERE a.category = c.label) AS award_count
+ FROM categories c
+ ORDER BY c.kind, c.label`,
+ {},
+ (r) => {
+ const billCount = num(r.bill_count);
+ const awardCount = num(r.award_count);
+ return {
+ slug: str(r.slug),
+ label: str(r.label),
+ kind: str(r.kind),
+ billCount,
+ awardCount,
+ total: billCount + awardCount,
+ };
+ }
+ );
+}
+
+/** Distinct policy-area labels in use (for browse filters / fallback). */
+export function getPolicyAreaLabels(): string[] {
+ return safeAll<{ label: string }>(
+ `SELECT DISTINCT policy_area AS label FROM bills
+ WHERE policy_area IS NOT NULL AND policy_area != ''
+ ORDER BY policy_area`,
+ {},
+ (r) => ({ label: str(r.label) })
+ ).map((r) => r.label);
+}
+
+/** Top legislative subjects tagged on a bill (bill_subjects). */
+export function getBillSubjects(billId: number, max = 8): string[] {
+ return safeAll<{ subject: string }>(
+ `SELECT subject FROM bill_subjects
+ WHERE bill_id = :id AND subject IS NOT NULL AND subject != ''
+ ORDER BY subject
+ LIMIT :max`,
+ { id: billId, max },
+ (r) => ({ subject: str(r.subject) })
+ ).map((r) => r.subject);
+}
+
+/* ------------------------------------------------------------------ */
+/* CHANGE EVENTS - the watcher feed (added / updated / removed) */
+/* ------------------------------------------------------------------ */
+
+export type ChangeKind = "added" | "updated" | "removed";
+
+export type ChangeEvent = {
+ id: string;
+ ts: string;
+ source: string;
+ refType: string;
+ refId: string;
+ change: ChangeKind;
+ summary: string;
+ sourceUrl: string;
+ href: string | null;
+};
+
+/** Recent change_events - what the ingest added / updated / removed. */
+export function getChangeEvents(limit = 25): ChangeEvent[] {
+ return safeAll<ChangeEvent>(
+ `SELECT id, ts, source, ref_type, ref_id, change, summary, source_url
+ FROM change_events
+ ORDER BY ts DESC, id DESC
+ LIMIT :limit`,
+ { limit },
+ (r) => {
+ const refType = str(r.ref_type);
+ const refId = str(r.ref_id);
+ return {
+ id: `chg:${r.id}`,
+ ts: str(r.ts),
+ source: str(r.source),
+ refType,
+ refId,
+ change: str(r.change) as ChangeKind,
+ summary: str(r.summary),
+ sourceUrl: ensureSource(r.source_url),
+ href: changeHref(refType, refId),
+ };
+ }
+ );
+}
+
+function changeHref(refType: string, refId: string): string | null {
+ switch (refType) {
+ case "member":
+ return refId ? `/members/${refId}` : null;
+ case "bill": {
+ const n = parseInt(refId, 10);
+ return Number.isFinite(n) ? `/bill/${n}` : null;
+ }
+ case "vote": {
+ const n = parseInt(refId, 10);
+ return Number.isFinite(n) ? `/votes/${n}` : null;
+ }
+ case "executive_action":
+ return "/executive";
+ default:
+ return null;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Row coercion + source-host guard */
+/* ------------------------------------------------------------------ */
+
+function str(v: unknown): string {
+ return v == null ? "" : String(v);
+}
+function num(v: unknown): number {
+ const n = typeof v === "number" ? v : parseFloat(String(v ?? ""));
+ return Number.isFinite(n) ? n : 0;
+}
+function nullableNum(v: unknown): number | null {
+ if (v == null || v === "") return null;
+ const n = typeof v === "number" ? v : parseFloat(String(v));
+ return Number.isFinite(n) ? n : null;
+}
+
+/**
+ * Ensure every returned fact carries an official source URL (neutrality #1).
+ * Missing/blank → usaspending.gov landing (an allowlisted .gov). The
+ * SourceLink component still refuses non-.gov hosts; this is a floor.
+ */
+function ensureSource(v: unknown): string {
+ const s = str(v);
+ return s || "https://www.usaspending.gov/";
+}
lib/format.ts +49 -0
@@ -0,0 +1,49 @@
+/**
+ * Pure formatting helpers - no DB, safe for client + server.
+ * (Previously lived in lib/mock-data.ts; centralized here so API + UI share one implementation.)
+ */
+
+export function formatUsd(n: number, opts?: { compact?: boolean }): string {
+ if (!Number.isFinite(n)) return "$0";
+ if (opts?.compact && Math.abs(n) >= 1_000_000_000) {
+ return `$${(n / 1_000_000_000).toFixed(1)}B`;
+ }
+ if (opts?.compact && Math.abs(n) >= 1_000_000) {
+ return `$${(n / 1_000_000).toFixed(0)}M`;
+ }
+ if (opts?.compact && Math.abs(n) >= 1_000) {
+ return `$${(n / 1_000).toFixed(0)}K`;
+ }
+ return n.toLocaleString("en-US", {
+ style: "currency",
+ currency: "USD",
+ maximumFractionDigits: 0,
+ });
+}
+
+export function formatDate(iso: string): string {
+ if (!iso) return "\u2014";
+ const d = new Date(iso + (iso.length === 10 ? "T00:00:00Z" : ""));
+ if (Number.isNaN(d.getTime())) return "\u2014";
+ return d.toLocaleDateString("en-US", {
+ timeZone: "UTC",
+ year: "numeric",
+ month: "short",
+ day: "2-digit",
+ });
+}
+
+/** ET wall-clock label like "14:02 ET" from an ISO string, or "" if undated. */
+export function formatEt(iso: string): string {
+ if (!iso) return "";
+ const d = new Date(iso.length === 10 ? iso + "T00:00:00Z" : iso);
+ if (Number.isNaN(d.getTime())) return "";
+ return (
+ d.toLocaleTimeString("en-US", {
+ timeZone: "America/New_York",
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false,
+ }) + " ET"
+ );
+}
next-env.d.ts +1 -1
@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
next.config.js +5 -0
@@ -1,6 +1,11 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
+ // better-sqlite3 is a native Node addon - never let webpack bundle it.
+ // Route handlers and server components load it as an external require.
+ experimental: {
+ serverComponentsExternalPackages: ["better-sqlite3"],
+ },
};
module.exports = nextConfig;
package-lock.json +6729 -0
@@ -0,0 +1,6729 @@
+{
+ "name": "docket",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "docket",
+ "version": "0.1.0",
+ "dependencies": {
+ "better-sqlite3": "^12.11.1",
+ "next": "14.2.15",
+ "react": "18.3.1",
+ "react-dom": "18.3.1"
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.13",
+ "@types/node": "20.16.11",
+ "@types/react": "18.3.11",
+ "@types/react-dom": "18.3.0",
+ "autoprefixer": "10.4.20",
+ "eslint": "8.57.1",
+ "eslint-config-next": "14.2.15",
+ "postcss": "8.4.47",
+ "tailwindcss": "3.4.13",
+ "typescript": "5.6.3"
+ }
+ },
+ "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/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "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/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "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/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/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.15.tgz",
+ "integrity": "sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==",
+ "license": "MIT"
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.15.tgz",
+ "integrity": "sha512-pKU0iqKRBlFB/ocOI1Ip2CkKePZpYpnw5bEItEkuZ/Nr9FQP1+p7VDWr4VfOdff4i9bFmrOaeaU1bFEyAcxiMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "10.3.10"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz",
+ "integrity": "sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz",
+ "integrity": "sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz",
+ "integrity": "sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz",
+ "integrity": "sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz",
+ "integrity": "sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz",
+ "integrity": "sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz",
+ "integrity": "sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz",
+ "integrity": "sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz",
+ "integrity": "sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==",
+ "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/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/@rushstack/eslint-patch": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz",
+ "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.13",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "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.16.11",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
+ "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.11",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz",
+ "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
+ "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz",
+ "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.62.0",
+ "@typescript-eslint/type-utils": "8.62.0",
+ "@typescript-eslint/utils": "8.62.0",
+ "@typescript-eslint/visitor-keys": "8.62.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.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.62.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.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.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz",
+ "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.62.0",
+ "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/typescript-estree": "8.62.0",
+ "@typescript-eslint/visitor-keys": "8.62.0",
+ "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.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz",
+ "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.62.0",
+ "@typescript-eslint/types": "^8.62.0",
+ "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.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz",
+ "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/visitor-keys": "8.62.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/tsconfig-utils": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz",
+ "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==",
+ "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.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz",
+ "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/typescript-estree": "8.62.0",
+ "@typescript-eslint/utils": "8.62.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.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.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz",
+ "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==",
+ "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.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz",
+ "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.62.0",
+ "@typescript-eslint/tsconfig-utils": "8.62.0",
+ "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/visitor-keys": "8.62.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.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.1.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.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz",
+ "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.62.0",
+ "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/typescript-estree": "8.62.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.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz",
+ "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.0",
+ "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/@ungap/structured-clone": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz",
+ "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz",
+ "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz",
+ "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz",
+ "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz",
+ "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz",
+ "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz",
+ "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz",
+ "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz",
+ "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz",
+ "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-loong64-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz",
+ "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-loong64-musl": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz",
+ "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz",
+ "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz",
+ "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz",
+ "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz",
+ "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz",
+ "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz",
+ "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-openharmony-arm64": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz",
+ "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz",
+ "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz",
+ "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz",
+ "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz",
+ "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/acorn": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
+ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "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.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/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/autoprefixer": {
+ "version": "10.4.20",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "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.12.1",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz",
+ "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==",
+ "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/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "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/baseline-browser-mapping": {
+ "version": "2.10.40",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
+ "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/better-sqlite3": {
+ "version": "12.11.1",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
+ "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ },
+ "engines": {
+ "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
+ "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
+ "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.10.38",
+ "caniuse-lite": "^1.0.30001799",
+ "electron-to-chromium": "^1.5.376",
+ "node-releases": "^2.0.48",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "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": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
+ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "get-intrinsic": "^1.3.0",
+ "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/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001799",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
+ "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "license": "ISC"
+ },
+ "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/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "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/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "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==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/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/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.379",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.379.tgz",
+ "integrity": "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA==",
+ "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/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
+ "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
+ "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-abstract-get": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz",
+ "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.2",
+ "is-callable": "^1.2.7",
+ "object-inspect": "^1.13.4"
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz",
+ "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.2",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz",
+ "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-abstract-get": "^1.0.0",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.1.0",
+ "is-symbol": "^1.1.1"
+ },
+ "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": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-next": {
+ "version": "14.2.15",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.15.tgz",
+ "integrity": "sha512-mKg+NC/8a4JKLZRIOBplxXNdStgxy7lzWuedUaCc8tev+Al9mwDUTujQH6W6qXDH9kycWiVo28tADWGvpBsZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@next/eslint-plugin-next": "14.2.15",
+ "@rushstack/eslint-patch": "^1.3.3",
+ "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
+ },
+ "peerDependencies": {
+ "eslint": "^7.23.0 || ^8.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz",
+ "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.16.1",
+ "resolve": "^2.0.0-next.6"
+ }
+ },
+ "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.13.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz",
+ "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==",
+ "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-import/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/eslint-plugin-import/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/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": "5.0.0-canary-7118f5dd7-20230705",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz",
+ "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react/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/eslint-plugin-react/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/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.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/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz",
+ "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2",
+ "hasown": "^2.0.4",
+ "is-callable": "^1.2.7",
+ "is-document.all": "^1.0.0"
+ },
+ "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/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.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "license": "MIT"
+ },
+ "node_modules/glob": {
+ "version": "10.3.10",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+ "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.5",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+ "path-scurry": "^1.10.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/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==",
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-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.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "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": "BSD-3-Clause"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
+ "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-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-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-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.2",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
+ "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.3"
+ },
+ "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-document.all": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz",
+ "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.4"
+ },
+ "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-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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-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/jackspeak": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+ "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "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": "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/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/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "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-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "license": "MIT"
+ },
+ "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": "14.2.15",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.15.tgz",
+ "integrity": "sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==",
+ "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "14.2.15",
+ "@swc/helpers": "0.5.5",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.1"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "14.2.15",
+ "@next/swc-darwin-x64": "14.2.15",
+ "@next/swc-linux-arm64-gnu": "14.2.15",
+ "@next/swc-linux-arm64-musl": "14.2.15",
+ "@next/swc-linux-x64-gnu": "14.2.15",
+ "@next/swc-linux-x64-musl": "14.2.15",
+ "@next/swc-win32-arm64-msvc": "14.2.15",
+ "@next/swc-win32-ia32-msvc": "14.2.15",
+ "@next/swc-win32-x64-msvc": "14.2.15"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/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-abi": {
+ "version": "3.92.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
+ "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-exports-info": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz",
+ "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==",
+ "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-exports-info/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/node-releases": {
+ "version": "2.0.50",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
+ "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/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/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/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-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/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.4.47",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+ "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "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.7",
+ "picocolors": "^1.1.0",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-import/node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "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/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
+ "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.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/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "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/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/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/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": "2.0.0-next.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz",
+ "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.2",
+ "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/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/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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.4",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz",
+ "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "get-intrinsic": "^1.3.0",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "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/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.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/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.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "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/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "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": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "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.11",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz",
+ "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.2",
+ "es-object-atoms": "^1.1.2",
+ "has-property-descriptors": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz",
+ "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.2"
+ },
+ "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-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-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.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.13",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
+ "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "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/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "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/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/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/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/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/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/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.8",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz",
+ "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "for-each": "^0.3.5",
+ "gopd": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "possible-typed-array-names": "^1.1.0",
+ "reflect.getprototypeof": "^1.0.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "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.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz",
+ "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.4"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.12.2",
+ "@unrs/resolver-binding-android-arm64": "1.12.2",
+ "@unrs/resolver-binding-darwin-arm64": "1.12.2",
+ "@unrs/resolver-binding-darwin-x64": "1.12.2",
+ "@unrs/resolver-binding-freebsd-x64": "1.12.2",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.12.2",
+ "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-loong64-musl": "1.12.2",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.12.2",
+ "@unrs/resolver-binding-linux-x64-musl": "1.12.2",
+ "@unrs/resolver-binding-openharmony-arm64": "1.12.2",
+ "@unrs/resolver-binding-wasm32-wasi": "1.12.2",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.12.2"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "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.22",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
+ "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.9",
+ "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/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
package.json +2 -0
@@ -10,11 +10,13 @@
"neutrality": "node lint/neutrality.mjs"
},
"dependencies": {
+ "better-sqlite3": "^12.11.1",
"next": "14.2.15",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
+ "@types/better-sqlite3": "^7.6.13",
"@types/node": "20.16.11",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.0",