From 00a24bcb3da9121d0c49507d1c1a591ac6df1d87 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 19:36:39 +0530 Subject: [PATCH 01/11] docs: add calendar feature guide page /calendar had no docs coverage, unlike /map (which has /docs/exam-centres). Add a step-by-step guide covering exam windows, provisional dates, and personal events, and list it in the /docs index. Closes #115 --- src/app/docs/calendar/page.tsx | 115 +++++++++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 ++- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/calendar/page.tsx diff --git a/src/app/docs/calendar/page.tsx b/src/app/docs/calendar/page.tsx new file mode 100644 index 0000000..21f85a0 --- /dev/null +++ b/src/app/docs/calendar/page.tsx @@ -0,0 +1,115 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { StepCard } from "@/components/docs/step-card"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Using the Exam Calendar", + description: + "How to read StudyMap's exam calendar: SAT, IB, and Cambridge IGCSE windows, result dates, and your own personal events.", +}; + +export default function CalendarGuidePage() { + return ( + <> + + The Calendar page tracks upcoming SAT,{" "} + IB, and Cambridge IGCSE exam windows and + result dates, sourced from the official boards. No sign-in needed to + browse it. + + } + /> + +
+ +

+ Go to the{" "} + + Calendar + {" "} + page. Use the arrows on either side of the month name to move between + months. Each board has its own dot color, shown in the legend above the + grid: blue for SAT, purple for IB, green for IGCSE. +

+

+ Days with an exam window are highlighted and show a dot for each board + in session that day. The list below the legend shows every session that + month with its exam dates, results date, and a link back to the + official source (College Board, IBO, or Cambridge International). +

+
+ + +

+ A session tagged Provisional means + the board has published an expected window that isn't officially + confirmed yet. Results dates marked{" "} + (expected) are estimates based on + board guidance, not a confirmed release date. +

+

+ Always confirm exam and result dates with your school or test centre + before making travel or study plans - boards occasionally revise + published timetables. +

+
+ + +

+ Signed-in users can add personal events - application deadlines, + coaching sessions, reminders - that show up on the same calendar with a + pink marker, under "Your events" for the current month. Tap{" "} + Add event above the exam list to + create one, or the pencil icon on an existing event to edit it. +

+

+ Personal events are private to your account - they never appear on + anyone else's calendar. +

+
+
+ + +

+ If a session is missing, a date has changed, or a provisional window has + since been confirmed, please{" "} + + open a GitHub issue + {" "} + with a link to the official board source. +

+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index b8e451c..26f247e 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, Gift, GitPullRequest, GraduationCap, MapPin, Puzzle } from "lucide-react"; +import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, MapPin, Puzzle } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -27,6 +27,14 @@ const DOCS = [ icon: MapPin, iconClassName: "text-marker-sat-centre", }, + { + href: "/docs/calendar", + title: "Using the Exam Calendar", + description: + "Read SAT, IB, and Cambridge IGCSE exam windows and result dates, and add your own personal events.", + icon: CalendarDays, + iconClassName: "text-primary", + }, { href: "/docs/contributing", title: "Contributing Places", From 8aa5361de3a07ee46abac9c3427abe67b90748aa Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 19:38:48 +0530 Subject: [PATCH 02/11] docs: add troubleshooting & common issues page docs/TROUBLESHOOTING.md existed at the repo root but wasn't surfaced in-app. Port its content to /docs/troubleshooting and list it in the /docs index. Closes #114 --- src/app/docs/page.tsx | 10 +- src/app/docs/troubleshooting/page.tsx | 167 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/troubleshooting/page.tsx diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 26f247e..788ee71 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, MapPin, Puzzle } from "lucide-react"; +import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, MapPin, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -67,6 +67,14 @@ const DOCS = [ icon: Puzzle, iconClassName: "text-primary", }, + { + href: "/docs/troubleshooting", + title: "Troubleshooting", + description: + "Common problems when running StudyMap locally or on a fork, and what actually causes them.", + icon: Wrench, + iconClassName: "text-primary", + }, ]; export default function DocsIndexPage() { diff --git a/src/app/docs/troubleshooting/page.tsx b/src/app/docs/troubleshooting/page.tsx new file mode 100644 index 0000000..4dd5c4d --- /dev/null +++ b/src/app/docs/troubleshooting/page.tsx @@ -0,0 +1,167 @@ +import type { Metadata } from "next"; + +import { site } from "@/lib/site"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Troubleshooting", + description: + "Common problems when running StudyMap locally or on a fork, and what actually causes them.", +}; + +export default function TroubleshootingPage() { + return ( + <> + + +
+ + + Map doesn't load, or the basemap is blank/grey + Almost always a missing or invalid MapTiler key + + +

+ The map tiles come from MapTiler, and unlike the Supabase variables, this + one is required - the basemap will not render without it. +

+

+ Get a free key at{" "} + + cloud.maptiler.com + + , add it to .env.local as{" "} + NEXT_PUBLIC_MAPTILER_KEY, + and restart the dev server - env vars are only read on boot. +

+
+
+ + + + Signed in, but saved places or the calendar show an error + The Supabase tables for the app's own data aren't set up yet + + +

+ This means the Supabase tables exist for auth but not for the app's own + data (saved places, home location, personal calendar events). Code already + catches this: isMissingTableError(){" "} + checks for Postgres error PGRST205{" "} + ("table not found in schema cache") and shows a message pointing here + instead of a generic failure. +

+

+ Fix: in the Supabase SQL editor, run every file in{" "} + supabase/migrations/, in + filename order. See{" "} + + SELF-HOSTING.md + {" "} + for the full self-host setup. +

+

+ If you don't need auth at all, leave the Supabase env vars blank. The map, + search, filters, and calendar all work without them - you just lose sign-in, + saved places, and personal events. +

+
+
+ + + + "Near me" doesn't work + Geolocation issues, and why the pin sometimes looks off + + +
    +
  • + No prompt appeared, or it's silently doing + nothing: the browser doesn't support the Geolocation API, or the + page isn't served over HTTPS (required by most browsers outside{" "} + localhost). +
  • +
  • + "Could not read your location":{" "} + location permission was denied, or the device couldn't get a fix. Check + the browser's site permissions and try again. +
  • +
  • + Location seems wrong: some browsers fall + back to IP-based location when GPS/Wi-Fi positioning is unavailable, which + can be off by kilometers, especially on desktop. +
  • +
+
+
+ + + + The app looks out of date after a deploy + Usually a PWA caching issue + + +

+ See{" "} + + OFFLINE_CACHING.md + {" "} + for what gets cached and how to force a fresh load. +

+
+
+
+ + +

+ Open an issue at{" "} + + github.com/StudentSuite/StudyMap/issues + {" "} + or email{" "} + + studentsuite3@gmail.com + + . +

+
+
+ + ); +} From 9892f5e436cce34bd484908c6e427f5876c7a5fe Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 19:48:19 +0530 Subject: [PATCH 03/11] docs: add changelog / release notes page Parse the existing CHANGELOG.md (Keep a Changelog format) into structured data instead of duplicating its content as hand-written JSX, so /docs/changelog can never drift from the real history. Add the page and list it in the /docs index. Closes #113 --- src/app/docs/changelog/page.tsx | 94 +++++++++++++++++++++++++++++++++ src/app/docs/page.tsx | 9 +++- src/lib/changelog.test.ts | 53 +++++++++++++++++++ src/lib/changelog.ts | 59 +++++++++++++++++++++ 4 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/changelog/page.tsx create mode 100644 src/lib/changelog.test.ts create mode 100644 src/lib/changelog.ts diff --git a/src/app/docs/changelog/page.tsx b/src/app/docs/changelog/page.tsx new file mode 100644 index 0000000..bbc96e0 --- /dev/null +++ b/src/app/docs/changelog/page.tsx @@ -0,0 +1,94 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +import { site } from "@/lib/site"; +import { getChangelog } from "@/lib/changelog"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; + +export const metadata: Metadata = { + title: "Changelog", + description: "Every notable change to StudyMap, release by release.", +}; + +/** Renders a changelog bullet's inline `` `code` `` spans as elements. */ +function renderItem(text: string): ReactNode { + const parts = text.split(/(`[^`]+`)/g).filter(Boolean); + return parts.map((part, i) => + part.startsWith("`") && part.endsWith("`") ? ( + + {part.slice(1, -1)} + + ) : ( + {part} + ), + ); +} + +export default function ChangelogPage() { + const releases = getChangelog(); + + return ( + <> + + Every notable change to StudyMap, generated from{" "} + + CHANGELOG.md + {" "} + so this page never drifts from the real history. + + } + /> + +
+ {releases.map((release) => ( + + +
+ v{release.version} + + {release.date} + +
+ + Changes in version {release.version} + +
+ + {release.sections.map((section) => ( +
+

+ {section.heading} +

+
    + {section.items.map((item, i) => ( +
  • {renderItem(item)}
  • + ))} +
+
+ ))} +
+
+ ))} +
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 788ee71..0f30241 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, MapPin, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, History, MapPin, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -75,6 +75,13 @@ const DOCS = [ icon: Wrench, iconClassName: "text-primary", }, + { + href: "/docs/changelog", + title: "Changelog", + description: "Every notable change to StudyMap, release by release.", + icon: History, + iconClassName: "text-primary", + }, ]; export default function DocsIndexPage() { diff --git a/src/lib/changelog.test.ts b/src/lib/changelog.test.ts new file mode 100644 index 0000000..0009e8a --- /dev/null +++ b/src/lib/changelog.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; + +import { parseChangelog } from "@/lib/changelog"; + +const CHANGELOG = `# Changelog + +All notable changes to StudyMap are documented here. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.1.0] - 2026-07-03 + +### Added + +- New feature one. +- New feature two with \`inline code\`. + +### Fixed + +- A bug (#42). + +## [2.0.0] - 2026-07-01 + +### Removed + +- Old thing. +`; + +describe("parseChangelog", () => { + it("parses releases in document order with version and date", () => { + const releases = parseChangelog(CHANGELOG); + + expect(releases).toHaveLength(2); + expect(releases[0]).toMatchObject({ version: "2.1.0", date: "2026-07-03" }); + expect(releases[1]).toMatchObject({ version: "2.0.0", date: "2026-07-01" }); + }); + + it("groups items under their nearest ### section within a release", () => { + const [latest] = parseChangelog(CHANGELOG); + + expect(latest.sections.map((s) => s.heading)).toEqual(["Added", "Fixed"]); + expect(latest.sections[0].items).toEqual([ + "New feature one.", + "New feature two with `inline code`.", + ]); + expect(latest.sections[1].items).toEqual(["A bug (#42)."]); + }); + + it("ignores the intro paragraph before the first ## [ heading", () => { + expect(parseChangelog("Just some prose.\n\nNo headings here.")).toEqual([]); + }); +}); diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts new file mode 100644 index 0000000..59edcbf --- /dev/null +++ b/src/lib/changelog.ts @@ -0,0 +1,59 @@ +import { readFileSync } from "fs"; +import { join } from "path"; + +export interface ChangelogSection { + heading: string; + items: string[]; +} + +export interface ChangelogRelease { + version: string; + date: string; + sections: ChangelogSection[]; +} + +const VERSION_PATTERN = /^## \[(.+?)\] - (.+)$/; +const SECTION_PATTERN = /^### (.+)$/; +const ITEM_PATTERN = /^- (.+)$/; + +/** + * Parses CHANGELOG.md's Keep-a-Changelog-formatted body (`## [x.y.z] - date`, + * `### Category`, `- item`) into structured releases. Content past the intro + * paragraph and before the first `## [` heading is ignored. + */ +export function parseChangelog(markdown: string): ChangelogRelease[] { + const releases: ChangelogRelease[] = []; + let currentRelease: ChangelogRelease | null = null; + let currentSection: ChangelogSection | null = null; + + for (const line of markdown.split("\n")) { + const versionMatch = VERSION_PATTERN.exec(line); + if (versionMatch) { + currentRelease = { version: versionMatch[1], date: versionMatch[2], sections: [] }; + releases.push(currentRelease); + currentSection = null; + continue; + } + if (!currentRelease) continue; + + const sectionMatch = SECTION_PATTERN.exec(line); + if (sectionMatch) { + currentSection = { heading: sectionMatch[1], items: [] }; + currentRelease.sections.push(currentSection); + continue; + } + + const itemMatch = ITEM_PATTERN.exec(line); + if (itemMatch && currentSection) { + currentSection.items.push(itemMatch[1]); + } + } + + return releases; +} + +/** Reads and parses CHANGELOG.md from the repo root. Server-only (uses `fs`). */ +export function getChangelog(): ChangelogRelease[] { + const raw = readFileSync(join(process.cwd(), "CHANGELOG.md"), "utf8"); + return parseChangelog(raw); +} From bd639860b52e4f577711e263de9120d06006b4b7 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 19:53:23 +0530 Subject: [PATCH 04/11] docs: add map controls & keyboard shortcuts reference Document zoom/pan/cluster/search interactions, including Leaflet's actual keyboard bindings (arrows pan, +/- zoom, Escape closes a popup) verified against the leaflet source. Since #99 (cluster a11y labels, keyboard equivalent for the scroll-zoom hint) is still open, the page discloses those gaps honestly instead of overclaiming support that doesn't exist yet. Closes #112 --- src/app/docs/map-controls/page.tsx | 165 +++++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 +- 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/map-controls/page.tsx diff --git a/src/app/docs/map-controls/page.tsx b/src/app/docs/map-controls/page.tsx new file mode 100644 index 0000000..202fae7 --- /dev/null +++ b/src/app/docs/map-controls/page.tsx @@ -0,0 +1,165 @@ +import type { Metadata } from "next"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Map Controls", + description: + "Every way to zoom, pan, search, and filter the StudyMap map, including keyboard equivalents.", +}; + +export default function MapControlsPage() { + return ( + <> + + +
+ + + Zoom + Mouse, touch, and keyboard + + +
    +
  • + Scroll wheel: plain scrolling moves + the page, not the map (so the map never traps your scroll). Hold{" "} + Ctrl ( + Cmd on Mac) and scroll to zoom - + a brief hint appears if you scroll over the map without it held. +
  • +
  • + + / - buttons: the zoom control in + the map's corner works with a click or tap, no modifier needed. +
  • +
  • + Double-click: zooms in one level, + centered on the click. +
  • +
  • + Pinch: on touch devices, pinch to + zoom in or out. +
  • +
  • + Keyboard: click or tab the map to + focus it, then press + or{" "} + = to zoom in, and{" "} + - to zoom out. +
  • +
+
+
+ + + + Pan + Move around the map + + +
    +
  • + Drag: click and drag (or swipe on + touch) to move the map. +
  • +
  • + Keyboard: with the map focused, the + arrow keys pan up, down, left, and right. +
  • +
  • + Near me: the locate button flies the + map to your current location (with permission). +
  • +
+
+
+ + + + Clusters + Pie-chart circles group nearby places + + +

+ Where pins are close together, they group into a circle showing a count + and a color split by place type. Click or tap a cluster to zoom into it; + it keeps splitting into smaller clusters and eventually individual pins + as you zoom in. +

+

+ Clicking an individual pin opens a popup with its name, type, and + Directions / Copy Link actions. Press{" "} + Escape, or click elsewhere on the + map, to close it. +

+
+
+ + + + Search & filter + Narrow down what's shown + + +
    +
  • + Search box: matches place name or + city as you type. +
  • +
  • + Category chips: tap one or more to + show only those place types; tap again to remove it. +
  • +
  • + City select: narrows results to one + city at a time. +
  • +
  • + Results list: click a result to fly + the map to that pin. +
  • +
  • + Share: copies a link that reopens + the map with your current filters and selection applied. +
  • +
+
+
+
+ + +

+ Cluster markers don't yet announce their contents to screen readers, and + the Ctrl+scroll zoom hint has no keyboard-specific messaging. Both are tracked + in{" "} + + issue #99 + + . This page will be updated with the keyboard equivalents once that ships. +

+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 0f30241..d198fed 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, History, MapPin, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -27,6 +27,14 @@ const DOCS = [ icon: MapPin, iconClassName: "text-marker-sat-centre", }, + { + href: "/docs/map-controls", + title: "Map Controls", + description: + "Every way to zoom, pan, search, and filter the map, including keyboard equivalents.", + icon: MousePointerClick, + iconClassName: "text-primary", + }, { href: "/docs/calendar", title: "Using the Exam Calendar", From 775a9aee06751a4246d9828895f5f75a0f854da2 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:00:11 +0530 Subject: [PATCH 05/11] docs: add data provenance & sourcing page Document where place and exam-centre data comes from (contributor PRs vs. the bulk SAT-centre import), that verification is honor- system rather than machine-checked (validate-places.mjs only checks structure), and licensing. Distinct from /legal/disclaimer, which covers liability, not provenance. Closes #111 --- src/app/docs/data-sources/page.tsx | 176 +++++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 +- 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/data-sources/page.tsx diff --git a/src/app/docs/data-sources/page.tsx b/src/app/docs/data-sources/page.tsx new file mode 100644 index 0000000..96406df --- /dev/null +++ b/src/app/docs/data-sources/page.tsx @@ -0,0 +1,176 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { site } from "@/lib/site"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Data Sources & Provenance", + description: + "Where StudyMap's place and exam-centre data comes from, how it's verified, licensing, and known accuracy caveats.", +}; + +export default function DataSourcesPage() { + return ( + <> + + Where the map's data comes from and how it's verified. For the + legal side (liability, use-at-your-own-risk), see the{" "} + + Disclaimer + {" "} + page instead - this one is about provenance, not privacy or liability. + + } + /> + +
+ + + How places get added + Community-sourced, via GitHub + + +

+ Every place in data/{" "} + was added by a contributor through a GitHub issue or pull request, not by + StudyMap staff independently researching locations. Each record carries an{" "} + added_by{" "} + field crediting the GitHub username that submitted it. +

+

+ Contributors are asked to include, in the pull request itself (never in the + committed data): a source or citation, a Google Maps rating of 4.0 or + higher, 50 or more Google Maps reviews, and the date they verified the + place. See{" "} + + Contributing Places + {" "} + for the full process. +

+
+
+ + + + Bulk-imported SAT centres + A different path from hand-added places + + +

+ A large share of sat_centre.json{" "} + came from a bulk import rather than individual contributor submissions - + identifiable by numeric, CEEB-style id{" "} + values instead of the usual city-type-number{" "} + format. These entries did not go through the same per-place rating/review + gate as hand-added places. +

+

+ Coordinates on these entries are accurate, but a few had formatting quirks + from the import (inconsistent city casing, a placeholder-style{" "} + gmaps_link{" "} + on some records) that have been progressively cleaned up. None of this + affects in-app navigation, since Directions is always computed live from + the stored coordinates, never from the stored link. +

+
+
+ + + + Verification is honor-system, not automated + What the validator actually checks + + +

+ scripts/validate-places.mjs{" "} + (run on every pull request) checks structure - required fields, valid + types, coordinate bounds, unique IDs, well-formed links. It cannot verify + that a place is real, still open, or actually has the rating and review + count claimed in the PR description. That part relies on reviewer judgment + and contributor honesty. +

+
+
+ + + + Exam calendar dates + Sourced from the official boards + + +

+ SAT, IB, and Cambridge IGCSE session and result dates are sourced directly + from College Board, IBO, and Cambridge International, with a link back to + the source on every entry. See{" "} + + Using the Exam Calendar + {" "} + for how provisional dates are marked. +

+
+
+ + + + Licensing + MIT, code and data alike + + +

+ The entire repository, including everything in{" "} + data/, is + MIT licensed. See{" "} + + LICENSE + {" "} + on GitHub. +

+
+
+
+ + +

+ Open an issue at{" "} + + github.com/StudentSuite/StudyMap/issues + {" "} + with what changed. See the{" "} + + Disclaimer + {" "} + for why you should always verify details before visiting. +

+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index d198fed..afed96b 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, Database, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -51,6 +51,14 @@ const DOCS = [ icon: GitPullRequest, iconClassName: "text-primary", }, + { + href: "/docs/data-sources", + title: "Data Sources & Provenance", + description: + "Where the place and exam-centre data comes from, how it's verified, licensing, and known accuracy caveats.", + icon: Database, + iconClassName: "text-primary", + }, { href: "/docs/awesome-student-resources", title: "Awesome Student Resources", From 13f73656119d4d881780f74c73e2f2f853098478 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:03:56 +0530 Subject: [PATCH 06/11] docs: add FAQ page Cover the questions that don't warrant their own guide: data accuracy, reporting wrong info, why a place isn't listed, and account/auth basics. Answers stay short and link out to the fuller docs pages (data-sources, contributing, login) instead of repeating them. Closes #110 --- src/app/docs/faq/page.tsx | 106 ++++++++++++++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 +++- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/faq/page.tsx diff --git a/src/app/docs/faq/page.tsx b/src/app/docs/faq/page.tsx new file mode 100644 index 0000000..bd7f9be --- /dev/null +++ b/src/app/docs/faq/page.tsx @@ -0,0 +1,106 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { site } from "@/lib/site"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "FAQ", + description: + "Common questions about StudyMap: data accuracy, reporting wrong info, why a place isn't listed, and accounts.", +}; + +export default function FaqPage() { + return ( + <> + + +
+ +

+ Every place was added by a contributor through GitHub, with a required + 4.0+ Google Maps rating and 50+ reviews at the time of submission - but + that proof lives in the pull request, not as an automated check, so it's + honor-system rather than machine-verified. Places can also go stale + (closed, moved, hours changed) after being added. See{" "} + + Data Sources & Provenance + {" "} + for the full picture. +

+
+ + +

+ Open an issue at{" "} + + github.com/StudentSuite/StudyMap/issues + {" "} + describing what changed. No GitHub account handy? Use the{" "} + Suggest a place button on the{" "} + + map + {" "} + instead - it opens a pre-filled issue for you. +

+
+ + +

+ Either nobody has submitted it yet, or it didn't clear the quality + gate: a 4.0+ Google Maps rating, 50+ reviews, and confirmation it's + real and currently operating. Add it yourself via the{" "} + Suggest a place button on the map, or + see{" "} + + Contributing Places + {" "} + to open a pull request directly. +

+
+ + +

+ No. The map, search, filters, and exam calendar all work fully without + signing in. An account is only needed for two optional, private features: + saving your own places and a home location, and adding personal events to + the calendar - neither of which are visible to anyone else. +

+
+ + +

+ Go to{" "} + + Sign in + {" "} + and use either Google or email and password. There's nothing else to + configure. +

+
+ + +

+ Adding a place (via GitHub PR or issue) puts it on the public map for every + visitor, after the quality gate above. Saving a place (via the map's + "My places" panel, signed in) is private to your account only and + never appears on anyone else's map - it's a personal bookmark, not + a public contribution. +

+
+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index afed96b..59c8d8c 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, Database, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, CircleHelp, Database, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -83,6 +83,14 @@ const DOCS = [ icon: Puzzle, iconClassName: "text-primary", }, + { + href: "/docs/faq", + title: "FAQ", + description: + "Common questions about StudyMap: data accuracy, reporting wrong info, why a place isn't listed, and accounts.", + icon: CircleHelp, + iconClassName: "text-primary", + }, { href: "/docs/troubleshooting", title: "Troubleshooting", From 797b3a8a83ded1403271c94b86d95abe6ea70751 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:08:17 +0530 Subject: [PATCH 07/11] docs: add PWA install & offline usage page Port docs/OFFLINE_CACHING.md in-app and add platform-specific "Add to Home Screen" steps (Android, iOS, desktop) verified against the actual manifest and service worker. Repoint troubleshooting's stale PWA content card at this page instead of the raw markdown file on GitHub, so the caching guide has one home instead of two. Closes #109 --- src/app/docs/install/page.tsx | 138 ++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 +- src/app/docs/troubleshooting/page.tsx | 12 +-- 3 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 src/app/docs/install/page.tsx diff --git a/src/app/docs/install/page.tsx b/src/app/docs/install/page.tsx new file mode 100644 index 0000000..ca90a15 --- /dev/null +++ b/src/app/docs/install/page.tsx @@ -0,0 +1,138 @@ +import type { Metadata } from "next"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Install & Offline Usage", + description: + "Install StudyMap as an app and use it offline: what's cached, how the service worker works, and how to force a fresh load.", +}; + +export default function InstallPage() { + return ( + <> + + +
+ + + Add to Home Screen + Uses your browser's built-in install feature, no app store + + +
    +
  • + Android (Chrome): tap the ⋮ menu + and choose Install app (or{" "} + Add to Home screen), or tap the + install icon in the address bar if it appears. +
  • +
  • + iPhone/iPad (Safari): tap the{" "} + Share button, then{" "} + Add to Home Screen. +
  • +
  • + Desktop (Chrome/Edge): click the + install icon in the address bar, or open the browser menu and choose{" "} + Install StudyMap.... +
  • +
+

+ StudyMap doesn't show its own install button - this is entirely your + browser's native feature, driven by the app's manifest and + service worker. +

+
+
+ + + + What works offline + Whatever you've already opened, plus the map itself + + +
    +
  • The app shell (home, map, and other pages you've visited before).
  • +
  • + Map tiles you've already panned/zoomed to - cached as you browse, up + to 300 tiles, so the whole world isn't hoarded on your device. +
  • +
  • + Any page you haven't visited yet falls back to an{" "} + Offline screen instead of a browser + error. +
  • +
+

+ Signing in, saving a new place, or loading a city you've never opened + still needs a connection - only what's already cached works fully + offline. +

+
+
+ + + + How the caching works + A versioned service worker (public/sw.js) + + +

+ Two caches, each tagged with a version: an app cache (page shell, build + assets, icons) and a tile cache (map tiles from MapTiler). Page loads try + the network first and fall back to the cache when offline; static assets + and tiles are cache-first since they don't change under the same URL. +

+

+ Every deploy bumps the version, which deletes old caches once the new + service worker activates - normally a reload is enough to pick up a new + release. +

+
+
+
+ + +

Try these in order:

+
    +
  1. + Hard refresh: Cmd+Shift+R on Mac, + Ctrl+Shift+R or Ctrl+F5 on Windows/Linux. +
  2. +
  3. + Clear site data: in your browser's + dev tools (Application tab in Chrome/Edge, Storage in Firefox), clear + Service Workers, Cache Storage, and Storage for the site, then reload. +
  4. +
  5. + Reinstall the PWA: if you installed it + to your home screen, remove it and reinstall from the site. +
  6. +
+

+ If none of these help, the deploy itself likely hasn't shipped yet - check + the deployment status before assuming it's a caching issue. +

+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 59c8d8c..af68cca 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, CircleHelp, Database, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, CircleHelp, Database, Download, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -91,6 +91,14 @@ const DOCS = [ icon: CircleHelp, iconClassName: "text-primary", }, + { + href: "/docs/install", + title: "Install & Offline Usage", + description: + "Install StudyMap as an app and use it offline: what's cached and how to force a fresh load.", + icon: Download, + iconClassName: "text-primary", + }, { href: "/docs/troubleshooting", title: "Troubleshooting", diff --git a/src/app/docs/troubleshooting/page.tsx b/src/app/docs/troubleshooting/page.tsx index 4dd5c4d..a0daf85 100644 --- a/src/app/docs/troubleshooting/page.tsx +++ b/src/app/docs/troubleshooting/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import Link from "next/link"; import { site } from "@/lib/site"; import { @@ -126,14 +127,9 @@ export default function TroubleshootingPage() {

See{" "} - - OFFLINE_CACHING.md - {" "} + + Install & Offline Usage + {" "} for what gets cached and how to force a fresh load.

From 962ae4f1945d56f6343fef6db37b315e3e0e9b6f Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:14:29 +0530 Subject: [PATCH 08/11] docs: add place data schema & format page Rather than re-typing the schema a 5th time (the exact duplication #94 fixed), read data/places.schema.json directly so the field list, required/optional badges, and 6 place types can never drift from what scripts/validate-places.mjs actually enforces. Closes #108 --- src/app/docs/data-format/page.tsx | 199 ++++++++++++++++++++++++++++++ src/app/docs/page.tsx | 10 +- 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/data-format/page.tsx diff --git a/src/app/docs/data-format/page.tsx b/src/app/docs/data-format/page.tsx new file mode 100644 index 0000000..8ea6ed1 --- /dev/null +++ b/src/app/docs/data-format/page.tsx @@ -0,0 +1,199 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { PLACE_TYPES, PLACE_TYPE_LABELS } from "@/lib/types"; +import placesSchema from "../../../../data/places.schema.json"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { CodeBlock } from "@/components/docs/code-block"; +import { CalloutCard } from "@/components/docs/callout-card"; + +export const metadata: Metadata = { + title: "Place Data Format", + description: + "The JSON schema for data/places/*.json: every field, the id-prefix convention, and the 6 place types.", +}; + +const EXAMPLE = `{ + "id": "mum-library-07", + "name": "City Library, Dadar branch", + "type": "library", + "city": "mumbai", + "lat": 19.0176, + "lng": 72.8562, + "address": "Gate 2, Gokhale Road, Dadar West", + "gmaps_link": "https://maps.app.goo.gl/xxxx", + "added_by": "your-github-handle" +}`; + +const REQUIRED = new Set(placesSchema.required); + +/** Falls back to a synthesized note for fields the schema doesn't annotate with prose. */ +function fieldDescription(name: string, field: Record): string { + if (typeof field.description === "string") return field.description; + if (typeof field.minimum === "number" && typeof field.maximum === "number") { + return `Range: ${field.minimum} to ${field.maximum}.`; + } + if (name === "gmaps_link") { + return "A Google Maps link (maps.google.com, maps.app.goo.gl, or a full google.com/maps URL)."; + } + return ""; +} + +export default function DataFormatPage() { + const fields = Object.entries(placesSchema.properties); + + return ( + <> + + The JSON schema behind every entry in{" "} + + data/places/<type>.json + + . For the GitHub issue/PR process itself, see{" "} + + Contributing Places + {" "} + instead - this page is only about the data shape. + + } + /> + +
+ + + The 6 place types + One JSON file per type, under data/places/ + + +
+ + + + + + + + + + {PLACE_TYPES.map((type) => ( + + + + + + ))} + +
FileType keyLabel
{type}.json{type}{PLACE_TYPE_LABELS[type]}
+
+
+
+ + + + Fields + + Generated from data/places.schema.json, the file{" "} + + scripts/validate-places.mjs + {" "} + validates every record against + + + + {fields.map(([name, field]) => ( +
+ {name} + + {(field as { type: string }).type} + + + {REQUIRED.has(name) ? "required" : "optional"} + + {fieldDescription(name, field as Record) && ( + + {fieldDescription(name, field as Record)} + + )} +
+ ))} +

+ Nothing outside this list is allowed on a committed record - proof of + quality (source, rating, review count, verified date) goes in the pull + request only. +

+
+
+ + + + Example record + + + + + + + + + ID prefix convention + <city-prefix>-<type>-<number> + + +

+ The prefix is a short slug for the city (e.g.{" "} + mum,{" "} + thane,{" "} + jkt), + and the number increments from the highest one already used for that + prefix in the same file - never reuse an ID. +

+

+ A handful of older bulk-imported entries don't follow this exact + format (see{" "} + + Data Sources & Provenance + + ), but every new place should. +

+
+
+
+ + +

+ If a place type ever needs a new field, add it to{" "} + + data/places.schema.json + {" "} + first. This page and{" "} + + scripts/validate-places.mjs + {" "} + read that file directly, so they update automatically - only{" "} + + data/CONTRIBUTING.md + {" "} + needs a manual edit to match. +

+
+
+ + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index af68cca..af4730c 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, CircleHelp, Database, Download, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, CircleHelp, Database, Download, FileJson, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -51,6 +51,14 @@ const DOCS = [ icon: GitPullRequest, iconClassName: "text-primary", }, + { + href: "/docs/data-format", + title: "Place Data Format", + description: + "The JSON schema for data/places/*.json: every field, the id-prefix convention, and the 6 place types.", + icon: FileJson, + iconClassName: "text-primary", + }, { href: "/docs/data-sources", title: "Data Sources & Provenance", From b948afb4dab49d45fdc78f47334287bcc9805caa Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:18:56 +0530 Subject: [PATCH 09/11] docs: add architecture overview page ARCHITECTURE.md is a living document (edited twice already today) - hand-copying it into JSX would drift almost immediately. Add react-markdown + remark-gfm, finally wire up the previously-unused mdxComponents map (extended with pre/table overrides for the file's code fences and GFM tables), and render the file live via fs so the page can never go stale. Closes #107 --- package-lock.json | 1587 ++++++++++++++++++++++++++-- package.json | 2 + src/app/docs/architecture/page.tsx | 40 + src/app/docs/page.tsx | 10 +- src/components/mdx-content.tsx | 34 + 5 files changed, 1600 insertions(+), 73 deletions(-) create mode 100644 src/app/docs/architecture/page.tsx diff --git a/package-lock.json b/package-lock.json index f3d4f02..fdd32f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "react-leaflet": "^5.0.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "shadcn": "^4.10.0", "sonner": "^2.0.7", "supercluster": "^8.0.1", @@ -4627,6 +4629,15 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -4638,9 +4649,17 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -4648,6 +4667,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4672,6 +4700,21 @@ "@types/geojson": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.42", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.42.tgz", @@ -4685,7 +4728,6 @@ "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -4726,6 +4768,12 @@ "@types/geojson": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/validate-npm-package-name": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", @@ -5027,6 +5075,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "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", @@ -5892,6 +5946,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6102,6 +6166,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -6129,6 +6203,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -6250,6 +6364,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", @@ -6406,7 +6530,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -6517,6 +6640,19 @@ "dev": true, "license": "MIT" }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dedent": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", @@ -6636,9 +6772,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6659,6 +6793,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", @@ -7436,6 +7583,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -7583,6 +7740,12 @@ "express": ">= 4.11" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8232,6 +8395,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/headers-polyfill": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-5.0.1.tgz", @@ -8281,6 +8484,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -8399,6 +8612,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -8432,6 +8651,30 @@ "node": ">= 0.10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -8596,6 +8839,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -8677,6 +8930,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-in-ssh": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", @@ -9619,6 +9882,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9671,6 +9944,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9680,109 +9963,954 @@ "node": ">= 0.4" } }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/unified" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/min-indent": { + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", @@ -10469,6 +11597,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -10753,6 +11906,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -10954,6 +12117,33 @@ "react-dom": "^19.0.0" } }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -11097,6 +12287,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11786,6 +13042,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -12012,6 +13278,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringify-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", @@ -12088,6 +13368,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -12341,6 +13639,26 @@ "node": ">=20" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -12627,6 +13945,93 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -12821,6 +14226,34 @@ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.2.tgz", @@ -13411,6 +14844,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 5ba8d89..892f7b8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "react-leaflet": "^5.0.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "shadcn": "^4.10.0", "sonner": "^2.0.7", "supercluster": "^8.0.1", diff --git a/src/app/docs/architecture/page.tsx b/src/app/docs/architecture/page.tsx new file mode 100644 index 0000000..fc65b36 --- /dev/null +++ b/src/app/docs/architecture/page.tsx @@ -0,0 +1,40 @@ +import { readFileSync } from "fs"; +import { join } from "path"; +import type { Metadata } from "next"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { mdxComponents } from "@/components/mdx-content"; + +export const metadata: Metadata = { + title: "Architecture", + description: + "A map of the codebase for new contributors: folder layout, data flow, and key modules.", +}; + +/** Strips the leading `# Architecture` heading - DocsPageHeader already renders the title. */ +function stripLeadingHeading(markdown: string): string { + return markdown.replace(/^#\s.+\n+/, ""); +} + +export default function ArchitecturePage() { + const raw = readFileSync(join(process.cwd(), "ARCHITECTURE.md"), "utf8"); + const body = stripLeadingHeading(raw); + + return ( + <> + + + + {body} + + + + ); +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index af4730c..e665160 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, CircleHelp, Database, Download, FileJson, Gift, GitPullRequest, GraduationCap, History, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, CircleHelp, Database, Download, FileJson, Gift, GitPullRequest, GraduationCap, History, Layers, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -67,6 +67,14 @@ const DOCS = [ icon: Database, iconClassName: "text-primary", }, + { + href: "/docs/architecture", + title: "Architecture", + description: + "A map of the codebase for new contributors: folder layout, data flow, and key modules.", + icon: Layers, + iconClassName: "text-primary", + }, { href: "/docs/awesome-student-resources", title: "Awesome Student Resources", diff --git a/src/components/mdx-content.tsx b/src/components/mdx-content.tsx index decd2fb..5266ffd 100644 --- a/src/components/mdx-content.tsx +++ b/src/components/mdx-content.tsx @@ -74,4 +74,38 @@ export const mdxComponents = { hr: ({ className, ...props }: ComponentPropsWithoutRef<"hr">) => (
), + // Fenced code blocks land as `
`, with or without a language
+  // className depending on whether the fence declares one (` ``` ` vs
+  // ` ```ts `) - resetting the inline `code` pill styling via a `pre >
+  // code` selector here works either way, instead of trying to detect
+  // "is this inline or block" from the code element alone.
+  pre: ({ className, ...props }: ComponentPropsWithoutRef<"pre">) => (
+    
code]:rounded-none [&>code]:bg-transparent [&>code]:p-0 [&>code]:text-xs",
+        className,
+      )}
+      {...props}
+    />
+  ),
+  table: ({ className, ...props }: ComponentPropsWithoutRef<"table">) => (
+    
+ + + ), + thead: ({ className, ...props }: ComponentPropsWithoutRef<"thead">) => ( + + ), + tr: ({ className, ...props }: ComponentPropsWithoutRef<"tr">) => ( + + ), + th: ({ className, ...props }: ComponentPropsWithoutRef<"th">) => ( +
+ ), + td: ({ className, ...props }: ComponentPropsWithoutRef<"td">) => ( + + ), }; From 3fffa758ef958249f918aeb0bc3bddb908a026e5 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:21:56 +0530 Subject: [PATCH 10/11] docs: add self-hosting guide page Port SELF-HOSTING.md into StepCard/CalloutCard JSX, following the exam-centres pattern - unlike ARCHITECTURE.md or CHANGELOG.md, this file has had exactly one commit ever, so a hand-authored copy is low-risk here. Link the schema section to the new in-app /docs/data-format page instead of the GitHub markdown file. Closes #106 --- src/app/docs/page.tsx | 10 +- src/app/docs/self-hosting/page.tsx | 248 +++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 src/app/docs/self-hosting/page.tsx diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index e665160..5f6654b 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { BookOpen, CalendarDays, CircleHelp, Database, Download, FileJson, Gift, GitPullRequest, GraduationCap, History, Layers, MapPin, MousePointerClick, Puzzle, Wrench } from "lucide-react"; +import { BookOpen, CalendarDays, CircleHelp, Database, Download, FileJson, Gift, GitPullRequest, GraduationCap, History, Layers, MapPin, MousePointerClick, Puzzle, Server, Wrench } from "lucide-react"; import { PageContainer } from "@/components/layout/page-container"; import { DocsPageHeader } from "@/components/docs/docs-page-header"; @@ -75,6 +75,14 @@ const DOCS = [ icon: Layers, iconClassName: "text-primary", }, + { + href: "/docs/self-hosting", + title: "Self-Hosting Guide", + description: + "Run StudyMap for your own city: fork, configure your region and dataset, and deploy.", + icon: Server, + iconClassName: "text-primary", + }, { href: "/docs/awesome-student-resources", title: "Awesome Student Resources", diff --git a/src/app/docs/self-hosting/page.tsx b/src/app/docs/self-hosting/page.tsx new file mode 100644 index 0000000..14f1aa8 --- /dev/null +++ b/src/app/docs/self-hosting/page.tsx @@ -0,0 +1,248 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { PageContainer } from "@/components/layout/page-container"; +import { DocsPageHeader } from "@/components/docs/docs-page-header"; +import { StepCard } from "@/components/docs/step-card"; +import { CalloutCard } from "@/components/docs/callout-card"; +import { CodeBlock } from "@/components/docs/code-block"; + +export const metadata: Metadata = { + title: "Self-Hosting Guide", + description: + "Run StudyMap for your own city: fork, configure your region and dataset, and deploy. No coding required beyond editing one config file.", +}; + +const CLONE = `git clone https://github.com//.git +cd +npm install`; + +const CONFIG_COPY = `cp studymap.config.example.ts studymap.config.ts`; + +const VALIDATE = `npm run validate`; + +const ENV_COPY = `cp .env.example .env.local`; + +const DEV = `npm run dev`; + +const DEPLOY = `npx vercel`; + +const UPSTREAM = `git remote add upstream https://github.com/StudentSuite/StudyMap.git +git fetch upstream +git merge upstream/main`; + +export default function SelfHostingPage() { + return ( + <> + + +
+ +

+ Click "Use this template" at + the top of{" "} + + StudentSuite/StudyMap + {" "} + to create your own copy, then clone it. +

+ +
+ + +

+ Everything region- and data-specific lives in{" "} + studymap.config.ts{" "} + at the repo root. +

+ +

Edit it:

+
    +
  • + center:{" "} + [lat, lng]{" "} + for the initial map view +
  • +
  • + defaultZoom: + initial zoom level (11-13 works well for a metro area) +
  • +
  • + bounds: rough + coordinate box around your region, used for data validation and map fitting +
  • +
  • + cities: + display order for the city filter (anything in your data but missing here still + shows, just sorted alphabetically after) +
  • +
  • + places: swap + the sample imports for your own data/places/*.json{" "} + files +
  • +
+

+ The dataset itself follows the schema on{" "} + + Place Data Format + + . data/places.sample/{" "} + has two minimal example entries if you want to start from a clean skeleton instead of + the dataset that ships with the template. +

+

Validate as you go:

+ +
+ + + +
    +
  • + + NEXT_PUBLIC_MAPTILER_KEY + {" "} + is required for the map basemap. Free tier, no credit card, at{" "} + + cloud.maptiler.com + + . +
  • +
  • + The Supabase variables are optional. Leave them blank and the map, filters, and + calendar all work; you just won't get sign-in or the two private features in + step 5. +
  • +
+
+ + + +

+ Open{" "} + + localhost:3000/map + {" "} + and confirm your places show up in the right spot. +

+
+ + +

+ These three features (sign-in, saved custom places, personal calendar events) need a + Supabase project. Skip this whole step if you only want the public map and calendar. +

+
    +
  1. + Create a free project at{" "} + + supabase.com + + . +
  2. +
  3. + Copy its URL and anon key into{" "} + .env.local{" "} + (NEXT_PUBLIC_SUPABASE_URL,{" "} + NEXT_PUBLIC_SUPABASE_ANON_KEY). +
  4. +
  5. + In the Supabase SQL editor, run every file in{" "} + supabase/migrations/, + in filename order. Each one creates its tables with row-level security already + scoped to auth.uid(), + so users can only ever read or write their own rows. +
  6. +
  7. + Enable whichever auth providers you want (email, Google, etc.) under + Authentication > Providers. +
  8. +
+
+ + +

Deploy like any standard Next.js app, for example on Vercel:

+ +

+ or import the repo at{" "} + + vercel.com/new + {" "} + and set the same environment variables from step 3 in the project settings. +

+

+ + Static export (output: "export") + is not supported. + {" "} + Sign-in needs a live server: the OAuth callback route exchanges a code for a session + server-side, and middleware refreshes that session on every request. Both are + incompatible with a static build. Deploy to a normal server/edge runtime instead - + that's the default for Vercel and most other Next.js hosts. +

+
+
+ + +

To pull in upstream fixes, add the original repo as a remote and merge from it:

+ +

+ Your studymap.config.ts,{" "} + .env.local, and{" "} + data/places/*.json{" "} + are yours - upstream changes to shared code (map, calendar, components) merge in + without touching them, unless you've also edited those files. +

+
+
+ + ); +} From bd660d3bbcfead10d7b8e88178e816b587268a86 Mon Sep 17 00:00:00 2001 From: Yash Kewlani Date: Tue, 28 Jul 2026 20:25:15 +0530 Subject: [PATCH 11/11] Update Docs --- CHANGELOG.md | 6 ++++++ README.md | 48 ++++++++++-------------------------------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 505fb12..24f7560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - In-app "Suggest a place" flow: a public, no-account form (`suggest-place-dialog.tsx`, launched from `MapPanel`) that opens a pre-filled GitHub issue, lowering the contribution barrier below forking the repo without touching the existing PR review process. - `data/places.schema.json`: a real JSON Schema for the place record. `scripts/validate-places.mjs` now derives its valid types, required fields, coordinate bounds, and `gmaps_link` pattern from it instead of a second hardcoded copy, and flags any field not declared in the schema. +- 10 new `/docs` pages, each cross-linking instead of duplicating: `calendar`, `troubleshooting`, `map-controls`, `data-sources`, `faq`, `install`, `self-hosting` (hand-authored guides), plus `changelog` (`src/lib/changelog.ts` parses `CHANGELOG.md` directly instead of hand-copying it), `data-format` (reads `data/places.schema.json` directly), and `architecture` (renders `ARCHITECTURE.md` live via a new `react-markdown` + `remark-gfm` dependency, finally putting the previously-unused `mdxComponents` map to work). +- 3 new `/docs` pages (`awesome-student-resources`, `awesome-study-resources`, `awesome-skills-plugins`) that fetch and render the StudentSuite awesome-list READMEs at request time with a 24h ISR revalidate window, plus a search box and section-count filter chips once a list crosses ~100 entries. ### Changed @@ -19,10 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `CODE_OF_CONDUCT.md`: replaced generic boilerplate with the Contributor Covenant v2.1, naming `studentsuite3@gmail.com` as the reporting contact. - Contact email updated to `studentsuite3@gmail.com` across `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md`, `docs/TROUBLESHOOTING.md`, and the GitHub issue templates. - `/about`: maintainer attribution corrected from an individual contributor to StudentSuite. +- `README.md`: the stale, hand-maintained "Architecture" folder tree (still listing 9 retired place types) replaced with a pointer to `/docs/architecture` and `ARCHITECTURE.md`. ### Fixed - `data/places/sat_centre.json`: normalized 225 ALL-CAPS `city` values (e.g. `"SALISBURY"` -> `"salisbury"`) left over from a bulk CEEB import, which were cluttering the map's city filter dropdown. +- Reinstalled `@vercel/analytics`: it was added via an automated Vercel PR, then reverted 11 minutes later with no stated reason, silently removing Web Analytics from the site ever since. +- 3 remaining `contact@thestudentsuite.com` addresses (footer, `/about`, `/contribute`) that an earlier pass missed, updated to `studentsuite3@gmail.com`. +- Awesome-list pages: table no longer silently clips on narrow viewports (was `overflow-hidden` with auto layout; switched to `table-fixed` with explicit column widths and an `overflow-x-auto` safety net), and filter chips with long labels no longer wrap mid-word (now a horizontally scrollable row). ## [2.3.0] - 2026-07-27 diff --git a/README.md b/README.md index e987372..5398920 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ A crowdsourced map of student-important places across the Mumbai Metropolitan Re ## What it does -- **Places map**: find exam centres, libraries, book shops, stationery, internet cafes, train stations, and airports across the MMR. Filter by type and city. -- **Contribute**: add places or fix data via GitHub pull request. No account needed. +- **Places map**: find libraries, SAT centres, foreign language exam centres, government offices, airports, and other student-relevant places. Filter by type and city. SAT and foreign language centres already span several countries; other categories currently have the most coverage in India. +- **Contribute**: add places or fix data via a GitHub pull request or issue, or the in-app "Suggest a place" button on the map - no account needed either way. +- **Docs**: guides covering the map, calendar, contributing, self-hosting, and more at [/docs](https://studyymap.com/docs). - **Legal**: privacy policy, terms of service, and data disclaimer for the crowdsourced dataset. ## Quick start @@ -41,40 +42,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines. ## Architecture -``` -studymap.config.ts # region + dataset config - the one file a fork edits to retarget StudyMap -src/ - app/ - page.tsx # homepage (hero + map preview) - map/page.tsx # full interactive map - calendar/ # public exam calendar + signed-in personal events - login/ # optional sign-in (Google OAuth, email) - auth/callback/ # OAuth code exchange - contribute/page.tsx # contribution guide - legal/ # privacy, terms, disclaimer - layout.tsx # root layout (navbar, footer, theme) - components/ - home/ # Hero, MapPreview - map/ # PlacesMap, MapView, MapPanel, MyPlacesPanel, dialogs - calendar/ # PersonalEventDialog - pins/ # PinPopup - layout/ # Navbar, Footer - lib/ - places.ts # getPlaces(), filterPlaces(), getCities() - reads from studymap.config.ts - geo.ts # distance calculation, LatLng type - types.ts # PlaceType, City, Place interface - map.ts # PLACE_TYPE_COLORS, directionsUrl - share.ts # URL state encode/decode for shareable links - site.ts # site metadata, navLinks - user-places.ts # saved places + home location (signed-in only) - user-events.ts # personal calendar events (signed-in only) -data/ - places/ # 9 JSON files, one per place type -supabase/ - migrations/ # SQL for the two optional, sign-in-gated tables (run once by hand) -``` - -See [ARCHITECTURE.md](ARCHITECTURE.md) for the full breakdown. +The folder layout, data flow, and key modules are documented once in +[ARCHITECTURE.md](ARCHITECTURE.md) (also rendered live at +[/docs/architecture](https://studyymap.com/docs/architecture)) - not duplicated here, so this +README can't drift from the real thing the way its old copy of the folder tree did. ## Tech stack @@ -87,8 +58,9 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for the full breakdown. ## Running your own fork Want StudyMap for a different city? Click "Use this template" above, then follow -[SELF-HOSTING.md](SELF-HOSTING.md): set your region and dataset in one config file, optionally -wire up your own Supabase project for sign-in, and deploy. +[SELF-HOSTING.md](SELF-HOSTING.md) (also at [/docs/self-hosting](https://studyymap.com/docs/self-hosting)): +set your region and dataset in one config file, optionally wire up your own Supabase project for +sign-in, and deploy. ## Contributing